ML-DSA (Module-Lattice Digital Signature Algorithm) is NIST's standardized post-quantum digital signature scheme, published as FIPS 204 in August 2024. It is the official name for the algorithm formerly known as CRYSTALS-Dilithium.
Replacement for Classical Signatures
Classical signatures (RSA, ECDSA, EdDSA) rely on integer factorization and discrete logarithms — problems that quantum computers solve efficiently via Shor's algorithm. ML-DSA instead relies on Module-LWE and Module-SIS — lattice problems for which no efficient quantum algorithm is known. The "module" structure (polynomial rings over finite fields) keeps keys and signatures practical while preserving strong security assumptions.
Sizes and Performance
| Parameter set | Public key | Secret key | Signature | Security category |
|---|---|---|---|---|
| ML-DSA-44 | 1,312 B | 2,560 B | 2,420 B | NIST 2 (SHA-256 collision) |
| ML-DSA-65 | 1,952 B | 4,032 B | 3,309 B | NIST 3 (AES-192) |
| ML-DSA-87 | 2,592 B | 4,896 B | 4,627 B | NIST 5 (AES-256) |
Key and signature sizes are from FIPS204, Table 2 (p. 16). Security categories are from FIPS204, Table 1 (p. 15).
Compared to classical schemes:
| Scheme | Public key | Signature |
|---|---|---|
| Ed25519 | 32 B | 64 B |
| ECDSA P-256 | 64 B | ~72 B |
| ML-DSA-65 | 1,952 B | 3,309 B |
ML-DSA keys and signatures are much larger — the main practical cost of post-quantum migration. Signing is also slower than Ed25519 due to polynomial arithmetic and rejection sampling, though verification is reasonably fast (especially with AVX2/NEON optimizations).
Mathematical Foundation
ML-DSA operates in the polynomial ring FIPS204, §2.4.1 (p. 7):
where and FIPS204, §2.3 (p. 5). The prime satisfies , enabling the Number Theoretic Transform for fast polynomial multiplication FIPS204, §2.5; §7.5 (pp. 8, 42).
Vectors of or polynomials from form the module structure FIPS204, §2.4.2 (p. 7). The security parameters are FIPS204, Table 1 (p. 15; see also §6.1 and §6.2):
| Parameter set | (secret coeff. bound) | ( in challenge) | (signature bounds) | ||
|---|---|---|---|---|---|
| ML-DSA-44 | 4 | 4 | 2 | 39 | , |
| ML-DSA-65 | 6 | 5 | 4 | 49 | , |
| ML-DSA-87 | 8 | 7 | 2 | 60 | , |
These map to NIST security categories 2, 3, and 5 respectively FIPS204, Table 1 NISTCAT. Category 2 is defined against SHA-256 collision resistance (~128-bit, collision context); categories 3 and 5 are defined against AES-192 and AES-256 key search (~192-bit and ~256-bit).
Keys
Secret key
The secret key consists of FIPS204, §6.1:
- and : short polynomial vectors with coefficients in
- : the dropped low-order bits of the public-key vector
- : public seed (also in the public key)
- : private seed for signing randomness
- : hash of the public key, used when forming
Public key
The public key is derived via a Module-LWE relationship:
- Sample a random matrix from seed (expanded on the fly, not stored).
- Compute .
- Split into high and low parts: , dropping low-order bits per coefficient.
The public key is . Finding from and is a Module-LWE problem — believed hard even for quantum computers.
Signing
Signing is a Fiat–Shamir with aborts protocol FIPS204, §3.3. In the interactive version, the prover commits to a random value, the verifier sends a challenge, and the prover responds. ML-DSA makes this non-interactive by deriving the challenge from a hash of the commitment and message — and adds rejection sampling so the response does not leak the secret key.
Before the commit/challenge loop, the signer computes a message representative (this binds the signature to both the message and the public key):
where is a hash of the public key FIPS204, Algorithm 7, line 6.
Phase 1: Commit
The goal of the commit phase is to pick a random masking vector , publish a compressed commitment to , and bind it to the message via a hash. The full vector is never included in the signature.
Step 1 — Sample the mask. Draw a random vector with coefficients in .
Step 2 — Compute the commitment. .
Step 3 — Round to high bits. Each coefficient of is decomposed mod into a coarse part and a fine part:
The commitment is , which keeps only from each coefficient — effectively rounding to the nearest multiple of . The fine part is discarded.
Think of each coefficient as a number on a line divided into buckets of width . The commitment records only which bucket each coefficient falls in, not its exact position within the bucket.
How many bits enter the hash? Each bucket index is small enough to encode in 6 bits (ML-DSA-44) or 4 bits (ML-DSA-65 and ML-DSA-87) FIPS204, Algorithm 28. The lower-order bits within each bucket are thrown away. This is why the signature can store a hash of rather than the full .
Step 4 — Hash the commitment. Encode and hash it together with :
The output is the commitment hash (64 bytes for ML-DSA-65). It is stored in the signature. At this point the signer has publicly committed to and bound it to the message — but has not yet produced the response.
Phase 2: Challenge
The challenge is a sparse polynomial with exactly coefficients equal to and the rest zero (e.g., for ML-DSA-65). It is not stored in the signature. Instead, both signer and verifier derive it deterministically from :
SampleInBall uses as a seed to pseudorandomly place nonzero coefficients among the 256 positions FIPS204, Algorithm 29. Because depends on both and , the challenge is bound to the commitment and the message. An adversary who tries to reuse a response for a different message will derive a different and fail verification.
Phase 3: Response
Given the challenge , the signer computes:
Because and , it follows that — the response links back to the original commitment. The signer also computes the hint (Phase 4 below).
Phase 4: The hint
The hint solves a practical problem: the verifier must reconstruct from the signature and compressed public key, but two sources of error make naive rounding unreliable.
Problem 1 — Compressed public key. The public key stores , not full . The low-order bits of each coefficient were dropped during key generation FIPS204, §6.1. The verifier computes:
This is close to but not exact — the missing bits of introduce small errors.
Problem 2 — Rounding ambiguity. records which bucket each coefficient falls in (multiples of ). When a value sits near a bucket boundary, a small error from Problem 1 can push it into the adjacent bucket — giving the verifier the wrong and causing the hash check to fail.
The fix. For each coefficient, the signer records a single hint bit:
During verification, UseHint adjusts the bucket index at every position where , correcting the rounding FIPS204, Algorithms 39–40. The hint is a binary vector in (one bit per coefficient across the polynomials). At most bits are set to 1 per signature ( for ML-DSA-44, for ML-DSA-65, for ML-DSA-87) FIPS204, Table 1.
Analogy: Suppose you round several measurements to the nearest integer, but your colleague only sees noisy versions. For measurements near or , they might round the wrong way. The hint is a list of flags saying "I rounded up here" or "I rounded down here" — just for the ambiguous cases.
Phase 5: Rejection sampling
If any check fails, the signer aborts and restarts from Phase 1 with a fresh . There are two stages FIPS204, Algorithm 7.
Stage 1 — abort if either norm is too large:
where
Stage 2 — abort if either condition holds:
Here is the Hamming weight of the hint vector (the number of coefficients with hint bit set to 1).
Expected number of restarts: 4.25 (ML-DSA-44), 5.1 (ML-DSA-65), 3.85 (ML-DSA-87) FIPS204, Table 1.
Why Stage 1 checks z. The response is a shift of the mask :
Without rejection, that shift would push coefficients across the boundary of y's original range, and the published distribution of z would leak s₁.
Why Stage 1 checks r₀. The same boundary argument applies to s₂. After the challenge is known, the signer forms
The term c·s₂ shifts Ay. Coefficients that already sit near a rounding-bucket edge can be pushed across the boundary of the accepted low-bit region. If those overflows were published, an observer could tell which way the shift went — and that leaks s₂. Rejection sampling keeps only those r₀ that remain strictly inside the safe interior, so the observed low bits no longer depend on s₂.
Why Stage 2 checks ‖c · t₀‖∞. These checks are not the same as the r₀ rejection: they control how the verifier reconstructs high bits from a compressed public key. The public vector is decomposed as
The verifier receives only t₁. Using w = Ay and z = y + c·s₁,
The verifier cannot form this exactly: it computes
so the missing term is c·t₀. That correction must stay small. If
it can cross too many rounding-bucket boundaries, and both the number and the locations of hints start to depend on the secret-dependent t₀. Requiring
keeps the correction inside a single-bucket range.
Why Stage 2 checks wt(h). The hint h is part of the signature, so it is public. A bit hᵢ = 1 means: for this coefficient, use the neighboring high-bit value — a rounding boundary was crossed. If wt(h) > ω, the signature would reveal too much about that rounding behavior. ML-DSA therefore caps the number of 1-bits at a public bound ω and restarts when the cap is exceeded.
Phase 6: Output
The signature is the triple — the commitment hash, response, and hint. The sparse challenge is not stored; the verifier re-derives it from .
Verification
Given public key , message , and signature :
- Recompute and derive .
- Reconstruct the approximate commitment:
- Apply the hint:
- Recompute .
- Accept if and .
ML-DSA in a Glance
Why Rejection Sampling and Bit Dropping?
FIPS204, §3.3, p. 10 explains both design choices by starting with a simplified interactive protocol. Understanding this toy version makes the full ML-DSA signing flow much clearer.
The basic interactive protocol
Imagine a prover who knows short secret matrices and , and a verifier who knows and . The prover wants to convince the verifier that it knows and without revealing them:
- Commitment. The prover picks random short vectors and and sends:
- Challenge. The verifier sends a short challenge vector .
- Response. The prover returns . The verifier checks that is short and that:
ML-DSA is this protocol made non-interactive (the challenge comes from a hash) and compressed (polynomial modules instead of big integer matrices). But the logic is the same.
A scalar example: why rejection sampling is needed
FIPS204, §3.3 states the flaw directly: without rejection sampling, the response is biased in a direction related to . The same is true of , which is biased in a direction related to . The problem is not that an adversary can algebraically solve for the secret — the coefficients are small and the random masks dominate. The problem is that the probability distribution of published values shifts toward the secret, especially when a shift crosses the boundary of the accepted region.
Strip everything down to one dimension. Suppose the secret is , the challenge is , and the prover picks a random mask uniformly from . The response is:
If there were no secret (), would be uniform on . With , the distribution of is uniform on — the same shape, but shifted three units to the right. An adversary who collects many responses does not need to solve a linear system; it can detect that the histogram of values is centered around a value that depends on rather than looking like uniform random noise.
In the full scheme, varies per signature (derived from a hash), so the shift direction changes. But the distribution of is still correlated with — the outputs cluster in a way that reveals information about the secret direction.
The same leak exists for , through the low bits of the shifted commitment FIPS204, §3.3:
Here is shifted by . Coefficients near a rounding-bucket edge can be pushed across the boundary of the accepted region
Those crossings are not random: they happen in the direction of . Publishing them would leak .
Rejection sampling fixes both leaks. As with Schnorr signatures, the signer derives the challenge from a hash of the commitment and message. It then aborts and restarts with a fresh unless both and lie strictly inside their accepted regions (the Stage 1 checks in Phase 5). Only those transcripts are published, so their distribution is independent of and .
A scalar example: why lower-order bits are removed
The bit-dropping optimizations serve a different goal: smaller keys and signatures, not security against bias.
Suppose a commitment coefficient is . ML-DSA does not store or transmit this full value. Instead it divides the number line into buckets of width and records only the bucket index:
Only (a few bits) goes into the hash that forms the challenge. The lower part (roughly 10 bits) is discarded. The signature stores a hash of , not the full — saving hundreds of bytes across all coefficients.
The public key uses the same trick. If has a coefficient , ML-DSA drops the low-order bits and publishes only the high part . The verifier works with this compressed value and the hint (Phase 4 above) to recover the correct rounding when the approximation is ambiguous.
| What | Full value | Stored | Bits saved |
|---|---|---|---|
| Commitment coefficient | (bucket index) | ~10 low-order bits per coefficient | |
| Public key coefficient | (top bits) | 13 low-order bits per coefficient |
Without bit dropping, ML-DSA signatures and public keys would be substantially larger. Without hints, the verifier could not reliably recover the signer's bucket choices from the compressed values. Without rejection sampling, the responses would leak the secret key. All three mechanisms work together FIPS204, §3.3.
Internal Hash Functions
FIPS204 specifies two hash functions used throughout the scheme:
- SHAKE-128: expands seeds to generate the matrix and masking vector
- SHAKE-256: hashes the public key, forms the message representative , and computes the commitment hash
Security Assumptions
ML-DSA's security reduces to:
- Module-LWE: Given and for short secrets, distinguish from uniform.
- Module-SIS: Find a short nonzero vector such that .
Both are believed hard against classical and quantum adversaries. No significant cryptanalytic breaks have been found against the full parameter sets since Dilithium's introduction in 2017.
Threshold ML-DSA
Standard ML-DSA assumes a single party holds the secret key. Threshold signing distributes that key across parties so that any of them can collaborate to produce a valid signature, but fewer than learn nothing about the key.
This is harder for ML-DSA than for classical schemes because:
- The secret is a vector of short polynomials, not a scalar
- Rejection sampling must be coordinated across parties without leaking shares
- The Fiat–Shamir structure requires secure multi-party computation for the response step
Threshold ML-DSA is an active research and engineering area — relevant for custodial wallets, HSM clusters, and any system where single-key compromise is unacceptable. We will discuss about the recent advancements in Threshold ML-DSA in the next blog.
References
FIPS204
National Institute of Standards and Technology. Module-Lattice-Based Digital Signature Standard (FIPS 204) (2024)
NISTCAT
Marin Ivezic. NIST PQC Security Strength Categories (1–5) Explained (2025)