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.26 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:
blake3—hash.Hash(New,Sum256,Sum512,DeriveKey) and the seekable XOF (Hasher.XOF).bao— Bao 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.
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 this fork fixes and adds that upstream does not have, each with the
change that landed it, is the table in UPSTREAM.md, beside
the fork point and what was taken from upstream since. The root package and
bao are held to ports of the specification's reference implementation
(contract_test.go, bounded_test.go), and upstream's suites retired under
a mutation score; the amd64 assembly is generated from avo/gen.go in its own
module, regenerated and compared by just lint-generated, and passes go vet.
Contributions that make sense upstream should go upstream first; see the organization's CONTRIBUTING and SECURITY policies.
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.
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.
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
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
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
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.
The AVX-512 kernels have not been executed since the fork. No CI runner has
avx512f (the amd64 legs report AVX2 only), the development machine is
arm64, and the fork changed the kernels' generator (the scalar broadcasts
now go through a register). The AVX2 kernels and the pure-Go code run on
every CI leg; the AVX-512 path is pinned by the same vectors but on a host
nobody has. TODO: run the test suite on an AVX-512 host, or under an
emulator that has it, before the next release that touches avo/gen.go.