ML-DSA (FIPS 204, August 2024) — CRYSTALS-Dilithium post-quantum signatures + HashML-DSA + internal interface.

Module ml_dsa | Source packages/front/fw/src/crypto/pkc/ml_dsa.js | Deps sha3, bitArray, random, sha256, sha384, sha512, sha512_224, sha512_256 | Worker-safe yes

NIST post-quantum signatures. 3 paramSets. Variants: pure (default), preHash (HashML-DSA, FIPS 204 §5.4), internal (raw §6.2), externalMu (pre-computed mu).

Resolve

const ml_dsa = runtime.resolve('ml_dsa');
// Returns: { ml_dsa44, ml_dsa65, ml_dsa87, _internal }

API

ParamSet Security publicKey secretKey signature
ml_dsa44 NIST PQC Level 2 1312 B 2560 B 2420 B
ml_dsa65 NIST PQC Level 3 1952 B 4032 B 3309 B
ml_dsa87 NIST PQC Level 5 2592 B 4896 B 4627 B
Method Signature Returns
ml_dsa<N>.keygen(seed?) (Uint8Array(32)?) => {publicKey, secretKey} Keypair
ml_dsa<N>.sign(msg, sk, ctx?, rnd?) (Uint8Array, Uint8Array, Uint8Array?, Uint8Array(32)?) => Uint8Array Sign external+pure
ml_dsa<N>.verify(sig, msg, pk, ctx?) — boolean
ml_dsa<N>.signPh(msg, sk, hashAlg, hashMod, ctx?, rnd?) — HashML-DSA (preHash + OID)
ml_dsa<N>.verifyPh(sig, msg, pk, hashAlg, hashMod, ctx?) — boolean
_internal.signInternal(M, sk, rnd?) — Raw §6.2 (no M' wrapper)
_internal.verifyInternal(sig, M, pk) — boolean raw
_internal.signWithMu(mu, sk, rnd?) — externalMu (pre-computed mu)
_internal.verifyWithMu(sig, mu, pk) — boolean

Supported hashAlg values (HashML-DSA)

12 OIDs: SHA2-{224,256,384,512,512/224,512/256}, SHA3-{224,256,384,512}, SHAKE-128, SHAKE-256.

Examples

Sign + verify (pure, default)

const { ml_dsa } = fw.runtime.resolveAll(['ml_dsa']);
const { publicKey, secretKey } = ml_dsa.ml_dsa65.keygen();

const msg = new TextEncoder().encode('Hello, ML-DSA!');
const sig = ml_dsa.ml_dsa65.sign(msg, secretKey);    // deterministic per default
const ok  = ml_dsa.ml_dsa65.verify(sig, msg, publicKey);   // true

HashML-DSA (preHash variant)

const sha256 = fw.runtime.resolve('sha256');
const sig = ml_dsa.ml_dsa65.signPh(msg, secretKey, 'SHA2-256', sha256, ctx);
const ok  = ml_dsa.ml_dsa65.verifyPh(sig, msg, publicKey, 'SHA2-256', sha256, ctx);

Internal interface (raw §6.2)

const sig = ml_dsa.ml_dsa65._internal.signInternal(M, secretKey);   // no M' wrapper

Worker Usage

// ml_dsa65 sign ≈ 30-100ms ; verify ≈ 5-15ms — delegate sign to a Worker
const worker = fw.createWorker(
    function ({ libs, args }) {
        self.postMessage(libs.ml_dsa.ml_dsa65.sign(args[0], args[1]));
    },
    { dependencies: ['ml_dsa'], args: [msg, secretKey] }
);

Notes

  • Deterministic by default: rnd=null → byte-exact reproducible signature.
  • HashML-DSA OID: DER prefix embedded in the M' wrapper (FIPS 204 Algorithm 4).
  • _internal.* test-only — bypasses §5.4 wrappers; do NOT expose to end users (risk of secret sk leakage).
  • Full ACVP under CRYPTO_FULL=1: 75 keyGen + 45 sigVer + 90 sigGen ext+pure.

See also