@noble/post-quantum 0.5.4 → 0.6.1
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/README.md +86 -42
- package/_crystals.d.ts +90 -4
- package/_crystals.d.ts.map +1 -1
- package/_crystals.js +77 -6
- package/_crystals.js.map +1 -1
- package/falcon.d.ts +84 -0
- package/falcon.d.ts.map +1 -0
- package/falcon.js +2385 -0
- package/falcon.js.map +1 -0
- package/hybrid.d.ts +194 -24
- package/hybrid.d.ts.map +1 -1
- package/hybrid.js +401 -77
- package/hybrid.js.map +1 -1
- package/index.js +8 -0
- package/index.js.map +1 -1
- package/ml-dsa.d.ts +29 -8
- package/ml-dsa.d.ts.map +1 -1
- package/ml-dsa.js +154 -78
- package/ml-dsa.js.map +1 -1
- package/ml-kem.d.ts +31 -7
- package/ml-kem.d.ts.map +1 -1
- package/ml-kem.js +194 -75
- package/ml-kem.js.map +1 -1
- package/package.json +15 -8
- package/slh-dsa.d.ts +137 -34
- package/slh-dsa.d.ts.map +1 -1
- package/slh-dsa.js +189 -68
- package/slh-dsa.js.map +1 -1
- package/src/_crystals.ts +135 -24
- package/src/falcon.ts +2503 -0
- package/src/hybrid.ts +515 -144
- package/src/index.ts +8 -0
- package/src/ml-dsa.ts +263 -138
- package/src/ml-kem.ts +240 -97
- package/src/slh-dsa.ts +391 -153
- package/src/utils.ts +491 -46
- package/utils.d.ts +362 -24
- package/utils.d.ts.map +1 -1
- package/utils.js +273 -20
- package/utils.js.map +1 -1
package/src/_crystals.ts
CHANGED
|
@@ -6,54 +6,118 @@
|
|
|
6
6
|
import { FFTCore, reverseBits } from '@noble/curves/abstract/fft.js';
|
|
7
7
|
import { shake128, shake256 } from '@noble/hashes/sha3.js';
|
|
8
8
|
import type { TypedArray } from '@noble/hashes/utils.js';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
type BytesCoderLen,
|
|
11
|
+
cleanBytes,
|
|
12
|
+
type Coder,
|
|
13
|
+
getMask,
|
|
14
|
+
type TArg,
|
|
15
|
+
type TRet,
|
|
16
|
+
} from './utils.ts';
|
|
10
17
|
|
|
18
|
+
/** Extendable-output reader used by the CRYSTALS implementations. */
|
|
11
19
|
export type XOF = (
|
|
12
20
|
seed: Uint8Array,
|
|
13
21
|
blockLen?: number
|
|
14
22
|
) => {
|
|
23
|
+
/**
|
|
24
|
+
* Read diagnostic counters for the current XOF session.
|
|
25
|
+
* @returns Current call and XOF block counters.
|
|
26
|
+
*/
|
|
15
27
|
stats: () => { calls: number; xofs: number };
|
|
28
|
+
/**
|
|
29
|
+
* Select one `(x, y)` coordinate pair and get a block reader for it.
|
|
30
|
+
* Only one coordinate stream is live at a time: a later `get(...)` call rebinds the shared
|
|
31
|
+
* SHAKE state and invalidates older readers.
|
|
32
|
+
* Each squeeze aliases one mutable internal output buffer, so callers must copy blocks they
|
|
33
|
+
* want to retain before the next read.
|
|
34
|
+
* @param x - First matrix coordinate.
|
|
35
|
+
* @param y - Second matrix coordinate.
|
|
36
|
+
* @returns Lazy block reader for that coordinate pair.
|
|
37
|
+
*/
|
|
16
38
|
get: (x: number, y: number) => () => Uint8Array; // return block aligned to blockLen and 3
|
|
39
|
+
/** Wipe any buffered state once the reader is no longer needed. */
|
|
17
40
|
clean: () => void;
|
|
18
41
|
};
|
|
19
42
|
|
|
20
43
|
/** CRYSTALS (ml-kem, ml-dsa) options */
|
|
44
|
+
/** Shared polynomial and NTT parameters for CRYSTALS algorithms. */
|
|
21
45
|
export type CrystalOpts<T extends TypedArray> = {
|
|
46
|
+
/**
|
|
47
|
+
* Allocate one zeroed polynomial/vector container.
|
|
48
|
+
* @param n - Number of coefficients to allocate.
|
|
49
|
+
* @returns Fresh typed container.
|
|
50
|
+
*/
|
|
22
51
|
newPoly: TypedCons<T>;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
52
|
+
/** Polynomial size, typically `256`. */
|
|
53
|
+
N: number;
|
|
54
|
+
/** Prime modulus used for all coefficient arithmetic. */
|
|
55
|
+
Q: number;
|
|
56
|
+
/** Inverse transform normalization factor:
|
|
57
|
+
* `256**-1 mod q` for Dilithium, `128**-1 mod q` for Kyber.
|
|
58
|
+
*/
|
|
59
|
+
F: number;
|
|
60
|
+
/** Principal root of unity for the transform domain. */
|
|
26
61
|
ROOT_OF_UNITY: number;
|
|
27
|
-
|
|
62
|
+
/** Number of bits used for bit-reversal ordering. */
|
|
63
|
+
brvBits: number;
|
|
64
|
+
/** `true` for Kyber/ML-KEM mode, `false` for Dilithium/ML-DSA mode. */
|
|
28
65
|
isKyber: boolean;
|
|
29
66
|
};
|
|
30
67
|
|
|
68
|
+
/** Constructor function for typed polynomial containers. */
|
|
31
69
|
export type TypedCons<T extends TypedArray> = (n: number) => T;
|
|
32
70
|
|
|
33
|
-
|
|
34
|
-
opts: CrystalOpts<T>
|
|
35
|
-
): {
|
|
71
|
+
type Crystals<T extends TypedArray> = {
|
|
36
72
|
mod: (a: number, modulo?: number) => number;
|
|
37
73
|
smod: (a: number, modulo?: number) => number;
|
|
38
74
|
nttZetas: T;
|
|
39
75
|
NTT: {
|
|
76
|
+
/** Forward transform in place. Mutates and returns `r`. */
|
|
40
77
|
encode: (r: T) => T;
|
|
78
|
+
/** Inverse transform in place. Mutates and returns `r`. */
|
|
41
79
|
decode: (r: T) => T;
|
|
42
80
|
};
|
|
43
81
|
bitsCoder: (d: number, c: Coder<number, number>) => BytesCoderLen<T>;
|
|
44
|
-
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Creates shared modular arithmetic, NTT, and packing helpers for CRYSTALS schemes.
|
|
86
|
+
* @param opts - Polynomial and transform parameters. See {@link CrystalOpts}.
|
|
87
|
+
* @returns CRYSTALS arithmetic and encoding helpers.
|
|
88
|
+
* @example
|
|
89
|
+
* Create shared modular arithmetic and NTT helpers for a CRYSTALS parameter set.
|
|
90
|
+
* ```ts
|
|
91
|
+
* const crystals = genCrystals({
|
|
92
|
+
* newPoly: (n) => new Uint16Array(n),
|
|
93
|
+
* N: 256,
|
|
94
|
+
* Q: 3329,
|
|
95
|
+
* F: 3303,
|
|
96
|
+
* ROOT_OF_UNITY: 17,
|
|
97
|
+
* brvBits: 7,
|
|
98
|
+
* isKyber: true,
|
|
99
|
+
* });
|
|
100
|
+
* const reduced = crystals.mod(-1);
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
export const genCrystals = <T extends TypedArray>(opts: CrystalOpts<T>): TRet<Crystals<T>> => {
|
|
45
104
|
// isKyber: true means Kyber, false means Dilithium
|
|
46
105
|
const { newPoly, N, Q, F, ROOT_OF_UNITY, brvBits, isKyber } = opts;
|
|
106
|
+
// Normalize JS `%` into the canonical Z_m representative `[0, modulo-1]` expected by
|
|
107
|
+
// FIPS 203 §2.3 / FIPS 204 §2.3 before downstream mod-q arithmetic.
|
|
47
108
|
const mod = (a: number, modulo = Q): number => {
|
|
48
109
|
const result = a % modulo | 0;
|
|
49
110
|
return (result >= 0 ? result | 0 : (modulo + result) | 0) | 0;
|
|
50
111
|
};
|
|
51
|
-
//
|
|
112
|
+
// FIPS 204 §7.4 uses the centered `mod ±` representative for low bits, keeping the
|
|
113
|
+
// positive midpoint when `modulo` is even.
|
|
114
|
+
// Center to `[-floor((modulo-1)/2), floor(modulo/2)]`.
|
|
52
115
|
const smod = (a: number, modulo = Q): number => {
|
|
53
116
|
const r = mod(a, modulo) | 0;
|
|
54
117
|
return (r > modulo >> 1 ? (r - modulo) | 0 : r) | 0;
|
|
55
118
|
};
|
|
56
|
-
//
|
|
119
|
+
// Kyber uses the FIPS 203 Appendix A `BitRev_7` table here via the first 128 entries, while
|
|
120
|
+
// Dilithium uses the FIPS 204 §7.5 / Appendix B `BitRev_8` zetas table over all 256 entries.
|
|
57
121
|
function getZettas() {
|
|
58
122
|
const out = newPoly(N);
|
|
59
123
|
for (let i = 0; i < N; i++) {
|
|
@@ -94,44 +158,57 @@ export const genCrystals = <T extends TypedArray>(
|
|
|
94
158
|
},
|
|
95
159
|
decode: (r: T): T => {
|
|
96
160
|
dit(r as any);
|
|
161
|
+
// The inverse-NTT normalization factor is family-specific: FIPS 203 Algorithm 10 line 14
|
|
162
|
+
// uses `128^-1 mod q` for Kyber, while FIPS 204 Algorithm 42 lines 21-23 use `256^-1 mod q`.
|
|
97
163
|
// kyber uses 128 here, because brv && stuff
|
|
98
164
|
for (let i = 0; i < r.length; i++) r[i] = mod(F * r[i]);
|
|
99
165
|
return r;
|
|
100
166
|
},
|
|
101
167
|
};
|
|
102
|
-
//
|
|
103
|
-
|
|
168
|
+
// Pack one little-endian `d`-bit word per coefficient, matching FIPS 203 ByteEncode /
|
|
169
|
+
// ByteDecode and the FIPS 204 BitsToBytes-based polynomial packing helpers.
|
|
170
|
+
const bitsCoder = (d: number, c: Coder<number, number>): TRet<BytesCoderLen<T>> => {
|
|
104
171
|
const mask = getMask(d);
|
|
105
172
|
const bytesLen = d * (N / 8);
|
|
106
173
|
return {
|
|
107
174
|
bytesLen,
|
|
108
|
-
encode: (
|
|
175
|
+
encode: (poly_: TArg<T>): TRet<Uint8Array> => {
|
|
176
|
+
const poly = poly_ as T;
|
|
109
177
|
const r = new Uint8Array(bytesLen);
|
|
110
178
|
for (let i = 0, buf = 0, bufLen = 0, pos = 0; i < poly.length; i++) {
|
|
111
179
|
buf |= (c.encode(poly[i]) & mask) << bufLen;
|
|
112
180
|
bufLen += d;
|
|
113
181
|
for (; bufLen >= 8; bufLen -= 8, buf >>= 8) r[pos++] = buf & getMask(bufLen);
|
|
114
182
|
}
|
|
115
|
-
return r
|
|
183
|
+
return r as TRet<Uint8Array>;
|
|
116
184
|
},
|
|
117
|
-
decode: (bytes: Uint8Array): T => {
|
|
185
|
+
decode: (bytes: TArg<Uint8Array>): TRet<T> => {
|
|
118
186
|
const r = newPoly(N);
|
|
119
187
|
for (let i = 0, buf = 0, bufLen = 0, pos = 0; i < bytes.length; i++) {
|
|
120
188
|
buf |= bytes[i] << bufLen;
|
|
121
189
|
bufLen += 8;
|
|
122
190
|
for (; bufLen >= d; bufLen -= d, buf >>= d) r[pos++] = c.decode(buf & mask);
|
|
123
191
|
}
|
|
124
|
-
return r
|
|
192
|
+
return r as TRet<T>;
|
|
125
193
|
},
|
|
126
|
-
}
|
|
194
|
+
} as TRet<BytesCoderLen<T>>;
|
|
127
195
|
};
|
|
128
196
|
|
|
129
|
-
return {
|
|
197
|
+
return {
|
|
198
|
+
mod,
|
|
199
|
+
smod,
|
|
200
|
+
nttZetas: nttZetas as TRet<T>,
|
|
201
|
+
NTT: {
|
|
202
|
+
encode: (r: TArg<T>): TRet<T> => NTT.encode(r as T) as TRet<T>,
|
|
203
|
+
decode: (r: TArg<T>): TRet<T> => NTT.decode(r as T) as TRet<T>,
|
|
204
|
+
},
|
|
205
|
+
bitsCoder: bitsCoder as TRet<Crystals<T>>['bitsCoder'],
|
|
206
|
+
};
|
|
130
207
|
};
|
|
131
208
|
|
|
132
209
|
const createXofShake =
|
|
133
|
-
(shake: typeof shake128): XOF =>
|
|
134
|
-
(seed: Uint8Array
|
|
210
|
+
(shake: typeof shake128): TRet<XOF> =>
|
|
211
|
+
(seed: TArg<Uint8Array>, blockLen?: number) => {
|
|
135
212
|
if (!blockLen) blockLen = shake.blockLen;
|
|
136
213
|
// Optimizations that won't mater:
|
|
137
214
|
// - cached seed update (two .update(), on start and on the end)
|
|
@@ -148,6 +225,8 @@ const createXofShake =
|
|
|
148
225
|
return {
|
|
149
226
|
stats: () => ({ calls, xofs }),
|
|
150
227
|
get: (x: number, y: number) => {
|
|
228
|
+
// Rebind to `seed || x || y` so callers can implement the spec's per-coordinate
|
|
229
|
+
// SHAKE inputs like `rho || j || i` and `rho || IntegerToBytes(counter, 2)`.
|
|
151
230
|
_seed[seedLen + 0] = x;
|
|
152
231
|
_seed[seedLen + 1] = y;
|
|
153
232
|
h.destroy();
|
|
@@ -155,7 +234,7 @@ const createXofShake =
|
|
|
155
234
|
calls++;
|
|
156
235
|
return () => {
|
|
157
236
|
xofs++;
|
|
158
|
-
return h.xofInto(buf)
|
|
237
|
+
return h.xofInto(buf) as TRet<Uint8Array>;
|
|
159
238
|
};
|
|
160
239
|
},
|
|
161
240
|
clean: () => {
|
|
@@ -165,5 +244,37 @@ const createXofShake =
|
|
|
165
244
|
};
|
|
166
245
|
};
|
|
167
246
|
|
|
168
|
-
|
|
169
|
-
|
|
247
|
+
/**
|
|
248
|
+
* SHAKE128-based extendable-output reader factory used by ML-KEM.
|
|
249
|
+
* `get(x, y)` selects one coordinate pair at a time; calling it again invalidates previously
|
|
250
|
+
* returned readers, and each squeeze reuses one mutable internal output buffer.
|
|
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-KEM SHAKE128 matrix expander and read one block.
|
|
256
|
+
* ```ts
|
|
257
|
+
* import { randomBytes } from '@noble/post-quantum/utils.js';
|
|
258
|
+
* import { XOF128 } from '@noble/post-quantum/_crystals.js';
|
|
259
|
+
* const reader = XOF128(randomBytes(32));
|
|
260
|
+
* const block = reader.get(0, 0)();
|
|
261
|
+
* ```
|
|
262
|
+
*/
|
|
263
|
+
export const XOF128: TRet<XOF> = /* @__PURE__ */ createXofShake(shake128);
|
|
264
|
+
/**
|
|
265
|
+
* SHAKE256-based extendable-output reader factory used by ML-DSA.
|
|
266
|
+
* `get(x, y)` appends raw one-byte coordinates to the seed, invalidates previously returned
|
|
267
|
+
* readers, and reuses one mutable internal output buffer for each squeeze.
|
|
268
|
+
* @param seed - Seed bytes for the reader.
|
|
269
|
+
* @param blockLen - Optional output block length.
|
|
270
|
+
* @returns Stateful XOF reader.
|
|
271
|
+
* @example
|
|
272
|
+
* Build the ML-DSA SHAKE256 coefficient expander and read one block.
|
|
273
|
+
* ```ts
|
|
274
|
+
* import { randomBytes } from '@noble/post-quantum/utils.js';
|
|
275
|
+
* import { XOF256 } from '@noble/post-quantum/_crystals.js';
|
|
276
|
+
* const reader = XOF256(randomBytes(32));
|
|
277
|
+
* const block = reader.get(0, 0)();
|
|
278
|
+
* ```
|
|
279
|
+
*/
|
|
280
|
+
export const XOF256: TRet<XOF> = /* @__PURE__ */ createXofShake(shake256);
|