Skip to content

Commit 42c4038

Browse files
j75689BrianBlandrakita
authored
feat: introduce opBNB Haber fork (bluealloy#26)
* feat(optimism): Add secp256r1 precompile for Fjord (bluealloy#1436) * feat(optimism): Add secp256r1 precompile for Fjord * Fix docs * Fix nostd build * Load fjord precompiles via optimism handler register * Remove outdated fjord() precompile spec constructor * Document the secp256r1 feature * Address feedback * Handle invalid signatures * Update crates/precompile/src/secp256r1.rs * Update crates/precompile/src/secp256r1.rs * Blank return on failed signature verification * Add test case for invalid (zero) pubkey --------- Co-authored-by: rakita <rakita@users.noreply.github.com> * feat: add Haber spec to opBNB --------- Co-authored-by: Brian Bland <brian.t.bland@gmail.com> Co-authored-by: rakita <rakita@users.noreply.github.com>
1 parent deb253b commit 42c4038

File tree

8 files changed

+324
-6
lines changed

8 files changed

+324
-6
lines changed

Cargo.lock

+62
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/interpreter/src/instructions/opcode.rs

+11
Original file line numberDiff line numberDiff line change
@@ -947,11 +947,21 @@ pub const fn spec_opcode_gas(spec_id: SpecId) -> &'static [OpInfo; 256] {
947947
const TABLE: &[OpInfo;256] = &make_gas_table(SpecId::ECOTONE);
948948
TABLE
949949
}
950+
#[cfg(feature = "optimism")]
951+
SpecId::FJORD => {
952+
const TABLE: &[OpInfo;256] = &make_gas_table(SpecId::FJORD);
953+
TABLE
954+
}
950955
#[cfg(feature = "opbnb")]
951956
SpecId::FERMAT => {
952957
const TABLE: &[OpInfo;256] = &make_gas_table(SpecId::FERMAT);
953958
TABLE
954959
}
960+
#[cfg(feature = "opbnb")]
961+
SpecId::HABER => {
962+
const TABLE: &[OpInfo;256] = &make_gas_table(SpecId::HABER);
963+
TABLE
964+
}
955965
}
956966
};
957967
}
@@ -975,6 +985,7 @@ pub const fn spec_opcode_gas(spec_id: SpecId) -> &'static [OpInfo; 256] {
975985
MERGE,
976986
SHANGHAI,
977987
CANCUN,
988+
PRAGUE,
978989
LATEST,
979990
)
980991
}

crates/precompile/Cargo.toml

+12-1
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,17 @@ cometbft-light-client = { git = "https://github.com/bnb-chain/greenfield-cometb
3939
prost = { version = "0.12.3" }
4040
bls_on_arkworks = "0.3.0"
4141

42+
# p256verify precompile
43+
p256 = { version = "0.13.2", optional = true, default-features = false, features = ["ecdsa"] }
44+
4245
[dev-dependencies]
4346
criterion = { version = "0.5" }
4447
rand = { version = "0.8", features = ["std"] }
48+
eyre = "0.6.12"
49+
rstest = "0.19.0"
50+
serde = "1.0"
51+
serde_json = "1.0"
52+
serde_derive = "1.0"
4553

4654
[features]
4755
default = ["std", "c-kzg", "secp256k1", "portable"]
@@ -56,7 +64,7 @@ std = [
5664
]
5765
asm-keccak = ["revm-primitives/asm-keccak"]
5866

59-
optimism = ["revm-primitives/optimism"]
67+
optimism = ["revm-primitives/optimism", "secp256r1"]
6068
# Optimism default handler enabled Optimism handler register by default in EvmBuilder.
6169
optimism-default-handler = [
6270
"optimism",
@@ -66,6 +74,9 @@ negate-optimism-default-handler = [
6674
"revm-primitives/negate-optimism-default-handler",
6775
]
6876

77+
# Enables the p256verify precompile.
78+
secp256r1 = ["dep:p256"]
79+
6980
# These libraries may not work on all no_std platforms as they depend on C.
7081

7182
# Enables the KZG point evaluation precompile.

crates/precompile/src/lib.rs

+26-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ pub mod identity;
2020
pub mod kzg_point_evaluation;
2121
pub mod modexp;
2222
pub mod secp256k1;
23+
#[cfg(feature = "secp256r1")]
24+
pub mod secp256r1;
2325
pub mod utilities;
2426

2527
use core::hash::Hash;
@@ -68,6 +70,7 @@ impl Precompiles {
6870
PrecompileSpecId::BERLIN => Self::berlin(),
6971
PrecompileSpecId::FERMAT => Self::fermat(),
7072
PrecompileSpecId::CANCUN => Self::cancun(),
73+
PrecompileSpecId::PRAGUE => Self::prague(),
7174
PrecompileSpecId::LATEST => Self::latest(),
7275
}
7376
}
@@ -183,6 +186,24 @@ impl Precompiles {
183186
})
184187
}
185188

