Skip to content

Releases: nandanito/slh-dsa-zig

v0.1.1 — supersedes v0.1.0 (EXPERIMENTAL)

Choose a tag to compare

@nandanito nandanito released this 30 Jul 14:08
439c180

⚠️ Supersedes v0.1.0

v0.1.0 was tagged hours earlier and shipped two documentation defects. Use this release. The library itself is byte-for-byte behaviour-identical — git diff v0.1.0..v0.1.1 -- src/ is comment-only. See What changed since v0.1.0 at the end.

🚧 EXPERIMENTAL — DO NOT USE IN PRODUCTION

Not audited by a third party. Passing KATs proves conformance to the spec, not resistance to an attacker. A tag is not an audit. Do not use this to protect anything you care about.

A pure-Zig implementation of SLH-DSA (FIPS 205) — the stateless hash-based post-quantum signature scheme also known as SPHINCS+. All 12 parameter sets, comptime-parameterised, no dependencies beyond the Zig standard library.

What's in it

The complete scheme, validated against external ground truth:

  • Key generation, signing, verification — FIPS 205 §9 (internal) and §10 (external, with context strings)
  • WOTS+ (§5), XMSS (§6), hypertree (§7), FORS (§8)
  • Both hash families — SHAKE (§11.1) and SHA-2 (§11.2), including the SHA-512 widening at security category 3 and above

NIST ACVP: keyGen 120/120 · sigGen 336/336 byte-identical · sigVer 336/336 accept/reject — across all 12 parameter sets, internal and external interfaces.

Phase gates

