github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/crypto/merkle/tree.go (about) 1 package merkle 2 3 import ( 4 "crypto/sha256" 5 "hash" 6 "math/bits" 7 ) 8 9 // HashFromByteSlices computes a Merkle tree where the leaves are the byte slice, 10 // in the provided order. It follows RFC-6962. 11 func HashFromByteSlices(items [][]byte) []byte { 12 return hashFromByteSlices(sha256.New(), items) 13 } 14 15 func hashFromByteSlices(sha hash.Hash, items [][]byte) []byte { 16 switch len(items) { 17 case 0: 18 return emptyHash() 19 case 1: 20 return leafHashOpt(sha, items[0]) 21 default: 22 k := getSplitPoint(int64(len(items))) 23 left := hashFromByteSlices(sha, items[:k]) 24 right := hashFromByteSlices(sha, items[k:]) 25 return innerHashOpt(sha, left, right) 26 } 27 } 28 29 // HashFromByteSliceIterative is an iterative alternative to 30 // HashFromByteSlice motivated by potential performance improvements. 31 // (#2611) had suggested that an iterative version of 32 // HashFromByteSlice would be faster, presumably because 33 // we can envision some overhead accumulating from stack 34 // frames and function calls. Additionally, a recursive algorithm risks 35 // hitting the stack limit and causing a stack overflow should the tree 36 // be too large. 37 // 38 // Provided here is an iterative alternative, a test to assert 39 // correctness and a benchmark. On the performance side, there appears to 40 // be no overall difference: 41 // 42 // BenchmarkHashAlternatives/recursive-4 20000 77677 ns/op 43 // BenchmarkHashAlternatives/iterative-4 20000 76802 ns/op 44 // 45 // On the surface it might seem that the additional overhead is due to 46 // the different allocation patterns of the implementations. The recursive 47 // version uses a single [][]byte slices which it then re-slices at each level of the tree. 48 // The iterative version reproduces [][]byte once within the function and 49 // then rewrites sub-slices of that array at each level of the tree. 50 // 51 // Experimenting by modifying the code to simply calculate the 52 // hash and not store the result show little to no difference in performance. 53 // 54 // These preliminary results suggest: 55 // 56 // 1. The performance of the HashFromByteSlice is pretty good 57 // 2. Go has low overhead for recursive functions 58 // 3. The performance of the HashFromByteSlice routine is dominated 59 // by the actual hashing of data 60 // 61 // Although this work is in no way exhaustive, point #3 suggests that 62 // optimization of this routine would need to take an alternative 63 // approach to make significant improvements on the current performance. 64 // 65 // Finally, considering that the recursive implementation is easier to 66 // read, it might not be worthwhile to switch to a less intuitive 67 // implementation for so little benefit. 68 func HashFromByteSlicesIterative(input [][]byte) []byte { 69 items := make([][]byte, len(input)) 70 sha := sha256.New() 71 for i, leaf := range input { 72 items[i] = leafHash(leaf) 73 } 74 75 size := len(items) 76 for { 77 switch size { 78 case 0: 79 return emptyHash() 80 case 1: 81 return items[0] 82 default: 83 rp := 0 // read position 84 wp := 0 // write position 85 for rp < size { 86 if rp+1 < size { 87 items[wp] = innerHashOpt(sha, items[rp], items[rp+1]) 88 rp += 2 89 } else { 90 items[wp] = items[rp] 91 rp++ 92 } 93 wp++ 94 } 95 size = wp 96 } 97 } 98 } 99 100 // getSplitPoint returns the largest power of 2 less than length 101 func getSplitPoint(length int64) int64 { 102 if length < 1 { 103 panic("Trying to split a tree with size < 1") 104 } 105 uLength := uint(length) 106 bitlen := bits.Len(uLength) 107 k := int64(1 << uint(bitlen-1)) 108 if k == length { 109 k >>= 1 110 } 111 return k 112 }