Skip to content

Commit 9404413

Browse files
committed
refactor(mlkem)!: rename kyber module to FIPS 203 ML-KEM name
Renames the Kyber module to ML-KEM throughout the codebase to match the official FIPS 203 (Module-Lattice-Based Key-Encapsulation Mechanism Standard) name. NIST published the final standard as ML-KEM, not Kyber; this commit aligns the codebase with that name. The Rust verifier already shipped under the mlkem name, so this brings the TS and AssemblyScript layers into line. Module paths. src/asm/kyber/ → src/asm/mlkem/ (12 files) src/ts/kyber/ → src/ts/mlkem/ (8 files) test/unit/kyber/ → test/unit/mlkem/ (10 files) test/e2e/kyber_suite.spec.ts → test/e2e/mlkem_suite.spec.ts test/vectors/kyber*.ts → test/vectors/mlkem*.ts (3 files) docs/kyber.md → docs/mlkem.md docs/asm_kyber.md → docs/asm_mlkem.md .github/workflows/unit-kyber.yml → unit-mlkem.yml **Breaking.** The consumer-facing subpath renames from `leviathan-crypto/kyber` to `leviathan-crypto/mlkem`. Existing imports break and need updating. The package.json `exports` map moves correspondingly.
1 parent c3cc470 commit 9404413

108 files changed

Lines changed: 683 additions & 657 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/test-suite.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,9 @@ jobs:
6464
with:
6565
bun-version: *bun_version
6666

67-
unit-kyber:
67+
unit-mlkem:
6868
needs: [build]
69-
uses: ./.github/workflows/unit-kyber.yml
69+
uses: ./.github/workflows/unit-mlkem.yml
7070
with:
7171
bun-version: *bun_version
7272

@@ -202,7 +202,7 @@ jobs:
202202
- unit-serpent
203203
- unit-chacha20
204204
- unit-stream
205-
- unit-kyber
205+
- unit-mlkem
206206
- unit-hashing
207207
- unit-montecarlo-cbc
208208
- unit-montecarlo-ecb
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: unit-kyber
1+
name: unit-mlkem
22
on:
33
workflow_call:
44
inputs:
@@ -21,5 +21,5 @@ jobs:
2121
bun-version: ${{ inputs.bun-version }}
2222
- name: install dependencies
2323
run: bun i
24-
- name: run kyber unit tests
25-
run: bun scripts/test.ts unit:group kyber
24+
- name: run mlkem unit tests
25+
run: bun scripts/test.ts unit:group mlkem

AGENTS.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ Decisions already made. Don't relitigate without raising it first.
194194
layout, AS entry point. `cte.wasm` is the internal SIMD constant-time
195195
equality module backing the TS `constantTimeEqual`. `cte/shared.ts`
196196
is the AS source-level `ctEqual` inlined by other modules' compile
197-
units (kyber, slhdsa, curve25519, p256) for in-WASM byte equality;
197+
units (mlkem, slhdsa, curve25519, p256) for in-WASM byte equality;
198198
never emitted as a WASM export from any consumer binary.
199199
- **Static buffers only**: no dynamic allocation (`memory.grow()`
200200
unused). All buffers are fixed offsets in linear memory defined in
@@ -286,7 +286,7 @@ Playwright discovers e2e specs automatically; no CI change needed for
286286
e2e. For unit tests:
287287

288288
- Find the `UNIT_GROUPS` entry in `scripts/lib/test-groups.ts` whose
289-
`name` matches the test family (`aes`, `mldsa`, `kyber`, etc.). When
289+
`name` matches the test family (`aes`, `mldsa`, `mlkem`, etc.). When
290290
unsure: `grep -l "test/unit/family" scripts/lib/test-groups.ts`.
291291
- Add or remove the test file path from that group's `files` array.
292292
- No workflow file edit is needed when adding a test to an existing

