rename output -> node, and more style changes

This commit is contained in:
lukechampine 2020-01-09 22:46:19 -05:00
parent 83947f416f
commit 51ea9b114e
2 changed files with 113 additions and 104 deletions

215
blake3.go
View File

@ -44,7 +44,7 @@ func g(state *[16]uint32, a, b, c, d int, mx, my uint32) {
state[b] = rotr(state[b]^state[c], 7) state[b] = rotr(state[b]^state[c], 7)
} }
func round(state, m *[16]uint32) { func round(state *[16]uint32, m [16]uint32) {
// Mix the columns. // Mix the columns.
g(state, 0, 4, 8, 12, m[0], m[1]) g(state, 0, 4, 8, 12, m[0], m[1])
g(state, 1, 5, 9, 13, m[2], m[3]) g(state, 1, 5, 9, 13, m[2], m[3])
@ -65,37 +65,50 @@ func permute(m *[16]uint32) {
*m = permuted *m = permuted
} }
func compress(cv [8]uint32, block [16]uint32, counter uint64, blockLen uint32, flags uint32) [16]uint32 { // 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 {
state := [16]uint32{ state := [16]uint32{
cv[0], cv[1], cv[2], cv[3], n.cv[0], n.cv[1], n.cv[2], n.cv[3],
cv[4], cv[5], cv[6], cv[7], n.cv[4], n.cv[5], n.cv[6], n.cv[7],
iv[0], iv[1], iv[2], iv[3], iv[0], iv[1], iv[2], iv[3],
uint32(counter), uint32(counter >> 32), blockLen, flags, uint32(n.counter), uint32(n.counter >> 32), n.blockLen, n.flags,
} }
round(&state, &block) // round 1 block := n.block
round(&state, block) // round 1
permute(&block) permute(&block)
round(&state, &block) // round 2 round(&state, block) // round 2
permute(&block) permute(&block)
round(&state, &block) // round 3 round(&state, block) // round 3
permute(&block) permute(&block)
round(&state, &block) // round 4 round(&state, block) // round 4
permute(&block) permute(&block)
round(&state, &block) // round 5 round(&state, block) // round 5
permute(&block) permute(&block)
round(&state, &block) // round 6 round(&state, block) // round 6
permute(&block) permute(&block)
round(&state, &block) // round 7 round(&state, block) // round 7
for i := range cv { for i := range n.cv {
state[i] ^= state[i+8] state[i] ^= state[i+8]
state[i+8] ^= cv[i] state[i+8] ^= n.cv[i]
} }
return state return state
} }
func first8(words [16]uint32) (out [8]uint32) { func (n node) chainingValue() (cv [8]uint32) {
copy(out[:], words[:8]) full := n.compress()
copy(cv[:], full[:8])
return return
} }
@ -105,68 +118,48 @@ func bytesToWords(bytes []byte, words []uint32) {
} }
} }
func wordsToBlock(words []uint32, bytes []byte) { func wordsToBytes(words []uint32, bytes []byte) {
for i, w := range words { for i, w := range words {
binary.LittleEndian.PutUint32(bytes[i*4:], w) binary.LittleEndian.PutUint32(bytes[i*4:], w)
} }
} }
// Each chunk or parent node can produce either an 8-word chaining value or, by
// setting flagRoot, any number of final output bytes. The output struct
// captures the state just prior to choosing between those two possibilities.
type output struct {
inChain [8]uint32
blockWords [16]uint32
counter uint64
blockLen uint32
flags uint32
}
func (o *output) chainingValue() [8]uint32 {
return first8(compress(o.inChain, o.blockWords, o.counter, o.blockLen, o.flags))
}
// An OutputReader produces an unbounded stream of output from its initial // An OutputReader produces an unbounded stream of output from its initial
// state. // state.
type OutputReader struct { type OutputReader struct {
o *output n node
block [blockLen]byte block [blockLen]byte
remaining int blockUsed int
blocksoutput uint64
} }
// Read implements io.Reader. Read always return len(p), nil. // Read implements io.Reader. Read always return len(p), nil.
func (or *OutputReader) Read(p []byte) (int, error) { func (or *OutputReader) Read(p []byte) (int, error) {
lenp := len(p) lenp := len(p)
for len(p) > 0 { for len(p) > 0 {
if or.remaining == 0 { if or.blockUsed == 0 {
words := compress( words := or.n.compress()
or.o.inChain, wordsToBytes(words[:], or.block[:])
or.o.blockWords, or.blockUsed = blockLen
or.blocksoutput, or.n.counter++
or.o.blockLen,
or.o.flags|flagRoot,
)
wordsToBlock(words[:], or.block[:])
or.remaining = blockLen
or.blocksoutput++
} }
// copy from output buffer // copy from output buffer
n := copy(p, or.block[blockLen-or.remaining:]) n := copy(p, or.block[blockLen-or.blockUsed:])
or.remaining -= n or.blockUsed -= n
p = p[n:] p = p[n:]
} }
return lenp, nil return lenp, nil
} }
type chunkState struct { type chunkState struct {
chainingValue [8]uint32 n node
chunkCounter uint64
block [blockLen]byte block [blockLen]byte
blockLen int blockLen int
bytesConsumed int bytesConsumed int
flags uint32 }
func (cs *chunkState) chunkCounter() uint64 {
return cs.n.counter
} }
func (cs *chunkState) update(input []byte) { func (cs *chunkState) update(input []byte) {
@ -174,19 +167,12 @@ func (cs *chunkState) update(input []byte) {
// If the block buffer is full, compress it and clear it. More // If the block buffer is full, compress it and clear it. More
// input is coming, so this compression is not flagChunkEnd. // input is coming, so this compression is not flagChunkEnd.
if cs.blockLen == blockLen { if cs.blockLen == blockLen {
var blockWords [16]uint32 bytesToWords(cs.block[:], cs.n.block[:])
bytesToWords(cs.block[:], blockWords[:]) cs.n.cv = cs.n.chainingValue()
cs.chainingValue = first8(compress(
cs.chainingValue,
blockWords,
cs.chunkCounter,
blockLen,
cs.flags,
))
cs.block = [blockLen]byte{} cs.block = [blockLen]byte{}
cs.blockLen = 0 cs.blockLen = 0
// After the first chunk has been compressed, clear the start flag. // After the first chunk has been compressed, clear the start flag.
cs.flags &^= flagChunkStart cs.n.flags &^= flagChunkStart
} }
// Copy input bytes into the block buffer. // Copy input bytes into the block buffer.
@ -197,37 +183,36 @@ func (cs *chunkState) update(input []byte) {
} }
} }
func (cs *chunkState) output() *output { func (cs *chunkState) node() node {
var blockWords [16]uint32 n := cs.n
bytesToWords(cs.block[:], blockWords[:]) bytesToWords(cs.block[:], n.block[:])
return &output{ n.blockLen = uint32(cs.blockLen)
inChain: cs.chainingValue, n.flags |= flagChunkEnd
blockWords: blockWords, return n
blockLen: uint32(cs.blockLen),
counter: cs.chunkCounter,
flags: cs.flags | flagChunkEnd,
}
} }
func newChunkState(key [8]uint32, chunkCounter uint64, flags uint32) chunkState { func newChunkState(key [8]uint32, chunkCounter uint64, flags uint32) chunkState {
return chunkState{ return chunkState{
chainingValue: key, n: node{
chunkCounter: chunkCounter, cv: key,
// compress the first chunk with the start flag set counter: chunkCounter,
flags: flags | flagChunkStart, blockLen: blockLen,
// compress the first chunk with the start flag set
flags: flags | flagChunkStart,
},
} }
} }
func parentOutput(left, right [8]uint32, key [8]uint32, flags uint32) *output { func parentNode(left, right [8]uint32, key [8]uint32, flags uint32) node {
var blockWords [16]uint32 var blockWords [16]uint32
copy(blockWords[:8], left[:]) copy(blockWords[:8], left[:])
copy(blockWords[8:], right[:]) copy(blockWords[8:], right[:])
return &output{ return node{
inChain: key, cv: key,
blockWords: blockWords, block: blockWords,
counter: 0, // Always 0 for parent nodes. counter: 0, // Always 0 for parent nodes.
blockLen: blockLen, // Always blockLen (64) for parent nodes. blockLen: blockLen, // Always blockLen (64) for parent nodes.
flags: flagParent | flags, flags: flags | flagParent,
} }
} }
@ -264,12 +249,11 @@ func New(size int, key []byte) *Hasher {
// NewFromDerivedKey returns a Hasher whose key was derived from the supplied // NewFromDerivedKey returns a Hasher whose key was derived from the supplied
// context string. // context string.
func NewFromDerivedKey(size int, ctx string) *Hasher { func NewFromDerivedKey(size int, ctx string) *Hasher {
const ( const derivedKeyLen = 32
derivedKeyLen = 32
)
h := newHasher(iv, flagDeriveKeyContext, derivedKeyLen) h := newHasher(iv, flagDeriveKeyContext, derivedKeyLen)
h.Write([]byte(ctx)) h.Write([]byte(ctx))
key := h.Sum(nil) key := make([]byte, derivedKeyLen)
h.Sum(key[:0])
var keyWords [8]uint32 var keyWords [8]uint32
bytesToWords(key, keyWords[:]) bytesToWords(key, keyWords[:])
return newHasher(keyWords, flagDeriveKeyMaterial, size) return newHasher(keyWords, flagDeriveKeyMaterial, size)
@ -289,7 +273,7 @@ func (h *Hasher) addChunkChainingValue(cv [8]uint32, totalChunks uint64) {
h.stackSize-- h.stackSize--
left := h.chainStack[h.stackSize] left := h.chainStack[h.stackSize]
// merge // merge
right = parentOutput(left, right, h.key, h.flags).chainingValue() right = parentNode(left, right, h.key, h.flags).chainingValue()
totalChunks >>= 1 totalChunks >>= 1
} }
h.chainStack[h.stackSize] = right h.chainStack[h.stackSize] = right
@ -315,8 +299,8 @@ func (h *Hasher) Write(p []byte) (int, error) {
// If the current chunk is complete, finalize it and reset the // If the current chunk is complete, finalize it and reset the
// chunk state. More input is coming, so this chunk is not flagRoot. // chunk state. More input is coming, so this chunk is not flagRoot.
if h.cs.bytesConsumed == chunkLen { if h.cs.bytesConsumed == chunkLen {
cv := h.cs.output().chainingValue() cv := h.cs.node().chainingValue()
totalChunks := h.cs.chunkCounter + 1 totalChunks := h.cs.chunkCounter() + 1
h.addChunkChainingValue(cv, totalChunks) h.addChunkChainingValue(cv, totalChunks)
h.cs = newChunkState(h.key, totalChunks, h.flags) h.cs = newChunkState(h.key, totalChunks, h.flags)
} }
@ -334,29 +318,54 @@ func (h *Hasher) Write(p []byte) (int, error) {
// Sum implements hash.Hash. // Sum implements hash.Hash.
func (h *Hasher) Sum(b []byte) []byte { func (h *Hasher) Sum(b []byte) []byte {
out := make([]byte, h.Size()) ret, fill := sliceForAppend(b, h.Size())
h.XOF().Read(out) h.XOF().Read(fill)
return append(b, out...) return ret
} }
// XOF returns an OutputReader initialized with the current hash state. // XOF returns an OutputReader initialized with the current hash state.
func (h *Hasher) XOF() *OutputReader { func (h *Hasher) XOF() *OutputReader {
// Starting with the output from the current chunk, compute all the // Starting with the node from the current chunk, compute all the
// parent chaining values along the right edge of the tree, until we // parent chaining values along the right edge of the tree, until we
// have the root output. // have the root node.
output := h.cs.output() n := h.cs.node()
for i := h.stackSize - 1; i >= 0; i-- { for i := h.stackSize - 1; i >= 0; i-- {
output = parentOutput( n = parentNode(h.chainStack[i], n.chainingValue(), h.key, h.flags)
h.chainStack[i],
output.chainingValue(),
h.key,
h.flags,
)
} }
n.flags |= flagRoot
return &OutputReader{ return &OutputReader{
o: output, n: n,
} }
} }
// 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
}
// ensure that Hasher implements hash.Hash // ensure that Hasher implements hash.Hash
var _ hash.Hash = (*Hasher)(nil) 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
}

View File

@ -80,7 +80,7 @@ func BenchmarkWrite(b *testing.B) {
func BenchmarkChunk(b *testing.B) { func BenchmarkChunk(b *testing.B) {
h := blake3.New(32, nil) h := blake3.New(32, nil)
buf := make([]byte, 1024) buf := make([]byte, 1024)
out := make([]byte, 32) out := make([]byte, 0, 32)
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
h.Write(buf) h.Write(buf)
h.Sum(out) h.Sum(out)