@noble/post-quantum 0.5.3 → 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/_crystals.ts CHANGED
@@ -8,28 +8,78 @@ import { shake128, shake256 } from '@noble/hashes/sha3.js';
8
8
  import type { TypedArray } from '@noble/hashes/utils.js';
9
9
  import { type BytesCoderLen, cleanBytes, type Coder, getMask } from './utils.ts';
10
10
 
11
+ /** Extendable-output reader used by the CRYSTALS implementations. */
11
12
  export type XOF = (
12
13
  seed: Uint8Array,
13
14
  blockLen?: number
14
15
  ) => {
16
+ /**
17
+ * Read diagnostic counters for the current XOF session.
18
+ * @returns Current call and XOF block counters.
19
+ */
15
20
  stats: () => { calls: number; xofs: number };
21
+ /**
22
+ * Select one `(x, y)` coordinate pair and get a block reader for it.
23
+ * Only one coordinate stream is live at a time: a later `get(...)` call rebinds the shared
24
+ * SHAKE state and invalidates older readers.
25
+ * Each squeeze aliases one mutable internal output buffer, so callers must copy blocks they
26
+ * want to retain before the next read.
27
+ * @param x - First matrix coordinate.
28
+ * @param y - Second matrix coordinate.
29
+ * @returns Lazy block reader for that coordinate pair.
30
+ */
16
31
  get: (x: number, y: number) => () => Uint8Array; // return block aligned to blockLen and 3
32
+ /** Wipe any buffered state once the reader is no longer needed. */
17
33
  clean: () => void;
18
34
  };
19
35
 
20
36
  /** CRYSTALS (ml-kem, ml-dsa) options */
37
+ /** Shared polynomial and NTT parameters for CRYSTALS algorithms. */
21
38
  export type CrystalOpts<T extends TypedArray> = {
39
+ /**
40
+ * Allocate one zeroed polynomial/vector container.
41
+ * @param n - Number of coefficients to allocate.
42
+ * @returns Fresh typed container.
43
+ */
22
44
  newPoly: TypedCons<T>;
23
- N: number; // poly size, 256
24
- Q: number; // modulo
25
- F: number; // 256**−1 mod q for dilithium, 128**−1 mod q for kyber
45
+ /** Polynomial size, typically `256`. */
46
+ N: number;
47
+ /** Prime modulus used for all coefficient arithmetic. */
48
+ Q: number;
49
+ /** Inverse transform normalization factor:
50
+ * `256**-1 mod q` for Dilithium, `128**-1 mod q` for Kyber.
51
+ */
52
+ F: number;
53
+ /** Principal root of unity for the transform domain. */
26
54
  ROOT_OF_UNITY: number;
27
- brvBits: number; // bits for bitReversal
55
+ /** Number of bits used for bit-reversal ordering. */
56
+ brvBits: number;
57
+ /** `true` for Kyber/ML-KEM mode, `false` for Dilithium/ML-DSA mode. */
28
58
  isKyber: boolean;
29
59
  };
30
60
 
61
+ /** Constructor function for typed polynomial containers. */
31
62
  export type TypedCons<T extends TypedArray> = (n: number) => T;
32
63
 