CHANGELOG

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@
1111
from `(masterKey, nonce, INFO || header)`, bytes 0..32 are the per-stream
1212
AES-GCM-SIV key, bytes 32..64 are a 32-byte commitment verified in
1313
constant time. Works with `Seal`, `SealStream`, `OpenStream`,
14-
`SealStreamPool`, and `KyberSuite`. The standalone `AESGCMSIV` primitive
14+
`SealStreamPool`, and `MlKemSuite`. The standalone `AESGCMSIV` primitive
1515
still supports both AES-128 and AES-256.
1616

17-
- **`KyberSuite` AES variants.** `KyberSuite(MlKem512/768/1024,
17+
- **`MlKemSuite` AES variants.** `MlKemSuite(MlKem512/768/1024,
1818
AESGCMSIVCipher)` produces `formatEnum` 0x14, 0x24, 0x34 with preambles
1919
820 / 1140 / 1620 bytes, identical to the XChaCha20 KEM variants.
2020

@@ -345,17 +345,17 @@
345345
| `_chachaReady()` | `isInitialized('chacha20')` |
346346
| `_sha2Ready()` | `isInitialized('sha2')` |
347347
| `_sha3Ready()` | `isInitialized('sha3')` |
348-
| `_kyberReady()` | `isInitialized('kyber')` |
348+
| `_kyberReady()` | `isInitialized('mlkem')` |
349349

350350
Update the import: `import { isInitialized } from 'leviathan-crypto'`.
351351

352352
One semantic narrowing: `_kyberReady()` previously checked **both** the
353-
`kyber` and `sha3` modules. The replacement `isInitialized('kyber')`
354-
checks only the `kyber` module. If the combined check matters, do
355-
`isInitialized('kyber') && isInitialized('sha3')`.
353+
`mlkem` and `sha3` modules. The replacement `isInitialized('mlkem')`
354+
checks only the `mlkem` module. If the combined check matters, do
355+
`isInitialized('mlkem') && isInitialized('sha3')`.
356356

357357
- **`isInitialized(mod)` now re-exported from every submodule subpath.**
358-
Previously only the root barrel and the kyber subpath re-exported it.
358+
Previously only the root barrel and the mlkem subpath re-exported it.
359359
Purely additive on the seven other submodule subpaths.
360360

361361
- **`CT_MAX_BYTES` renamed to `CTE_MAX_BYTES`.** The public const that
@@ -367,6 +367,32 @@
367367
`docs/asm_cte.md`. The TS function name `constantTimeEqual` and the
368368
WASM ABI are unchanged.
369369

370+
- **`kyber` renamed to `mlkem` everywhere.** Identifiers, file paths,
371+
subpath exports, init keys, CI handles, and feature-doc filenames now
372+
use the FIPS 203 standardized name. Wire format is unchanged: locked
373+
`formatEnum` bytes (0x12-0x34), KEM labels (`mlkem512`/`mlkem768`/
374+
`mlkem1024`), and all `MlKem*`/`MLKEM*` symbols were already correct.
375+
376+
| Old | New |
377+
|--- |--- |
378+
| `import from 'leviathan-crypto/kyber'` | `import from 'leviathan-crypto/mlkem'` |
379+
| `import from 'leviathan-crypto/kyber/embedded'` | `import from 'leviathan-crypto/mlkem/embedded'` |
380+
| `KyberSuite` | `MlKemSuite` |
381+
| `KyberParams` | `MlKemParams` |
382+
| `KyberExports` | `MlKemExports` |
383+
| `KyberKeyPair` | `MlKemKeyPair` |
384+
| `KyberEncapsulation` | `MlKemEncapsulation` |
385+
| `kyberInit(src)` | `mlkemInit(src)` |
386+
| `kyberWasm` | `mlkemWasm` |
387+
| `init({ kyber: src })` | `init({ mlkem: src })` |
388+
| `isInitialized('kyber')` | `isInitialized('mlkem')` |
389+
390+
No backward-compat alias. The `MlKem512`/`MlKem768`/`MlKem1024` class
391+
names, the `MLKEM512`/`MLKEM768`/`MLKEM1024` parameter consts, and
392+
every wire-format byte and label are unchanged. The historical
393+
`docs/kyber_audit.md` keeps its filename and wiki URL as the audit
394+
trail for the original FIPS 203 implementation work.
395+
370396
### Security
371397

372398
- **HtE explicit commitment on `AESGCMSIVCipher`.** Closes the Invisible
@@ -446,7 +472,7 @@
446472

447473
- **`cte/shared.ts ctEqual` for in-WASM byte equality.** New `@inline`
448474
AssemblyScript helper exporting `ctEqual(aOff, bOff, len): i32`.
449-
Imported by `kyber/verify.ts`, `slhdsa/hypertree.ts`,
475+
Imported by `mlkem/verify.ts`, `slhdsa/hypertree.ts`,
450476
`curve25519/ed25519.ts`, and `p256/ecdsa.ts`; the AS compiler inlines
451477
the body at each call site so the symbol never appears in any consumer
452478
WASM exports table. Scalar XOR-accumulate with a branch-free
@@ -555,7 +581,7 @@
555581

556582
- **`docs/architecture.md`** per-submodule export map drops the
557583
`_<module>Ready` entries, picks up the `ratchet/index.ts` row,
558-
`kyberInit` in the subpath-init list, and a footnote noting
584+
`mlkemInit` in the subpath-init list, and a footnote noting
559585
`isInitialized(mod)` is now reachable from every submodule subpath.
560586
New entries for ECDSA-P256, BLAKE3, SLH-DSA, ML-DSA, curve25519, and
561587
the merkle layer.
@@ -568,7 +594,7 @@
568594
latest release). SRI example keeps an explicit version pin since the
569595
integrity hash is bytes-specific. Adds a "Pinning" callout
570596
recommending pinned versions for production. WASM filenames table
571-
picks up a missing `kyber.wasm` row.
597+
picks up a missing `mlkem.wasm` row.
572598

573599
- **New shipped docs.** `docs/signaturesuite.md`,
574600
`docs/phase-index.md`, `docs/blake3.md`, `docs/slhdsa.md`,

README.md

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424

2525
**Above the cipher suites sits a cipher-agnostic [AEAD layer](https://github.com/xero/leviathan-crypto/wiki/aead):** `Seal`, `SealStream`, `OpenStream`, and `SealStreamPool`. Each takes a `CipherSuite` at construction, and the seal layer handles key derivation, nonce management, and authentication. `Seal` covers one-shot encryption for data that fits in memory. `SealStream` and `OpenStream` handle chunked data too large to buffer. WASM instances are single-threaded by design, so `SealStreamPool` distributes chunks across Web Workers to reach multi-core throughput. Any authentication failure kills the pool. Pending operations reject, workers zero their keys and terminate, and the master synchronously zeroes its copies. No retry, no partial results. All four share one wire format. A `Seal` blob is structurally a single-chunk `SealStream` output, and `OpenStream` decrypts it interchangeably.
2626

27-
**[ML-KEM](https://github.com/xero/leviathan-crypto/wiki/kyber): post-quantum handshake.** `KyberSuite` is a fourth `CipherSuite` factory that wraps an ML-KEM parameter set (`MlKem512`, `MlKem768`, `MlKem1024`) around any of the three ciphers above. The result slots into `Seal`, `SealStream`, `OpenStream`, and `SealStreamPool` unchanged. Constant-time Fujisaki-Okamoto comparisons run inside the Kyber WASM module; the 32-byte shared secret derives directly from a SHA-3 output and never crosses the wire, so the leading-zero-trim timing leak that hit TLS-DH(E) (the Raccoon attack) has no structural analog here.
27+
**[ML-KEM](https://github.com/xero/leviathan-crypto/wiki/mlkem): post-quantum handshake.** `MlKemSuite` is a fourth `CipherSuite` factory that wraps an ML-KEM parameter set (`MlKem512`, `MlKem768`, `MlKem1024`) around any of the three ciphers above. The result slots into `Seal`, `SealStream`, `OpenStream`, and `SealStreamPool` unchanged. Constant-time Fujisaki-Okamoto comparisons run inside the ML-KEM WASM module; the 32-byte shared secret derives directly from a SHA-3 output and never crosses the wire, so the leading-zero-trim timing leak that hit TLS-DH(E) (the Raccoon attack) has no structural analog here.
2828

2929
**[X25519](https://github.com/xero/leviathan-crypto/wiki/x25519): classical key agreement.** Curve25519 Diffie-Hellman per RFC 7748 §5, with a constant-time Montgomery ladder and TS-layer rejection of the all-zero shared secret. Same key-agreement role as ML-KEM but without post-quantum guarantees; use it for ecosystem interop, ML-KEM when the threat model assumes a future CRQC, or both together when you want a hybrid handshake.
3030

@@ -70,7 +70,7 @@ npm install leviathan-crypto
7070
v3 is the current stable line; semver applies. Runs in modern browsers, Node.js 22+, Bun, Deno, and Cloudflare Workers.
7171

7272
> [!IMPORTANT]
73-
> [Serpent](https://github.com/xero/leviathan-crypto/wiki/serpent), [ChaCha20](https://github.com/xero/leviathan-crypto/wiki/chacha20), [ML-KEM](https://github.com/xero/leviathan-crypto/wiki/kyber), [AES](https://github.com/xero/leviathan-crypto/wiki/aes), [ML-DSA](https://github.com/xero/leviathan-crypto/wiki/mldsa), [BLAKE3](https://github.com/xero/leviathan-crypto/wiki/blake3), and [constantTimeEqual](https://github.com/xero/leviathan-crypto/wiki/utils#constanttimeequal) require WebAssembly SIMD support. This has been a baseline feature of all major browsers and runtimes [since 2021](https://caniuse.com/wasm-simd).
73+
> [Serpent](https://github.com/xero/leviathan-crypto/wiki/serpent), [ChaCha20](https://github.com/xero/leviathan-crypto/wiki/chacha20), [ML-KEM](https://github.com/xero/leviathan-crypto/wiki/mlkem), [AES](https://github.com/xero/leviathan-crypto/wiki/aes), [ML-DSA](https://github.com/xero/leviathan-crypto/wiki/mldsa), [BLAKE3](https://github.com/xero/leviathan-crypto/wiki/blake3), and [constantTimeEqual](https://github.com/xero/leviathan-crypto/wiki/utils#constanttimeequal) require WebAssembly SIMD support. This has been a baseline feature of all major browsers and runtimes [since 2021](https://caniuse.com/wasm-simd).
7474
7575
SIMD throughput on Apple Silicon peaks at ~1.3 GB/s for ChaCha20 and ~40 MB/s for Serpent, single-threaded; 1.2-3.2× over scalar. Full matrix across V8, SpiderMonkey, and JSC in [benchmarks](https://github.com/xero/leviathan-crypto/wiki/benchmarks).
7676

@@ -126,13 +126,13 @@ import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
126126
await serpentInit(serpentWasm)
127127
await sha2Init(sha2Wasm)
128128

129-
// ML-KEM requires kyber + sha3
130-
import { kyberInit } from 'leviathan-crypto/kyber'
131-
import { kyberWasm } from 'leviathan-crypto/kyber/embedded'
129+
// ML-KEM requires mlkem + sha3
130+
import { mlkemInit } from 'leviathan-crypto/mlkem'
131+
import { mlkemWasm } from 'leviathan-crypto/mlkem/embedded'
132132
import { sha3Init } from 'leviathan-crypto/sha3'
133133
import { sha3Wasm } from 'leviathan-crypto/sha3/embedded'
134134

135-
await kyberInit(kyberWasm)
135+
await mlkemInit(mlkemWasm)
136136
await sha3Init(sha3Wasm)
137137
```
138138