189+
/// Returns precompiles for Prague spec.
190+
pub fn prague() -> &'static Self {
191+
static INSTANCE: OnceBox<Precompiles> = OnceBox::new();
192+
INSTANCE.get_or_init(|| {
193+
let precompiles = Self::cancun().clone();
194+
195+
// Don't include BLS12-381 precompiles in no_std builds.
196+
#[cfg(feature = "blst")]
197+
let precompiles = {
198+
let mut precompiles = precompiles;
199+
precompiles.extend(bls12_381::precompiles());
200+
precompiles
201+
};
202+
203+
Box::new(precompiles)
204+
})
205+
}
206+
186207
/// Returns the precompiles for the latest spec.
187208
pub fn latest() -> &'static Self {
188209
Self::cancun()
@@ -259,6 +280,7 @@ pub enum PrecompileSpecId {
259280
BERLIN,
260281
FERMAT,
261282
CANCUN,
283+
PRAGUE,
262284
LATEST,
263285
}
264286

@@ -274,13 +296,16 @@ impl PrecompileSpecId {
274296
ISTANBUL | MUIR_GLACIER => Self::ISTANBUL,
275297
BERLIN | LONDON | ARROW_GLACIER | GRAY_GLACIER | MERGE | SHANGHAI => Self::BERLIN,
276298
CANCUN => Self::CANCUN,
299+
PRAGUE => Self::PRAGUE,
277300
LATEST => Self::LATEST,
278301
#[cfg(feature = "optimism")]
279302
BEDROCK | REGOLITH | CANYON => Self::BERLIN,
280303
#[cfg(feature = "optimism")]
281-
ECOTONE => Self::CANCUN,
304+
ECOTONE | FJORD => Self::CANCUN,
282305
#[cfg(feature = "opbnb")]
283306
FERMAT => Self::FERMAT,
307+
#[cfg(feature = "opbnb")]
308+
HABER => Self::CANCUN,
284309
}
285310
}
286311
}

crates/precompile/src/secp256r1.rs

