crypto: refactor Keccak256Hash to reduce allocations

This commit is contained in:
MariusVanDerWijden 2026-03-16 11:32:57 +01:00
parent 189f9d0b17
commit e0530706c7
13 changed files with 92 additions and 55 deletions

View file

@ -155,8 +155,7 @@ func ecrecover(header *types.Header, sigcache *sigLRU) (common.Address, error) {
if err != nil { if err != nil {
return common.Address{}, err return common.Address{}, err
} }
var signer common.Address signer := crypto.Keccak256Address(pubkey[1:])
copy(signer[:], crypto.Keccak256(pubkey[1:])[12:])
sigcache.Add(hash, signer) sigcache.Add(hash, signer)
return signer, nil return signer, nil
@ -644,9 +643,9 @@ func (c *Clique) Close() error {
// SealHash returns the hash of a block prior to it being sealed. // SealHash returns the hash of a block prior to it being sealed.
func SealHash(header *types.Header) (hash common.Hash) { func SealHash(header *types.Header) (hash common.Hash) {
hasher := keccak.NewLegacyKeccak256() hasher := keccak.NewLegacyKeccak256State()
encodeSigHeader(hasher, header) encodeSigHeader(hasher, header)
hasher.(crypto.KeccakState).Read(hash[:]) hasher.Read(hash[:])
return hash return hash
} }

View file

@ -23,7 +23,7 @@ import (
"github.com/ethereum/go-ethereum/common/bitutil" "github.com/ethereum/go-ethereum/common/bitutil"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/keccak"
) )
type bytesBacked interface { 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 // 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) { 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.Reset()
sha.Write(data) sha.Write(data)
sha.Read(hashbuf[:]) sha.Read(hashbuf[:])

View file

@ -24,6 +24,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/keccak"
"github.com/ethereum/go-ethereum/rlp" "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. // rlpHash encodes x and hashes the encoded bytes.
func rlpHash(x interface{}) (h common.Hash) { func rlpHash(x interface{}) (h common.Hash) {
sha := hasherPool.Get().(crypto.KeccakState) sha := hasherPool.Get().(*keccak.KeccakState)
defer hasherPool.Put(sha) defer hasherPool.Put(sha)
sha.Reset() sha.Reset()
rlp.Encode(sha, x) 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. // prefixedRlpHash writes the prefix into the hasher before rlp-encoding x.
// It's used for typed transactions. // It's used for typed transactions.
func prefixedRlpHash(prefix byte, x interface{}) (h common.Hash) { func prefixedRlpHash(prefix byte, x interface{}) (h common.Hash) {
sha := hasherPool.Get().(crypto.KeccakState) sha := hasherPool.Get().(*keccak.KeccakState)
defer hasherPool.Put(sha) defer hasherPool.Put(sha)
sha.Reset() sha.Reset()
sha.Write([]byte{prefix}) sha.Write([]byte{prefix})

View file

@ -498,8 +498,9 @@ func recoverPlain(sighash common.Hash, R, S, Vb *big.Int, homestead bool) (commo
if len(pub) == 0 || pub[0] != 4 { if len(pub) == 0 || pub[0] != 4 {
return common.Address{}, errors.New("invalid public key") return common.Address{}, errors.New("invalid public key")
} }
hash := crypto.Keccak256Hash(pub[1:])
var addr common.Address var addr common.Address
copy(addr[:], crypto.Keccak256(pub[1:])[12:]) copy(addr[:], hash[12:])
return addr, nil return addr, nil
} }

View file

@ -136,9 +136,7 @@ func (a *SetCodeAuthorization) Authority() (common.Address, error) {
if len(pub) == 0 || pub[0] != 4 { if len(pub) == 0 || pub[0] != 4 {
return common.Address{}, errors.New("invalid public key") return common.Address{}, errors.New("invalid public key")
} }
var addr common.Address return crypto.Keccak256Address(pub[1:]), nil
copy(addr[:], crypto.Keccak256(pub[1:])[12:])
return addr, nil
} }
// copy creates a deep copy of the transaction data and initializes all fields. // copy creates a deep copy of the transaction data and initializes all fields.

View file

@ -316,7 +316,7 @@ func (c *ecrecover) Run(input []byte) ([]byte, error) {
} }
// the first byte of pubkey is bitcoin heritage // 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 { func (c *ecrecover) Name() string {

View file

@ -24,13 +24,13 @@ import (
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt" "fmt"
"hash"
"io" "io"
"math/big" "math/big"
"os" "os"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/crypto/keccak"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
) )
@ -59,16 +59,8 @@ type EllipticCurve interface {
Unmarshal(data []byte) (x, y *big.Int) 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 // 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.Reset()
kh.Write(data) kh.Write(data)
kh.Read(h[:]) 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 // CreateAddress creates an ethereum address given the bytes and the nonce
func CreateAddress(b common.Address, nonce uint64) common.Address { func CreateAddress(b common.Address, nonce uint64) common.Address {
data, _ := rlp.EncodeToBytes([]interface{}{b, nonce}) 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 // 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 { func PubkeyToAddress(p ecdsa.PublicKey) common.Address {
pubBytes := FromECDSAPub(&p) pubBytes := FromECDSAPub(&p)
return common.BytesToAddress(Keccak256(pubBytes[1:])[12:]) return Keccak256Address(pubBytes[1:])
} }
func zeroBytes(bytes []byte) { func zeroBytes(bytes []byte) {

View file

@ -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) { func TestUnmarshalPubkey(t *testing.T) {
key, err := UnmarshalPubkey(nil) key, err := UnmarshalPubkey(nil)
if err != errInvalidPubkey || key != nil { if err != errInvalidPubkey || key != nil {

View file

@ -26,20 +26,20 @@ import (
) )
// NewKeccakState creates a new KeccakState // NewKeccakState creates a new KeccakState
func NewKeccakState() KeccakState { func NewKeccakState() *keccak.KeccakState {
return keccak.NewLegacyKeccak256().(KeccakState) return keccak.NewLegacyKeccak256State()
} }
var hasherPool = sync.Pool{ var hasherPool = sync.Pool{
New: func() any { New: func() any {
return keccak.NewLegacyKeccak256().(KeccakState) return keccak.NewLegacyKeccak256State()
}, },
} }
// Keccak256 calculates and returns the Keccak256 hash of the input data. // Keccak256 calculates and returns the Keccak256 hash of the input data.
func Keccak256(data ...[]byte) []byte { func Keccak256(data ...[]byte) []byte {
b := make([]byte, 32) b := make([]byte, 32)
d := hasherPool.Get().(KeccakState) d := hasherPool.Get().(*keccak.KeccakState)
d.Reset() d.Reset()
for _, b := range data { for _, b := range data {
d.Write(b) d.Write(b)
@ -51,8 +51,19 @@ func Keccak256(data ...[]byte) []byte {
// Keccak256Hash calculates and returns the Keccak256 hash of the input data, // Keccak256Hash calculates and returns the Keccak256 hash of the input data,
// converting it to an internal Hash data structure. // converting it to an internal Hash data structure.
func Keccak256Hash(data ...[]byte) (h common.Hash) { func Keccak256Hash(data []byte) (h common.Hash) {
d := hasherPool.Get().(KeccakState) 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() d.Reset()
for _, b := range data { for _, b := range data {
d.Write(b) d.Write(b)
@ -61,3 +72,11 @@ func Keccak256Hash(data ...[]byte) (h common.Hash) {
hasherPool.Put(d) hasherPool.Put(d)
return h 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
}

View file

@ -141,7 +141,6 @@ func (d *state) Read(out []byte) (n int, err error) {
} }
n = len(out) n = len(out)
// Now, do the squeezing. // Now, do the squeezing.
for len(out) > 0 { for len(out) > 0 {
// Apply the permutation if we've squeezed the sponge dry. // 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 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 // 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. // number of output bytes. It panics if any output has already been read.
func (d *state) Sum(in []byte) []byte { func (d *state) Sum(in []byte) []byte {

View file

@ -458,7 +458,7 @@ type testBackend struct {
} }
func fakeBlockHash(txh common.Hash) common.Hash { 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 { func newTestBackend(t *testing.T, n int, gspec *core.Genesis, engine consensus.Engine, generator func(i int, b *core.BlockGen)) *testBackend {

View file

@ -22,13 +22,14 @@ import (
"sync" "sync"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/keccak"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
) )
// hasher is a type used for the trie Hash operation. A hasher has some // hasher is a type used for the trie Hash operation. A hasher has some
// internal preallocated temp space // internal preallocated temp space
type hasher struct { type hasher struct {
sha crypto.KeccakState sha *keccak.KeccakState
tmp []byte tmp []byte
encbuf rlp.EncoderBuffer encbuf rlp.EncoderBuffer
parallel bool // Whether to use parallel threads when hashing parallel bool // Whether to use parallel threads when hashing

View file

@ -102,14 +102,16 @@ func NewStateTrie(id *ID, db database.NodeDatabase) (*StateTrie, error) {
// This function will omit any encountered error but just // This function will omit any encountered error but just
// print out an error message. // print out an error message.
func (t *StateTrie) MustGet(key []byte) []byte { 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. // GetAccount attempts to retrieve an account with provided account address.
// If the specified account is not in the trie, nil will be returned. // 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. // If a trie node is not found in the database, a MissingNodeError is returned.
func (t *StateTrie) GetAccount(address common.Address) (*types.StateAccount, error) { 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 { if res == nil || err != nil {
return nil, err 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 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. // If a trie node is not found in the database, a MissingNodeError is returned.
func (t *StateTrie) GetStorage(_ common.Address, key []byte) ([]byte, error) { 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 { if err != nil || len(enc) == 0 {
return nil, err 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 // This function will omit any encountered error but just print out an
// error message. // error message.
func (t *StateTrie) MustUpdate(key, value []byte) { func (t *StateTrie) MustUpdate(key, value []byte) {
hk := crypto.Keccak256(key) hk := crypto.Keccak256Hash(key)
t.trie.MustUpdate(hk, value) t.trie.MustUpdate(hk[:], value)
if t.preimages != nil { 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. // If a node is not found in the database, a MissingNodeError is returned.
func (t *StateTrie) UpdateStorage(_ common.Address, key, value []byte) error { func (t *StateTrie) UpdateStorage(_ common.Address, key, value []byte) error {
hk := crypto.Keccak256(key) hk := crypto.Keccak256Hash(key)
v, _ := rlp.EncodeToBytes(value) v, _ := rlp.EncodeToBytes(value)
err := t.trie.Update(hk, v) err := t.trie.Update(hk[:], v)
if err != nil { if err != nil {
return err return err
} }
if t.preimages != nil { if t.preimages != nil {
t.secKeyCache[common.Hash(hk)] = common.CopyBytes(key) t.secKeyCache[hk] = common.CopyBytes(key)
} }
return nil return nil
} }
// UpdateAccount will abstract the write of an account to the secure trie. // UpdateAccount will abstract the write of an account to the secure trie.
func (t *StateTrie) UpdateAccount(address common.Address, acc *types.StateAccount, _ int) error { 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) data, err := rlp.EncodeToBytes(acc)
if err != nil { if err != nil {
return err return err
} }
if err := t.trie.Update(hk, data); err != nil { if err := t.trie.Update(hk[:], data); err != nil {
return err return err
} }
if t.preimages != nil { if t.preimages != nil {
t.secKeyCache[common.Hash(hk)] = address.Bytes() t.secKeyCache[hk] = address.Bytes()
} }
return nil 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 // MustDelete removes any existing value for key from the trie. This function
// will omit any encountered error but just print out an error message. // will omit any encountered error but just print out an error message.
func (t *StateTrie) MustDelete(key []byte) { func (t *StateTrie) MustDelete(key []byte) {
hk := crypto.Keccak256(key) hk := crypto.Keccak256Hash(key)
if t.preimages != nil { 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. // DeleteStorage removes any existing storage slot from the trie.
// If the specified trie node is not in the trie, nothing will be changed. // 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. // If a node is not found in the database, a MissingNodeError is returned.
func (t *StateTrie) DeleteStorage(_ common.Address, key []byte) error { func (t *StateTrie) DeleteStorage(_ common.Address, key []byte) error {
hk := crypto.Keccak256(key) hk := crypto.Keccak256Hash(key)
if t.preimages != nil { 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. // DeleteAccount abstracts an account deletion from the trie.
func (t *StateTrie) DeleteAccount(address common.Address) error { func (t *StateTrie) DeleteAccount(address common.Address) error {
hk := crypto.Keccak256(address.Bytes()) hk := crypto.Keccak256Hash(address.Bytes())
if t.preimages != nil { 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 // GetKey returns the sha3 preimage of a hashed key that was