Navigation Menu

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
JayT106 and tac0turtle committed Sep 21, 2022
1 parent bfdeccd commit ab4238a
Show file tree
Hide file tree
Showing 6 changed files with 34 additions and 9 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG_PENDING.md
Expand Up @@ -22,7 +22,7 @@
### IMPROVEMENTS

- [pubsub] \#7319 Performance improvements for the event query API (@creachadair)
- [crypto/merkle] \#6443 Improve HashAlternatives performance (@cuonglm)
- [crypto/merkle] \#6443 & \#6513 Improve HashAlternatives performance (@cuonglm, @marbar3778)

### BUG FIXES

Expand Down
18 changes: 18 additions & 0 deletions crypto/merkle/hash.go
@@ -1,6 +1,8 @@
package merkle

import (
"hash"

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

Expand All @@ -20,6 +22,14 @@ 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 {
data := make([]byte, len(innerPrefix)+len(left)+len(right))
Expand All @@ -28,3 +38,11 @@ func innerHash(left []byte, right []byte) []byte {
copy(data[n:], right)
return tmhash.Sum(data)
}

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)
}
2 changes: 1 addition & 1 deletion crypto/merkle/proof.go
Expand Up @@ -50,13 +50,13 @@ 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 {
leafHash := leafHash(leaf)
if sp.Total < 0 {
return errors.New("proof total must be positive")
}
if sp.Index < 0 {
return errors.New("proof index cannot be negative")
}
leafHash := leafHash(leaf)
if !bytes.Equal(sp.LeafHash, leafHash) {
return fmt.Errorf("invalid leaf hash: wanted %X got %X", leafHash, sp.LeafHash)
}
Expand Down
1 change: 1 addition & 0 deletions crypto/merkle/proof_key_path_test.go
Expand Up @@ -35,6 +35,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
Expand Up @@ -171,12 +171,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
@@ -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 ab4238a

Please sign in to comment.