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