alloy_primitives/signature/
parity.rs1use crate::{
2 signature::{utils::normalize_v_to_byte, SignatureError},
3 to_eip155_v, ChainId, Uint, U64,
4};
5
6#[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 Eip155(u64),
14 NonEip155(bool),
16 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 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 pub const fn has_eip155_value(&self) -> bool {
90 self.chain_id().is_some()
91 }
92
93 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 pub const fn y_parity_byte(&self) -> u8 {
104 self.y_parity() as u8
105 }
106
107 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 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 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 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 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 #[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 match recid_opt {
166 Some(recid) => recid,
167 None => unreachable!(),
168 }
169 }
170
171 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 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}