64
+ /**
65
+ * Creates shared modular arithmetic, NTT, and packing helpers for CRYSTALS schemes.
66
+ * @param opts - Polynomial and transform parameters. See {@link CrystalOpts}.
67
+ * @returns CRYSTALS arithmetic and encoding helpers.
68
+ * @example
69
+ * Create shared modular arithmetic and NTT helpers for a CRYSTALS parameter set.
70
+ * ```ts
71
+ * const crystals = genCrystals({
72
+ * newPoly: (n) => new Uint16Array(n),
73
+ * N: 256,
74
+ * Q: 3329,
75
+ * F: 3303,
76
+ * ROOT_OF_UNITY: 17,
77
+ * brvBits: 7,
78
+ * isKyber: true,
79
+ * });
80
+ * const reduced = crystals.mod(-1);
81
+ * ```
82
+ */
33
83
  export const genCrystals = <T extends TypedArray>(
34
84
  opts: CrystalOpts<T>
35
85
  ): {
@@ -37,23 +87,30 @@ export const genCrystals = <T extends TypedArray>(
37
87
  smod: (a: number, modulo?: number) => number;
38
88
  nttZetas: T;
39
89
  NTT: {
90
+ /** Forward transform in place. Mutates and returns `r`. */
40
91
  encode: (r: T) => T;
92
+ /** Inverse transform in place. Mutates and returns `r`. */
41
93
  decode: (r: T) => T;
42
94
  };
43
95
  bitsCoder: (d: number, c: Coder<number, number>) => BytesCoderLen<T>;
44
96
  } => {
45
97
  // isKyber: true means Kyber, false means Dilithium
46
98
  const { newPoly, N, Q, F, ROOT_OF_UNITY, brvBits, isKyber } = opts;
99
+ // Normalize JS `%` into the canonical Z_m representative `[0, modulo-1]` expected by
100
+ // FIPS 203 §2.3 / FIPS 204 §2.3 before downstream mod-q arithmetic.
47
101
  const mod = (a: number, modulo = Q): number => {
48
102
  const result = a % modulo | 0;
49
103
  return (result >= 0 ? result | 0 : (modulo + result) | 0) | 0;
50
104
  };
51
- // -(Q-1)/2 < a <= (Q-1)/2
105
+ // FIPS 204 §7.4 uses the centered `mod ±` representative for low bits, keeping the
106
+ // positive midpoint when `modulo` is even.
107
+ // Center to `[-floor((modulo-1)/2), floor(modulo/2)]`.
52
108
  const smod = (a: number, modulo = Q): number => {
53
109
  const r = mod(a, modulo) | 0;
54
110
  return (r > modulo >> 1 ? (r - modulo) | 0 : r) | 0;
55
111
  };
56
- // Generate zettas (different from roots of unity, negacyclic uses phi, where acyclic uses omega)
112
+ // Kyber uses the FIPS 203 Appendix A `BitRev_7` table here via the first 128 entries, while
113
+ // Dilithium uses the FIPS 204 §7.5 / Appendix B `BitRev_8` zetas table over all 256 entries.
57
114
  function getZettas() {
58
115
  const out = newPoly(N);
59
116
  for (let i = 0; i < N; i++) {
@@ -94,12 +151,15 @@ export const genCrystals = <T extends TypedArray>(
94
151
  },
95
152
  decode: (r: T): T => {
96
153
  dit(r as any);
154
+ // The inverse-NTT normalization factor is family-specific: FIPS 203 Algorithm 10 line 14
155
+ // uses `128^-1 mod q` for Kyber, while FIPS 204 Algorithm 42 lines 21-23 use `256^-1 mod q`.
97
156
  // kyber uses 128 here, because brv && stuff
98
157
  for (let i = 0; i < r.length; i++) r[i] = mod(F * r[i]);
99
158
  return r;
100
159
  },
101
160
  };
102
- // Encode polynominal as bits
161
+ // Pack one little-endian `d`-bit word per coefficient, matching FIPS 203 ByteEncode /
162
+ // ByteDecode and the FIPS 204 BitsToBytes-based polynomial packing helpers.
103
163
  const bitsCoder = (d: number, c: Coder<number, number>): BytesCoderLen<T> => {
104
164
  const mask = getMask(d);
105
165
  const bytesLen = d * (N / 8);
@@ -148,6 +208,8 @@ const createXofShake =
148
208
  return {
149
209
  stats: () => ({ calls, xofs }),
150
210
  get: (x: number, y: number) => {
211
+ // Rebind to `seed || x || y` so callers can implement the spec's per-coordinate
212
+ // SHAKE inputs like `rho || j || i` and `rho || IntegerToBytes(counter, 2)`.
151
213
  _seed[seedLen + 0] = x;
152
214
  _seed[seedLen + 1] = y;
153
215
  h.destroy();
@@ -165,5 +227,37 @@ const createXofShake =
165
227
  };
166
228
  };
167
229
 
230
+ /**
231
+ * SHAKE128-based extendable-output reader factory used by ML-KEM.
232
+ * `get(x, y)` selects one coordinate pair at a time; calling it again invalidates previously
233
+ * returned readers, and each squeeze reuses one mutable internal output buffer.
234
+ * @param seed - Seed bytes for the reader.
235
+ * @param blockLen - Optional output block length.
236
+ * @returns Stateful XOF reader.
237
+ * @example
238
+ * Build the ML-KEM SHAKE128 matrix expander and read one block.
239
+ * ```ts
240
+ * import { randomBytes } from '@noble/post-quantum/utils.js';
241
+ * import { XOF128 } from '@noble/post-quantum/_crystals.js';
242
+ * const reader = XOF128(randomBytes(32));
243
+ * const block = reader.get(0, 0)();
244
+ * ```
245
+ */
168
246
  export const XOF128: XOF = /* @__PURE__ */ createXofShake(shake128);
247
+ /**
248
+ * SHAKE256-based extendable-output reader factory used by ML-DSA.
249
+ * `get(x, y)` appends raw one-byte coordinates to the seed, invalidates previously returned
250
+ * readers, and reuses one mutable internal output buffer for each squeeze.
251
+ * @param seed - Seed bytes for the reader.
252
+ * @param blockLen - Optional output block length.
253
+ * @returns Stateful XOF reader.
254
+ * @example
255
+ * Build the ML-DSA SHAKE256 coefficient expander and read one block.
256
+ * ```ts
257
+ * import { randomBytes } from '@noble/post-quantum/utils.js';
258
+ * import { XOF256 } from '@noble/post-quantum/_crystals.js';
259
+ * const reader = XOF256(randomBytes(32));
260
+ * const block = reader.get(0, 0)();
261
+ * ```
262
+ */
169
263
  export const XOF256: XOF = /* @__PURE__ */ createXofShake(shake256);