diff --git a/rfc6962/rfc6962.go b/rfc6962/rfc6962.go index b04f952e..ab109c7a 100644 --- a/rfc6962/rfc6962.go +++ b/rfc6962/rfc6962.go @@ -17,7 +17,7 @@ package rfc6962 import ( "crypto" - _ "crypto/sha256" // SHA256 is the default algorithm. + "crypto/sha256" ) // Domain separation prefixes @@ -41,12 +41,18 @@ func New(h crypto.Hash) *Hasher { // EmptyRoot returns a special case for an empty tree. func (t *Hasher) EmptyRoot() []byte { + if t.Hash == crypto.SHA256 { + h := sha256.Sum256(nil) + return h[:] + } return t.New().Sum(nil) } // HashLeaf returns the Merkle tree leaf hash of the data passed in through leaf. // The data in leaf is prefixed by the LeafHashPrefix. func (t *Hasher) HashLeaf(leaf []byte) []byte { + // Note: A SHA-256 fast path using sha256.Sum256 is possible here if leaf data is + // staged into an array on the stack, but requires an arbitrary buffer size threshold. h := t.New() h.Write([]byte{RFC6962LeafHashPrefix}) h.Write(leaf) @@ -56,6 +62,10 @@ func (t *Hasher) HashLeaf(leaf []byte) []byte { // HashChildren returns the inner Merkle tree node hash of the two child nodes l and r. // The hashed structure is NodeHashPrefix||l||r. func (t *Hasher) HashChildren(l, r []byte) []byte { + if t.Hash == crypto.SHA256 && len(l) == sha256.Size && len(r) == sha256.Size { + // Fast path for sha256 common case + return hashChildren256(l, r) + } h := t.New() b := append(append(append( make([]byte, 0, 1+len(l)+len(r)), @@ -66,3 +76,19 @@ func (t *Hasher) HashChildren(l, r []byte) []byte { h.Write(b) return h.Sum(nil) } + +// hashChildren256 hashes the fixed 65-byte RFC6962 interior node preimage on the stack. +// +// This optimization relies on using the concrete sha256.Sum256 function directly. +// Because the compiler has full visibility into Sum256, it can prove that b does +// not outlive the call, keeping b on the stack (1 allocation total for the returned slice). +// Passing b through an interface (e.g. t.New().Write(b)) would force b to escape to the +// heap because the compiler cannot prove an interface method won't retain it. +func hashChildren256(l, r []byte) []byte { + var b [1 + 2*sha256.Size]byte + b[0] = RFC6962NodeHashPrefix + copy(b[1:], l) + copy(b[1+sha256.Size:], r) + h := sha256.Sum256(b[:]) + return h[:] +} diff --git a/rfc6962/rfc6962_test.go b/rfc6962/rfc6962_test.go index 015983c0..9d3d7a33 100644 --- a/rfc6962/rfc6962_test.go +++ b/rfc6962/rfc6962_test.go @@ -105,3 +105,12 @@ func BenchmarkHashChildren(b *testing.B) { _ = h.HashChildren(l, r) } } + +func BenchmarkHashLeaf(b *testing.B) { + h := DefaultHasher + data := []byte("benchmark leaf payload") + b.ResetTimer() + for range b.N { + _ = h.HashLeaf(data) + } +}