Gate Status
1. Functional ✅ ACVP passes on x86_64 and ARM64
2. Constant-time ✅ keygen + signing verified end-to-end in SK.seed/SK.prf under Valgrind, plus WOTS+/FORS primitives in isolation, with a negative control proving the taint is live. x86-64-v3 only (#6)
3. Fuzz 🚧 harnesses + nightly cumulative workflow merged; 24h total still accruing
4. Benchmark ✅ with one named exception — see below
5. Documentation nandan.me/slh-dsa-zig
6. Banner 🚧 EXPERIMENTAL stays until a third-party audit

On the benchmark gate

Measured portable-against-portable on x86-64 (-Dcpu=x86_64_v3 — AVX2, no SHA-NI), against PQClean's portable clean C reference: 35 of 36 measurements within 2×.

SLH-DSA-SHA2-256s keygen sits at 2.06×. That is reported rather than papered over, and it is attributed rather than excused: the SHAKE sets sit at 0.96×, and both families run the same structural code — wots.zig, xmss.zig, hypertree.zig and fors.zig are all generic over hash.zig's Hash(p), and nothing above the adapter branches on hash family. That control isolates the excess to the hash-adapter layerstd.crypto's SHA-2 implementations — rather than to this library's SLH-DSA machinery.

The two adapters differ by more than the primitive, which is worth stating precisely: §11.1 hashes the expanded 32-byte address while §11.2 hashes the 22-byte compressed ADRSc; §11.2's H_msg adds an MGF1 layer §11.1 has no equivalent of; and within §11.2 the primitive changes with security level — H, T_l and H_msg are SHA-256 at n = 16 but SHA-512 at n = 24, 32. Since SLH-DSA-SHA2-256s is n = 32, its keygen drives F (SHA-256) and H (SHA-512), so the exceedance is not attributable to SHA-256 specifically. The conclusion holds regardless — §11.2 feeds less data per hash call and is still slower, and MGF1 runs once per operation against millions of F/H calls.

An accelerated build (+sha) passes 36/36 at 0.33×–1.00×, and is published alongside but never gated — a gate you can pass with a CPU feature is a gate that cannot fail for the right reason. Full numbers and reasoning in bench/README.md.

Known limitations

  • HashSLH-DSA pre-hash variants are not implemented (#45) — those ACVP groups are skipped, never silently dropped
  • Verification is not constant-time audited, by construction: it handles only public data
  • The constant-time audit is x86-64 only, with AVX-512 paths excluded (#6)
  • xmss_sign recomputes shared subtrees — iterative treehash is open (#38)

Install

zig fetch --save git+https://github.com/nandanito/slh-dsa-zig.git#v0.1.1

Requires Zig 0.16.0.

Documentation

nandan.me/slh-dsa-zig — a learning-oriented guide: why hash-based signatures exist, a chapter per component in dependency order, per-module walkthroughs mapped to FIPS 205, a glossary, and a build-it-yourself study path for deriving SLH-DSA from the standard yourself.

Licence

Code 0BSD. Documentation CC BY 4.0 (LICENSE-DOCS) — reuse it, credit it.

What changed since v0.1.0

Documentation and comments only.

1. A stale example header. examples/basic_sign.zig still carried "This example will @panic until the scheme bodies land" from the stub era. examples is in build.zig.zon's paths, so that shipped inside the package — the most obvious first file told a new reader the library does not work.

2. The benchmark attribution named the wrong primitive. Four documents said the SHAKE and SHA-2 sets "differ only in the hash primitive" and pinned the one over-gate measurement on std.crypto's SHA-256. The two hash adapters differ on three axes — ADRS encoding, MGF1, and the SHA-256/SHA-512 split by security level — and SLH-DSA-SHA2-256s is n = 32, where H and T_l are SHA-512. Nine instances across four files, six of them inside the published package.

The structural half of that argument is unchanged and still holds: every layer above hash.zig is generic over Hash(p), so the SHAKE control does establish that this library's SLH-DSA machinery is at parity with PQClean's. What moved is that the claim is now made at hash-adapter granularity, where it is true.

Everything else — all 12 parameter sets, ACVP 120/120 and 336/336, the constant-time audit, and the benchmark numbers themselves — is unchanged from v0.1.0.

v0.1.0 stays published and marked superseded. It is the honest record of what was tagged.

v0.1.0 — superseded by v0.1.1 (EXPERIMENTAL)

Choose a tag to compare

@nandanito nandanito released this 30 Jul 11:30
1966991

⛔ Superseded by v0.1.1

Use v0.1.1 instead. This tag shipped two documentation defects, both fixed hours later: a stale header in examples/basic_sign.zig claiming the example panics, and a benchmark attribution naming std.crypto's SHA-256 where the failing measurement is n = 32 and therefore SHA-512 (nine instances across four files, six of them inside the package).

The library is behaviour-identical — git diff v0.1.0..v0.1.1 -- src/ is comment-only — so nothing here is unsafe, it is inaccurate. This release stays published as the honest record of what was tagged.

🚧 EXPERIMENTAL — DO NOT USE IN PRODUCTION

Not audited by a third party. Passing KATs proves conformance to the spec, not resistance to an attacker. A tag is not an audit. Do not use this to protect anything you care about.

A pure-Zig implementation of SLH-DSA (FIPS 205) — the stateless hash-based post-quantum signature scheme also known as SPHINCS+. All 12 parameter sets, comptime-parameterised, no dependencies beyond the Zig standard library.

What's in it

The complete scheme, validated against external ground truth:

  • Key generation, signing, verification — FIPS 205 §9 (internal) and §10 (external, with context strings)
  • WOTS+ (§5), XMSS (§6), hypertree (§7), FORS (§8)
  • Both hash families — SHAKE (§11.1) and SHA-2 (§11.2), including the SHA-512 widening at security category 3 and above

NIST ACVP: keyGen 120/120 · sigGen 336/336 byte-identical · sigVer 336/336 accept/reject — across all 12 parameter sets, internal and external interfaces.

Phase gates

Gate Status
1. Functional ✅ ACVP passes on x86_64 and ARM64
2. Constant-time ✅ keygen + signing verified end-to-end in SK.seed/SK.prf under Valgrind, plus WOTS+/FORS primitives in isolation, with a negative control proving the taint is live. x86-64-v3 only (#6)
3. Fuzz 🚧 harnesses + nightly cumulative workflow merged; 24h total still accruing
4. Benchmark ✅ with one named exception — see below
5. Documentation nandan.me/slh-dsa-zig
6. Banner 🚧 EXPERIMENTAL stays until a third-party audit

On the benchmark gate

Measured portable-against-portable on x86-64 (-Dcpu=x86_64_v3 — AVX2, no SHA-NI), against PQClean's portable clean C reference: 35 of 36 measurements within 2×.

SLH-DSA-SHA2-256s keygen sits at 2.06×. That is reported rather than papered over, and it is attributed rather than excused: the SHAKE sets sit at 0.96×, and both families run the same structural code — wots.zig, xmss.zig, hypertree.zig and fors.zig are all generic over hash.zig's Hash(p), and nothing above the adapter branches on hash family. That control isolates the excess to the hash-adapter layerstd.crypto's SHA-2 implementations — rather than to this library's SLH-DSA machinery.

The two adapters differ by more than the primitive, which is worth stating precisely: §11.1 hashes the expanded 32-byte address while §11.2 hashes the 22-byte compressed ADRSc; §11.2's H_msg adds an MGF1 layer §11.1 has no equivalent of; and within §11.2 the primitive changes with security level — H, T_l and H_msg are SHA-256 at n = 16 but SHA-512 at n = 24, 32. Since SLH-DSA-SHA2-256s is n = 32, its keygen drives F (SHA-256) and H (SHA-512), so the exceedance is not attributable to SHA-256 specifically. The conclusion holds regardless — §11.2 feeds less data per hash call and is still slower, and MGF1 runs once per operation against millions of F/H calls.

An accelerated build (+sha) passes 36/36 at 0.33×–1.00×, and is published alongside but never gated — a gate you can pass with a CPU feature is a gate that cannot fail for the right reason. Full numbers and reasoning in bench/README.md.

Known limitations

  • HashSLH-DSA pre-hash variants are not implemented (#45) — those ACVP groups are skipped, never silently dropped
  • Verification is not constant-time audited, by construction: it handles only public data
  • The constant-time audit is x86-64 only, with AVX-512 paths excluded (#6)
  • xmss_sign recomputes shared subtrees — iterative treehash is open (#38)

Install

zig fetch --save git+https://github.com/nandanito/slh-dsa-zig.git#v0.1.0

Requires Zig 0.16.0.

Documentation

nandan.me/slh-dsa-zig — a learning-oriented guide: why hash-based signatures exist, a chapter per component in dependency order, per-module walkthroughs mapped to FIPS 205, a glossary, and a build-it-yourself study path for deriving SLH-DSA from the standard yourself.

Licence

Code 0BSD. Documentation CC BY 4.0 (LICENSE-DOCS) — reuse it, credit it.