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 }