Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(optimism): Add secp256r1 precompile for Fjord #1436

Merged
merged 12 commits into from
May 26, 2024
22 changes: 22 additions & 0 deletions Cargo.lock

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

8 changes: 7 additions & 1 deletion crates/precompile/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ secp256k1 = { version = "0.29.0", default-features = false, features = [
# BLS12-381 precompiles
blst = { version = "0.3.11", optional = true }

# p256verify precompile
p256 = { version = "0.13.2", optional = true, default-features = false, features = ["ecdsa"] }

[dev-dependencies]
criterion = { version = "0.5" }
rand = { version = "0.8", features = ["std"] }
Expand All @@ -67,7 +70,7 @@ std = [
hashbrown = ["revm-primitives/hashbrown"]
asm-keccak = ["revm-primitives/asm-keccak"]

optimism = ["revm-primitives/optimism"]
optimism = ["revm-primitives/optimism", "secp256r1"]
# Optimism default handler enabled Optimism handler register by default in EvmBuilder.
optimism-default-handler = [
"optimism",
Expand All @@ -77,6 +80,9 @@ negate-optimism-default-handler = [
"revm-primitives/negate-optimism-default-handler",
]

# Enables the p256verify precompile.
secp256r1 = ["dep:p256"]

# These libraries may not work on all no_std platforms as they depend on C.

# Enables the KZG point evaluation precompile.
Expand Down
4 changes: 3 additions & 1 deletion crates/precompile/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ pub mod identity;
pub mod kzg_point_evaluation;
pub mod modexp;
pub mod secp256k1;
#[cfg(feature = "secp256r1")]
pub mod secp256r1;
pub mod utilities;

use core::hash::Hash;
Expand Down Expand Up @@ -271,7 +273,7 @@ impl PrecompileSpecId {
#[cfg(feature = "optimism")]
BEDROCK | REGOLITH | CANYON => Self::BERLIN,
#[cfg(feature = "optimism")]
ECOTONE => Self::CANCUN,
ECOTONE | FJORD => Self::CANCUN,
}
}
}
Expand Down
119 changes: 119 additions & 0 deletions crates/precompile/src/secp256r1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
//! # EIP-7212 secp256r1 Precompile
//!
//! This module implements the [EIP-7212](https://eips.ethereum.org/EIPS/eip-7212) precompile for
//! secp256r1 curve support.
//!
//! The main purpose of this precompile is to verify ECDSA signatures that use the secp256r1, or
//! P256 elliptic curve. The [`P256VERIFY`] const represents the implementation of this precompile,
//! with the address that it is currently deployed at.
use crate::{u64_to_address, Precompile, PrecompileWithAddress};
use p256::ecdsa::{signature::hazmat::PrehashVerifier, Signature, VerifyingKey};
use revm_primitives::{Bytes, PrecompileError, PrecompileResult, B256};

/// Base gas fee for secp256r1 p256verify operation.
const P256VERIFY_BASE: u64 = 3450;

/// Returns the secp256r1 precompile with its address.
pub fn precompiles() -> impl Iterator<Item = PrecompileWithAddress> {
[P256VERIFY].into_iter()
}

/// [EIP-7212](https://eips.ethereum.org/EIPS/eip-7212#specification) secp256r1 precompile.
pub const P256VERIFY: PrecompileWithAddress =
PrecompileWithAddress(u64_to_address(0x100), Precompile::Standard(p256_verify));

/// secp256r1 precompile logic. It takes the input bytes sent to the precompile
/// and the gas limit. The output represents the result of verifying the
/// secp256r1 signature of the input.
///
/// The input is encoded as follows:
///
/// | signed message hash | r | s | public key x | public key y |
/// | :-----------------: | :-: | :-: | :----------: | :----------: |
/// | 32 | 32 | 32 | 32 | 32 |
pub fn p256_verify(input: &Bytes, gas_limit: u64) -> PrecompileResult {
if P256VERIFY_BASE > gas_limit {
return Err(PrecompileError::OutOfGas);
}
let result = verify_impl(input).is_some();
Ok((P256VERIFY_BASE, B256::with_last_byte(result as u8).into()))
BrianBland marked this conversation as resolved.
Show resolved Hide resolved
}

/// Returns `Some(())` if the signature included in the input byte slice is
/// valid, `None` otherwise.
pub fn verify_impl(input: &[u8]) -> Option<()> {
if input.len() != 160 {
return None;
}

// msg signed (msg is already the hash of the original message)
let msg = &input[..32];
// r, s: signature
let sig = &input[32..96];
// x, y: public key
let pk = &input[96..160];

// 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);

// Can fail only if the input is not exact length.
rakita marked this conversation as resolved.
Show resolved Hide resolved
let signature = match Signature::from_slice(sig).ok()?;
rakita marked this conversation as resolved.
Show resolved Hide resolved
// Can fail if the input is not valid, so we have to propagate the error.
let public_key = VerifyingKey::from_sec1_bytes(&uncompressed_pk).ok()?;
Copy link
Member

Choose a reason for hiding this comment

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

Have checked this part of the spec:
Note that many implementations use (0, 0) as the reference point at infinity, which is not on the curve and should therefore be rejected.

I have feed the VerifyingKey::from_sec1_bytes with pk = [0;64] and it fails to get key, this is expected behaviour, just wanting to note it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, added a test case for this


public_key.verify_prehash(msg, &signature).ok()
}

#[cfg(test)]
mod test {
use super::*;
use revm_primitives::hex::FromHex;
use rstest::rstest;

#[rstest]
// test vectors from https://github.com/daimo-eth/p256-verifier/tree/master/test-vectors
#[case::ok_1("4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e", true)]
#[case::ok_2("3fec5769b5cf4e310a7d150508e82fb8e3eda1c2c94c61492d3bd8aea99e06c9e22466e928fdccef0de49e3503d2657d00494a00e764fd437bdafa05f5922b1fbbb77c6817ccf50748419477e843d5bac67e6a70e97dde5a57e0c983b777e1ad31a80482dadf89de6302b1988c82c29544c9c07bb910596158f6062517eb089a2f54c9a0f348752950094d3228d3b940258c75fe2a413cb70baa21dc2e352fc5", true)]
#[case::ok_3("e775723953ead4a90411a02908fd1a629db584bc600664c609061f221ef6bf7c440066c8626b49daaa7bf2bcc0b74be4f7a1e3dcf0e869f1542fe821498cbf2de73ad398194129f635de4424a07ca715838aefe8fe69d1a391cfa70470795a80dd056866e6e1125aff94413921880c437c9e2570a28ced7267c8beef7e9b2d8d1547d76dfcf4bee592f5fefe10ddfb6aeb0991c5b9dbbee6ec80d11b17c0eb1a", true)]
#[case::ok_4("b5a77e7a90aa14e0bf5f337f06f597148676424fae26e175c6e5621c34351955289f319789da424845c9eac935245fcddd805950e2f02506d09be7e411199556d262144475b1fa46ad85250728c600c53dfd10f8b3f4adf140e27241aec3c2da3a81046703fccf468b48b145f939efdbb96c3786db712b3113bb2488ef286cdcef8afe82d200a5bb36b5462166e8ce77f2d831a52ef2135b2af188110beaefb1", true)]
#[case::ok_5("858b991cfd78f16537fe6d1f4afd10273384db08bdfc843562a22b0626766686f6aec8247599f40bfe01bec0e0ecf17b4319559022d4d9bf007fe929943004eb4866760dedf31b7c691f5ce665f8aae0bda895c23595c834fecc2390a5bcc203b04afcacbb4280713287a2d0c37e23f7513fab898f2c1fefa00ec09a924c335d9b629f1d4fb71901c3e59611afbfea354d101324e894c788d1c01f00b3c251b2", true)]
#[case::fail_wrong_msg_1("3cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e", false)]
#[case::fail_wrong_msg_2("afec5769b5cf4e310a7d150508e82fb8e3eda1c2c94c61492d3bd8aea99e06c9e22466e928fdccef0de49e3503d2657d00494a00e764fd437bdafa05f5922b1fbbb77c6817ccf50748419477e843d5bac67e6a70e97dde5a57e0c983b777e1ad31a80482dadf89de6302b1988c82c29544c9c07bb910596158f6062517eb089a2f54c9a0f348752950094d3228d3b940258c75fe2a413cb70baa21dc2e352fc5", false)]
#[case::fail_wrong_msg_3("f775723953ead4a90411a02908fd1a629db584bc600664c609061f221ef6bf7c440066c8626b49daaa7bf2bcc0b74be4f7a1e3dcf0e869f1542fe821498cbf2de73ad398194129f635de4424a07ca715838aefe8fe69d1a391cfa70470795a80dd056866e6e1125aff94413921880c437c9e2570a28ced7267c8beef7e9b2d8d1547d76dfcf4bee592f5fefe10ddfb6aeb0991c5b9dbbee6ec80d11b17c0eb1a", false)]
#[case::fail_wrong_msg_4("c5a77e7a90aa14e0bf5f337f06f597148676424fae26e175c6e5621c34351955289f319789da424845c9eac935245fcddd805950e2f02506d09be7e411199556d262144475b1fa46ad85250728c600c53dfd10f8b3f4adf140e27241aec3c2da3a81046703fccf468b48b145f939efdbb96c3786db712b3113bb2488ef286cdcef8afe82d200a5bb36b5462166e8ce77f2d831a52ef2135b2af188110beaefb1", false)]
#[case::fail_wrong_msg_5("958b991cfd78f16537fe6d1f4afd10273384db08bdfc843562a22b0626766686f6aec8247599f40bfe01bec0e0ecf17b4319559022d4d9bf007fe929943004eb4866760dedf31b7c691f5ce665f8aae0bda895c23595c834fecc2390a5bcc203b04afcacbb4280713287a2d0c37e23f7513fab898f2c1fefa00ec09a924c335d9b629f1d4fb71901c3e59611afbfea354d101324e894c788d1c01f00b3c251b2", false)]
#[case::fail_short_input_1("4cee90eb86eaa050036147a12d49004b6a", false)]
#[case::fail_short_input_2("4cee90eb86eaa050036147a12d49004b6a958b991cfd78f16537fe6d1f4afd10273384db08bdfc843562a22b0626766686f6aec8247599f40bfe01bec0e0ecf17b4319559022d4d9bf007fe929943004eb4866760dedf319", false)]
#[case::fail_long_input("4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e00", false)]
#[case::fail_invalid_sig("4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e", 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 = B256::with_last_byte(expect_success as u8);
assert_eq!(res, expected_result.to_vec());
}

#[rstest]
fn test_not_enough_gas_errors() {
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));
}

