From dcc10c72d1765f7803971c3a98b3cb658b1fae25 Mon Sep 17 00:00:00 2001 From: CoderZhi Date: Wed, 27 Jul 2022 17:22:08 -0700 Subject: [PATCH] Precompiled contract secp256r1 (#10) --- core/state/statedb.go | 10 +-- core/vm/contracts.go | 51 ++++++++++--- core/vm/contracts_test.go | 74 ++++++++++++++----- core/vm/evm.go | 2 +- core/vm/interface.go | 2 +- .../testdata/precompiles/fail-secp256r1.json | 23 ++++++ core/vm/testdata/precompiles/secp256r1.json | 30 ++++++++ 7 files changed, 157 insertions(+), 35 deletions(-) create mode 100644 core/vm/testdata/precompiles/fail-secp256r1.json create mode 100644 core/vm/testdata/precompiles/secp256r1.json diff --git a/core/state/statedb.go b/core/state/statedb.go index 205e0a689378..1567d1bef327 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -257,11 +257,6 @@ func (s *StateDB) Empty(addr common.Address) bool { return so == nil || so.empty() } -// InitNonce returns the initial nonce -func (s *StateDB) InitNonce() uint64 { - return 0 -} - // GetBalance retrieves the balance from the given address or 0 if object not found func (s *StateDB) GetBalance(addr common.Address) *big.Int { stateObject := s.getStateObject(addr) @@ -271,6 +266,11 @@ func (s *StateDB) GetBalance(addr common.Address) *big.Int { return common.Big0 } +// IsNewAccount returns true if this is a new account +func (s *StateDB) IsNewAccount(addr common.Address) bool { + return s.GetNonce(addr) == 0 +} + func (s *StateDB) GetNonce(addr common.Address) uint64 { stateObject := s.getStateObject(addr) if stateObject != nil { diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 9210f5486c57..71587e69a379 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -17,6 +17,8 @@ package vm import ( + "crypto/ecdsa" + "crypto/elliptic" "crypto/sha256" "encoding/binary" "errors" @@ -81,15 +83,16 @@ var PrecompiledContractsIstanbul = map[common.Address]PrecompiledContract{ // PrecompiledContractsBerlin contains the default set of pre-compiled Ethereum // contracts used in the Berlin release. var PrecompiledContractsBerlin = map[common.Address]PrecompiledContract{ - common.BytesToAddress([]byte{1}): &ecrecover{}, - common.BytesToAddress([]byte{2}): &sha256hash{}, - common.BytesToAddress([]byte{3}): &ripemd160hash{}, - common.BytesToAddress([]byte{4}): &dataCopy{}, - common.BytesToAddress([]byte{5}): &bigModExp{eip2565: true}, - common.BytesToAddress([]byte{6}): &bn256AddIstanbul{}, - common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, - common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, - common.BytesToAddress([]byte{9}): &blake2F{}, + common.BytesToAddress([]byte{1}): &ecrecover{}, + common.BytesToAddress([]byte{2}): &sha256hash{}, + common.BytesToAddress([]byte{3}): &ripemd160hash{}, + common.BytesToAddress([]byte{4}): &dataCopy{}, + common.BytesToAddress([]byte{5}): &bigModExp{eip2565: true}, + common.BytesToAddress([]byte{6}): &bn256AddIstanbul{}, + common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, + common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, + common.BytesToAddress([]byte{9}): &blake2F{}, + common.BytesToAddress([]byte{128, 1}): &secp256r1{}, } // PrecompiledContractsBLS contains the set of pre-compiled Ethereum @@ -1043,3 +1046,33 @@ func (c *bls12381MapG2) Run(input []byte) ([]byte, error) { // Encode the G2 point to 256 bytes return g.EncodePoint(r), nil } + +var ( + errSecp256r1InvalidInputLength = errors.New("invalid input length") + errSecp256r1InvalidCurvePoint = errors.New("invalid curve point") +) + +// secp256r1 implements secp256r1 signature verification +type secp256r1 struct{} + +func (s *secp256r1) RequiredGas(input []byte) uint64 { + return params.EcrecoverGas +} + +func (sec *secp256r1) Run(input []byte) ([]byte, error) { + if len(input) <= 96 { + return nil, errSecp256r1InvalidInputLength + } + hash := input[:32] + r := new(big.Int).SetBytes(input[32:64]) + s := new(big.Int).SetBytes(input[64:96]) + curve := elliptic.P256() + x, y := elliptic.Unmarshal(curve, input[96:]) + if x == nil || y == nil { + return nil, errSecp256r1InvalidCurvePoint + } + if ecdsa.Verify(&ecdsa.PublicKey{Curve: curve, X: x, Y: y}, hash, r, s) { + return []byte{1}, nil + } + return []byte{0}, nil +} diff --git a/core/vm/contracts_test.go b/core/vm/contracts_test.go index 30d9b49f719b..0eb4976b41c2 100644 --- a/core/vm/contracts_test.go +++ b/core/vm/contracts_test.go @@ -18,6 +18,11 @@ package vm import ( "bytes" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/sha256" + "encoding/hex" "encoding/json" "fmt" "io/ioutil" @@ -46,25 +51,26 @@ type precompiledFailureTest struct { // allPrecompiles does not map to the actual set of precompiles, as it also contains // repriced versions of precompiles at certain slots var allPrecompiles = map[common.Address]PrecompiledContract{ - common.BytesToAddress([]byte{1}): &ecrecover{}, - common.BytesToAddress([]byte{2}): &sha256hash{}, - common.BytesToAddress([]byte{3}): &ripemd160hash{}, - common.BytesToAddress([]byte{4}): &dataCopy{}, - common.BytesToAddress([]byte{5}): &bigModExp{eip2565: false}, - common.BytesToAddress([]byte{0xf5}): &bigModExp{eip2565: true}, - common.BytesToAddress([]byte{6}): &bn256AddIstanbul{}, - common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, - common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, - common.BytesToAddress([]byte{9}): &blake2F{}, - common.BytesToAddress([]byte{10}): &bls12381G1Add{}, - common.BytesToAddress([]byte{11}): &bls12381G1Mul{}, - common.BytesToAddress([]byte{12}): &bls12381G1MultiExp{}, - common.BytesToAddress([]byte{13}): &bls12381G2Add{}, - common.BytesToAddress([]byte{14}): &bls12381G2Mul{}, - common.BytesToAddress([]byte{15}): &bls12381G2MultiExp{}, - common.BytesToAddress([]byte{16}): &bls12381Pairing{}, - common.BytesToAddress([]byte{17}): &bls12381MapG1{}, - common.BytesToAddress([]byte{18}): &bls12381MapG2{}, + common.BytesToAddress([]byte{1}): &ecrecover{}, + common.BytesToAddress([]byte{2}): &sha256hash{}, + common.BytesToAddress([]byte{3}): &ripemd160hash{}, + common.BytesToAddress([]byte{4}): &dataCopy{}, + common.BytesToAddress([]byte{5}): &bigModExp{eip2565: false}, + common.BytesToAddress([]byte{0xf5}): &bigModExp{eip2565: true}, + common.BytesToAddress([]byte{6}): &bn256AddIstanbul{}, + common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, + common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, + common.BytesToAddress([]byte{9}): &blake2F{}, + common.BytesToAddress([]byte{10}): &bls12381G1Add{}, + common.BytesToAddress([]byte{11}): &bls12381G1Mul{}, + common.BytesToAddress([]byte{12}): &bls12381G1MultiExp{}, + common.BytesToAddress([]byte{13}): &bls12381G2Add{}, + common.BytesToAddress([]byte{14}): &bls12381G2Mul{}, + common.BytesToAddress([]byte{15}): &bls12381G2MultiExp{}, + common.BytesToAddress([]byte{16}): &bls12381Pairing{}, + common.BytesToAddress([]byte{17}): &bls12381MapG1{}, + common.BytesToAddress([]byte{18}): &bls12381MapG2{}, + common.BytesToAddress([]byte{128, 1}): &secp256r1{}, } // EIP-152 test vectors @@ -191,6 +197,28 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) { }) } +// Benchmarks the sample inputs from the Secp256r1 precompile +func BenchmarkPrecompiledSecp256r1(bench *testing.B) { + priKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + hashed := sha256.Sum256([]byte("testing")) + r, s, err := ecdsa.Sign(rand.Reader, priKey, hashed[:]) + if err != nil { + bench.Error(err) + return + } + rb := make([]byte, 32) + r.FillBytes(rb) + sb := make([]byte, 32) + s.FillBytes(sb) + pubKey := priKey.PublicKey + t := precompiledTest{ + Input: hex.EncodeToString(append(append(append(hashed[:], rb...), sb...), elliptic.Marshal(pubKey.Curve, pubKey.X, pubKey.Y)...)), + Expected: "01", + Name: "", + } + benchmarkPrecompiled("8001", t, bench) +} + // Benchmarks the sample inputs from the ECRECOVER precompile. func BenchmarkPrecompiledEcrecover(bench *testing.B) { t := precompiledTest{ @@ -272,6 +300,14 @@ func TestPrecompileBlake2FMalformedInput(t *testing.T) { func TestPrecompiledEcrecover(t *testing.T) { testJson("ecRecover", "01", t) } +func TestPrecompiledSecp256r1Fail(t *testing.T) { + testJsonFail("secp256r1", "8001", t) +} + +func TestPrecompiledSecp256r1(t *testing.T) { + testJson("secp256r1", "8001", t) +} + func testJson(name, addr string, t *testing.T) { tests, err := loadJson(name) if err != nil { diff --git a/core/vm/evm.go b/core/vm/evm.go index 32fc1d852336..deac1c0279a6 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -424,7 +424,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, } // Ensure there's no existing contract already at the designated address contractHash := evm.StateDB.GetCodeHash(address) - if evm.StateDB.GetNonce(address) != evm.StateDB.InitNonce() || (contractHash != (common.Hash{}) && contractHash != emptyCodeHash) { + if !evm.StateDB.IsNewAccount(address) || (contractHash != (common.Hash{}) && contractHash != emptyCodeHash) { return nil, common.Address{}, 0, ErrContractAddressCollision } // Create a new account on the state diff --git a/core/vm/interface.go b/core/vm/interface.go index 63223d77f216..661d514ee2cf 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -31,7 +31,7 @@ type StateDB interface { AddBalance(common.Address, *big.Int) GetBalance(common.Address) *big.Int - InitNonce() uint64 + IsNewAccount(common.Address) bool GetNonce(common.Address) uint64 SetNonce(common.Address, uint64) diff --git a/core/vm/testdata/precompiles/fail-secp256r1.json b/core/vm/testdata/precompiles/fail-secp256r1.json new file mode 100644 index 000000000000..8b78d75140df --- /dev/null +++ b/core/vm/testdata/precompiles/fail-secp256r1.json @@ -0,0 +1,23 @@ +[ + { + "Input": "cf80cd8aed482d5d1527d7dc72fceff84e6326592848447d2dc0b0e87dfc9a9037c416673d9a1548b5b79e1529e9e0bca5b3019af6462547c86c5f2d8d5c91511bbaa47b73f668f9a1c59201d1e453679420577ed9548b95836bd20b5d821da9", + "Gas": 3000, + "ExpectedError": "invalid input length", + "Name": "InvalidInputLength", + "NoBenchmark": false + }, + { + "Input": "cf80cd8aed482d5d1527d7dc72fceff84e6326592848447d2dc0b0e87dfc9a9037c416673d9a1548b5b79e1529e9e0bca5b3019af6462547c86c5f2d8d5c91511bbaa47b73f668f9a1c59201d1e453679420577ed9548b95836bd20b5d821da904f9f37b3e660e01eb3f3f5535da7c96cffc620e3027883f312cda7ad59a8be1b3cc8e7334a8c0174a7e0db5265e62624cfe1f113a6ed9826247ae1debc33f", + "Gas": 3000, + "ExpectedError": "invalid curve point", + "Name": "InvalidCurvePoint", + "NoBenchmark": false + }, + { + "Input": "cf80cd8aed482d5d1527d7dc72fceff84e6326592848447d2dc0b0e87dfc9a9037c416673d9a1548b5b79e1529e9e0bca5b3019af6462547c86c5f2d8d5c91511bbaa47b73f668f9a1c59201d1e453679420577ed9548b95836bd20b5d821da904f9f37b3e660e01eb3f3f5535da7c96cffc620e3027883f312cda7ad59a8be1b3cc8e7334a8c0174a7e0db5265e62624cfe1f113a6ed9826247ae1debc33faa2eaa", + "Gas": 3000, + "ExpectedError": "invalid curve point", + "Name": "InvalidCurvePoint", + "NoBenchmark": false + } +] \ No newline at end of file diff --git a/core/vm/testdata/precompiles/secp256r1.json b/core/vm/testdata/precompiles/secp256r1.json new file mode 100644 index 000000000000..c07ae221065c --- /dev/null +++ b/core/vm/testdata/precompiles/secp256r1.json @@ -0,0 +1,30 @@ +[ + { + "Input": "cf80cd8aed482d5d1527d7dc72fceff84e6326592848447d2dc0b0e87dfc9a9037c416673d9a1548b5b79e1529e9e0bca5b3019af6462547c86c5f2d8d5c91511bbaa47b73f668f9a1c59201d1e453679420577ed9548b95836bd20b5d821da904f9f37b3e660e01eb3f3f5535da7c96cffc620e3027883f312cda7ad59a8be1b3cc8e7334a8c0174a7e0db5265e62624cfe1f113a6ed9826247ae1debc33faa2e", + "Gas": 3000, + "Expected": "01", + "Name": "ValidKey", + "NoBenchmark": false + }, + { + "Input": "af80cd8aed482d5d1527d7dc72fceff84e6326592848447d2dc0b0e87dfc9a9037c416673d9a1548b5b79e1529e9e0bca5b3019af6462547c86c5f2d8d5c91511bbaa47b73f668f9a1c59201d1e453679420577ed9548b95836bd20b5d821da904f9f37b3e660e01eb3f3f5535da7c96cffc620e3027883f312cda7ad59a8be1b3cc8e7334a8c0174a7e0db5265e62624cfe1f113a6ed9826247ae1debc33faa2e", + "Gas": 3000, + "Expected": "00", + "Name": "Invalid Hash", + "NoBenchmark": false + }, + { + "Input": "cf80cd8aed482d5d1527d7dc72fceff84e6326592848447d2dc0b0e87dfc9a9047c416673d9a1548b5b79e1529e9e0bca5b3019af6462547c86c5f2d8d5c91511bbaa47b73f668f9a1c59201d1e453679420577ed9548b95836bd20b5d821da904f9f37b3e660e01eb3f3f5535da7c96cffc620e3027883f312cda7ad59a8be1b3cc8e7334a8c0174a7e0db5265e62624cfe1f113a6ed9826247ae1debc33faa2e", + "Gas": 3000, + "Expected": "00", + "Name": "Invalid r", + "NoBenchmark": false + }, + { + "Input": "cf80cd8aed482d5d1527d7dc72fceff84e6326592848447d2dc0b0e87dfc9a9037c416673d9a1548b5b79e1529e9e0bca5b3019af6462547c86c5f2d8d5c91512bbaa47b73f668f9a1c59201d1e453679420577ed9548b95836bd20b5d821da904f9f37b3e660e01eb3f3f5535da7c96cffc620e3027883f312cda7ad59a8be1b3cc8e7334a8c0174a7e0db5265e62624cfe1f113a6ed9826247ae1debc33faa2e", + "Gas": 3000, + "Expected": "00", + "Name": "Invalid s", + "NoBenchmark": false + } +] \ No newline at end of file