279 lines
9.8 KiB
Go
279 lines
9.8 KiB
Go
// Copyright (c) 2024 The Decred developers
|
|
// Use of this source code is governed by an ISC
|
|
// license that can be found in the LICENSE file.
|
|
//
|
|
// Main Go code originally written and optimized by Dave Collins May 2020.
|
|
// Additional cleanup and comments added in July 2024.
|
|
|
|
package blake256
|
|
|
|
import (
|
|
"hash"
|
|
)
|
|
|
|
// iv224 is the BLAKE-224 initialization vector.
|
|
var iv224 = [8]uint32{
|
|
0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939,
|
|
0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4,
|
|
}
|
|
|
|
// statePrefix224 is the prefix used when serializing the intermediate state to
|
|
// identify the state as belonging to a BLAKE-224 rolling hash. It is the
|
|
// second value in iv224.
|
|
const statePrefix224 = 0x367cd507
|
|
|
|
// Hasher224 provides a zero-allocation implementation to compute a rolling
|
|
// BLAKE-224 checksum.
|
|
//
|
|
// It can safely be copied at any point to save its intermediate state for use
|
|
// in additional processing later, without having to write the previously
|
|
// written data again.
|
|
//
|
|
// In addition to the aforementioned in-process state saving capability, it also
|
|
// supports serializing the intermediate state to enable sharing across process
|
|
// boundaries.
|
|
//
|
|
// It is effectively a mix of a [hash.Hash], [encoding.BinaryMarshaler], and
|
|
// [encoding.BinaryUnmarshaler] with a modified API that enables zero
|
|
// allocations and also provides additional convenience funcs for writing
|
|
// integers encoded with both big and little endian as well as writing
|
|
// individual bytes.
|
|
//
|
|
// However, it also implements [hash.Hash], [encoding.BinaryMarshaler], and
|
|
// [encoding.BinaryUnmarshaler] for callers that aren't as concerned about
|
|
// reducing allocations and would prefer to use it with the aforementioned
|
|
// standard library interfaces.
|
|
//
|
|
// NOTE: The zero value is NOT safe to use. It must be initialized via
|
|
// NewHasher224 or NewHasher224Salt.
|
|
type Hasher224 struct {
|
|
h hasher
|
|
}
|
|
|
|
// Write adds the given bytes to the rolling hash.
|
|
//
|
|
// NOTE: This method only returns an error in order to satisfy the [io.Writer]
|
|
// and [hash.Hash] interfaces. However, it will never error, meaning the error
|
|
// will always be nil, so it is safe to ignore.
|
|
//
|
|
// Callers may optionally choose to call [WriteBytes] which does not return an
|
|
// error to make the fact writing can never fail.
|
|
func (h *Hasher224) Write(b []byte) (int, error) {
|
|
return h.h.write(b)
|
|
}
|
|
|
|
// WriteByte adds the given byte to the rolling hash.
|
|
func (h *Hasher224) WriteByte(b byte) {
|
|
h.h.writeByte(b)
|
|
}
|
|
|
|
// WriteBytes adds the given bytes to the rolling hash.
|
|
//
|
|
// This method is identical to [Write] except it does not return an error in
|
|
// order to make it clear that writing can never fail.
|
|
func (h *Hasher224) WriteBytes(b []byte) {
|
|
h.h.write(b)
|
|
}
|
|
|
|
// WriteString adds the given string to the rolling hash.
|
|
func (h *Hasher224) WriteString(s string) {
|
|
h.h.writeString(s)
|
|
}
|
|
|
|
// WriteUint16LE encodes the given unsigned 16-bit integer as a 2-byte
|
|
// little-endian byte sequence and adds it to the rolling hash.
|
|
func (h *Hasher224) WriteUint16LE(val uint16) {
|
|
h.h.writeUint16LE(val)
|
|
}
|
|
|
|
// WriteUint16BE encodes the given unsigned 16-bit integer as a 2-byte
|
|
// big-endian byte sequence and adds it to the rolling hash.
|
|
func (h *Hasher224) WriteUint16BE(val uint16) {
|
|
h.h.writeUint16BE(val)
|
|
}
|
|
|
|
// WriteUint32LE encodes the given unsigned 32-bit integer as a 4-byte
|
|
// little-endian byte sequence and adds it to the rolling hash.
|
|
func (h *Hasher224) WriteUint32LE(val uint32) {
|
|
h.h.writeUint32LE(val)
|
|
}
|
|
|
|
// WriteUint32BE encodes the given unsigned 32-bit integer as a 4-byte
|
|
// big-endian byte sequence and adds it to the rolling hash.
|
|
func (h *Hasher224) WriteUint32BE(val uint32) {
|
|
h.h.writeUint32BE(val)
|
|
}
|
|
|
|
// WriteUint64LE encodes the given unsigned 64-bit integer as an 8-byte
|
|
// little-endian byte sequence and adds it to the rolling hash.
|
|
func (h *Hasher224) WriteUint64LE(val uint64) {
|
|
h.h.writeUint64LE(val)
|
|
}
|
|
|
|
// WriteUint64BE encodes the given unsigned 64-bit integer as an 8-byte
|
|
// big-endian byte sequence and adds it to the rolling hash.
|
|
func (h *Hasher224) WriteUint64BE(val uint64) {
|
|
h.h.writeUint64BE(val)
|
|
}
|
|
|
|
// Reset resets the state of the rolling hash.
|
|
//
|
|
// This is part of the [hash.Hash] interface.
|
|
func (h *Hasher224) Reset() {
|
|
h.h.reset(iv224)
|
|
}
|
|
|
|
// Size returns the size of a BLAKE-224 hash in bytes.
|
|
//
|
|
// This is part of the [hash.Hash] interface.
|
|
func (h *Hasher224) Size() int {
|
|
return Size224
|
|
}
|
|
|
|
// BlockSize returns the underlying block size of the BLAKE-224 hashing
|
|
// algorithm.
|
|
//
|
|
// This is part of the [hash.Hash] interface.
|
|
func (h *Hasher224) BlockSize() int {
|
|
return BlockSize
|
|
}
|
|
|
|
// Sum finalizes the rolling hash, appends the resulting checksum to the
|
|
// provided slice and returns the resulting slice. It does not change the
|
|
// underlying hash state.
|
|
//
|
|
// Note that allocations can often be avoided by providing a slice that has
|
|
// enough capacity to house the resulting checksum. For example:
|
|
//
|
|
// digest := make([]byte, blake256.Size224)
|
|
// h := blake256.NewHasher224()
|
|
// h.WriteUint64LE(1)
|
|
// digest = h.Sum(digest[:0])
|
|
//
|
|
// This is part of the [hash.Hash] interface.
|
|
func (h Hasher224) Sum(b []byte) []byte {
|
|
// Note h is a copy so that the caller can keep writing and summing.
|
|
sum := h.h.finalize224()
|
|
return append(b, sum[:]...)
|
|
}
|
|
|
|
// Sum224 finalizes the rolling hash and returns the resulting checksum. It
|
|
// does not change the underlying hash state.
|
|
func (h Hasher224) Sum224() [Size224]byte {
|
|
// Note h is a copy so that the caller can keep writing and summing.
|
|
return h.h.finalize224()
|
|
}
|
|
|
|
// SaveState appends the current intermediate state of the rolling hash, as
|
|
// generated by [Hasher224.MarshalBinary], to the provided slice and returns the
|
|
// resulting slice. It does not change the underlying hash state.
|
|
//
|
|
// The resulting serialized data may be used to resume from the current
|
|
// intermediate state later without having to write the previously written data
|
|
// again by providing it to [Hasher224.UnmarshalBinary].
|
|
//
|
|
// As described by the [Hasher224] documentation, the hasher instance can simply
|
|
// be copied to achieve the same result much more efficiently when the caller is
|
|
// able to keep a copy. Therefore, that approach should be preferred when
|
|
// possible.
|
|
//
|
|
// However, the ability to serialize the state is also provided to enable
|
|
// sharing it across process boundaries.
|
|
//
|
|
// Note that allocations can typically be avoided by providing a slice that has
|
|
// enough capacity to house the resulting state as defined by the
|
|
// [SavedStateSize] constant. For example:
|
|
//
|
|
// state := make([]byte, blake256.SavedStateSize)
|
|
// h := blake256.NewHasher224()
|
|
// h.WriteUint64LE(1)
|
|
// state = h.SaveState(state[:0])
|
|
func (h *Hasher224) SaveState(target []byte) []byte {
|
|
return h.h.saveState(target, statePrefix224)
|
|
}
|
|
|
|
// MarshalBinary returns the intermediate state of the rolling hash serialized
|
|
// into a binary form that may be used to resume from the current state later
|
|
// without having to write the previously written data again. It does not
|
|
// change the underlying hash state.
|
|
//
|
|
// As described by the [Hasher224] documentation, the hasher instance can simply
|
|
// be copied to achieve the same result much more efficiently when the caller is
|
|
// able to keep a copy. Therefore, that approach should be preferred when
|
|
// possible.
|
|
//
|
|
// However, the ability to serialize the state is also provided to enable
|
|
// sharing it across process boundaries.
|
|
//
|
|
// NOTE: This method only returns an error in order to satisfy the
|
|
// [encoding.BinaryMarshaler] interface. However, it will never error, meaning
|
|
// the error will always be nil, so it is safe to ignore.
|
|
//
|
|
// Callers that wish to avoid allocations should prefer [Hasher224.SaveState]
|
|
// instead.
|
|
func (h *Hasher224) MarshalBinary() ([]byte, error) {
|
|
var state [SavedStateSize]byte
|
|
h.h.putSavedState(state[:], statePrefix224)
|
|
return state[:], nil
|
|
}
|
|
|
|
// UnmarshalBinary restores the rolling hash to the provided serialized
|
|
// intermediate state. See [Hasher224.MarshalBinary] for more details.
|
|
//
|
|
// [ErrMalformedState] will be returned when the provided serialized state is
|
|
// not at least the required [SavedStateSize] number of bytes.
|
|
//
|
|
// [ErrMismatchedState] will be returned if the provided state is not for a
|
|
// BLAKE-224 hash. For example, it will be returned when attempting to restore
|
|
// a BLAKE-256 intermediate state.
|
|
//
|
|
// This implements the [encoding.BinaryUnmarshaler] interface.
|
|
func (h *Hasher224) UnmarshalBinary(state []byte) error {
|
|
return h.h.loadState(state, statePrefix224)
|
|
}
|
|
|
|
// NewHasher224 returns a zero-allocation hasher for computing a rolling
|
|
// BLAKE-224 checksum.
|
|
func NewHasher224() *Hasher224 {
|
|
h := Hasher224{makeHasher(iv224)}
|
|
return &h
|
|
}
|
|
|
|
// NewHasher224Salt returns a zero-allocation hasher for computing a rolling
|
|
// BLAKE-224 checksum initialized with the given 16-byte salt slice.
|
|
//
|
|
// It will panic if the provided salt is not 16 bytes.
|
|
func NewHasher224Salt(salt []byte) *Hasher224 {
|
|
h := Hasher224{makeHasher(iv224)}
|
|
h.h.initializeSalt(salt)
|
|
return &h
|
|
}
|
|
|
|
// New224 returns a new [hash.Hash] computing the BLAKE-224 checksum.
|
|
//
|
|
// Callers should prefer [NewHasher224] instead since it returns a concrete type
|
|
// that has more functionality and allows avoiding additional allocations. It
|
|
// can also be used as a [hash.Hash] if desired.
|
|
func New224() hash.Hash {
|
|
return NewHasher224()
|
|
}
|
|
|
|
// New224Salt returns a new [hash.Hash] computing the BLAKE-224 checksum
|
|
// initialized with the given 16-byte salt.
|
|
//
|
|
// It will panic if the provided salt is not 16 bytes.
|
|
//
|
|
// Callers should prefer [NewHasher224Salt] instead since it returns a concrete
|
|
// type that has more functionality and allows avoiding additional allocations.
|
|
// It can also be used as a [hash.Hash] if desired.
|
|
func New224Salt(salt []byte) hash.Hash {
|
|
return NewHasher224Salt(salt)
|
|
}
|
|
|
|
// Sum224 returns the BLAKE-224 checksum of the data.
|
|
func Sum224(data []byte) [Size224]byte {
|
|
h := makeHasher(iv224)
|
|
h.write(data)
|
|
return h.finalize224()
|
|
}
|