Skip to content

Commit 20e2de9

Browse files
committed
feat: RIP-7212 precompile
1 parent 2d94327 commit 20e2de9

File tree

3 files changed

+258
-0
lines changed

3 files changed

+258
-0
lines changed
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Copyright 2025 the libevm authors.
2+
//
3+
// The libevm additions to go-ethereum are free software: you can redistribute
4+
// them and/or modify them under the terms of the GNU Lesser General Public License
5+
// as published by the Free Software Foundation, either version 3 of the License,
6+
// or (at your option) any later version.
7+
//
8+
// The libevm additions are distributed in the hope that they will be useful,
9+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
11+
// General Public License for more details.
12+
//
13+
// You should have received a copy of the GNU Lesser General Public License
14+
// along with the go-ethereum library. If not, see
15+
// <http://www.gnu.org/licenses/>.
16+
17+
// Package p256verify implements an EVM precompile to verify P256 ECDSA
18+
// signatures, as described in RIP-7212.
19+
package p256verify
20+
21+
import (
22+
"crypto/ecdsa"
23+
"crypto/elliptic"
24+
"crypto/rand"
25+
"math/big"
26+
)
27+
28+
// Precompile implements ECDSA verification on the P256 curve, as defined by
29+
// [RIP-7212].
30+
//
31+
// [RIP-7212]: https://github.com/ethereum/RIPs/blob/1f55794f65caa4c4bb2b8d9bda7d713b8c734157/RIPS/rip-7212.md
32+
type Precompile struct{}
33+
34+
// RequiredGas always returns 3450.
35+
func (Precompile) RequiredGas([]byte) uint64 { return 3450 }
36+
37+
const inputLen = 160
38+
39+
type input [inputLen]byte
40+
41+
// Run parses and verifies the signature. On success it returns a 32-byte
42+
// big-endian representation of the number 1, otherwise it returns an empty
43+
// slice. The returned error is always nil.
44+
func (Precompile) Run(sig []byte) ([]byte, error) {
45+
if len(sig) != inputLen || !(*input)(sig).verify() {
46+
return nil, nil
47+
}
48+
return []byte{31: 1}, nil
49+
}
50+
51+
func (in *input) verify() bool {
52+
key, ok := in.pubkey()
53+
if !ok {
54+
return false
55+
}
56+
return ecdsa.Verify(key, in.word(0), in.bigWord(1), in.bigWord(2))
57+
}
58+
59+
func (in *input) pubkey() (*ecdsa.PublicKey, bool) {
60+
x := in.bigWord(3)
61+
y := in.bigWord(4)
62+
if x.Sign() == 0 && y.Sign() == 0 {
63+
return nil, false
64+
}
65+
66+
curve := elliptic.P256()
67+
if !curve.IsOnCurve(x, y) {
68+
return nil, false
69+
}
70+
return &ecdsa.PublicKey{
71+
Curve: curve,
72+
X: x,
73+
Y: y,
74+
}, true
75+
}
76+
77+
func (in *input) word(index int) []byte {
78+
s := index * 32
79+
return in[s : s+32]
80+
}
81+
82+
func (in *input) bigWord(index int) *big.Int {
83+
return new(big.Int).SetBytes(in.word(index))
84+
}
85+
86+
// Sign signs `hash` with the private key, returning a byte slice compatible
87+
// with [Precompile.Run]. It uses [rand.Reader] as the first argument to
88+
// [ecdsa.Sign].
89+
func Sign(priv *ecdsa.PrivateKey, hash [32]byte) ([]byte, error) {
90+
r, s, err := ecdsa.Sign(rand.Reader, priv, hash[:])
91+
if err != nil {
92+
return nil, err
93+
}
94+
95+
var in input
96+
copy(in.word(0), hash[:])
97+
r.FillBytes(in.word(1))
98+
s.FillBytes(in.word(2))
99+
priv.X.FillBytes(in.word(3))
100+
priv.Y.FillBytes(in.word(4))
101+
return in[:], nil
102+
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
// Copyright 2025 the libevm authors.
2+
//
3+
// The libevm additions to go-ethereum are free software: you can redistribute
4+
// them and/or modify them under the terms of the GNU Lesser General Public License
5+
// as published by the Free Software Foundation, either version 3 of the License,
6+
// or (at your option) any later version.
7+
//
8+
// The libevm additions are distributed in the hope that they will be useful,
9+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
11+
// General Public License for more details.
12+
//
13+
// You should have received a copy of the GNU Lesser General Public License
14+
// along with the go-ethereum library. If not, see
15+
// <http://www.gnu.org/licenses/>.
16+
17+
package p256verify
18+
19+
import (
20+
"crypto/ecdsa"
21+
"crypto/elliptic"
22+
"crypto/rand"
23+
"testing"
24+
25+
"github.com/ava-labs/libevm/common"
26+
"github.com/ava-labs/libevm/core/vm"
27+
"github.com/ava-labs/libevm/libevm"
28+
"github.com/ava-labs/libevm/libevm/ethtest"
29+
"github.com/ava-labs/libevm/libevm/hookstest"
30+
"github.com/ava-labs/libevm/params"
31+
"github.com/holiman/uint256"
32+
"github.com/stretchr/testify/assert"
33+
"github.com/stretchr/testify/require"
34+
)
35+
36+
// ulerdoganTestCase is the test case from
37+
// https://github.com/ulerdogan/go-ethereum/blob/cec0b058115282168c5afc5197de3f6b5479dc4a/core/vm/testdata/precompiles/p256Verify.json,
38+
// copied under LGPL. See the respective commit for copyright and license
39+
// information.
40+
const ulerdoganTestCase = `4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e`
41+
42+
func TestPrecompile(t *testing.T) {
43+
assert.Equal(t, params.P256VerifyGas, Precompile{}.RequiredGas(nil), "RequiredGas()")
44+
45+
type testCase struct {
46+
name string
47+
in []byte
48+
wantSuccess bool
49+
}
50+
51+
tests := []testCase{
52+
{
53+
name: "empty input",
54+
},
55+
{
56+
name: "input too short",
57+
in: make([]byte, inputLen-1),
58+
},
59+
{
60+
name: "input too long",
61+
in: make([]byte, inputLen+1),
62+
},
63+
{
64+
name: "pub key at infinity",
65+
in: make([]byte, inputLen),
66+
},
67+
{
68+
name: "pub key not on curve",
69+
in: []byte{inputLen - 1: 1},
70+
},
71+
{
72+
name: "ulerdogan",
73+
in: common.Hex2Bytes(ulerdoganTestCase),
74+
wantSuccess: true,
75+
},
76+
}
77+
78+
for range 50 {
79+
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
80+
require.NoError(t, err, "ecdsa.GenerateKey(elliptic.P256(), crypto/rand.Reader)")
81+
82+
for range 50 {
83+
var hash [32]byte
84+
_, err := rand.Read(hash[:])
85+
require.NoErrorf(t, err, "crypto/rand.Read(%T)", hash)
86+
87+
in, err := Sign(priv, hash)
88+
require.NoErrorf(t, err, "Sign([P256 key], %#x)", hash)
89+
tests = append(tests, testCase{
90+
name: "fuzz",
91+
in: in,
92+
wantSuccess: true,
93+
})
94+
}
95+
}
96+
97+
for _, tt := range tests {
98+
t.Run(tt.name, func(t *testing.T) {
99+
got, err := Precompile{}.Run(tt.in)
100+
require.NoError(t, err, "Run() always returns nil, even on verification failure")
101+
102+
var want []byte
103+
if tt.wantSuccess {
104+
want = common.LeftPadBytes([]byte{1}, 32)
105+
}
106+
assert.Equal(t, want, got)
107+
})
108+
}
109+
}
110+
111+
func BenchmarkPrecompile(b *testing.B) {
112+
in := common.Hex2Bytes(ulerdoganTestCase)
113+
var p Precompile
114+
115+
for range b.N {
116+
p.Run(in)
117+
}
118+
}
119+
120+
func TestViaEVM(t *testing.T) {
121+
addr := common.Address{42}
122+
hooks := hookstest.Stub{
123+
PrecompileOverrides: map[common.Address]libevm.PrecompiledContract{
124+
addr: Precompile{},
125+
},
126+
}
127+
hooks.Register(t)
128+
129+
_, evm := ethtest.NewZeroEVM(t)
130+
in := common.Hex2Bytes(ulerdoganTestCase)
131+
132+
got, _, err := evm.Call(vm.AccountRef{}, addr, in, 25000, uint256.NewInt(0))
133+
require.NoError(t, err)
134+
assert.Equal(t, []byte{31: 1}, got)
135+
}

params/protocol_params.libevm.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright 2025 the libevm authors.
2+
//
3+
// The libevm additions to go-ethereum are free software: you can redistribute
4+
// them and/or modify them under the terms of the GNU Lesser General Public License
5+
// as published by the Free Software Foundation, either version 3 of the License,
6+
// or (at your option) any later version.
7+
//
8+
// The libevm additions are distributed in the hope that they will be useful,
9+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
11+
// General Public License for more details.
12+
//
13+
// You should have received a copy of the GNU Lesser General Public License
14+
// along with the go-ethereum library. If not, see
15+
// <http://www.gnu.org/licenses/>.
16+
17+
package params
18+
19+
// P256VerifyGas is the gas required by the RIP-7212 precompile for P256 ECDSA
20+
// verification.
21+
const P256VerifyGas uint64 = 3450

0 commit comments

Comments
 (0)