github.com/lazyledger/lazyledger-core@v0.35.0-dev.0.20210613111200-4c651f053571/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  func hashFromByteSlices(sha hash.Hash, items [][]byte) []byte {
    15  	switch len(items) {
    16  	case 0:
    17  		return emptyHash()
    18  	case 1:
    19  		return leafHashOpt(sha, items[0])
    20  	default:
    21  		k := getSplitPoint(int64(len(items)))
    22  		left := hashFromByteSlices(sha, items[:k])
    23  		right := hashFromByteSlices(sha, items[k:])
    24  		return innerHashOpt(sha, left, right)
    25  	}
    26  }
    27  
    28  // HashFromByteSliceIterative is an iterative alternative to
    29  // HashFromByteSlice motivated by potential performance improvements.
    30  // (#2611) had suggested that an iterative version of
    31  // HashFromByteSlice would be faster, presumably because
    32  // we can envision some overhead accumulating from stack
    33  // frames and function calls. Additionally, a recursive algorithm risks
    34  // hitting the stack limit and causing a stack overflow should the tree
    35  // be too large.
    36  //
    37  // Provided here is an iterative alternative, a test to assert
    38  // correctness and a benchmark. On the performance side, there appears to
    39  // be no overall difference:
    40  //
    41  // BenchmarkHashAlternatives/recursive-4                20000 77677 ns/op
    42  // BenchmarkHashAlternatives/iterative-4                20000 76802 ns/op
    43  //
    44  // On the surface it might seem that the additional overhead is due to
    45  // the different allocation patterns of the implementations. The recursive
    46  // version uses a single [][]byte slices which it then re-slices at each level of the tree.
    47  // The iterative version reproduces [][]byte once within the function and
    48  // then rewrites sub-slices of that array at each level of the tree.
    49  //
    50  // Experimenting by modifying the code to simply calculate the
    51  // hash and not store the result show little to no difference in performance.
    52  //
    53  // These preliminary results suggest:
    54  //
    55  // 1. The performance of the HashFromByteSlice is pretty good
    56  // 2. Go has low overhead for recursive functions
    57  // 3. The performance of the HashFromByteSlice routine is dominated
    58  //    by the actual hashing of data
    59  //
    60  // Although this work is in no way exhaustive, point #3 suggests that
    61  // optimization of this routine would need to take an alternative
    62  // approach to make significant improvements on the current performance.
    63  //
    64  // Finally, considering that the recursive implementation is easier to
    65  // read, it might not be worthwhile to switch to a less intuitive
    66  // implementation for so little benefit.
    67  func HashFromByteSlicesIterative(input [][]byte) []byte {
    68  	sha := sha256.New()
    69  	items := make([][]byte, len(input))
    70  
    71  	for i, leaf := range input {
    72  		items[i] = leafHashOpt(sha, 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  }