github.com/MetalBlockchain/metalgo@v1.11.9/x/merkledb/hashing.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package merkledb
     5  
     6  import (
     7  	"crypto/sha256"
     8  	"encoding/binary"
     9  	"slices"
    10  
    11  	"github.com/MetalBlockchain/metalgo/ids"
    12  )
    13  
    14  // TODO: Support configurable hash lengths
    15  const HashLength = 32
    16  
    17  var (
    18  	SHA256Hasher Hasher = &sha256Hasher{}
    19  
    20  	// If a Hasher isn't specified, this package defaults to using the
    21  	// [SHA256Hasher].
    22  	DefaultHasher = SHA256Hasher
    23  )
    24  
    25  type Hasher interface {
    26  	// Returns the canonical hash of the non-nil [node].
    27  	HashNode(node *node) ids.ID
    28  	// Returns the canonical hash of [value].
    29  	HashValue(value []byte) ids.ID
    30  }
    31  
    32  type sha256Hasher struct{}
    33  
    34  // This method is performance critical. It is not expected to perform any memory
    35  // allocations.
    36  func (*sha256Hasher) HashNode(n *node) ids.ID {
    37  	var (
    38  		// sha.Write always returns nil, so we ignore its return values.
    39  		sha  = sha256.New()
    40  		hash ids.ID
    41  		// The hash length is larger than the maximum Uvarint length. This
    42  		// ensures binary.AppendUvarint doesn't perform any memory allocations.
    43  		emptyHashBuffer = hash[:0]
    44  	)
    45  
    46  	// By directly calling sha.Write rather than passing sha around as an
    47  	// io.Writer, the compiler can perform sufficient escape analysis to avoid
    48  	// allocating buffers on the heap.
    49  	numChildren := len(n.children)
    50  	_, _ = sha.Write(binary.AppendUvarint(emptyHashBuffer, uint64(numChildren)))
    51  
    52  	// Avoid allocating keys entirely if the node doesn't have any children.
    53  	if numChildren != 0 {
    54  		// By allocating BranchFactorLargest rather than [numChildren], this
    55  		// slice is allocated on the stack rather than the heap.
    56  		// BranchFactorLargest is at least [numChildren] which avoids memory
    57  		// allocations.
    58  		keys := make([]byte, numChildren, BranchFactorLargest)
    59  		i := 0
    60  		for k := range n.children {
    61  			keys[i] = k
    62  			i++
    63  		}
    64  
    65  		// Ensure that the order of entries is correct.
    66  		slices.Sort(keys)
    67  		for _, index := range keys {
    68  			entry := n.children[index]
    69  			_, _ = sha.Write(binary.AppendUvarint(emptyHashBuffer, uint64(index)))
    70  			_, _ = sha.Write(entry.id[:])
    71  		}
    72  	}
    73  
    74  	if n.valueDigest.HasValue() {
    75  		_, _ = sha.Write(trueBytes)
    76  		value := n.valueDigest.Value()
    77  		_, _ = sha.Write(binary.AppendUvarint(emptyHashBuffer, uint64(len(value))))
    78  		_, _ = sha.Write(value)
    79  	} else {
    80  		_, _ = sha.Write(falseBytes)
    81  	}
    82  
    83  	_, _ = sha.Write(binary.AppendUvarint(emptyHashBuffer, uint64(n.key.length)))
    84  	_, _ = sha.Write(n.key.Bytes())
    85  	sha.Sum(emptyHashBuffer)
    86  	return hash
    87  }
    88  
    89  // This method is performance critical. It is not expected to perform any memory
    90  // allocations.
    91  func (*sha256Hasher) HashValue(value []byte) ids.ID {
    92  	sha := sha256.New()
    93  	// sha.Write always returns nil, so we ignore its return values.
    94  	_, _ = sha.Write(value)
    95  
    96  	var hash ids.ID
    97  	sha.Sum(hash[:0])
    98  	return hash
    99  }