#[rstest]
#[case::ok_1("b5a77e7a90aa14e0bf5f337f06f597148676424fae26e175c6e5621c34351955289f319789da424845c9eac935245fcddd805950e2f02506d09be7e411199556d262144475b1fa46ad85250728c600c53dfd10f8b3f4adf140e27241aec3c2da3a81046703fccf468b48b145f939efdbb96c3786db712b3113bb2488ef286cdcef8afe82d200a5bb36b5462166e8ce77f2d831a52ef2135b2af188110beaefb1", true)]
#[case::fail_1("b5a77e7a90aa14e0bf5f337f06f597148676424fae26e175c6e5621c34351955289f319789da424845c9eac935245fcddd805950e2f02506d09be7e411199556d262144475b1fa46ad85250728c600c53dfd10f8b3f4adf140e27241aec3c2daaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaef8afe82d200a5bb36b5462166e8ce77f2d831a52ef2135b2af188110beaefb1", false)]
fn test_verify_impl(#[case] input: &str, #[case] expect_success: bool) {
let input = Bytes::from_hex(input).unwrap();
let result = verify_impl(&input);

assert_eq!(result.is_some(), expect_success);
}
}
43 changes: 42 additions & 1 deletion crates/primitives/src/specification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ pub enum SpecId {
CANYON = 19,
CANCUN = 20,
ECOTONE = 21,
PRAGUE = 22,
FJORD = 22,
PRAGUE = 23,
#[default]
LATEST = u8::MAX,
}
Expand Down Expand Up @@ -114,6 +115,8 @@ impl From<&str> for SpecId {
"Canyon" => SpecId::CANYON,
#[cfg(feature = "optimism")]
"Ecotone" => SpecId::ECOTONE,
#[cfg(feature = "optimism")]
"Fjord" => SpecId::FJORD,
_ => Self::LATEST,
}
}
Expand Down Expand Up @@ -149,6 +152,8 @@ impl From<SpecId> for &'static str {
SpecId::CANYON => "Canyon",
#[cfg(feature = "optimism")]
SpecId::ECOTONE => "Ecotone",
#[cfg(feature = "optimism")]
SpecId::FJORD => "Fjord",
SpecId::LATEST => "Latest",
}
}
Expand Down Expand Up @@ -207,6 +212,8 @@ spec!(REGOLITH, RegolithSpec);
spec!(CANYON, CanyonSpec);
#[cfg(feature = "optimism")]
spec!(ECOTONE, EcotoneSpec);
#[cfg(feature = "optimism")]
spec!(FJORD, FjordSpec);

