hasher224.go raw

   1  // Copyright (c) 2024 The Decred developers
   2  // Use of this source code is governed by an ISC
   3  // license that can be found in the LICENSE file.
   4  //
   5  // Main Go code originally written and optimized by Dave Collins May 2020.
   6  // Additional cleanup and comments added in July 2024.
   7  
   8  package blake256
   9  
  10  import (
  11  	"hash"
  12  )
  13  
  14  // iv224 is the BLAKE-224 initialization vector.
  15  var iv224 = [8]uint32{
  16  	0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939,
  17  	0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4,
  18  }
  19  
  20  // statePrefix224 is the prefix used when serializing the intermediate state to
  21  // identify the state as belonging to a BLAKE-224 rolling hash.  It is the
  22  // second value in iv224.
  23  const statePrefix224 = 0x367cd507
  24  
  25  // Hasher224 provides a zero-allocation implementation to compute a rolling
  26  // BLAKE-224 checksum.
  27  //
  28  // It can safely be copied at any point to save its intermediate state for use
  29  // in additional processing later, without having to write the previously
  30  // written data again.
  31  //
  32  // In addition to the aforementioned in-process state saving capability, it also
  33  // supports serializing the intermediate state to enable sharing across process
  34  // boundaries.
  35  //
  36  // It is effectively a mix of a [hash.Hash], [encoding.BinaryMarshaler], and
  37  // [encoding.BinaryUnmarshaler] with a modified API that enables zero
  38  // allocations and also provides additional convenience funcs for writing
  39  // integers encoded with both big and little endian as well as writing
  40  // individual bytes.
  41  //
  42  // However, it also implements [hash.Hash], [encoding.BinaryMarshaler], and
  43  // [encoding.BinaryUnmarshaler] for callers that aren't as concerned about
  44  // reducing allocations and would prefer to use it with the aforementioned
  45  // standard library interfaces.
  46  //
  47  // NOTE: The zero value is NOT safe to use.  It must be initialized via
  48  // NewHasher224 or NewHasher224Salt.
  49  type Hasher224 struct {
  50  	h hasher
  51  }
  52  
  53  // Write adds the given bytes to the rolling hash.
  54  //
  55  // NOTE: This method only returns an error in order to satisfy the [io.Writer]
  56  // and [hash.Hash] interfaces.  However, it will never error, meaning the error
  57  // will always be nil, so it is safe to ignore.
  58  //
  59  // Callers may optionally choose to call [WriteBytes] which does not return an
  60  // error to make the fact writing can never fail.
  61  func (h *Hasher224) Write(b []byte) (int, error) {
  62  	return h.h.write(b)
  63  }
  64  
  65  // WriteByte adds the given byte to the rolling hash.
  66  func (h *Hasher224) WriteByte(b byte) {
  67  	h.h.writeByte(b)
  68  }
  69  
  70  // WriteBytes adds the given bytes to the rolling hash.
  71  //
  72  // This method is identical to [Write] except it does not return an error in
  73  // order to make it clear that writing can never fail.
  74  func (h *Hasher224) WriteBytes(b []byte) {
  75  	h.h.write(b)
  76  }
  77  
  78  // WriteString adds the given string to the rolling hash.
  79  func (h *Hasher224) WriteString(s string) {
  80  	h.h.writeString(s)
  81  }
  82  
  83  // WriteUint16LE encodes the given unsigned 16-bit integer as a 2-byte
  84  // little-endian byte sequence and adds it to the rolling hash.
  85  func (h *Hasher224) WriteUint16LE(val uint16) {
  86  	h.h.writeUint16LE(val)
  87  }
  88  
  89  // WriteUint16BE encodes the given unsigned 16-bit integer as a 2-byte
  90  // big-endian byte sequence and adds it to the rolling hash.
  91  func (h *Hasher224) WriteUint16BE(val uint16) {
  92  	h.h.writeUint16BE(val)
  93  }
  94  
  95  // WriteUint32LE encodes the given unsigned 32-bit integer as a 4-byte
  96  // little-endian byte sequence and adds it to the rolling hash.
  97  func (h *Hasher224) WriteUint32LE(val uint32) {
  98  	h.h.writeUint32LE(val)
  99  }
 100  
 101  // WriteUint32BE encodes the given unsigned 32-bit integer as a 4-byte
 102  // big-endian byte sequence and adds it to the rolling hash.
 103  func (h *Hasher224) WriteUint32BE(val uint32) {
 104  	h.h.writeUint32BE(val)
 105  }
 106  
 107  // WriteUint64LE encodes the given unsigned 64-bit integer as an 8-byte
 108  // little-endian byte sequence and adds it to the rolling hash.
 109  func (h *Hasher224) WriteUint64LE(val uint64) {
 110  	h.h.writeUint64LE(val)
 111  }
 112  
 113  // WriteUint64BE encodes the given unsigned 64-bit integer as an 8-byte
 114  // big-endian byte sequence and adds it to the rolling hash.
 115  func (h *Hasher224) WriteUint64BE(val uint64) {
 116  	h.h.writeUint64BE(val)
 117  }
 118  
 119  // Reset resets the state of the rolling hash.
 120  //
 121  // This is part of the [hash.Hash] interface.
 122  func (h *Hasher224) Reset() {
 123  	h.h.reset(iv224)
 124  }
 125  
 126  // Size returns the size of a BLAKE-224 hash in bytes.
 127  //
 128  // This is part of the [hash.Hash] interface.
 129  func (h *Hasher224) Size() int {
 130  	return Size224
 131  }
 132  
 133  // BlockSize returns the underlying block size of the BLAKE-224 hashing
 134  // algorithm.
 135  //
 136  // This is part of the [hash.Hash] interface.
 137  func (h *Hasher224) BlockSize() int {
 138  	return BlockSize
 139  }
 140  
 141  // Sum finalizes the rolling hash, appends the resulting checksum to the
 142  // provided slice and returns the resulting slice.  It does not change the
 143  // underlying hash state.
 144  //
 145  // Note that allocations can often be avoided by providing a slice that has
 146  // enough capacity to house the resulting checksum.  For example:
 147  //
 148  //	digest := make([]byte, blake256.Size224)
 149  //	h := blake256.NewHasher224()
 150  //	h.WriteUint64LE(1)
 151  //	digest = h.Sum(digest[:0])
 152  //
 153  // This is part of the [hash.Hash] interface.
 154  func (h Hasher224) Sum(b []byte) []byte {
 155  	// Note h is a copy so that the caller can keep writing and summing.
 156  	sum := h.h.finalize224()
 157  	return append(b, sum[:]...)
 158  }
 159  
 160  // Sum224 finalizes the rolling hash and returns the resulting checksum.  It
 161  // does not change the underlying hash state.
 162  func (h Hasher224) Sum224() [Size224]byte {
 163  	// Note h is a copy so that the caller can keep writing and summing.
 164  	return h.h.finalize224()
 165  }
 166  
 167  // SaveState appends the current intermediate state of the rolling hash, as
 168  // generated by [Hasher224.MarshalBinary], to the provided slice and returns the
 169  // resulting slice.  It does not change the underlying hash state.
 170  //
 171  // The resulting serialized data may be used to resume from the current
 172  // intermediate state later without having to write the previously written data
 173  // again by providing it to [Hasher224.UnmarshalBinary].
 174  //
 175  // As described by the [Hasher224] documentation, the hasher instance can simply
 176  // be copied to achieve the same result much more efficiently when the caller is
 177  // able to keep a copy.  Therefore, that approach should be preferred when
 178  // possible.
 179  //
 180  // However, the ability to serialize the state is also provided to enable
 181  // sharing it across process boundaries.
 182  //
 183  // Note that allocations can typically be avoided by providing a slice that has
 184  // enough capacity to house the resulting state as defined by the
 185  // [SavedStateSize] constant.  For example:
 186  //
 187  //	state := make([]byte, blake256.SavedStateSize)
 188  //	h := blake256.NewHasher224()
 189  //	h.WriteUint64LE(1)
 190  //	state = h.SaveState(state[:0])
 191  func (h *Hasher224) SaveState(target []byte) []byte {
 192  	return h.h.saveState(target, statePrefix224)
 193  }
 194  
 195  // MarshalBinary returns the intermediate state of the rolling hash serialized
 196  // into a binary form that may be used to resume from the current state later
 197  // without having to write the previously written data again.  It does not
 198  // change the underlying hash state.
 199  //
 200  // As described by the [Hasher224] documentation, the hasher instance can simply
 201  // be copied to achieve the same result much more efficiently when the caller is
 202  // able to keep a copy.  Therefore, that approach should be preferred when
 203  // possible.
 204  //
 205  // However, the ability to serialize the state is also provided to enable
 206  // sharing it across process boundaries.
 207  //
 208  // NOTE: This method only returns an error in order to satisfy the
 209  // [encoding.BinaryMarshaler] interface.  However, it will never error, meaning
 210  // the error will always be nil, so it is safe to ignore.
 211  //
 212  // Callers that wish to avoid allocations should prefer [Hasher224.SaveState]
 213  // instead.
 214  func (h *Hasher224) MarshalBinary() ([]byte, error) {
 215  	var state [SavedStateSize]byte
 216  	h.h.putSavedState(state[:], statePrefix224)
 217  	return state[:], nil
 218  }
 219  
 220  // UnmarshalBinary restores the rolling hash to the provided serialized
 221  // intermediate state.  See [Hasher224.MarshalBinary] for more details.
 222  //
 223  // [ErrMalformedState] will be returned when the provided serialized state is
 224  // not at least the required [SavedStateSize] number of bytes.
 225  //
 226  // [ErrMismatchedState] will be returned if the provided state is not for a
 227  // BLAKE-224 hash.  For example, it will be returned when attempting to restore
 228  // a BLAKE-256 intermediate state.
 229  //
 230  // This implements the [encoding.BinaryUnmarshaler] interface.
 231  func (h *Hasher224) UnmarshalBinary(state []byte) error {
 232  	return h.h.loadState(state, statePrefix224)
 233  }
 234  
 235  // NewHasher224 returns a zero-allocation hasher for computing a rolling
 236  // BLAKE-224 checksum.
 237  func NewHasher224() *Hasher224 {
 238  	h := Hasher224{makeHasher(iv224)}
 239  	return &h
 240  }
 241  
 242  // NewHasher224Salt returns a zero-allocation hasher for computing a rolling
 243  // BLAKE-224 checksum initialized with the given 16-byte salt slice.
 244  //
 245  // It will panic if the provided salt is not 16 bytes.
 246  func NewHasher224Salt(salt []byte) *Hasher224 {
 247  	h := Hasher224{makeHasher(iv224)}
 248  	h.h.initializeSalt(salt)
 249  	return &h
 250  }
 251  
 252  // New224 returns a new [hash.Hash] computing the BLAKE-224 checksum.
 253  //
 254  // Callers should prefer [NewHasher224] instead since it returns a concrete type
 255  // that has more functionality and allows avoiding additional allocations.  It
 256  // can also be used as a [hash.Hash] if desired.
 257  func New224() hash.Hash {
 258  	return NewHasher224()
 259  }
 260  
 261  // New224Salt returns a new [hash.Hash] computing the BLAKE-224 checksum
 262  // initialized with the given 16-byte salt.
 263  //
 264  // It will panic if the provided salt is not 16 bytes.
 265  //
 266  // Callers should prefer [NewHasher224Salt] instead since it returns a concrete
 267  // type that has more functionality and allows avoiding additional allocations.
 268  // It can also be used as a [hash.Hash] if desired.
 269  func New224Salt(salt []byte) hash.Hash {
 270  	return NewHasher224Salt(salt)
 271  }
 272  
 273  // Sum224 returns the BLAKE-224 checksum of the data.
 274  func Sum224(data []byte) [Size224]byte {
 275  	h := makeHasher(iv224)
 276  	h.write(data)
 277  	return h.finalize224()
 278  }
 279