Skip to content

Commit

Permalink
Revert "chore: replace Signature with PrimitiveSignature (#796)"
Browse files Browse the repository at this point in the history
This reverts commit 9856a37.
  • Loading branch information
klkvr authored Oct 31, 2024
1 parent 9856a37 commit ae2c781
Show file tree
Hide file tree
Showing 6 changed files with 1,294 additions and 121 deletions.
4 changes: 3 additions & 1 deletion crates/primitives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ mod signed;
pub use signed::{BigIntConversionError, ParseSignedError, Sign, Signed};

mod signature;
pub use signature::{normalize_v, to_eip155_v, Signature, SignatureError};
pub use signature::{
normalize_v, to_eip155_v, Parity, PrimitiveSignature, Signature, SignatureError,
};

pub mod utils;
pub use utils::{eip191_hash_message, keccak256, Keccak256};
Expand Down
6 changes: 6 additions & 0 deletions crates/primitives/src/signature/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
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;
280 changes: 280 additions & 0 deletions crates/primitives/src/signature/parity.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
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<k256::ecdsa::RecoveryId> for Parity {
fn from(value: k256::ecdsa::RecoveryId) -> Self {
Self::Parity(value.is_y_odd())
}
}

impl TryFrom<U64> for Parity {
type Error = <Self as TryFrom<u64>>::Error;
fn try_from(value: U64) -> Result<Self, Self::Error> {
value.as_limbs()[0].try_into()
}
}

impl From<Uint<1, 1>> for Parity {
fn from(value: Uint<1, 1>) -> Self {
Self::Parity(!value.is_zero())
}
}

impl From<bool> for Parity {
fn from(value: bool) -> Self {
Self::Parity(value)
}
}

impl TryFrom<u64> for Parity {
type Error = SignatureError;

fn try_from(value: u64) -> Result<Self, Self::Error> {
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<ChainId> {
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<u8> {
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<Self, alloy_rlp::Error> {
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));
}
}
Loading

0 comments on commit ae2c781

Please sign in to comment.