leviathan-crypto 1.3.1 → 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.
- package/CLAUDE.md +129 -76
- package/README.md +166 -221
- package/SECURITY.md +89 -37
- package/dist/chacha20/cipher-suite.d.ts +4 -0
- package/dist/chacha20/cipher-suite.js +78 -0
- package/dist/chacha20/embedded.d.ts +1 -0
- package/dist/chacha20/embedded.js +27 -0
- package/dist/chacha20/index.d.ts +20 -7
- package/dist/chacha20/index.js +41 -14
- package/dist/chacha20/ops.d.ts +1 -1
- package/dist/chacha20/ops.js +19 -18
- package/dist/chacha20/pool-worker.js +77 -0
- package/dist/ct-wasm.d.ts +1 -0
- package/dist/ct-wasm.js +3 -0
- package/dist/ct.wasm +0 -0
- package/dist/docs/aead.md +320 -0
- package/dist/docs/architecture.md +419 -285
- package/dist/docs/argon2id.md +42 -30
- package/dist/docs/chacha20.md +218 -150
- package/dist/docs/exports.md +241 -0
- package/dist/docs/fortuna.md +65 -74
- package/dist/docs/init.md +172 -178
- package/dist/docs/loader.md +87 -132
- package/dist/docs/serpent.md +134 -565
- package/dist/docs/sha2.md +91 -103
- package/dist/docs/sha3.md +70 -36
- package/dist/docs/types.md +93 -16
- package/dist/docs/utils.md +114 -41
- package/dist/embedded/chacha20.d.ts +1 -1
- package/dist/embedded/chacha20.js +2 -1
- package/dist/embedded/kyber.d.ts +1 -0
- package/dist/embedded/kyber.js +3 -0
- package/dist/embedded/serpent.d.ts +1 -1
- package/dist/embedded/serpent.js +2 -1
- package/dist/embedded/sha2.d.ts +1 -1
- package/dist/embedded/sha2.js +2 -1
- package/dist/embedded/sha3.d.ts +1 -1
- package/dist/embedded/sha3.js +2 -1
- package/dist/errors.d.ts +10 -0
- package/dist/{serpent/seal.js → errors.js} +14 -46
- package/dist/fortuna.d.ts +2 -8
- package/dist/fortuna.js +11 -9
- package/dist/index.d.ts +25 -9
- package/dist/index.js +36 -7
- package/dist/init.d.ts +3 -7
- package/dist/init.js +18 -35
- package/dist/keccak/embedded.d.ts +1 -0
- package/dist/keccak/embedded.js +27 -0
- package/dist/keccak/index.d.ts +4 -0
- package/dist/keccak/index.js +31 -0
- package/dist/kyber/embedded.d.ts +1 -0
- package/dist/kyber/embedded.js +27 -0
- package/dist/kyber/indcpa.d.ts +49 -0
- package/dist/kyber/indcpa.js +352 -0
- package/dist/kyber/index.d.ts +38 -0
- package/dist/kyber/index.js +150 -0
- package/dist/kyber/kem.d.ts +21 -0
- package/dist/kyber/kem.js +160 -0
- package/dist/kyber/params.d.ts +14 -0
- package/dist/kyber/params.js +37 -0
- package/dist/kyber/suite.d.ts +13 -0
- package/dist/kyber/suite.js +93 -0
- package/dist/kyber/types.d.ts +98 -0
- package/dist/kyber/types.js +25 -0
- package/dist/kyber/validate.d.ts +19 -0
- package/dist/kyber/validate.js +68 -0
- package/dist/kyber.wasm +0 -0
- package/dist/loader.d.ts +19 -4
- package/dist/loader.js +91 -25
- package/dist/serpent/cipher-suite.d.ts +4 -0
- package/dist/serpent/cipher-suite.js +121 -0
- package/dist/serpent/embedded.d.ts +1 -0
- package/dist/serpent/embedded.js +27 -0
- package/dist/serpent/index.d.ts +6 -37
- package/dist/serpent/index.js +9 -118
- package/dist/serpent/pool-worker.d.ts +1 -0
- package/dist/serpent/pool-worker.js +202 -0
- package/dist/serpent/serpent-cbc.d.ts +30 -0
- package/dist/serpent/serpent-cbc.js +136 -0
- package/dist/sha2/embedded.d.ts +1 -0
- package/dist/sha2/embedded.js +27 -0
- package/dist/sha2/hkdf.js +6 -2
- package/dist/sha2/index.d.ts +3 -2
- package/dist/sha2/index.js +3 -4
- package/dist/sha3/embedded.d.ts +1 -0
- package/dist/sha3/embedded.js +27 -0
- package/dist/sha3/index.d.ts +3 -2
- package/dist/sha3/index.js +3 -4
- package/dist/stream/constants.d.ts +6 -0
- package/dist/stream/constants.js +30 -0
- package/dist/stream/header.d.ts +9 -0
- package/dist/stream/header.js +77 -0
- package/dist/stream/index.d.ts +7 -0
- package/dist/stream/index.js +27 -0
- package/dist/stream/open-stream.d.ts +21 -0
- package/dist/stream/open-stream.js +146 -0
- package/dist/stream/seal-stream-pool.d.ts +38 -0
- package/dist/stream/seal-stream-pool.js +391 -0
- package/dist/stream/seal-stream.d.ts +20 -0
- package/dist/stream/seal-stream.js +142 -0
- package/dist/stream/seal.d.ts +9 -0
- package/dist/stream/seal.js +75 -0
- package/dist/stream/types.d.ts +24 -0
- package/dist/stream/types.js +26 -0
- package/dist/utils.d.ts +12 -7
- package/dist/utils.js +75 -19
- package/dist/wasm-source.d.ts +12 -0
- package/dist/wasm-source.js +26 -0
- package/package.json +13 -5
- package/dist/chacha20/pool.d.ts +0 -52
- package/dist/chacha20/pool.js +0 -188
- package/dist/chacha20/pool.worker.js +0 -37
- package/dist/docs/chacha20_pool.md +0 -309
- package/dist/docs/wasm.md +0 -194
- package/dist/serpent/seal.d.ts +0 -8
- package/dist/serpent/stream-pool.d.ts +0 -48
- package/dist/serpent/stream-pool.js +0 -285
- package/dist/serpent/stream-sealer.d.ts +0 -50
- package/dist/serpent/stream-sealer.js +0 -341
- package/dist/serpent/stream.d.ts +0 -28
- package/dist/serpent/stream.js +0 -205
- package/dist/serpent/stream.worker.d.ts +0 -32
- package/dist/serpent/stream.worker.js +0 -117
- /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;
|