From 1505a025afc3f9d1921b41e89334a1ff55db030b Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 9 Jun 2025 15:33:26 +0200 Subject: [PATCH] core/vm, crypto/secp2561r1: implement secp256r1 precompile (#31991) --- core/vm/contracts.go | 43 ++++++++++++++++++++ core/vm/contracts_test.go | 14 +++++++ core/vm/testdata/precompiles/p256Verify.json | 9 ++++ crypto/secp256r1/verifier.go | 43 ++++++++++++++++++++ params/protocol_params.go | 2 + 5 files changed, 111 insertions(+) create mode 100644 core/vm/testdata/precompiles/p256Verify.json create mode 100644 crypto/secp256r1/verifier.go diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 92a4e7d016..fe20d4986c 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -35,6 +35,7 @@ import ( "github.com/ethereum/go-ethereum/crypto/blake2b" "github.com/ethereum/go-ethereum/crypto/bn256" "github.com/ethereum/go-ethereum/crypto/kzg4844" + "github.com/ethereum/go-ethereum/crypto/secp256r1" "github.com/ethereum/go-ethereum/params" "golang.org/x/crypto/ripemd160" ) @@ -161,6 +162,14 @@ var PrecompiledContractsOsaka = PrecompiledContracts{ common.BytesToAddress([]byte{0x0f}): &bls12381Pairing{}, common.BytesToAddress([]byte{0x10}): &bls12381MapG1{}, common.BytesToAddress([]byte{0x11}): &bls12381MapG2{}, + + common.BytesToAddress([]byte{0x1, 0x00}): &p256Verify{}, +} + +// PrecompiledContractsP256Verify contains the precompiled Ethereum +// contract specified in EIP-7212. This is exported for testing purposes. +var PrecompiledContractsP256Verify = PrecompiledContracts{ + common.BytesToAddress([]byte{0x1, 0x00}): &p256Verify{}, } var ( @@ -1232,3 +1241,37 @@ func kZGToVersionedHash(kzg kzg4844.Commitment) common.Hash { return h } + +// P256VERIFY (secp256r1 signature verification) +// implemented as a native contract +type p256Verify struct{} + +// RequiredGas returns the gas required to execute the precompiled contract +func (c *p256Verify) RequiredGas(input []byte) uint64 { + return params.P256VerifyGas +} + +// Run executes the precompiled contract with given 160 bytes of param, returning the output and the used gas +func (c *p256Verify) Run(input []byte) ([]byte, error) { + // Required input length is 160 bytes + const p256VerifyInputLength = 160 + // Check the input length + if len(input) != p256VerifyInputLength { + // Input length is invalid + return nil, nil + } + + // Extract the hash, r, s, x, y from the input + hash := input[0:32] + r, s := new(big.Int).SetBytes(input[32:64]), new(big.Int).SetBytes(input[64:96]) + x, y := new(big.Int).SetBytes(input[96:128]), new(big.Int).SetBytes(input[128:160]) + + // Verify the secp256r1 signature + if secp256r1.Verify(hash, r, s, x, y) { + // Signature is valid + return common.LeftPadBytes(common.Big1.Bytes(), 32), nil + } else { + // Signature is invalid + return nil, nil + } +} diff --git a/core/vm/contracts_test.go b/core/vm/contracts_test.go index f86a8919a9..da44e250e1 100644 --- a/core/vm/contracts_test.go +++ b/core/vm/contracts_test.go @@ -66,6 +66,8 @@ var allPrecompiles = map[common.Address]PrecompiledContract{ common.BytesToAddress([]byte{0x0f, 0x0e}): &bls12381Pairing{}, common.BytesToAddress([]byte{0x0f, 0x0f}): &bls12381MapG1{}, common.BytesToAddress([]byte{0x0f, 0x10}): &bls12381MapG2{}, + + common.BytesToAddress([]byte{0x0b}): &p256Verify{}, } // EIP-152 test vectors @@ -397,3 +399,15 @@ func BenchmarkPrecompiledBLS12381G2MultiExpWorstCase(b *testing.B) { } benchmarkPrecompiled("f0d", testcase, b) } + +// Benchmarks the sample inputs from the P256VERIFY precompile. +func BenchmarkPrecompiledP256Verify(bench *testing.B) { + t := precompiledTest{ + Input: "4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e", + Expected: "0000000000000000000000000000000000000000000000000000000000000001", + Name: "p256Verify", + } + benchmarkPrecompiled("0b", t, bench) +} + +func TestPrecompiledP256Verify(t *testing.T) { testJson("p256Verify", "0b", t) } diff --git a/core/vm/testdata/precompiles/p256Verify.json b/core/vm/testdata/precompiles/p256Verify.json new file mode 100644 index 0000000000..fbcac41e9f --- /dev/null +++ b/core/vm/testdata/precompiles/p256Verify.json @@ -0,0 +1,9 @@ +[ + { + "Input": "4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Gas": 3450, + "Name": "CallP256Verify", + "NoBenchmark": false + } + ] \ No newline at end of file diff --git a/crypto/secp256r1/verifier.go b/crypto/secp256r1/verifier.go new file mode 100644 index 0000000000..9943000764 --- /dev/null +++ b/crypto/secp256r1/verifier.go @@ -0,0 +1,43 @@ +// Copyright 2024 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Package secp256r1 implements signature verification for the P256VERIFY precompile. +package secp256r1 + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "math/big" +) + +func newPublicKey(x, y *big.Int) *ecdsa.PublicKey { + if x == nil || y == nil || !elliptic.P256().IsOnCurve(x, y) { + return nil + } + if x.Sign() == 0 && y.Sign() == 0 { + return nil + } + return &ecdsa.PublicKey{Curve: elliptic.P256(), X: x, Y: y} +} + +// Verify checks the given signature (r, s) for the given hash and public key (x, y). +func Verify(hash []byte, r, s, x, y *big.Int) bool { + publicKey := newPublicKey(x, y) + if publicKey == nil { + return false + } + return ecdsa.Verify(publicKey, hash, r, s) +} diff --git a/params/protocol_params.go b/params/protocol_params.go index 14a1338c84..157c610166 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -164,6 +164,8 @@ const ( Bls12381MapG1Gas uint64 = 5500 // Gas price for BLS12-381 mapping field element to G1 operation Bls12381MapG2Gas uint64 = 23800 // Gas price for BLS12-381 mapping field element to G2 operation + P256VerifyGas uint64 = 3450 // secp256r1 elliptic curve signature verifier gas price + // The Refund Quotient is the cap on how much of the used gas can be refunded. Before EIP-3529, // up to half the consumed gas could be refunded. Redefined as 1/5th in EIP-3529 RefundQuotient uint64 = 2