From 70d958370f5fd7e463450abc9ed4b3f40d0c541a Mon Sep 17 00:00:00 2001 From: Simsonraj Easvarasakthi Date: Fri, 17 Sep 2021 22:36:42 +0530 Subject: [PATCH] Implementation of SHA3-256 FIPS 202 hash precompile and EcRecover Uncompressed public key precompile to integrate with ICON blockchain (#3801) * SHA3-256 FIPS-202 precompiled contract implementation * go fmt * Added precompiles address under VRF * added sha3 prcompiles to Istanbul * fix goimports error * Updated the sha3 & ecreceover pk contract address to 253 & 254 respectively * Added a new PrecompiledContractsSHA3FIPS and add forking epoch & Changed the gas prices for sha3 Co-authored-by: Ganesha Upadhyaya --- core/vm/contracts.go | 79 ++++++++++++++++++++++++++++++ core/vm/contracts_test.go | 64 ++++++++++++++++++++++-- core/vm/evm.go | 6 +++ internal/params/config.go | 26 ++++++++-- internal/params/protocol_params.go | 5 ++ 5 files changed, 172 insertions(+), 8 deletions(-) diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 2f5042804..64754a15f 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -30,6 +30,11 @@ import ( "github.com/ethereum/go-ethereum/crypto/bn256" "github.com/harmony-one/harmony/internal/params" "golang.org/x/crypto/ripemd160" + + //Needed for SHA3-256 FIPS202 + "encoding/hex" + + "golang.org/x/crypto/sha3" ) // PrecompiledContract is the basic interface for native Go contracts. The implementation @@ -91,6 +96,24 @@ var PrecompiledContractsVRF = map[common.Address]PrecompiledContract{ common.BytesToAddress([]byte{255}): &vrf{}, } +// PrecompiledContractsSHA3FIPS contains the default set of pre-compiled Ethereum +// contracts used in the Istanbul release. plus VRF and SHA3FIPS-202 standard +var PrecompiledContractsSHA3FIPS = 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{}, + common.BytesToAddress([]byte{6}): &bn256AddIstanbul{}, + common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, + common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, + common.BytesToAddress([]byte{9}): &blake2F{}, + common.BytesToAddress([]byte{255}): &vrf{}, + + common.BytesToAddress([]byte{253}): &sha3fip{}, + common.BytesToAddress([]byte{254}): &ecrecoverPublicKey{}, +} + // RunPrecompiledContract runs and evaluates the output of a precompiled contract. func RunPrecompiledContract(p PrecompiledContract, input []byte, contract *Contract) (ret []byte, err error) { gas := p.RequiredGas(input) @@ -533,3 +556,59 @@ func (c *vrf) Run(input []byte) ([]byte, error) { // So here we simply return it return append([]byte{}, input...), nil } + +// SHA3-256 FIPS 202 standard implemented as a native contract. + +type sha3fip struct{} + +// TODO Check if the gas price calculation needs modification +// RequiredGas returns the gas required to execute the pre-compiled contract. +// +// This method does not require any overflow checking as the input size gas costs +// required for anything significant is so high it's impossible to pay for. +func (c *sha3fip) RequiredGas(input []byte) uint64 { + return uint64(len(input)+31)/32*params.Sha3FipsWordGas + params.Sha3FipsGas +} +func (c *sha3fip) Run(input []byte) ([]byte, error) { + hexStr := common.Bytes2Hex(input) + pub, _ := hex.DecodeString(hexStr) + h := sha3.Sum256(pub[:]) + return h[:], nil +} + +// ECRECOVER implemented as a native contract. +type ecrecoverPublicKey struct{} + +func (c *ecrecoverPublicKey) RequiredGas(input []byte) uint64 { + return params.EcrecoverGas +} + +func (c *ecrecoverPublicKey) Run(input []byte) ([]byte, error) { + const ecrecoverPublicKeyInputLength = 128 + + input = common.RightPadBytes(input, ecrecoverPublicKeyInputLength) + // "input" is (hash, v, r, s), each 32 bytes + // but for ecrecover we want (r, s, v) + + r := new(big.Int).SetBytes(input[64:96]) + s := new(big.Int).SetBytes(input[96:128]) + v := input[63] + + // tighter sig s values input homestead only apply to tx sigs + if !allZero(input[32:63]) || !crypto.ValidateSignatureValues(v, r, s, false) { + return nil, nil + } + // We must make sure not to modify the 'input', so placing the 'v' along with + // the signature needs to be done on a new allocation + sig := make([]byte, 65) + copy(sig, input[64:128]) + sig[64] = v + // v needs to be at the end for libsecp256k1 + pubKey, err := crypto.Ecrecover(input[:32], sig) + // make sure the public key is a valid one + if err != nil { + return nil, nil + } + + return pubKey, nil +} diff --git a/core/vm/contracts_test.go b/core/vm/contracts_test.go index be003a60c..54cfcb5f5 100644 --- a/core/vm/contracts_test.go +++ b/core/vm/contracts_test.go @@ -399,7 +399,7 @@ var blake2FTests = []precompiledTest{ } func testPrecompiled(addr string, test precompiledTest, t *testing.T) { - p := PrecompiledContractsIstanbul[common.HexToAddress(addr)] + p := PrecompiledContractsSHA3FIPS[common.HexToAddress(addr)] in := common.Hex2Bytes(test.input) contract := NewContract(AccountRef(common.HexToAddress("1337")), nil, new(big.Int), p.RequiredGas(in)) @@ -418,7 +418,7 @@ func testPrecompiled(addr string, test precompiledTest, t *testing.T) { } func testPrecompiledOOG(addr string, test precompiledTest, t *testing.T) { - p := PrecompiledContractsIstanbul[common.HexToAddress(addr)] + p := PrecompiledContractsSHA3FIPS[common.HexToAddress(addr)] in := common.Hex2Bytes(test.input) contract := NewContract(AccountRef(common.HexToAddress("1337")), nil, new(big.Int), p.RequiredGas(in)-1) @@ -436,7 +436,7 @@ func testPrecompiledOOG(addr string, test precompiledTest, t *testing.T) { } func testPrecompiledFailure(addr string, test precompiledFailureTest, t *testing.T) { - p := PrecompiledContractsIstanbul[common.HexToAddress(addr)] + p := PrecompiledContractsSHA3FIPS[common.HexToAddress(addr)] in := common.Hex2Bytes(test.input) contract := NewContract(AccountRef(common.HexToAddress("31337")), nil, new(big.Int), p.RequiredGas(in)) @@ -458,7 +458,7 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) { if test.noBenchmark { return } - p := PrecompiledContractsIstanbul[common.HexToAddress(addr)] + p := PrecompiledContractsSHA3FIPS[common.HexToAddress(addr)] in := common.Hex2Bytes(test.input) reqGas := p.RequiredGas(in) contract := NewContract(AccountRef(common.HexToAddress("1337")), @@ -661,3 +661,59 @@ func TestPrecompiledEcrecover(t *testing.T) { } } + +// sha3fip test vectors +var sha3fipTests = []precompiledTest{ + { + input: "0448250ebe88d77e0a12bcf530fe6a2cf1ac176945638d309b840d631940c93b78c2bd6d16f227a8877e3f1604cd75b9c5a8ab0cac95174a8a0a0f8ea9e4c10bca", + expected: "c7647f7e251bf1bd70863c8693e93a4e77dd0c9a689073e987d51254317dc704", + name: "sha3fip", + }, + { + input: "1234", + expected: "19becdc0e8d6dd4aa2c9c2983dbb9c61956a8ade69b360d3e6019f0bcd5557a9", + name: "sha3fip", + }, +} + +func TestPrecompiledSHA3fip(t *testing.T) { + + for _, test := range sha3fipTests { + testPrecompiled("FD", test, t) + } + +} + +// EcRecover test vectors +var ecRecoverPublicKeyTests = []precompiledTest{ + { + input: "c5d6c454e4d7a8e8a654f5ef96e8efe41d21a65b171b298925414aa3dc061e37" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "4011de30c04302a2352400df3d1459d6d8799580dceb259f45db1d99243a8d0c" + + "64f548b7776cb93e37579b830fc3efce41e12e0958cda9f8c5fcad682c610795", + expected: "0448250ebe88d77e0a12bcf530fe6a2cf1ac176945638d309b840d631940c93b78c2bd6d16f227a8877e3f1604cd75b9c5a8ab0cac95174a8a0a0f8ea9e4c10bca", + name: "CallEcrecoverrecoverable Key", + }, + { + input: "c5d6c454e4d7a8e8a654f5ef96e8efe41d21a65b171b298925414aa3dc061e37" + + "000000000000000000000000000000000000000000000000000000000000001b" + + "4011de30c04302a2352400df3d1459d6d8799580dceb259f45db1d99243a8d0c" + + "64f548b7776cb93e37579b830fc3efce41e12e0958cda9f8c5fcad682c610795", + expected: "", + name: "InvalidLowV-bits-1", + }, { + input: "c5d6c454e4d7a8e8a654f5ef96e8efe41d21a65b171b298925414aa3dc061e37" + + "000000000000000000000000000000000000000000000000000000000000001c" + + "4011de30c04302a2352400df3d1459d6d8799580dceb259f45db1d99243a8d0c" + + "64f548b7776cb93e37579b830fc3efce41e12e0958cda9f8c5fcad682c610795", + expected: "", + name: "InvalidLowV-bits-1", + }, +} + +func TestPrecompiledEcrecoverPublicKey(t *testing.T) { + for _, test := range ecRecoverPublicKeyTests { + testPrecompiled("FE", test, t) + } + +} diff --git a/core/vm/evm.go b/core/vm/evm.go index 61a3269a3..4bb02409d 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -60,6 +60,9 @@ func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, err if evm.chainRules.IsVRF { precompiles = PrecompiledContractsVRF } + if evm.chainRules.IsSHA3 { + precompiles = PrecompiledContractsSHA3FIPS + } if p := precompiles[*contract.CodeAddr]; p != nil { if _, ok := p.(*vrf); ok { if evm.chainRules.IsPrevVRF { @@ -262,6 +265,9 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas if evm.chainRules.IsVRF { precompiles = PrecompiledContractsVRF } + if evm.chainRules.IsSHA3 { + precompiles = PrecompiledContractsSHA3FIPS + } if precompiles[addr] == nil && evm.ChainConfig().IsS3(evm.EpochNumber) && value.Sign() == 0 { // Calling a non existing account, don't do anything, but ping the tracer diff --git a/internal/params/config.go b/internal/params/config.go index 0ec90c5ac..f016b5eb3 100644 --- a/internal/params/config.go +++ b/internal/params/config.go @@ -62,6 +62,7 @@ var ( DataCopyFixEpoch: big.NewInt(689), // Around Wed Sept 15th 2021 with 3.5s block time IstanbulEpoch: big.NewInt(314), ReceiptLogEpoch: big.NewInt(101), + SHA3Epoch: EpochTBD, //EpochTBD } // TestnetChainConfig contains the chain parameters to run a node on the harmony test network. @@ -92,6 +93,7 @@ var ( DataCopyFixEpoch: big.NewInt(74412), IstanbulEpoch: big.NewInt(43800), ReceiptLogEpoch: big.NewInt(0), + SHA3Epoch: EpochTBD, //EpochTBD } // PangaeaChainConfig contains the chain parameters for the Pangaea network. @@ -123,6 +125,7 @@ var ( DataCopyFixEpoch: big.NewInt(0), IstanbulEpoch: big.NewInt(0), ReceiptLogEpoch: big.NewInt(0), + SHA3Epoch: big.NewInt(0), } // PartnerChainConfig contains the chain parameters for the Partner network. @@ -154,6 +157,7 @@ var ( DataCopyFixEpoch: big.NewInt(0), IstanbulEpoch: big.NewInt(0), ReceiptLogEpoch: big.NewInt(0), + SHA3Epoch: big.NewInt(0), } // StressnetChainConfig contains the chain parameters for the Stress test network. @@ -185,6 +189,7 @@ var ( DataCopyFixEpoch: big.NewInt(0), IstanbulEpoch: big.NewInt(0), ReceiptLogEpoch: big.NewInt(0), + SHA3Epoch: big.NewInt(0), } // LocalnetChainConfig contains the chain parameters to run for local development. @@ -215,6 +220,7 @@ var ( DataCopyFixEpoch: big.NewInt(0), IstanbulEpoch: big.NewInt(0), ReceiptLogEpoch: big.NewInt(0), + SHA3Epoch: big.NewInt(0), } // AllProtocolChanges ... @@ -247,6 +253,7 @@ var ( big.NewInt(0), // DataCopyFixEpoch big.NewInt(0), // IstanbulEpoch big.NewInt(0), // ReceiptLogEpoch + big.NewInt(0), } // TestChainConfig ... @@ -279,6 +286,7 @@ var ( big.NewInt(0), // DataCopyFixEpoch big.NewInt(0), // IstanbulEpoch big.NewInt(0), // ReceiptLogEpoch + big.NewInt(0), } // TestRules ... @@ -387,11 +395,14 @@ type ChainConfig struct { // ReceiptLogEpoch is the first epoch support receiptlog ReceiptLogEpoch *big.Int `json:"receipt-log-epoch,omitempty"` + + // IsSHA3Epoch is the first epoch in supporting SHA3 FIPS-202 standard + SHA3Epoch *big.Int `json:"sha3-epoch,omitempty"` } // String implements the fmt.Stringer interface. func (c *ChainConfig) String() string { - return fmt.Sprintf("{ChainID: %v EthCompatibleChainID: %v EIP155: %v CrossTx: %v Staking: %v CrossLink: %v ReceiptLog: %v}", + return fmt.Sprintf("{ChainID: %v EthCompatibleChainID: %v EIP155: %v CrossTx: %v Staking: %v CrossLink: %v ReceiptLog: %v SHA3Epoch:%v}", c.ChainID, c.EthCompatibleChainID, c.EIP155Epoch, @@ -399,6 +410,7 @@ func (c *ChainConfig) String() string { c.StakingEpoch, c.CrossLinkEpoch, c.ReceiptLogEpoch, + c.SHA3Epoch, ) } @@ -527,6 +539,11 @@ func (c *ChainConfig) IsReceiptLog(epoch *big.Int) bool { return isForked(c.ReceiptLogEpoch, epoch) } +// IsSHA3 returns whether epoch is either equal to the IsSHA3 fork epoch or greater. +func (c *ChainConfig) IsSHA3(epoch *big.Int) bool { + return isForked(c.SHA3Epoch, epoch) +} + // UpdateEthChainIDByShard update the ethChainID based on shard ID. func UpdateEthChainIDByShard(shardID uint32) { once.Do(func() { @@ -575,9 +592,9 @@ func isForked(s, epoch *big.Int) bool { // Rules is a one time interface meaning that it shouldn't be used in between transition // phases. type Rules struct { - ChainID *big.Int - EthChainID *big.Int - IsCrossLink, IsEIP155, IsS3, IsReceiptLog, IsIstanbul, IsVRF, IsPrevVRF bool + ChainID *big.Int + EthChainID *big.Int + IsCrossLink, IsEIP155, IsS3, IsReceiptLog, IsIstanbul, IsVRF, IsPrevVRF, IsSHA3 bool } // Rules ensures c's ChainID is not nil. @@ -600,5 +617,6 @@ func (c *ChainConfig) Rules(epoch *big.Int) Rules { IsIstanbul: c.IsIstanbul(epoch), IsVRF: c.IsVRF(epoch), IsPrevVRF: c.IsPrevVRF(epoch), + IsSHA3: c.IsSHA3(epoch), } } diff --git a/internal/params/protocol_params.go b/internal/params/protocol_params.go index a86313fab..316011841 100644 --- a/internal/params/protocol_params.go +++ b/internal/params/protocol_params.go @@ -170,6 +170,11 @@ const ( Bn256PairingBaseGasIstanbul uint64 = 45000 // Base price for an elliptic curve pairing check Bn256PairingPerPointGasByzantium uint64 = 80000 // Byzantium per-point price for an elliptic curve pairing check Bn256PairingPerPointGasIstanbul uint64 = 34000 // Per-point price for an elliptic curve pairing check + + //SHA3-FIPS Precompiled contracts gas price esstimation as per ethereum yellow paper appendix G + Sha3FipsGas uint64 = 30 // Once per SHA3-256 operation. + Sha3FipsWordGas uint64 = 6 // Once per word of the SHA3-256 operation's data. + ) // nolint