#[cfg(not(feature = "optimism"))]
#[macro_export]
Expand Down Expand Up @@ -354,6 +361,10 @@ macro_rules! spec_to_generic {
use $crate::EcotoneSpec as SPEC;
$e
}
$crate::SpecId::FJORD => {
use $crate::FjordSpec as SPEC;
$e
}
}
}};
}
Expand Down Expand Up @@ -390,6 +401,10 @@ mod tests {
#[cfg(feature = "optimism")]
spec_to_generic!(CANYON, assert_eq!(SPEC::SPEC_ID, CANYON));
spec_to_generic!(CANCUN, assert_eq!(SPEC::SPEC_ID, CANCUN));
#[cfg(feature = "optimism")]
spec_to_generic!(ECOTONE, assert_eq!(SPEC::SPEC_ID, ECOTONE));
#[cfg(feature = "optimism")]
spec_to_generic!(FJORD, assert_eq!(SPEC::SPEC_ID, FJORD));
spec_to_generic!(PRAGUE, assert_eq!(SPEC::SPEC_ID, PRAGUE));
spec_to_generic!(LATEST, assert_eq!(SPEC::SPEC_ID, LATEST));
}
Expand Down Expand Up @@ -485,4 +500,30 @@ mod optimism_tests {
assert!(SpecId::enabled(SpecId::ECOTONE, SpecId::CANYON));
assert!(SpecId::enabled(SpecId::ECOTONE, SpecId::ECOTONE));
}

