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

Commit cb52bc7

Browse files
fgimenezDaniPopes
andauthored
feat: integrate secp256r1 precompile (#6)
* feat: integrate secp256r1 precompile * update precompile address * fix and enable test * add more tests * prevent copying bytes * use pad utils from revm_precompile * Update crates/precompile/src/secp256r1.rs Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> * simplify result return * return verification failure on input len < 160 * add input format doc --------- Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com>
1 parent 8439dbb commit cb52bc7

File tree

7 files changed

+217
-13
lines changed

7 files changed

+217
-13
lines changed

Cargo.lock

Lines changed: 78 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[workspace]
2-
members = ["bin/alphanet/", "crates/node"]
2+
members = ["bin/alphanet/", "crates/node", "crates/precompile"]
33
default-members = ["bin/alphanet/"]
44
resolver = "2"
55

@@ -43,6 +43,7 @@ incremental = false
4343
[workspace.dependencies]
4444
# alphanet
4545
alphanet-node = { path = "crates/node" }
46+
alphanet-precompile = { path = "crates/precompile" }
4647

4748
# tokio
4849
tokio = { version = "1.21", default-features = false }
@@ -62,3 +63,6 @@ revm-primitives = { version = "2.1.0", features = ["std"], default-features = fa
6263
clap = "4"
6364
eyre = "0.6.12"
6465
tracing = "0.1.0"
66+
67+
# misc-testing
68+
rstest = "0.18.2"

crates/node/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ keywords.workspace = true
1010
categories.workspace = true
1111

1212
[dependencies]
13+
alphanet-precompile.workspace = true
1314
reth.workspace = true
1415
reth-node-api.workspace = true
1516
reth-node-optimism.workspace = true

crates/node/src/evm.rs

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1+
use alphanet_precompile::secp256r1::P256VERIFY;
12
use reth::{
23
primitives::{
3-
address,
44
revm::{config::revm_spec, env::fill_op_tx_env},
5-
revm_primitives::{AnalysisKind, CfgEnvWithHandlerCfg, Env, PrecompileResult, TxEnv},
5+
revm_primitives::{AnalysisKind, CfgEnvWithHandlerCfg, TxEnv},
66
Address, Bytes, ChainSpec, Head, Header, Transaction, U256,
77
},
88
revm::{
99
handler::register::EvmHandler,
10-
precompile::{Precompile, PrecompileSpecId, Precompiles},
10+
precompile::{PrecompileSpecId, Precompiles},
1111
Database, Evm, EvmBuilder,
1212
},
1313
};
@@ -36,18 +36,10 @@ impl AlphaNetEvmConfig {
3636
// install the precompiles
3737
handler.pre_execution.load_precompiles = Arc::new(move || {
3838
let mut precompiles = Precompiles::new(PrecompileSpecId::from_spec_id(spec_id)).clone();
39-
precompiles.inner.insert(
40-
address!("0000000000000000000000000000000000000999"),
41-
Precompile::Env(Self::alphanet_precompile),
42-
);
39+
precompiles.inner.insert(P256VERIFY.0, P256VERIFY.1);
4340
precompiles
4441
});
4542
}
46-
47-
/// A custom precompile that does nothing
48-
fn alphanet_precompile(_data: &Bytes, _gas: u64, _env: &Env) -> PrecompileResult {
49-
Ok((0, Bytes::new()))
50-
}
5143
}
5244

5345
impl ConfigureEvm for AlphaNetEvmConfig {

crates/precompile/Cargo.toml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[package]
2+
name = "alphanet-precompile"
3+
version.workspace = true
4+
edition.workspace = true
5+
rust-version.workspace = true
6+
authors.workspace = true
7+
license.workspace = true
8+
repository.workspace = true
9+
keywords.workspace = true
10+
categories.workspace = true
11+
12+
[dependencies]
13+
p256 = { version = "0.13.2", features = ["ecdsa"] }
14+
reth.workspace = true
15+
revm-primitives.workspace = true
16+
17+
[dev-dependencies]
18+
rstest.workspace = true
19+
20+
[lints]
21+
workspace = true

crates/precompile/src/lib.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//! # alphanet-precompile
2+
//!
3+
//! Implementations of EVM precompiled contracts for AlphaNet.
4+
5+
/// EIP-7212 secp256r1 precompile.
6+
pub mod secp256r1;
7+
8+
/// Const function for making an address by concatenating the bytes from two given numbers.
9+
///
10+
/// Note that 32 + 128 = 160 = 20 bytes (the length of an address). This function is used
11+
/// as a convenience for specifying the addresses of the various precompiles.
12+
use revm_primitives::Address;
13+
#[inline]
14+
const fn u64_to_address(x: u64) -> Address {
15+
let x = x.to_be_bytes();
16+
Address::new([
17+
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],
18+
])
19+
}

crates/precompile/src/secp256r1.rs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
use reth::revm::precompile::{Precompile, PrecompileWithAddress};
2+
use revm_primitives::{Bytes, PrecompileError, PrecompileResult, StandardPrecompileFn, B256};
3+
4+
/// EIP-7212 secp256r1 precompile.
5+
pub const P256VERIFY: PrecompileWithAddress = PrecompileWithAddress(
6+
crate::u64_to_address(0x0b), /* 0x0b according to https://eips.ethereum.org/EIPS/eip-7212#specification */
7+
Precompile::Standard(p256_verify as StandardPrecompileFn),
8+
);
9+
10+
/// The input is encoded as follows:
11+
/// | signed msg hash | r | s | pk x | pk y |
12+
/// | 32 | 32 | 32 | 32 | 32 |
13+
fn p256_verify(input: &Bytes, target_gas: u64) -> PrecompileResult {
14+
use p256::ecdsa::{signature::hazmat::PrehashVerifier, Signature, VerifyingKey};
15+
16+
const P256VERIFY_BASE: u64 = 3_450;
17+
18+
if P256VERIFY_BASE > target_gas {
19+
return Err(PrecompileError::OutOfGas);
20+
}
21+
if input.len() < 160 {
22+
return Ok((P256VERIFY_BASE, B256::ZERO.into()));
23+
}
24+
25+
// msg signed (msg is already the hash of the original message)
26+
let msg: &[u8; 32] = input[..32].try_into().unwrap();
27+
// r, s: signature
28+
let sig: &[u8; 64] = input[32..96].try_into().unwrap();
29+
// x, y: public key
30+
let pk: &[u8; 64] = input[96..160].try_into().unwrap();
31+
// append 0x04 to the public key: uncompressed form
32+
let mut uncompressed_pk = [0u8; 65];
33+
uncompressed_pk[0] = 0x04;
34+
uncompressed_pk[1..].copy_from_slice(pk);
35+
36+
let signature: Signature = Signature::from_slice(sig).unwrap();
37+
let public_key: VerifyingKey = VerifyingKey::from_sec1_bytes(&uncompressed_pk).unwrap();
38+
39+
let result = public_key.verify_prehash(msg, &signature).is_ok();
40+
Ok((P256VERIFY_BASE, B256::with_last_byte(result as u8).into()))
41+
}
42+
43+
#[cfg(test)]
44+
mod test {
45+
use super::p256_verify;
46+
use revm_primitives::{hex::FromHex, Bytes, PrecompileError, B256};
47+
use rstest::rstest;
48+
49+
#[rstest]
50+
// test vectors from https://github.com/daimo-eth/p256-verifier/tree/master/test-vectors
51+
#[case::ok_1("4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e", true)]
52+
#[case::ok_2("3fec5769b5cf4e310a7d150508e82fb8e3eda1c2c94c61492d3bd8aea99e06c9e22466e928fdccef0de49e3503d2657d00494a00e764fd437bdafa05f5922b1fbbb77c6817ccf50748419477e843d5bac67e6a70e97dde5a57e0c983b777e1ad31a80482dadf89de6302b1988c82c29544c9c07bb910596158f6062517eb089a2f54c9a0f348752950094d3228d3b940258c75fe2a413cb70baa21dc2e352fc5", true)]
53+
#[case::ok_3("e775723953ead4a90411a02908fd1a629db584bc600664c609061f221ef6bf7c440066c8626b49daaa7bf2bcc0b74be4f7a1e3dcf0e869f1542fe821498cbf2de73ad398194129f635de4424a07ca715838aefe8fe69d1a391cfa70470795a80dd056866e6e1125aff94413921880c437c9e2570a28ced7267c8beef7e9b2d8d1547d76dfcf4bee592f5fefe10ddfb6aeb0991c5b9dbbee6ec80d11b17c0eb1a", true)]
54+
#[case::ok_4("b5a77e7a90aa14e0bf5f337f06f597148676424fae26e175c6e5621c34351955289f319789da424845c9eac935245fcddd805950e2f02506d09be7e411199556d262144475b1fa46ad85250728c600c53dfd10f8b3f4adf140e27241aec3c2da3a81046703fccf468b48b145f939efdbb96c3786db712b3113bb2488ef286cdcef8afe82d200a5bb36b5462166e8ce77f2d831a52ef2135b2af188110beaefb1", true)]
55+
#[case::ok_5("858b991cfd78f16537fe6d1f4afd10273384db08bdfc843562a22b0626766686f6aec8247599f40bfe01bec0e0ecf17b4319559022d4d9bf007fe929943004eb4866760dedf31b7c691f5ce665f8aae0bda895c23595c834fecc2390a5bcc203b04afcacbb4280713287a2d0c37e23f7513fab898f2c1fefa00ec09a924c335d9b629f1d4fb71901c3e59611afbfea354d101324e894c788d1c01f00b3c251b2", true)]
56+
#[case::fail_1("3cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e", false)]
57+
#[case::fail_2("afec5769b5cf4e310a7d150508e82fb8e3eda1c2c94c61492d3bd8aea99e06c9e22466e928fdccef0de49e3503d2657d00494a00e764fd437bdafa05f5922b1fbbb77c6817ccf50748419477e843d5bac67e6a70e97dde5a57e0c983b777e1ad31a80482dadf89de6302b1988c82c29544c9c07bb910596158f6062517eb089a2f54c9a0f348752950094d3228d3b940258c75fe2a413cb70baa21dc2e352fc5", false)]
58+
#[case::fail_3("f775723953ead4a90411a02908fd1a629db584bc600664c609061f221ef6bf7c440066c8626b49daaa7bf2bcc0b74be4f7a1e3dcf0e869f1542fe821498cbf2de73ad398194129f635de4424a07ca715838aefe8fe69d1a391cfa70470795a80dd056866e6e1125aff94413921880c437c9e2570a28ced7267c8beef7e9b2d8d1547d76dfcf4bee592f5fefe10ddfb6aeb0991c5b9dbbee6ec80d11b17c0eb1a", false)]
59+
#[case::fail_4("c5a77e7a90aa14e0bf5f337f06f597148676424fae26e175c6e5621c34351955289f319789da424845c9eac935245fcddd805950e2f02506d09be7e411199556d262144475b1fa46ad85250728c600c53dfd10f8b3f4adf140e27241aec3c2da3a81046703fccf468b48b145f939efdbb96c3786db712b3113bb2488ef286cdcef8afe82d200a5bb36b5462166e8ce77f2d831a52ef2135b2af188110beaefb1", false)]
60+
#[case::fail_5("958b991cfd78f16537fe6d1f4afd10273384db08bdfc843562a22b0626766686f6aec8247599f40bfe01bec0e0ecf17b4319559022d4d9bf007fe929943004eb4866760dedf31b7c691f5ce665f8aae0bda895c23595c834fecc2390a5bcc203b04afcacbb4280713287a2d0c37e23f7513fab898f2c1fefa00ec09a924c335d9b629f1d4fb71901c3e59611afbfea354d101324e894c788d1c01f00b3c251b2", false)]
61+
fn test_sig_verify(#[case] input: &str, #[case] expect_success: bool) {
62+
let input = Bytes::from_hex(input).unwrap();
63+
let target_gas = 3_500u64;
64+
let (gas_used, res) = p256_verify(&input, target_gas).unwrap();
65+
assert_eq!(gas_used, 3_450u64);
66+
let expected_result = B256::with_last_byte(expect_success as u8);
67+
assert_eq!(res, expected_result.to_vec());
68+
}
69+
70+
#[rstest]
71+
fn test_not_enough_gas_errors() {
72+
let input = Bytes::from_hex("4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e").unwrap();
73+
let target_gas = 2_500u64;
74+
let result = p256_verify(&input, target_gas);
75+
76+
assert!(result.is_err());
77+
assert_eq!(result.err(), Some(PrecompileError::OutOfGas));
78+
}
79+
80+
#[rstest]
81+
fn test_smaller_input_fails() {
82+
let input = Bytes::from_hex("4cee90eb86eaa050036147a12d49004b6a").unwrap();
83+
let target_gas = 3_500u64;
84+
let result = p256_verify(&input, target_gas);
85+
86+
assert!(result.is_ok());
87+
assert_eq!(result.ok(), Some((3_450u64, B256::ZERO.into())));
88+
}
89+
}

0 commit comments

Comments
 (0)