@@ -159,8 +159,8 @@ Real bundle sizes (esbuild minified + gzip):
159159
| `leviathan-crypto/sha3/embedded` | SHA-3 WASM blob |
160160
| `leviathan-crypto/keccak` | Keccak alias for SHA-3 |
161161
| `leviathan-crypto/keccak/embedded` | Keccak WASM blob (same bytes as `sha3/embedded`) |
162-
| `leviathan-crypto/kyber` | ML-KEM |
163-
| `leviathan-crypto/kyber/embedded` | ML-KEM WASM blob |
162+
| `leviathan-crypto/mlkem` | ML-KEM |
163+
| `leviathan-crypto/mlkem/embedded` | ML-KEM WASM blob |
164164
| `leviathan-crypto/aes` | AES-256-GCM-SIV |
165165
| `leviathan-crypto/aes/embedded` | AES WASM blob |
166166
| `leviathan-crypto/blake3` | BLAKE3 |
@@ -260,16 +260,16 @@ const decrypted = await pool.open(encrypted)
260260
pool.destroy()
261261
```
262262

263-
**_Want post-quantum security?_** [`KyberSuite`](https://github.com/xero/leviathan-crypto/wiki/kyber#kybersuite) wraps ML-KEM and a cipher suite into a hybrid construction. It plugs directly into [`SealStream`](https://github.com/xero/leviathan-crypto/wiki/aead#sealstream). The sender encrypts with the public encapsulation key and only the recipient's private decapsulation key can open it.
263+
**_Want post-quantum security?_** [`MlKemSuite`](https://github.com/xero/leviathan-crypto/wiki/mlkem#mlkemsuite) wraps ML-KEM and a cipher suite into a hybrid construction. It plugs directly into [`SealStream`](https://github.com/xero/leviathan-crypto/wiki/aead#sealstream). The sender encrypts with the public encapsulation key and only the recipient's private decapsulation key can open it.
264264

265265
```typescript
266-
import { KyberSuite, MlKem768 } from 'leviathan-crypto/kyber'
267-
import { kyberWasm } from 'leviathan-crypto/kyber/embedded'
266+
import { MlKemSuite, MlKem768 } from 'leviathan-crypto/mlkem'
267+
import { mlkemWasm } from 'leviathan-crypto/mlkem/embedded'
268268
import { sha3Wasm } from 'leviathan-crypto/sha3/embedded'
269269

