Skip to content

Commit 5531d24

Browse files
committed
add support for Bao slices
1 parent 2ae44f0 commit 5531d24

File tree

2 files changed

+162
-0
lines changed

2 files changed

+162
-0
lines changed

bao.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,3 +199,120 @@ func BaoVerifyBuf(data, outboard []byte, group int, root [32]byte) bool {
199199
ok, _ := BaoDecode(io.Discard, d, or, group, root)
200200
return ok && d.Len() == 0 && o.Len() == 0 // check for trailing data
201201
}
202+
203+
// BaoExtractSlice returns the slice encoding for the given offset and length.
204+
// When extracting from an outboard encoding, data should contain only the chunk
205+
// groups that will be present in the slice.
206+
func BaoExtractSlice(dst io.Writer, data, outboard io.Reader, group int, offset uint64, length uint64) error {
207+
combinedEncoding := outboard == nil
208+
if combinedEncoding {
209+
outboard = data
210+
}
211+
groupSize := uint64(chunkSize << group)
212+
buf := make([]byte, groupSize)
213+
var err error
214+
read := func(r io.Reader, n uint64, copy bool) {
215+
if err == nil {
216+
_, err = io.ReadFull(r, buf[:n])
217+
if err == nil && copy {
218+
_, err = dst.Write(buf[:n])
219+
}
220+
}
221+
}
222+
var pos uint64
223+
var rec func(bufLen uint64)
224+
rec = func(bufLen uint64) {
225+
inSlice := pos < (offset+length) && offset < (pos+bufLen)
226+
if err != nil {
227+
return
228+
} else if bufLen <= groupSize {
229+
if combinedEncoding || inSlice {
230+
read(data, bufLen, inSlice)
231+
}
232+
pos += bufLen
233+
return
234+
}
235+
read(outboard, 64, inSlice)
236+
mid := uint64(1) << (bits.Len64(bufLen-1) - 1)
237+
rec(mid)
238+
rec(bufLen - mid)
239+
}
240+
read(outboard, 8, true)
241+
rec(binary.LittleEndian.Uint64(buf[:8]))
242+
return err
243+
}
244+
245+
// BaoDecodeSlice reads from data, which must contain a slice encoding for the
246+
// given offset and length, and streams verified content to dst. It returns
247+
// false if verification fails.
248+
func BaoDecodeSlice(dst io.Writer, data io.Reader, group int, offset, length uint64, root [32]byte) (bool, error) {
249+
groupSize := uint64(chunkSize << group)
250+
buf := make([]byte, groupSize)
251+
var err error
252+
read := func(n uint64) []byte {
253+
if err == nil {
254+
_, err = io.ReadFull(data, buf[:n])
255+
}
256+
return buf[:n]
257+
}
258+
readParent := func() (l, r [8]uint32) {
259+
read(64)
260+
return bytesToCV(buf[:32]), bytesToCV(buf[32:])
261+
}
262+
write := func(p []byte) {
263+
if err == nil {
264+
_, err = dst.Write(p)
265+
}
266+
}
267+
var pos uint64
268+
var rec func(cv [8]uint32, bufLen uint64, flags uint32) bool
269+
rec = func(cv [8]uint32, bufLen uint64, flags uint32) bool {
270+
inSlice := pos < (offset+length) && offset < (pos+bufLen)
271+
if err != nil {
272+
return false
273+
} else if bufLen <= groupSize {
274+
if !inSlice {
275+
pos += bufLen
276+
return true
277+
}
278+
n := compressGroup(read(bufLen), pos/chunkSize)
279+
n.flags |= flags
280+
valid := cv == chainingValue(n)
281+
if valid {
282+
// only write within range
283+
p := buf[:bufLen]
284+
if pos+bufLen > offset+length {
285+
p = p[:offset+length-pos]
286+
}
287+
if pos < offset {
288+
p = p[offset-pos:]
289+
}
290+
write(p)
291+
}
292+
pos += bufLen
293+
return valid
294+
}
295+
if !inSlice {
296+
return true
297+
}
298+
l, r := readParent()
299+
n := parentNode(l, r, iv, flags)
300+
mid := uint64(1) << (bits.Len64(bufLen-1) - 1)
301+
return chainingValue(n) == cv && rec(l, mid, 0) && rec(r, bufLen-mid, 0)
302+
}
303+
304+
dataLen := binary.LittleEndian.Uint64(read(8))
305+
ok := rec(bytesToCV(root[:]), dataLen, flagRoot)
306+
return ok, err
307+
}
308+
309+
// BaoVerifySlice verifies the Bao slice encoding in data, returning the
310+
// verified bytes.
311+
func BaoVerifySlice(data []byte, group int, offset uint64, length uint64, root [32]byte) ([]byte, bool) {
312+
d := bytes.NewBuffer(data)
313+
var buf bytes.Buffer
314+
if ok, _ := BaoDecodeSlice(&buf, d, group, offset, length, root); !ok || d.Len() > 0 {
315+
return nil, false
316+
}
317+
return buf.Bytes(), true
318+
}

bao_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,3 +190,48 @@ func TestBaoStreaming(t *testing.T) {
190190
t.Fatal("invalid data was written to buf")
191191
}
192192
}
193+
194+
func TestBaoSlice(t *testing.T) {
195+
data := make([]byte, 1<<20)
196+
blake3.New(0, nil).XOF().Read(data)
197+
198+
for _, test := range []struct {
199+
off, len uint64
200+
}{
201+
{0, uint64(len(data))},
202+
{0, 1024},
203+
{1024, 1024},
204+
{0, 10},
205+
{1020, 10},
206+
{1030, uint64(len(data) - 1030)},
207+
} {
208+
// combined encoding
209+
{
210+
enc, root := blake3.BaoEncodeBuf(data, 0, false)
211+
var buf bytes.Buffer
212+
if err := blake3.BaoExtractSlice(&buf, bytes.NewReader(enc), nil, 0, test.off, test.len); err != nil {
213+
t.Error(err)
214+
} else if vdata, ok := blake3.BaoVerifySlice(buf.Bytes(), 0, test.off, test.len, root); !ok {
215+
t.Error("combined verify failed", test)
216+
} else if !bytes.Equal(vdata, data[test.off:][:test.len]) {
217+
t.Error("combined bad decode", test, vdata, data[test.off:][:test.len])
218+
}
219+
}
220+
// outboard encoding
221+
{
222+
enc, root := blake3.BaoEncodeBuf(data, 0, true)
223+
start, end := (test.off/1024)*1024, ((test.off+test.len+1024-1)/1024)*1024
224+
if end > uint64(len(data)) {
225+
end = uint64(len(data))
226+
}
227+
var buf bytes.Buffer
228+
if err := blake3.BaoExtractSlice(&buf, bytes.NewReader(data[start:end]), bytes.NewReader(enc), 0, test.off, test.len); err != nil {
229+
t.Error(err)
230+
} else if vdata, ok := blake3.BaoVerifySlice(buf.Bytes(), 0, test.off, test.len, root); !ok {
231+
t.Error("outboard verify failed", test)
232+
} else if !bytes.Equal(vdata, data[test.off:][:test.len]) {
233+
t.Error("outboard bad decode", test, vdata, data[test.off:][:test.len])
234+
}
235+
}
236+
}
237+
}

0 commit comments

Comments
 (0)