ML-DSA is the algorithm that replaces RSA and ECDSA for digital signatures. NIST standardised it in August 2024 as FIPS 204. It works, it is implemented, and the reason people find it painful has nothing to do with the mathematics: an ML-DSA signature is roughly fifty times larger than the ECDSA signature it replaces.
What a signature proves
Worth being precise, because signatures get confused with encryption constantly. A signature does not hide anything. The message stays readable to everyone. What a signature establishes is two things at once:
- Who sent it. Only the holder of the private key could have produced this signature.
- That it has not changed. Alter one byte of the message and verification fails.
You sign with the private key and anyone verifies with the public one, which is the mirror image of encryption, where anyone encrypts with the public key and only you decrypt. Try it in the playground: sign a message, then edit one character and verify again.
Where you are already relying on signatures
More places than most people realise, and this is the reason the migration is broad rather than confined to TLS.
- Every TLS certificate, and every certificate above it in the chain.
- Software updates, app stores, and signed packages. Your operating system checks a signature before installing anything.
- Secure boot, which checks a signature before your machine will run its own firmware.
- Container images, git commits, and build provenance.
- Every blockchain transaction and every Nostr event.
Why the signatures are so big
ML-DSA rests on the same family of lattice problems as ML-KEM. A signature is essentially a proof that you know a short secret vector satisfying a noisy equation, and expressing that proof takes a lot of coefficients. There is no clever encoding that makes it small: the size is the construction.
| Parameter set | NIST category | Public key | Signature |
|---|---|---|---|
| ML-DSA-44 | Category 2 | 1,312 B | 2,420 B |
| ML-DSA-65 | Category 3 | 1,952 B | 3,309 B |
| ML-DSA-87 | Category 5 | 2,592 B | 4,627 B |
ML-DSA-65 is the sensible default. ML-DSA-87 is what CNSA 2.0 requires for national security systems, and it is what @nostr-wot/pq uses for Nostr identity keys.
The size problem, concretely
| ECDSA P-256 | ML-DSA-65 | Change | |
|---|---|---|---|
| Public key | 64 B | 1,952 B | 30x |
| Signature | 64 B | 3,309 B | 52x |
Now apply that to a TLS handshake, which carries a certificate chain with a signature at every level. A chain that was a couple of kilobytes becomes tens of kilobytes. That is the real cost, and it is why post-quantum certificates are further behind post-quantum key exchange in actual deployment.
Check your buffers before you check your algorithms
Plenty of systems have a hard limit somewhere that nobody documented: a database column sized for a 64-byte signature, a firmware image with a fixed signature slot, a protocol field with a two-byte length. These fail at integration time, not at design time. Finding them is the first task of a signature migration, not the last.
ML-DSA or SLH-DSA
NIST standardised a second signature algorithm, SLH-DSA (FIPS 205), built on hash functions rather than lattices. It is far more conservative, resting only on assumptions about hash functions that cryptographers have trusted for decades, and its signatures are larger still, in the range of 8 to 30 kB.
Use ML-DSA by default. Reach for SLH-DSA where a key must remain trustworthy for decades and you cannot accept the risk that lattice cryptanalysis improves: firmware signing roots and long-lived trust anchors. ML-DSA vs SLH-DSA works through the choice properly.
Sign something yourself
OpenSSL 3.5 and later supports ML-DSA directly, so you can generate a key and sign a file from the command line. The playground does the same in a browser if you want to see it without installing anything, and it shows the signature size next to the message so the comparison against ECDSA is immediate.
The signatures on quantakrypto's own attestations are ML-DSA; verifying a quantakrypto attestation walks through checking one.