Lab
—Experiment

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.

Mode
m
01Pipeline

Expand matrix A · 01 / 13

Key generation

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

ÂNTT

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)

02Workspace
live state · brand = just written

Values accumulate as you advance. Older slots fade; the current step's writes stay highlighted - scrub back to see an earlier snapshot.

ÂNTT

Public matrix

2×2 polys (NTT)

03Variable guide

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).
04All steps
Key generation
Encryption
Decryption
Result
Outcome

Message recovered

m = [1, 0, 1, 1] → m′ = [1, 0, 1, 1]

Companion post: How ML-KEM Works.