Skip to main content

revm_precompile/
secp256r1.rs

1//! # RIP-7212 secp256r1 Precompile
2//!
3//! This module implements the [RIP-7212](https://github.com/ethereum/RIPs/blob/master/RIPS/rip-7212.md) 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.
9use crate::{
10    crypto, eth_precompile_fn, u64_to_address, EthPrecompileOutput, EthPrecompileResult,
11    Precompile, PrecompileHalt, PrecompileId,
12};
13use primitives::{alloy_primitives::B512, Bytes, B256};
14
15/// Address of secp256r1 precompile.
16pub const P256VERIFY_ADDRESS: u64 = 256;
17
18/// Base gas fee for secp256r1 p256verify operation.
19pub const P256VERIFY_BASE_GAS_FEE: u64 = 3450;
20
21/// Base gas fee for secp256r1 p256verify operation post Osaka.
22pub const P256VERIFY_BASE_GAS_FEE_OSAKA: u64 = 6900;
23
24/// Returns the secp256r1 precompile with its address.
25pub fn precompiles() -> impl Iterator<Item = Precompile> {
26    [P256VERIFY].into_iter()
27}
28
29eth_precompile_fn!(p256verify_precompile, p256_verify);
30eth_precompile_fn!(p256verify_osaka_precompile, p256_verify_osaka);
31
32/// [RIP-7212](https://github.com/ethereum/RIPs/blob/master/RIPS/rip-7212.md#specification) secp256r1 precompile.
33pub const P256VERIFY: Precompile = Precompile::new(
34    PrecompileId::P256Verify,
35    u64_to_address(P256VERIFY_ADDRESS),
36    p256verify_precompile,
37);
38
39/// [RIP-7212](https://github.com/ethereum/RIPs/blob/master/RIPS/rip-7212.md#specification) secp256r1 precompile.
40pub const P256VERIFY_OSAKA: Precompile = Precompile::new(
41    PrecompileId::P256Verify,
42    u64_to_address(P256VERIFY_ADDRESS),
43    p256verify_osaka_precompile,
44);
45
46/// secp256r1 precompile logic. It takes the input bytes sent to the precompile
47/// and the gas limit. The output represents the result of verifying the
48/// secp256r1 signature of the input.
49///
50/// The input is encoded as follows:
51///
52/// | signed message hash |  r  |  s  | public key x | public key y |
53/// | :-----------------: | :-: | :-: | :----------: | :----------: |
54/// |          32         | 32  | 32  |     32       |      32      |
55pub fn p256_verify(input: &[u8], gas_limit: u64) -> EthPrecompileResult {
56    p256_verify_inner(input, gas_limit, P256VERIFY_BASE_GAS_FEE)
57}
58
59/// secp256r1 precompile logic with Osaka gas cost. It takes the input bytes sent to the precompile
60/// and the gas limit. The output represents the result of verifying the
61/// secp256r1 signature of the input.
62///
63/// The input is encoded as follows:
64///
65/// | signed message hash |  r  |  s  | public key x | public key y |
66/// | :-----------------: | :-: | :-: | :----------: | :----------: |
67/// |          32         | 32  | 32  |     32       |      32      |
68pub fn p256_verify_osaka(input: &[u8], gas_limit: u64) -> EthPrecompileResult {
69    p256_verify_inner(input, gas_limit, P256VERIFY_BASE_GAS_FEE_OSAKA)
70}
71
72fn p256_verify_inner(input: &[u8], gas_limit: u64, gas_cost: u64) -> EthPrecompileResult {
73    if gas_cost > gas_limit {
74        return Err(PrecompileHalt::OutOfGas);
75    }
76    let result = if verify_impl(input) {
77        B256::with_last_byte(1).into()
78    } else {
79        Bytes::new()
80    };
81    Ok(EthPrecompileOutput::new(gas_cost, result))
82}
83
84/// Returns `Some(())` if the signature included in the input byte slice is
85/// valid, `None` otherwise.
86pub fn verify_impl(input: &[u8]) -> bool {
87    if input.len() != 160 {
88        return false;
89    }
90
91    // msg signed (msg is already the hash of the original message)
92    let msg = <&B256>::try_from(&input[..32]).unwrap();
93    // r, s: signature
94    let sig = <&B512>::try_from(&input[32..96]).unwrap();
95    // x, y: public key
96    let pk = <&B512>::try_from(&input[96..160]).unwrap();
97
98    crypto().secp256r1_verify_signature(&msg.0, &sig.0, &pk.0)
99}
100
101pub(crate) fn verify_signature(msg: &[u8; 32], sig: &[u8; 64], pk: &[u8; 64]) -> Option<()> {
102    cfg_if::cfg_if! {
103        if #[cfg(feature = "p256-aws-lc-rs")] {
104            use aws_lc_rs::{digest, signature::{self, UnparsedPublicKey}};
105
106            // Construct a Digest from the raw prehashed message bytes.
107            let digest = digest::Digest::import_less_safe(msg, &digest::SHA256).ok()?;
108
109            // Build uncompressed public key: 0x04 || x || y
110            let mut pubkey_bytes = [0u8; 65];
111            pubkey_bytes[0] = 0x04;
112            pubkey_bytes[1..].copy_from_slice(pk);
113
114            let public_key = UnparsedPublicKey::new(&signature::ECDSA_P256_SHA256_FIXED, &pubkey_bytes);
115
116            public_key.verify_digest(&digest, sig).ok()
117        } else {
118            use p256::{
119                ecdsa::{signature::hazmat::PrehashVerifier, Signature, VerifyingKey},
120                EncodedPoint,
121            };
122
123            // Can fail only if the input is not exact length.
124            let signature = Signature::from_slice(sig).ok()?;
125            // Decode the public key bytes (x,y coordinates) using EncodedPoint
126            let encoded_point = EncodedPoint::from_untagged_bytes(&(*pk).into());
127            // Create VerifyingKey from the encoded point
128            let public_key = VerifyingKey::from_encoded_point(&encoded_point).ok()?;
129
130            public_key.verify_prehash(msg, &signature).ok()
131        }
132    }
133}
134
135#[cfg(test)]
136mod test {
137    use super::*;
138    use crate::PrecompileHalt;
139    use primitives::hex::FromHex;
140    use rstest::rstest;
141
142    #[rstest]
143    // Test vectors from https://github.com/daimo-eth/p256-verifier/tree/master/test-vectors
144    #[case::ok_1("4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e", true)]
145    #[case::ok_2("3fec5769b5cf4e310a7d150508e82fb8e3eda1c2c94c61492d3bd8aea99e06c9e22466e928fdccef0de49e3503d2657d00494a00e764fd437bdafa05f5922b1fbbb77c6817ccf50748419477e843d5bac67e6a70e97dde5a57e0c983b777e1ad31a80482dadf89de6302b1988c82c29544c9c07bb910596158f6062517eb089a2f54c9a0f348752950094d3228d3b940258c75fe2a413cb70baa21dc2e352fc5", true)]
146    #[case::ok_3("e775723953ead4a90411a02908fd1a629db584bc600664c609061f221ef6bf7c440066c8626b49daaa7bf2bcc0b74be4f7a1e3dcf0e869f1542fe821498cbf2de73ad398194129f635de4424a07ca715838aefe8fe69d1a391cfa70470795a80dd056866e6e1125aff94413921880c437c9e2570a28ced7267c8beef7e9b2d8d1547d76dfcf4bee592f5fefe10ddfb6aeb0991c5b9dbbee6ec80d11b17c0eb1a", true)]
147    #[case::ok_4("b5a77e7a90aa14e0bf5f337f06f597148676424fae26e175c6e5621c34351955289f319789da424845c9eac935245fcddd805950e2f02506d09be7e411199556d262144475b1fa46ad85250728c600c53dfd10f8b3f4adf140e27241aec3c2da3a81046703fccf468b48b145f939efdbb96c3786db712b3113bb2488ef286cdcef8afe82d200a5bb36b5462166e8ce77f2d831a52ef2135b2af188110beaefb1", true)]
148    #[case::ok_5("858b991cfd78f16537fe6d1f4afd10273384db08bdfc843562a22b0626766686f6aec8247599f40bfe01bec0e0ecf17b4319559022d4d9bf007fe929943004eb4866760dedf31b7c691f5ce665f8aae0bda895c23595c834fecc2390a5bcc203b04afcacbb4280713287a2d0c37e23f7513fab898f2c1fefa00ec09a924c335d9b629f1d4fb71901c3e59611afbfea354d101324e894c788d1c01f00b3c251b2", true)]
149    #[case::fail_wrong_msg_1("3cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e", false)]
150    #[case::fail_wrong_msg_2("afec5769b5cf4e310a7d150508e82fb8e3eda1c2c94c61492d3bd8aea99e06c9e22466e928fdccef0de49e3503d2657d00494a00e764fd437bdafa05f5922b1fbbb77c6817ccf50748419477e843d5bac67e6a70e97dde5a57e0c983b777e1ad31a80482dadf89de6302b1988c82c29544c9c07bb910596158f6062517eb089a2f54c9a0f348752950094d3228d3b940258c75fe2a413cb70baa21dc2e352fc5", false)]
151    #[case::fail_wrong_msg_3("f775723953ead4a90411a02908fd1a629db584bc600664c609061f221ef6bf7c440066c8626b49daaa7bf2bcc0b74be4f7a1e3dcf0e869f1542fe821498cbf2de73ad398194129f635de4424a07ca715838aefe8fe69d1a391cfa70470795a80dd056866e6e1125aff94413921880c437c9e2570a28ced7267c8beef7e9b2d8d1547d76dfcf4bee592f5fefe10ddfb6aeb0991c5b9dbbee6ec80d11b17c0eb1a", false)]
152    #[case::fail_wrong_msg_4("c5a77e7a90aa14e0bf5f337f06f597148676424fae26e175c6e5621c34351955289f319789da424845c9eac935245fcddd805950e2f02506d09be7e411199556d262144475b1fa46ad85250728c600c53dfd10f8b3f4adf140e27241aec3c2da3a81046703fccf468b48b145f939efdbb96c3786db712b3113bb2488ef286cdcef8afe82d200a5bb36b5462166e8ce77f2d831a52ef2135b2af188110beaefb1", false)]
153    #[case::fail_wrong_msg_5("958b991cfd78f16537fe6d1f4afd10273384db08bdfc843562a22b0626766686f6aec8247599f40bfe01bec0e0ecf17b4319559022d4d9bf007fe929943004eb4866760dedf31b7c691f5ce665f8aae0bda895c23595c834fecc2390a5bcc203b04afcacbb4280713287a2d0c37e23f7513fab898f2c1fefa00ec09a924c335d9b629f1d4fb71901c3e59611afbfea354d101324e894c788d1c01f00b3c251b2", false)]
154    #[case::fail_short_input_1("4cee90eb86eaa050036147a12d49004b6a", false)]
155    #[case::fail_short_input_2("4cee90eb86eaa050036147a12d49004b6a958b991cfd78f16537fe6d1f4afd10273384db08bdfc843562a22b0626766686f6aec8247599f40bfe01bec0e0ecf17b4319559022d4d9bf007fe929943004eb4866760dedf319", false)]
156    #[case::fail_long_input("4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e00", false)]
157    #[case::fail_invalid_sig("4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e", false)]
158    #[case::fail_invalid_pubkey("4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", false)]
159    fn test_sig_verify(#[case] input: &str, #[case] expect_success: bool) {
160        let input = Bytes::from_hex(input).unwrap();
161        let target_gas = 3_500u64;
162        let outcome = p256_verify(&input, target_gas).unwrap();
163        assert_eq!(outcome.gas_used, 3_450u64);
164        let expected_result = if expect_success {
165            B256::with_last_byte(1).into()
166        } else {
167            Bytes::new()
168        };
169        assert_eq!(outcome.bytes, expected_result);
170    }
171
172    #[rstest]
173    fn test_not_enough_gas_errors() {
174        let input = Bytes::from_hex("4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e").unwrap();
175        let target_gas = 2_500u64;
176        let result = p256_verify(&input, target_gas);
177
178        assert!(result.is_err());
179        assert_eq!(result.err(), Some(PrecompileHalt::OutOfGas));
180    }
181
182    #[rstest]
183    #[case::ok_1("b5a77e7a90aa14e0bf5f337f06f597148676424fae26e175c6e5621c34351955289f319789da424845c9eac935245fcddd805950e2f02506d09be7e411199556d262144475b1fa46ad85250728c600c53dfd10f8b3f4adf140e27241aec3c2da3a81046703fccf468b48b145f939efdbb96c3786db712b3113bb2488ef286cdcef8afe82d200a5bb36b5462166e8ce77f2d831a52ef2135b2af188110beaefb1", true)]
184    #[case::fail_1("b5a77e7a90aa14e0bf5f337f06f597148676424fae26e175c6e5621c34351955289f319789da424845c9eac935245fcddd805950e2f02506d09be7e411199556d262144475b1fa46ad85250728c600c53dfd10f8b3f4adf140e27241aec3c2daaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaef8afe82d200a5bb36b5462166e8ce77f2d831a52ef2135b2af188110beaefb1", false)]
185    fn test_verify_impl(#[case] input: &str, #[case] expect_success: bool) {
186        let input = Bytes::from_hex(input).unwrap();
187        let result = verify_impl(&input);
188
189        assert_eq!(result, expect_success);
190    }
191}