diff --git a/Cargo.lock b/Cargo.lock index b10bf41..03ce5f2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -347,11 +347,21 @@ dependencies = [ name = "alphanet-node" version = "0.0.0" dependencies = [ + "alphanet-precompile", "reth", "reth-node-api", "reth-node-optimism", ] +[[package]] +name = "alphanet-precompile" +version = "0.0.0" +dependencies = [ + "p256", + "reth", + "revm-primitives", +] + [[package]] name = "android-tzdata" version = "0.1.1" @@ -1640,6 +1650,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" dependencies = [ "const-oid", + "pem-rfc7468", "zeroize", ] @@ -1925,6 +1936,7 @@ dependencies = [ "ff", "generic-array", "group", + "pem-rfc7468", "pkcs8", "rand_core 0.6.4", "sec1", @@ -3832,6 +3844,18 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + [[package]] name = "page_size" version = "0.6.0" @@ -3952,6 +3976,15 @@ dependencies = [ "base64 0.13.1", ] +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -4125,6 +4158,15 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "primitive-types" version = "0.12.2" diff --git a/Cargo.toml b/Cargo.toml index 6c19ceb..ab40905 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["bin/alphanet/", "crates/node"] +members = ["bin/alphanet/", "crates/node", "crates/precompile"] default-members = ["bin/alphanet/"] resolver = "2" @@ -43,6 +43,7 @@ incremental = false [workspace.dependencies] # alphanet alphanet-node = { path = "crates/node" } +alphanet-precompile = { path = "crates/precompile" } # tokio tokio = { version = "1.21", default-features = false } diff --git a/crates/node/Cargo.toml b/crates/node/Cargo.toml index 699ac02..82aa642 100644 --- a/crates/node/Cargo.toml +++ b/crates/node/Cargo.toml @@ -10,6 +10,7 @@ keywords.workspace = true categories.workspace = true [dependencies] +alphanet-precompile.workspace = true reth.workspace = true reth-node-api.workspace = true reth-node-optimism.workspace = true diff --git a/crates/node/src/evm.rs b/crates/node/src/evm.rs index 743a853..465a3e5 100644 --- a/crates/node/src/evm.rs +++ b/crates/node/src/evm.rs @@ -1,13 +1,13 @@ +use alphanet_precompile::secp256r1::P256VERIFY; use reth::{ primitives::{ - address, revm::{config::revm_spec, env::fill_op_tx_env}, - revm_primitives::{AnalysisKind, CfgEnvWithHandlerCfg, Env, PrecompileResult, TxEnv}, + revm_primitives::{AnalysisKind, CfgEnvWithHandlerCfg, TxEnv}, Address, Bytes, ChainSpec, Head, Header, Transaction, U256, }, revm::{ handler::register::EvmHandler, - precompile::{Precompile, PrecompileSpecId, Precompiles}, + precompile::{PrecompileSpecId, Precompiles}, Database, Evm, EvmBuilder, }, }; @@ -36,18 +36,10 @@ impl AlphaNetEvmConfig { // install the precompiles handler.pre_execution.load_precompiles = Arc::new(move || { let mut precompiles = Precompiles::new(PrecompileSpecId::from_spec_id(spec_id)).clone(); - precompiles.inner.insert( - address!("0000000000000000000000000000000000000999"), - Precompile::Env(Self::alphanet_precompile), - ); + precompiles.inner.insert(P256VERIFY.0, P256VERIFY.1); precompiles }); } - - /// A custom precompile that does nothing - fn alphanet_precompile(_data: &Bytes, _gas: u64, _env: &Env) -> PrecompileResult { - Ok((0, Bytes::new())) - } } impl ConfigureEvm for AlphaNetEvmConfig { diff --git a/crates/precompile/Cargo.toml b/crates/precompile/Cargo.toml new file mode 100644 index 0000000..fab3fc4 --- /dev/null +++ b/crates/precompile/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "alphanet-precompile" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +keywords.workspace = true +categories.workspace = true + +[dependencies] +p256 = { version = "0.13.2", features = ["ecdsa"] } +reth.workspace = true +revm-primitives.workspace = true + +[lints] +workspace = true diff --git a/crates/precompile/src/lib.rs b/crates/precompile/src/lib.rs new file mode 100644 index 0000000..8c8f309 --- /dev/null +++ b/crates/precompile/src/lib.rs @@ -0,0 +1,19 @@ +//! # alphanet-precompile +//! +//! Implementations of EVM precompiled contracts for AlphaNet. + +/// EIP-7212 secp256r1 precompile. +pub mod secp256r1; + +/// Const function for making an address by concatenating the bytes from two given numbers. +/// +/// Note that 32 + 128 = 160 = 20 bytes (the length of an address). This function is used +/// as a convenience for specifying the addresses of the various precompiles. +use revm_primitives::Address; +#[inline] +const fn u64_to_address(x: u64) -> Address { + let x = x.to_be_bytes(); + Address::new([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, x[0], x[1], x[2], x[3], x[4], x[5], x[6], x[7], + ]) +} diff --git a/crates/precompile/src/secp256r1.rs b/crates/precompile/src/secp256r1.rs new file mode 100644 index 0000000..072c69f --- /dev/null +++ b/crates/precompile/src/secp256r1.rs @@ -0,0 +1,63 @@ +use reth::revm::precompile::{Precompile, PrecompileWithAddress}; +use revm_primitives::{Bytes, PrecompileError, PrecompileResult, StandardPrecompileFn}; + +/// EIP-7212 secp256r1 precompile. +pub const P256VERIFY: PrecompileWithAddress = PrecompileWithAddress( + crate::u64_to_address(10), + Precompile::Standard(p256_verify as StandardPrecompileFn), +); + +fn p256_verify(i: &Bytes, target_gas: u64) -> PrecompileResult { + use core::cmp::min; + use p256::ecdsa::{signature::hazmat::PrehashVerifier, Signature, VerifyingKey}; + + const P256VERIFY_BASE: u64 = 3_450; + + if P256VERIFY_BASE > target_gas { + return Err(PrecompileError::OutOfGas); + } + let mut input = [0u8; 160]; + input[..min(i.len(), 160)].copy_from_slice(&i[..min(i.len(), 160)]); + + // msg signed (msg is already the hash of the original message) + let msg: [u8; 32] = input[..32].try_into().unwrap(); + // r, s: signature + let sig: [u8; 64] = input[32..96].try_into().unwrap(); + // x, y: public key + let pk: [u8; 64] = input[96..160].try_into().unwrap(); + // append 0x04 to the public key: uncompressed form + let mut uncompressed_pk = [0u8; 65]; + uncompressed_pk[0] = 0x04; + uncompressed_pk[1..].copy_from_slice(&pk); + + let signature: Signature = Signature::from_slice(&sig).unwrap(); + let public_key: VerifyingKey = VerifyingKey::from_sec1_bytes(&uncompressed_pk).unwrap(); + + let mut result = [0u8; 32]; + + // verify + if public_key.verify_prehash(&msg, &signature).is_ok() { + result[31] = 0x01; + Ok((P256VERIFY_BASE, result.into())) + } else { + Ok((P256VERIFY_BASE, result.into())) + } +} + +#[cfg(test)] +mod test { + use super::p256_verify; + use revm_primitives::Bytes; + + #[test] + #[ignore] + fn proper_sig_verify() { + let input = Bytes::from("4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e"); + let target_gas = 3_500u64; + let (gas_used, res) = p256_verify(&input, target_gas).unwrap(); + assert_eq!(gas_used, 3_450u64); + let mut expected_res = [0u8; 32]; + expected_res[31] = 1; + assert_eq!(res, expected_res.to_vec()); + } +}