1//! Public key recovery support.
23use crate::{Error, Result};
45/// 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);
2021impl RecoveryId {
22/// Maximum supported value for the recovery ID (inclusive).
23pub const MAX: u8 = 3;
2425/// 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?
29pub const fn new(is_y_odd: bool, is_x_reduced: bool) -> Self {
30Self((is_x_reduced as u8) << 1 | (is_y_odd as u8))
31 }
3233/// Did the affine x-coordinate of 𝑘×𝑮 overflow the curve order?
34pub const fn is_x_reduced(self) -> bool {
35 (self.0 & 0b10) != 0
36}
3738/// Is the affine y-coordinate of 𝑘×𝑮 odd?
39pub const fn is_y_odd(self) -> bool {
40 (self.0 & 1) != 0
41}
4243/// Convert a `u8` into a [`RecoveryId`].
44pub const fn from_byte(byte: u8) -> Option<Self> {
45if byte <= Self::MAX {
46Some(Self(byte))
47 } else {
48None
49}
50 }
5152/// Convert this [`RecoveryId`] into a `u8`.
53pub const fn to_byte(self) -> u8 {
54self.0
55}
56}
5758impl TryFrom<u8> for RecoveryId {
59type Error = Error;
6061fn try_from(byte: u8) -> Result<Self> {
62Self::from_byte(byte).ok_or_else(Error::new)
63 }
64}
6566impl From<RecoveryId> for u8 {
67fn from(id: RecoveryId) -> u8 {
68 id.0
69}
70}
7172#[cfg(test)]
73mod tests {
74use super::RecoveryId;
7576#[test]
77fn new() {
78assert_eq!(RecoveryId::new(false, false).to_byte(), 0);
79assert_eq!(RecoveryId::new(true, false).to_byte(), 1);
80assert_eq!(RecoveryId::new(false, true).to_byte(), 2);
81assert_eq!(RecoveryId::new(true, true).to_byte(), 3);
82 }
8384#[test]
85fn try_from() {
86for n in 0u8..=3 {
87assert_eq!(RecoveryId::try_from(n).unwrap().to_byte(), n);
88 }
8990for n in 4u8..=255 {
91assert!(RecoveryId::try_from(n).is_err());
92 }
93 }
9495#[test]
96fn is_x_reduced() {
97assert_eq!(RecoveryId::try_from(0).unwrap().is_x_reduced(), false);
98assert_eq!(RecoveryId::try_from(1).unwrap().is_x_reduced(), false);
99assert_eq!(RecoveryId::try_from(2).unwrap().is_x_reduced(), true);
100assert_eq!(RecoveryId::try_from(3).unwrap().is_x_reduced(), true);
101 }
102103#[test]
104fn is_y_odd() {
105assert_eq!(RecoveryId::try_from(0).unwrap().is_y_odd(), false);
106assert_eq!(RecoveryId::try_from(1).unwrap().is_y_odd(), true);
107assert_eq!(RecoveryId::try_from(2).unwrap().is_y_odd(), false);
108assert_eq!(RecoveryId::try_from(3).unwrap().is_y_odd(), true);
109 }
110}