// 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() }