+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
//! # EIP-7212 secp256r1 Precompile
2+
//!
3+
//! This module implements the [EIP-7212](https://eips.ethereum.org/EIPS/eip-7212) precompile for
4+
//! secp256r1 curve support.
5+
//!
6+
//! The main purpose of this precompile is to verify ECDSA signatures that use the secp256r1, or
7+
//! P256 elliptic curve. The [`P256VERIFY`] const represents the implementation of this precompile,
8+
//! with the address that it is currently deployed at.
9+
use crate::{u64_to_address, Precompile, PrecompileWithAddress};
10+
use p256::ecdsa::{signature::hazmat::PrehashVerifier, Signature, VerifyingKey};
11+
use revm_primitives::{Bytes, PrecompileError, PrecompileResult, B256};
12+
13+
/// Base gas fee for secp256r1 p256verify operation.
14+
const P256VERIFY_BASE: u64 = 3450;
15+
16+
/// Returns the secp256r1 precompile with its address.
17+
pub fn precompiles() -> impl Iterator<Item = PrecompileWithAddress> {
18+
[P256VERIFY].into_iter()
19+
}
20+
21+
/// [EIP-7212](https://eips.ethereum.org/EIPS/eip-7212#specification) secp256r1 precompile.
22+
pub const P256VERIFY: PrecompileWithAddress =
23+
PrecompileWithAddress(u64_to_address(0x100), Precompile::Standard(p256_verify));
24+
25+
/// secp256r1 precompile logic. It takes the input bytes sent to the precompile
26+
/// and the gas limit. The output represents the result of verifying the
27+
/// secp256r1 signature of the input.
28+
///
29+
/// The input is encoded as follows:
30+
///
31+
/// | signed message hash | r | s | public key x | public key y |
32+
/// | :-----------------: | :-: | :-: | :----------: | :----------: |
33+
/// | 32 | 32 | 32 | 32 | 32 |
34+
pub fn p256_verify(input: &Bytes, gas_limit: u64) -> PrecompileResult {
35+
if P256VERIFY_BASE > gas_limit {
36+
return Err(PrecompileError::OutOfGas);
37+
}
38+
let result = if verify_impl(input).is_some() {
39+
B256::with_last_byte(1).into()
40+
} else {
41+
Bytes::new()
42+
};
43+
Ok((P256VERIFY_BASE, result))
44+
}
45+
46+
/// Returns `Some(())` if the signature included in the input byte slice is
47+
/// valid, `None` otherwise.
48+
pub fn verify_impl(input: &[u8]) -> Option<()> {
49+
if input.len() != 160 {
50+
return None;
51+
}
52+
53+
// msg signed (msg is already the hash of the original message)
54+
let msg = &input[..32];
55+
// r, s: signature
56+
let sig = &input[32..96];
57+
// x, y: public key
58+
let pk = &input[96..160];
59+
60+
// prepend 0x04 to the public key: uncompressed form
61+
let mut uncompressed_pk = [0u8; 65];
62+
uncompressed_pk[0] = 0x04;
63+
uncompressed_pk[1..].copy_from_slice(pk);
64+
65+
// Can fail only if the input is not exact length.
66+
let signature = Signature::from_slice(sig).ok()?;
67+
// Can fail if the input is not valid, so we have to propagate the error.
68+
let public_key = VerifyingKey::from_sec1_bytes(&uncompressed_pk).ok()?;
69+
70+
public_key.verify_prehash(msg, &signature).ok()
71+
}
72+
73+
#[cfg(test)]
74+
mod test {
75+
use super::*;
76+
use revm_primitives::hex::FromHex;
77+
use rstest::rstest;
78+
79+
#[rstest]
80+
// test vectors from https://github.com/daimo-eth/p256-verifier/tree/master/test-vectors
81+
#[case::ok_1("4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e", true)]
82+
#[case::ok_2("3fec5769b5cf4e310a7d150508e82fb8e3eda1c2c94c61492d3bd8aea99e06c9e22466e928fdccef0de49e3503d2657d00494a00e764fd437bdafa05f5922b1fbbb77c6817ccf50748419477e843d5bac67e6a70e97dde5a57e0c983b777e1ad31a80482dadf89de6302b1988c82c29544c9c07bb910596158f6062517eb089a2f54c9a0f348752950094d3228d3b940258c75fe2a413cb70baa21dc2e352fc5", true)]
83+
#[case::ok_3("e775723953ead4a90411a02908fd1a629db584bc600664c609061f221ef6bf7c440066c8626b49daaa7bf2bcc0b74be4f7a1e3dcf0e869f1542fe821498cbf2de73ad398194129f635de4424a07ca715838aefe8fe69d1a391cfa70470795a80dd056866e6e1125aff94413921880c437c9e2570a28ced7267c8beef7e9b2d8d1547d76dfcf4bee592f5fefe10ddfb6aeb0991c5b9dbbee6ec80d11b17c0eb1a", true)]
84+
#[case::ok_4("b5a77e7a90aa14e0bf5f337f06f597148676424fae26e175c6e5621c34351955289f319789da424845c9eac935245fcddd805950e2f02506d09be7e411199556d262144475b1fa46ad85250728c600c53dfd10f8b3f4adf140e27241aec3c2da3a81046703fccf468b48b145f939efdbb96c3786db712b3113bb2488ef286cdcef8afe82d200a5bb36b5462166e8ce77f2d831a52ef2135b2af188110beaefb1", true)]
85+
#[case::ok_5("858b991cfd78f16537fe6d1f4afd10273384db08bdfc843562a22b0626766686f6aec8247599f40bfe01bec0e0ecf17b4319559022d4d9bf007fe929943004eb4866760dedf31b7c691f5ce665f8aae0bda895c23595c834fecc2390a5bcc203b04afcacbb4280713287a2d0c37e23f7513fab898f2c1fefa00ec09a924c335d9b629f1d4fb71901c3e59611afbfea354d101324e894c788d1c01f00b3c251b2", true)]
86+
#[case::fail_wrong_msg_1("3cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e", false)]
87+
#[case::fail_wrong_msg_2("afec5769b5cf4e310a7d150508e82fb8e3eda1c2c94c61492d3bd8aea99e06c9e22466e928fdccef0de49e3503d2657d00494a00e764fd437bdafa05f5922b1fbbb77c6817ccf50748419477e843d5bac67e6a70e97dde5a57e0c983b777e1ad31a80482dadf89de6302b1988c82c29544c9c07bb910596158f6062517eb089a2f54c9a0f348752950094d3228d3b940258c75fe2a413cb70baa21dc2e352fc5", false)]
88+
#[case::fail_wrong_msg_3("f775723953ead4a90411a02908fd1a629db584bc600664c609061f221ef6bf7c440066c8626b49daaa7bf2bcc0b74be4f7a1e3dcf0e869f1542fe821498cbf2de73ad398194129f635de4424a07ca715838aefe8fe69d1a391cfa70470795a80dd056866e6e1125aff94413921880c437c9e2570a28ced7267c8beef7e9b2d8d1547d76dfcf4bee592f5fefe10ddfb6aeb0991c5b9dbbee6ec80d11b17c0eb1a", false)]
89+
#[case::fail_wrong_msg_4("c5a77e7a90aa14e0bf5f337f06f597148676424fae26e175c6e5621c34351955289f319789da424845c9eac935245fcddd805950e2f02506d09be7e411199556d262144475b1fa46ad85250728c600c53dfd10f8b3f4adf140e27241aec3c2da3a81046703fccf468b48b145f939efdbb96c3786db712b3113bb2488ef286cdcef8afe82d200a5bb36b5462166e8ce77f2d831a52ef2135b2af188110beaefb1", false)]
90+
#[case::fail_wrong_msg_5("958b991cfd78f16537fe6d1f4afd10273384db08bdfc843562a22b0626766686f6aec8247599f40bfe01bec0e0ecf17b4319559022d4d9bf007fe929943004eb4866760dedf31b7c691f5ce665f8aae0bda895c23595c834fecc2390a5bcc203b04afcacbb4280713287a2d0c37e23f7513fab898f2c1fefa00ec09a924c335d9b629f1d4fb71901c3e59611afbfea354d101324e894c788d1c01f00b3c251b2", false)]
91+
#[case::fail_short_input_1("4cee90eb86eaa050036147a12d49004b6a", false)]
92+
#[case::fail_short_input_2("4cee90eb86eaa050036147a12d49004b6a958b991cfd78f16537fe6d1f4afd10273384db08bdfc843562a22b0626766686f6aec8247599f40bfe01bec0e0ecf17b4319559022d4d9bf007fe929943004eb4866760dedf319", false)]
93+
#[case::fail_long_input("4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e00", false)]
94+
#[case::fail_invalid_sig("4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e", false)]
95+
#[case::fail_invalid_pubkey("4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", false)]
96+
fn test_sig_verify(#[case] input: &str, #[case] expect_success: bool) {
97+
let input = Bytes::from_hex(input).unwrap();
98+
let target_gas = 3_500u64;
99+
let (gas_used, res) = p256_verify(&input, target_gas).unwrap();
100+
assert_eq!(gas_used, 3_450u64);
101+
let expected_result = if expect_success {
102+
B256::with_last_byte(1).into()
103+
} else {
104+
Bytes::new()
105+
};
106+
assert_eq!(res, expected_result);
107+
}
108+
109+
#[rstest]
110+
fn test_not_enough_gas_errors() {
111+
let input = Bytes::from_hex("4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e").unwrap();
112+
let target_gas = 2_500u64;
113+
let result = p256_verify(&input, target_gas);
114+
115+
assert!(result.is_err());
116+
assert_eq!(result.err(), Some(PrecompileError::OutOfGas));
117+
}
118+
119+
#[rstest]
120+
#[case::ok_1("b5a77e7a90aa14e0bf5f337f06f597148676424fae26e175c6e5621c34351955289f319789da424845c9eac935245fcddd805950e2f02506d09be7e411199556d262144475b1fa46ad85250728c600c53dfd10f8b3f4adf140e27241aec3c2da3a81046703fccf468b48b145f939efdbb96c3786db712b3113bb2488ef286cdcef8afe82d200a5bb36b5462166e8ce77f2d831a52ef2135b2af188110beaefb1", true)]
121+
#[case::fail_1("b5a77e7a90aa14e0bf5f337f06f597148676424fae26e175c6e5621c34351955289f319789da424845c9eac935245fcddd805950e2f02506d09be7e411199556d262144475b1fa46ad85250728c600c53dfd10f8b3f4adf140e27241aec3c2daaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaef8afe82d200a5bb36b5462166e8ce77f2d831a52ef2135b2af188110beaefb1", false)]
122+
fn test_verify_impl(#[case] input: &str, #[case] expect_success: bool) {
123+
let input = Bytes::from_hex(input).unwrap();
124+
let result = verify_impl(&input);
125+
126+
assert_eq!(result.is_some(), expect_success);
127+
}
128+
}

0 commit comments

Comments
 (0)