-
-
Notifications
You must be signed in to change notification settings - Fork 1
mldsa_audit
Audit of the leviathan-crypto WebAssembly ML-DSA implementation (AssemblyScript) against FIPS 204, covering all three parameter sets (ML-DSA-44, ML-DSA-65, ML-DSA-87) and verified against NIST ACVP vectors.
| Meta | Description |
|---|---|
| Target: |
leviathan-crypto WebAssembly implementation (AssemblyScript) |
| Spec: | FIPS 204 (ML-DSA Standard, August 2024) |
| Parameter sets: | ML-DSA-44, ML-DSA-65, ML-DSA-87 |
| Test vectors: | NIST ACVP (ML-DSA-keyGen-FIPS204, ML-DSA-sigGen-FIPS204, ML-DSA-sigVer-FIPS204) |
MlDsaBase exposes four methods that accept a caller-supplied prehash digest in place of the raw message: signHashPrehashed, signHashPrehashedDeterministic, signHashPrehashedDerand, and verifyHashPrehashed. They share the post-PH path with the non-prehashed signHash family via two internal helpers, signWithPrehash (in src/ts/mldsa/sign.ts) and verifyWithPrehash (in src/ts/mldsa/verify.ts).
Audit checklist:
-
signHashPrehashedvalidates digest size againstdigestSize(ph)before signing; mismatch throwsSigningError('sig-malformed-input'). -
signHashPrehashedDeterministicvalidates digest size and usesrnd ← 0³²per FIPS 204 §3.4 (a freshnew Uint8Array(32)is already zeroed; no manual fill needed). -
signHashPrehashedDerandvalidates bothrnd.length === 32anddigest.length === digestSize(ph); passes caller-suppliedrndtosignWithPrehashwithout modification. -
signHashPrehashedwipes the hedged-rndUint8Array in afinallyblock; deterministic and derand variants do not ownrnd(zeros / caller-supplied) and so do not wipe. -
verifyHashPrehashedreturnsfalse(no throw) on:- wrong-length
vk, - wrong-length
sig, - wrong-size
digest, - non-
Uint8Arraydigest.
- wrong-length
-
verifyHashPrehashedthrowsRangeErroronly on caller-side contract violations (ctx.length > 255, unsupportedph), mirroring the §3.6.2 posture ofverifyHash. - All four methods call
_assertNotOwned('sha3')and_assertNotOwned('mldsa'), plus_assertNotOwned('sha2')whenalgoNeedsSha2(ph)is true (via_assertHashPrereqs). -
_assertHashPrereqsvalidatesph(viadigestSize(ph)) before any sha2-initialization check, so widened-type callers (e.g. parsing vector files viaas PreHashAlgorithm) hit the canonical "unsupported HashML-DSA pre-hash"RangeErrorrather than a downstream sha2-not-initialized error. -
signWithPrehashbuilds M' =0x01 ‖ |ctx| ‖ ctx ‖ OID(algo) ‖ prehashviaconstructMPrimeHashand wipes M' infinally; it does NOT compute the prehash (caller supplies it) and does NOT wipeprehashorrnd(caller owns). -
verifyWithPrehashmirrorssignWithPrehash's contract on the verify side; returns whatevermldsaVerifyInternalreturns and wipes M' infinally. - The refactor preserves byte-identical output for the existing
signHash/signHashDeterministic/signHashDerand/verifyHashmethods. Coverage:test/unit/mldsa/hashvariant.test.ts(Gates 8/9/10 across 3 parameter sets × 12 pre-hash functions × 90 ACVP sigGen vectors + 90 ACVP sigVer vectors) all pass unchanged. - Equivalence with the non-prehashed family is asserted in
test/unit/mldsa/mldsa-prehashed.test.ts:signHashDeterministic(sk, M, ph, ctx)andsignHashPrehashedDeterministic(sk, Hash(M, ph), ph, ctx)produce byte-identical signatures across all 36 (paramSet × ph) tuples; cross-API verify (sign via prehashed → verify via non-prehashed and vice versa) succeeds. - ACVP sigGen vectors with
preHash=preHashare re-oracled throughsignHashPrehashedDeterministic/signHashPrehashedDerand(with externally-computed PH) and produce byte-identical signatures to the canonical vector.
FIPS 204 §6.2 Algorithm 7 ML-DSA.Sign_internal drives a rejection-sampling loop with four reject conditions: ‖z‖∞ ≥ γ₁ − β, ‖r₀‖∞ ≥ γ₂ − β, hint popcount ‖h‖₁ > ω, and (ML-DSA-44 only) ‖ct₀‖∞ ≥ γ₂. Random AFT sampling triggers these branches too rarely to give meaningful correctness assurance; ACVP ML-DSA JSON Specification §6.1.2 supplies KATs that exercise each path deterministically.
Audit checklist:
- Rejection-path coverage. Every reachable reject branch on every parameter set is exercised by at least one KAT vector in
test/vectors/mldsa_siggen_kats.ts. Source: ACVP ML-DSA JSON Specification §6.1.2 Table 1 (5 vectors per parameter set, 15 total). Coverage:test/unit/mldsa/mldsa_siggen_kats.test.tsrecords labelledt1-seed-*. - High-rejection-count coverage. At least two KAT vectors per parameter set force
‖rejection-count‖ ≥ 32(the ACVP §6.1.2 SHALL threshold) to confirm the signing loop tolerates heavy retry without early abort. Source: ACVP ML-DSA JSON Specification §6.1.2 Table 2. Recorded counts range 64-100 (ML-DSA-44 has 2 vectors at 77 and 100; ML-DSA-65 has 5 vectors at 64-73; ML-DSA-87 has 5 vectors at 64-69). Coverage:test/unit/mldsa/mldsa_siggen_kats.test.tsrecords labelledt2-rej-*. - Per-KAT byte equivalence. For every vector,
KeyGen(seed)reproduces(pk, sk)whoseSHA2-256(pk ‖ sk)matches the spec;Sign_internal(sk, M′, rnd = 0³²)produces σ whoseSHA2-256(σ)matches the spec. The spec stores hashes rather than full bytes to bound vector-file size; the test reconstructs both halves and compares hashes. - Vector provenance. ML-DSA-65 and ML-DSA-87 Table 1 entries reflect the Nov 19 2025 ACVP regeneration (
usnistgov/ACVP@f66d187, "Fixes ML-DSA tables with test cases that cover what they are intended to cover") that superseded the originally published vectors flagged on PQC-Forum. The ML-DSA-44 set was correct in the original specification and is unchanged. - Spec authority. Vectors are transcribed verbatim from
usnistgov/ACVP/src/ml-dsa/sections/04-testtypes.adoc(the asciidoc source for the live spec athttps://pages.nist.gov/ACVP/draft-celi-acvp-ml-dsa.html); the SHA256SUMS entry pins the vector file against further drift.
| Document | Description |
|---|---|
| mldsa | ML-DSA public API reference, including the prehashed surface |
| audits | Project audit index |
| architecture | Repository structure, build and CI, WASM modules, public API, test suite, and security posture |
- Sign Tools
-
SignatureSuite
- format-byte catalog, hybrid composite encodings, custom suite contract
- Serpent-256 TypeScript | WASM
-
Serpent,SerpentCtr,SerpentCbc,SerpentGenerator
-
- ChaCha20 TypeScript | WASM
-
ChaCha20,Poly1305,ChaCha20Poly1305,XChaCha20Poly1305,ChaCha20Generator
-
- AES TypeScript | WASM
-
AES,AESCbc,AESCtr,AESGCM,AESGCMSIV,AESGenerator
-
- ML-DSA TypeScript | WASM
- pure (FIPS 204):
MlDsa44,MlDsa65,MlDsa87 - pure-mode suites:
MlDsa44Suite,MlDsa65Suite,MlDsa87Suite - prehash suites:
MlDsa44PreHashSuite,MlDsa65PreHashSuite,MlDsa87PreHashSuite
- pure (FIPS 204):
- SLH-DSA TypeScript | WASM
- pure (FIPS 205):
SlhDsa128f,SlhDsa192f,SlhDsa256f - pure-mode suites:
SlhDsa128fSuite,SlhDsa192fSuite,SlhDsa256fSuite - prehash suites:
SlhDsa128fPreHashSuite,SlhDsa192fPreHashSuite,SlhDsa256fPreHashSuite
- pure (FIPS 205):
- Ed25519 TypeScript | WASM
-
Ed25519(pure + Ed25519ph),Ed25519Suite,Ed25519PreHashSuite
-
- ECDSA-P256 TypeScript | WASM
-
EcdsaP256(hedged + RFC 6979),EcdsaP256Suite - DER codec:
ecdsaSignatureToDer,ecdsaSignatureFromDer,encodeEcPrivateKey,decodeEcPrivateKey,pointDecompress
-
- Hybrid composites PQ-only | Classical+PQ
- PQ-only:
MlDsa44SlhDsa128fSuite,MlDsa65SlhDsa192fSuite,MlDsa87SlhDsa256fSuite - Classical+PQ:
MlDsa44Ed25519Suite,MlDsa65Ed25519Suite,MlDsa44EcdsaP256Suite,MlDsa65EcdsaP256Suite
- PQ-only:
- X25519 TypeScript | WASM
-
X25519,KeyAgreementError(RFC 7748)
-
- ML-KEM TypeScript | WASM
-
MlKem512,MlKem768,MlKem1024
-
-
Ratchet (SPQR)
-
KDFChain,ratchetInit,kemRatchetEncap,kemRatchetDecap,RatchetKeypair,SkippedKeyStore
-
- Hashing overview
- SHA-2 TypeScript | WASM
-
SHA256,SHA384,SHA512,SHA224,SHA512_224,SHA512_256 -
HMAC_SHA256,HMAC_SHA384,HMAC_SHA512,HKDF_SHA256,HKDF_SHA512
-
- SHA-3 TypeScript | WASM
-
SHA3_224,SHA3_256,SHA3_384,SHA3_512,SHAKE128,SHAKE256
-
- BLAKE3 TypeScript | WASM
-
BLAKE3,BLAKE3Stream,BLAKE3KeyedHash,BLAKE3KeyedHashStream -
BLAKE3DeriveKey,BLAKE3DeriveKeyStream,BLAKE3OutputReader,BLAKE3Hash
-
-
KMAC
-
CSHAKE128,CSHAKE256,KMAC128,KMAC256,KMACXOF128,KMACXOF256
-
-
Merkle
-
MerkleVerifier,MerkleLog -
SignedLog,Sha256Tree,Blake3Tree,MemoryStorage
-
-
Fortuna CSPRNG
-
Fortuna,SerpentGenerator,ChaCha20Generator,AESGenerator,SHA256Hash,SHA3_256Hash,BLAKE3Hash
-
- Utils TypeScript | WASM
-
constantTimeEqual,randomBytes,wipe, encoding helpers
-
-
TypeScript interfaces
-
Hash,KeyedHash,Blockcipher,Streamcipher,AEAD,Generator,HashFn
-