ecdsa/
recovery.rs

1//! Public key recovery support.
2
3use crate::{Error, Result};
4
5/// Recovery IDs, a.k.a. "recid".
6///
7/// This is an integer value `0`, `1`, `2`, or `3` included along with a
8/// signature which is used during the recovery process to select the correct
9/// public key from the signature.
10///
11/// It consists of two bits of information:
12///
13/// - low bit (0/1): was the y-coordinate of the affine point resulting from
14///   the fixed-base multiplication 𝑘×𝑮 odd? This part of the algorithm
15///   functions similar to point decompression.
16/// - hi bit (3/4): did the affine x-coordinate of 𝑘×𝑮 overflow the order of
17///   the scalar field, requiring a reduction when computing `r`?
18#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
19pub struct RecoveryId(u8);
20
21impl RecoveryId {
22    /// Maximum supported value for the recovery ID (inclusive).
23    pub const MAX: u8 = 3;
24
25    /// Create a new [`RecoveryId`] from the following 1-bit arguments:
26    ///
27    /// - `is_y_odd`: is the affine y-coordinate of 𝑘×𝑮 odd?
28    /// - `is_x_reduced`: did the affine x-coordinate of 𝑘×𝑮 overflow the curve order?
29    pub const fn new(is_y_odd: bool, is_x_reduced: bool) -> Self {
30        Self((is_x_reduced as u8) << 1 | (is_y_odd as u8))
31    }
32
33    /// Did the affine x-coordinate of 𝑘×𝑮 overflow the curve order?
34    pub const fn is_x_reduced(self) -> bool {
35        (self.0 & 0b10) != 0
36    }
37
38    /// Is the affine y-coordinate of 𝑘×𝑮 odd?
39    pub const fn is_y_odd(self) -> bool {
40        (self.0 & 1) != 0
41    }
42
43    /// Convert a `u8` into a [`RecoveryId`].
44    pub const fn from_byte(byte: u8) -> Option<Self> {
45        if byte <= Self::MAX {
46            Some(Self(byte))
47        } else {
48            None
49        }
50    }
51
52    /// Convert this [`RecoveryId`] into a `u8`.
53    pub const fn to_byte(self) -> u8 {
54        self.0
55    }
56}
57
58impl TryFrom<u8> for RecoveryId {
59    type Error = Error;
60
61    fn try_from(byte: u8) -> Result<Self> {
62        Self::from_byte(byte).ok_or_else(Error::new)
63    }
64}
65
66impl From<RecoveryId> for u8 {
67    fn from(id: RecoveryId) -> u8 {
68        id.0
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::RecoveryId;
75
76    #[test]
77    fn new() {
78        assert_eq!(RecoveryId::new(false, false).to_byte(), 0);
79        assert_eq!(RecoveryId::new(true, false).to_byte(), 1);
80        assert_eq!(RecoveryId::new(false, true).to_byte(), 2);
81        assert_eq!(RecoveryId::new(true, true).to_byte(), 3);
82    }
83
84    #[test]
85    fn try_from() {
86        for n in 0u8..=3 {
87            assert_eq!(RecoveryId::try_from(n).unwrap().to_byte(), n);
88        }
89
90        for n in 4u8..=255 {
91            assert!(RecoveryId::try_from(n).is_err());
92        }
93    }
94
95    #[test]
96    fn is_x_reduced() {
97        assert_eq!(RecoveryId::try_from(0).unwrap().is_x_reduced(), false);
98        assert_eq!(RecoveryId::try_from(1).unwrap().is_x_reduced(), false);
99        assert_eq!(RecoveryId::try_from(2).unwrap().is_x_reduced(), true);
100        assert_eq!(RecoveryId::try_from(3).unwrap().is_x_reduced(), true);
101    }
102
103    #[test]
104    fn is_y_odd() {
105        assert_eq!(RecoveryId::try_from(0).unwrap().is_y_odd(), false);
106        assert_eq!(RecoveryId::try_from(1).unwrap().is_y_odd(), true);
107        assert_eq!(RecoveryId::try_from(2).unwrap().is_y_odd(), false);
108        assert_eq!(RecoveryId::try_from(3).unwrap().is_y_odd(), true);
109    }
110}