1// Copyright 2017 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package blake2b
6
7import (
8 "encoding/binary"
9 "errors"
10 "io"
11)
12
13// XOF defines the interface to hash functions that
14// support arbitrary-length output.
15//
16// New callers should prefer the standard library [hash.XOF].
17type XOF interface {
18 // Write absorbs more data into the hash's state. It panics if called
19 // after Read.
20 io.Writer
21
22 // Read reads more output from the hash. It returns io.EOF if the limit
23 // has been reached.
24 io.Reader
25
26 // Clone returns a copy of the XOF in its current state.
27 Clone() XOF
28
29 // Reset resets the XOF to its initial state.
30 Reset()
31}
32
33// OutputLengthUnknown can be used as the size argument to NewXOF to indicate
34// the length of the output is not known in advance.
35const OutputLengthUnknown = 0
36
37// magicUnknownOutputLength is a magic value for the output size that indicates
38// an unknown number of output bytes.
39const magicUnknownOutputLength = (1 << 32) - 1
40
41// maxOutputLength is the absolute maximum number of bytes to produce when the
42// number of output bytes is unknown.
43const maxOutputLength = (1 << 32) * 64
44
45// NewXOF creates a new variable-output-length hash. The hash either produce a
46// known number of bytes (1 <= size < 2**32-1), or an unknown number of bytes
47// (size == OutputLengthUnknown). In the latter case, an absolute limit of
48// 256GiB applies.
49//
50// A non-nil key turns the hash into a MAC. The key must between
51// zero and 32 bytes long.
52//
53// The result can be safely interface-upgraded to [hash.XOF].
54func NewXOF(size uint32, key []byte) (XOF, error) {
55 if len(key) > Size {
56 return nil, errKeySize
57 }
58 if size == magicUnknownOutputLength {
59 // 2^32-1 indicates an unknown number of bytes and thus isn't a
60 // valid length.
61 return nil, errors.New("blake2b: XOF length too large")
62 }
63 if size == OutputLengthUnknown {
64 size = magicUnknownOutputLength
65 }
66 x := &xof{
67 d: digest{
68 size: Size,
69 keyLen: len(key),
70 },
71 length: size,
72 }
73 copy(x.d.key[:], key)
74 x.Reset()
75 return x, nil
76}
77
78type xof struct {
79 d digest
80 length uint32
81 remaining uint64
82 cfg, root, block [Size]byte
83 offset int
84 nodeOffset uint32
85 readMode bool
86}
87
88func (x *xof) Write(p []byte) (n int, err error) {
89 if x.readMode {
90 panic("blake2b: write to XOF after read")
91 }
92 return x.d.Write(p)
93}
94
95func (x *xof) Clone() XOF {
96 clone := *x
97 return &clone
98}
99
100func (x *xof) BlockSize() int {
101 return x.d.BlockSize()
102}
103
104func (x *xof) Reset() {
105 x.cfg[0] = byte(Size)
106 binary.LittleEndian.PutUint32(x.cfg[4:], uint32(Size)) // leaf length
107 binary.LittleEndian.PutUint32(x.cfg[12:], x.length) // XOF length
108 x.cfg[17] = byte(Size) // inner hash size
109
110 x.d.Reset()
111 x.d.h[1] ^= uint64(x.length) << 32
112
113 x.remaining = uint64(x.length)
114 if x.remaining == magicUnknownOutputLength {
115 x.remaining = maxOutputLength
116 }
117 x.offset, x.nodeOffset = 0, 0
118 x.readMode = false
119}
120
121func (x *xof) Read(p []byte) (n int, err error) {
122 if !x.readMode {
123 x.d.finalize(&x.root)
124 x.readMode = true
125 }
126
127 if x.remaining == 0 {
128 return 0, io.EOF
129 }
130
131 n = len(p)
132 if uint64(n) > x.remaining {
133 n = int(x.remaining)
134 p = p[:n]
135 }
136
137 if x.offset > 0 {
138 blockRemaining := Size - x.offset
139 if n < blockRemaining {
140 x.offset += copy(p, x.block[x.offset:])
141 x.remaining -= uint64(n)
142 return
143 }
144 copy(p, x.block[x.offset:])
145 p = p[blockRemaining:]
146 x.offset = 0
147 x.remaining -= uint64(blockRemaining)
148 }
149
150 for len(p) >= Size {
151 binary.LittleEndian.PutUint32(x.cfg[8:], x.nodeOffset)
152 x.nodeOffset++
153
154 x.d.initConfig(&x.cfg)
155 x.d.Write(x.root[:])
156 x.d.finalize(&x.block)
157
158 copy(p, x.block[:])
159 p = p[Size:]
160 x.remaining -= uint64(Size)
161 }
162
163 if todo := len(p); todo > 0 {
164 if x.remaining < uint64(Size) {
165 x.cfg[0] = byte(x.remaining)
166 }
167 binary.LittleEndian.PutUint32(x.cfg[8:], x.nodeOffset)
168 x.nodeOffset++
169
170 x.d.initConfig(&x.cfg)
171 x.d.Write(x.root[:])
172 x.d.finalize(&x.block)
173
174 x.offset = copy(p, x.block[:todo])
175 x.remaining -= uint64(todo)
176 }
177 return
178}
179
180func (d *digest) initConfig(cfg *[Size]byte) {
181 d.offset, d.c[0], d.c[1] = 0, 0, 0
182 for i := range d.h {
183 d.h[i] = iv[i] ^ binary.LittleEndian.Uint64(cfg[i*8:])
184 }
185}