Benchmarks

Run benchmarks yourself with:

v run tools/bench.vsh

ML-KEM

OperationML-KEM-512ML-KEM-768ML-KEM-1024
KeyGen44 us55 us88 us
Encapsulate22 us28 us38 us
Decapsulate35 us46 us56 us
Encapsulate (w/ EK parsing)38 us67 us95 us

ML-DSA

OperationML-DSA-44ML-DSA-65ML-DSA-87
KeyGen156 us216 us426 us
Sign711 us483 us456 us
Sign (deterministic)581 us106 us144 us
Verify46 us47 us90 us
Verify (w/ PK parsing)138 us181 us293 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

ClassicalPost-Quantum EquivalentRatio
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:

v run tools/fuzz.vsh run mlkem
v run tools/fuzz.vsh run mldsa

You can target a specific harness with -t:

v run tools/fuzz.vsh run mlkem -t keygen

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.