implement XOF

This commit is contained in:
lukechampine 2020-01-09 16:25:24 -05:00
parent 062bad69bc
commit 2ca7badf67
2 changed files with 59 additions and 21 deletions

View File

@ -141,24 +141,40 @@ func (o *output) chaining_value() [8]uint32 {
)) ))
} }
func (o *output) root_output_bytes(out_slice []byte) { // An OutputReader produces an unbounded stream of output from its initial
output_block_counter := uint64(0) // state.
for len(out_slice) > 0 { type OutputReader struct {
o *output
block [BLOCK_LEN]byte
remaining int
blocks_output uint64
}
// Read implements io.Reader. Read always return len(p), nil.
func (or *OutputReader) Read(p []byte) (int, error) {
lenp := len(p)
for len(p) > 0 {
if or.remaining == 0 {
words := compress( words := compress(
&o.input_chaining_value, &or.o.input_chaining_value,
&o.block_words, &or.o.block_words,
output_block_counter, or.blocks_output,
o.block_len, or.o.block_len,
o.flags|ROOT, or.o.flags|ROOT,
) )
var wordsBytes [16 * 4]byte
for i, w := range words { for i, w := range words {
binary.LittleEndian.PutUint32(wordsBytes[i*4:], w) binary.LittleEndian.PutUint32(or.block[i*4:], w)
} }
n := copy(out_slice, wordsBytes[:]) or.remaining = BLOCK_LEN
out_slice = out_slice[n:] or.blocks_output++
output_block_counter++
} }
// copy from output buffer
n := copy(p, or.block[BLOCK_LEN-or.remaining:])
or.remaining -= n
p = p[n:]
}
return lenp, nil
} }
type chunkState struct { type chunkState struct {
@ -348,6 +364,13 @@ func (h *Hasher) Write(input []byte) (int, error) {
// Sum implements hash.Hash. // Sum implements hash.Hash.
func (h *Hasher) Sum(out_slice []byte) []byte { func (h *Hasher) Sum(out_slice []byte) []byte {
out := make([]byte, h.Size())
h.XOF().Read(out)
return append(out_slice, out...)
}
// XOF returns an OutputReader initialized with the current hash state.
func (h *Hasher) XOF() *OutputReader {
// Starting with the output from the current chunk, compute all the // Starting with the output 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 output.
@ -362,9 +385,9 @@ func (h *Hasher) Sum(out_slice []byte) []byte {
h.flags, h.flags,
) )
} }
out := make([]byte, h.Size()) return &OutputReader{
output.root_output_bytes(out) o: output,
return append(out_slice, out...) }
} }
// ensure that Hasher implements hash.Hash // ensure that Hasher implements hash.Hash

View File

@ -1,8 +1,10 @@
package blake3_test package blake3_test
import ( import (
"bytes"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"io"
"io/ioutil" "io/ioutil"
"testing" "testing"
@ -65,6 +67,14 @@ func TestVectors(t *testing.T) {
if out := toHex(h.Sum(nil)); out != vec.DeriveKey { if out := toHex(h.Sum(nil)); out != vec.DeriveKey {
t.Errorf("output did not match test vector:\n\texpected: %v...\n\t got: %v...", vec.DeriveKey[:10], out[:10]) t.Errorf("output did not match test vector:\n\texpected: %v...\n\t got: %v...", vec.DeriveKey[:10], out[:10])
} }
// XOF should produce identical results, even when outputting 7 bytes at a time
h = blake3.New(len(vec.Hash)/2, nil)
h.Write(in)
var xofBuf bytes.Buffer
io.CopyBuffer(&xofBuf, io.LimitReader(h.XOF(), int64(len(vec.Hash)/2)), make([]byte, 7))
if out := toHex(xofBuf.Bytes()); out != vec.Hash {
t.Errorf("XOF output did not match test vector:\n\texpected: %v...\n\t got: %v...", vec.Hash[:10], out[:10])
}
} }
} }
@ -77,12 +87,17 @@ func BenchmarkWrite(b *testing.B) {
} }
} }
func BenchmarkBlock(b *testing.B) { func BenchmarkChunk(b *testing.B) {
h := blake3.New(32, nil) h := blake3.New(32, nil)
buf := make([]byte, h.BlockSize()) buf := make([]byte, blake3.CHUNK_LEN)
out := make([]byte, 32) out := make([]byte, 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)
} }
} }
func BenchmarkXOF(b *testing.B) {
b.SetBytes(1)
io.CopyN(ioutil.Discard, blake3.New(32, nil).XOF(), int64(b.N))
}