leviathan-crypto 1.4.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/CLAUDE.md +129 -94
  2. package/README.md +166 -223
  3. package/SECURITY.md +85 -45
  4. package/dist/chacha20/cipher-suite.d.ts +4 -0
  5. package/dist/chacha20/cipher-suite.js +78 -0
  6. package/dist/chacha20/embedded.d.ts +1 -0
  7. package/dist/chacha20/embedded.js +27 -0
  8. package/dist/chacha20/index.d.ts +20 -27
  9. package/dist/chacha20/index.js +40 -59
  10. package/dist/chacha20/ops.d.ts +1 -1
  11. package/dist/chacha20/ops.js +19 -18
  12. package/dist/chacha20/pool-worker.js +77 -0
  13. package/dist/ct-wasm.d.ts +1 -0
  14. package/dist/ct-wasm.js +3 -0
  15. package/dist/ct.wasm +0 -0
  16. package/dist/docs/aead.md +320 -0
  17. package/dist/docs/architecture.md +419 -285
  18. package/dist/docs/argon2id.md +42 -30
  19. package/dist/docs/chacha20.md +192 -266
  20. package/dist/docs/exports.md +241 -0
  21. package/dist/docs/fortuna.md +60 -69
  22. package/dist/docs/init.md +172 -178
  23. package/dist/docs/loader.md +87 -142
  24. package/dist/docs/serpent.md +134 -583
  25. package/dist/docs/sha2.md +91 -103
  26. package/dist/docs/sha3.md +70 -36
  27. package/dist/docs/types.md +93 -16
  28. package/dist/docs/utils.md +109 -32
  29. package/dist/embedded/kyber.d.ts +1 -0
  30. package/dist/embedded/kyber.js +3 -0
  31. package/dist/errors.d.ts +10 -0
  32. package/dist/errors.js +38 -0
  33. package/dist/fortuna.d.ts +0 -6
  34. package/dist/fortuna.js +5 -5
  35. package/dist/index.d.ts +25 -9
  36. package/dist/index.js +36 -7
  37. package/dist/init.d.ts +3 -7
  38. package/dist/init.js +18 -35
  39. package/dist/keccak/embedded.d.ts +1 -0
  40. package/dist/keccak/embedded.js +27 -0
  41. package/dist/keccak/index.d.ts +4 -0
  42. package/dist/keccak/index.js +31 -0
  43. package/dist/kyber/embedded.d.ts +1 -0
  44. package/dist/kyber/embedded.js +27 -0
  45. package/dist/kyber/indcpa.d.ts +49 -0
  46. package/dist/kyber/indcpa.js +352 -0
  47. package/dist/kyber/index.d.ts +38 -0
  48. package/dist/kyber/index.js +150 -0
  49. package/dist/kyber/kem.d.ts +21 -0
  50. package/dist/kyber/kem.js +160 -0
  51. package/dist/kyber/params.d.ts +14 -0
  52. package/dist/kyber/params.js +37 -0
  53. package/dist/kyber/suite.d.ts +13 -0
  54. package/dist/kyber/suite.js +93 -0
  55. package/dist/kyber/types.d.ts +98 -0
  56. package/dist/kyber/types.js +25 -0
  57. package/dist/kyber/validate.d.ts +19 -0
  58. package/dist/kyber/validate.js +68 -0
  59. package/dist/kyber.wasm +0 -0
  60. package/dist/loader.d.ts +15 -6
  61. package/dist/loader.js +65 -21
  62. package/dist/serpent/cipher-suite.d.ts +4 -0
  63. package/dist/serpent/cipher-suite.js +121 -0
  64. package/dist/serpent/embedded.d.ts +1 -0
  65. package/dist/serpent/embedded.js +27 -0
  66. package/dist/serpent/index.d.ts +6 -37
  67. package/dist/serpent/index.js +9 -118
  68. package/dist/serpent/pool-worker.d.ts +1 -0
  69. package/dist/serpent/pool-worker.js +202 -0
  70. package/dist/serpent/serpent-cbc.d.ts +30 -0
  71. package/dist/serpent/serpent-cbc.js +136 -0
  72. package/dist/sha2/embedded.d.ts +1 -0
  73. package/dist/sha2/embedded.js +27 -0
  74. package/dist/sha2/hkdf.js +6 -2
  75. package/dist/sha2/index.d.ts +3 -2
  76. package/dist/sha2/index.js +3 -4
  77. package/dist/sha3/embedded.d.ts +1 -0
  78. package/dist/sha3/embedded.js +27 -0
  79. package/dist/sha3/index.d.ts +3 -2
  80. package/dist/sha3/index.js +3 -4
  81. package/dist/stream/constants.d.ts +6 -0
  82. package/dist/stream/constants.js +30 -0
  83. package/dist/stream/header.d.ts +9 -0
  84. package/dist/stream/header.js +77 -0
  85. package/dist/stream/index.d.ts +7 -0
  86. package/dist/stream/index.js +27 -0
  87. package/dist/stream/open-stream.d.ts +21 -0
  88. package/dist/stream/open-stream.js +146 -0
  89. package/dist/stream/seal-stream-pool.d.ts +38 -0
  90. package/dist/stream/seal-stream-pool.js +391 -0
  91. package/dist/stream/seal-stream.d.ts +20 -0
  92. package/dist/stream/seal-stream.js +142 -0
  93. package/dist/stream/seal.d.ts +9 -0
  94. package/dist/stream/seal.js +75 -0
  95. package/dist/stream/types.d.ts +24 -0
  96. package/dist/stream/types.js +26 -0
  97. package/dist/utils.d.ts +7 -2
  98. package/dist/utils.js +49 -3
  99. package/dist/wasm-source.d.ts +12 -0
  100. package/dist/wasm-source.js +26 -0
  101. package/package.json +13 -5
  102. package/dist/chacha20/pool.d.ts +0 -52
  103. package/dist/chacha20/pool.js +0 -178
  104. package/dist/chacha20/pool.worker.js +0 -37
  105. package/dist/chacha20/stream-sealer.d.ts +0 -49
  106. package/dist/chacha20/stream-sealer.js +0 -327
  107. package/dist/docs/chacha20_pool.md +0 -309
  108. package/dist/docs/wasm.md +0 -194
  109. package/dist/serpent/seal.d.ts +0 -8
  110. package/dist/serpent/seal.js +0 -72
  111. package/dist/serpent/stream-pool.d.ts +0 -48
  112. package/dist/serpent/stream-pool.js +0 -275
  113. package/dist/serpent/stream-sealer.d.ts +0 -55
  114. package/dist/serpent/stream-sealer.js +0 -342
  115. package/dist/serpent/stream.d.ts +0 -28
  116. package/dist/serpent/stream.js +0 -205
  117. package/dist/serpent/stream.worker.d.ts +0 -32
  118. package/dist/serpent/stream.worker.js +0 -117
  119. /package/dist/chacha20/{pool.worker.d.ts → pool-worker.d.ts} +0 -0
