Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: move all secp256k1 helpers to primitives-traits #13363

Merged
merged 4 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 47 additions & 9 deletions crates/primitives-traits/src/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,47 @@ pub const SECP256K1N_HALF: U256 = U256::from_be_bytes([
]);

/// Secp256k1 utility functions.
#[cfg(feature = "secp256k1")]
pub mod secp256k1 {
pub use super::impl_secp256k1::*;
}
use super::*;
use revm_primitives::{Address, B256};

/// Secp256k1 utility functions.
#[cfg(not(feature = "secp256k1"))]
pub mod secp256k1 {
pub use super::impl_k256::*;
#[cfg(not(feature = "secp256k1"))]
use super::impl_k256 as imp;
#[cfg(feature = "secp256k1")]
use super::impl_secp256k1 as imp;

pub use imp::{public_key_to_address, sign_message};

/// Recover signer from message hash, _without ensuring that the signature has a low `s`
/// value_.
///
/// Using this for signature validation will succeed, even if the signature is malleable or not
/// compliant with EIP-2. This is provided for compatibility with old signatures which have
/// large `s` values.
pub fn recover_signer_unchecked(signature: &Signature, hash: B256) -> Option<Address> {
let mut sig: [u8; 65] = [0; 65];

sig[0..32].copy_from_slice(&signature.r().to_be_bytes::<32>());
sig[32..64].copy_from_slice(&signature.s().to_be_bytes::<32>());
sig[64] = signature.v() as u8;

// NOTE: we are removing error from underlying crypto library as it will restrain primitive
// errors and we care only if recovery is passing or not.
imp::recover_signer_unchecked(&sig, &hash.0).ok()
}

/// Recover signer address from message hash. This ensures that the signature S value is
/// greater than `secp256k1n / 2`, as specified in
/// [EIP-2](https://eips.ethereum.org/EIPS/eip-2).
///
/// If the S value is too large, then this will return `None`
pub fn recover_signer(signature: &Signature, hash: B256) -> Option<Address> {
if signature.s() > SECP256K1N_HALF {
return None
}

recover_signer_unchecked(signature, hash)
}
}

#[cfg(feature = "secp256k1")]
Expand All @@ -41,7 +73,10 @@ mod impl_secp256k1 {
///
/// This does not ensure that the `s` value in the signature is low, and _just_ wraps the
/// underlying secp256k1 library.
pub fn recover_signer_unchecked(sig: &[u8; 65], msg: &[u8; 32]) -> Result<Address, Error> {
pub(crate) fn recover_signer_unchecked(
sig: &[u8; 65],
msg: &[u8; 32],
) -> Result<Address, Error> {
let sig =
RecoverableSignature::from_compact(&sig[0..64], RecoveryId::from_i32(sig[64] as i32)?)?;

Expand Down Expand Up @@ -87,7 +122,10 @@ mod impl_k256 {
///
/// This does not ensure that the `s` value in the signature is low, and _just_ wraps the
/// underlying secp256k1 library.
pub fn recover_signer_unchecked(sig: &[u8; 65], msg: &[u8; 32]) -> Result<Address, Error> {
pub(crate) fn recover_signer_unchecked(
sig: &[u8; 65],
msg: &[u8; 32],
) -> Result<Address, Error> {
let mut signature = k256::ecdsa::Signature::from_slice(&sig[0..64])?;
let mut recid = sig[64];

Expand Down
43 changes: 5 additions & 38 deletions crates/primitives/src/transaction/signature.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::transaction::util::secp256k1;
use alloy_consensus::transaction::from_eip155_value;
use alloy_primitives::{Address, PrimitiveSignature as Signature, B256, U256};
use alloy_primitives::{PrimitiveSignature as Signature, U256};
use alloy_rlp::Decodable;
use reth_primitives_traits::crypto::SECP256K1N_HALF;

pub use reth_primitives_traits::crypto::secp256k1::{recover_signer, recover_signer_unchecked};

pub(crate) fn decode_with_eip155_chain_id(
buf: &mut &[u8],
Expand All @@ -26,43 +26,10 @@ pub(crate) fn decode_with_eip155_chain_id(
Ok((Signature::new(r, s, parity), chain_id))
}

/// Recover signer from message hash, _without ensuring that the signature has a low `s`
/// value_.
///
/// Using this for signature validation will succeed, even if the signature is malleable or not
/// compliant with EIP-2. This is provided for compatibility with old signatures which have
/// large `s` values.
pub fn recover_signer_unchecked(signature: &Signature, hash: B256) -> Option<Address> {
let mut sig: [u8; 65] = [0; 65];

sig[0..32].copy_from_slice(&signature.r().to_be_bytes::<32>());
sig[32..64].copy_from_slice(&signature.s().to_be_bytes::<32>());
sig[64] = signature.v() as u8;

// NOTE: we are removing error from underlying crypto library as it will restrain primitive
// errors and we care only if recovery is passing or not.
secp256k1::recover_signer_unchecked(&sig, &hash.0).ok()
}

/// Recover signer address from message hash. This ensures that the signature S value is
/// greater than `secp256k1n / 2`, as specified in
/// [EIP-2](https://eips.ethereum.org/EIPS/eip-2).
///
/// If the S value is too large, then this will return `None`
pub fn recover_signer(signature: &Signature, hash: B256) -> Option<Address> {
if signature.s() > SECP256K1N_HALF {
return None
}

recover_signer_unchecked(signature, hash)
}

#[cfg(test)]
mod tests {
use crate::transaction::signature::{
recover_signer, recover_signer_unchecked, SECP256K1N_HALF,
};
use alloy_eips::eip2718::Decodable2718;
use crate::transaction::signature::{recover_signer, recover_signer_unchecked};
use alloy_eips::{eip2718::Decodable2718, eip7702::constants::SECP256K1N_HALF};
use alloy_primitives::{hex, Address, PrimitiveSignature as Signature, B256, U256};
use reth_primitives_traits::SignedTransaction;
use std::str::FromStr;
Expand Down
Loading