diff --git a/Cargo.lock b/Cargo.lock index 5d619dffc..cb37053b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -442,7 +442,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976" dependencies = [ "data-encoding", - "syn 2.0.106", + "syn 1.0.109", ] [[package]] @@ -804,7 +804,9 @@ dependencies = [ "num-derive", "num-traits", "once_cell", + "p256", "rand 0.8.5", + "rstest", "serde", "serde_json", "substrate-bn", @@ -1256,6 +1258,49 @@ dependencies = [ "thiserror", ] +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-macro", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "fvm_actor_utils" version = "14.0.0" @@ -1827,6 +1872,18 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[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 = "paste" version = "1.0.15" @@ -1878,6 +1935,18 @@ dependencies = [ "sha2", ] +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -1897,6 +1966,15 @@ dependencies = [ "log", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro-crate" version = "3.3.0" @@ -2044,6 +2122,12 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "relative-path" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" + [[package]] name = "rfc6979" version = "0.4.0" @@ -2073,17 +2157,46 @@ dependencies = [ "rustc-hex", ] +[[package]] +name = "rstest" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5a3193c063baaa2a95a33f03035c8a72b83d97a54916055ba22d35ed3839d49" +dependencies = [ + "futures-timer", + "futures-util", + "rstest_macros", +] + +[[package]] +name = "rstest_macros" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c845311f0ff7951c5506121a9ad75aec44d083c31583b2ea5a30bcb0b0abba0" +dependencies = [ + "cfg-if", + "glob", + "proc-macro-crate", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version", + "syn 2.0.106", + "unicode-ident", +] + [[package]] name = "ruint" -version = "1.14.0" +version = "1.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78a46eb779843b2c4f21fac5773e25d6d5b7c8f0922876c91541790d2ca27eef" +checksum = "c141e807189ad38a07276942c6623032d3753c8859c146104ac2e4d68865945a" dependencies = [ "proptest", "rand 0.8.5", "rand 0.9.0", "ruint-macro", - "serde", + "serde_core", "valuable", "zeroize", ] @@ -2100,6 +2213,15 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustversion" version = "1.0.20" @@ -2131,6 +2253,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + [[package]] name = "serde" version = "1.0.226" @@ -2263,6 +2391,12 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + [[package]] name = "snafu" version = "0.7.5" diff --git a/actors/evm/Cargo.toml b/actors/evm/Cargo.toml index de100f92d..0b1106a9f 100644 --- a/actors/evm/Cargo.toml +++ b/actors/evm/Cargo.toml @@ -15,6 +15,7 @@ exclude = ["/precompile-testdata", "/tests/measurements", "/tests/contracts"] crate-type = ["cdylib", "lib"] [dependencies] +p256 = { version = "0.13.2", features = ["ecdsa"], default-features = false } fil_actors_runtime = { workspace = true } fvm_shared = { workspace = true } fvm_ipld_kamt = { workspace = true } @@ -34,6 +35,7 @@ hex-literal = { workspace = true } substrate-bn = { workspace = true } thiserror = { workspace = true } blst = "0.3.14" +alloy-core = { workspace = true } [dev-dependencies] hex = { workspace = true, features = ["serde"] } @@ -44,6 +46,7 @@ alloy-core = { workspace = true } serde_json = { workspace = true } rand = { workspace = true } once_cell = { workspace = true } +rstest = "0.26.0" [features] diff --git a/actors/evm/src/interpreter/precompiles/mod.rs b/actors/evm/src/interpreter/precompiles/mod.rs index 4e85684cb..57899d572 100644 --- a/actors/evm/src/interpreter/precompiles/mod.rs +++ b/actors/evm/src/interpreter/precompiles/mod.rs @@ -14,6 +14,7 @@ mod bls12_381; mod bls_util; mod evm; mod fvm; +mod secp256r1; use bls12_381::{ bls12_g1add, bls12_g1msm, bls12_g2add, bls12_g2msm, bls12_map_fp_to_g1, bls12_map_fp2_to_g2, @@ -21,6 +22,7 @@ use bls12_381::{ }; use evm::{blake2f, ec_add, ec_mul, ec_pairing, ec_recover, identity, modexp, ripemd160, sha256}; use fvm::{call_actor, call_actor_id, get_randomness, lookup_delegated_address, resolve_address}; +use secp256r1::p256_verify; type PrecompileFn = fn(&mut System, &[u8], PrecompileContext) -> PrecompileResult; pub type PrecompileResult = Result, PrecompileError>; @@ -38,6 +40,16 @@ impl PrecompileTable { } pub fn is_reserved_precompile_address(addr: &EthAddress) -> bool { + // Special case for secp256r1 (P-256) `P256VERIFY` precompile at 0x0100 (EIP-7951 / RIP-7212 interface). + if addr.0 + == [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + ] + { + return true; + } + let [prefix, middle @ .., index] = addr.0; (prefix == 0x00 || prefix == NATIVE_PRECOMPILE_ADDRESS_PREFIX) && middle == [0u8; 18] @@ -80,6 +92,15 @@ impl Precompiles { ]); fn lookup_precompile(addr: &EthAddress) -> Option> { + // Special-case secp256r1 (P-256) `P256VERIFY` precompile at 0x...0100 (EIP-7951 / RIP-7212 interface). + if addr.0[0] == 0x00 + && addr.0[1..18] == [0u8; 17] + && addr.0[18] == 0x01 + && addr.0[19] == 0x00 + { + return Some(p256_verify::); + } + let [prefix, _m @ .., index] = addr.0; if is_reserved_precompile_address(addr) { let index = index as usize - 1; @@ -217,6 +238,13 @@ mod test { assert!(!is_reserved_precompile_address(&addr)); } + #[test] + fn is_rip_7212_precompile() { + let addr = EthAddress(hex_literal::hex!("0000000000000000000000000000000000000100")); + assert!(Precompiles::::is_precompile(&addr)); + assert!(is_reserved_precompile_address(&addr)); + } + #[test] fn zero_addr_precompile() { let eth_addr = EthAddress(hex_literal::hex!("fe00000000000000000000000000000000000000")); diff --git a/actors/evm/src/interpreter/precompiles/secp256r1.rs b/actors/evm/src/interpreter/precompiles/secp256r1.rs new file mode 100644 index 000000000..ab6387b69 --- /dev/null +++ b/actors/evm/src/interpreter/precompiles/secp256r1.rs @@ -0,0 +1,175 @@ +//! # secp256r1 (P-256) Precompile (`P256VERIFY`, `0x0100`) +//! +//! This module implements secp256r1 (P-256) ECDSA signature verification for the FEVM precompile at `0x0100`. +//! +//! The precompile is specified by Ethereum's [EIP-7951](https://eips.ethereum.org/EIPS/eip-7951) and is interface-compatible +//! with [RIP-7212](https://github.com/ethereum/RIPs/blob/master/RIPS/rip-7212.md). +//! +//! The main purpose of this precompile is to verify ECDSA signatures that use the secp256r1, or +//! P256 elliptic curve. The [`p256_verify`] function represents the implementation of this +//! precompile. +use fil_actors_runtime::runtime::Runtime; +use p256::{ + EncodedPoint, + ecdsa::{Signature, VerifyingKey, signature::hazmat::PrehashVerifier}, +}; + +use alloy_core::primitives::{B256, B512}; + +use super::{PrecompileContext, PrecompileResult}; +use crate::interpreter::System; + +/// P256 verify precompile function +pub fn p256_verify( + _system: &mut System, + input: &[u8], + _context: PrecompileContext, +) -> PrecompileResult { + p256_verify_inner(input) +} + +/// The input is encoded as follows: +/// +/// | signed message hash | r | s | public key x | public key y | +/// | :-----------------: | :-: | :-: | :----------: | :----------: | +/// | 32 | 32 | 32 | 32 | 32 | +fn p256_verify_inner(input: &[u8]) -> PrecompileResult { + if verify_impl(input) { + // Return 32 bytes with last byte set to 1 for success + Ok(B256::with_last_byte(1).to_vec()) + } else { + // Return empty vector for failure + Ok(vec![]) + } +} + +/// Returns `true` if the signature included in the input byte slice is +/// valid, `false` otherwise. +pub fn verify_impl(input: &[u8]) -> bool { + if input.len() != 160 { + return false; + } + + // msg signed (msg is already the hash of the original message) + let msg = <&B256>::try_from(&input[..32]).unwrap(); + // r, s: signature + let sig = <&B512>::try_from(&input[32..96]).unwrap(); + // x, y: public key + let pk = <&B512>::try_from(&input[96..160]).unwrap(); + + verify_signature(msg.0, sig.0, pk.0).is_some() +} + +pub(crate) fn verify_signature(msg: [u8; 32], sig: [u8; 64], pk: [u8; 64]) -> Option<()> { + // Can fail only if the input is not exact length. + let signature = Signature::from_slice(&sig).ok()?; + // Decode the public key bytes (x,y coordinates) using EncodedPoint + let encoded_point = EncodedPoint::from_untagged_bytes(&pk.into()); + // Create VerifyingKey from the encoded point + let public_key = VerifyingKey::from_encoded_point(&encoded_point).ok()?; + + public_key.verify_prehash(&msg, &signature).ok() +} + +#[cfg(test)] +mod test { + use super::*; + use crate::interpreter::System; + use crate::interpreter::precompiles::PrecompileContext; + use fil_actors_runtime::test_utils::MockRuntime; + 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 + )] + #[case::fail_invalid_pubkey( + "4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + false + )] + fn test_sig_verify(#[case] input: &str, #[case] expect_success: bool) { + let rt = MockRuntime::default(); + rt.in_call.replace(true); + let mut system = System::create(&rt).unwrap(); + + // let input = Bytes::from_hex(input).unwrap(); + let input_bytes = hex::decode(input).unwrap(); + let outcome = p256_verify(&mut system, &input_bytes, PrecompileContext::default()).unwrap(); + + let expected_result = if expect_success { + // Return 32 bytes with last byte set to 1 for success + B256::with_last_byte(1).to_vec() + } else { + // Return empty vector for failure + vec![] + }; + assert_eq!(outcome, expected_result); + } + + #[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 = hex::decode(input).unwrap(); + let result = verify_impl(&input_bytes); + + assert_eq!(result, expect_success); + } +} diff --git a/actors/evm/tests/basic.rs b/actors/evm/tests/basic.rs index 518434d7b..b488b4364 100644 --- a/actors/evm/tests/basic.rs +++ b/actors/evm/tests/basic.rs @@ -380,3 +380,37 @@ fn bls_precompile_test(bytecode: Vec) { rt.expect_gas_available(10_000_000_000u64); util::invoke_contract(&rt, &pairing_failure_params); } + +#[test] +fn test_secp256r1_precompile_all_solidity_functions() { + // Load the compiled Secp256r1Precompile.sol contract + let contract_hex = include_str!("contracts/Secp256r1Precompile.hex"); + let contract_bytecode = hex::decode(contract_hex).expect("Failed to decode contract hex"); + + let rt = util::construct_and_verify(contract_bytecode); + + // Test all valid signature functions (only testOk1 remains) + let ok_functions = [("testOk1", "9c6662a5")]; + + for (name, selector) in ok_functions.iter() { + rt.expect_gas_available(10_000_000_000u64); + let result = util::invoke_contract(&rt, &hex::decode(selector).unwrap()); + println!("{} completed successfully", name); + assert!(result.is_empty(), "{} should not return data or revert", name); + } + + // Test all failure functions (reduced to 5 functions) + let fail_functions = [ + ("testFailWrongMsg1", "f85b4231"), + ("testFailShortInput1", "e249e66c"), + ("testFailLongInput", "dc860e27"), + ("testFailInvalidSig", "93ed6a1b"), + ("testFailInvalidPubkey", "88da55f6"), + ]; + + for (name, selector) in fail_functions.iter() { + rt.expect_gas_available(10_000_000_000u64); + let result = util::invoke_contract(&rt, &hex::decode(selector).unwrap()); + assert!(result.is_empty(), "{} should not return data or revert", name); + } +} diff --git a/actors/evm/tests/contracts/Secp256r1Precompile.hex b/actors/evm/tests/contracts/Secp256r1Precompile.hex new file mode 100644 index 000000000..b8056e23b --- /dev/null +++ b/actors/evm/tests/contracts/Secp256r1Precompile.hex @@ -0,0 +1 @@ +6080604052348015600e575f5ffd5b50610e3d8061001c5f395ff3fe608060405234801561000f575f5ffd5b5060043610610060575f3560e01c806388da55f61461006457806393ed6a1b1461006e5780639c6662a514610078578063dc860e2714610082578063e249e66c1461008c578063f85b423114610096575b5f5ffd5b61006c6100a0565b005b6100766101af565b005b6100806102be565b005b61008a61041c565b005b61009461052b565b005b61009e610657565b005b5f6040518060c0016040528060a08152602001610b8760a0913990505f5f61010073ffffffffffffffffffffffffffffffffffffffff16836040516100e591906107b8565b5f60405180830381855afa9150503d805f811461011d576040519150601f19603f3d011682016040523d82523d5f602084013e610122565b606091505b509150915081610167576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161015e90610828565b60405180910390fd5b5f8151146101aa576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101a1906108b6565b60405180910390fd5b505050565b5f6040518060c0016040528060a08152602001610c2760a0913990505f5f61010073ffffffffffffffffffffffffffffffffffffffff16836040516101f491906107b8565b5f60405180830381855afa9150503d805f811461022c576040519150601f19603f3d011682016040523d82523d5f602084013e610231565b606091505b509150915081610276576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161026d90610828565b60405180910390fd5b5f8151146102b9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016102b090610944565b60405180910390fd5b505050565b5f6040518060c0016040528060a08152602001610d6860a0913990505f5f61010073ffffffffffffffffffffffffffffffffffffffff168360405161030391906107b8565b5f60405180830381855afa9150503d805f811461033b576040519150601f19603f3d011682016040523d82523d5f602084013e610340565b606091505b509150915081610385576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161037c90610828565b60405180910390fd5b60208151146103c9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103c0906109d2565b60405180910390fd5b5f6020820151905060015f1b8114610416576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161040d90610a3a565b60405180910390fd5b50505050565b5f6040518060e0016040528060a18152602001610cc760a1913990505f5f61010073ffffffffffffffffffffffffffffffffffffffff168360405161046191906107b8565b5f60405180830381855afa9150503d805f8114610499576040519150601f19603f3d011682016040523d82523d5f602084013e61049e565b606091505b5091509150816104e3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104da90610828565b60405180910390fd5b5f815114610526576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161051d90610ac8565b60405180910390fd5b505050565b5f6040518060400160405280601181526020017f4cee90eb86eaa050036147a12d49004b6a00000000000000000000000000000081525090505f5f61010073ffffffffffffffffffffffffffffffffffffffff168360405161058d91906107b8565b5f60405180830381855afa9150503d805f81146105c5576040519150601f19603f3d011682016040523d82523d5f602084013e6105ca565b606091505b50915091508161060f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161060690610828565b60405180910390fd5b5f815114610652576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161064990610ac8565b60405180910390fd5b505050565b5f6040518060c0016040528060a08152602001610ae760a0913990505f5f61010073ffffffffffffffffffffffffffffffffffffffff168360405161069c91906107b8565b5f60405180830381855afa9150503d805f81146106d4576040519150601f19603f3d011682016040523d82523d5f602084013e6106d9565b606091505b50915091508161071e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161071590610828565b60405180910390fd5b5f815114610761576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161075890610944565b60405180910390fd5b505050565b5f81519050919050565b5f81905092915050565b8281835e5f83830152505050565b5f61079282610766565b61079c8185610770565b93506107ac81856020860161077a565b80840191505092915050565b5f6107c38284610788565b915081905092915050565b5f82825260208201905092915050565b7f507265636f6d70696c652063616c6c206661696c6564000000000000000000005f82015250565b5f6108126016836107ce565b915061081d826107de565b602082019050919050565b5f6020820190508181035f83015261083f81610806565b9050919050565b7f496e76616c6964207075626c6963206b65792073686f756c642072657475726e5f8201527f2030206279746573000000000000000000000000000000000000000000000000602082015250565b5f6108a06028836107ce565b91506108ab82610846565b604082019050919050565b5f6020820190508181035f8301526108cd81610894565b9050919050565b7f496e76616c6964207369676e61747572652073686f756c642072657475726e205f8201527f3020627974657300000000000000000000000000000000000000000000000000602082015250565b5f61092e6027836107ce565b9150610939826108d4565b604082019050919050565b5f6020820190508181035f83015261095b81610922565b9050919050565b7f56616c6964207369676e61747572652073686f756c642072657475726e2033325f8201527f2062797465730000000000000000000000000000000000000000000000000000602082015250565b5f6109bc6026836107ce565b91506109c782610962565b604082019050919050565b5f6020820190508181035f8301526109e9816109b0565b9050919050565b7f56616c6964207369676e61747572652073686f756c642072657475726e2031005f82015250565b5f610a24601f836107ce565b9150610a2f826109f0565b602082019050919050565b5f6020820190508181035f830152610a5181610a18565b9050919050565b7f496e76616c696420696e7075742073686f756c642072657475726e20302062795f8201527f7465730000000000000000000000000000000000000000000000000000000000602082015250565b5f610ab26023836107ce565b9150610abd82610a58565b604082019050919050565b5f6020820190508181035f830152610adf81610aa6565b905091905056fe3cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e004cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10ea2646970667358221220193ecb9203e63bb5e343dd3148153583b08f02ac6c56cc41fb6b51498598b4ea64736f6c634300081e0033 \ No newline at end of file diff --git a/actors/evm/tests/contracts/Secp256r1Precompile.sol b/actors/evm/tests/contracts/Secp256r1Precompile.sol new file mode 100644 index 000000000..1ad8d33b9 --- /dev/null +++ b/actors/evm/tests/contracts/Secp256r1Precompile.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +contract Secp256r1PrecompileCheck { + address constant SECP256R1_VERIFY_PRECOMPILE = address(0x0100); + + /// @notice Test vector ok_1: Valid signature verification + function testOk1() public view { + bytes memory input = hex"4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e"; + + (bool success, bytes memory output) = SECP256R1_VERIFY_PRECOMPILE.staticcall(input); + + require(success, "Precompile call failed"); + require(output.length == 32, "Valid signature should return 32 bytes"); + + bytes32 result; + assembly { + result := mload(add(output, 0x20)) + } + require(result == bytes32(uint256(1)), "Valid signature should return 1"); + } + + /// @notice Test vector fail_wrong_msg_1: Invalid signature due to wrong message + function testFailWrongMsg1() public view { + bytes memory input = hex"3cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e"; + + (bool success, bytes memory output) = SECP256R1_VERIFY_PRECOMPILE.staticcall(input); + + require(success, "Precompile call failed"); + require(output.length == 0, "Invalid signature should return 0 bytes"); + } + + /// @notice Test vector fail_short_input_1: Input too short + function testFailShortInput1() public view { + bytes memory input = hex"4cee90eb86eaa050036147a12d49004b6a"; + + (bool success, bytes memory output) = SECP256R1_VERIFY_PRECOMPILE.staticcall(input); + + require(success, "Precompile call failed"); + require(output.length == 0, "Invalid input should return 0 bytes"); + } + + /// @notice Test vector fail_long_input: Input too long + function testFailLongInput() public view { + bytes memory input = hex"4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e00"; + + (bool success, bytes memory output) = SECP256R1_VERIFY_PRECOMPILE.staticcall(input); + + require(success, "Precompile call failed"); + require(output.length == 0, "Invalid input should return 0 bytes"); + } + + /// @notice Test vector fail_invalid_sig: Invalid signature + function testFailInvalidSig() public view { + bytes memory input = hex"4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e"; + + (bool success, bytes memory output) = SECP256R1_VERIFY_PRECOMPILE.staticcall(input); + + require(success, "Precompile call failed"); + require(output.length == 0, "Invalid signature should return 0 bytes"); + } + + /// @notice Test vector fail_invalid_pubkey: Invalid public key + function testFailInvalidPubkey() public view { + bytes memory input = hex"4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + + (bool success, bytes memory output) = SECP256R1_VERIFY_PRECOMPILE.staticcall(input); + + require(success, "Precompile call failed"); + require(output.length == 0, "Invalid public key should return 0 bytes"); + } +} diff --git a/actors/evm/tests/rip7212_precompile.rs b/actors/evm/tests/rip7212_precompile.rs new file mode 100644 index 000000000..e439f47ea --- /dev/null +++ b/actors/evm/tests/rip7212_precompile.rs @@ -0,0 +1,156 @@ +mod asm; +mod util; + +use fvm_shared::{METHOD_SEND, address::Address as FILAddress, econ::TokenAmount, error::ExitCode}; + +fn p256_input() -> Vec { + // Using a well-known test vector from the daimo-eth/p256-verifier test suite + // Test case: valid secp256r1 ECDSA signature verification + hex::decode("4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e").unwrap() +} + +fn p256_verify_contract_call() -> Vec { + // Call 0x0100 precompile with calldata as input (exact 160 bytes) + let init = ""; + let body = r#" + +calldatasize +push1 0x00 +push1 0x00 +calldatacopy + +# out size +push1 0x20 +# out off +push2 0xA000 + +# in size (160) +push1 0xA0 +# in off +push1 0x00 + +# value (0) +push1 0x00 + +# dst (0x0100) +push20 0x0000000000000000000000000000000000000100 + +# gas +push1 0x00 + +call + +# write exit code memory +push1 0x00 # offset +mstore8 + +returndatasize +push1 0x00 # input offset +push1 0x01 # dest offset +returndatacopy + +returndatasize +push1 0x01 +add +push1 0x00 +return +"#; + asm::new_contract("rip7212-precompile-caller", init, body).unwrap() +} + +#[test] +fn rip7212_call_success() { + let rt = util::construct_and_verify(p256_verify_contract_call()); + + let input = p256_input(); + let result = util::invoke_contract(&rt, &input); + let mut expected = [0u8; 32]; + expected[31] = 1; + assert_eq!(result[0], util::PrecompileExit::Success as u8); + assert_eq!(&result[1..], &expected); +} + +#[test] +fn rip7212_invalid_input_returns_empty() { + let rt = util::construct_and_verify(p256_verify_contract_call()); + let input = vec![0u8; 10]; + let result = util::invoke_contract(&rt, &input); + assert_eq!(result[0], util::PrecompileExit::Success as u8); + assert!(result[1..].is_empty()); +} + +fn p256_verify_contract_call_value() -> Vec { + let init = ""; + let body = r#" + +calldatasize +push1 0x00 +push1 0x00 +calldatacopy + +# out size +push1 0x20 +# out off +push2 0xA000 + +# in size (160) +push1 0xA0 +# in off +push1 0x00 + +# value (1 atto) +push1 0x01 + +# dst (0x0100) +push20 0x0000000000000000000000000000000000000100 + +# gas +push1 0x00 + +call + +# write exit code memory +push1 0x00 # offset +mstore8 + +returndatasize +push1 0x00 # input offset +push1 0x01 # dest offset +returndatacopy + +returndatasize +push1 0x01 +add +push1 0x00 +return +"#; + asm::new_contract("rip7212-precompile-caller-value", init, body).unwrap() +} + +#[test] +fn rip7212_call_with_value_transfers_on_success() { + let rt = util::construct_and_verify(p256_verify_contract_call_value()); + rt.set_balance(TokenAmount::from_atto(100)); + + let input = p256_input(); + let mut expected = [0u8; 32]; + expected[31] = 1; + + let addr = fil_actors_evm_shared::address::EthAddress(hex_literal::hex!( + "0000000000000000000000000000000000000100" + )); + let fil_addr = + FILAddress::new_delegated(fil_actors_runtime::EAM_ACTOR_ID, addr.as_ref()).unwrap(); + rt.expect_send_simple( + fil_addr, + METHOD_SEND, + None, + TokenAmount::from_atto(1), + None, + ExitCode::OK, + ); + + let result = util::invoke_contract(&rt, &input); + assert_eq!(result[0], util::PrecompileExit::Success as u8); + assert_eq!(&result[1..], &expected); +}