Skip to content
 
 

Latest commit

 

History

102 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

blake3

go get github.com/forkcloser/blake3

blake3 implements the BLAKE3 cryptographic hash function. The implementation aims to be performant without sacrificing (too much) readability. Requires Go 1.25 or later.

In addition to the pure-Go implementation, this package also contains AVX-512 and AVX2 routines (generated by avo) that greatly increase performance for large inputs and outputs on amd64. Every other architecture runs the pure-Go code.

Three packages:

  • blake3hash.Hash (New, Sum256, Sum512, DeriveKey) and the seekable XOF (Hasher.XOF).
  • baoBao verified streaming: combined and outboard encodings, slices, and chunk-group verification.
  • guts — the low-level tree-hashing primitives the two packages above are built on, for callers that hash their own trees.

About this fork

A friendly fork of lukechampine/blake3 (import path lukechampine.com/blake3), taken at upstream v1.4.1 — which is also upstream's current master. The exported API is upstream's with two additions (guts.CompressBlocksN, bao.MaxGroup) and one removal: the five Bao* wrappers upstream kept in the root package after moving Bao to its own package, already deprecated there, are gone — use package bao. For everything else, switching is a change of import path.

What diverges:

Correctness. OutputReader.Seek to an offset that was not aligned to the XOF's internal buffer produced corrupt output on the next Read; the buffer now caches an absolute window of the stream. Seek rejects offsets that would wrap. New panics on a negative size or a key of the wrong length instead of producing an undefined state. In bao, offset+length overflow is rejected in ExtractSlice, DecodeSlice and VerifyChunk; VerifyChunk and DecodeSlice verify the root of an empty encoding, VerifyChunk counts leaves correctly so its outboard-length check is exact, and the parent-merge stack in compressGroup is sized for 2⁶⁴ bytes (upstream's could overflow). Every bao function validates its group (a value outside [0, MaxGroup] panics with a message instead of an unrelated runtime fault), and Encode rejects a negative length before writing anything. On amd64 a short eigentree is copied into a stack buffer before it reaches the SIMD kernels, which read a full 16 KiB: the bytes past the input's length are no longer touched.

Performance. Hasher.Write schedules a large write's eigentrees by total size — serial below 24 KiB of trees, concurrent above — and CompressEigentree deals 16 KiB groups out in NumCPU-capped runs, so a 1 MiB write no longer spawns 64 goroutines. Small or unaligned XOF reads compress only the blocks they need (guts.CompressBlocksN); large reads run across CPUs above a threshold. Bao encoding reuses one parent buffer instead of allocating per node.

Assurance. The eigentree fast path is pinned to the chunk-at-a-time reference by a deterministic test (every shape to 64 chunks, at many starting counters) and a fuzz target on the split pattern; CI fuzzes it on every run. XOF seeks are tested at buffer-unaligned offsets and with block counters past 2³². The bao decoders are fuzzed on hostile encodings and slice bounds. The amd64 assembly is generated from avo/gen.go, which lives in its own module so avo never enters the library's dependency graph; just lint-generated regenerates it and fails if the committed file differs, and the generated file passes go vet on amd64 (upstream's does not: its kernels broadcast scalar arguments straight from the stack frame, which vet's asmdecl rejects; here they go through a register first). The module builds and passes its tests on the go 1.25 floor declared in go.mod as well as on the pinned toolchain; vet's stdversion check keeps the floor honest.

Contributions that make sense upstream should go upstream first; see the organization's CONTRIBUTING and SECURITY policies.

Benchmarks

This fork against upstream, same machine

Upstream lukechampine/blake3 v1.4.1 (dd9ffb9, the fork point) and this fork, measured 2026-09-07 with Go 1.26.5 on an Apple M5 Pro (darwin/arm64, so the generic implementation; no SIMD paths run on this host). The two were run interleaved, three rounds of go test -run '^$' -bench . -benchmem -count=2 each, and paired with benchstat; both use the same plain b.N loop, since b.Loop alone costs ~2 ns per iteration and shows up as a false 4% on the 73 ns case. Lower is better; ~ means no statistically significant difference. Upstream's write-size rows were noisier (up to ±51%) than the fork's (≤4%) in this run; the medians agree with two earlier runs.

