diff --git a/.github/assets/check_wasm.sh b/.github/assets/check_wasm.sh index f9832324c3b7..95e2aa6d5b08 100755 --- a/.github/assets/check_wasm.sh +++ b/.github/assets/check_wasm.sh @@ -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 diff --git a/Cargo.lock b/Cargo.lock index 297fda920f85..e2b920d69c5c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8066,6 +8066,7 @@ dependencies = [ "c-kzg", "criterion", "derive_more", + "k256", "modular-bitfield", "nybbles", "once_cell", diff --git a/Cargo.toml b/Cargo.toml index 0c8d34aad70b..0bc6011aae0e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 diff --git a/crates/ethereum/evm/Cargo.toml b/crates/ethereum/evm/Cargo.toml index 7ea2e4b587c9..25f2d9c6af78 100644 --- a/crates/ethereum/evm/Cargo.toml +++ b/crates/ethereum/evm/Cargo.toml @@ -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,9 +31,10 @@ 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 [features] default = ["std"] -std = [] \ No newline at end of file +std = [] diff --git a/crates/exex/exex/Cargo.toml b/crates/exex/exex/Cargo.toml index 3ddeebcd6ade..03400f78cd26 100644 --- a/crates/exex/exex/Cargo.toml +++ b/crates/exex/exex/Cargo.toml @@ -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 diff --git a/crates/net/network/Cargo.toml b/crates/net/network/Cargo.toml index 5955f6cfe95e..3eb6cd193ed2 100644 --- a/crates/net/network/Cargo.toml +++ b/crates/net/network/Cargo.toml @@ -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 diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 98d37c282adc..36329510c023 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -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", diff --git a/crates/primitives/src/transaction/mod.rs b/crates/primitives/src/transaction/mod.rs index feb0aba93853..a635499832a6 100644 --- a/crates/primitives/src/transaction/mod.rs +++ b/crates/primitives/src/transaction/mod.rs @@ -1653,18 +1653,14 @@ impl WithEncoded> { #[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::(), *PARALLEL_SENDER_RECOVERY_THRESHOLD * 5)) { + fn test_parallel_recovery_order(txes in proptest::collection::vec( + proptest_arbitrary_interop::arb::(), + *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 = 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(); diff --git a/crates/primitives/src/transaction/util.rs b/crates/primitives/src/transaction/util.rs index b4a2db7f6b52..9aa05ef3b2ba 100644 --- a/crates/primitives/src/transaction/util.rs +++ b/crates/primitives/src/transaction/util.rs @@ -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 { + pub fn sign_message(secret: B256, message: B256) -> Result { 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 { + 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 { + 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() { - let sig = hex!("650acf9d3f5f0a2c799776a1254355d5f4061762a237396a99a0e0e3fc2bcd6729514a0dacb2e623ac4abd157cb18163ff942280db4d5caad66ddf941ba12e0300"); - let hash = hex!("47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad"); - let out = address!("c08b5542d177ac6686946920409741463a15dddb"); + 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_secp256k1_k256_compat() { + use super::{impl_k256, impl_secp256k1}; + use revm_primitives::{keccak256, B256}; + + 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::recover_signer_unchecked(&sig, &hash), Ok(out)); + assert_eq!(secp256k1_recovered, k256_recovered); } } diff --git a/crates/rpc/rpc-eth-types/Cargo.toml b/crates/rpc/rpc-eth-types/Cargo.toml index 3fb20836e46f..247ca35fe701 100644 --- a/crates/rpc/rpc-eth-types/Cargo.toml +++ b/crates/rpc/rpc-eth-types/Cargo.toml @@ -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 diff --git a/crates/rpc/rpc/Cargo.toml b/crates/rpc/rpc/Cargo.toml index fea6c1705c25..d1146021bec0 100644 --- a/crates/rpc/rpc/Cargo.toml +++ b/crates/rpc/rpc/Cargo.toml @@ -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 diff --git a/crates/stages/stages/Cargo.toml b/crates/stages/stages/Cargo.toml index fce1df25cf20..934e9b109053 100644 --- a/crates/stages/stages/Cargo.toml +++ b/crates/stages/stages/Cargo.toml @@ -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 diff --git a/crates/storage/provider/Cargo.toml b/crates/storage/provider/Cargo.toml index 48058084e7af..ca58f80f4f65 100644 --- a/crates/storage/provider/Cargo.toml +++ b/crates/storage/provider/Cargo.toml @@ -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 diff --git a/crates/transaction-pool/Cargo.toml b/crates/transaction-pool/Cargo.toml index 459784b61017..8790db64a61a 100644 --- a/crates/transaction-pool/Cargo.toml +++ b/crates/transaction-pool/Cargo.toml @@ -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 diff --git a/testing/testing-utils/Cargo.toml b/testing/testing-utils/Cargo.toml index 815976dea562..da4375e411f9 100644 --- a/testing/testing-utils/Cargo.toml +++ b/testing/testing-utils/Cargo.toml @@ -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 -rand.workspace = true \ No newline at end of file +secp256k1 = { workspace = true, features = ["rand"] } +rand.workspace = true