github.com/number571/tendermint@v0.34.11-gost/crypto/merkle/tree.go (about)

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