This repository has been archived on 2023-05-01. You can view files and clone it, but cannot push or open issues or pull requests.
blake3/blake3.go

420 lines
10 KiB
Go
Raw Normal View History

2020-01-09 20:10:01 +00:00
// Package blake3 implements the BLAKE3 cryptographic hash function.
//
// This is a direct port of the Rust reference implementation. It is not
// optimized for performance.
package blake3
import (
"encoding/binary"
2020-01-10 17:43:02 +00:00
"errors"
2020-01-09 20:10:01 +00:00
"hash"
2020-01-10 17:43:02 +00:00
"io"
2020-01-10 19:39:34 +00:00
"math"
"math/bits"
2020-01-09 20:10:01 +00:00
)
const (
2020-01-10 21:01:33 +00:00
blockSize = 64
2020-01-10 21:37:28 +00:00
chunkSize = 1024
2020-01-09 20:10:01 +00:00
)
// flags
const (
flagChunkStart = 1 << iota
flagChunkEnd
flagParent
flagRoot
flagKeyedHash
flagDeriveKeyContext
flagDeriveKeyMaterial
)
2020-01-09 20:10:01 +00:00
var iv = [8]uint32{
0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A,
0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19,
2020-01-09 20:10:01 +00:00
}
func gx(state *[16]uint32, a, b, c, d int, mx uint32) {
state[a] += state[b] + mx
state[d] = bits.RotateLeft32(state[d]^state[a], -16)
state[c] += state[d]
state[b] = bits.RotateLeft32(state[b]^state[c], -12)
}
func gy(state *[16]uint32, a, b, c, d int, my uint32) {
state[a] += state[b] + my
state[d] = bits.RotateLeft32(state[d]^state[a], -8)
state[c] += state[d]
state[b] = bits.RotateLeft32(state[b]^state[c], -7)
2020-01-09 20:10:01 +00:00
}
2020-01-10 04:02:28 +00:00
func round(state *[16]uint32, m *[16]uint32) {
2020-01-09 20:10:01 +00:00
// Mix the columns.
gx(state, 0, 4, 8, 12, m[0])
gy(state, 0, 4, 8, 12, m[1])
gx(state, 1, 5, 9, 13, m[2])
gy(state, 1, 5, 9, 13, m[3])
gx(state, 2, 6, 10, 14, m[4])
gy(state, 2, 6, 10, 14, m[5])
gx(state, 3, 7, 11, 15, m[6])
gy(state, 3, 7, 11, 15, m[7])
2020-01-09 20:10:01 +00:00
// Mix the diagonals.
gx(state, 0, 5, 10, 15, m[8])
gy(state, 0, 5, 10, 15, m[9])
gx(state, 1, 6, 11, 12, m[10])
gy(state, 1, 6, 11, 12, m[11])
gx(state, 2, 7, 8, 13, m[12])
gy(state, 2, 7, 8, 13, m[13])
gx(state, 3, 4, 9, 14, m[14])
gy(state, 3, 4, 9, 14, m[15])
2020-01-09 20:10:01 +00:00
}
func permute(m *[16]uint32) {
2020-01-10 04:02:28 +00:00
*m = [16]uint32{
m[2], m[6], m[3], m[10],
m[7], m[0], m[4], m[13],
m[1], m[11], m[12], m[5],
m[9], m[14], m[15], m[8],
2020-01-09 20:10:01 +00:00
}
}
// Each chunk or parent node can produce either an 8-word chaining value or, by
// setting flagRoot, any number of final output bytes. The node struct
// captures the state just prior to choosing between those two possibilities.
type node struct {
cv [8]uint32
block [16]uint32
counter uint64
blockLen uint32
flags uint32
}
func (n node) compress() [16]uint32 {
2020-01-09 20:10:01 +00:00
state := [16]uint32{
n.cv[0], n.cv[1], n.cv[2], n.cv[3],
n.cv[4], n.cv[5], n.cv[6], n.cv[7],
iv[0], iv[1], iv[2], iv[3],
uint32(n.counter), uint32(n.counter >> 32), n.blockLen, n.flags,
2020-01-09 20:10:01 +00:00
}
2020-01-10 04:02:28 +00:00
round(&state, &n.block) // round 1
permute(&n.block)
round(&state, &n.block) // round 2
permute(&n.block)
round(&state, &n.block) // round 3
permute(&n.block)
round(&state, &n.block) // round 4
permute(&n.block)
round(&state, &n.block) // round 5
permute(&n.block)
round(&state, &n.block) // round 6
permute(&n.block)
round(&state, &n.block) // round 7
2020-01-09 20:10:01 +00:00
for i := range n.cv {
2020-01-09 20:10:01 +00:00
state[i] ^= state[i+8]
state[i+8] ^= n.cv[i]
2020-01-09 20:10:01 +00:00
}
return state
}
func (n node) chainingValue() (cv [8]uint32) {
full := n.compress()
copy(cv[:], full[:8])
2020-01-09 20:10:01 +00:00
return
}
func bytesToWords(bytes []byte, words []uint32) {
2020-01-10 04:02:28 +00:00
for i := range words {
words[i] = binary.LittleEndian.Uint32(bytes[i*4:])
2020-01-09 20:10:01 +00:00
}
}
func wordsToBytes(words []uint32, bytes []byte) {
for i, w := range words {
binary.LittleEndian.PutUint32(bytes[i*4:], w)
}
}
2020-01-10 19:39:34 +00:00
// An OutputReader produces an seekable stream of 2^64 - 1 output bytes.
2020-01-09 21:25:24 +00:00
type OutputReader struct {
2020-01-10 19:39:34 +00:00
n node
2020-01-10 21:01:33 +00:00
block [blockSize]byte
2020-01-10 19:39:34 +00:00
off uint64
2020-01-09 21:25:24 +00:00
}
2020-01-10 19:39:34 +00:00
// Read implements io.Reader. Callers may assume that Read returns len(p), nil
// unless the read would extend beyond the end of the stream.
2020-01-09 21:25:24 +00:00
func (or *OutputReader) Read(p []byte) (int, error) {
2020-01-10 19:39:34 +00:00
if or.off == math.MaxUint64 {
return 0, io.EOF
} else if rem := math.MaxUint64 - or.off; uint64(len(p)) > rem {
p = p[:rem]
}
2020-01-09 21:25:24 +00:00
lenp := len(p)
for len(p) > 0 {
2020-01-10 21:01:33 +00:00
if or.off%blockSize == 0 {
or.n.counter = or.off / blockSize
words := or.n.compress()
wordsToBytes(words[:], or.block[:])
2020-01-09 20:10:01 +00:00
}
2020-01-09 21:25:24 +00:00
2020-01-10 21:01:33 +00:00
n := copy(p, or.block[or.off%blockSize:])
2020-01-09 21:25:24 +00:00
p = p[n:]
2020-01-10 19:39:34 +00:00
or.off += uint64(n)
2020-01-09 20:10:01 +00:00
}
2020-01-09 21:25:24 +00:00
return lenp, nil
2020-01-09 20:10:01 +00:00
}
2020-01-10 19:39:34 +00:00
// Seek implements io.Seeker.
2020-01-10 17:43:02 +00:00
func (or *OutputReader) Seek(offset int64, whence int) (int64, error) {
2020-01-10 19:39:34 +00:00
off := or.off
2020-01-10 17:43:02 +00:00
switch whence {
case io.SeekStart:
2020-01-10 19:39:34 +00:00
if offset < 0 {
return 0, errors.New("seek position cannot be negative")
}
off = uint64(offset)
2020-01-10 17:43:02 +00:00
case io.SeekCurrent:
2020-01-10 19:39:34 +00:00
if offset < 0 {
if uint64(-offset) > off {
return 0, errors.New("seek position cannot be negative")
}
off -= uint64(-offset)
} else {
off += uint64(offset)
}
2020-01-10 17:43:02 +00:00
case io.SeekEnd:
2020-01-10 19:39:34 +00:00
off = uint64(offset) - 1
2020-01-10 17:43:02 +00:00
default:
panic("invalid whence")
}
2020-01-10 19:39:34 +00:00
or.off = off
2020-01-10 21:01:33 +00:00
or.n.counter = uint64(off) / blockSize
if or.off%blockSize != 0 {
2020-01-10 17:43:02 +00:00
words := or.n.compress()
wordsToBytes(words[:], or.block[:])
}
2020-01-10 19:39:34 +00:00
// NOTE: or.off >= 2^63 will result in a negative return value.
// Nothing we can do about this.
return int64(or.off), nil
2020-01-10 17:43:02 +00:00
}
2020-01-09 20:10:01 +00:00
type chunkState struct {
n node
2020-01-10 21:01:33 +00:00
block [blockSize]byte
blockLen int
bytesConsumed int
}
func (cs *chunkState) chunkCounter() uint64 {
return cs.n.counter
2020-01-09 20:10:01 +00:00
}
func (cs *chunkState) update(input []byte) {
for len(input) > 0 {
// If the block buffer is full, compress it and clear it. More
// input is coming, so this compression is not flagChunkEnd.
2020-01-10 21:01:33 +00:00
if cs.blockLen == blockSize {
bytesToWords(cs.block[:], cs.n.block[:])
cs.n.cv = cs.n.chainingValue()
2020-01-10 21:01:33 +00:00
cs.block = [blockSize]byte{}
cs.blockLen = 0
// After the first chunk has been compressed, clear the start flag.
cs.n.flags &^= flagChunkStart
2020-01-09 20:10:01 +00:00
}
// Copy input bytes into the block buffer.
n := copy(cs.block[cs.blockLen:], input)
cs.blockLen += n
cs.bytesConsumed += n
2020-01-09 20:10:01 +00:00
input = input[n:]
}
}
func (cs *chunkState) node() node {
n := cs.n
bytesToWords(cs.block[:], n.block[:])
n.blockLen = uint32(cs.blockLen)
n.flags |= flagChunkEnd
return n
2020-01-09 20:10:01 +00:00
}
func newChunkState(key [8]uint32, chunkCounter uint64, flags uint32) chunkState {
2020-01-09 20:10:01 +00:00
return chunkState{
n: node{
cv: key,
counter: chunkCounter,
2020-01-10 21:01:33 +00:00
blockLen: blockSize,
// compress the first chunk with the start flag set
flags: flags | flagChunkStart,
},
2020-01-09 20:10:01 +00:00
}
}
func parentNode(left, right [8]uint32, key [8]uint32, flags uint32) node {
var blockWords [16]uint32
copy(blockWords[:8], left[:])
copy(blockWords[8:], right[:])
return node{
cv: key,
block: blockWords,
2020-01-10 21:01:33 +00:00
counter: 0, // Always 0 for parent nodes.
blockLen: blockSize, // Always blockSize (64) for parent nodes.
flags: flags | flagParent,
2020-01-09 20:10:01 +00:00
}
}
// Hasher implements hash.Hash.
type Hasher struct {
cs chunkState
key [8]uint32
2020-01-10 21:37:28 +00:00
chainStack [54][8]uint32 // space for 54 subtrees (2^54 * chunkSize = 2^64)
stackSize int // index within chainStack
flags uint32
size int // output size, for Sum
2020-01-09 20:10:01 +00:00
}
func newHasher(key [8]uint32, flags uint32, size int) *Hasher {
2020-01-09 20:10:01 +00:00
return &Hasher{
cs: newChunkState(key, 0, flags),
key: key,
flags: flags,
size: size,
2020-01-09 20:10:01 +00:00
}
}
2020-01-09 20:38:44 +00:00
// New returns a Hasher for the specified size and key. If key is nil, the hash
// is unkeyed.
2020-01-09 20:10:01 +00:00
func New(size int, key []byte) *Hasher {
if key == nil {
return newHasher(iv, 0, size)
2020-01-09 20:10:01 +00:00
}
var keyWords [8]uint32
bytesToWords(key[:], keyWords[:])
return newHasher(keyWords, flagKeyedHash, size)
2020-01-09 20:10:01 +00:00
}
func (h *Hasher) addChunkChainingValue(cv [8]uint32, totalChunks uint64) {
2020-01-09 20:10:01 +00:00
// This chunk might complete some subtrees. For each completed subtree,
// its left child will be the current top entry in the CV stack, and
// its right child will be the current value of `cv`. Pop each left
// child off the stack, merge it with `cv`, and overwrite `cv`
2020-01-09 20:10:01 +00:00
// with the result. After all these merges, push the final value of
// `cv` onto the stack. The number of completed subtrees is given
2020-01-09 20:10:01 +00:00
// by the number of trailing 0-bits in the new total number of chunks.
for totalChunks&1 == 0 {
2020-01-10 21:35:20 +00:00
// pop and merge
h.stackSize--
2020-01-10 21:35:20 +00:00
cv = parentNode(h.chainStack[h.stackSize], cv, h.key, h.flags).chainingValue()
totalChunks >>= 1
2020-01-09 20:10:01 +00:00
}
2020-01-10 21:35:20 +00:00
h.chainStack[h.stackSize] = cv
h.stackSize++
2020-01-09 20:10:01 +00:00
}
// Reset implements hash.Hash.
func (h *Hasher) Reset() {
h.cs = newChunkState(h.key, 0, h.flags)
h.stackSize = 0
2020-01-09 20:10:01 +00:00
}
// BlockSize implements hash.Hash.
2020-01-09 20:39:50 +00:00
func (h *Hasher) BlockSize() int { return 64 }
2020-01-09 20:10:01 +00:00
// Size implements hash.Hash.
func (h *Hasher) Size() int { return h.size }
2020-01-09 20:10:01 +00:00
// Write implements hash.Hash.
func (h *Hasher) Write(p []byte) (int, error) {
lenp := len(p)
for len(p) > 0 {
2020-01-09 20:10:01 +00:00
// If the current chunk is complete, finalize it and reset the
// chunk state. More input is coming, so this chunk is not flagRoot.
2020-01-10 21:37:28 +00:00
if h.cs.bytesConsumed == chunkSize {
cv := h.cs.node().chainingValue()
totalChunks := h.cs.chunkCounter() + 1
h.addChunkChainingValue(cv, totalChunks)
h.cs = newChunkState(h.key, totalChunks, h.flags)
2020-01-09 20:10:01 +00:00
}
// Compress input bytes into the current chunk state.
2020-01-10 21:37:28 +00:00
n := chunkSize - h.cs.bytesConsumed
if n > len(p) {
n = len(p)
2020-01-09 20:10:01 +00:00
}
h.cs.update(p[:n])
p = p[n:]
2020-01-09 20:10:01 +00:00
}
return lenp, nil
2020-01-09 20:10:01 +00:00
}
// Sum implements hash.Hash.
func (h *Hasher) Sum(b []byte) []byte {
ret, fill := sliceForAppend(b, h.Size())
h.XOF().Read(fill)
return ret
2020-01-09 21:25:24 +00:00
}
// XOF returns an OutputReader initialized with the current hash state.
func (h *Hasher) XOF() *OutputReader {
// Starting with the node from the current chunk, compute all the
2020-01-09 20:10:01 +00:00
// parent chaining values along the right edge of the tree, until we
// have the root node.
n := h.cs.node()
for i := h.stackSize - 1; i >= 0; i-- {
n = parentNode(h.chainStack[i], n.chainingValue(), h.key, h.flags)
2020-01-09 20:10:01 +00:00
}
n.flags |= flagRoot
2020-01-09 21:25:24 +00:00
return &OutputReader{
n: n,
2020-01-09 21:25:24 +00:00
}
2020-01-09 20:10:01 +00:00
}
// Sum256 returns the unkeyed BLAKE3 hash of b, truncated to 256 bits.
func Sum256(b []byte) [32]byte {
var out [32]byte
h := New(32, nil)
h.Write(b)
h.Sum(out[:0])
return out
}
// Sum512 returns the unkeyed BLAKE3 hash of b, truncated to 512 bits.
func Sum512(b []byte) [64]byte {
var out [64]byte
h := New(64, nil)
h.Write(b)
h.Sum(out[:0])
return out
}
2020-01-10 05:46:44 +00:00
// DeriveKey derives a subkey from ctx and srcKey.
func DeriveKey(subKey []byte, ctx string, srcKey []byte) {
// construct the derivation Hasher
const derivationIVLen = 32
h := newHasher(iv, flagDeriveKeyContext, 32)
h.Write([]byte(ctx))
var derivationIV [8]uint32
bytesToWords(h.Sum(make([]byte, 0, derivationIVLen)), derivationIV[:])
h = newHasher(derivationIV, flagDeriveKeyMaterial, len(subKey))
// derive the subKey
h.Write(srcKey)
h.Sum(subKey[:0])
}
2020-01-09 20:10:01 +00:00
// ensure that Hasher implements hash.Hash
var _ hash.Hash = (*Hasher)(nil)
func sliceForAppend(in []byte, n int) (head, tail []byte) {
if total := len(in) + n; cap(in) >= total {
head = in[:total]
} else {
head = make([]byte, total)
copy(head, in)
}
tail = head[len(in):]
return
}