WO-11 — SLH-DSA keys and KMAC derivation

8 October 2026 · Branch wo-11-slh-dsa, starting at 2c8dd32.

Status: DONE

Decision #19 option A is implemented for testnet. CMake requires exactly OpenSSL 3.6.5 and fails configuration on any other version. The key-generation entry point runs the NIST ACVP SHAKE-128s seeded-keygen known-answer test once per process before generating a requested key; a mismatch throws and refuses key generation. startup_self_test() is exposed for wallet startup, and the same check also gates every key-generation entry point. The NIST KAT is also run by the CTest suite. A unit test substitutes a one-bit-wrong expected public key and verifies refusal. Revisit the provider choice before mainnet, as decision #19 directs. No library was installed.

Implemented

  • e46/src/keys.cpp and e46/include/e46/keys.hpp provide KMAC wallet-seed derivation, seeded key generation, randomized signing, deterministic test signing, and verification for SLH-DSA-SHAKE-128s.

  • Seed derivation follows decision #18 exactly: 32-byte master key; message ASCII("E46-key") || u32le(account) || u32le(index); empty customization; 48-byte KMAC256 output. The output is passed in FIPS 205 order: bytes 0–15 SK.seed, 16–31 SK.prf, 32–47 PK.seed.

  • Key and signature sizes are 32-byte public key, 64-byte private key, and 7,856-byte signature. Signing and verification reject contexts longer than 255 bytes. Keygen/sign/verify are backed by OpenSSL; no custom SLH-DSA code was written.

  • Added e46/golden/wo11-golden.json and its deterministic generator, plus the official NIST ACVP SLH-DSA-SHAKE-128s keygen and signature fixtures in e46/golden/wo11-nist-kat.json.

  • Added the keys library, CLI, unit/differential/golden tests, and fuzz harness to CMake.

Evidence

  • NIST ACVP SHAKE-128s keygen tcId 11 matches the expected 64-byte private key and 32-byte public key. The fixture’s first 48 private-key bytes are the exact input seed in the approved FIPS 205 order.

  • Startup KAT refusal test: replacing the expected KAT public key with a one-bit-different value causes the shared self-test gate to throw before returning a generated key.

  • Fixed empty-context signing and verification: OpenSSL now receives a non-null placeholder address with a zero-length context parameter. Added a randomized empty-context sign/verify round trip and a context mismatch rejection check.

  • Added official NIST ACVP empty-context vectors: deterministic sigGen tcId 215 matches all 7,856 signature bytes; sigVer tcId 343 accepts. Flipping a bit in that NIST sigVer signature is rejected. These are SHAKE-128s, external-interface, pure-message cases with an explicitly empty context.

  • NIST ACVP deterministic signature tcId 214 matches all 7,856 bytes and verifies. Source files are linked in the golden fixture and originate from NIST ACVP keygen, signature-generation, and signature-verification vectors.

  • Differential check: 24 frozen wallet-seed cases, 1 keygen KAT, 1 signature KAT, empty-context sigGen/sigVer KATs, and 1,000 fresh KMAC inputs against the independent pure-Python SP 800-185 reference; 0 mismatches. Invalid lengths, malformed input, and the changed empty-context signature were rejected as expected.

  • Full repository release and ASan/UBSan suites passed 28/28 before this review correction. After the empty-context fix, all three WO-11 tests pass in both release and ASan/UBSan builds (3/3 each).

  • LibFuzzer with Homebrew LLVM Clang 22 and ASan/UBSan: 2,782,145 runs in 31 seconds, no crash or sanitizer finding. Apple system Clang lacked its libFuzzer runtime, so the dedicated fuzz build used Homebrew LLVM.

  • WO-11 invokes no SHA-2 primitive; it uses KMAC256 and the SHAKE-based SLH-DSA parameter set. Earlier golden files were not edited.

Limits

  • Only the installed OpenSSL 3.6.5 default provider was exercised. Its deterministic seeded-keygen API is documented for testing; option A constrains this work to the pinned provider and startup KAT, with review required before mainnet.

  • sign_deterministic_for_test exists solely to reproduce the NIST ACVP signature vector; the production sign function uses the provider’s randomized signing mode.

  • No FIPS provider configuration, cross-platform build, transaction/wallet integration, or network consensus behavior was tested.

  • No changes were made to owner files in MAPS/ or docs/; no commits, pushes, merges, history rewrites, or file deletions were performed.