270-
await init({ kyber: kyberWasm, sha3: sha3Wasm, chacha20: chacha20Wasm, sha2: sha2Wasm })
270+
await init({ mlkem: mlkemWasm, sha3: sha3Wasm, chacha20: chacha20Wasm, sha2: sha2Wasm })
271271

272-
const suite = KyberSuite(new MlKem768(), XChaCha20Cipher)
272+
const suite = MlKemSuite(new MlKem768(), XChaCha20Cipher)
273273
const { encapsulationKey: ek, decapsulationKey: dk } = suite.keygen()
274274

275275
// sender: encrypts with the public key
@@ -356,7 +356,7 @@ ECDSA-P256 is classical (not post-quantum); pair it with an ML-DSA or SLH-DSA su
356356
```typescript
357357
import { ratchetInit, KDFChain } from 'leviathan-crypto/ratchet'
358358

359-
await init({ sha2: sha2Wasm }) // KDF layer only; add kyber + sha3 for KEM steps
359+
await init({ sha2: sha2Wasm }) // KDF layer only; add mlkem + sha3 for KEM steps
360360

361361
const { nextRootKey, sendChainKey, recvChainKey } = ratchetInit(sharedSecret)
362362
const chain = new KDFChain(sendChainKey)
@@ -393,13 +393,13 @@ lvthn keygen --armor -o my.key
393393
cat secret.txt | lvthn encrypt -k my.key --armor > secret.enc
394394
```
395395

