alloy_primitives/signature/
parity.rs

1use crate::{
2    signature::{utils::normalize_v_to_byte, SignatureError},
3    to_eip155_v, ChainId, Uint, U64,
4};
5
6/// The parity of the signature, stored as either a V value (which may include
7/// a chain id), or the y-parity.
8#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
9#[cfg_attr(feature = "arbitrary", derive(derive_arbitrary::Arbitrary, proptest_derive::Arbitrary))]
10pub enum Parity {
11    /// Explicit V value. May be EIP-155 modified.
12    Eip155(u64),
13    /// Non-EIP155. 27 or 28.
14    NonEip155(bool),
15    /// Parity flag. True for odd.
16    Parity(bool),
17}
18
19impl Default for Parity {
20    fn default() -> Self {
21        Self::Parity(false)
22    }
23}
24
25#[cfg(feature = "k256")]
26impl From<k256::ecdsa::RecoveryId> for Parity {
27    fn from(value: k256::ecdsa::RecoveryId) -> Self {
28        Self::Parity(value.is_y_odd())
29    }
30}
31
32impl TryFrom<U64> for Parity {
33    type Error = <Self as TryFrom<u64>>::Error;
34    fn try_from(value: U64) -> Result<Self, Self::Error> {
35        value.as_limbs()[0].try_into()
36    }
37}
38
39impl From<Uint<1, 1>> for Parity {
40    fn from(value: Uint<1, 1>) -> Self {
41        Self::Parity(!value.is_zero())
42    }
43}
44
45impl From<bool> for Parity {
46    fn from(value: bool) -> Self {
47        Self::Parity(value)
48    }
49}
50
51impl TryFrom<u64> for Parity {
52    type Error = SignatureError;
53
54    fn try_from(value: u64) -> Result<Self, Self::Error> {
55        match value {
56            0 | 1 => Ok(Self::Parity(value != 0)),
57            27 | 28 => Ok(Self::NonEip155((value - 27) != 0)),
58            value @ 35..=u64::MAX => Ok(Self::Eip155(value)),
59            _ => Err(SignatureError::InvalidParity(value)),
60        }
61    }
62}
63
64impl Parity {
65    /// Get the chain_id of the V value, if any.
66    pub const fn chain_id(&self) -> Option<ChainId> {
67        match *self {
68            Self::Eip155(mut v @ 35..) => {
69                if v % 2 == 0 {
70                    v -= 1;
71                }
72                v -= 35;
73                Some(v / 2)
74            }
75            _ => None,
76        }
77    }
78
79    /// Return the y-parity as a boolean.
80    pub const fn y_parity(&self) -> bool {
81        match self {
82            Self::Eip155(v @ 0..=34) => *v % 2 == 1,
83            Self::Eip155(v) => (*v ^ 1) % 2 == 1,
84            Self::NonEip155(b) | Self::Parity(b) => *b,
85        }
86    }
87
88    /// Return the y-parity as 0 or 1
89    pub const fn y_parity_byte(&self) -> u8 {
90        self.y_parity() as u8
91    }
92
93    /// Return the y-parity byte as 27 or 28,
94    /// in the case of a non-EIP155 signature.
95    pub const fn y_parity_byte_non_eip155(&self) -> Option<u8> {
96        match self {
97            Self::NonEip155(v) | Self::Parity(v) => Some(*v as u8 + 27),
98            _ => None,
99        }
100    }
101
102    /// Return the corresponding u64 V value.
103    pub const fn to_u64(&self) -> u64 {
104        match self {
105            Self::Eip155(v) => *v,
106            Self::NonEip155(b) => *b as u64 + 27,
107            Self::Parity(b) => *b as u64,
108        }
109    }
110
111    /// Inverts the parity.
112    pub const fn inverted(&self) -> Self {
113        match *self {
114            Self::Parity(b) => Self::Parity(!b),
115            Self::NonEip155(b) => Self::NonEip155(!b),
116            Self::Eip155(0) => Self::Eip155(1),
117            Self::Eip155(v @ 1..=34) => Self::Eip155(if v % 2 == 0 { v - 1 } else { v + 1 }),
118            Self::Eip155(v @ 35..) => Self::Eip155(v ^ 1),
119        }
120    }
121
122    /// Converts an EIP-155 V value to a non-EIP-155 V value.
123    ///
124    /// This is a nop for non-EIP-155 values.
125    pub const fn strip_chain_id(&self) -> Self {
126        match *self {
127            Self::Eip155(v) => Self::NonEip155(v % 2 == 1),
128            this => this,
129        }
130    }
131
132    /// Applies EIP-155 with the given chain ID.
133    pub const fn with_chain_id(self, chain_id: ChainId) -> Self {
134        let parity = match self {
135            Self::Eip155(v) => normalize_v_to_byte(v) == 1,
136            Self::NonEip155(b) | Self::Parity(b) => b,
137        };
138
139        Self::Eip155(to_eip155_v(parity as u8, chain_id))
140    }
141
142    /// Determines the recovery ID.
143    #[cfg(feature = "k256")]
144    pub const fn recid(&self) -> k256::ecdsa::RecoveryId {
145        let recid_opt = match self {
146            Self::Eip155(v) => Some(crate::signature::utils::normalize_v(*v)),
147            Self::NonEip155(b) | Self::Parity(b) => k256::ecdsa::RecoveryId::from_byte(*b as u8),
148        };
149
150        // manual unwrap for const fn
151        match recid_opt {
152            Some(recid) => recid,
153            None => unreachable!(),
154        }
155    }
156
157    /// Convert to a parity bool, dropping any V information.
158    pub const fn to_parity_bool(self) -> Self {
159        Self::Parity(self.y_parity())
160    }
161}
162
163#[cfg(feature = "rlp")]
164impl alloy_rlp::Encodable for Parity {
165    fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
166        match self {
167            Self::Eip155(v) => v.encode(out),
168            Self::NonEip155(v) => (*v as u8 + 27).encode(out),
169            Self::Parity(b) => b.encode(out),
170        }
171    }
172
173    fn length(&self) -> usize {
174        match self {
175            Self::Eip155(v) => v.length(),
176            Self::NonEip155(_) => 0u8.length(),
177            Self::Parity(v) => v.length(),
178        }
179    }
180}
181
182#[cfg(feature = "rlp")]
183impl alloy_rlp::Decodable for Parity {
184    fn decode(buf: &mut &[u8]) -> Result<Self, alloy_rlp::Error> {
185        let v = u64::decode(buf)?;
186        Ok(match v {
187            0 => Self::Parity(false),
188            1 => Self::Parity(true),
189            27 => Self::NonEip155(false),
190            28 => Self::NonEip155(true),
191            v @ 35..=u64::MAX => Self::try_from(v).expect("checked range"),
192            _ => return Err(alloy_rlp::Error::Custom("Invalid parity value")),
193        })
194    }
195}
196
197#[cfg(test)]
198mod test {
199    use crate::Parity;
200
201    #[cfg(feature = "rlp")]
202    #[test]
203    fn basic_rlp() {
204        use crate::hex;
205        use alloy_rlp::{Decodable, Encodable};
206
207        let vector = vec![
208            (hex!("01").as_slice(), Parity::Parity(true)),
209            (hex!("1b").as_slice(), Parity::NonEip155(false)),
210            (hex!("25").as_slice(), Parity::Eip155(37)),
211            (hex!("26").as_slice(), Parity::Eip155(38)),
212            (hex!("81ff").as_slice(), Parity::Eip155(255)),
213        ];
214
215        for test in vector.into_iter() {
216            let mut buf = vec![];
217            test.1.encode(&mut buf);
218            assert_eq!(test.0, buf.as_slice());
219
220            assert_eq!(test.1, Parity::decode(&mut buf.as_slice()).unwrap());
221        }
222    }
223
224    #[test]
225    fn u64_round_trip() {
226        let parity = Parity::Eip155(37);
227        assert_eq!(parity, Parity::try_from(parity.to_u64()).unwrap());
228        let parity = Parity::Eip155(38);
229        assert_eq!(parity, Parity::try_from(parity.to_u64()).unwrap());
230        let parity = Parity::NonEip155(false);
231        assert_eq!(parity, Parity::try_from(parity.to_u64()).unwrap());
232        let parity = Parity::NonEip155(true);
233        assert_eq!(parity, Parity::try_from(parity.to_u64()).unwrap());
234        let parity = Parity::Parity(false);
235        assert_eq!(parity, Parity::try_from(parity.to_u64()).unwrap());
236        let parity = Parity::Parity(true);
237        assert_eq!(parity, Parity::try_from(parity.to_u64()).unwrap());
238    }
239
240    #[test]
241    fn round_trip() {
242        // with chain ID 1
243        let p = Parity::Eip155(37);
244
245        assert_eq!(p.to_parity_bool(), Parity::Parity(false));
246
247        assert_eq!(p.with_chain_id(1), Parity::Eip155(37));
248    }
249
250    #[test]
251    fn invert_parity() {
252        let p = Parity::Eip155(0);
253        assert_eq!(p.inverted(), Parity::Eip155(1));
254
255        let p = Parity::Eip155(22);
256        assert_eq!(p.inverted(), Parity::Eip155(21));
257
258        let p = Parity::Eip155(58);
259        assert_eq!(p.inverted(), Parity::Eip155(59));
260
261        let p = Parity::NonEip155(false);
262        assert_eq!(p.inverted(), Parity::NonEip155(true));
263
264        let p = Parity::Parity(true);
265        assert_eq!(p.inverted(), Parity::Parity(false));
266    }
267}