Benchmark upstream v1.4.1 this fork vs upstream
Write (32 KiB via io.CopyN) 912.0 ns 809.7 ns −11%
XOF/64 1009 ns 3.1 ns −99.7%
XOF/1024 1.004 µs 1.006 µs ~
XOF/65536 37.84 µs 36.10 µs −4.6%
XOF/1048576 223.2 µs 209.5 µs −6.1%
Sum256/64 73.11 ns 72.96 ns ~
Sum256/1024 1.034 µs 1.030 µs ~
Sum256/65536 44.61 µs 38.83 µs −13%
Sum256/1048576 236.5 µs 225.7 µs −4.6%
WriteSizes/4096 9.47 µs 4.70 µs −50%
WriteSizes/8192 14.89 µs 9.39 µs −37%
WriteSizes/16384 20.27 µs 18.21 µs −10%
WriteSizes/24576 27.06 µs 26.93 µs ~
WriteSizes/32768 29.84 µs 25.88 µs −13%
WriteSizes/49152 40.98 µs 31.56 µs −23%
WriteSizes/65536 42.73 µs 38.37 µs −10%
WriteSizes/131072 58.39 µs 53.29 µs −8.7%
WriteSizes/1048576 234.0 µs 223.6 µs −4.4%

Allocations per operation, where either side allocates at all:

Benchmark upstream v1.4.1 this fork
XOF/65536 58 9
XOF/1048576 70 37
Sum256/65536 30 20
Sum256/1048576 171 133
WriteSizes/4096/24576 9 – 18 2 – 3
WriteSizes/32768/1048576 20 – 170 10 – 132

The XOF/64 row is the OutputReader fix: upstream recomputes the block on every Seek(0)+Read, this fork serves it from its window. WriteSizes is this fork's benchmark (writesizes_bench_test.go), added because BenchmarkWrite alone, at io.Copy's 32 KiB, hid a 27% regression at exactly that size in an earlier version of the write scheduler; the same file compiled against upstream produced the upstream column.

Upstream's numbers (amd64, not re-measured here)

The figures below are upstream's, from its README, on its own hardware ("2020 MacBook Air (i5-7600K @ 3.80GHz)"). They predate every change in this fork and are kept only to show the relative shape of the three paths; the fork's amd64 kernels have not been re-measured.

AVX-512

BenchmarkSum256/64           120 ns/op       533.00 MB/s
BenchmarkSum256/1024        2229 ns/op       459.36 MB/s
BenchmarkSum256/65536      16245 ns/op      4034.11 MB/s
BenchmarkWrite               245 ns/op      4177.38 MB/s
BenchmarkXOF                 246 ns/op      4159.30 MB/s

AVX2

BenchmarkSum256/64           120 ns/op       533.00 MB/s
BenchmarkSum256/1024        2229 ns/op       459.36 MB/s
BenchmarkSum256/65536      31137 ns/op      2104.76 MB/s
BenchmarkWrite               487 ns/op      2103.12 MB/s
BenchmarkXOF                 329 ns/op      3111.27 MB/s

Pure Go

BenchmarkSum256/64           120 ns/op       533.00 MB/s
BenchmarkSum256/1024        2229 ns/op       459.36 MB/s
BenchmarkSum256/65536     133505 ns/op       490.89 MB/s
BenchmarkWrite              2022 ns/op       506.36 MB/s
BenchmarkXOF                1914 ns/op       534.98 MB/s

Shortcomings

There is no assembly routine for single-block compressions. This is most noticeable for ~1KB inputs.

Each assembly routine inlines all 7 rounds, causing thousands of lines of duplicated code. Ideally the routines could be merged such that only a single routine is generated for AVX-512 and AVX2, without sacrificing too much performance.

On amd64, the SIMD kernels always process a full 16-chunk (16 KiB) buffer: a shorter eigentree tail is padded and the unused lanes' work discarded.

About

An AVX-512 accelerated implementation of the BLAKE3 cryptographic hash function

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages