Skip to content
This repository has been archived by the owner on Nov 25, 2024. It is now read-only.

feat: integrate secp256r1 precompile #6

Merged
merged 10 commits into from
Mar 7, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
78 changes: 78 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[workspace]
members = ["bin/alphanet/", "crates/node"]
members = ["bin/alphanet/", "crates/node", "crates/precompile"]
default-members = ["bin/alphanet/"]
resolver = "2"

Expand Down Expand Up @@ -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 }
Expand All @@ -62,3 +63,6 @@ revm-primitives = { version = "2.1.0", features = ["std"], default-features = fa
clap = "4"
eyre = "0.6.12"
tracing = "0.1.0"

# misc-testing
rstest = "0.18.2"
1 change: 1 addition & 0 deletions crates/node/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 4 additions & 12 deletions crates/node/src/evm.rs
Original file line number Diff line number Diff line change
@@ -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,
},
};
Expand Down Expand Up @@ -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 {
Expand Down
21 changes: 21 additions & 0 deletions crates/precompile/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[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

[dev-dependencies]
rstest.workspace = true

[lints]
workspace = true
19 changes: 19 additions & 0 deletions crates/precompile/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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],
])
}
Comment on lines +8 to +19
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

think alloy should support this? @DaniPopes

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

found it in revm, but it's not public there https://github.com/bluealloy/revm/blob/main/crates/precompile/src/lib.rs#L263 would be indeed very helpful to be able to import it from somewhere

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will make it pub

88 changes: 88 additions & 0 deletions crates/precompile/src/secp256r1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
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(11), /* 0x0b according to https://eips.ethereum.org/EIPS/eip-7212#specification */
fgimenez marked this conversation as resolved.
Show resolved Hide resolved
Precompile::Standard(p256_verify as StandardPrecompileFn),
);

fn p256_verify(i: &Bytes, target_gas: u64) -> PrecompileResult {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

excellent

fgimenez marked this conversation as resolved.
Show resolved Hide resolved
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)]);
fgimenez marked this conversation as resolved.
Show resolved Hide resolved

// 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();
fgimenez marked this conversation as resolved.
Show resolved Hide resolved
// 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()))
DaniPopes marked this conversation as resolved.
Show resolved Hide resolved
}
}

