From 9856a37633e7884ee67cdc8335dfd8626964776c Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Thu, 31 Oct 2024 22:21:17 +0400 Subject: [PATCH] chore: replace Signature with PrimitiveSignature (#796) * chore: replace Signature with PrimitiveSignature * fmt --- crates/primitives/src/lib.rs | 4 +- crates/primitives/src/signature/mod.rs | 6 - crates/primitives/src/signature/parity.rs | 280 --------- .../primitives/src/signature/primitive_sig.rs | 578 ------------------ crates/primitives/src/signature/sig.rs | 503 ++++----------- crates/primitives/src/signature/utils.rs | 44 -- 6 files changed, 121 insertions(+), 1294 deletions(-) delete mode 100644 crates/primitives/src/signature/parity.rs delete mode 100644 crates/primitives/src/signature/primitive_sig.rs diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index a4ad0ea93..b81aff933 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -54,9 +54,7 @@ mod signed; pub use signed::{BigIntConversionError, ParseSignedError, Sign, Signed}; mod signature; -pub use signature::{ - normalize_v, to_eip155_v, Parity, PrimitiveSignature, Signature, SignatureError, -}; +pub use signature::{normalize_v, to_eip155_v, Signature, SignatureError}; pub mod utils; pub use utils::{eip191_hash_message, keccak256, Keccak256}; diff --git a/crates/primitives/src/signature/mod.rs b/crates/primitives/src/signature/mod.rs index 815b32cdf..554e44770 100644 --- a/crates/primitives/src/signature/mod.rs +++ b/crates/primitives/src/signature/mod.rs @@ -1,14 +1,8 @@ mod error; pub use error::SignatureError; -mod parity; -pub use parity::Parity; - mod sig; pub use sig::Signature; mod utils; pub use utils::{normalize_v, to_eip155_v}; - -mod primitive_sig; -pub use primitive_sig::PrimitiveSignature; diff --git a/crates/primitives/src/signature/parity.rs b/crates/primitives/src/signature/parity.rs deleted file mode 100644 index b16011b0d..000000000 --- a/crates/primitives/src/signature/parity.rs +++ /dev/null @@ -1,280 +0,0 @@ -use crate::{ - signature::{utils::normalize_v_to_byte, SignatureError}, - to_eip155_v, ChainId, Uint, U64, -}; - -/// The parity of the signature, stored as either a V value (which may include -/// a chain id), or the y-parity. -#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] -#[cfg_attr(feature = "arbitrary", derive(derive_arbitrary::Arbitrary, proptest_derive::Arbitrary))] -pub enum Parity { - /// Explicit V value. May be EIP-155 modified. - Eip155(u64), - /// Non-EIP155. 27 or 28. - NonEip155(bool), - /// Parity flag. True for odd. - Parity(bool), -} - -impl Default for Parity { - fn default() -> Self { - Self::Parity(false) - } -} - -#[cfg(feature = "k256")] -impl From for Parity { - fn from(value: k256::ecdsa::RecoveryId) -> Self { - Self::Parity(value.is_y_odd()) - } -} - -impl TryFrom for Parity { - type Error = >::Error; - fn try_from(value: U64) -> Result { - value.as_limbs()[0].try_into() - } -} - -impl From> for Parity { - fn from(value: Uint<1, 1>) -> Self { - Self::Parity(!value.is_zero()) - } -} - -impl From for Parity { - fn from(value: bool) -> Self { - Self::Parity(value) - } -} - -impl TryFrom for Parity { - type Error = SignatureError; - - fn try_from(value: u64) -> Result { - match value { - 0 | 1 => Ok(Self::Parity(value != 0)), - 27 | 28 => Ok(Self::NonEip155((value - 27) != 0)), - value @ 35..=u64::MAX => Ok(Self::Eip155(value)), - _ => Err(SignatureError::InvalidParity(value)), - } - } -} - -impl Parity { - /// Returns the chain ID associated with the V value, if this signature is - /// replay-protected by [EIP-155]. - /// - /// [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 - pub const fn chain_id(&self) -> Option { - match *self { - Self::Eip155(mut v @ 35..) => { - if v % 2 == 0 { - v -= 1; - } - v -= 35; - Some(v / 2) - } - _ => None, - } - } - - /// Returns true if the signature is replay-protected by [EIP-155]. - /// - /// This is true if the V value is 35 or greater. Values less than 35 are - /// either not replay protected (27/28), or are invalid. - /// - /// [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 - pub const fn has_eip155_value(&self) -> bool { - self.chain_id().is_some() - } - - /// Return the y-parity as a boolean. - pub const fn y_parity(&self) -> bool { - match self { - Self::Eip155(v @ 0..=34) => *v % 2 == 1, - Self::Eip155(v) => (*v ^ 1) % 2 == 1, - Self::NonEip155(b) | Self::Parity(b) => *b, - } - } - - /// Return the y-parity as 0 or 1 - pub const fn y_parity_byte(&self) -> u8 { - self.y_parity() as u8 - } - - /// Return the y-parity byte as 27 or 28, - /// in the case of a non-EIP155 signature. - pub const fn y_parity_byte_non_eip155(&self) -> Option { - match self { - Self::NonEip155(v) | Self::Parity(v) => Some(*v as u8 + 27), - _ => None, - } - } - - /// Return the corresponding u64 V value. - pub const fn to_u64(&self) -> u64 { - match self { - Self::Eip155(v) => *v, - Self::NonEip155(b) => *b as u64 + 27, - Self::Parity(b) => *b as u64, - } - } - - /// Inverts the parity. - pub const fn inverted(&self) -> Self { - match *self { - Self::Parity(b) => Self::Parity(!b), - Self::NonEip155(b) => Self::NonEip155(!b), - Self::Eip155(0) => Self::Eip155(1), - Self::Eip155(v @ 1..=34) => Self::Eip155(if v % 2 == 0 { v - 1 } else { v + 1 }), - Self::Eip155(v @ 35..) => Self::Eip155(v ^ 1), - } - } - - /// Converts an EIP-155 V value to a non-EIP-155 V value. - /// - /// This is a nop for non-EIP-155 values. - pub const fn strip_chain_id(&self) -> Self { - match *self { - Self::Eip155(v) => Self::NonEip155(v % 2 == 1), - this => this, - } - } - - /// Applies EIP-155 with the given chain ID. - pub const fn with_chain_id(self, chain_id: ChainId) -> Self { - let parity = match self { - Self::Eip155(v) => normalize_v_to_byte(v) == 1, - Self::NonEip155(b) | Self::Parity(b) => b, - }; - - Self::Eip155(to_eip155_v(parity as u8, chain_id)) - } - - /// Determines the recovery ID. - #[cfg(feature = "k256")] - pub const fn recid(&self) -> k256::ecdsa::RecoveryId { - let recid_opt = match self { - Self::Eip155(v) => Some(crate::signature::utils::normalize_v_to_recid(*v)), - Self::NonEip155(b) | Self::Parity(b) => k256::ecdsa::RecoveryId::from_byte(*b as u8), - }; - - // manual unwrap for const fn - match recid_opt { - Some(recid) => recid, - None => unreachable!(), - } - } - - /// Convert to a parity bool, dropping any V information. - pub const fn to_parity_bool(self) -> Self { - Self::Parity(self.y_parity()) - } -} - -#[cfg(feature = "rlp")] -impl alloy_rlp::Encodable for Parity { - fn encode(&self, out: &mut dyn alloy_rlp::BufMut) { - match self { - Self::Eip155(v) => v.encode(out), - Self::NonEip155(v) => (*v as u8 + 27).encode(out), - Self::Parity(b) => b.encode(out), - } - } - - fn length(&self) -> usize { - match self { - Self::Eip155(v) => v.length(), - Self::NonEip155(_) => 0u8.length(), - Self::Parity(v) => v.length(), - } - } -} - -#[cfg(feature = "rlp")] -impl alloy_rlp::Decodable for Parity { - fn decode(buf: &mut &[u8]) -> Result { - let v = u64::decode(buf)?; - Ok(match v { - 0 => Self::Parity(false), - 1 => Self::Parity(true), - 27 => Self::NonEip155(false), - 28 => Self::NonEip155(true), - v @ 35..=u64::MAX => Self::try_from(v).expect("checked range"), - _ => return Err(alloy_rlp::Error::Custom("Invalid parity value")), - }) - } -} - -#[cfg(test)] -mod test { - use crate::Parity; - - #[cfg(feature = "rlp")] - #[test] - fn basic_rlp() { - use crate::hex; - use alloy_rlp::{Decodable, Encodable}; - - let vector = vec![ - (hex!("01").as_slice(), Parity::Parity(true)), - (hex!("1b").as_slice(), Parity::NonEip155(false)), - (hex!("25").as_slice(), Parity::Eip155(37)), - (hex!("26").as_slice(), Parity::Eip155(38)), - (hex!("81ff").as_slice(), Parity::Eip155(255)), - ]; - - for test in vector.into_iter() { - let mut buf = vec![]; - test.1.encode(&mut buf); - assert_eq!(test.0, buf.as_slice()); - - assert_eq!(test.1, Parity::decode(&mut buf.as_slice()).unwrap()); - } - } - - #[test] - fn u64_round_trip() { - let parity = Parity::Eip155(37); - assert_eq!(parity, Parity::try_from(parity.to_u64()).unwrap()); - let parity = Parity::Eip155(38); - assert_eq!(parity, Parity::try_from(parity.to_u64()).unwrap()); - let parity = Parity::NonEip155(false); - assert_eq!(parity, Parity::try_from(parity.to_u64()).unwrap()); - let parity = Parity::NonEip155(true); - assert_eq!(parity, Parity::try_from(parity.to_u64()).unwrap()); - let parity = Parity::Parity(false); - assert_eq!(parity, Parity::try_from(parity.to_u64()).unwrap()); - let parity = Parity::Parity(true); - assert_eq!(parity, Parity::try_from(parity.to_u64()).unwrap()); - } - - #[test] - fn round_trip() { - // with chain ID 1 - let p = Parity::Eip155(37); - - assert_eq!(p.to_parity_bool(), Parity::Parity(false)); - - assert_eq!(p.with_chain_id(1), Parity::Eip155(37)); - } - - #[test] - fn invert_parity() { - let p = Parity::Eip155(0); - assert_eq!(p.inverted(), Parity::Eip155(1)); - - let p = Parity::Eip155(22); - assert_eq!(p.inverted(), Parity::Eip155(21)); - - let p = Parity::Eip155(58); - assert_eq!(p.inverted(), Parity::Eip155(59)); - - let p = Parity::NonEip155(false); - assert_eq!(p.inverted(), Parity::NonEip155(true)); - - let p = Parity::Parity(true); - assert_eq!(p.inverted(), Parity::Parity(false)); - } -} diff --git a/crates/primitives/src/signature/primitive_sig.rs b/crates/primitives/src/signature/primitive_sig.rs deleted file mode 100644 index aa030342b..000000000 --- a/crates/primitives/src/signature/primitive_sig.rs +++ /dev/null @@ -1,578 +0,0 @@ -#![allow(unknown_lints, unnameable_types)] - -use crate::{hex, normalize_v, signature::SignatureError, uint, U256}; -use alloc::vec::Vec; -use core::str::FromStr; - -/// The order of the secp256k1 curve -const SECP256K1N_ORDER: U256 = - uint!(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141_U256); - -/// An Ethereum ECDSA signature. -#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] -pub struct PrimitiveSignature { - y_parity: bool, - r: U256, - s: U256, -} - -impl<'a> TryFrom<&'a [u8]> for PrimitiveSignature { - type Error = SignatureError; - - /// Parses a raw signature which is expected to be 65 bytes long where - /// the first 32 bytes is the `r` value, the second 32 bytes the `s` value - /// and the final byte is the `v` value in 'Electrum' notation. - fn try_from(bytes: &'a [u8]) -> Result { - if bytes.len() != 65 { - return Err(SignatureError::FromBytes("expected exactly 65 bytes")); - } - let parity = - normalize_v(bytes[64] as u64).ok_or(SignatureError::InvalidParity(bytes[64] as u64))?; - Ok(Self::from_bytes_and_parity(&bytes[..64], parity)) - } -} - -impl FromStr for PrimitiveSignature { - type Err = SignatureError; - - fn from_str(s: &str) -> Result { - let bytes = hex::decode(s)?; - Self::try_from(&bytes[..]) - } -} - -impl From<&PrimitiveSignature> for [u8; 65] { - #[inline] - fn from(value: &PrimitiveSignature) -> [u8; 65] { - value.as_bytes() - } -} - -impl From for [u8; 65] { - #[inline] - fn from(value: PrimitiveSignature) -> [u8; 65] { - value.as_bytes() - } -} - -impl From<&PrimitiveSignature> for Vec { - #[inline] - fn from(value: &PrimitiveSignature) -> Self { - value.as_bytes().to_vec() - } -} - -impl From for Vec { - #[inline] - fn from(value: PrimitiveSignature) -> Self { - value.as_bytes().to_vec() - } -} - -#[cfg(feature = "k256")] -impl From<(k256::ecdsa::Signature, k256::ecdsa::RecoveryId)> for PrimitiveSignature { - fn from(value: (k256::ecdsa::Signature, k256::ecdsa::RecoveryId)) -> Self { - Self::from_signature_and_parity(value.0, value.1.is_y_odd()) - } -} - -#[cfg(feature = "k256")] -impl TryFrom for k256::ecdsa::Signature { - type Error = k256::ecdsa::Error; - - fn try_from(value: PrimitiveSignature) -> Result { - value.to_k256() - } -} - -#[cfg(feature = "rlp")] -impl PrimitiveSignature { - /// Decode an RLP-encoded VRS signature. Accepts `decode_parity` closure which allows to - /// customize parity decoding and possibly extract additional data from it (e.g chain_id for - /// legacy signature). - pub fn decode_rlp_vrs( - buf: &mut &[u8], - decode_parity: impl FnOnce(&mut &[u8]) -> alloy_rlp::Result, - ) -> Result { - use alloy_rlp::Decodable; - - let parity = decode_parity(buf)?; - let r = Decodable::decode(buf)?; - let s = Decodable::decode(buf)?; - - Ok(Self::new(r, s, parity)) - } -} - -impl PrimitiveSignature { - #[doc(hidden)] - pub fn test_signature() -> Self { - Self::from_scalars_and_parity( - b256!("840cfc572845f5786e702984c2a582528cad4b49b2a10b9db1be7fca90058565"), - b256!("25e7109ceb98168d95b09b18bbf6b685130e0562f233877d492b94eee0c5b6d1"), - false, - ) - } - - /// Instantiate a new signature from `r`, `s`, and `v` values. - #[allow(clippy::missing_const_for_fn)] - pub fn new(r: U256, s: U256, v: bool) -> Self { - Self { r, s, y_parity: v } - } - - /// Returns the inner ECDSA signature. - #[cfg(feature = "k256")] - #[deprecated(note = "use `Signature::to_k256` instead")] - #[inline] - pub fn into_inner(self) -> k256::ecdsa::Signature { - self.try_into().expect("signature conversion failed") - } - - /// Returns the inner ECDSA signature. - #[cfg(feature = "k256")] - #[inline] - pub fn to_k256(self) -> Result { - k256::ecdsa::Signature::from_scalars(self.r.to_be_bytes(), self.s.to_be_bytes()) - } - - /// Instantiate from a signature and recovery id - #[cfg(feature = "k256")] - pub fn from_signature_and_parity(sig: k256::ecdsa::Signature, v: bool) -> Self { - let r = U256::from_be_slice(sig.r().to_bytes().as_ref()); - let s = U256::from_be_slice(sig.s().to_bytes().as_ref()); - Self { y_parity: v, r, s } - } - - /// Creates a [`PrimitiveSignature`] from the serialized `r` and `s` scalar values, which - /// comprise the ECDSA signature, alongside a `v` value, used to determine the recovery ID. - #[inline] - pub fn from_scalars_and_parity(r: crate::B256, s: crate::B256, parity: bool) -> Self { - Self::new(U256::from_be_slice(r.as_ref()), U256::from_be_slice(s.as_ref()), parity) - } - - /// Normalizes the signature into "low S" form as described in - /// [BIP 0062: Dealing with Malleability][1]. - /// - /// [1]: https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki - #[inline] - pub fn normalize_s(&self) -> Option { - let s = self.s(); - - if s > SECP256K1N_ORDER >> 1 { - Some(Self { y_parity: !self.y_parity, r: self.r, s: SECP256K1N_ORDER - s }) - } else { - None - } - } - - /// Returns the recovery ID. - #[cfg(feature = "k256")] - #[inline] - pub const fn recid(&self) -> k256::ecdsa::RecoveryId { - k256::ecdsa::RecoveryId::new(self.y_parity, false) - } - - #[cfg(feature = "k256")] - #[doc(hidden)] - #[deprecated(note = "use `Signature::recid` instead")] - pub const fn recovery_id(&self) -> k256::ecdsa::RecoveryId { - self.recid() - } - - /// Recovers an [`Address`] from this signature and the given message by first prefixing and - /// hashing the message according to [EIP-191](crate::eip191_hash_message). - /// - /// [`Address`]: crate::Address - #[cfg(feature = "k256")] - #[inline] - pub fn recover_address_from_msg>( - &self, - msg: T, - ) -> Result { - self.recover_from_msg(msg).map(|vk| crate::Address::from_public_key(&vk)) - } - - /// Recovers an [`Address`] from this signature and the given prehashed message. - /// - /// [`Address`]: crate::Address - #[cfg(feature = "k256")] - #[inline] - pub fn recover_address_from_prehash( - &self, - prehash: &crate::B256, - ) -> Result { - self.recover_from_prehash(prehash).map(|vk| crate::Address::from_public_key(&vk)) - } - - /// Recovers a [`VerifyingKey`] from this signature and the given message by first prefixing and - /// hashing the message according to [EIP-191](crate::eip191_hash_message). - /// - /// [`VerifyingKey`]: k256::ecdsa::VerifyingKey - #[cfg(feature = "k256")] - #[inline] - pub fn recover_from_msg>( - &self, - msg: T, - ) -> Result { - self.recover_from_prehash(&crate::eip191_hash_message(msg)) - } - - /// Recovers a [`VerifyingKey`] from this signature and the given prehashed message. - /// - /// [`VerifyingKey`]: k256::ecdsa::VerifyingKey - #[cfg(feature = "k256")] - #[inline] - pub fn recover_from_prehash( - &self, - prehash: &crate::B256, - ) -> Result { - let this = self.normalize_s().unwrap_or(*self); - k256::ecdsa::VerifyingKey::recover_from_prehash( - prehash.as_slice(), - &this.to_k256()?, - this.recid(), - ) - .map_err(Into::into) - } - - /// Parses a signature from a byte slice, with a v value - /// - /// # Panics - /// - /// If the slice is not at least 64 bytes long. - #[inline] - pub fn from_bytes_and_parity(bytes: &[u8], parity: bool) -> Self { - let r = U256::from_be_slice(&bytes[..32]); - let s = U256::from_be_slice(&bytes[32..64]); - Self::new(r, s, parity) - } - - /// Returns the `r` component of this signature. - #[inline] - pub const fn r(&self) -> U256 { - self.r - } - - /// Returns the `s` component of this signature. - #[inline] - pub const fn s(&self) -> U256 { - self.s - } - - /// Returns the recovery ID as a `bool`. - #[inline] - pub const fn v(&self) -> bool { - self.y_parity - } - - /// Returns the byte-array representation of this signature. - /// - /// The first 32 bytes are the `r` value, the second 32 bytes the `s` value - /// and the final byte is the `v` value in 'Electrum' notation. - #[inline] - pub fn as_bytes(&self) -> [u8; 65] { - let mut sig = [0u8; 65]; - sig[..32].copy_from_slice(&self.r.to_be_bytes::<32>()); - sig[32..64].copy_from_slice(&self.s.to_be_bytes::<32>()); - sig[64] = 27 + self.y_parity as u8; - sig - } - - /// Sets the recovery ID by normalizing a `v` value. - #[inline] - pub const fn with_parity(self, v: bool) -> Self { - Self { y_parity: v, r: self.r, s: self.s } - } - - /// Length of RLP RS field encoding - #[cfg(feature = "rlp")] - pub fn rlp_rs_len(&self) -> usize { - alloy_rlp::Encodable::length(&self.r) + alloy_rlp::Encodable::length(&self.s) - } - - /// Write R and S to an RLP buffer in progress. - #[cfg(feature = "rlp")] - pub fn write_rlp_rs(&self, out: &mut dyn alloy_rlp::BufMut) { - alloy_rlp::Encodable::encode(&self.r, out); - alloy_rlp::Encodable::encode(&self.s, out); - } - - /// Write the VRS to the output. - #[cfg(feature = "rlp")] - pub fn write_rlp_vrs(&self, out: &mut dyn alloy_rlp::BufMut, v: impl alloy_rlp::Encodable) { - v.encode(out); - self.write_rlp_rs(out); - } -} - -#[cfg(feature = "arbitrary")] -impl<'a> arbitrary::Arbitrary<'a> for PrimitiveSignature { - fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { - Ok(Self::new(u.arbitrary()?, u.arbitrary()?, u.arbitrary()?)) - } -} - -#[cfg(feature = "arbitrary")] -impl proptest::arbitrary::Arbitrary for PrimitiveSignature { - type Parameters = (); - type Strategy = proptest::strategy::Map< - <(U256, U256, bool) as proptest::arbitrary::Arbitrary>::Strategy, - fn((U256, U256, bool)) -> Self, - >; - - fn arbitrary_with((): Self::Parameters) -> Self::Strategy { - use proptest::strategy::Strategy; - proptest::arbitrary::any::<(U256, U256, bool)>() - .prop_map(|(r, s, parity)| Self::new(r, s, parity)) - } -} - -#[cfg(feature = "serde")] -mod signature_serde { - use serde::{Deserialize, Deserializer, Serialize}; - - use crate::{U256, U64}; - - use super::PrimitiveSignature; - - #[derive(Serialize, Deserialize)] - struct HumanReadableRepr { - r: U256, - s: U256, - #[serde(rename = "yParity")] - y_parity: U64, - } - - #[derive(Serialize, Deserialize)] - #[serde(transparent)] - struct NonHumanReadableRepr((U256, U256, U64)); - - impl Serialize for PrimitiveSignature { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - // if the serializer is human readable, serialize as a map, otherwise as a tuple - if serializer.is_human_readable() { - HumanReadableRepr { - y_parity: U64::from(self.y_parity as u64), - r: self.r, - s: self.s, - } - .serialize(serializer) - } else { - NonHumanReadableRepr((self.r, self.s, U64::from(self.y_parity as u64))) - .serialize(serializer) - } - } - } - - impl<'de> Deserialize<'de> for PrimitiveSignature { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let (y_parity, r, s) = if deserializer.is_human_readable() { - let HumanReadableRepr { y_parity, r, s } = <_>::deserialize(deserializer)?; - (y_parity, r, s) - } else { - let NonHumanReadableRepr((r, s, y_parity)) = <_>::deserialize(deserializer)?; - (y_parity, r, s) - }; - - if y_parity > U64::from(1) { - Err(serde::de::Error::custom("invalid y_parity")) - } else { - Ok(Self::new(r, s, y_parity == U64::from(1))) - } - } - } -} - -#[cfg(test)] -#[allow(unused_imports)] -mod tests { - use super::*; - use crate::Bytes; - use core::str::FromStr; - use hex::FromHex; - - #[cfg(feature = "rlp")] - use alloy_rlp::{Decodable, Encodable}; - - #[test] - #[cfg(feature = "k256")] - fn can_recover_tx_sender_not_normalized() { - let sig = PrimitiveSignature::from_str("48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c8041b").unwrap(); - let hash = b256!("5eb4f5a33c621f32a8622d5f943b6b102994dfe4e5aebbefe69bb1b2aa0fc93e"); - let expected = address!("0f65fe9276bc9a24ae7083ae28e2660ef72df99e"); - assert_eq!(sig.recover_address_from_prehash(&hash).unwrap(), expected); - } - - #[test] - #[cfg(feature = "k256")] - fn recover_web3_signature() { - // test vector taken from: - // https://web3js.readthedocs.io/en/v1.2.2/web3-eth-accounts.html#sign - let sig = PrimitiveSignature::from_str( - "b91467e570a6466aa9e9876cbcd013baba02900b8979d43fe208a4a4f339f5fd6007e74cd82e037b800186422fc2da167c747ef045e5d18a5f5d4300f8e1a0291c" - ).expect("could not parse signature"); - let expected = address!("2c7536E3605D9C16a7a3D7b1898e529396a65c23"); - assert_eq!(sig.recover_address_from_msg("Some data").unwrap(), expected); - } - - #[test] - fn signature_from_str() { - let s1 = PrimitiveSignature::from_str( - "0xaa231fbe0ed2b5418e6ba7c19bee2522852955ec50996c02a2fe3e71d30ddaf1645baf4823fea7cb4fcc7150842493847cfb6a6d63ab93e8ee928ee3f61f503500" - ).expect("could not parse 0x-prefixed signature"); - - let s2 = PrimitiveSignature::from_str( - "aa231fbe0ed2b5418e6ba7c19bee2522852955ec50996c02a2fe3e71d30ddaf1645baf4823fea7cb4fcc7150842493847cfb6a6d63ab93e8ee928ee3f61f503500" - ).expect("could not parse non-prefixed signature"); - - assert_eq!(s1, s2); - } - - #[cfg(feature = "serde")] - #[test] - fn deserialize_with_parity() { - let raw_signature_with_y_parity = serde_json::json!({ - "r": "0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0", - "s": "0x1a891b566d369e79b7a66eecab1e008831e22daa15f91a0a0cf4f9f28f47ee05", - "v": "0x1", - "yParity": "0x1" - }); - - let signature: PrimitiveSignature = - serde_json::from_value(raw_signature_with_y_parity).unwrap(); - - let expected = PrimitiveSignature::new( - U256::from_str("0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0") - .unwrap(), - U256::from_str("0x1a891b566d369e79b7a66eecab1e008831e22daa15f91a0a0cf4f9f28f47ee05") - .unwrap(), - true, - ); - - assert_eq!(signature, expected); - } - - #[cfg(feature = "serde")] - #[test] - fn serialize_both_parity() { - // this test should be removed if the struct moves to an enum based on tx type - let signature = PrimitiveSignature::new( - U256::from_str("0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0") - .unwrap(), - U256::from_str("0x1a891b566d369e79b7a66eecab1e008831e22daa15f91a0a0cf4f9f28f47ee05") - .unwrap(), - true, - ); - - let serialized = serde_json::to_string(&signature).unwrap(); - assert_eq!( - serialized, - r#"{"r":"0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0","s":"0x1a891b566d369e79b7a66eecab1e008831e22daa15f91a0a0cf4f9f28f47ee05","yParity":"0x1"}"# - ); - } - - #[cfg(feature = "serde")] - #[test] - fn serialize_v_only() { - // this test should be removed if the struct moves to an enum based on tx type - let signature = PrimitiveSignature::new( - U256::from_str("0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0") - .unwrap(), - U256::from_str("0x1a891b566d369e79b7a66eecab1e008831e22daa15f91a0a0cf4f9f28f47ee05") - .unwrap(), - true, - ); - - let expected = r#"{"r":"0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0","s":"0x1a891b566d369e79b7a66eecab1e008831e22daa15f91a0a0cf4f9f28f47ee05","yParity":"0x1"}"#; - - let serialized = serde_json::to_string(&signature).unwrap(); - assert_eq!(serialized, expected); - } - - #[cfg(feature = "serde")] - #[test] - fn test_bincode_roundtrip() { - let signature = PrimitiveSignature::new( - U256::from_str("0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0") - .unwrap(), - U256::from_str("0x1a891b566d369e79b7a66eecab1e008831e22daa15f91a0a0cf4f9f28f47ee05") - .unwrap(), - true, - ); - - let bin = bincode::serialize(&signature).unwrap(); - assert_eq!(bincode::deserialize::(&bin).unwrap(), signature); - } - - #[cfg(feature = "rlp")] - #[test] - fn signature_rlp_encode() { - // Given a Signature instance - let sig = PrimitiveSignature::from_str("48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c8041b").unwrap(); - - // Initialize an empty buffer - let mut buf = vec![]; - - // Encode the Signature into the buffer - sig.write_rlp_vrs(&mut buf, sig.v()); - - // Define the expected hex-encoded string - let expected = "80a048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a0efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804"; - - // Assert that the encoded buffer matches the expected hex-encoded string - assert_eq!(hex::encode(&buf), expected); - } - - #[cfg(feature = "rlp")] - #[test] - fn signature_rlp_length() { - // Given a Signature instance - let sig = PrimitiveSignature::from_str("48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c8041b").unwrap(); - - // Assert that the length of the Signature matches the expected length - assert_eq!(sig.rlp_rs_len() + sig.v().length(), 67); - } - - #[cfg(feature = "rlp")] - #[test] - fn test_rlp_vrs_len() { - let signature = PrimitiveSignature::test_signature(); - assert_eq!(67, signature.rlp_rs_len() + 1); - } - - #[cfg(feature = "rlp")] - #[test] - fn test_encode_and_decode() { - let signature = PrimitiveSignature::test_signature(); - - let mut encoded = Vec::new(); - signature.write_rlp_vrs(&mut encoded, signature.v()); - assert_eq!(encoded.len(), signature.rlp_rs_len() + signature.v().length()); - let decoded = PrimitiveSignature::decode_rlp_vrs(&mut &*encoded, bool::decode).unwrap(); - assert_eq!(signature, decoded); - } - - #[test] - fn test_as_bytes() { - let signature = PrimitiveSignature::new( - U256::from_str( - "18515461264373351373200002665853028612451056578545711640558177340181847433846", - ) - .unwrap(), - U256::from_str( - "46948507304638947509940763649030358759909902576025900602547168820602576006531", - ) - .unwrap(), - false, - ); - - let expected = Bytes::from_hex("0x28ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa63627667cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d831b").unwrap(); - assert_eq!(signature.as_bytes(), **expected); - } -} diff --git a/crates/primitives/src/signature/sig.rs b/crates/primitives/src/signature/sig.rs index f121c4730..38aca440a 100644 --- a/crates/primitives/src/signature/sig.rs +++ b/crates/primitives/src/signature/sig.rs @@ -1,10 +1,6 @@ #![allow(unknown_lints, unnameable_types)] -use crate::{ - hex, - signature::{Parity, SignatureError}, - uint, U256, -}; +use crate::{hex, normalize_v, signature::SignatureError, uint, U256}; use alloc::vec::Vec; use core::str::FromStr; @@ -15,7 +11,7 @@ const SECP256K1N_ORDER: U256 = /// An Ethereum ECDSA signature. #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] pub struct Signature { - v: Parity, + y_parity: bool, r: U256, s: U256, } @@ -30,7 +26,9 @@ impl<'a> TryFrom<&'a [u8]> for Signature { if bytes.len() != 65 { return Err(SignatureError::FromBytes("expected exactly 65 bytes")); } - Self::from_bytes_and_parity(&bytes[..64], bytes[64] as u64) + let parity = + normalize_v(bytes[64] as u64).ok_or(SignatureError::InvalidParity(bytes[64] as u64))?; + Ok(Self::from_bytes_and_parity(&bytes[..64], parity)) } } @@ -74,7 +72,7 @@ impl From for Vec { #[cfg(feature = "k256")] impl From<(k256::ecdsa::Signature, k256::ecdsa::RecoveryId)> for Signature { fn from(value: (k256::ecdsa::Signature, k256::ecdsa::RecoveryId)) -> Self { - Self::from_signature_and_parity(value.0, value.1).unwrap() + Self::from_signature_and_parity(value.0, value.1.is_y_odd()) } } @@ -89,16 +87,20 @@ impl TryFrom for k256::ecdsa::Signature { #[cfg(feature = "rlp")] impl Signature { - /// Decode an RLP-encoded VRS signature. - pub fn decode_rlp_vrs(buf: &mut &[u8]) -> Result { + /// Decode an RLP-encoded VRS signature. Accepts `decode_parity` closure which allows to + /// customize parity decoding and possibly extract additional data from it (e.g chain_id for + /// legacy signature). + pub fn decode_rlp_vrs( + buf: &mut &[u8], + decode_parity: impl FnOnce(&mut &[u8]) -> alloy_rlp::Result, + ) -> Result { use alloy_rlp::Decodable; - let parity: Parity = Decodable::decode(buf)?; + let parity = decode_parity(buf)?; let r = Decodable::decode(buf)?; let s = Decodable::decode(buf)?; - Self::from_rs_and_parity(r, s, parity) - .map_err(|_| alloy_rlp::Error::Custom("attempted to decode invalid field element")) + Ok(Self::new(r, s, parity)) } } @@ -110,13 +112,12 @@ impl Signature { b256!("25e7109ceb98168d95b09b18bbf6b685130e0562f233877d492b94eee0c5b6d1"), false, ) - .unwrap() } /// Instantiate a new signature from `r`, `s`, and `v` values. #[allow(clippy::missing_const_for_fn)] - pub fn new(r: U256, s: U256, v: Parity) -> Self { - Self { r, s, v } + pub fn new(r: U256, s: U256, v: bool) -> Self { + Self { r, s, y_parity: v } } /// Returns the inner ECDSA signature. @@ -136,28 +137,17 @@ impl Signature { /// Instantiate from a signature and recovery id #[cfg(feature = "k256")] - pub fn from_signature_and_parity, E: Into>( - sig: k256::ecdsa::Signature, - parity: T, - ) -> Result { + pub fn from_signature_and_parity(sig: k256::ecdsa::Signature, v: bool) -> Self { let r = U256::from_be_slice(sig.r().to_bytes().as_ref()); let s = U256::from_be_slice(sig.s().to_bytes().as_ref()); - Ok(Self { v: parity.try_into().map_err(Into::into)?, r, s }) + Self { y_parity: v, r, s } } - /// Creates a [`Signature`] from the serialized `r` and `s` scalar values, which comprise the - /// ECDSA signature, alongside a `v` value, used to determine the recovery ID. + /// Creates a [`Signature`] from the serialized `r` and `s` scalar values, which + /// comprise the ECDSA signature, alongside a `v` value, used to determine the recovery ID. #[inline] - pub fn from_scalars_and_parity, E: Into>( - r: crate::B256, - s: crate::B256, - parity: T, - ) -> Result { - Self::from_rs_and_parity( - U256::from_be_slice(r.as_ref()), - U256::from_be_slice(s.as_ref()), - parity, - ) + pub fn from_scalars_and_parity(r: crate::B256, s: crate::B256, parity: bool) -> Self { + Self::new(U256::from_be_slice(r.as_ref()), U256::from_be_slice(s.as_ref()), parity) } /// Normalizes the signature into "low S" form as described in @@ -169,7 +159,7 @@ impl Signature { let s = self.s(); if s > SECP256K1N_ORDER >> 1 { - Some(Self { v: self.v.inverted(), r: self.r, s: SECP256K1N_ORDER - s }) + Some(Self { y_parity: !self.y_parity, r: self.r, s: SECP256K1N_ORDER - s }) } else { None } @@ -179,7 +169,7 @@ impl Signature { #[cfg(feature = "k256")] #[inline] pub const fn recid(&self) -> k256::ecdsa::RecoveryId { - self.v.recid() + k256::ecdsa::RecoveryId::new(self.y_parity, false) } #[cfg(feature = "k256")] @@ -251,36 +241,10 @@ impl Signature { /// /// If the slice is not at least 64 bytes long. #[inline] - pub fn from_bytes_and_parity, E: Into>( - bytes: &[u8], - parity: T, - ) -> Result { + pub fn from_bytes_and_parity(bytes: &[u8], parity: bool) -> Self { let r = U256::from_be_slice(&bytes[..32]); let s = U256::from_be_slice(&bytes[32..64]); - Self::from_rs_and_parity(r, s, parity) - } - - /// Instantiate from v, r, s. - pub fn from_rs_and_parity, E: Into>( - r: U256, - s: U256, - parity: T, - ) -> Result { - Ok(Self { v: parity.try_into().map_err(Into::into)?, r, s }) - } - - /// Modifies the recovery ID by applying [EIP-155] to a `v` value. - /// - /// [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 - #[inline] - pub fn with_chain_id(self, chain_id: u64) -> Self { - self.with_parity(self.v.with_chain_id(chain_id)) - } - - /// Modifies the recovery ID by dropping any [EIP-155] v value, converting - /// to a simple parity bool. - pub fn with_parity_bool(self) -> Self { - self.with_parity(self.v.to_parity_bool()) + Self::new(r, s, parity) } /// Returns the `r` component of this signature. @@ -295,29 +259,10 @@ impl Signature { self.s } - /// Returns the recovery ID as a `u8`. - #[inline] - pub const fn v(&self) -> Parity { - self.v - } - - /// Returns the chain ID associated with the V value, if this signature is - /// replay-protected by [EIP-155]. - /// - /// [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 - pub const fn chain_id(&self) -> Option { - self.v.chain_id() - } - - /// Returns true if the signature is replay-protected by [EIP-155]. - /// - /// This is true if the V value is 35 or greater. Values less than 35 are - /// either not replay protected (27/28), or are invalid. - /// - /// [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + /// Returns the recovery ID as a `bool`. #[inline] - pub const fn has_eip155_value(&self) -> bool { - self.v().has_eip155_value() + pub const fn v(&self) -> bool { + self.y_parity } /// Returns the byte-array representation of this signature. @@ -329,14 +274,14 @@ impl Signature { let mut sig = [0u8; 65]; sig[..32].copy_from_slice(&self.r.to_be_bytes::<32>()); sig[32..64].copy_from_slice(&self.s.to_be_bytes::<32>()); - sig[64] = self.v.y_parity_byte_non_eip155().unwrap_or(self.v.y_parity_byte()); + sig[64] = 27 + self.y_parity as u8; sig } /// Sets the recovery ID by normalizing a `v` value. #[inline] - pub fn with_parity>(self, parity: T) -> Self { - Self { v: parity.into(), r: self.r, s: self.s } + pub const fn with_parity(self, v: bool) -> Self { + Self { y_parity: v, r: self.r, s: self.s } } /// Length of RLP RS field encoding @@ -345,12 +290,6 @@ impl Signature { alloy_rlp::Encodable::length(&self.r) + alloy_rlp::Encodable::length(&self.s) } - #[cfg(feature = "rlp")] - /// Length of RLP V field encoding - pub fn rlp_vrs_len(&self) -> usize { - self.rlp_rs_len() + alloy_rlp::Encodable::length(&self.v) - } - /// Write R and S to an RLP buffer in progress. #[cfg(feature = "rlp")] pub fn write_rlp_rs(&self, out: &mut dyn alloy_rlp::BufMut) { @@ -358,244 +297,95 @@ impl Signature { alloy_rlp::Encodable::encode(&self.s, out); } - /// Write the V to an RLP buffer without using EIP-155. + /// Write the VRS to the output. #[cfg(feature = "rlp")] - pub fn write_rlp_v(&self, out: &mut dyn alloy_rlp::BufMut) { - alloy_rlp::Encodable::encode(&self.v, out); - } - - /// Write the VRS to the output. The V will always be 27 or 28. - #[cfg(feature = "rlp")] - pub fn write_rlp_vrs(&self, out: &mut dyn alloy_rlp::BufMut) { - self.write_rlp_v(out); + pub fn write_rlp_vrs(&self, out: &mut dyn alloy_rlp::BufMut, v: impl alloy_rlp::Encodable) { + v.encode(out); self.write_rlp_rs(out); } } -#[cfg(feature = "rlp")] -impl alloy_rlp::Encodable for Signature { - fn encode(&self, out: &mut dyn alloy_rlp::BufMut) { - alloy_rlp::Header { list: true, payload_length: self.rlp_vrs_len() }.encode(out); - self.write_rlp_vrs(out); - } - - fn length(&self) -> usize { - let payload_length = self.rlp_vrs_len(); - payload_length + alloy_rlp::length_of_length(payload_length) - } -} - -#[cfg(feature = "rlp")] -impl alloy_rlp::Decodable for Signature { - fn decode(buf: &mut &[u8]) -> Result { - let header = alloy_rlp::Header::decode(buf)?; - let pre_len = buf.len(); - let decoded = Self::decode_rlp_vrs(buf)?; - let consumed = pre_len - buf.len(); - if consumed != header.payload_length { - return Err(alloy_rlp::Error::Custom("consumed incorrect number of bytes")); - } - - Ok(decoded) +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for Signature { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + Ok(Self::new(u.arbitrary()?, u.arbitrary()?, u.arbitrary()?)) } } -#[cfg(feature = "serde")] -impl serde::Serialize for Signature { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - // if the serializer is human readable, serialize as a map, otherwise as a tuple - if serializer.is_human_readable() { - use serde::ser::SerializeMap; - - let mut map = serializer.serialize_map(Some(3))?; - - map.serialize_entry("r", &self.r)?; - map.serialize_entry("s", &self.s)?; - - match self.v { - Parity::Eip155(v) => map.serialize_entry("v", &crate::U64::from(v))?, - Parity::NonEip155(b) => { - map.serialize_entry("v", &crate::U64::from(b as u8 + 27))? - } - Parity::Parity(true) => map.serialize_entry("yParity", "0x1")?, - Parity::Parity(false) => map.serialize_entry("yParity", "0x0")?, - } - map.end() - } else { - use serde::ser::SerializeTuple; +#[cfg(feature = "arbitrary")] +impl proptest::arbitrary::Arbitrary for Signature { + type Parameters = (); + type Strategy = proptest::strategy::Map< + <(U256, U256, bool) as proptest::arbitrary::Arbitrary>::Strategy, + fn((U256, U256, bool)) -> Self, + >; - let mut tuple = serializer.serialize_tuple(3)?; - tuple.serialize_element(&self.r)?; - tuple.serialize_element(&self.s)?; - tuple.serialize_element(&crate::U64::from(self.v.to_u64()))?; - tuple.end() - } + fn arbitrary_with((): Self::Parameters) -> Self::Strategy { + use proptest::strategy::Strategy; + proptest::arbitrary::any::<(U256, U256, bool)>() + .prop_map(|(r, s, parity)| Self::new(r, s, parity)) } } #[cfg(feature = "serde")] -impl<'de> serde::Deserialize<'de> for Signature { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - use serde::de::MapAccess; - - enum Field { - R, - S, - V, - YParity, - Unknown, - } +mod signature_serde { + use serde::{Deserialize, Deserializer, Serialize}; - impl<'de> serde::Deserialize<'de> for Field { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - struct FieldVisitor; - - impl serde::de::Visitor<'_> for FieldVisitor { - type Value = Field; - - fn expecting( - &self, - formatter: &mut core::fmt::Formatter<'_>, - ) -> core::fmt::Result { - formatter.write_str("v, r, s, or yParity") - } - - fn visit_str(self, value: &str) -> Result - where - E: serde::de::Error, - { - match value { - "r" => Ok(Field::R), - "s" => Ok(Field::S), - "v" => Ok(Field::V), - "yParity" => Ok(Field::YParity), - _ => Ok(Field::Unknown), - } - } - } - deserializer.deserialize_identifier(FieldVisitor) - } - } + use crate::{U256, U64}; - struct MapVisitor; - impl<'de> serde::de::Visitor<'de> for MapVisitor { - type Value = Signature; + use super::Signature; - fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - formatter.write_str("a JSON signature object containing r, s, and v or yParity") - } - - fn visit_map(self, mut map: A) -> Result - where - A: MapAccess<'de>, - { - let mut v: Option = None; - let mut r = None; - let mut s = None; - - while let Some(key) = map.next_key()? { - match key { - Field::V => { - let value: crate::U64 = map.next_value()?; - let parity = value.try_into().map_err(|_| { - serde::de::Error::invalid_value( - serde::de::Unexpected::Unsigned(value.as_limbs()[0]), - &"a valid v value matching the range 0 | 1 | 27 | 28 | 35..", - ) - })?; - v = Some(parity); - } - Field::YParity => { - let value: crate::Uint<1, 1> = map.next_value()?; - if v.is_none() { - v = Some(value.into()); - } - } - Field::R => { - let value: U256 = map.next_value()?; - r = Some(value); - } - Field::S => { - let value: U256 = map.next_value()?; - s = Some(value); - } - _ => {} - } + #[derive(Serialize, Deserialize)] + struct HumanReadableRepr { + r: U256, + s: U256, + #[serde(rename = "yParity")] + y_parity: U64, + } + + #[derive(Serialize, Deserialize)] + #[serde(transparent)] + struct NonHumanReadableRepr((U256, U256, U64)); + + impl Serialize for Signature { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + // if the serializer is human readable, serialize as a map, otherwise as a tuple + if serializer.is_human_readable() { + HumanReadableRepr { + y_parity: U64::from(self.y_parity as u64), + r: self.r, + s: self.s, } - - let v = v.ok_or_else(|| serde::de::Error::missing_field("v"))?; - let r = r.ok_or_else(|| serde::de::Error::missing_field("r"))?; - let s = s.ok_or_else(|| serde::de::Error::missing_field("s"))?; - - Signature::from_rs_and_parity(r, s, v).map_err(serde::de::Error::custom) + .serialize(serializer) + } else { + NonHumanReadableRepr((self.r, self.s, U64::from(self.y_parity as u64))) + .serialize(serializer) } } + } - struct TupleVisitor; - impl<'de> serde::de::Visitor<'de> for TupleVisitor { - type Value = Signature; - - fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - formatter.write_str("a tuple containing r, s, and v") - } - - fn visit_seq(self, mut seq: A) -> Result - where - A: serde::de::SeqAccess<'de>, - { - let r = seq - .next_element()? - .ok_or_else(|| serde::de::Error::invalid_length(0, &self))?; - let s = seq - .next_element()? - .ok_or_else(|| serde::de::Error::invalid_length(1, &self))?; - let v: crate::U64 = seq - .next_element()? - .ok_or_else(|| serde::de::Error::invalid_length(2, &self))?; - - Signature::from_rs_and_parity(r, s, v).map_err(serde::de::Error::custom) + impl<'de> Deserialize<'de> for Signature { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let (y_parity, r, s) = if deserializer.is_human_readable() { + let HumanReadableRepr { y_parity, r, s } = <_>::deserialize(deserializer)?; + (y_parity, r, s) + } else { + let NonHumanReadableRepr((r, s, y_parity)) = <_>::deserialize(deserializer)?; + (y_parity, r, s) + }; + + if y_parity > U64::from(1) { + Err(serde::de::Error::custom("invalid y_parity")) + } else { + Ok(Self::new(r, s, y_parity == U64::from(1))) } } - - if deserializer.is_human_readable() { - deserializer.deserialize_map(MapVisitor) - } else { - deserializer.deserialize_tuple(3, TupleVisitor) - } - } -} - -#[cfg(feature = "arbitrary")] -impl<'a> arbitrary::Arbitrary<'a> for Signature { - fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { - Self::from_rs_and_parity(u.arbitrary()?, u.arbitrary()?, u.arbitrary::()?) - .map_err(|_| arbitrary::Error::IncorrectFormat) - } -} - -#[cfg(feature = "arbitrary")] -impl proptest::arbitrary::Arbitrary for Signature { - type Parameters = (); - type Strategy = proptest::strategy::FilterMap< - <(U256, U256, Parity) as proptest::arbitrary::Arbitrary>::Strategy, - fn((U256, U256, Parity)) -> Option, - >; - - fn arbitrary_with((): Self::Parameters) -> Self::Strategy { - use proptest::strategy::Strategy; - proptest::arbitrary::any::<(U256, U256, Parity)>() - .prop_filter_map("invalid signature", |(r, s, parity)| { - Self::from_rs_and_parity(r, s, parity).ok() - }) } } @@ -644,29 +434,6 @@ mod tests { assert_eq!(s1, s2); } - #[cfg(feature = "serde")] - #[test] - fn deserialize_without_parity() { - let raw_signature_without_y_parity = r#"{ - "r":"0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0", - "s":"0x1a891b566d369e79b7a66eecab1e008831e22daa15f91a0a0cf4f9f28f47ee05", - "v":"0x1" - }"#; - - let signature: Signature = serde_json::from_str(raw_signature_without_y_parity).unwrap(); - - let expected = Signature::from_rs_and_parity( - U256::from_str("0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0") - .unwrap(), - U256::from_str("0x1a891b566d369e79b7a66eecab1e008831e22daa15f91a0a0cf4f9f28f47ee05") - .unwrap(), - 1, - ) - .unwrap(); - - assert_eq!(signature, expected); - } - #[cfg(feature = "serde")] #[test] fn deserialize_with_parity() { @@ -679,14 +446,13 @@ mod tests { let signature: Signature = serde_json::from_value(raw_signature_with_y_parity).unwrap(); - let expected = Signature::from_rs_and_parity( + let expected = Signature::new( U256::from_str("0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0") .unwrap(), U256::from_str("0x1a891b566d369e79b7a66eecab1e008831e22daa15f91a0a0cf4f9f28f47ee05") .unwrap(), - 1, - ) - .unwrap(); + true, + ); assert_eq!(signature, expected); } @@ -695,14 +461,13 @@ mod tests { #[test] fn serialize_both_parity() { // this test should be removed if the struct moves to an enum based on tx type - let signature = Signature::from_rs_and_parity( + let signature = Signature::new( U256::from_str("0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0") .unwrap(), U256::from_str("0x1a891b566d369e79b7a66eecab1e008831e22daa15f91a0a0cf4f9f28f47ee05") .unwrap(), - 1, - ) - .unwrap(); + true, + ); let serialized = serde_json::to_string(&signature).unwrap(); assert_eq!( @@ -715,14 +480,13 @@ mod tests { #[test] fn serialize_v_only() { // this test should be removed if the struct moves to an enum based on tx type - let signature = Signature::from_rs_and_parity( + let signature = Signature::new( U256::from_str("0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0") .unwrap(), U256::from_str("0x1a891b566d369e79b7a66eecab1e008831e22daa15f91a0a0cf4f9f28f47ee05") .unwrap(), - 1, - ) - .unwrap(); + true, + ); let expected = r#"{"r":"0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0","s":"0x1a891b566d369e79b7a66eecab1e008831e22daa15f91a0a0cf4f9f28f47ee05","yParity":"0x1"}"#; @@ -730,48 +494,21 @@ mod tests { assert_eq!(serialized, expected); } - #[cfg(feature = "serde")] - #[test] - fn serialize_v_hex() { - let s = r#"{"r":"0x3d43270611ffb1a10fcab841e636e355a787151969b920cf10fef48d3a61aac3","s":"0x11336489e3050e3ec017079dfe16582ce3d167559bcaa8383b665b3fda4eb963","v":"0x1b"}"#; - - let sig = serde_json::from_str::(s).unwrap(); - let serialized = serde_json::to_string(&sig).unwrap(); - assert_eq!(serialized, s); - } - #[cfg(feature = "serde")] #[test] fn test_bincode_roundtrip() { - let signature = Signature::from_rs_and_parity( + let signature = Signature::new( U256::from_str("0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0") .unwrap(), U256::from_str("0x1a891b566d369e79b7a66eecab1e008831e22daa15f91a0a0cf4f9f28f47ee05") .unwrap(), - 1, - ) - .unwrap(); + true, + ); let bin = bincode::serialize(&signature).unwrap(); assert_eq!(bincode::deserialize::(&bin).unwrap(), signature); } - #[cfg(feature = "rlp")] - #[test] - fn signature_rlp_decode() { - // Given a hex-encoded byte sequence - let bytes = crate::hex!("f84301a048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a010002cef538bc0c8e21c46080634a93e082408b0ad93f4a7207e63ec5463793d"); - - // Decode the byte sequence into a Signature instance - let result = Signature::decode(&mut &bytes[..]).unwrap(); - - // Assert that the decoded Signature matches the expected Signature - assert_eq!( - result, - Signature::from_str("48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a3664935310002cef538bc0c8e21c46080634a93e082408b0ad93f4a7207e63ec5463793d01").unwrap() - ); - } - #[cfg(feature = "rlp")] #[test] fn signature_rlp_encode() { @@ -782,10 +519,10 @@ mod tests { let mut buf = vec![]; // Encode the Signature into the buffer - sig.encode(&mut buf); + sig.write_rlp_vrs(&mut buf, sig.v()); // Define the expected hex-encoded string - let expected = "f8431ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a0efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804"; + let expected = "80a048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a0efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804"; // Assert that the encoded buffer matches the expected hex-encoded string assert_eq!(hex::encode(&buf), expected); @@ -798,14 +535,14 @@ mod tests { let sig = Signature::from_str("48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c8041b").unwrap(); // Assert that the length of the Signature matches the expected length - assert_eq!(sig.length(), 69); + assert_eq!(sig.rlp_rs_len() + sig.v().length(), 67); } #[cfg(feature = "rlp")] #[test] fn test_rlp_vrs_len() { let signature = Signature::test_signature(); - assert_eq!(67, signature.rlp_vrs_len()); + assert_eq!(67, signature.rlp_rs_len() + 1); } #[cfg(feature = "rlp")] @@ -814,9 +551,9 @@ mod tests { let signature = Signature::test_signature(); let mut encoded = Vec::new(); - signature.encode(&mut encoded); - assert_eq!(encoded.len(), signature.length()); - let decoded = Signature::decode(&mut &*encoded).unwrap(); + signature.write_rlp_vrs(&mut encoded, signature.v()); + assert_eq!(encoded.len(), signature.rlp_rs_len() + signature.v().length()); + let decoded = Signature::decode_rlp_vrs(&mut &*encoded, bool::decode).unwrap(); assert_eq!(signature, decoded); } @@ -831,7 +568,7 @@ mod tests { "46948507304638947509940763649030358759909902576025900602547168820602576006531", ) .unwrap(), - Parity::Parity(false), + false, ); let expected = Bytes::from_hex("0x28ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa63627667cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d831b").unwrap(); diff --git a/crates/primitives/src/signature/utils.rs b/crates/primitives/src/signature/utils.rs index bba5f9d03..339a6270b 100644 --- a/crates/primitives/src/signature/utils.rs +++ b/crates/primitives/src/signature/utils.rs @@ -43,43 +43,6 @@ const fn is_valid_v(v: u64) -> bool { ) } -/// Normalizes a `v` value, respecting raw, legacy, and EIP-155 values. -/// -/// This function covers the entire u64 range, producing v-values as follows: -/// - 0-26 - raw/bare. 0-3 are legal. In order to ensure that all values are covered, we also handle -/// 4-26 here by returning v % 4. -/// - 27-34 - legacy. 27-30 are legal. By legacy bitcoin convention range 27-30 signals uncompressed -/// pubkeys, while 31-34 signals compressed pubkeys. We do not respect the compression convention. -/// All Ethereum keys are uncompressed. -/// - 35+ - EIP-155. By EIP-155 convention, `v = 35 + CHAIN_ID * 2 + 0/1` We return (v-1 % 2) here. -/// -/// NB: raw and legacy support values 2, and 3, while EIP-155 does not. -/// Recovery values of 2 and 3 are unlikely to occur in practice. In the -/// vanishingly unlikely event that you encounter an EIP-155 signature with a -/// recovery value of 2 or 3, you should normalize out of band. -#[cfg(feature = "k256")] -#[inline] -pub(crate) const fn normalize_v_to_recid(v: u64) -> k256::ecdsa::RecoveryId { - let byte = normalize_v_to_byte(v); - debug_assert!(byte <= k256::ecdsa::RecoveryId::MAX); - match k256::ecdsa::RecoveryId::from_byte(byte) { - Some(recid) => recid, - None => unsafe { core::hint::unreachable_unchecked() }, - } -} - -/// Normalize the v value to a single byte. -pub(crate) const fn normalize_v_to_byte(v: u64) -> u8 { - match v { - // Case 1: raw/bare - 0..=26 => (v % 4) as u8, - // Case 2: non-EIP-155 v value - 27..=34 => ((v - 27) % 4) as u8, - // Case 3: EIP-155 V value - 35.. => ((v - 1) % 2) as u8, - } -} - #[cfg(test)] mod test { use super::*; @@ -106,11 +69,4 @@ mod test { assert_eq!(normalize_v(v), Some((v - 35) % 2 != 0)); } } - - #[test] - #[cfg(feature = "k256")] - fn normalizes_v_to_recid() { - assert_eq!(normalize_v_to_recid(27), k256::ecdsa::RecoveryId::from_byte(0).unwrap()); - assert_eq!(normalize_v_to_recid(28), k256::ecdsa::RecoveryId::from_byte(1).unwrap()); - } }