Skip to content

Commit 41e40f1

Browse files
authored
Merge pull request #303 from OffchainLabs/secp256r1-precompile
Add Precompile for secp256r1 conditionally based on ArbOS version
2 parents 1e8c23b + f041492 commit 41e40f1

File tree

10 files changed

+5563
-1
lines changed

10 files changed

+5563
-1
lines changed

core/vm/contracts.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
"github.com/ethereum/go-ethereum/crypto/bls12381"
3131
"github.com/ethereum/go-ethereum/crypto/bn256"
3232
"github.com/ethereum/go-ethereum/crypto/kzg4844"
33+
"github.com/ethereum/go-ethereum/crypto/secp256r1"
3334
"github.com/ethereum/go-ethereum/params"
3435
"golang.org/x/crypto/ripemd160"
3536
)
@@ -107,6 +108,12 @@ var PrecompiledContractsCancun = map[common.Address]PrecompiledContract{
107108
common.BytesToAddress([]byte{0x0a}): &kzgPointEvaluation{},
108109
}
109110

111+
// PrecompiledContractsP256Verify contains the precompiled Ethereum
112+
// contract specified in EIP-7212.
113+
var PrecompiledContractsP256Verify = map[common.Address]PrecompiledContract{
114+
common.BytesToAddress([]byte{0x01, 0x00}): &p256Verify{},
115+
}
116+
110117
// PrecompiledContractsBLS contains the set of pre-compiled Ethereum
111118
// contracts specified in EIP-2537. These are exported for testing purposes.
112119
var PrecompiledContractsBLS = map[common.Address]PrecompiledContract{
@@ -151,6 +158,9 @@ func init() {
151158
func ActivePrecompiles(rules params.Rules) []common.Address {
152159
switch {
153160
case rules.IsArbitrum:
161+
if rules.ArbOSVersion >= 30 {
162+
return PrecompiledAddressesArbOS30
163+
}
154164
return PrecompiledAddressesArbitrum
155165
case rules.IsCancun:
156166
return PrecompiledAddressesCancun
@@ -1156,3 +1166,36 @@ func kZGToVersionedHash(kzg kzg4844.Commitment) common.Hash {
11561166

11571167
return h
11581168
}
1169+
1170+
// P256VERIFY (secp256r1 signature verification)
1171+
// implemented as a native contract.
1172+
type p256Verify struct{}
1173+
1174+
// RequiredGas returns the gas required to execute the precompiled contract.
1175+
func (c *p256Verify) RequiredGas(input []byte) uint64 {
1176+
return params.P256VerifyGas
1177+
}
1178+
1179+
// Run executes the precompiled contract with given 160 bytes of param, returning the output and the used gas.
1180+
func (c *p256Verify) Run(input []byte) ([]byte, error) {
1181+
// Required input length is 160 bytes.
1182+
const p256VerifyInputLength = 160
1183+
// Check the input length.
1184+
if len(input) != p256VerifyInputLength {
1185+
// Input length is invalid.
1186+
return nil, nil
1187+
}
1188+
1189+
// Extract the hash, r, s, x, y from the input.
1190+
hash := input[0:32]
1191+
r, s := new(big.Int).SetBytes(input[32:64]), new(big.Int).SetBytes(input[64:96])
1192+
x, y := new(big.Int).SetBytes(input[96:128]), new(big.Int).SetBytes(input[128:160])
1193+
1194+
// Verify the secp256r1 signature.
1195+
if secp256r1.Verify(hash, r, s, x, y) {
1196+
// Signature is valid
1197+
return common.LeftPadBytes(common.Big1.Bytes(), 32), nil
1198+
}
1199+
// Signature is invalid.
1200+
return nil, nil
1201+
}

core/vm/contracts_arbitrum.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,6 @@ import "github.com/ethereum/go-ethereum/common"
55
var (
66
PrecompiledContractsArbitrum = make(map[common.Address]PrecompiledContract)
77
PrecompiledAddressesArbitrum []common.Address
8+
PrecompiledContractsArbOS30 = make(map[common.Address]PrecompiledContract)
9+
PrecompiledAddressesArbOS30 []common.Address
810
)

core/vm/contracts_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ var allPrecompiles = map[common.Address]PrecompiledContract{
5858
common.BytesToAddress([]byte{9}): &blake2F{},
5959
common.BytesToAddress([]byte{0x0a}): &kzgPointEvaluation{},
6060

61+
common.BytesToAddress([]byte{0x01, 0x00}): &p256Verify{},
62+
6163
common.BytesToAddress([]byte{0x0f, 0x0a}): &bls12381G1Add{},
6264
common.BytesToAddress([]byte{0x0f, 0x0b}): &bls12381G1Mul{},
6365
common.BytesToAddress([]byte{0x0f, 0x0c}): &bls12381G1MultiExp{},
@@ -395,3 +397,15 @@ func BenchmarkPrecompiledBLS12381G2MultiExpWorstCase(b *testing.B) {
395397
}
396398
benchmarkPrecompiled("0f", testcase, b)
397399
}
400+
401+
// Benchmarks the sample inputs from the P256VERIFY precompile.
402+
func BenchmarkPrecompiledP256Verify(bench *testing.B) {
403+
t := precompiledTest{
404+
Input: "4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e",
405+
Expected: "0000000000000000000000000000000000000000000000000000000000000001",
406+
Name: "p256Verify",
407+
}
408+
benchmarkPrecompiled("100", t, bench)
409+
}
410+
411+
func TestPrecompiledP256Verify(t *testing.T) { testJson("p256Verify", "100", t) }

core/vm/evm.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) {
4242
var precompiles map[common.Address]PrecompiledContract
4343
switch {
4444
case evm.chainRules.IsArbitrum:
45+
if evm.chainRules.ArbOSVersion >= 30 {
46+
precompiles = PrecompiledContractsArbOS30
47+
break
48+
}
4549
precompiles = PrecompiledContractsArbitrum
4650
case evm.chainRules.IsCancun:
4751
precompiles = PrecompiledContractsCancun

core/vm/testdata/precompiles/p256Verify.json

Lines changed: 5448 additions & 0 deletions
Large diffs are not rendered by default.

crypto/secp256r1/pubkey.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package secp256r1
2+
3+
import (
4+
"crypto/ecdsa"
5+
"crypto/elliptic"
6+
"math/big"
7+
)
8+
9+
// Generates approptiate public key format from given coordinates
10+
func newPublicKey(x, y *big.Int) *ecdsa.PublicKey {
11+
// Check if the given coordinates are valid
12+
if x == nil || y == nil || !elliptic.P256().IsOnCurve(x, y) {
13+
return nil
14+
}
15+
16+
// Check if the given coordinates are the reference point (infinity)
17+
if x.Sign() == 0 && y.Sign() == 0 {
18+
return nil
19+
}
20+
21+
return &ecdsa.PublicKey{
22+
Curve: elliptic.P256(),
23+
X: x,
24+
Y: y,
25+
}
26+
}

crypto/secp256r1/verifier.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package secp256r1
2+
3+
import (
4+
"crypto/ecdsa"
5+
"math/big"
6+
)
7+
8+
// Verifies the given signature (r, s) for the given hash and public key (x, y).
9+
func Verify(hash []byte, r, s, x, y *big.Int) bool {
10+
// Create the public key format
11+
publicKey := newPublicKey(x, y)
12+
13+
// Check if they are invalid public key coordinates
14+
if publicKey == nil {
15+
return false
16+
}
17+
18+
// Verify the signature with the public key,
19+
// then return true if it's valid, false otherwise
20+
return ecdsa.Verify(publicKey, hash, r, s)
21+
}

params/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -867,6 +867,7 @@ func (err *ConfigCompatError) Error() string {
867867
type Rules struct {
868868
IsArbitrum bool
869869
ChainID *big.Int
870+
ArbOSVersion uint64
870871
IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool
871872
IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool
872873
IsBerlin, IsLondon bool
@@ -883,6 +884,7 @@ func (c *ChainConfig) Rules(num *big.Int, isMerge bool, timestamp uint64, curren
883884
return Rules{
884885
IsArbitrum: c.IsArbitrum(),
885886
ChainID: new(big.Int).Set(chainID),
887+
ArbOSVersion: currentArbosVersion,
886888
IsHomestead: c.IsHomestead(num),
887889
IsEIP150: c.IsEIP150(num),
888890
IsEIP155: c.IsEIP155(num),

params/protocol_params.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,8 @@ const (
159159
Bls12381MapG1Gas uint64 = 5500 // Gas price for BLS12-381 mapping field element to G1 operation
160160
Bls12381MapG2Gas uint64 = 110000 // Gas price for BLS12-381 mapping field element to G2 operation
161161

162+
P256VerifyGas uint64 = 3450 // secp256r1 elliptic curve signature verifier gas price
163+
162164
// The Refund Quotient is the cap on how much of the used gas can be refunded. Before EIP-3529,
163165
// up to half the consumed gas could be refunded. Redefined as 1/5th in EIP-3529
164166
RefundQuotient uint64 = 2

tests/testdata

0 commit comments

Comments
 (0)