A reader landing on README.md saw "First practical threshold signature
scheme fully compatible with NIST FIPS 204 ML-DSA" + parameter tables +
bandwidth tables, then a buried "Skeleton + parameter tables shipped"
status line. The honest framing is harder up front: the threshold
signer (keygen / sign / combine / a_posteriori) does not exist yet,
HRej returns "not yet wired to CIRCL ring", and any caller relying on
this package to sign today will fail.
Mirrors the per-validator-vs-threshold framing magnetar (FIPS 205
SLH-DSA) made explicit at v0.4.2: per-validator standalone is the
production lane; true threshold without a trusted dealer is research-
grade. The architectural reasoning is the same — both primitives have
the same shape, and the bridge / Warp 2.0 already use the per-validator
ML-DSA path (luxfi/crypto/mldsa + luxfi/warp MLDSACertSet) in
production today.
Changes:
- README.md Top-level status banner with prod vs research preview
table; file table now shipped vs ❌ not written; gap
list mirrored from threshold/CLAUDE.md
- doc.go Matching "STATUS — research preview, NOT production"
banner so `go doc luxfi/threshold/protocols/mldsa`
surfaces the same caveat to callers before they import
Tests untouched, still pass (0.36s).
4.8 KiB
Threshold ML-DSA
Status — research preview, NOT production.
Lane What's here Status Per-validator ML-DSA N independent FIPS 204 signatures, aggregated at the consensus layer as MLDSACertSetProduction. Lives at luxfi/crypto/mldsa(CIRCL-backed) +luxfi/warpMLDSACertSet. This is the PQ identity-proof lane used by Warp 2.0 + bridge admin/pause schemes today.Threshold ML-DSA (this package) RSS partitions, parameter tables, structural cert-set fuzz seed Research preview. keygen.go/sign.go/combine.go/a_posteriori.godo not ship yet.hrej.go::HRejreturns"not yet wired to CIRCL ring". Do NOT depend on this package for live signing — there is no working signer to depend on.The pattern mirrors
luxfi/magnetar(FIPS 205 SLH-DSA): per-validator standalone primitive is production; true threshold without trusted dealer is research-grade and not yet deployed. The vocabulary distinction matters — seemagnetar/README.md§ "Why per-validator instead of threshold?" for the architectural reasoning.
First practical threshold signature scheme fully compatible with NIST FIPS 204 ML-DSA. Outputs standard ML-DSA signatures (drop-in verification) — once the signer lands. The current tree is the paper-port scaffold up to that point.
Paper: Celi, del Pino, Espitau, Niot, Prest — Efficient Threshold ML-DSA,
USENIX Security 2026. The full LaTeX source is being prepared at
papers/threshold-mldsa.tex and is not yet checked in (the hrej.go
reference to that path will become live when the paper lands).
Configurations
- Security levels: ML-DSA-44 / 65 / 87 (NIST I / III / V)
- Threshold range:
2 ≤ T ≤ N ≤ 6(hard upper bound in this release) - Typical:
2-of-3,3-of-5 - Security model: static dishonest majority in the ROM
Rounds
3 rounds per signing attempt, K parallel instances (see params.go):
- Commit — each party publishes
H(vk, i, wᵢ)withwᵢ = A·rᵢ - Reveal — open
wᵢ, derive challengec - Respond — each party publishes
zᵢafter local hyperball rejection
Combiner verifies the aggregated z meets ML-DSA bounds and emits a
standard signature.
Bandwidth (per party, per successful attempt, ML-DSA-44)
| N\T | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|
| 2 | 10.5 kB | ||||
| 3 | 15.8 kB | 21.0 kB | |||
| 4 | 15.8 kB | 36.8 kB | 42.0 kB | ||
| 5 | 15.8 kB | 73.5 kB | 157.4 kB | 84.0 kB | |
| 6 | 21.0 kB | 99.8 kB | 388.4 kB | 524.8 kB | 194.2 kB |
Files (current tree)
| File | Status | Role |
|---|---|---|
doc.go |
✅ shipped | package doc |
params.go |
✅ shipped | (T,N) × level parameter sets (Tables 3, 10, 11) |
rss.go |
✅ shipped | replicated secret sharing, hardcoded optimal partitions (Appendix B, Algorithm 6) |
hrej.go |
⚠ stub | HRej() returns "not yet wired to CIRCL ring" |
rss_test.go |
✅ shipped | RSS partition correctness + subset count + recovery |
fuzz_certset_test.go |
✅ shipped | wire-format parser fuzz seed |
keygen.go |
❌ not written | Algorithm 1 — paper port pending CIRCL ring integration |
sign.go |
❌ not written | Algorithms 2–4 (commit / reveal / respond) |
combine.go |
❌ not written | aggregate z + emit FIPS-204-byte-compat sig |
a_posteriori.go |
❌ not written | Section 6.2 acceptance check + retry |
Gap to "solid and done"
This list is mirrored in the parent threshold/CLAUDE.md.
1-day fixes (mostly docs / wiring):
- Land
papers/threshold-mldsa.texor remove the citation fromhrej.go. - NIST CAVP
.rspKAT vector ingestion inluxfi/crypto/mldsa(generator C exists atc/ref/nistkat/PQCgenKAT_sign.c; the Go side never consumes published vectors). FIPS-TRACEABILITY.mdinluxfi/crypto/mldsamapping parameter sets → FIPS 204 sections.- Profile-level scheme pin in
luxfi/bridge/cmd/bridge/so the daemon refuses an inbound non-PQ signing request when the operator profile namesml-dsa-65.
Multi-week (real research / engineering):
- Implement
keygen.go/sign.go/combine.go/a_posteriori.go. Paper claims 3-round protocol; none of the round transitions are coded. - End-to-end round-protocol test (2-of-3 keygen → sign → verify-via-CIRCL byte-compat). Without it the "byte-compatible with FIPS 204" claim in
doc.gois unverified. dudectconstant-time validation on the per-party sign path once it exists.- External cryptographer sign-off (Tier A submission shape, mirroring
magnetar/CRYPTOGRAPHER-SIGN-OFF.md). - Benchmark harness (
mldsa_bench_test.go.brokenalready exists incrypto/mldsaas a broken artifact — repair + add a threshold bench).