@@ -0,0 +1,352 @@
1
+ // ▄▄▄▄▄▄▄▄▄▄
2
+ // ▄████████████████████▄▄ ▒ ▄▀▀ ▒ ▒ █ ▄▀▄ ▀█▀ █ ▒ ▄▀▄ █▀▄
3
+ // ▄██████████████████████ ▀████▄ ▓ ▓▀ ▓ ▓ ▓ ▓▄▓ ▓ ▓▀▓ ▓▄▓ ▓ ▓
4
+ // ▄█████████▀▀▀ ▀███████▄▄███████▌ ▀▄ ▀▄▄ ▀▄▀ ▒ ▒ ▒ ▒ ▒ █ ▒ ▒ ▒ █
5
+ // ▐████████▀ ▄▄▄▄ ▀████████▀██▀█▌
6
+ // ████████ ███▀▀ ████▀ █▀ █▀ Leviathan Crypto Library
7
+ // ███████▌ ▀██▀ ███
8
+ // ███████ ▀███ ▀██ ▀█▄ Repository & Mirror:
9
+ // ▀██████ ▄▄██ ▀▀ ██▄ github.com/xero/leviathan-crypto
10
+ // ▀█████▄ ▄██▄ ▄▀▄▀ unpkg.com/leviathan-crypto
11
+ // ▀████▄ ▄██▄
12
+ // ▐████ ▐███ Author: xero (https://x-e.ro)
13
+ // ▄▄██████████ ▐███ ▄▄ License: MIT
14
+ // ▄██▀▀▀▀▀▀▀▀▀▀ ▄████ ▄██▀
15
+ // ▄▀ ▄▄█████████▄▄ ▀▀▀▀▀ ▄███ This file is provided completely
16
+ // ▄██████▀▀▀▀▀▀██████▄ ▀▄▄▄▄████▀ free, "as is", and without
17
+ // ████▀ ▄▄▄▄▄▄▄ ▀████▄ ▀█████▀ ▄▄▄▄ warranty of any kind. The author
18
+ // █████▄▄█████▀▀▀▀▀▀▄ ▀███▄ ▄████ assumes absolutely no liability
19
+ // ▀██████▀ ▀████▄▄▄████▀ for its {ab,mis,}use.
20
+ // ▀█████▀▀
21
+ //
22
+ // src/ts/kyber/indcpa.ts
23
+ //
24
+ // ML-KEM IND-CPA PKE scheme — FIPS 203 Algorithms 12, 13, 14 (K-PKE).
25
+ // Orchestrates kyber WASM (polynomial math) and sha3 WASM (Keccak sponge).
26
+ import { wipe } from '../utils.js';
27
+ // ── SHA3 helpers ──────────────────────────────────────────────────────────────
28
+ // All operate directly on raw Sha3Exports, no init() system involved.
29
+ /** Absorb msg into the sha3 sponge in 168-byte chunks (max rate). */
30
+ function sha3Absorb(sx, msg) {
31
+ const mem = new Uint8Array(sx.memory.buffer);
32
+ const inOff = sx.getInputOffset();
33
+ let pos = 0;
34
+ while (pos < msg.length) {
35
+ const chunk = Math.min(msg.length - pos, 168);
36
+ mem.set(msg.subarray(pos, pos + chunk), inOff);
37
+ sx.keccakAbsorb(chunk);
38
+ pos += chunk;
39
+ }
40
+ }
41
+ /** SHA3-512(msg) → 64 bytes. Resets sha3 state. */
42
+ export function sha3_512Hash(sx, msg) {
43
+ sx.sha3_512Init();
44
+ sha3Absorb(sx, msg);
45
+ sx.sha3_512Final();
46
+ const mem = new Uint8Array(sx.memory.buffer);
47
+ const off = sx.getOutOffset();
48
+ return mem.slice(off, off + 64);
49
+ }
50
+ /** SHA3-256(msg) → 32 bytes. Resets sha3 state. */
51
+ export function sha3_256Hash(sx, msg) {
52
+ sx.sha3_256Init();
53
+ sha3Absorb(sx, msg);
54
+ sx.sha3_256Final();
55
+ const mem = new Uint8Array(sx.memory.buffer);
56
+ const off = sx.getOutOffset();
57
+ return mem.slice(off, off + 32);
58
+ }
59
+ /**
60
+ * SHAKE256(msg, n) → n bytes. Resets sha3 state.
61
+ * Used for J function (z || c) and PRF seeding in kem.ts.
62
+ */
63
+ export function shake256Hash(sx, msg, n) {
64
+ const rate = 136;
65
+ sx.shake256Init();
66
+ sha3Absorb(sx, msg);
67
+ sx.shakePad();
68
+ const sha3Mem = new Uint8Array(sx.memory.buffer);
69
+ const outOff = sx.getOutOffset();
70
+ const out = new Uint8Array(n);
71
+ let pos = 0;
72
+ while (pos < n) {
73
+ sx.shakeSqueezeBlock();
74
+ const take = Math.min(n - pos, rate);
75
+ out.set(sha3Mem.subarray(outOff, outOff + take), pos);
76
+ pos += take;
77
+ }
78
+ return out;
79
+ }
80
+ // ── Matrix generation ─────────────────────────────────────────────────────────
81
+ /**
82
+ * Generate row `rowI` of matrix  (or Â^T) into polyvec slot `pvecSlot`.
83
+ *
84
+ * FIPS 203 Algorithm 6 (SampleNTT): each entry Â[i][j] = XOF(ρ, j, i).
85
+ * The matrix entries are already in NTT domain by construction — no separate
86
+ * NTT call is needed after rej_uniform.
87
+ *
88
+ * transposed=false (keygen): XOF input = ρ || j || i → Â[i][j]
89
+ * transposed=true (encrypt): XOF input = ρ || i || j → Â^T[i][j] = Â[j][i]
90
+ */
91
+ function genMatrixRow(kx, sx, k, rho, transposed, pvecSlot, rowI) {
92
+ const xofPrfOff = kx.getXofPrfOffset();
93
+ const kyberMem = new Uint8Array(kx.memory.buffer);
94
+ const sha3Mem = new Uint8Array(sx.memory.buffer);
95
+ const outOff = sx.getOutOffset();
96
+ // XOF seed buffer: ρ(32) || byte0 || byte1
97
+ const xofSeed = new Uint8Array(34);
98
+ xofSeed.set(rho, 0);
99
+ for (let j = 0; j < k; j++) {
100
+ // Build XOF seed before the squeeze loop (no branch inside)
101
+ if (!transposed) {
102
+ xofSeed[32] = j; // ρ || j || i → Â[rowI][j]
103
+ xofSeed[33] = rowI;
104
+ }
105
+ else {
106
+ xofSeed[32] = rowI; // ρ || i || j → Â^T[rowI][j] = Â[j][rowI]
107
+ xofSeed[33] = j;
108
+ }
109
+ // Init SHAKE128 and absorb seed
110
+ sx.shake128Init();
111
+ sha3Absorb(sx, xofSeed);
112
+ sx.shakePad();
113
+ // Reject-sample until 256 NTT coefficients accepted
114
+ const polyOff = pvecSlot + j * 512;
115
+ let ctr = 0;
116
+ while (ctr < 256) {
117
+ sx.shakeSqueezeBlock(); // 168 bytes at sha3 OUT_OFFSET
118
+ kyberMem.set(sha3Mem.subarray(outOff, outOff + 168), xofPrfOff);
119
+ ctr += kx.rej_uniform(polyOff, ctr, xofPrfOff, 168);
120
+ }
121
+ }
122
+ }
123
+ // ── Noise generation ──────────────────────────────────────────────────────────
124
+ /**
125
+ * CBD noise polyvec: SHAKE256(σ || nonce) for each entry.
126
+ * FIPS 203 Algorithm 7: PRF_η(σ, N) = SHAKE256(σ || N)[0..64η-1].
127
+ */
128
+ function noisePolyvec(kx, sx, pvSlot, k, sigma, nonceStart, eta) {
129
+ const xofPrfOff = kx.getXofPrfOffset();
130
+ const kyberMem = new Uint8Array(kx.memory.buffer);
131
+ const sha3Mem = new Uint8Array(sx.memory.buffer);
132
+ const outOff = sx.getOutOffset();
133
+ const prfLen = eta * 64; // 128 for η=2, 192 for η=3
134
+ const rate = 136; // SHAKE256 rate
135
+ // PRF input buffer: σ(32) || nonce(1)
136
+ const prfInput = new Uint8Array(33);
137
+ prfInput.set(sigma, 0);
138
+ try {
139
+ for (let i = 0; i < k; i++) {
140
+ prfInput[32] = nonceStart + i;
141
+ sx.shake256Init();
142
+ sha3Absorb(sx, prfInput);
143
+ sx.shakePad();
144
+ let pos = 0;
145
+ while (pos < prfLen) {
146
+ sx.shakeSqueezeBlock();
147
+ const take = Math.min(prfLen - pos, rate);
148
+ kyberMem.set(sha3Mem.subarray(outOff, outOff + take), xofPrfOff + pos);
149
+ pos += take;
150
+ }
151
+ kx.poly_getnoise(pvSlot + i * 512, xofPrfOff, eta);
152
+ }
153
+ }
154
+ finally {
155
+ wipe(prfInput);
156
+ }
157
+ }
158
+ /**
159
+ * CBD noise single polynomial: SHAKE256(σ || nonce).
160
+ */
161
+ function noisePoly(kx, sx, polyOff, sigma, nonce, eta) {
162
+ const xofPrfOff = kx.getXofPrfOffset();
163
+ const kyberMem = new Uint8Array(kx.memory.buffer);
164
+ const sha3Mem = new Uint8Array(sx.memory.buffer);
165
+ const outOff = sx.getOutOffset();
166
+ const prfLen = eta * 64;
167
+ const rate = 136;
168
+ const prfInput = new Uint8Array(33);
169
+ prfInput.set(sigma, 0);
170
+ prfInput[32] = nonce;
171
+ try {
172
+ sx.shake256Init();
173
+ sha3Absorb(sx, prfInput);
174
+ sx.shakePad();
175
+ let pos = 0;
176
+ while (pos < prfLen) {
177
+ sx.shakeSqueezeBlock();
178
+ const take = Math.min(prfLen - pos, rate);
179
+ kyberMem.set(sha3Mem.subarray(outOff, outOff + take), xofPrfOff + pos);
180
+ pos += take;
181
+ }
182
+ kx.poly_getnoise(polyOff, xofPrfOff, eta);
183
+ }
184
+ finally {
185
+ wipe(prfInput);
186
+ }
187
+ }
188
+ // ── IND-CPA functions ─────────────────────────────────────────────────────────
189
+ /**
190
+ * K-PKE.KeyGen (FIPS 203 Algorithm 12) — deterministic.
191
+ *
192
+ * Slot map:
193
+ * pvec0 — current row of  (overwritten per row)
194
+ * pvec1 — ŝ (noise, persistent through dot products)
195
+ * pvec2 — ê (noise)
196
+ * pvec3 — t̂ = ·ŝ + ê (output)
197
+ */
198
+ export function indcpaKeypairDerand(kx, sx, params, d) {
199
+ const { k, eta1 } = params;
200
+ // Step 1: G(d || k) → (ρ, σ) [FIPS 203 §5.1 G = SHA3-512]
201
+ const gInput = new Uint8Array(33);
202
+ gInput.set(d, 0);
203
+ gInput[32] = k;
204
+ const gOut = sha3_512Hash(sx, gInput);
205
+ const rho = gOut.slice(0, 32);
206
+ const sigma = gOut.slice(32, 64);
207
+ // Slot addresses
208
+ const pvec0 = kx.getPolyvecSlot0();
209
+ const pvec1 = kx.getPolyvecSlot1();
210
+ const pvec2 = kx.getPolyvecSlot2();
211
+ const pvec3 = kx.getPolyvecSlot3();
212
+ const pkOff = kx.getPkOffset();
213
+ const skOff = kx.getSkOffset();
214
+ try {
215
+ // Steps 2-3: Generate ŝ and ê BEFORE matrix (so ŝ is stable during dot products)
216
+ noisePolyvec(kx, sx, pvec1, k, sigma, 0, eta1); // ŝ ← CBD(σ, 0..k-1)
217
+ noisePolyvec(kx, sx, pvec2, k, sigma, k, eta1); // ê ← CBD(σ, k..2k-1)
218
+ // Step 4: NTT(ŝ), NTT(ê)
219
+ kx.polyvec_ntt(pvec1, k);
220
+ kx.polyvec_ntt(pvec2, k);
221
+ // Step 5: For each row i, t̂[i] = Â[i] · ŝ
222
+ const kyberMem = new Uint8Array(kx.memory.buffer);
223
+ for (let i = 0; i < k; i++) {
224
+ genMatrixRow(kx, sx, k, rho, false, pvec0, i);
225
+ kx.polyvec_basemul_acc_montgomery(pvec3 + i * 512, pvec0, pvec1, k);
226
+ kx.poly_tomont(pvec3 + i * 512);
227
+ }
228
+ // Step 6-7: t̂ = t̂ + ê, reduce
229
+ kx.polyvec_add(pvec3, pvec3, pvec2, k);
230
+ kx.polyvec_reduce(pvec3, k);
231
+ // Step 8: ek = polyvec_tobytes(t̂) || ρ
232
+ kx.polyvec_tobytes(pkOff, pvec3, k);
233
+ kyberMem.set(rho, pkOff + k * 384);
234
+ // Step 9: sk = polyvec_tobytes(ŝ)
235
+ kx.polyvec_tobytes(skOff, pvec1, k);
236
+ return {
237
+ ekCpa: kyberMem.slice(pkOff, pkOff + params.ekBytes),
238
+ skCpa: kyberMem.slice(skOff, skOff + params.skCpaBytes),
239
+ };
240
+ }
241
+ finally {
242
+ wipe(sigma);
243
+ wipe(gOut);
244
+ }
245
+ }
246
+ /**
247
+ * K-PKE.Encrypt (FIPS 203 Algorithm 13) — deterministic.
248
+ *
249
+ * Slot map:
250
+ * pvec0 — current row of Â^T (transposed, overwritten per row)
251
+ * pvec1 — r̂ = NTT(r)
252
+ * pvec2 — e₁ (noise)
253
+ * pvec3 — u = invNTT(Â^T · r̂) + e₁
254
+ * pvec4 — t̂ (unpacked from ek)
255
+ * poly1 — e₂ (noise)
256
+ * poly2 — v = invNTT(t̂^T · r̂) + e₂ + msg
257
+ * poly3 — message polynomial
258
+ */
259
+ export function indcpaEncrypt(kx, sx, params, ek, m, coins) {
260
+ const { k, eta1, eta2, du, dv } = params;
261
+ const kyberMem = new Uint8Array(kx.memory.buffer);
262
+ const pvec0 = kx.getPolyvecSlot0();
263
+ const pvec1 = kx.getPolyvecSlot1();
264
+ const pvec2 = kx.getPolyvecSlot2();
265
+ const pvec3 = kx.getPolyvecSlot3();
266
+ const pvec4 = kx.getPolyvecSlot4();
267
+ const poly1 = kx.getPolySlot1();
268
+ const poly2 = kx.getPolySlot2();
269
+ const poly3 = kx.getPolySlot3();
270
+ const pkOff = kx.getPkOffset();
271
+ const ctOff = kx.getCtOffset();
272
+ const msgOff = kx.getMsgOffset();
273
+ // Step 1: Unpack ek — t̂ → pvec4, ρ from ek tail
274
+ kyberMem.set(ek, pkOff);
275
+ kx.polyvec_frombytes(pvec4, pkOff, k);
276
+ const rho = ek.slice(k * 384, k * 384 + 32);
277
+ // Steps 2-4: Generate noise r, e₁, e₂ from coins
278
+ noisePolyvec(kx, sx, pvec1, k, coins, 0, eta1); // r → pvec1
279
+ noisePolyvec(kx, sx, pvec2, k, coins, k, eta2); // e₁ → pvec2
280
+ noisePoly(kx, sx, poly1, coins, 2 * k, eta2); // e₂ → poly1
281
+ // Step 5: r̂ = NTT(r)
282
+ kx.polyvec_ntt(pvec1, k);
283
+ // Step 6: For each row i, u[i] = Â^T[i] · r̂
284
+ // basemul_acc produces (A·r)/R; invntt_tomont cancels the /R: INTT(A·r)
285
+ for (let i = 0; i < k; i++) {
286
+ genMatrixRow(kx, sx, k, rho, true, pvec0, i);
287
+ kx.polyvec_basemul_acc_montgomery(pvec3 + i * 512, pvec0, pvec1, k);
288
+ }
289
+ // Steps 7-9: u = invNTT(Â^T · r̂) + e₁, reduce
290
+ // invntt acts as invntt_tomont: input=(A·r)/R → output=INTT(A·r) in time domain
291
+ // Add e₁ AFTER invntt (both in time domain)
292
+ kx.polyvec_invntt(pvec3, k);
293
+ kx.polyvec_add(pvec3, pvec3, pvec2, k);
294
+ kx.polyvec_reduce(pvec3, k);
295
+ // Steps 10-11: v = invNTT(t̂^T · r̂)
296
+ kx.polyvec_basemul_acc_montgomery(poly2, pvec4, pvec1, k);
297
+ kx.poly_invntt(poly2);
298
+ // Step 12: decode message
299
+ kyberMem.set(m, msgOff);
300
+ kx.poly_frommsg(poly3, msgOff);
301
+ // Steps 13-15: v = v + e₂ + msg, reduce
302
+ kx.poly_add(poly2, poly2, poly1);
303
+ kx.poly_add(poly2, poly2, poly3);
304
+ kx.poly_reduce(poly2);
305
+ // Step 16: pack ciphertext — Compress_du(u) || Compress_dv(v)
306
+ const pvecCompBytes = k * du * 32;
307
+ kx.polyvec_compress(ctOff, pvec3, k, du);
308
+ kx.poly_compress(ctOff + pvecCompBytes, poly2, dv);
309
+ return kyberMem.slice(ctOff, ctOff + params.ctBytes);
310
+ }
311
+ /**
312
+ * K-PKE.Decrypt (FIPS 203 Algorithm 14).
313
+ *
314
+ * Slot map:
315
+ * pvec0 — û (decompressed from ct)
316
+ * pvec1 — ŝ (from sk)
317
+ * poly0 — v (decompressed from ct)
318
+ * poly1 — w = invNTT(ŝ^T · NTT(û))
319
+ * poly2 — m' = v - w
320
+ */
321
+ export function indcpaDecrypt(kx, params, skCpa, ct) {
322
+ const { k, du, dv } = params;
323
+ const kyberMem = new Uint8Array(kx.memory.buffer);
324
+ const pvec0 = kx.getPolyvecSlot0();
325
+ const pvec1 = kx.getPolyvecSlot1();
326
+ const poly0 = kx.getPolySlot0();
327
+ const poly1 = kx.getPolySlot1();
328
+ const poly2 = kx.getPolySlot2();
329
+ const ctOff = kx.getCtOffset();
330
+ const skOff = kx.getSkOffset();
331
+ const msgOff = kx.getMsgOffset();
332
+ // Load ct and sk into kyber memory
333
+ kyberMem.set(ct, ctOff);
334
+ kyberMem.set(skCpa, skOff);
335
+ // Steps 1-2: Decompress û and v
336
+ const pvecCompBytes = k * du * 32;
337
+ kx.polyvec_decompress(pvec0, ctOff, k, du);
338
+ kx.poly_decompress(poly0, ctOff + pvecCompBytes, dv);
339
+ // Step 3: Unpack ŝ
340
+ kx.polyvec_frombytes(pvec1, skOff, k);
341
+ // Steps 4-6: w = invNTT(ŝ^T · NTT(û))
342
+ // basemul_acc produces (ŝ·û)/R; invntt cancels it: INTT(ŝ·û) in time domain
343
+ kx.polyvec_ntt(pvec0, k);
344
+ kx.polyvec_basemul_acc_montgomery(poly1, pvec1, pvec0, k);
345
+ kx.poly_invntt(poly1);
346
+ // Steps 7-8: m' = v - w, reduce
347
+ kx.poly_sub(poly2, poly0, poly1);
348
+ kx.poly_reduce(poly2);
349
+ // Step 9-10: poly_tomsg → 32-byte message
350
+ kx.poly_tomsg(msgOff, poly2);
351
+ return kyberMem.slice(msgOff, msgOff + 32);
352
+ }
@@ -0,0 +1,38 @@
1
+ import { isInitialized } from '../init.js';
2
+ import type { WasmSource } from '../wasm-source.js';
3
+ import type { KyberExports, Sha3Exports, KyberKeyPair, KyberEncapsulation } from './types.js';
4
+ import { KyberParams, MLKEM512, MLKEM768, MLKEM1024 } from './params.js';
5
+ export declare function kyberInit(source: WasmSource): Promise<void>;
6
+ export declare function _kyberReady(): boolean;
7
+ export type { WasmSource };
8
+ export type { KyberKeyPair, KyberEncapsulation, KyberExports, Sha3Exports };
9
+ export { MLKEM512, MLKEM768, MLKEM1024 };
10
+ export type { KyberParams };
11
+ export { isInitialized };
12
+ export { KyberSuite } from './suite.js';
13
+ export declare class MlKemBase {
14
+ readonly params: KyberParams;
15
+ constructor(params: KyberParams);
16
+ private get kx();
17
+ private get sx();
18
+ keygenDerand(d: Uint8Array, z: Uint8Array): KyberKeyPair;
19
+ keygen(): KyberKeyPair;
20
+ encapsulateDerand(ek: Uint8Array, m: Uint8Array): KyberEncapsulation;
21
+ encapsulate(ek: Uint8Array): KyberEncapsulation;
22
+ decapsulate(dk: Uint8Array, c: Uint8Array): Uint8Array;
23
+ checkEncapsulationKey(ek: Uint8Array): boolean;
24
+ checkDecapsulationKey(dk: Uint8Array): boolean;
25
+ dispose(): void;
26
+ }
27
+ /** ML-KEM-512 — k=2, η₁=3, η₂=2, dᵤ=10, dᵥ=4. */
28
+ export declare class MlKem512 extends MlKemBase {
29
+ constructor();
30
+ }
31
+ /** ML-KEM-768 — k=3, η₁=2, η₂=2, dᵤ=10, dᵥ=4. */
32
+ export declare class MlKem768 extends MlKemBase {
33
+ constructor();
34
+ }
35
+ /** ML-KEM-1024 — k=4, η₁=2, η₂=2, dᵤ=11, dᵥ=5. */
36
+ export declare class MlKem1024 extends MlKemBase {
37
+ constructor();
38
+ }
@@ -0,0 +1,150 @@
1
+ // ▄▄▄▄▄▄▄▄▄▄
2
+ // ▄████████████████████▄▄ ▒ ▄▀▀ ▒ ▒ █ ▄▀▄ ▀█▀ █ ▒ ▄▀▄ █▀▄
3
+ // ▄██████████████████████ ▀████▄ ▓ ▓▀ ▓ ▓ ▓ ▓▄▓ ▓ ▓▀▓ ▓▄▓ ▓ ▓
4
+ // ▄█████████▀▀▀ ▀███████▄▄███████▌ ▀▄ ▀▄▄ ▀▄▀ ▒ ▒ ▒ ▒ ▒ █ ▒ ▒ ▒ █
5
+ // ▐████████▀ ▄▄▄▄ ▀████████▀██▀█▌
6
+ // ████████ ███▀▀ ████▀ █▀ █▀ Leviathan Crypto Library
7
+ // ███████▌ ▀██▀ ███
8
+ // ███████ ▀███ ▀██ ▀█▄ Repository & Mirror:
9
+ // ▀██████ ▄▄██ ▀▀ ██▄ github.com/xero/leviathan-crypto
10
+ // ▀█████▄ ▄██▄ ▄▀▄▀ unpkg.com/leviathan-crypto
11
+ // ▀████▄ ▄██▄
12
+ // ▐████ ▐███ Author: xero (https://x-e.ro)
13
+ // ▄▄██████████ ▐███ ▄▄ License: MIT
14
+ // ▄██▀▀▀▀▀▀▀▀▀▀ ▄████ ▄██▀
15
+ // ▄▀ ▄▄█████████▄▄ ▀▀▀▀▀ ▄███ This file is provided completely
16
+ // ▄██████▀▀▀▀▀▀██████▄ ▀▄▄▄▄████▀ free, "as is", and without
17
+ // ████▀ ▄▄▄▄▄▄▄ ▀████▄ ▀█████▀ ▄▄▄▄ warranty of any kind. The author
18
+ // █████▄▄█████▀▀▀▀▀▀▄ ▀███▄ ▄████ assumes absolutely no liability
19
+ // ▀██████▀ ▀████▄▄▄████▀ for its {ab,mis,}use.
20
+ // ▀█████▀▀
21
+ //
22
+ // src/ts/kyber/index.ts
23
+ //
24
+ // ML-KEM public API — MlKem512, MlKem768, MlKem1024 classes.
25
+ // Uses the init() module cache — call init({ kyber: ..., sha3: ... }) before constructing.
26
+ import { getInstance, initModule, isInitialized } from '../init.js';
27
+ import { randomBytes, wipe } from '../utils.js';
28
+ import { MLKEM512, MLKEM768, MLKEM1024 } from './params.js';
29
+ import { kemKeypairDerand, kemEncapsulateDerand, kemDecapsulate } from './kem.js';
30
+ import { checkEncapsulationKey, checkDecapsulationKey } from './validate.js';
31
+ export async function kyberInit(source) {
32
+ return initModule('kyber', source);
33
+ }
34
+ export function _kyberReady() {
35
+ try {
36
+ getInstance('kyber');
37
+ getInstance('sha3');
38
+ return true;
39
+ }
40
+ catch {
41
+ return false;
42
+ }
43
+ }
44
+ export { MLKEM512, MLKEM768, MLKEM1024 };
45
+ export { isInitialized };
46
+ export { KyberSuite } from './suite.js';
47
+ // ── Layout assertion ──────────────────────────────────────────────────────────
48
+ function assertLayout(kx, p) {
49
+ const pk = kx.getPkOffset();
50
+ const sk = kx.getSkOffset();
51
+ const ct = kx.getCtOffset();
52
+ const ctPrime = kx.getCtPrimeOffset();
53
+ const xof = kx.getXofPrfOffset();
54
+ if (pk + p.ekBytes > sk)
55
+ throw new Error('leviathan-crypto: kyber buffer overflow — ek overflows into SK region');
56
+ if (sk + p.skCpaBytes > ct)
57
+ throw new Error('leviathan-crypto: kyber buffer overflow — sk overflows into CT region');
58
+ if (ct + p.ctBytes > ctPrime)
59
+ throw new Error('leviathan-crypto: kyber buffer overflow — ct overflows into CT_PRIME region');
60
+ if (ctPrime + p.ctBytes > xof)
61
+ throw new Error('leviathan-crypto: kyber buffer overflow — ct_prime overflows into XOF region');
62
+ }
63
+ // ── Base class ────────────────────────────────────────────────────────────────
64
+ export class MlKemBase {
65
+ params;
66
+ constructor(params) {
67
+ if (!isInitialized('kyber'))
68
+ throw new Error('leviathan-crypto: call init({ kyber: ... }) before using MlKem classes');
69
+ if (!isInitialized('sha3'))
70
+ throw new Error('leviathan-crypto: call init({ sha3: ... }) before using MlKem classes');
71
+ this.params = params;
72
+ assertLayout(this.kx, params);
73
+ }
74
+ get kx() {
75
+ return getInstance('kyber').exports;
76
+ }
77
+ get sx() {
78
+ return getInstance('sha3').exports;
79
+ }
80
+ keygenDerand(d, z) {
81
+ if (d.length !== 32)
82
+ throw new RangeError(`d seed must be 32 bytes (got ${d.length})`);
83
+ if (z.length !== 32)
84
+ throw new RangeError(`z seed must be 32 bytes (got ${z.length})`);
85
+ return kemKeypairDerand(this.kx, this.sx, this.params, d, z);
86
+ }
87
+ keygen() {
88
+ const d = randomBytes(32);
89
+ const z = randomBytes(32);
90
+ try {
91
+ return this.keygenDerand(d, z);
92
+ }
93
+ finally {
94
+ wipe(d);
95
+ wipe(z);
96
+ }
97
+ }
98
+ encapsulateDerand(ek, m) {
99
+ if (ek.length !== this.params.ekBytes)
100
+ throw new RangeError(`encapsulation key must be ${this.params.ekBytes} bytes (got ${ek.length})`);
101
+ if (m.length !== 32)
102
+ throw new RangeError(`randomness m must be 32 bytes (got ${m.length})`);
103
+ return kemEncapsulateDerand(this.kx, this.sx, this.params, ek, m);
104
+ }
105
+ encapsulate(ek) {
106
+ const m = randomBytes(32);
107
+ try {
108
+ return this.encapsulateDerand(ek, m);
109
+ }
110
+ finally {
111
+ wipe(m);
112
+ }
113
+ }
114
+ decapsulate(dk, c) {
115
+ if (dk.length !== this.params.dkBytes)
116
+ throw new RangeError(`decapsulation key must be ${this.params.dkBytes} bytes (got ${dk.length})`);
117
+ if (c.length !== this.params.ctBytes)
118
+ throw new RangeError(`ciphertext must be ${this.params.ctBytes} bytes (got ${c.length})`);
119
+ return kemDecapsulate(this.kx, this.sx, this.params, dk, c);
120
+ }
121
+ checkEncapsulationKey(ek) {
122
+ return checkEncapsulationKey(this.kx, this.params, ek);
123
+ }
124
+ checkDecapsulationKey(dk) {
125
+ return checkDecapsulationKey(this.kx, this.sx, this.params, dk);
126
+ }
127
+ dispose() {
128
+ this.kx.wipeBuffers();
129
+ this.sx.wipeBuffers();
130
+ }
131
+ }
132
+ // ── Public classes ────────────────────────────────────────────────────────────
133
+ /** ML-KEM-512 — k=2, η₁=3, η₂=2, dᵤ=10, dᵥ=4. */
134
+ export class MlKem512 extends MlKemBase {
135
+ constructor() {
136
+ super(MLKEM512);
137
+ }
138
+ }
139
+ /** ML-KEM-768 — k=3, η₁=2, η₂=2, dᵤ=10, dᵥ=4. */
140
+ export class MlKem768 extends MlKemBase {
141
+ constructor() {
142
+ super(MLKEM768);
143
+ }
144
+ }
145
+ /** ML-KEM-1024 — k=4, η₁=2, η₂=2, dᵤ=11, dᵥ=5. */
146
+ export class MlKem1024 extends MlKemBase {
147
+ constructor() {
148
+ super(MLKEM1024);
149
+ }
150
+ }
@@ -0,0 +1,21 @@
1
+ import type { KyberExports, Sha3Exports, KyberKeyPair, KyberEncapsulation } from './types.js';
2
+ import type { KyberParams } from './params.js';
3
+ /**
4
+ * ML-KEM.KeyGen_internal (FIPS 203 Algorithm 15).
5
+ *
6
+ * dk = skCpa || ek || H(ek) || z
7
+ */
8
+ export declare function kemKeypairDerand(kx: KyberExports, sx: Sha3Exports, params: KyberParams, d: Uint8Array, z: Uint8Array): KyberKeyPair;
9
+ /**
10
+ * ML-KEM.Encaps_internal (FIPS 203 Algorithm 16).
11
+ *
12
+ * (K, r) = G(m || H(ek)), c = K-PKE.Encrypt(ek, m, r)
13
+ */
14
+ export declare function kemEncapsulateDerand(kx: KyberExports, sx: Sha3Exports, params: KyberParams, ek: Uint8Array, m: Uint8Array): KyberEncapsulation;
15
+ /**
16
+ * ML-KEM.Decaps_internal (FIPS 203 Algorithm 17).
17
+ *
18
+ * Constant-time: uses ct_verify and ct_cmov from kyber WASM.
19
+ * MUST NOT branch on secret data in JS — all comparison via WASM primitives.
20
+ */
21
+ export declare function kemDecapsulate(kx: KyberExports, sx: Sha3Exports, params: KyberParams, dk: Uint8Array, c: Uint8Array): Uint8Array;