libzstd-bitexact-rs
A pure-Rust reimplementation of Zstandard, built to be bit-exact with the reference C implementation.
Most Rust compression ports settle for "produces valid output". This project holds itself to a stricter standard: for any input, behavior indistinguishable from C libzstd — identical decompressed bytes, matching accept/reject decisions on malformed data, and identical compressed bytes at every compression level — this is the zstd 1.5.5 line (see Versioning). Bit-exactness is enforced mechanically, by differential-testing every code path against the real libzstd, not by review (reproduce the proof: validating bit-exactness).
Status
| Area | State |
|---|---|
| Decompression (frames, raw/RLE/compressed blocks) | ✅ implemented |
| FSE, Huffman (1- and 4-stream), sequences, repeat offsets | ✅ implemented |
| Content checksums (XXH64), skippable frames, multi-frame input | ✅ implemented |
| Dictionaries (raw-content and trained/ZDICT) | ✅ implemented |
windowLogMax enforcement |
✅ implemented |
Streaming decompression (Read-based, bounded sliding window) |
✅ implemented |
| Differential proof vs C libzstd 1.5.5, error-parity audit | ✅ proven for 0.155.0 — reproduce via the validation doc |
| Compression, bit-exact with C — every level (1–22 and negatives) | ✅ byte-identical, differential-tested |
Streaming compression (ZSTD_compressStream2 flush/end parity) |
✅ byte-identical; unlimited length at every level¹ |
Dictionary compression (raw + trained/ZDICT, all 9 strategies, usingDict + CDict paths) |
✅ byte-identical |
Multithreaded mode (ZSTDMT job-splitting: one-shot + streaming, with dictionary and LDM) |
✅ byte-identical² |
| Long-distance matching (LDM) compression | ✅ byte-identical |
¹ Streams longer than windowSize + blockSize turn the C window into an
extDict; the extDict match finders are ported for all nine strategies, and
index overflow correction recycles the 32-bit index space past 3500 MiB, so
every level streams without any length limit. This includes the configurations
where C auto-enables long-distance matching (`strategy >= btopt && windowLog
= 27
— level 22 at unknown or > 64 MiB content sizes): LDM is ported and bit-exact, including a faithful reproduction of zstd 1.5.7's no-opZSTD_ldm_gear_reset`.
² C's multithreaded output is deterministic and worker-count-independent — only
the job decomposition matters — so this crate reproduces it single-threaded,
keeping the library zero-dependency. One-shot and streaming, content checksums,
flush and pledged sizes, dictionaries, and cross-job long-distance matching are
all byte-identical to the C zstdmt oracle.
Performance
Correctness is the headline; speed was worked level by level. Single-threaded on
a typical x86-64 desktop, both directions measured within ~2–2.5x of C libzstd
while remaining #![forbid(unsafe_code)]:
- Compression ≈ 0.4–0.6x of C at the fast levels, closing to ~parity (≈1.0x) at the highest levels, where the optimal parser dominates the cost.
- Decompression ≈ 0.4–0.6x of C on entropy-coded data; at C speed on incompressible input and large copies.
The residual decompression gap is structural: C's hot loop relies on wildcopy — writing past the end of each match into uninitialized output — which safe Rust cannot express without a compensating zeroing pass. Compressed and decompressed bytes are, of course, identical to C regardless of speed.
Usage
let data = decompress?;
// On untrusted input, cap the output size to defuse decompression bombs:
let data = decompress_with_limit?;
// Dictionaries, output limits, and a maximum window log compose through the
// DecodeOptions builder:
use ;
let dict = new?;
let data = new
.dictionary
.limit
.window_log_max
.decompress?;
// Streaming: wrap any Read source; memory stays bounded by the frame's
// window rather than its content.
use Read;
use StreamDecoder;
let mut out = Vecnew;
new.read_to_end?;
// Compression: byte-identical to ZSTD_compress at every level (1-22,
// and negative levels for faster-than-1 modes).
let frame = compress?;
// Streaming compression: byte-identical to ZSTD_compressStream2 for the
// same sequence of operations (continue/flush/end, pledged sizes,
// checksums). Note that without a pledged size, parameters resolve as
// "content size unknown", exactly as in C.
let mut enc = new;
let mut frame = Vecnew;
enc.compress?;
enc.flush?;
enc.finish?;
Design principles
- The C implementation is the specification. Where RFC 8878 and the zstd
sources allow latitude, this crate does what
lib/decompressdoes. Every table-construction routine is a line-by-line port of its C counterpart (FSE_buildDTable's spread step,HUF_readDTableX1's rank fill, …), with the C function named in the comments. - Differential testing as ground truth. The test suite compresses a
spread of datasets with the real libzstd (via the
zstdcrate's C bindings) at levels 1–22, in bulk and streaming modes, with and without checksums and dictionaries (raw-content and trained), and requires byte-identical round-trips. The streaming decoder is fed the same frames at chunk sizes from one byte up, and must reproduce identical output regardless of how the input is split. ThewindowLogMaxknob is checked for accept/reject parity against C's streaming decoder. Random-input probes assert we never accept data the C decoder rejects. - No
unsafe, no dependencies. The library is#![forbid(unsafe_code)]and dependency-free — and on this 1.5.5 line so is the whole project: the C oracle that proves bit-exactness lives only in git history now, reproduced out-of-tree (see validating bit-exactness). - Correctness first, speed second. Optimizations come only after parity is locked in by tests.
Testing
This crate builds and tests in pure Rust — no C toolchain, no dependencies:
tests/fuzz_smoke.rs builds frames with this crate's own compressor, then
mutates and truncates them to assert the decoder never panics or runs away on
arbitrary input.
The bit-exactness claims above — identical compressed and decompressed bytes
vs C libzstd 1.5.5, at every level, with dictionaries, LDM, and ZSTDMT — are
proven by a differential suite that runs this crate against the real C libzstd
1.5.5 as an oracle. To keep the repository C-free, that suite was archived in git
history at tag v0.155.0 once 0.155.0 was published and CI-green. Reproducing
the proof is a two-command worktree checkout that never adds C to this repo — see
validating bit-exactness.
Versioning
The crate version encodes the upstream zstd release it is bit-exact with:
0.<zstd digits>.<patch>. This is the 0.155.x line — bit-exact with zstd
1.5.5 (the parallel 0.157.x line targets zstd 1.5.7); the patch component
(0.155.0, 0.155.1, …) counts this crate's own fixes against that target.
Retargeting a newer zstd bumps the minor (zstd 1.5.8 → 0.158.0). The leading
0. marks the public API as still pre-1.0. Bit-exactness is only meaningful
against a single upstream release, so the target is fixed at 1.5.5 — see
validating bit-exactness.
License
BSD 3-Clause, matching upstream zstd. This is an independent reimplementation; it is not affiliated with or endorsed by Meta.