From e0530706c7465075d7bc32b638be40cb8e91fecc Mon Sep 17 00:00:00 2001 From: MariusVanDerWijden Date: Mon, 16 Mar 2026 11:32:57 +0100 Subject: [PATCH 1/2] crypto: refactor Keccak256Hash to reduce allocations --- consensus/clique/clique.go | 7 +++-- core/types/bloom9.go | 4 +-- core/types/hashing.go | 5 ++-- core/types/transaction_signing.go | 3 ++- core/types/tx_setcode.go | 4 +-- core/vm/contracts.go | 2 +- crypto/crypto.go | 16 +++-------- crypto/crypto_test.go | 14 ++++++++++ crypto/keccak.go | 31 ++++++++++++++++----- crypto/keccak/sha3.go | 11 +++++++- internal/ethapi/api_test.go | 2 +- trie/hasher.go | 3 ++- trie/secure_trie.go | 45 ++++++++++++++++--------------- 13 files changed, 92 insertions(+), 55 deletions(-) diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index 28095011c1..04d130b039 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -155,8 +155,7 @@ func ecrecover(header *types.Header, sigcache *sigLRU) (common.Address, error) { if err != nil { return common.Address{}, err } - var signer common.Address - copy(signer[:], crypto.Keccak256(pubkey[1:])[12:]) + signer := crypto.Keccak256Address(pubkey[1:]) sigcache.Add(hash, signer) return signer, nil @@ -644,9 +643,9 @@ func (c *Clique) Close() error { // SealHash returns the hash of a block prior to it being sealed. func SealHash(header *types.Header) (hash common.Hash) { - hasher := keccak.NewLegacyKeccak256() + hasher := keccak.NewLegacyKeccak256State() encodeSigHeader(hasher, header) - hasher.(crypto.KeccakState).Read(hash[:]) + hasher.Read(hash[:]) return hash } diff --git a/core/types/bloom9.go b/core/types/bloom9.go index 1d57e8e4bc..4bef842007 100644 --- a/core/types/bloom9.go +++ b/core/types/bloom9.go @@ -23,7 +23,7 @@ import ( "github.com/ethereum/go-ethereum/common/bitutil" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/crypto/keccak" ) type bytesBacked interface { @@ -141,7 +141,7 @@ func Bloom9(data []byte) []byte { // bloomValues returns the bytes (index-value pairs) to set for the given data func bloomValues(data []byte, hashbuf *[6]byte) (uint, byte, uint, byte, uint, byte) { - sha := hasherPool.Get().(crypto.KeccakState) + sha := hasherPool.Get().(*keccak.KeccakState) sha.Reset() sha.Write(data) sha.Read(hashbuf[:]) diff --git a/core/types/hashing.go b/core/types/hashing.go index 98fe64e15a..f900049a08 100644 --- a/core/types/hashing.go +++ b/core/types/hashing.go @@ -24,6 +24,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/crypto/keccak" "github.com/ethereum/go-ethereum/rlp" ) @@ -55,7 +56,7 @@ func getPooledBuffer(size uint64) ([]byte, *bytes.Buffer, error) { // rlpHash encodes x and hashes the encoded bytes. func rlpHash(x interface{}) (h common.Hash) { - sha := hasherPool.Get().(crypto.KeccakState) + sha := hasherPool.Get().(*keccak.KeccakState) defer hasherPool.Put(sha) sha.Reset() rlp.Encode(sha, x) @@ -66,7 +67,7 @@ func rlpHash(x interface{}) (h common.Hash) { // prefixedRlpHash writes the prefix into the hasher before rlp-encoding x. // It's used for typed transactions. func prefixedRlpHash(prefix byte, x interface{}) (h common.Hash) { - sha := hasherPool.Get().(crypto.KeccakState) + sha := hasherPool.Get().(*keccak.KeccakState) defer hasherPool.Put(sha) sha.Reset() sha.Write([]byte{prefix}) diff --git a/core/types/transaction_signing.go b/core/types/transaction_signing.go index 5854af07c5..380702a6f1 100644 --- a/core/types/transaction_signing.go +++ b/core/types/transaction_signing.go @@ -498,8 +498,9 @@ func recoverPlain(sighash common.Hash, R, S, Vb *big.Int, homestead bool) (commo if len(pub) == 0 || pub[0] != 4 { return common.Address{}, errors.New("invalid public key") } + hash := crypto.Keccak256Hash(pub[1:]) var addr common.Address - copy(addr[:], crypto.Keccak256(pub[1:])[12:]) + copy(addr[:], hash[12:]) return addr, nil } diff --git a/core/types/tx_setcode.go b/core/types/tx_setcode.go index 9487c9cc81..a01f0198bc 100644 --- a/core/types/tx_setcode.go +++ b/core/types/tx_setcode.go @@ -136,9 +136,7 @@ func (a *SetCodeAuthorization) Authority() (common.Address, error) { if len(pub) == 0 || pub[0] != 4 { return common.Address{}, errors.New("invalid public key") } - var addr common.Address - copy(addr[:], crypto.Keccak256(pub[1:])[12:]) - return addr, nil + return crypto.Keccak256Address(pub[1:]), nil } // copy creates a deep copy of the transaction data and initializes all fields. diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 010f477337..2d7e807e72 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -316,7 +316,7 @@ func (c *ecrecover) Run(input []byte) ([]byte, error) { } // the first byte of pubkey is bitcoin heritage - return common.LeftPadBytes(crypto.Keccak256(pubKey[1:])[12:], 32), nil + return common.LeftPadBytes(crypto.Keccak256Address(pubKey[1:]).Bytes(), 32), nil } func (c *ecrecover) Name() string { diff --git a/crypto/crypto.go b/crypto/crypto.go index db6b6ee071..94037605e3 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -24,13 +24,13 @@ import ( "encoding/hex" "errors" "fmt" - "hash" "io" "math/big" "os" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/crypto/keccak" "github.com/ethereum/go-ethereum/rlp" ) @@ -59,16 +59,8 @@ type EllipticCurve interface { Unmarshal(data []byte) (x, y *big.Int) } -// KeccakState wraps sha3.state. In addition to the usual hash methods, it also supports -// Read to get a variable amount of data from the hash state. Read is faster than Sum -// because it doesn't copy the internal state, but also modifies the internal state. -type KeccakState interface { - hash.Hash - Read([]byte) (int, error) -} - // HashData hashes the provided data using the KeccakState and returns a 32 byte hash -func HashData(kh KeccakState, data []byte) (h common.Hash) { +func HashData(kh *keccak.KeccakState, data []byte) (h common.Hash) { kh.Reset() kh.Write(data) kh.Read(h[:]) @@ -78,7 +70,7 @@ func HashData(kh KeccakState, data []byte) (h common.Hash) { // CreateAddress creates an ethereum address given the bytes and the nonce func CreateAddress(b common.Address, nonce uint64) common.Address { data, _ := rlp.EncodeToBytes([]interface{}{b, nonce}) - return common.BytesToAddress(Keccak256(data)[12:]) + return Keccak256Address(data) } // CreateAddress2 creates an ethereum address given the address bytes, initial @@ -252,7 +244,7 @@ func ValidateSignatureValues(v byte, r, s *big.Int, homestead bool) bool { func PubkeyToAddress(p ecdsa.PublicKey) common.Address { pubBytes := FromECDSAPub(&p) - return common.BytesToAddress(Keccak256(pubBytes[1:])[12:]) + return Keccak256Address(pubBytes[1:]) } func zeroBytes(bytes []byte) { diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go index d803ab27c5..f6624e05f2 100644 --- a/crypto/crypto_test.go +++ b/crypto/crypto_test.go @@ -65,6 +65,20 @@ func BenchmarkSha3(b *testing.B) { } } +func BenchmarkSha3_2(b *testing.B) { + a := []byte("hello world") + for b.Loop() { + Keccak256Hash(a) + } +} + +func BenchmarkKeccak256Address(b *testing.B) { + a := []byte("hello world hello world hello world") // 36 bytes to simulate pubkey + for b.Loop() { + Keccak256Address(a) + } +} + func TestUnmarshalPubkey(t *testing.T) { key, err := UnmarshalPubkey(nil) if err != errInvalidPubkey || key != nil { diff --git a/crypto/keccak.go b/crypto/keccak.go index 3fafddc92e..14404a26fe 100644 --- a/crypto/keccak.go +++ b/crypto/keccak.go @@ -26,20 +26,20 @@ import ( ) // NewKeccakState creates a new KeccakState -func NewKeccakState() KeccakState { - return keccak.NewLegacyKeccak256().(KeccakState) +func NewKeccakState() *keccak.KeccakState { + return keccak.NewLegacyKeccak256State() } var hasherPool = sync.Pool{ New: func() any { - return keccak.NewLegacyKeccak256().(KeccakState) + return keccak.NewLegacyKeccak256State() }, } // Keccak256 calculates and returns the Keccak256 hash of the input data. func Keccak256(data ...[]byte) []byte { b := make([]byte, 32) - d := hasherPool.Get().(KeccakState) + d := hasherPool.Get().(*keccak.KeccakState) d.Reset() for _, b := range data { d.Write(b) @@ -51,8 +51,19 @@ func Keccak256(data ...[]byte) []byte { // Keccak256Hash calculates and returns the Keccak256 hash of the input data, // converting it to an internal Hash data structure. -func Keccak256Hash(data ...[]byte) (h common.Hash) { - d := hasherPool.Get().(KeccakState) +func Keccak256Hash(data []byte) (h common.Hash) { + d := hasherPool.Get().(*keccak.KeccakState) + d.Reset() + d.Write(data) + d.Read(h[:]) + hasherPool.Put(d) + return h +} + +// Keccak256HashV calculates and returns the Keccak256 hash of the input data, +// converting it to an internal Hash data structure. +func Keccak256HashV(data ...[]byte) (h common.Hash) { + d := hasherPool.Get().(*keccak.KeccakState) d.Reset() for _, b := range data { d.Write(b) @@ -61,3 +72,11 @@ func Keccak256Hash(data ...[]byte) (h common.Hash) { hasherPool.Put(d) return h } + +// Keccak256Address calculates the Keccak256 hash of the input data and +// returns the last 20 bytes as an address. +func Keccak256Address(data []byte) (a common.Address) { + hash := Keccak256Hash(data) + copy(a[:], hash[12:]) + return a +} diff --git a/crypto/keccak/sha3.go b/crypto/keccak/sha3.go index a554323244..cd18661177 100644 --- a/crypto/keccak/sha3.go +++ b/crypto/keccak/sha3.go @@ -141,7 +141,6 @@ func (d *state) Read(out []byte) (n int, err error) { } n = len(out) - // Now, do the squeezing. for len(out) > 0 { // Apply the permutation if we've squeezed the sponge dry. @@ -157,6 +156,16 @@ func (d *state) Read(out []byte) (n int, err error) { return } +// Sum256 finalizes the hash and returns the 256-bit digest. +// Returns the result by value to avoid heap allocation. +func (d *state) Sum256() (out [32]byte) { + if d.state == spongeAbsorbing { + d.padAndPermute() + } + copy(out[:], d.a[:32]) + return out +} + // Sum applies padding to the hash state and then squeezes out the desired // number of output bytes. It panics if any output has already been read. func (d *state) Sum(in []byte) []byte { diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 62e9979d3d..76e97843bd 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -458,7 +458,7 @@ type testBackend struct { } func fakeBlockHash(txh common.Hash) common.Hash { - return crypto.Keccak256Hash([]byte("testblock"), txh.Bytes()) + return crypto.Keccak256HashV([]byte("testblock"), txh.Bytes()) } func newTestBackend(t *testing.T, n int, gspec *core.Genesis, engine consensus.Engine, generator func(i int, b *core.BlockGen)) *testBackend { diff --git a/trie/hasher.go b/trie/hasher.go index a2a1f5b662..4ddf7a54df 100644 --- a/trie/hasher.go +++ b/trie/hasher.go @@ -22,13 +22,14 @@ import ( "sync" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/crypto/keccak" "github.com/ethereum/go-ethereum/rlp" ) // hasher is a type used for the trie Hash operation. A hasher has some // internal preallocated temp space type hasher struct { - sha crypto.KeccakState + sha *keccak.KeccakState tmp []byte encbuf rlp.EncoderBuffer parallel bool // Whether to use parallel threads when hashing diff --git a/trie/secure_trie.go b/trie/secure_trie.go index 1f150ede8c..6a1ec174e7 100644 --- a/trie/secure_trie.go +++ b/trie/secure_trie.go @@ -102,14 +102,16 @@ func NewStateTrie(id *ID, db database.NodeDatabase) (*StateTrie, error) { // This function will omit any encountered error but just // print out an error message. func (t *StateTrie) MustGet(key []byte) []byte { - return t.trie.MustGet(crypto.Keccak256(key)) + hk := crypto.Keccak256Hash(key) + return t.trie.MustGet(hk[:]) } // GetAccount attempts to retrieve an account with provided account address. // If the specified account is not in the trie, nil will be returned. // If a trie node is not found in the database, a MissingNodeError is returned. func (t *StateTrie) GetAccount(address common.Address) (*types.StateAccount, error) { - res, err := t.trie.Get(crypto.Keccak256(address.Bytes())) + hk := crypto.Keccak256Hash(address.Bytes()) + res, err := t.trie.Get(hk[:]) if res == nil || err != nil { return nil, err } @@ -146,7 +148,8 @@ func (t *StateTrie) PrefetchAccount(addresses []common.Address) error { // If the specified storage slot is not in the trie, nil will be returned. // If a trie node is not found in the database, a MissingNodeError is returned. func (t *StateTrie) GetStorage(_ common.Address, key []byte) ([]byte, error) { - enc, err := t.trie.Get(crypto.Keccak256(key)) + hk := crypto.Keccak256Hash(key) + enc, err := t.trie.Get(hk[:]) if err != nil || len(enc) == 0 { return nil, err } @@ -182,10 +185,10 @@ func (t *StateTrie) GetNode(path []byte) ([]byte, int, error) { // This function will omit any encountered error but just print out an // error message. func (t *StateTrie) MustUpdate(key, value []byte) { - hk := crypto.Keccak256(key) - t.trie.MustUpdate(hk, value) + hk := crypto.Keccak256Hash(key) + t.trie.MustUpdate(hk[:], value) if t.preimages != nil { - t.secKeyCache[common.Hash(hk)] = common.CopyBytes(key) + t.secKeyCache[hk] = common.CopyBytes(key) } } @@ -198,30 +201,30 @@ func (t *StateTrie) MustUpdate(key, value []byte) { // // If a node is not found in the database, a MissingNodeError is returned. func (t *StateTrie) UpdateStorage(_ common.Address, key, value []byte) error { - hk := crypto.Keccak256(key) + hk := crypto.Keccak256Hash(key) v, _ := rlp.EncodeToBytes(value) - err := t.trie.Update(hk, v) + err := t.trie.Update(hk[:], v) if err != nil { return err } if t.preimages != nil { - t.secKeyCache[common.Hash(hk)] = common.CopyBytes(key) + t.secKeyCache[hk] = common.CopyBytes(key) } return nil } // UpdateAccount will abstract the write of an account to the secure trie. func (t *StateTrie) UpdateAccount(address common.Address, acc *types.StateAccount, _ int) error { - hk := crypto.Keccak256(address.Bytes()) + hk := crypto.Keccak256Hash(address.Bytes()) data, err := rlp.EncodeToBytes(acc) if err != nil { return err } - if err := t.trie.Update(hk, data); err != nil { + if err := t.trie.Update(hk[:], data); err != nil { return err } if t.preimages != nil { - t.secKeyCache[common.Hash(hk)] = address.Bytes() + t.secKeyCache[hk] = address.Bytes() } return nil } @@ -233,31 +236,31 @@ func (t *StateTrie) UpdateContractCode(_ common.Address, _ common.Hash, _ []byte // MustDelete removes any existing value for key from the trie. This function // will omit any encountered error but just print out an error message. func (t *StateTrie) MustDelete(key []byte) { - hk := crypto.Keccak256(key) + hk := crypto.Keccak256Hash(key) if t.preimages != nil { - delete(t.secKeyCache, common.Hash(hk)) + delete(t.secKeyCache, hk) } - t.trie.MustDelete(hk) + t.trie.MustDelete(hk[:]) } // DeleteStorage removes any existing storage slot from the trie. // If the specified trie node is not in the trie, nothing will be changed. // If a node is not found in the database, a MissingNodeError is returned. func (t *StateTrie) DeleteStorage(_ common.Address, key []byte) error { - hk := crypto.Keccak256(key) + hk := crypto.Keccak256Hash(key) if t.preimages != nil { - delete(t.secKeyCache, common.Hash(hk)) + delete(t.secKeyCache, hk) } - return t.trie.Delete(hk) + return t.trie.Delete(hk[:]) } // DeleteAccount abstracts an account deletion from the trie. func (t *StateTrie) DeleteAccount(address common.Address) error { - hk := crypto.Keccak256(address.Bytes()) + hk := crypto.Keccak256Hash(address.Bytes()) if t.preimages != nil { - delete(t.secKeyCache, common.Hash(hk)) + delete(t.secKeyCache, hk) } - return t.trie.Delete(hk) + return t.trie.Delete(hk[:]) } // GetKey returns the sha3 preimage of a hashed key that was From 940a333f1c1089efe0a77c4a05644ddc0d94e955 Mon Sep 17 00:00:00 2001 From: MariusVanDerWijden Date: Mon, 16 Mar 2026 13:59:08 +0100 Subject: [PATCH 2/2] crypto/keccak: push missing file --- crypto/keccak/keccak_state.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 crypto/keccak/keccak_state.go diff --git a/crypto/keccak/keccak_state.go b/crypto/keccak/keccak_state.go new file mode 100644 index 0000000000..c83709cec7 --- /dev/null +++ b/crypto/keccak/keccak_state.go @@ -0,0 +1,12 @@ +package keccak + +// KeccakState wraps sha3.state. In addition to the usual hash methods, it also supports +// Read to get a variable amount of data from the hash state. Read is faster than Sum +// because it doesn't copy the internal state, but also modifies the internal state. +type KeccakState struct { + state +} + +func NewLegacyKeccak256State() *KeccakState { + return &KeccakState{state{rate: rateK512, outputLen: 32, dsbyte: dsbyteKeccak}} +}