📖 Abbreviations & Definitions
▼| Term | Full Form / Definition | Ref |
|---|---|---|
| ML-DSA | Module Lattice-based Digital Signature Algorithm — NIST post-quantum standard (FIPS 204) | FIPS 204 |
| DSA | Digital Signature Algorithm — a cryptographic scheme to verify authenticity | FIPS 186-5 |
| PKI | Public Key Infrastructure — system managing public/private key pairs | NIST Glossary |
| SHAKE | Secure Hash Algorithm with Keccak Extendable-Output Function | FIPS 202 §6 |
| SHA3 | Secure Hash Algorithm 3 — family including SHAKE128, SHAKE256 | FIPS 202 |
| ρ (rho) | 32-byte seed used to generate public matrix A via SHAKE128 | FIPS 204 §5.1 |
| ρ′ (rho-prime) | 64-byte seed used to generate secret vectors s₁ and s₂ | FIPS 204 §5.1 |
| K | 32-byte key used for signing randomness | FIPS 204 §5.1 |
| A | Public matrix of polynomials — k×ℓ = 4×4 for ML-DSA-44 | FIPS 204 §4.2 |
| s₁, s₂ | Secret vectors with small coefficients (η=2), kept private | FIPS 204 §5.1 |
| t | Public key commitment: t = As₁ + s₂ | FIPS 204 §5.1 |
| t₀, t₁ | Low/high bits of t after Power2Round split | FIPS 204 §6 |
| pk | Public Key = ρ ∥ t₁ → 1312 bytes | FIPS 204 Table 1 |
| sk | Secret Key = ρ ∥ K ∥ tr ∥ s₁ ∥ s₂ ∥ t₀ → 2560 bytes | FIPS 204 Table 1 |
| η (eta) | Bound on secret coefficients — η=2 for ML-DSA-44 | FIPS 204 Table 1 |
| q | Prime modulus = 8,380,417 for all ML-DSA variants | FIPS 204 §2 |
📜 History of Digital Signatures
▼Before the internet, authenticating a document meant physically signing it. But how do you prove a digital file hasn't been changed — or that it really came from who it claims?
1976 — Diffie & Hellman introduced the concept of public key cryptography.
1978 — RSA was the first practical digital signature scheme. Security relies on the difficulty of factoring large numbers.
2016–2024 — Post-Quantum Era: Quantum computers threaten RSA and ECDSA. In 2024, ML-DSA (FIPS 204) became the new standard — based on hard lattice problems.
🔄 Key Generation Flow
▼📋 ML-DSA-44 Complete Flow — Plain Language Guide
▼1. Key Generation
First of all, a 32-byte random seed ξ is generated using a cryptographically secure random source provided by the operating system or hardware. This seed is then given to SHAKE256, which expands it into 128 bytes of deterministic output, split into three parts:
public seed → matrix A
secret seed → s₁, s₂
kept secret → used in signing
Matrix A is generated from ρ using ExpandA (SHAKE128 with row/column indices). For ML-DSA-44, A is a 4×4 matrix — 16 polynomials, each with 256 coefficients, giving 4096 coefficients total. Every coefficient is taken modulo q = 8,380,417, so values lie in [0, 8,380,416].
The two secret vectors s₁ and s₂ are generated from ρ′ using ExpandS (SHAKE256 + centered binomial distribution). With η = 2 for ML-DSA-44, coefficients are in {−2, −1, 0, 1, 2}. Each vector has 4 polynomials × 256 coefficients = 1024 coefficients.
Alice then calculates t = As₁ + s₂. Since A is 4×4 and s₁ is 4×1, the result is a 4×1 vector — four polynomials with 256 coefficients each (1024 coefficients total).
Storing every t coefficient uncompressed would need 23 bits each (since q < 2²³), giving 1024 × 23 / 8 = 2944 bytes — unnecessarily large. So ML-DSA uses Power2Round to split each coefficient:
t₁ holds the high bits (10 bits encoded per coefficient), t₀ holds the centered low bits (13 bits each). The public key is ρ ∥ t₁:
2. Private Key
The private key stores ρ, K, tr, s₁, s₂, and t₀. After generating the public key, Alice computes tr = H(pk) producing 64 bytes. The sizes break down as:
| Component | Size | Encoding |
|---|---|---|
| ρ | 32 bytes | raw |
| K | 32 bytes | raw |
| tr = H(pk) | 64 bytes | raw |
| s₁ (η=2, 3 bits/coeff) | 384 bytes | 1024 × 3 / 8 |
| s₂ (η=2, 3 bits/coeff) | 384 bytes | 1024 × 3 / 8 |
| t₀ (13 bits/coeff) | 1664 bytes | 1024 × 13 / 8 |
3. Signing Process
Alice wants to sign message M. She first computes the message representative μ:
Then she derives signing randomness ρ″ using K, a random 32-byte value rnd, and μ:
Using ρ″, she generates the masking vector y (ExpandMask) — 4 polynomials, 256 coefficients each, with coefficients in [−γ₁+1, γ₁] where γ₁ = 2¹⁷ = 131,072.
She computes w = A·y, then decomposes it into high bits w₁ and low bits w₀. She encodes w₁ and hashes to get the challenge seed:
Then SampleInBall converts c̃ into the challenge polynomial c: 256 coefficients, exactly 39 are ±1 (τ = 39 for ML-DSA-44), the other 217 are zero.
The response vector is computed as: z = y + c·s₁ — the masking y hides s₁ from any observer.
4. Creating the Hint
Alice computes the intermediate values c·s₂ and c·t₀, then forms:
h = MakeHint(−c·t₀, r)
MakeHint checks all 4 × 256 = 1024 coefficient positions. For each position, hᵢ = 1 if the relevant high bits change when the correction is applied, otherwise hᵢ = 0. ML-DSA-44 allows at most ω = 80 hint positions to be 1. The hint is encoded compactly as the positions where hᵢ = 1 plus 4 polynomial boundary values:
5. Final Signature
Alice assembles σ = (c̃ ∥ z ∥ h):
| Component | Size |
|---|---|
| c̃ (challenge seed) | 32 bytes |
| z (response vector) | 2304 bytes |
| h (hint bits) | 84 bytes |
6. How Bob Verifies the Signature
Bob receives M, pk, and σ = (c̃, z, h). He also has pk = (ρ, t₁). His steps:
- Recompute μ = H(tr ∥ M) using tr derived from pk.
- Run c = SampleInBall(c̃) — deterministic, produces the same c as Alice.
- Compute the approximate commitment: w′ = A·z − c·t₁·2d (d = 13). Bob does not need s₁, s₂, or t₀ — the public t₁ and the math make the secret terms cancel approximately.
- Apply the hint: w₁′ = UseHint(h, w′). Each hint bit tells Bob which coefficients need their high-bit value corrected to account for the t₁/t₀ split.
- Recompute the challenge seed: c̃′ = H(μ ∥ Encode(w₁′)).
- Compare c̃′ with the c̃ from the signature, and check all norm and validity bounds.
The verifier never needs the secret key. Even a quantum computer cannot forge this signature.
🌱 Seed ξ → SHAKE256 → (ρ, ρ′, K)
▼Key generation starts with a random 32-byte seed ξ. SHAKE256 expands it into 128 bytes total, split as: FIPS 204 §5.1 Alg.1
🧮 Matrix A — Generated via SHAKE128
▼Matrix A is 4×4 polynomials. Each polynomial has 256 coefficients in [0, 8,380,416]. FIPS 204 §4.2
↑ Compact view. See Main Panel → for full column vectors.
🔑 Secret Vectors s₁ and s₂
▼Sampled from centered binomial distribution. All coefficients in [-2, 2]. FIPS 204 §5.1, η=2
🔢 t = A·s₁ + s₂
▼✂️ Power2Round → t₀ and t₁
▼Split each coefficient into high bits (t₁) and low bits (t₀). FIPS 204 §6 Power2Round
🔑 Public Key & Secret Key
▼Public Key (pk) = ρ ∥ t₁
Secret Key (sk) = ρ ∥ K ∥ tr ∥ s₁ ∥ s₂ ∥ t₀
✍️ Signing Process — Alice Signs a Message
▼Alice has generated her keys. Now she signs a message. The signature proves she used her secret key without revealing it.
Signing Flow — FIPS 204 §5.2
pk = ρ ∥ t₁ (1312 bytes of public key)
any length, UTF-8 encoded
binds signature to both pk and message
deterministic signing randomness
4 polys, coeffs ∈ [−γ₁+1, γ₁], γ₁=2¹⁷=131072
w₁ = HighBits(w, 2γ₂) | w₀ = LowBits(w, 2γ₂)
γ₂ = (q−1)/88 = 95232
Fiat-Shamir challenge hash
sparse poly: τ=39 nonzeros (±1), 217 zeros
‖c·s₁‖∞ ≤ τ·η = 39·2 = 78
REJECT → restart from ③ if too large
h = MakeHint(−c·t₀, w − c·s₂ + c·t₀)
hint marks where HighBits(r) ≠ HighBits(r+z)
Input Message
Enter the message Alice will sign:
① Compute μ = SHAKE256(tr ∥ M) FIPS 204 §5.2 Alg.2
▼μ (mu) is the message representative — a 64-byte hash binding the signature to both the message and the public key.
where tr = SHAKE256(pk)
② Generate Signing Randomness ρ″ FIPS 204 §5.2 Alg.2
▼ρ″ is a 64-byte deterministic seed for the masking vector y. Derived from K and μ — no weak RNG risk.
③ Sample Masking Vector y FIPS 204 §5.2, γ₁=2¹⁷
▼4 polynomials, each with 256 coefficients in [−γ₁+1, γ₁] = [−131071, 131072]. Acts as a one-time pad to hide s₁.
④ Compute w = A·y → w₁ = HighBits(w) FIPS 204 §6 HighBits
▼w₁[i] = HighBits(w[i], γ₂)
⑤ Compute Challenge Hash c̃ = SHAKE256(μ ∥ w₁) FIPS 204 §5.2
▼Fiat-Shamir transform — turns an interactive proof into a non-interactive signature. The challenge is derived from the transcript, not sent by a verifier.
⑥ Sample Challenge Polynomial c = SampleInBall(c̃) FIPS 204 §6 Alg.7, τ=39
▼Sparse polynomial — exactly 39 non-zero coefficients (±1), 217 zeros. Per FIPS 204 Table 1, τ=39 for ML-DSA-44.
→ 256 coeffs: 39 are ±1, 217 are 0
⑦ Compute Response z = y + c·s₁ FIPS 204 §5.2 Alg.2
▼The response vector. The masking y hides s₁ — even though z is public, s₁ cannot be extracted from it.
⑧ Check Bounds — Reject if Needed FIPS 204 §5.2, γ₁-β
▼⑨ Compute Hint Bits h = MakeHint(...) FIPS 204 §6 Alg.8
▼Sparse bit vector helping the verifier correct rounding errors. At most 80 hints across 4 polynomials.
⑩ Assemble Signature σ = (c̃ ∥ z ∥ h) FIPS 204 §5.2, 2420 bytes
▼What the Verifier Does
- Recompute μ = SHAKE256(tr ∥ M) using pk
- Extract c̃, z, h from σ
- Recompute c = SampleInBall(c̃)
- Compute w' = A·z - c·t₁·2^d
- Use hints h to correct w' → get w₁'
- Recompute c̃' = SHAKE256(μ ∥ w₁')
- Check if c̃' = c̃ → valid ✓
The verifier never needs the secret key!
🔍 Bob's Verification Flow FIPS 204 §5.3
▼Bob receives (M, pk, σ) where the signature σ = (c̃, z, h). He uses only Alice's public key — never the secret key.
Verification Flow — FIPS 204 §5.3 Alg.3
tr = SHAKE256(pk) derived from pk
received from Alice
received from Alice
same as Alice's step ①
deterministic — same c̃ → same c
d=13, uses only public A, z, c, t₁
corrects rounding with hint bits h
⑪ Bob Recomputes μ = SHAKE256(tr ∥ M) FIPS 204 §5.3
▼Bob derives tr from Alice's public key, then hashes it together with M — exactly as Alice did in signing step ①. No secret information is used.
where tr = SHAKE256(pk), pk = ρ ∥ t₁
⑫ Bob Reconstructs c = SampleInBall(c̃) FIPS 204 §6 Alg.7
▼Bob takes c̃ directly from the signature σ and runs SampleInBall — the same deterministic function Alice used. Since c̃ is identical, the resulting c polynomial is identical.
c̃ extracted from σ = (c̃ ∥ z ∥ h)
⑬ Bob Computes w′ = A·z − c·t₁·2d FIPS 204 §5.3
▼This is the key step. Bob approximates Alice's original commitment w using only public values — he never needs s₁, s₂, or t₀.
Why this works: z = y + c·s₁, so A·z = A·y + c·A·s₁ = w + c·(t − s₂). Since t = t₁·2d + t₀, substituting gives w′ ≈ w up to the small t₀ term corrected by hints.
⑭ Bob Applies Hints: w₁′ = UseHint(h, w′) FIPS 204 §6 Alg.12
▼Because t was split into t₁ and t₀, some high-bit information in w′ may be off by one. The hint bits h — packed into 84 bytes in σ — tell Bob exactly which positions to correct.
if hᵢ = 1 and r₀ > 0: r₁ = (r₁ + 1) mod m
if hᵢ = 1 and r₀ ≤ 0: r₁ = (r₁ − 1) mod m
m = (q−1)/(2γ₂) = 88 for ML-DSA-44
⑮ Bob Recomputes c̃′ = SHAKE256(μ ∥ w₁′) FIPS 204 §5.3
▼Bob recomputes the challenge seed using his recovered μ and w₁′. If the signature is genuine, this will exactly equal the c̃ Alice placed in σ.
⑯ Final Verdict — Accept or Reject FIPS 204 §5.3
▼① c̃′ = c̃ (challenge seeds match)
② ‖z‖∞ < γ₁ − β = 130,994
📊 Main Visualization Panel
connecting...Live column vectors from backend API. Each matrix scrolls horizontally ←→ and vertically ↑↓
🌱 Seed Expansion
▼🧮 Matrix A — 16 Column Vectors
▼← Scroll horizontally → Each column = 256 coefficients
🔑 Secret Vectors s₁ and s₂
▼🔢 t = As₁ + s₂ and Power2Round
▼📦 Generated Keys
▼✍️ Signing — Started
▼① μ = SHAKE256(tr ∥ message) — 64 bytes
▼② ρ″ — 64 bytes signing randomness
▼③ y — masking vector (4 column vectors, γ₁=131072)
▼④ w = A·y, then w₁ = HighBits(w), w₀ = LowBits(w)
▼⑤ c̃ = SHAKE256(μ ∥ w₁) — 32 bytes
▼⑥ c = SampleInBall(c̃, τ=39) — sparse challenge polynomial
▼⑦ c·s₁ then z = y + c·s₁
▼⑧ Bound check ‖z‖∞ < γ₁ − β
▼⑨ Hint computation — MakeHint per FIPS 204 §6 Alg.8
▼where z = −c·t₀, r = w − c·s₂