ML-DSA signature and key sizes

Short answer

ML-DSA-44: 2,420-byte signature, 1,312-byte public key.
ML-DSA-65: 3,309-byte signature, 1,952-byte public key.
ML-DSA-87: 4,627-byte signature, 2,592-byte public key.

An ML-DSA-65 signature is 52× the size of an Ed25519 signature (64 bytes), and 4,412 bytes once Base64-encoded.

Raw sizes (FIPS 204)

ML-DSA (Module-Lattice-Based Digital Signature Algorithm) is standardised in FIPS 204. All three parameter sets have fixed-size signatures, so these are exact values, not averages.

Parameter setSecurityPublic keyPrivate keySignaturevs. Ed25519 sig
ML-DSA-44NIST level 21,3122,5602,42038×
ML-DSA-65NIST level 31,9524,0323,30952×
ML-DSA-87NIST level 52,5924,8964,62772×
Ed25519classical3264641×
ECDSA P-256classical643264 (70–72 DER)1×
RSA-2048classical2561,1902564×

Bytes. ECDSA public key shown as raw x‖y; the private-key size for ML-DSA is the expanded FIPS 204 encoding (implementations may store a 32-byte seed instead).

Encoded signature sizes

Signatures are rarely stored raw. Base64 adds a third, hex doubles them, and a JWT adds its header and claims on top.

Parameter setRawHexBase64Base64urlDER BIT STRINGJWT (279 B claims)
ML-DSA-442,4204,8403,2283,2272,4253,507
ML-DSA-653,3096,6184,4124,4123,3144,692
ML-DSA-874,6279,2546,1726,1704,6326,450

What ML-DSA breaks

The same artifacts checked against limits commonly found in real stacks. Hard limits like the cookie cap and the B-tree tuple can't be raised by configuration.

LimitCarried asML-DSA-44ML-DSA-65ML-DSA-87
QUIC datagram1,200 B, raw+1,220+2,109+3,427
VARCHAR(255)Base64+2,973+4,157+5,917
VARCHAR(1024)Base64+2,204+3,388+5,148
PostgreSQL B-tree index2,704 B, Base64+524+1,708+3,468
Browser cookie4,096 B, as JWTfits+596+2,354
mbedTLS record buffer16,384 B, 3-cert chainfits+184+6,058

Overflow in bytes. Cookie and header JWTs assume 279 B of header and claims. TLS assumes three certificates with minimal X.509 metadata; real certificates are larger.

How big should the database column be?

Sized with Tokenkeep's column policy: the Base64 length plus 25% headroom, rounded up to a power of two. If you store raw bytes, use BYTEA / VARBINARY and skip Base64 entirely. Don't put a B-tree index on the signature itself: index a SHA-256 digest of it instead.

Parameter setBase64 signatureColumn for signatureBase64 public keyColumn for public key
ML-DSA-443,228VARCHAR(4096)1,752VARCHAR(4096)
ML-DSA-654,412VARCHAR(8192)2,604VARCHAR(4096)
ML-DSA-876,172VARCHAR(8192)3,456VARCHAR(8192)

ML-DSA vs. Dilithium sizes

ML-DSA is the standardised form of CRYSTALS-Dilithium. Public keys are unchanged, but the final standard changed the signature encoding: round-3 Dilithium3 signatures were 3,293 bytes and Dilithium5 4,595 bytes, against 3,309 and 4,627 for ML-DSA-65 and ML-DSA-87. If you sized columns or buffers from older Dilithium documentation, check them again.

Find every limit these sizes break in your stack

Paste a migration, an nginx.conf or an mbedTLS header into the checker, or ask us for a free scan of the whole repository.

Related