#[test]
fn test_fjord_post_merge_hardforks() {
assert!(FjordSpec::enabled(SpecId::MERGE));
assert!(FjordSpec::enabled(SpecId::SHANGHAI));
assert!(FjordSpec::enabled(SpecId::CANCUN));
assert!(!FjordSpec::enabled(SpecId::LATEST));
assert!(FjordSpec::enabled(SpecId::BEDROCK));
assert!(FjordSpec::enabled(SpecId::REGOLITH));
assert!(FjordSpec::enabled(SpecId::CANYON));
assert!(FjordSpec::enabled(SpecId::ECOTONE));
assert!(FjordSpec::enabled(SpecId::FJORD));
}

#[test]
fn test_fjord_post_merge_hardforks_spec_id() {
assert!(SpecId::enabled(SpecId::FJORD, SpecId::MERGE));
assert!(SpecId::enabled(SpecId::FJORD, SpecId::SHANGHAI));
assert!(SpecId::enabled(SpecId::FJORD, SpecId::CANCUN));
assert!(!SpecId::enabled(SpecId::FJORD, SpecId::LATEST));
assert!(SpecId::enabled(SpecId::FJORD, SpecId::BEDROCK));
assert!(SpecId::enabled(SpecId::FJORD, SpecId::REGOLITH));
assert!(SpecId::enabled(SpecId::FJORD, SpecId::CANYON));
assert!(SpecId::enabled(SpecId::FJORD, SpecId::ECOTONE));
assert!(SpecId::enabled(SpecId::FJORD, SpecId::FJORD));
}
}
2 changes: 1 addition & 1 deletion crates/revm/src/db/in_memory_db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,7 @@ mod tests {
let serialized = serde_json::to_string(&init_state).unwrap();
let deserialized: CacheDB<EmptyDB> = serde_json::from_str(&serialized).unwrap();

assert!(deserialized.accounts.get(&account).is_some());
assert!(deserialized.accounts.contains_key(&account));
assert_eq!(
deserialized.accounts.get(&account).unwrap().info.nonce,
nonce
Expand Down
4 changes: 2 additions & 2 deletions crates/revm/src/optimism.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ mod handler_register;
mod l1block;

pub use handler_register::{
deduct_caller, end, last_frame_return, load_accounts, optimism_handle_register, output,
reward_beneficiary, validate_env, validate_tx_against_state,
deduct_caller, end, last_frame_return, load_accounts, load_precompiles,
optimism_handle_register, output, reward_beneficiary, validate_env, validate_tx_against_state,
};
pub use l1block::{L1BlockInfo, BASE_FEE_RECIPIENT, L1_BLOCK_CONTRACT, L1_FEE_RECIPIENT};
20 changes: 19 additions & 1 deletion crates/revm/src/optimism/handler_register.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ use crate::{
db::Database, spec_to_generic, Account, EVMError, Env, ExecutionResult, HaltReason,
HashMap, InvalidTransaction, ResultAndState, Spec, SpecId, SpecId::REGOLITH, U256,
},
Context, FrameResult,
Context, ContextPrecompiles, FrameResult,
};
use core::ops::Mul;
use revm_precompile::{secp256r1, PrecompileSpecId, Precompiles};
use std::string::ToString;
use std::sync::Arc;

Expand All @@ -23,6 +24,8 @@ pub fn optimism_handle_register<DB: Database, EXT>(handler: &mut EvmHandler<'_,
handler.validation.env = Arc::new(validate_env::<SPEC, DB>);
// Validate transaction against state.
handler.validation.tx_against_state = Arc::new(validate_tx_against_state::<SPEC, EXT, DB>);
// Load additional precompiles for the given chain spec.
handler.pre_execution.load_precompiles = Arc::new(load_precompiles::<SPEC, EXT, DB>);
// load l1 data
handler.pre_execution.load_accounts = Arc::new(load_accounts::<SPEC, EXT, DB>);
// An estimated batch cost is charged from the caller and added to L1 Fee Vault.
Expand Down Expand Up @@ -137,6 +140,21 @@ pub fn last_frame_return<SPEC: Spec, EXT, DB: Database>(
Ok(())
}

/// Load precompiles for Optimism chain.
#[inline]
pub fn load_precompiles<SPEC: Spec, EXT, DB: Database>() -> ContextPrecompiles<DB> {
let mut precompiles = Precompiles::new(PrecompileSpecId::from_spec_id(SPEC::SPEC_ID)).clone();

if SPEC::enabled(SpecId::FJORD) {
precompiles.extend([
// EIP-7212: secp256r1 P256verify
secp256r1::P256VERIFY,
])
}

precompiles.into()
}

/// Load account (make them warm) and l1 data from database.
#[inline]
pub fn load_accounts<SPEC: Spec, EXT, DB: Database>(
Expand Down
Loading