mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
feat(primitives): k256 crate fallback for secp256k1 module (#9989)
This commit is contained in:
8
.github/assets/check_wasm.sh
vendored
8
.github/assets/check_wasm.sh
vendored
@ -4,18 +4,18 @@ set +e # Disable immediate exit on error
|
||||
# Array of crates
|
||||
wasm_crates=(
|
||||
# The following were confirmed not working in the past, but could be enabled if issues have been resolved
|
||||
# reth-consensus
|
||||
# reth-db
|
||||
# reth-primitives
|
||||
# reth-revm
|
||||
# reth-evm
|
||||
# reth-evm-ethereum
|
||||
# reth-consensus
|
||||
# reth-revm
|
||||
# The following are confirmed working
|
||||
reth-codecs
|
||||
reth-errors
|
||||
reth-ethereum-forks
|
||||
reth-network-peers
|
||||
reth-primitives
|
||||
reth-primitives-traits
|
||||
reth-codecs
|
||||
)
|
||||
|
||||
# Array to hold the results
|
||||
|
||||
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -8066,6 +8066,7 @@ dependencies = [
|
||||
"c-kzg",
|
||||
"criterion",
|
||||
"derive_more",
|
||||
"k256",
|
||||
"modular-bitfield",
|
||||
"nybbles",
|
||||
"once_cell",
|
||||
|
||||
@ -523,6 +523,7 @@ secp256k1 = { version = "0.29", default-features = false, features = [
|
||||
"global-context",
|
||||
"recovery",
|
||||
] }
|
||||
k256 = { version = "0.13", default-features = false, features = ["ecdsa"] }
|
||||
enr = { version = "0.12.1", default-features = false }
|
||||
|
||||
# for eip-4844
|
||||
|
||||
@ -15,7 +15,7 @@ workspace = true
|
||||
reth-chainspec.workspace = true
|
||||
reth-ethereum-forks.workspace = true
|
||||
reth-evm.workspace = true
|
||||
reth-primitives.workspace = true
|
||||
reth-primitives = { workspace = true, features = ["reth-codec"] }
|
||||
reth-revm.workspace = true
|
||||
reth-ethereum-consensus.workspace = true
|
||||
reth-prune-types.workspace = true
|
||||
@ -31,6 +31,7 @@ alloy-sol-types.workspace = true
|
||||
[dev-dependencies]
|
||||
reth-testing-utils.workspace = true
|
||||
reth-revm = { workspace = true, features = ["test-utils"] }
|
||||
reth-primitives = { workspace = true, features = ["secp256k1"] }
|
||||
secp256k1.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
||||
|
||||
@ -21,7 +21,7 @@ reth-node-api.workspace = true
|
||||
reth-node-core.workspace = true
|
||||
reth-payload-builder.workspace = true
|
||||
reth-primitives-traits.workspace = true
|
||||
reth-primitives.workspace = true
|
||||
reth-primitives = { workspace = true, features = ["secp256k1"] }
|
||||
reth-provider.workspace = true
|
||||
reth-prune-types.workspace = true
|
||||
reth-revm.workspace = true
|
||||
|
||||
@ -15,7 +15,7 @@ workspace = true
|
||||
# reth
|
||||
reth-chainspec.workspace = true
|
||||
reth-fs-util.workspace = true
|
||||
reth-primitives.workspace = true
|
||||
reth-primitives = { workspace = true, features = ["secp256k1"] }
|
||||
reth-net-banlist.workspace = true
|
||||
reth-network-api.workspace = true
|
||||
reth-network-p2p.workspace = true
|
||||
|
||||
@ -33,7 +33,8 @@ secp256k1 = { workspace = true, features = [
|
||||
"global-context",
|
||||
"recovery",
|
||||
"rand",
|
||||
] }
|
||||
], optional = true }
|
||||
k256.workspace = true
|
||||
# for eip-4844
|
||||
c-kzg = { workspace = true, features = ["serde"], optional = true }
|
||||
|
||||
@ -81,10 +82,9 @@ pprof = { workspace = true, features = [
|
||||
"frame-pointer",
|
||||
"criterion",
|
||||
] }
|
||||
secp256k1.workspace = true
|
||||
|
||||
[features]
|
||||
default = ["c-kzg", "alloy-compat", "std", "reth-codec"]
|
||||
default = ["c-kzg", "alloy-compat", "std", "reth-codec", "secp256k1"]
|
||||
std = ["thiserror-no-std?/std", "reth-primitives-traits/std"]
|
||||
reth-codec = ["dep:reth-codecs", "dep:zstd", "dep:modular-bitfield"]
|
||||
asm-keccak = ["alloy-primitives/asm-keccak"]
|
||||
@ -100,6 +100,7 @@ arbitrary = [
|
||||
"dep:proptest",
|
||||
"reth-codec",
|
||||
]
|
||||
secp256k1 = ["dep:secp256k1"]
|
||||
c-kzg = ["dep:c-kzg", "revm-primitives/c-kzg", "dep:tempfile", "alloy-eips/kzg", "dep:thiserror-no-std"]
|
||||
optimism = [
|
||||
"reth-chainspec/optimism",
|
||||
|
||||
@ -1653,18 +1653,14 @@ impl<T> WithEncoded<Option<T>> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
hex, sign_message,
|
||||
transaction::{
|
||||
signature::Signature, TxEip1559, TxKind, TxLegacy, PARALLEL_SENDER_RECOVERY_THRESHOLD,
|
||||
},
|
||||
hex,
|
||||
transaction::{signature::Signature, TxEip1559, TxKind, TxLegacy},
|
||||
Address, Bytes, Transaction, TransactionSigned, TransactionSignedEcRecovered,
|
||||
TransactionSignedNoHash, B256, U256,
|
||||
};
|
||||
use alloy_primitives::{address, b256, bytes};
|
||||
use alloy_rlp::{Decodable, Encodable, Error as RlpError};
|
||||
use proptest_arbitrary_interop::arb;
|
||||
use reth_codecs::Compact;
|
||||
use secp256k1::{Keypair, Secp256k1};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
@ -1934,23 +1930,27 @@ mod tests {
|
||||
assert_eq!(data.as_slice(), b.as_slice());
|
||||
}
|
||||
|
||||
#[cfg(feature = "secp256k1")]
|
||||
proptest::proptest! {
|
||||
#![proptest_config(proptest::prelude::ProptestConfig::with_cases(1))]
|
||||
|
||||
#[test]
|
||||
fn test_parallel_recovery_order(txes in proptest::collection::vec(arb::<Transaction>(), *PARALLEL_SENDER_RECOVERY_THRESHOLD * 5)) {
|
||||
fn test_parallel_recovery_order(txes in proptest::collection::vec(
|
||||
proptest_arbitrary_interop::arb::<Transaction>(),
|
||||
*crate::transaction::PARALLEL_SENDER_RECOVERY_THRESHOLD * 5
|
||||
)) {
|
||||
let mut rng =rand::thread_rng();
|
||||
let secp = Secp256k1::new();
|
||||
let secp = secp256k1::Secp256k1::new();
|
||||
let txes: Vec<TransactionSigned> = txes.into_iter().map(|mut tx| {
|
||||
if let Some(chain_id) = tx.chain_id() {
|
||||
// Otherwise we might overflow when calculating `v` on `recalculate_hash`
|
||||
tx.set_chain_id(chain_id % (u64::MAX / 2 - 36));
|
||||
}
|
||||
|
||||
let key_pair = Keypair::new(&secp, &mut rng);
|
||||
let key_pair = secp256k1::Keypair::new(&secp, &mut rng);
|
||||
|
||||
let signature =
|
||||
sign_message(B256::from_slice(&key_pair.secret_bytes()[..]), tx.signature_hash()).unwrap();
|
||||
crate::sign_message(B256::from_slice(&key_pair.secret_bytes()[..]), tx.signature_hash()).unwrap();
|
||||
|
||||
TransactionSigned::from_transaction_and_signature(tx, signature)
|
||||
}).collect();
|
||||
|
||||
@ -1,12 +1,26 @@
|
||||
use crate::{Address, Signature};
|
||||
use revm_primitives::B256;
|
||||
|
||||
#[cfg(feature = "secp256k1")]
|
||||
pub(crate) mod secp256k1 {
|
||||
pub use super::impl_secp256k1::*;
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "secp256k1"))]
|
||||
pub(crate) mod secp256k1 {
|
||||
pub use super::impl_k256::*;
|
||||
}
|
||||
|
||||
#[cfg(feature = "secp256k1")]
|
||||
mod impl_secp256k1 {
|
||||
use super::*;
|
||||
use crate::{keccak256, Address, Signature};
|
||||
use crate::keccak256;
|
||||
pub(crate) use ::secp256k1::Error;
|
||||
use ::secp256k1::{
|
||||
ecdsa::{RecoverableSignature, RecoveryId},
|
||||
Message, PublicKey, SecretKey, SECP256K1,
|
||||
};
|
||||
use revm_primitives::{B256, U256};
|
||||
use revm_primitives::U256;
|
||||
|
||||
/// Recovers the address of the sender using secp256k1 pubkey recovery.
|
||||
///
|
||||
@ -24,7 +38,7 @@ pub(crate) mod secp256k1 {
|
||||
|
||||
/// Signs message with the given secret key.
|
||||
/// Returns the corresponding signature.
|
||||
pub fn sign_message(secret: B256, message: B256) -> Result<Signature, secp256k1::Error> {
|
||||
pub fn sign_message(secret: B256, message: B256) -> Result<Signature, Error> {
|
||||
let sec = SecretKey::from_slice(secret.as_ref())?;
|
||||
let s = SECP256K1.sign_ecdsa_recoverable(&Message::from_digest(message.0), &sec);
|
||||
let (rec_id, data) = s.serialize_compact();
|
||||
@ -47,17 +61,150 @@ pub(crate) mod secp256k1 {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "secp256k1", allow(unused, unreachable_pub))]
|
||||
mod impl_k256 {
|
||||
use super::*;
|
||||
use crate::keccak256;
|
||||
pub(crate) use k256::ecdsa::Error;
|
||||
use k256::ecdsa::{RecoveryId, SigningKey, VerifyingKey};
|
||||
use revm_primitives::U256;
|
||||
|
||||
/// Recovers the address of the sender using secp256k1 pubkey recovery.
|
||||
///
|
||||
/// Converts the public key into an ethereum address by hashing the public key with keccak256.
|
||||
///
|
||||
/// 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> {
|
||||
let mut signature = k256::ecdsa::Signature::from_slice(&sig[0..64])?;
|
||||
let mut recid = sig[64];
|
||||
|
||||
// normalize signature and flip recovery id if needed.
|
||||
if let Some(sig_normalized) = signature.normalize_s() {
|
||||
signature = sig_normalized;
|
||||
recid ^= 1;
|
||||
}
|
||||
let recid = RecoveryId::from_byte(recid).expect("recovery ID is valid");
|
||||
|
||||
// recover key
|
||||
let recovered_key = VerifyingKey::recover_from_prehash(&msg[..], &signature, recid)?;
|
||||
Ok(public_key_to_address(recovered_key))
|
||||
}
|
||||
|
||||
/// Signs message with the given secret key.
|
||||
/// Returns the corresponding signature.
|
||||
pub fn sign_message(secret: B256, message: B256) -> Result<Signature, Error> {
|
||||
let sec = SigningKey::from_slice(secret.as_ref())?;
|
||||
let (sig, rec_id) = sec.sign_prehash_recoverable(&message.0)?;
|
||||
let (r, s) = sig.split_bytes();
|
||||
|
||||
let signature = Signature {
|
||||
r: U256::try_from_be_slice(&r).expect("The slice has at most 32 bytes"),
|
||||
s: U256::try_from_be_slice(&s).expect("The slice has at most 32 bytes"),
|
||||
odd_y_parity: rec_id.is_y_odd(),
|
||||
};
|
||||
Ok(signature)
|
||||
}
|
||||
|
||||
/// Converts a public key into an ethereum address by hashing the encoded public key with
|
||||
/// keccak256.
|
||||
pub fn public_key_to_address(public: VerifyingKey) -> Address {
|
||||
let hash = keccak256(&public.to_encoded_point(/* compress = */ false).as_bytes()[1..]);
|
||||
Address::from_slice(&hash[12..])
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{address, hex};
|
||||
#[cfg(feature = "secp256k1")]
|
||||
#[test]
|
||||
fn sanity_ecrecover_call_secp256k1() {
|
||||
use super::impl_secp256k1::*;
|
||||
use revm_primitives::{keccak256, B256};
|
||||
|
||||
let (secret, public) = secp256k1::generate_keypair(&mut rand::thread_rng());
|
||||
let signer = public_key_to_address(public);
|
||||
|
||||
let message = b"hello world";
|
||||
let hash = keccak256(message);
|
||||
let signature =
|
||||
sign_message(B256::from_slice(&secret.secret_bytes()[..]), hash).expect("sign message");
|
||||
|
||||
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.odd_y_parity as u8;
|
||||
|
||||
assert_eq!(recover_signer_unchecked(&sig, &hash), Ok(signer));
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "secp256k1"))]
|
||||
#[test]
|
||||
fn sanity_ecrecover_call_k256() {
|
||||
use super::impl_k256::*;
|
||||
use revm_primitives::{keccak256, B256};
|
||||
|
||||
let secret = k256::ecdsa::SigningKey::random(&mut rand::thread_rng());
|
||||
let public = *secret.verifying_key();
|
||||
let signer = public_key_to_address(public);
|
||||
|
||||
let message = b"hello world";
|
||||
let hash = keccak256(message);
|
||||
let signature =
|
||||
sign_message(B256::from_slice(&secret.to_bytes()[..]), hash).expect("sign message");
|
||||
|
||||
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.odd_y_parity as u8;
|
||||
|
||||
assert_eq!(recover_signer_unchecked(&sig, &hash).ok(), Some(signer));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sanity_ecrecover_call() {
|
||||
let sig = hex!("650acf9d3f5f0a2c799776a1254355d5f4061762a237396a99a0e0e3fc2bcd6729514a0dacb2e623ac4abd157cb18163ff942280db4d5caad66ddf941ba12e0300");
|
||||
let hash = hex!("47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad");
|
||||
let out = address!("c08b5542d177ac6686946920409741463a15dddb");
|
||||
fn sanity_secp256k1_k256_compat() {
|
||||
use super::{impl_k256, impl_secp256k1};
|
||||
use revm_primitives::{keccak256, B256};
|
||||
|
||||
assert_eq!(secp256k1::recover_signer_unchecked(&sig, &hash), Ok(out));
|
||||
let (secp256k1_secret, secp256k1_public) =
|
||||
secp256k1::generate_keypair(&mut rand::thread_rng());
|
||||
let k256_secret = k256::ecdsa::SigningKey::from_slice(&secp256k1_secret.secret_bytes())
|
||||
.expect("k256 secret");
|
||||
let k256_public = *k256_secret.verifying_key();
|
||||
|
||||
let secp256k1_signer = impl_secp256k1::public_key_to_address(secp256k1_public);
|
||||
let k256_signer = impl_k256::public_key_to_address(k256_public);
|
||||
assert_eq!(secp256k1_signer, k256_signer);
|
||||
|
||||
let message = b"hello world";
|
||||
let hash = keccak256(message);
|
||||
|
||||
let secp256k1_signature = impl_secp256k1::sign_message(
|
||||
B256::from_slice(&secp256k1_secret.secret_bytes()[..]),
|
||||
hash,
|
||||
)
|
||||
.expect("secp256k1 sign");
|
||||
let k256_signature =
|
||||
impl_k256::sign_message(B256::from_slice(&k256_secret.to_bytes()[..]), hash)
|
||||
.expect("k256 sign");
|
||||
assert_eq!(secp256k1_signature, k256_signature);
|
||||
|
||||
let mut sig: [u8; 65] = [0; 65];
|
||||
|
||||
sig[0..32].copy_from_slice(&secp256k1_signature.r.to_be_bytes::<32>());
|
||||
sig[32..64].copy_from_slice(&secp256k1_signature.s.to_be_bytes::<32>());
|
||||
sig[64] = secp256k1_signature.odd_y_parity as u8;
|
||||
let secp256k1_recovered =
|
||||
impl_secp256k1::recover_signer_unchecked(&sig, &hash).expect("secp256k1 recover");
|
||||
assert_eq!(secp256k1_recovered, secp256k1_signer);
|
||||
|
||||
sig[0..32].copy_from_slice(&k256_signature.r.to_be_bytes::<32>());
|
||||
sig[32..64].copy_from_slice(&k256_signature.s.to_be_bytes::<32>());
|
||||
sig[64] = k256_signature.odd_y_parity as u8;
|
||||
let k256_recovered =
|
||||
impl_k256::recover_signer_unchecked(&sig, &hash).expect("k256 recover");
|
||||
assert_eq!(k256_recovered, k256_signer);
|
||||
|
||||
assert_eq!(secp256k1_recovered, k256_recovered);
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,7 +17,7 @@ reth-errors.workspace = true
|
||||
reth-evm.workspace = true
|
||||
reth-execution-types.workspace = true
|
||||
reth-metrics.workspace = true
|
||||
reth-primitives.workspace = true
|
||||
reth-primitives = { workspace = true, features = ["secp256k1"] }
|
||||
reth-provider.workspace = true
|
||||
reth-revm.workspace = true
|
||||
reth-rpc-server-types.workspace = true
|
||||
|
||||
@ -14,7 +14,7 @@ workspace = true
|
||||
[dependencies]
|
||||
# reth
|
||||
reth-chainspec.workspace = true
|
||||
reth-primitives.workspace = true
|
||||
reth-primitives = { workspace = true, features = ["secp256k1"] }
|
||||
reth-rpc-api.workspace = true
|
||||
reth-rpc-eth-api.workspace = true
|
||||
reth-rpc-types.workspace = true
|
||||
|
||||
@ -23,7 +23,7 @@ reth-etl.workspace = true
|
||||
reth-evm.workspace = true
|
||||
reth-exex.workspace = true
|
||||
reth-network-p2p.workspace = true
|
||||
reth-primitives.workspace = true
|
||||
reth-primitives = { workspace = true, features = ["secp256k1"] }
|
||||
reth-primitives-traits.workspace = true
|
||||
reth-provider.workspace = true
|
||||
reth-execution-types.workspace = true
|
||||
|
||||
@ -16,7 +16,7 @@ workspace = true
|
||||
reth-chainspec.workspace = true
|
||||
reth-blockchain-tree-api.workspace = true
|
||||
reth-execution-types.workspace = true
|
||||
reth-primitives = { workspace = true, features = ["reth-codec"] }
|
||||
reth-primitives = { workspace = true, features = ["reth-codec", "secp256k1"] }
|
||||
reth-fs-util.workspace = true
|
||||
reth-errors.workspace = true
|
||||
reth-storage-errors.workspace = true
|
||||
|
||||
@ -15,7 +15,7 @@ workspace = true
|
||||
# reth
|
||||
reth-chainspec.workspace = true
|
||||
reth-eth-wire-types.workspace = true
|
||||
reth-primitives = { workspace = true, features = ["c-kzg"] }
|
||||
reth-primitives = { workspace = true, features = ["c-kzg", "secp256k1"] }
|
||||
reth-execution-types.workspace = true
|
||||
reth-fs-util.workspace = true
|
||||
reth-provider.workspace = true
|
||||
|
||||
@ -12,9 +12,9 @@ repository.workspace = true
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
reth-primitives.workspace = true
|
||||
reth-primitives = { workspace = true, features = ["secp256k1"] }
|
||||
|
||||
alloy-genesis.workspace = true
|
||||
|
||||
secp256k1.workspace = true
|
||||
secp256k1 = { workspace = true, features = ["rand"] }
|
||||
rand.workspace = true
|
||||
Reference in New Issue
Block a user