Skip to content
forkcloserPublic

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

 
 

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.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.

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 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.

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.

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.

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