@noble/post-quantum 0.5.4 → 0.6.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/src/ml-kem.ts CHANGED
@@ -21,7 +21,7 @@
21
21
  */
22
22
  /*! noble-post-quantum - MIT License (c) 2024 Paul Miller (paulmillr.com) */
23
23
  import { sha3_256, sha3_512, shake256 } from '@noble/hashes/sha3.js';
24
- import { type CHash, u32 } from '@noble/hashes/utils.js';
24
+ import { type CHash, swap32IfBE, u32 } from '@noble/hashes/utils.js';
25
25
  import { genCrystals, type XOF, XOF128 } from './_crystals.ts';
26
26
  import {
27
27
  abytes,
@@ -29,6 +29,7 @@ import {
29
29
  type Coder,
30
30
  copyBytes,
31
31
  equalBytes,
32
+ getMask,
32
33
  type KEM,
33
34
  randomBytes,
34
35
  splitCoder,
@@ -41,7 +42,10 @@ const N = 256; // Kyber (not FIPS-203) supports different lengths, but all std m
41
42
  const Q = 3329; // 13*(2**8)+1, modulo prime
42
43
  const F = 3303; // 3303 ≡ 128**(−1) mod q (FIPS-203)
43
44
  const ROOT_OF_UNITY = 17; // ζ = 17 ∈ Zq is a primitive 256-th root of unity modulo Q. ζ**128 ≡−1
44
- const { mod, nttZetas, NTT, bitsCoder } = genCrystals({
45
+ // treeshake: keep genCrystals behind the object so PARAMS-only bundles can drop it entirely.
46
+ // Shared CRYSTALS helper in the ML-KEM branch: Kyber mode, 7-bit bit-reversal,
47
+ // and Uint16Array polys because current coefficients stay reduced modulo q.
48
+ const crystals = /* @__PURE__ */ genCrystals({
45
49
  N,
46
50
  Q,
47
51
  F,
@@ -52,67 +56,100 @@ const { mod, nttZetas, NTT, bitsCoder } = genCrystals({
52
56
  });
53
57
 
54
58
  /** FIPS 203: 7. Parameter Sets */
59
+ /** Public ML-KEM parameter-set description. */
55
60
  export type KEMParam = {
61
+ /** Polynomial size. */
56
62
  N: number;
63
+ /** Module rank. */
57
64
  K: number;
65
+ /** Prime modulus. */
58
66
  Q: number;
67
+ /** CBD parameter used for secret-key noise. */
59
68
  ETA1: number;
69
+ /** CBD parameter used for error noise. */
60
70
  ETA2: number;
71
+ /** Compression width for the `u` vector. */
61
72
  du: number;
73
+ /** Compression width for the `v` polynomial. */
62
74
  dv: number;
75
+ /** Required strength of the randomness source in bits. */
63
76
  RBGstrength: number;
64
77
  };
65
78
  /** Internal params of ML-KEM versions */
66
79
  // prettier-ignore
67
- export const PARAMS: Record<string, KEMParam> = {
80
+ /** Built-in ML-KEM parameter presets keyed by the public export names
81
+ * `ml_kem512` / `ml_kem768` / `ml_kem1024`.
82
+ * `RBGstrength` is Table 2's required randomness-source strength in bits,
83
+ * not a generic security label.
84
+ */
85
+ export const PARAMS: Record<string, KEMParam> = /* @__PURE__ */ (() => ({
68
86
  512: { N, Q, K: 2, ETA1: 3, ETA2: 2, du: 10, dv: 4, RBGstrength: 128 },
69
87
  768: { N, Q, K: 3, ETA1: 2, ETA2: 2, du: 10, dv: 4, RBGstrength: 192 },
70
88
  1024:{ N, Q, K: 4, ETA1: 2, ETA2: 2, du: 11, dv: 5, RBGstrength: 256 },
71
- } as const;
89
+ } as const))();
72
90
 
73
91
  // FIPS-203: compress/decompress
74
92
  const compress = (d: number): Coder<number, number> => {
75
- // Special case, no need to compress, pass as is, but strip high bytes on compression
76
- if (d >= 12) return { encode: (i: number) => i, decode: (i: number) => i };
77
- // NOTE: we don't use float arithmetic (forbidden by FIPS-203 and high chance of bugs).
93
+ // d=12 is the ByteEncode12/ByteDecode12 path, not lossy compression.
94
+ // ByteDecode12 interprets each 12-bit word modulo q; without that reduction the public-key
95
+ // modulus check in encapsulate() becomes a no-op for malformed coefficients like 4095.
96
+ if (d >= 12) return { encode: (i: number) => i, decode: (i: number) => (i >= Q ? i - Q : i) };
78
97
  // Comments map to python implementation in RFC (draft-cfrg-schwabe-kyber)
79
98
  // const round = (i: number) => Math.floor(i + 0.5) | 0;
80
99
  const a = 2 ** (d - 1);
81
100
  return {
82
- // const compress = (i: number) => round((2 ** d / Q) * i) % 2 ** d;
101
+ // This only matches standalone Compress_d after bitsCoder masks the result into Z_(2^d).
83
102
  encode: (i: number) => ((i << d) + Q / 2) / Q,
84
103
  // const decompress = (i: number) => round((Q / 2 ** d) * i);
85
104
  decode: (i: number) => (i * Q + a) >>> d,
86
105
  };
87
106
  };
88
107
 
108
+ // Raw ByteEncode_d / ByteDecode_d from FIPS 203 operate on d-bit words directly.
109
+ // That differs from `polyCoder(d)` for d<12, where noble folds packing together with the lossy
110
+ // ciphertext compression step used by u/v. Tests that exercise the spec's raw packing surface need
111
+ // this exact non-lossy variant instead.
112
+ const byteCoder = (d: number) =>
113
+ crystals.bitsCoder(
114
+ d,
115
+ d === 12
116
+ ? { encode: (i: number) => i, decode: (i: number) => (i >= Q ? i - Q : i) }
117
+ : { encode: (i: number) => i, decode: (i: number) => i }
118
+ );
119
+
89
120
  // NOTE: we merge encoding and compress because it is faster, also both require same d param
90
- // Converts between bytes and d-bits compressed representation. Kinda like convertRadix2 from @scure/base
121
+ // d=12 is the ByteEncode12/ByteDecode12 path rather than compression, and caller-side
122
+ // public-key modulus checks route through this helper's decode/encode roundtrip.
123
+ // Converts between bytes and d-bits compressed representation.
124
+ // Kinda like convertRadix2 from @scure/base.
91
125
  // decode(encode(t)) == t, but there is loss of information on encode(decode(t))
92
- const polyCoder = (d: number) => bitsCoder(d, compress(d));
126
+ const polyCoder = (d: number) => (d === 12 ? byteCoder(12) : crystals.bitsCoder(d, compress(d)));
93
127
 
94
128
  // Poly is mod Q, so 12 bits
95
129
  type Poly = Uint16Array<any>;
96
130
 
97
131
  function polyAdd(a: Poly, b: Poly) {
98
- for (let i = 0; i < N; i++) a[i] = mod(a[i] + b[i]); // a += b
132
+ // Mutates `a` in place; callers must pass two N=256 polynomials.
133
+ for (let i = 0; i < N; i++) a[i] = crystals.mod(a[i] + b[i]); // a += b
99
134
  }
100
135
  function polySub(a: Poly, b: Poly) {
101
- for (let i = 0; i < N; i++) a[i] = mod(a[i] - b[i]); // a -= b
136
+ // Mutates `a` in place; callers must pass two N=256 polynomials.
137
+ for (let i = 0; i < N; i++) a[i] = crystals.mod(a[i] - b[i]); // a -= b
102
138
  }
103
139
 
104
140
  // FIPS-203: Computes the product of two degree-one polynomials with respect to a quadratic modulus
105
141
  function BaseCaseMultiply(a0: number, a1: number, b0: number, b1: number, zeta: number) {
106
- const c0 = mod(a1 * b1 * zeta + a0 * b0);
107
- const c1 = mod(a0 * b1 + a1 * b0);
142
+ // `zeta` here is Algorithm 11's γ = ζ^(2BitRev_7(i)+1).
143
+ const c0 = crystals.mod(a1 * b1 * zeta + a0 * b0);
144
+ const c1 = crystals.mod(a0 * b1 + a1 * b0);
108
145
  return { c0, c1 };
109
146
  }
110
147
 
111
- // FIPS-203: Computes the product (in the ring Tq) of two NTT representations. NOTE: works inplace for f
112
- // NOTE: since multiply defined only for NTT representation, we need to convert to NTT, multiply and convert back
148
+ // FIPS-203: Computes the product (in the ring Tq) of two NTT representations.
149
+ // Works in place on `f`; `g` is read-only and both inputs must already be in NTT form.
113
150
  function MultiplyNTTs(f: Poly, g: Poly): Poly {
114
151
  for (let i = 0; i < N / 2; i++) {
115
- let z = nttZetas[64 + (i >> 1)];
152
+ let z = crystals.nttZetas[64 + (i >> 1)];
116
153
  if (i & 1) z = -z;
117
154
  const { c0, c1 } = BaseCaseMultiply(f[2 * i + 0], f[2 * i + 1], g[2 * i + 0], g[2 * i + 1], z);
118
155
  f[2 * i + 0] = c0;
@@ -136,6 +173,8 @@ type KyberOpts = KEMParam & {
136
173
 
137
174
  // Return poly in NTT representation
138
175
  function SampleNTT(xof: XofGet) {
176
+ // The reader must already bind the Algorithm 7 seed||j||i bytes
177
+ // and return block lengths divisible by 3.
139
178
  const r: Poly = new Uint16Array(N);
140
179
  for (let j = 0; j < N; ) {
141
180
  const b = xof();
@@ -151,11 +190,14 @@ function SampleNTT(xof: XofGet) {
151
190
  }
152
191
 
153
192
  // Sampling from the centered binomial distribution
154
- // Returns poly with small coefficients (noise/errors)
155
- function sampleCBD(PRF: PRF, seed: Uint8Array, nonce: number, eta: number): Poly {
156
- const buf = PRF((eta * N) / 4, seed, nonce);
193
+ // Returns poly with small coefficients (noise/errors) stored modulo q in ordinary coefficient form.
194
+ // Current callers only use Table 2 eta values {2,3} and PRF outputs of exactly 64*eta bytes.
195
+ const sampleCBDBytes = (buf: Uint8Array, eta: number): Poly => {
157
196
  const r: Poly = new Uint16Array(N);
197
+ // CBD consumes the PRF bitstream in little-endian byte order; normalize the word view on BE,
198
+ // then swap it back so callers still observe `buf` as read-only.
158
199
  const b32 = u32(buf);
200
+ swap32IfBE(b32);
159
201
  let len = 0;
160
202
  for (let i = 0, p = 0, bb = 0, t0 = 0; i < b32.length; i++) {
161
203
  let b = b32[i];
@@ -167,18 +209,25 @@ function sampleCBD(PRF: PRF, seed: Uint8Array, nonce: number, eta: number): Poly
167
209
  t0 = bb;
168
210
  bb = 0;
169
211
  } else if (len === 2 * eta) {
170
- r[p++] = mod(t0 - bb);
212
+ r[p++] = crystals.mod(t0 - bb);
171
213
  bb = 0;
172
214
  len = 0;
173
215
  }
174
216
  }
175
217
  }
218
+ swap32IfBE(b32);
176
219
  if (len) throw new Error(`sampleCBD: leftover bits: ${len}`);
177
220
  return r;
221
+ };
222
+
223
+ function sampleCBD(PRF: PRF, seed: Uint8Array, nonce: number, eta: number): Poly {
224
+ return sampleCBDBytes(PRF((eta * N) / 4, seed, nonce), eta);
178
225
  }
179
226
 
180
227
  // K-PKE
181
- // As per FIPS-203, it doesn't perform any input validation and can't be used in standalone fashion.
228
+ // Internal ML-KEM subroutine only: exact 32-byte `seed` / `msg` inputs
229
+ // come from Algorithms 13-15, and the helper mutates decoded temporary
230
+ // polynomials in place while leaving caller byte arrays unchanged.
182
231
  const genKPKE = (opts: KyberOpts) => {
183
232
  const { K, PRF, XOF, HASH512, ETA1, ETA2, du, dv } = opts;
184
233
  const poly1 = polyCoder(1);
@@ -199,18 +248,21 @@ const genKPKE = (opts: KyberOpts) => {
199
248
  abytes(seed, 32, 'seed');
200
249
  const seedDst = new Uint8Array(33);
201
250
  seedDst.set(seed);
251
+ // FIPS 203 Algorithm 13 appends the parameter-set byte `k`
252
+ // before `G(d || k)`, so expanding the same 32-byte seed
253
+ // under a different ML-KEM parameter set yields unrelated keys.
202
254
  seedDst[32] = K;
203
255
  const seedHash = HASH512(seedDst);
204
256
 
205
257
  const [rho, sigma] = seedCoder.decode(seedHash);
206
258
  const sHat: Poly[] = [];
207
259
  const tHat: Poly[] = [];
208
- for (let i = 0; i < K; i++) sHat.push(NTT.encode(sampleCBD(PRF, sigma, i, ETA1)));
260
+ for (let i = 0; i < K; i++) sHat.push(crystals.NTT.encode(sampleCBD(PRF, sigma, i, ETA1)));
209
261
  const x = XOF(rho);
210
262
  for (let i = 0; i < K; i++) {
211
- const e = NTT.encode(sampleCBD(PRF, sigma, K + i, ETA1));
263
+ const e = crystals.NTT.encode(sampleCBD(PRF, sigma, K + i, ETA1));
212
264
  for (let j = 0; j < K; j++) {
213
- const aji = SampleNTT(x.get(j, i)); // A[j][i], inplace
265
+ const aji = SampleNTT(x.get(j, i)); // A[i][j], inplace
214
266
  polyAdd(e, MultiplyNTTs(aji, sHat[j]));
215
267
  }
216
268
  tHat.push(e); // t ← A ◦ s + e
@@ -226,7 +278,7 @@ const genKPKE = (opts: KyberOpts) => {
226
278
  encrypt: (publicKey: Uint8Array, msg: Uint8Array, seed: Uint8Array) => {
227
279
  const [tHat, rho] = publicCoder.decode(publicKey);
228
280
  const rHat = [];
229
- for (let i = 0; i < K; i++) rHat.push(NTT.encode(sampleCBD(PRF, seed, i, ETA1)));
281
+ for (let i = 0; i < K; i++) rHat.push(crystals.NTT.encode(sampleCBD(PRF, seed, i, ETA1)));
230
282
  const x = XOF(rho);
231
283
  const tmp2 = new Uint16Array(N);
232
284
  const u = [];
@@ -234,17 +286,17 @@ const genKPKE = (opts: KyberOpts) => {
234
286
  const e1 = sampleCBD(PRF, seed, K + i, ETA2);
235
287
  const tmp = new Uint16Array(N);
236
288
  for (let j = 0; j < K; j++) {
237
- const aij = SampleNTT(x.get(i, j)); // A[i][j], inplace
289
+ const aij = SampleNTT(x.get(i, j)); // A[j][i], inplace transpose access
238
290
  polyAdd(tmp, MultiplyNTTs(aij, rHat[j])); // t += aij * rHat[j]
239
291
  }
240
- polyAdd(e1, NTT.decode(tmp)); // e1 += tmp
292
+ polyAdd(e1, crystals.NTT.decode(tmp)); // e1 += tmp
241
293
  u.push(e1);
242
294
  polyAdd(tmp2, MultiplyNTTs(tHat[i], rHat[i])); // t2 += tHat[i] * rHat[i]
243
295
  cleanBytes(tmp);
244
296
  }
245
297
  x.clean();
246
298
  const e2 = sampleCBD(PRF, seed, 2 * K, ETA2);
247
- polyAdd(e2, NTT.decode(tmp2)); // e2 += tmp2
299
+ polyAdd(e2, crystals.NTT.decode(tmp2)); // e2 += tmp2
248
300
  const v = poly1.decode(msg); // encode plaintext m into polynomial v
249
301
  polyAdd(v, e2); // v += e2
250
302
  cleanBytes(tHat, rHat, tmp2, e2);
@@ -254,14 +306,24 @@ const genKPKE = (opts: KyberOpts) => {
254
306
  const [u, v] = cipherCoder.decode(cipherText);
255
307
  const sk = secretCoder.decode(privateKey); // s ← ByteDecode_12(dkPKE)
256
308
  const tmp = new Uint16Array(N);
257
- for (let i = 0; i < K; i++) polyAdd(tmp, MultiplyNTTs(sk[i], NTT.encode(u[i]))); // tmp += sk[i] * u[i]
258
- polySub(v, NTT.decode(tmp)); // v += tmp
309
+ // tmp += sk[i] * u[i]
310
+ for (let i = 0; i < K; i++) polyAdd(tmp, MultiplyNTTs(sk[i], crystals.NTT.encode(u[i])));
311
+ polySub(v, crystals.NTT.decode(tmp)); // w = v' - tmp
259
312
  cleanBytes(tmp, sk, u);
260
313
  return poly1.encode(v);
261
314
  },
262
315
  };
263
316
  };
264
317
 
318
+ /**
319
+ * Public ML-KEM wrapper over the internal K-PKE subroutine.
320
+ * `keygen(seed)` and `encapsulate(publicKey, msg)` are deterministic/test-oriented hooks that map
321
+ * more directly to Algorithms 16-17 than to the pure no-input / random-internal Algorithms 19-20.
322
+ * decapsulate() tries to follow the Algorithms 18/21 implicit-reject structure as closely as
323
+ * practical here by re-encrypting, comparing ciphertexts, returning `Khat` on match or `Kbar` on
324
+ * mismatch, and zeroizing the non-returned shared-secret candidate; JS/JIT still provides no
325
+ * constant-time guarantees for that path.
326
+ */
265
327
  function createKyber(opts: KyberOpts) {
266
328
  const KPKE = genKPKE(opts);
267
329
  const { HASH256, HASH512, KDF } = opts;
@@ -297,7 +359,8 @@ function createKyber(opts: KyberOpts) {
297
359
 
298
360
  // FIPS-203 includes additional verification check for modulus
299
361
  const eke = publicKey.subarray(0, 384 * opts.K);
300
- const ek = KPKESecretCoder.encode(KPKESecretCoder.decode(copyBytes(eke))); // Copy because of inplace encoding
362
+ // Copy because of inplace encoding
363
+ const ek = KPKESecretCoder.encode(KPKESecretCoder.decode(copyBytes(eke)));
301
364
  // (Modulus check.) Perform the computation ek ← ByteEncode12(ByteDecode12(eke)).
302
365
  // If ek = ̸ eke, the input is invalid. (See Section 4.2.1.)
303
366
  if (!equalBytes(ek, eke)) {
@@ -305,7 +368,8 @@ function createKyber(opts: KyberOpts) {
305
368
  throw new Error('ML-KEM.encapsulate: wrong publicKey modulus');
306
369
  }
307
370
  cleanBytes(ek);
308
- const kr = HASH512.create().update(msg).update(HASH256(publicKey)).digest(); // derive randomness
371
+ // derive randomness
372
+ const kr = HASH512.create().update(msg).update(HASH256(publicKey)).digest();
309
373
  const cipherText = KPKE.encrypt(publicKey, msg, kr.subarray(32, 64));
310
374
  cleanBytes(kr.subarray(32));
311
375
  return { cipherText, sharedSecret: kr.subarray(0, 32) };
@@ -322,10 +386,13 @@ function createKyber(opts: KyberOpts) {
322
386
  throw new Error('invalid secretKey: hash check failed');
323
387
  const [sk, publicKey, publicKeyHash, z] = secretCoder.decode(secretKey);
324
388
  const msg = KPKE.decrypt(cipherText, sk);
325
- const kr = HASH512.create().update(msg).update(publicKeyHash).digest(); // derive randomness, Khat, rHat = G(mHat || h)
389
+ // derive randomness, Khat, rHat = G(mHat || h)
390
+ const kr = HASH512.create().update(msg).update(publicKeyHash).digest();
326
391
  const Khat = kr.subarray(0, 32);
327
- const cipherText2 = KPKE.encrypt(publicKey, msg, kr.subarray(32, 64)); // re-encrypt using the derived randomness
328
- const isValid = equalBytes(cipherText, cipherText2); // if ciphertexts do not match, “implicitly reject”
392
+ // re-encrypt using the derived randomness
393
+ const cipherText2 = KPKE.encrypt(publicKey, msg, kr.subarray(32, 64));
394
+ // if ciphertexts do not match, “implicitly reject”
395
+ const isValid = equalBytes(cipherText, cipherText2);
329
396
  const Kbar = KDF.create({ dkLen: 32 }).update(z).update(cipherText).digest();
330
397
  cleanBytes(msg, cipherText2, !isValid ? Khat : Kbar);
331
398
  return isValid ? Khat : Kbar;
@@ -333,6 +400,8 @@ function createKyber(opts: KyberOpts) {
333
400
  };
334
401
  }
335
402
 
403
+ // FIPS 203's PRF_eta binding: current callers use only 32-byte keys, one-byte nonces,
404
+ // and dkLen values {128, 192}; out-of-range nonce numbers still wrap modulo 256 here.
336
405
  function shakePRF(dkLen: number, key: Uint8Array, nonce: number) {
337
406
  return shake256
338
407
  .create({ dkLen })
@@ -341,28 +410,73 @@ function shakePRF(dkLen: number, key: Uint8Array, nonce: number) {
341
410
  .digest();
342
411
  }
343
412
 
344
- const opts = {
413
+ // Fixed ML-KEM hash/XOF bindings. `KDF` here is the spec's fixed 32-byte `J` call,
414
+ // and swapping any field changes the scheme rather than tuning an internal dependency.
415
+ const opts = /* @__PURE__ */ (() => ({
345
416
  HASH256: sha3_256,
346
417
  HASH512: sha3_512,
347
418
  KDF: shake256,
348
419
  XOF: XOF128,
349
420
  PRF: shakePRF,
350
- };
351
-
352
- /** ML-KEM-512 for 128-bit security level. Not recommended after 2030, as per ASD. */
353
- export const ml_kem512: KEM = /* @__PURE__ */ createKyber({
354
- ...opts,
355
- ...PARAMS[512],
356
- });
421
+ }))();
422
+ // Parameter-set instantiation step for the spec's "ML-KEM-x" names; current correctness relies
423
+ // on the internal PARAMS rows rather than local validation of arbitrary KEMParam objects.
424
+ const mk = (params: KEMParam) =>
425
+ createKyber({
426
+ ...opts,
427
+ ...params,
428
+ });
357
429
 
358
- /** ML-KEM-768, for 192-bit security level. Not recommended after 2030, as per ASD. */
359
- export const ml_kem768: KEM = /* @__PURE__ */ createKyber({
360
- ...opts,
361
- ...PARAMS[768],
362
- });
430
+ /**
431
+ * ML-KEM-512: Table 2 row `k=2, η1=3, η2=2, du=10, dv=4`; Table 3 sizes `800/1632/768/32`.
432
+ * The ASD lifecycle note here is external policy guidance, not a FIPS 203 requirement.
433
+ */
434
+ export const ml_kem512: KEM = /* @__PURE__ */ (() => mk(PARAMS[512]))();
435
+ /**
436
+ * ML-KEM-768: Table 2 row `k=3, η1=2, η2=2, du=10, dv=4`; Table 3 sizes `1184/2400/1088/32`.
437
+ * The ASD lifecycle note here is external policy guidance, not a FIPS 203 requirement.
438
+ */
439
+ export const ml_kem768: KEM = /* @__PURE__ */ (() => mk(PARAMS[768]))();
440
+ /**
441
+ * ML-KEM-1024: Table 2 row `k=4, η1=2, η2=2, du=11, dv=5`; Table 3 sizes `1568/3168/1568/32`.
442
+ * The ASD lifecycle note here is external policy guidance, not a FIPS 203 requirement.
443
+ */
444
+ export const ml_kem1024: KEM = /* @__PURE__ */ (() => mk(PARAMS[1024]))();
363
445
 
364
- /** ML-KEM-1024 for 256-bit security level. OK after 2030, as per ASD. */
365
- export const ml_kem1024: KEM = /* @__PURE__ */ createKyber({
366
- ...opts,
367
- ...PARAMS[1024],
368
- });
446
+ // NOTE: for tests only, don't use. This keeps the exact internal ML-KEM math surfaces available
447
+ // without re-implementing them in separate test code.
448
+ export const __tests: any = /* @__PURE__ */ (() => ({
449
+ Compress_d: (x: number, d: number) => {
450
+ if (d < 1 || d > 11) throw new Error(`Compress_d: expected d in [1..11], got ${d}`);
451
+ return compress(d).encode(x) & getMask(d);
452
+ },
453
+ Decompress_d: (y: number, d: number) => {
454
+ if (d < 1 || d > 11) throw new Error(`Decompress_d: expected d in [1..11], got ${d}`);
455
+ return compress(d).decode(y);
456
+ },
457
+ ByteEncode_d: (F: Uint16Array, d: number) => {
458
+ if (d < 1 || d > 12) throw new Error(`ByteEncode_d: expected d in [1..12], got ${d}`);
459
+ return byteCoder(d).encode(F);
460
+ },
461
+ ByteDecode_d: (B: Uint8Array, d: number) => {
462
+ if (d < 1 || d > 12) throw new Error(`ByteDecode_d: expected d in [1..12], got ${d}`);
463
+ return byteCoder(d).decode(B);
464
+ },
465
+ NTT: (f: Uint16Array) => crystals.NTT.encode(Uint16Array.from(f)),
466
+ NTT_inv: (fHat: Uint16Array) => crystals.NTT.decode(Uint16Array.from(fHat)),
467
+ MultiplyNTTs: (fHat: Uint16Array, gHat: Uint16Array) =>
468
+ MultiplyNTTs(Uint16Array.from(fHat), Uint16Array.from(gHat)),
469
+ SamplePolyCBD: (B: Uint8Array, eta: number) => {
470
+ abytes(B, 64 * eta, 'B');
471
+ return sampleCBDBytes(B, eta);
472
+ },
473
+ SampleNTT: (B: Uint8Array) => {
474
+ abytes(B, 34, 'B');
475
+ const xof = XOF128(B.subarray(0, 32));
476
+ try {
477
+ return SampleNTT(xof.get(B[32], B[33]));
478
+ } finally {
479
+ xof.clean();
480
+ }
481
+ },
482
+ }))();