Mini ML-KEM
Watch the state fill and mutate: each step writes new values into a shared workspace. Polynomials have 4 coefficients modulo 41.
Mini educational demo only. Parameters n=4, q=41 are not secure and are not ML-KEM.
Expand matrix A · 01 / 13
Expand matrix A
01
PhaseBuild the public matrix  and the noisy public key t̂ from a secret s. Only the owner keeps s.
We need a public matrix  for the key equations, but shipping every coefficient would be huge. Instead we grow  from a small public seed ρ so anyone with ρ rebuilds the same matrix.
ThenNext we sample the secret s and the noise e that hide that secret.
Before
Public matrix seed. A public seed derived from d. Anyone can expand it into the big matrix Â, so we never need to send  itself.
2654435731
Op
ρ = expand(d)
for i in range(k):
for j in range(k):
Â[i][j] = sample_ntt(ρ + bytes([j, i]))After
Public matrix. The shared public grid of numbers everyone can rebuild from ρ. Encryption and keygen both use it as the public linear system.
2×2 polys (NTT)
Values accumulate as you advance. Older slots fade; the current step's writes stay highlighted - scrub back to see an earlier snapshot.
Public matrix
2×2 polys (NTT)
Plain-language meanings for every name that appears in the pipeline. Entries already in your workspace are marked live.
Seeds and randomness
- d
- Key-generation seed. The random starting number used to build the key pair. Same d always produces the same keys.
- ρ
- Public matrix seed. A public seed derived from d. Anyone can expand it into the big matrix Â, so we never need to send  itself.
- σ
- Noise seed. A private-side seed derived from d. It drives the random draws that produce the secret s and the noise e.
- r
- Encryption coins. Fresh randomness for one encryption. It draws y and the small errors so each ciphertext looks different.
Keys
- Âlive
- Public matrix. The shared public grid of numbers everyone can rebuild from ρ. Encryption and keygen both use it as the public linear system.
- s
- Secret key. The owner's private value. Knowing s is what lets you decrypt; outsiders only see noisy clues about it.
- e
- Keygen noise. Small deliberate noise added when publishing the public key, so nobody can solve for s with plain algebra.
- ŝ
- Secret in fast form. The same secret s, rewritten so multiplying by  is cheap. Still private.
- ê
- Noise in fast form. The keygen noise e in the same fast form as Â. Used once to build the public key, then discarded.
- Â∘ŝ
- Clean public product. What  · s would look like before adding noise. Shown so you can see how e hides the secret.
- t̂
- Public key. What everyone encrypts to: a noisy version of  · s. Safe to publish; hard to turn back into s.
Encryption
- m
- Message bits. The short bit string being encrypted (or, in full ML-KEM, the random bits that seed the shared secret).
- y
- Encryption mixer. Fresh short randomness that picks a random mix of the public equations for this ciphertext alone.
- ŷ
- Mixer in fast form. y rewritten for cheap multiply with  and t̂. Same value, different packaging.
- e₁
- Noise on u. Small noise added to the first ciphertext half so u is not an exact equation an attacker could reverse.
- e₂
- Noise on v. Small noise added on top of the message half for the same reason.
- μ(m)
- Message as numbers. Each bit rewritten as 0 or roughly half of q, so bit 0 and bit 1 sit far apart and survive a little noise.
- NTT⁻¹(Âᵀ∘ŷ)
- Shared mix before noise. The public mix of  with y, before adding e₁. This is the shared term that also appears (via t̂) in v.
- u
- Ciphertext half (no message). First half of the ciphertext: a noisy random mix of the public matrix. It does not carry m; it helps the owner cancel noise later.
- NTT⁻¹(t̂ᵀ∘ŷ)
- Shared cover term. The noisy cover that sits under the message in v. Only someone with s can estimate and remove it.
- v
- Ciphertext half (with message). Second half of the ciphertext: the message numbers sitting on top of a noisy cover. Outsiders see mush; the owner can peel the cover.
- Compress(u)
- Packed u. u shrunk to fewer bits per number so it is cheaper to send. A little precision is thrown away on purpose.
- Compress(v)
- Packed v. v shrunk the same way. Together with Compress(u) this is the ciphertext on the wire.
Decryption
- u′
- Unpacked u. Compress(u) expanded back toward full-size numbers. Close to the original u, not identical.
- v′
- Unpacked v. Compress(v) expanded the same way. This is what decryption subtracts the cover from.
- NTT⁻¹(ŝᵀ∘NTT(u′))
- Estimated cover. Using the secret, an estimate of the noisy cover that was added into v. Subtracting this reveals the fuzzy message.
- w
- Fuzzy message. What is left after removing the cover: each slot is near 0 or near half of q, plus leftover noise.
- m′
- Recovered bits. The bits after rounding w. In a good run this equals m; in full ML-KEM it is also re-encrypted to check integrity.
Checks
- match
- Keys agree?. True when sender and receiver hold the same session key (and, in the inner demo, when m′ equals m).
Message recovered
m = [1, 0, 1, 1] → m′ = [1, 0, 1, 1]
Companion post: How ML-KEM Works.