Note
A standalone browser demo for ML-KEM post-quantum key establishment, built on
leviathan-crypto. It runs entirely
client-side in one HTML file using MlKem512, MlKem768, MlKem1024,
HKDF_SHA256, and XChaCha20Poly1305. No server, no runtime dependencies.
ML-KEM (formerly Kyber) is a key encapsulation mechanism, not an encryption scheme. Two parties establish a shared secret across an asymmetric boundary without ever transmitting it. This demo makes that boundary visible.
The page simulates Alice and Bob as two browser-side clients. A wire at the top logs everything that crosses the channel in real time. The shared secret never appears in the wire. That absence is the whole point.
Alice runs keygen() and sends her encapsulation key. Bob encapsulates against
it, producing a ciphertext and a shared secret; he sends back the ciphertext.
Alice decapsulates the ciphertext with her private decapsulation key and
recovers the identical shared secret. The decapsulation key never leaves Alice,
and the shared secret never crosses the wire.
Both sides then derive a symmetric key from the shared secret with HKDF-SHA256 (RFC 5869, HMAC-based key derivation). Bidirectional messaging uses XChaCha20-Poly1305 with the sender label bound in as authenticated data. Every message frame in the wire is expandable, showing the nonce, ciphertext body, Poly1305 tag, and AAD.
Security levels 512, 768, and 1024 are selectable. Alice chooses before keygen, and the selection locks for the duration of the ceremony.
Requires Bun.
bun install
bun bake
# → dist/index.htmlOpen dist/index.html in any modern browser. No server required.
bake runs build.ts, which bundles leviathan.kyber-entry.ts with Bun, then
inlines that bundle, src/style.css, and src/app.js into src/template.html.
The entry file loads the mlkem, sha3, chacha20, and sha2 WASM modules
and re-exports the classes the page needs.
kyber/
├── src/
│ ├── template.html # page structure and markup
│ ├── style.css # all styles
│ └── app.js # ceremony logic and messaging
├── leviathan.kyber-entry.ts # leviathan-crypto bundle entry
├── build.ts # build script
└── dist/
└── index.html # built output
alice wire bob
───── ──── ───
keygen()
└─ encapsulationKey ─────────── ek → ──────────────────────► receive ek
decapsulationKey (never leaves alice) encapsulate(ek)
◄──────── ct ← ──────────────────────── ciphertext
decapsulate(dk, ct)
└─ sharedSecret ───────── [not transmitted] ────────────────── sharedSecret
HKDF(ss) → key HKDF(ss) → key
Important
The shared secret is never sent over the wire. Only the encapsulation key and the ciphertext cross the channel; both sides compute the secret independently.
- Forward boundary. The decapsulation key stays on Alice's side for the whole ceremony. Capturing the wire reveals the public key and ciphertext, neither of which recovers the secret.
- Key derivation. The raw ML-KEM shared secret is run through HKDF-SHA256 before use, producing a uniformly distributed, domain-separated message key.
- Message encryption. XChaCha20-Poly1305 uses a fresh random 24-byte nonce per message, with the sender label bound in as authenticated data.
- leviathan-crypto: the underlying library
- web demo, tamper demo, jwt demo, cli tool
- covcom: a production secure messenger built on the same library
- ML-KEM wiki: API reference
- XChaCha20-Poly1305 wiki: messaging cipher reference
- Live demo: leviathan.3xi.club/kyber
Leviathan and its demos are written under the MIT license.
▄▄▄▄▄▄▄▄▄▄
▄████████████████████▄▄
▄██████████████████████ ▀████▄
▄█████████▀▀▀ ▀███████▄▄███████▌
▐████████▀ ▄▄▄▄ ▀████████▀██▀█▌
████████ ███▀▀ ████▀ █▀ █▀
███████▌ ▀██▀ ██
███████ ▀███ ▀██ ▀█▄
▀██████ ▄▄██ ▀▀ ██▄
▀█████▄ ▄██▄ ▄▀▄▀
▀████▄ ▄██▄
▐████ ▐███
▄▄██████████ ▐███ ▄▄
▄██▀▀▀▀▀▀▀▀▀▀ ▄████ ▄██▀
▄▀ ▄▄█████████▄▄ ▀▀▀▀▀ ▄███
▄██████▀▀▀▀▀▀██████▄ ▀▄▄▄▄████▀
████▀ ▄▄▄▄▄▄▄ ▀████▄ ▀█████▀ ▄▄▄▄
█████▄▄█████▀▀▀▀▀▀▄ ▀███▄ ▄███▀
▀██████▀ ▀████▄▄▄████▀
▀█████▀