#[cfg(test)]
mod test {
use super::p256_verify;
use revm_primitives::{hex::FromHex, Bytes, PrecompileError};
use rstest::rstest;

#[rstest]
// test vectors from https://github.com/daimo-eth/p256-verifier/tree/master/test-vectors
#[case::ok_1("4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e", true)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interesting that this generates multiple tests, I usually just do this with an array / loop

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, there are other niceties from rstest like fixtures with #[once]

#[case::ok_2("3fec5769b5cf4e310a7d150508e82fb8e3eda1c2c94c61492d3bd8aea99e06c9e22466e928fdccef0de49e3503d2657d00494a00e764fd437bdafa05f5922b1fbbb77c6817ccf50748419477e843d5bac67e6a70e97dde5a57e0c983b777e1ad31a80482dadf89de6302b1988c82c29544c9c07bb910596158f6062517eb089a2f54c9a0f348752950094d3228d3b940258c75fe2a413cb70baa21dc2e352fc5", true)]
#[case::ok_3("e775723953ead4a90411a02908fd1a629db584bc600664c609061f221ef6bf7c440066c8626b49daaa7bf2bcc0b74be4f7a1e3dcf0e869f1542fe821498cbf2de73ad398194129f635de4424a07ca715838aefe8fe69d1a391cfa70470795a80dd056866e6e1125aff94413921880c437c9e2570a28ced7267c8beef7e9b2d8d1547d76dfcf4bee592f5fefe10ddfb6aeb0991c5b9dbbee6ec80d11b17c0eb1a", true)]
#[case::ok_4("b5a77e7a90aa14e0bf5f337f06f597148676424fae26e175c6e5621c34351955289f319789da424845c9eac935245fcddd805950e2f02506d09be7e411199556d262144475b1fa46ad85250728c600c53dfd10f8b3f4adf140e27241aec3c2da3a81046703fccf468b48b145f939efdbb96c3786db712b3113bb2488ef286cdcef8afe82d200a5bb36b5462166e8ce77f2d831a52ef2135b2af188110beaefb1", true)]
#[case::ok_5("858b991cfd78f16537fe6d1f4afd10273384db08bdfc843562a22b0626766686f6aec8247599f40bfe01bec0e0ecf17b4319559022d4d9bf007fe929943004eb4866760dedf31b7c691f5ce665f8aae0bda895c23595c834fecc2390a5bcc203b04afcacbb4280713287a2d0c37e23f7513fab898f2c1fefa00ec09a924c335d9b629f1d4fb71901c3e59611afbfea354d101324e894c788d1c01f00b3c251b2", true)]
#[case::fail_1("3cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e", false)]
#[case::fail_2("afec5769b5cf4e310a7d150508e82fb8e3eda1c2c94c61492d3bd8aea99e06c9e22466e928fdccef0de49e3503d2657d00494a00e764fd437bdafa05f5922b1fbbb77c6817ccf50748419477e843d5bac67e6a70e97dde5a57e0c983b777e1ad31a80482dadf89de6302b1988c82c29544c9c07bb910596158f6062517eb089a2f54c9a0f348752950094d3228d3b940258c75fe2a413cb70baa21dc2e352fc5", false)]
#[case::fail_3("f775723953ead4a90411a02908fd1a629db584bc600664c609061f221ef6bf7c440066c8626b49daaa7bf2bcc0b74be4f7a1e3dcf0e869f1542fe821498cbf2de73ad398194129f635de4424a07ca715838aefe8fe69d1a391cfa70470795a80dd056866e6e1125aff94413921880c437c9e2570a28ced7267c8beef7e9b2d8d1547d76dfcf4bee592f5fefe10ddfb6aeb0991c5b9dbbee6ec80d11b17c0eb1a", false)]
#[case::fail_4("c5a77e7a90aa14e0bf5f337f06f597148676424fae26e175c6e5621c34351955289f319789da424845c9eac935245fcddd805950e2f02506d09be7e411199556d262144475b1fa46ad85250728c600c53dfd10f8b3f4adf140e27241aec3c2da3a81046703fccf468b48b145f939efdbb96c3786db712b3113bb2488ef286cdcef8afe82d200a5bb36b5462166e8ce77f2d831a52ef2135b2af188110beaefb1", false)]
#[case::fail_5("958b991cfd78f16537fe6d1f4afd10273384db08bdfc843562a22b0626766686f6aec8247599f40bfe01bec0e0ecf17b4319559022d4d9bf007fe929943004eb4866760dedf31b7c691f5ce665f8aae0bda895c23595c834fecc2390a5bcc203b04afcacbb4280713287a2d0c37e23f7513fab898f2c1fefa00ec09a924c335d9b629f1d4fb71901c3e59611afbfea354d101324e894c788d1c01f00b3c251b2", false)]
fn test_sig_verify(#[case] input: &str, #[case] expect_success: bool) {
let input = Bytes::from_hex(input).unwrap();
let target_gas = 3_500u64;
let (gas_used, res) = p256_verify(&input, target_gas).unwrap();
assert_eq!(gas_used, 3_450u64);
let expected_result_str = if expect_success {
"0000000000000000000000000000000000000000000000000000000000000001"
} else {
"0000000000000000000000000000000000000000000000000000000000000000"
};
let expected_result = Bytes::from_hex(expected_result_str).unwrap();
assert_eq!(res, expected_result.to_vec());
}

#[rstest]
fn test_not_enough_gas_fails() {
let input = Bytes::from_hex("4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e").unwrap();
let target_gas = 2_500u64;
let result = p256_verify(&input, target_gas);

assert!(result.is_err());
assert_eq!(result.err(), Some(PrecompileError::OutOfGas));
}
}