Skip to content

Commit

Permalink
Merge pull request #303 from OffchainLabs/secp256r1-precompile
Browse files Browse the repository at this point in the history
Add Precompile for secp256r1 conditionally based on ArbOS version
  • Loading branch information
PlasmaPower authored Apr 29, 2024
2 parents 1e8c23b + f041492 commit 41e40f1
Show file tree
Hide file tree
Showing 10 changed files with 5,563 additions and 1 deletion.
43 changes: 43 additions & 0 deletions core/vm/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"github.com/ethereum/go-ethereum/crypto/bls12381"
"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"
)
Expand Down Expand Up @@ -107,6 +108,12 @@ var PrecompiledContractsCancun = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{0x0a}): &kzgPointEvaluation{},
}

// PrecompiledContractsP256Verify contains the precompiled Ethereum
// contract specified in EIP-7212.
var PrecompiledContractsP256Verify = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{0x01, 0x00}): &p256Verify{},
}

// PrecompiledContractsBLS contains the set of pre-compiled Ethereum
// contracts specified in EIP-2537. These are exported for testing purposes.
var PrecompiledContractsBLS = map[common.Address]PrecompiledContract{
Expand Down Expand Up @@ -151,6 +158,9 @@ func init() {
func ActivePrecompiles(rules params.Rules) []common.Address {
switch {
case rules.IsArbitrum:
if rules.ArbOSVersion >= 30 {
return PrecompiledAddressesArbOS30
}
return PrecompiledAddressesArbitrum
case rules.IsCancun:
return PrecompiledAddressesCancun
Expand Down Expand Up @@ -1156,3 +1166,36 @@ 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
}
// Signature is invalid.
return nil, nil
}
2 changes: 2 additions & 0 deletions core/vm/contracts_arbitrum.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ import "github.com/ethereum/go-ethereum/common"
var (
PrecompiledContractsArbitrum = make(map[common.Address]PrecompiledContract)
PrecompiledAddressesArbitrum []common.Address
PrecompiledContractsArbOS30 = make(map[common.Address]PrecompiledContract)
PrecompiledAddressesArbOS30 []common.Address
)
14 changes: 14 additions & 0 deletions core/vm/contracts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ var allPrecompiles = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{9}): &blake2F{},
common.BytesToAddress([]byte{0x0a}): &kzgPointEvaluation{},

common.BytesToAddress([]byte{0x01, 0x00}): &p256Verify{},

common.BytesToAddress([]byte{0x0f, 0x0a}): &bls12381G1Add{},
common.BytesToAddress([]byte{0x0f, 0x0b}): &bls12381G1Mul{},
common.BytesToAddress([]byte{0x0f, 0x0c}): &bls12381G1MultiExp{},
Expand Down Expand Up @@ -395,3 +397,15 @@ func BenchmarkPrecompiledBLS12381G2MultiExpWorstCase(b *testing.B) {
}
benchmarkPrecompiled("0f", testcase, b)
}

// Benchmarks the sample inputs from the P256VERIFY precompile.
func BenchmarkPrecompiledP256Verify(bench *testing.B) {
t := precompiledTest{
Input: "4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e",
Expected: "0000000000000000000000000000000000000000000000000000000000000001",
Name: "p256Verify",
}
benchmarkPrecompiled("100", t, bench)
}

func TestPrecompiledP256Verify(t *testing.T) { testJson("p256Verify", "100", t) }
4 changes: 4 additions & 0 deletions core/vm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) {
var precompiles map[common.Address]PrecompiledContract
switch {
case evm.chainRules.IsArbitrum:
if evm.chainRules.ArbOSVersion >= 30 {
precompiles = PrecompiledContractsArbOS30
break
}
precompiles = PrecompiledContractsArbitrum
case evm.chainRules.IsCancun:
precompiles = PrecompiledContractsCancun
Expand Down
5,448 changes: 5,448 additions & 0 deletions core/vm/testdata/precompiles/p256Verify.json

Large diffs are not rendered by default.

26 changes: 26 additions & 0 deletions crypto/secp256r1/pubkey.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package secp256r1

import (
"crypto/ecdsa"
"crypto/elliptic"
"math/big"
)

// Generates approptiate public key format from given coordinates
func newPublicKey(x, y *big.Int) *ecdsa.PublicKey {
// Check if the given coordinates are valid
if x == nil || y == nil || !elliptic.P256().IsOnCurve(x, y) {
return nil
}

// Check if the given coordinates are the reference point (infinity)
if x.Sign() == 0 && y.Sign() == 0 {
return nil
}

return &ecdsa.PublicKey{
Curve: elliptic.P256(),
X: x,
Y: y,
}
}
21 changes: 21 additions & 0 deletions crypto/secp256r1/verifier.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package secp256r1

import (
"crypto/ecdsa"
"math/big"
)

// Verifies 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 {
// Create the public key format
publicKey := newPublicKey(x, y)

// Check if they are invalid public key coordinates
if publicKey == nil {
return false
}

// Verify the signature with the public key,
// then return true if it's valid, false otherwise
return ecdsa.Verify(publicKey, hash, r, s)
}
2 changes: 2 additions & 0 deletions params/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -867,6 +867,7 @@ func (err *ConfigCompatError) Error() string {
type Rules struct {
IsArbitrum bool
ChainID *big.Int
ArbOSVersion uint64
IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool
IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool
IsBerlin, IsLondon bool
Expand All @@ -883,6 +884,7 @@ func (c *ChainConfig) Rules(num *big.Int, isMerge bool, timestamp uint64, curren
return Rules{
IsArbitrum: c.IsArbitrum(),
ChainID: new(big.Int).Set(chainID),
ArbOSVersion: currentArbosVersion,
IsHomestead: c.IsHomestead(num),
IsEIP150: c.IsEIP150(num),
IsEIP155: c.IsEIP155(num),
Expand Down
2 changes: 2 additions & 0 deletions params/protocol_params.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@ const (
Bls12381MapG1Gas uint64 = 5500 // Gas price for BLS12-381 mapping field element to G1 operation
Bls12381MapG2Gas uint64 = 110000 // 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
Expand Down
2 changes: 1 addition & 1 deletion tests/testdata

0 comments on commit 41e40f1

Please sign in to comment.