// Package bao implements BLAKE3 verified streaming.
package bao import ( ) func bytesToCV( []byte) ( [8]uint32) { _ = [31] // bounds check hint for := range { [] = binary.LittleEndian.Uint32([4*:]) } return } func cvToBytes( *[8]uint32) *[32]byte { var [32]byte for , := range { binary.LittleEndian.PutUint32([4*:], ) } return & } func compressGroup( []byte, uint64) guts.Node { var [54 - guts.MaxSIMD][8]uint32 var uint64 := func( [8]uint32) { := 0 for &(1<<) != 0 { = guts.ChainingValue(guts.ParentNode([], , &guts.IV, 0)) ++ } [] = ++ } var [guts.MaxSIMD * guts.ChunkSize]byte var int for len() > 0 { if == len() { (guts.ChainingValue(guts.CompressBuffer(&, , &guts.IV, +(*guts.MaxSIMD), 0))) = 0 } := copy([:], ) += = [:] } := guts.CompressBuffer(&, , &guts.IV, +(*guts.MaxSIMD), 0) for := bits.TrailingZeros64(); < bits.Len64(); ++ { if &(1<<) != 0 { = guts.ParentNode([], guts.ChainingValue(), &guts.IV, 0) } } return } // EncodedSize returns the size of a Bao encoding for the provided quantity // of data. func ( int, int, bool) int { := guts.ChunkSize << := 8 if > 0 { := ( + - 1) / := 2* - 2 // no I will not elaborate += * 32 } if ! { += } return } // Encode computes the intermediate BLAKE3 tree hashes of data and writes them // to dst. If outboard is false, the contents of data are also written to dst, // interleaved with the tree hashes. It also returns the tree root, i.e. the // 256-bit BLAKE3 hash. The group parameter controls how many chunks are hashed // per "group," as a power of 2; for standard Bao, use 0. // // Note that dst is not written sequentially, and therefore must be initialized // with sufficient capacity to hold the encoding; see EncodedSize. func ( io.WriterAt, io.Reader, int64, int, bool) ([32]byte, error) { := uint64(guts.ChunkSize << ) := make([]byte, ) var error := func( []byte) []byte { if == nil { _, = io.ReadFull(, ) } return } := func( []byte, uint64) { if == nil { _, = .WriteAt(, int64()) } } var uint64 // NOTE: unlike the reference implementation, we write directly in // pre-order, rather than writing in post-order and then flipping. This cuts // the I/O required in half, at the cost of making it a lot trickier to hash // multiple groups in SIMD. However, you can still get the SIMD speedup if // group > 0, so maybe just do that. var func( uint64, uint32, uint64) (uint64, [8]uint32) = func( uint64, uint32, uint64) (uint64, [8]uint32) { if != nil { return 0, [8]uint32{} } else if <= { := ([:]) if ! { (, ) } := compressGroup(, ) += / guts.ChunkSize .Flags |= return 0, guts.ChainingValue() } := uint64(1) << (bits.Len64(-1) - 1) , := (, 0, +64) := * 32 if ! { += ( / ) * } , := (-, 0, +64+) (cvToBytes(&)[:], ) (cvToBytes(&)[:], +32) return 2 + + , guts.ChainingValue(guts.ParentNode(, , &guts.IV, )) } binary.LittleEndian.PutUint64([:8], uint64()) ([:8], 0) , := (uint64(), guts.FlagRoot, 8) return *cvToBytes(&), } // Decode reads content and tree data from the provided reader(s), and // streams the verified content to dst. It returns false if verification fails. // If the content and tree data are interleaved, outboard should be nil. func ( io.Writer, , io.Reader, int, [32]byte) (bool, error) { if == nil { = } := uint64(guts.ChunkSize << ) := make([]byte, ) var error := func( io.Reader, []byte) []byte { if == nil { _, = io.ReadFull(, ) } return } := func( io.Writer, []byte) { if == nil { _, = .Write() } } := func() (, [8]uint32) { (, [:64]) return bytesToCV([:32]), bytesToCV([32:]) } var uint64 var func( [8]uint32, uint64, uint32) bool = func( [8]uint32, uint64, uint32) bool { if != nil { return false } else if <= { := compressGroup((, [:]), ) += / guts.ChunkSize .Flags |= := == guts.ChainingValue() if { (, [:]) } return } , := () := guts.ParentNode(, , &guts.IV, ) := uint64(1) << (bits.Len64(-1) - 1) return guts.ChainingValue() == && (, , 0) && (, -, 0) } (, [:8]) := binary.LittleEndian.Uint64([:8]) := (bytesToCV([:]), , guts.FlagRoot) return , } type bufferAt struct { buf []byte } func ( *bufferAt) ( []byte, int64) (int, error) { if copy(.buf[:], ) != len() { panic("bad buffer size") } return len(), nil } // EncodeBuf returns the Bao encoding and root (i.e. BLAKE3 hash) for data. func ( []byte, int, bool) ([]byte, [32]byte) { := bufferAt{buf: make([]byte, EncodedSize(len(), , ))} , := Encode(&, bytes.NewReader(), int64(len()), , ) return .buf, } // VerifyBuf verifies the Bao encoding and root (i.e. BLAKE3 hash) for data. // If the content and tree data are interleaved, outboard should be nil. func (, []byte, int, [32]byte) bool { , := bytes.NewBuffer(), bytes.NewBuffer() var io.Reader = if == nil { = nil } , := Decode(io.Discard, , , , ) return && .Len() == 0 && .Len() == 0 // check for trailing data } // ExtractSlice returns the slice encoding for the given offset and length. When // extracting from an outboard encoding, data should contain only the chunk // groups that will be present in the slice. func ( io.Writer, , io.Reader, int, uint64, uint64) error { := == nil if { = } := uint64(guts.ChunkSize << ) := make([]byte, ) var error := func( io.Reader, uint64, bool) { if == nil { _, = io.ReadFull(, [:]) if == nil && { _, = .Write([:]) } } } var func(, uint64) = func(, uint64) { := < (+) && < (+) if != nil { return } else if <= { if || { (, , ) } return } (, 64, ) := uint64(1) << (bits.Len64(-1) - 1) (, ) (+, -) } (, 8, true) := binary.LittleEndian.Uint64([:8]) if < + { return errors.New("invalid slice length") } (0, ) return } // DecodeSlice reads from data, which must contain a slice encoding for the // given offset and length, and streams verified content to dst. It returns // false if verification fails. func ( io.Writer, io.Reader, int, , uint64, [32]byte) (bool, error) { := uint64(guts.ChunkSize << ) := make([]byte, ) var error := func( uint64) []byte { if == nil { _, = io.ReadFull(, [:]) } return [:] } := func() (, [8]uint32) { (64) return bytesToCV([:32]), bytesToCV([32:]) } := func( []byte) { if == nil { _, = .Write() } } var func( [8]uint32, , uint64, uint32) bool = func( [8]uint32, , uint64, uint32) bool { := < (+) && < (+) if != nil { return false } else if <= { if ! { return true } := compressGroup((), /guts.ChunkSize) .Flags |= := == guts.ChainingValue() if { // only write within range := [:] if + > + { = [:+-] } if < { = [-:] } () } return } if ! { return true } , := () := guts.ParentNode(, , &guts.IV, ) := uint64(1) << (bits.Len64(-1) - 1) return guts.ChainingValue() == && (, , , 0) && (, +, -, 0) } := binary.LittleEndian.Uint64((8)) if < + { return false, errors.New("invalid slice length") } := (bytesToCV([:]), 0, , guts.FlagRoot) return , } // VerifySlice verifies the Bao slice encoding in data, returning the // verified bytes. func ( []byte, int, uint64, uint64, [32]byte) ([]byte, bool) { := bytes.NewBuffer() var bytes.Buffer if , := DecodeSlice(&, , , , , ); ! || .Len() > 0 { return nil, false } return .Bytes(), true } // VerifyChunks verifies the provided chunks with a full outboard encoding. func (, []byte, int, uint64, [32]byte) bool { := bytes.NewBuffer() := bytes.NewBuffer() := uint64(guts.ChunkSize << ) := uint64(len()) := func( uint64) int { := int( / ) if % == 0 { -- } return } var func( [8]uint32, , uint64, uint32) bool = func( [8]uint32, , uint64, uint32) bool { := < (+) && < (+) if <= { if ! { return true } := compressGroup(.Next(int()), /guts.ChunkSize) .Flags |= return == guts.ChainingValue() } if ! { _ = .Next(64 * ()) // skip return true } , := bytesToCV(.Next(32)), bytesToCV(.Next(32)) := guts.ParentNode(, , &guts.IV, ) := uint64(1) << (bits.Len64(-1) - 1) return guts.ChainingValue() == && (, , , 0) && (, +, -, 0) } if .Len() < 8 { return false } := binary.LittleEndian.Uint64(.Next(8)) if < + || .Len() != 64*() { return false } return (bytesToCV([:]), 0, , guts.FlagRoot) }