PQC · Lecture 3 · Florida Atlantic University · Fall 2026
Lecture 3: ML-KEM and ML-DSA
ML-KEM hides one message bit per coefficient under noise that only the secret key can remove. ML-DSA is a Schnorr signature built from lattices, with high bits and rejection sampling. Both start here as toy versions with every number shown.
1088bytes in an ML-KEM-768 ciphertext
2−165decryption failure rate of ML-KEM-768
2420bytes in an ML-DSA-44 signature
4.25average signing tries in ML-DSA-44
We have MLWE and fast multiplication. How do we exchange a key and sign a message with them?
Key encapsulation
A KEM instead of Diffie-Hellman
- Diffie-Hellman: both sides send a share and combine them. A KEM (key encapsulation mechanism) works differently: Bob picks the key and sends it to Alice, locked with her public key.
- Three algorithms: KeyGen, Encaps, Decaps. Sizes shown for ML-KEM-768.
ML-KEM is already in use
- TLS 1.3 in current browsers and at Cloudflare uses a hybrid: X25519 (ECDH) and ML-KEM-768 together. The session key mixes both, so an attacker must break both.
| Bytes on the wire | client server | server client |
|---|---|---|
| X25519 alone | 32 | 32 |
| ML-KEM-768 alone | 1184 | 1088 |
| hybrid X25519MLKEM768 | 1216 | 1120 |
- More than 30 times the data of ECDH alone, about 2.3 KB per handshake instead of 64 bytes.
Toy Kyber
Key generation: the Lecture 1 example
- KeyGen is an MLWE instance: public key , secret key . We reuse the toy instance from Lecture 1 (, , ):
- Alice publishes and , keeps , and throws away.
Encryption
- Bob picks fresh small values and computes two things:
- is a new MLWE sample, so it hides . hides the message under , which looks random.
Encryption with numbers
- Message . Bob adds each bit as 0 or 9 (about ), so .
Bob's small random values
Ciphertext
- Every product is a ring multiplication from Lecture 1, done with the NTT from Lecture 2 in real code.
- Bob sends : 12 numbers that look random. The message bits are not visible in .
Decryption with numbers
Alice computes
round: , , ,
Why decryption works
- Use and expand:
In our example
noise ,
- The big term cancels only for the owner of . Everyone else sees random-looking numbers.
When decryption fails
- The noise is a sum of many small products. With there is almost no room:
| Scheme | Room for noise () | Chance a decryption fails |
|---|---|---|
| our toy, | 4 | 54% to 66% (depends on the message) |
| ML-KEM-512 | 832 | |
| ML-KEM-768 | 832 | |
| ML-KEM-1024 | 832 |
- We picked the toy values by hand so that our example works.
- leaves enough room that a failure is never expected in practice.
Try it
Toy Kyber, live
The key is the Lecture 1 instance. Flip message bits, draw new random r, e1, e2, or run many encryptions to see how often the toy fails.
From toy Kyber to ML-KEM
What changes
| Part | Toy | ML-KEM-768 (FIPS 203) |
|---|---|---|
| ring | , , | , , |
| matrix | written out | expanded from a 32-byte seed |
| small values | chosen by hand | sampled from a binomial distribution |
| multiplication | schoolbook | NTT, 7 stages |
| ciphertext | full numbers | compressed |
| use | encrypt a message | KEM with a re-encryption check |
- The core, and , stays exactly the same.
A seed instead of a matrix
- is polynomials of 256 coefficients, 12 bits each: 3,456 bytes.
- Instead, Alice publishes a random 32-byte seed . Both sides feed it into SHAKE128 and read the coefficients of from the output.
Two details
- SHAKE128 gives 12-bit numbers. Values of 3329 or more are skipped, so every coefficient is uniform mod .
- is random anyway, so it is read directly as NTT values (Lecture 2) and never transformed.
- The public key becomes : bytes.
Small values from coin flips
- ML-KEM-768 needs coefficients between and , with small values more likely.
- Centred binomial distribution with : flip 4 random bits and output .
| value | |||||
| probability |
- Example: bits give .
- The bits come from SHAKE256 of a secret 32-byte seed, so the whole secret key can be regenerated from 32 bytes.
Compression: fewer bits on the wire
- Decryption tolerates noise, so the ciphertext does not need all 12 bits per coefficient. Keep only the top bits:
(used for )
error 1
(used for )
error 81, still far below
- Ciphertext for ML-KEM-768: bits bytes, instead of 1536.
From encryption to a KEM
- Encaps: pick a random 32-byte (256 bits, one per coefficient). Hash it into the key and the randomness: . Encrypt using .
- Decaps: decrypt to get , then rebuild everything from and check:
- This re-encryption check is the Fujisaki-Okamoto (FO) transform.
Why the re-encryption check?
- Without it, an attacker can send Alice slightly changed ciphertexts and watch whether the session works.
- Each answer leaks a little about . Enough queries recover the whole key.
With the check
- A changed ciphertext never re-encrypts to itself, so it is always caught.
- Alice then returns a fake key from a secret seed . To the attacker it looks like any other key, so even the failure reveals nothing.
Where the NTT is used
| Step | Computation |
|---|---|
| KeyGen | (stays in NTT form) |
| Encrypt | , , |
| Decrypt |
- is the pointwise product of Lecture 2 (BaseCaseMultiply on 128 pairs).
- In optimized software, a large share of the time goes to SHAKE, the hashing of seeds.
ML-KEM parameter sets
| Set | ek | dk | ciphertext | security | |||
|---|---|---|---|---|---|---|---|
| ML-KEM-512 | 2 | 3 | (10, 4) | 800 | 1632 | 768 | 128-bit |
| ML-KEM-768 | 3 | 2 | (10, 4) | 1184 | 2400 | 1088 | 192-bit |
| ML-KEM-1024 | 4 | 2 | (11, 5) | 1568 | 3168 | 1568 | 256-bit |
| X25519 (ECDH) | 32 | 32 | 32 | 128-bit | |||
| RSA-2048 | 256 | 256 | 256 | 112-bit |
- Sizes in bytes. All three sets share and ; is for and . The shared key is always 32 bytes.
Signatures: from Schnorr to Dilithium
Recap: Schnorr signatures
The scheme
secret , public Sign: random , ,
,
Verify: ,
accept if
Numbers: ,
, ,
suppose gives
✓
- Same key as Alice in the Lecture 1 Diffie-Hellman example. Exponents work mod 22, the order of .
The same three moves with lattices
| Schnorr | Dilithium (toy) | |
|---|---|---|
| secret | small | |
| public key | ||
| commit | ||
| challenge | , a small polynomial | |
| response | ||
| check |
- Two new problems: the check is only approximate (), and could leak .
Two problems, two fixes
Problem 1: the verifier gets , not
Fix: hash only the high bits of . A small change () leaves the high bits alone, unless sits near a boundary. The signer checks for that.
Problem 2: can reveal
is random in a fixed range. Values of near the edge of that range happen more often for one sign of than for the other.Fix: rejection sampling. Publish only if all its coefficients stay well inside the range. Otherwise start over with a new .
Toy Dilithium
Toy parameters and keys
- Signatures need more room than our KEM toy, so: , , and one polynomial per key part ().
Parameters
coefficients in ()high bits in steps of ()
has two coefficients ()
in ()
: the largest size of
Keys
High bits and low bits
- Write every coefficient as with . is the high part, the low part.
- Examples: , so . , so .
- If , adding anything of size at most keeps in the same block.
Signing
- "z small": every coefficient of below in size.
- "low bits safe": every coefficient of below in size.
Signing with numbers: try 1
Message = "hello"
,
our toy gives ,
Rejected
. A value this close to the edge could leak the sign of . Throw everything away and draw a new .
- Nothing from a rejected try is ever published.
Signing with numbers: try 2
New
,
, ,
Both checks pass
: all coefficients below 14 in size ✓, low bits : all below 6 in size ✓
Signature:
Verification
With numbers
, , , so ✓
Why verification works
- Numbers: and differ by .
- The signer already checked that the low bits of are safe, so its high bits equal those of . The verifier gets the same and the same .
- A forger without cannot find a small that passes. That would solve a hard lattice problem close to MLWE.
How many tries?
- Each try passes both checks with some fixed probability, so the number of tries is random.
| Scheme | Average tries per signature |
|---|---|
| our toy | about 9 |
| ML-DSA-44 | 4.25 |
| ML-DSA-65 | 5.1 |
| ML-DSA-87 | 3.85 |
- Signing time varies from one signature to the next. Verification always runs once.
Try it
Toy Dilithium signer
Each click signs the message with fresh random y and shows every try, accepted or rejected, then verifies the signature. Same keys and toy hash as the slides.
From toy Dilithium to ML-DSA
What changes
| Part | Toy | ML-DSA-44 (FIPS 204) |
|---|---|---|
| ring | , | , , full NTT |
| sizes | is , from a 32-byte seed | |
| challenge | two | 39 coefficients (SampleInBall, SHAKE256) |
| bounds | , | , |
| public key | full | only the high part , plus hints |
| message |
- The sign and verify logic is the same as in the toy.
Shrinking the public key with hints
- has 23-bit coefficients. ML-DSA splits and publishes only (10 bits each).
- ML-DSA-44 public key: bytes, instead of about 2,976.
The cost
The verifier now computes , a little further from .The signer adds hint bits that say which high bits to move by one. At most hints are allowed.
Deterministic or hedged signing
- The random comes from SHAKE256 of the secret key, the message and a 32-byte value
rnd.
Hedged (default)
rnd = 32 fresh random bytessame message, different signature each time
harder to attack with side channels and faults
Deterministic
rnd = 32 zero bytessame message, same signature
for devices without a good random number generator
ML-DSA parameter sets and sizes
| Scheme | public key | secret key | signature | |||
|---|---|---|---|---|---|---|
| ML-DSA-44 | (4, 4) | 2 | 39 | 1312 | 2560 | 2420 |
| ML-DSA-65 | (6, 5) | 4 | 49 | 1952 | 4032 | 3309 |
| ML-DSA-87 | (8, 7) | 2 | 60 | 2592 | 4896 | 4627 |
| ECDSA P-256 | 64 | 32 | 64 | |||
| RSA-3072 | 384 | 384 | 384 | |||
| FN-DSA-512 (Falcon) | 897 | 666 | ||||
| SLH-DSA-128s | 32 | 64 | 7856 |
- Sizes in bytes. ML-DSA signatures are about 40 times larger than ECDSA, and signing and verifying are fast.
Summary
Summary
ML-KEM
- Key: an MLWE sample .
- Encrypt: hide under .
- Decrypt: , then round.
- KEM: random , hash to , re-encryption check.
ML-DSA
- Schnorr with lattices: commit, challenge, response.
- Hash only the high bits of .
- Reject any that could leak .
- Verify: .
Under both
Polynomials in (Lecture 1), multiplied with the NTT (Lecture 2), and the MLWE problem.
Thank you!