Benchmarks
Run benchmarks yourself with:
ML-KEM
| Operation | ML-KEM-512 | ML-KEM-768 | ML-KEM-1024 |
|---|---|---|---|
| KeyGen | 44 us | 55 us | 88 us |
| Encapsulate | 22 us | 28 us | 38 us |
| Decapsulate | 35 us | 46 us | 56 us |
| Encapsulate (w/ EK parsing) | 38 us | 67 us | 95 us |
ML-DSA
| Operation | ML-DSA-44 | ML-DSA-65 | ML-DSA-87 |
|---|---|---|---|
| KeyGen | 156 us | 216 us | 426 us |
| Sign | 711 us | 483 us | 456 us |
| Sign (deterministic) | 581 us | 106 us | 144 us |
| Verify | 46 us | 47 us | 90 us |
| Verify (w/ PK parsing) | 138 us | 181 us | 293 us |
Signing times vary between runs because of rejection sampling (~5.22 attempts on average for ML-DSA-65). Measured on Intel Core Ultra 5 135U, V with -prod, single-threaded.
Higher security levels use larger matrices, so they're slower. Key parsing is often the bottleneck since deserializing a key means expanding the matrix via SHAKE. If you're reusing a key, parse it once and hold onto it. Under the hood, the NTT (Number Theoretic Transform) dominates everything; it's what makes polynomial multiplication tractable.
Sizes vs. classical
| Classical | Post-Quantum Equivalent | Ratio |
|---|---|---|
| X25519 (32 B) | ML-KEM-768 (1,184 B) | ~37x |
| Ed25519 sig (64 B) | ML-DSA-65 sig (3,309 B) | ~52x |
| Ed25519 PK (32 B) | ML-DSA-65 PK (1,952 B) | ~61x |
Post-quantum keys and signatures are significantly larger. Keep this in mind when designing wire formats.
Fuzzing
Veil ships fuzzing harnesses for all modules, using libFuzzer with ASan and UBSan:
You can target a specific harness with -t:
Available targets: keygen, sign/encaps, verify/decaps, parse_pk/parse_ek, parse_sk/parse_dk.
Corpus is packed into a single archive for version control. Use v run tools/fuzz.vsh pack mlkem and v run tools/fuzz.vsh unpack mlkem to manage it.