alloy_primitives/signature/
parity.rs1use crate::{
2 signature::{utils::normalize_v_to_byte, SignatureError},
3 to_eip155_v, ChainId, Uint, U64,
4};
5
6#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
9#[cfg_attr(feature = "arbitrary", derive(derive_arbitrary::Arbitrary, proptest_derive::Arbitrary))]
10pub enum Parity {
11 Eip155(u64),
13 NonEip155(bool),
15 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 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 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 pub const fn y_parity_byte(&self) -> u8 {
90 self.y_parity() as u8
91 }
92
93 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 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 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 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 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 #[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 match recid_opt {
152 Some(recid) => recid,
153 None => unreachable!(),
154 }
155 }
156
157 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 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}