Skip to content

Commit

Permalink
crypto/merkle: optimize merkle tree hashing (#6513) (#9446)
Browse files Browse the repository at this point in the history
* crypto/merkle: optimize merkle tree hashing (#6513)

Upstream celestiaorg/celestia-core#351 to optimize merkle tree hashing

```
benchmark                                 old ns/op     new ns/op     delta
BenchmarkHashAlternatives/recursive-8     22914         21949         -4.21%
BenchmarkHashAlternatives/iterative-8     21634         21939         +1.41%

benchmark                                 old allocs     new allocs     delta
BenchmarkHashAlternatives/recursive-8     398            200            -49.75%
BenchmarkHashAlternatives/iterative-8     399            301            -24.56%

benchmark                                 old bytes     new bytes     delta
BenchmarkHashAlternatives/recursive-8     19088         6496          -65.97%
BenchmarkHashAlternatives/iterative-8     21776         13984         -35.78%
```

cc @odeke-em @cuonglm

* update pending log

Co-authored-by: Marko <marbar3778@yahoo.com>
  • Loading branch information
2 people authored and czarcas7ic committed Apr 23, 2024
1 parent 9311fe7 commit 8e787f0
Show file tree
Hide file tree
Showing 5 changed files with 32 additions and 10 deletions.
18 changes: 18 additions & 0 deletions crypto/merkle/hash.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package merkle

import (
"hash"

"github.com/cometbft/cometbft/crypto/tmhash"
)

Expand All @@ -20,7 +22,23 @@ func leafHash(leaf []byte) []byte {
return tmhash.Sum(append(leafPrefix, leaf...))
}

// returns tmhash(0x00 || leaf)
func leafHashOpt(s hash.Hash, leaf []byte) []byte {
s.Reset()
s.Write(leafPrefix)
s.Write(leaf)
return s.Sum(nil)
}

// returns tmhash(0x01 || left || right)
func innerHash(left []byte, right []byte) []byte {
return tmhash.Sum(append(innerPrefix, append(left, right...)...))
}

func innerHashOpt(s hash.Hash, left []byte, right []byte) []byte {
s.Reset()
s.Write(innerPrefix)
s.Write(left)
s.Write(right)
return s.Sum(nil)
}
3 changes: 0 additions & 3 deletions crypto/merkle/proof.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,6 @@ func ProofsFromByteSlices(items [][]byte) (rootHash []byte, proofs []*Proof) {
// Verify that the Proof proves the root hash.
// Check sp.Index/sp.Total manually if needed
func (sp *Proof) Verify(rootHash []byte, leaf []byte) error {
if rootHash == nil {
return fmt.Errorf("invalid root hash: cannot be nil")
}
if sp.Total < 0 {
return errors.New("proof total must be positive")
}
Expand Down
1 change: 1 addition & 0 deletions crypto/merkle/proof_key_path_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ func TestKeyPath(t *testing.T) {

res, err := KeyPathToKeys(path.String())
require.Nil(t, err)
require.Equal(t, len(keys), len(res))

for i, key := range keys {
require.Equal(t, key, res[i])
Expand Down
2 changes: 1 addition & 1 deletion crypto/merkle/proof_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,12 +173,12 @@ func TestProofValidateBasic(t *testing.T) {
}
}
func TestVoteProtobuf(t *testing.T) {

_, proofs := ProofsFromByteSlices([][]byte{
[]byte("apple"),
[]byte("watermelon"),
[]byte("kiwi"),
})

testCases := []struct {
testName string
v1 *Proof
Expand Down
18 changes: 12 additions & 6 deletions crypto/merkle/tree.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,28 @@
package merkle

import (
"crypto/sha256"
"hash"
"math/bits"
)

// HashFromByteSlices computes a Merkle tree where the leaves are the byte slice,
// in the provided order. It follows RFC-6962.
func HashFromByteSlices(items [][]byte) []byte {
return hashFromByteSlices(sha256.New(), items)
}

func hashFromByteSlices(sha hash.Hash, items [][]byte) []byte {
switch len(items) {
case 0:
return emptyHash()
case 1:
return leafHash(items[0])
return leafHashOpt(sha, items[0])
default:
k := getSplitPoint(int64(len(items)))
left := HashFromByteSlices(items[:k])
right := HashFromByteSlices(items[k:])
return innerHash(left, right)
left := hashFromByteSlices(sha, items[:k])
right := hashFromByteSlices(sha, items[k:])
return innerHashOpt(sha, left, right)
}
}

Expand Down Expand Up @@ -61,7 +67,7 @@ func HashFromByteSlices(items [][]byte) []byte {
// implementation for so little benefit.
func HashFromByteSlicesIterative(input [][]byte) []byte {
items := make([][]byte, len(input))

sha := sha256.New()
for i, leaf := range input {
items[i] = leafHash(leaf)
}
Expand All @@ -78,7 +84,7 @@ func HashFromByteSlicesIterative(input [][]byte) []byte {
wp := 0 // write position
for rp < size {
if rp+1 < size {
items[wp] = innerHash(items[rp], items[rp+1])
items[wp] = innerHashOpt(sha, items[rp], items[rp+1])
rp += 2
} else {
items[wp] = items[rp]
Expand Down

0 comments on commit 8e787f0

Please sign in to comment.