github.com/aakash4dev/cometbft@v0.38.2/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  }