396-
**`kyber`** [ [demo](https://leviathan.3xi.club/kyber) · [source](https://github.com/xero/leviathan-demos/tree/main/kyber) · [readme](https://github.com/xero/leviathan-demos/blob/main/kyber/README.md) ]
396+
**`mlkem`** [ [demo](https://leviathan.3xi.club/mlkem) · [source](https://github.com/xero/leviathan-demos/tree/main/mlkem) · [readme](https://github.com/xero/leviathan-demos/blob/main/mlkem/README.md) ]
397397

398398
Post-quantum cryptography demo simulating a complete ML-KEM key encapsulation ceremony between two browser-side clients. A live wire at the top of the page logs every value that crosses the channel; importantly, the shared secret never appears in the wire. After the ceremony completes, both sides independently derive a symmetric key using HKDF-SHA256 and exchange messages encrypted with XChaCha20-Poly1305. Each wire frame is expandable, revealing the raw nonce, ciphertext, Poly1305 tag, and AAD.
399399

400400
**`COVCOM`** [ [demo](https://leviathan.3xi.club/covcom) · [source](https://github.com/xero/covcom/) · [readme](https://github.com/xero/covcom/blob/master/README.md) ]
401401

402-
A covert communications application for end-to-end encrypted group conversations. Share an invite, talk, exit, and it's gone. Clients available for both the web and cli, along with a containerized dumb server for managing rooms. No secrets or cleartext beyond the handle you chose to join a room with are ever visible to the server. Featuring sparse post-quantum ratcheting, ML-KEM-768, KDFChains, Seal+KyberSuite, and a XChaCha20-Poly1305 core.
402+
A covert communications application for end-to-end encrypted group conversations. Share an invite, talk, exit, and it's gone. Clients available for both the web and cli, along with a containerized dumb server for managing rooms. No secrets or cleartext beyond the handle you chose to join a room with are ever visible to the server. Featuring sparse post-quantum ratcheting, ML-KEM-768, KDFChains, Seal+MlKemSuite, and a XChaCha20-Poly1305 core.
403403

404404
---
405405

@@ -410,7 +410,7 @@ A covert communications application for end-to-end encrypted group conversations
410410
| Encrypt data | [`Seal`](./aead.md#seal) with [`SerpentCipher`](./serpent.md#serpentcipher), [`XChaCha20Cipher`](./chacha20.md#xchacha20cipher), or [`AESGCMSIVCipher`](./aes.md#aesgcmsivcipher) |
411411
| Encrypt a stream or large file | [`SealStream`](./aead.md#sealstream) to encrypt, [`OpenStream`](./aead.md#openstream) to decrypt |
412412
| Encrypt in parallel | [`SealStreamPool`](./aead.md#sealstreampool) distributes chunks across Web Workers |
413-
| Add post-quantum security | [`KyberSuite`](./kyber.md#kybersuite) wraps [`MlKem512`](./kyber.md#parameter-sets), [`MlKem768`](./kyber.md#parameter-sets), or [`MlKem1024`](./kyber.md#parameter-sets) with any cipher suite |
413+
| Add post-quantum security | [`MlKemSuite`](./mlkem.md#mlkemsuite) wraps [`MlKem512`](./mlkem.md#parameter-sets), [`MlKem768`](./mlkem.md#parameter-sets), or [`MlKem1024`](./mlkem.md#parameter-sets) with any cipher suite |
414414
| Build a forward-secret session | [`ratchetInit`](./ratchet.md#ratchetinit), [`KDFChain`](./ratchet.md#kdfchain), [`kemRatchetEncap`](./ratchet.md#kemratchetencap) / [`kemRatchetDecap`](./ratchet.md#kemratchetdecap), [`SkippedKeyStore`](./ratchet.md#skippedkeystore) |
415415
| Sign data with a classical signature | [`Ed25519Suite`](./signaturesuite.md#ed25519-suites) / [`Ed25519PreHashSuite`](./signaturesuite.md#ed25519-suites) ([ed25519.md](./ed25519.md)) or [`EcdsaP256Suite`](./signaturesuite.md#ecdsa-p256-suite) ([ecdsa-p256.md](./ecdsa-p256.md)) via [`Sign`](./signing.md#sign) / [`SignStream`](./signing.md#signstream) / [`VerifyStream`](./signing.md#verifystream) |
416416
| Sign data with a post-quantum signature | `MlDsa44/65/87Suite` (+ `*PreHashSuite`) for lattice ML-DSA ([mldsa.md](./mldsa.md)) or `SlhDsa128f/192f/256fSuite` (+ `*PreHashSuite`) for hash-based SLH-DSA ([slhdsa.md](./slhdsa.md)). Full catalog in [signaturesuite.md](./signaturesuite.md) |

0 commit comments

Comments
 (0)