@noble/post-quantum 0.6.0 → 0.7.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/README.md +106 -80
- package/_crystals.d.ts +23 -16
- package/_crystals.js +50 -12
- package/falcon.d.ts +7 -8
- package/falcon.js +109 -74
- package/hybrid.d.ts +21 -32
- package/hybrid.js +157 -78
- package/index.d.ts +0 -1
- package/index.js +8 -1
- package/ml-dsa.d.ts +35 -9
- package/ml-dsa.js +116 -43
- package/ml-kem.d.ts +46 -5
- package/ml-kem.js +175 -67
- package/package.json +10 -18
- package/slh-dsa.d.ts +44 -24
- package/slh-dsa.js +150 -84
- package/src/_crystals.ts +86 -35
- package/src/falcon.ts +230 -169
- package/src/hybrid.ts +243 -129
- package/src/index.ts +8 -0
- package/src/ml-dsa.ts +194 -87
- package/src/ml-kem.ts +283 -122
- package/src/slh-dsa.ts +310 -182
- package/src/utils.ts +244 -48
- package/utils.d.ts +99 -24
- package/utils.js +92 -24
- package/_crystals.d.ts.map +0 -1
- package/_crystals.js.map +0 -1
- package/falcon.d.ts.map +0 -1
- package/falcon.js.map +0 -1
- package/hybrid.d.ts.map +0 -1
- package/hybrid.js.map +0 -1
- package/index.d.ts.map +0 -1
- package/index.js.map +0 -1
- package/ml-dsa.d.ts.map +0 -1
- package/ml-dsa.js.map +0 -1
- package/ml-kem.d.ts.map +0 -1
- package/ml-kem.js.map +0 -1
- package/slh-dsa.d.ts.map +0 -1
- package/slh-dsa.js.map +0 -1
- package/utils.d.ts.map +0 -1
- package/utils.js.map +0 -1
package/src/_crystals.ts
CHANGED
|
@@ -6,7 +6,14 @@
|
|
|
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
|
|
|
11
18
|
/** Extendable-output reader used by the CRYSTALS implementations. */
|
|
12
19
|
export type XOF = (
|
|
@@ -61,6 +68,25 @@ export type CrystalOpts<T extends TypedArray> = {
|
|
|
61
68
|
/** Constructor function for typed polynomial containers. */
|
|
62
69
|
export type TypedCons<T extends TypedArray> = (n: number) => T;
|
|
63
70
|
|
|
71
|
+
type Crystals<T extends TypedArray> = {
|
|
72
|
+
mod: (a: number, modulo?: number) => number;
|
|
73
|
+
smod: (a: number, modulo?: number) => number;
|
|
74
|
+
nttZetas: T;
|
|
75
|
+
NTT: {
|
|
76
|
+
/**
|
|
77
|
+
* Forward transform in place. Mutates and returns `r`.
|
|
78
|
+
* Kyber-mode input coefficients must already use canonical representatives in `[0, Q)`.
|
|
79
|
+
*/
|
|
80
|
+
encode: (r: T) => T;
|
|
81
|
+
/**
|
|
82
|
+
* Inverse transform in place. Mutates and returns `r`.
|
|
83
|
+
* Kyber-mode input coefficients must already use canonical representatives in `[0, Q)`.
|
|
84
|
+
*/
|
|
85
|
+
decode: (r: T) => T;
|
|
86
|
+
};
|
|
87
|
+
bitsCoder: (d: number, c: Coder<number, number>) => BytesCoderLen<T>;
|
|
88
|
+
};
|
|
89
|
+
|
|
64
90
|
/**
|
|
65
91
|
* Creates shared modular arithmetic, NTT, and packing helpers for CRYSTALS schemes.
|
|
66
92
|
* @param opts - Polynomial and transform parameters. See {@link CrystalOpts}.
|
|
@@ -80,20 +106,7 @@ export type TypedCons<T extends TypedArray> = (n: number) => T;
|
|
|
80
106
|
* const reduced = crystals.mod(-1);
|
|
81
107
|
* ```
|
|
82
108
|
*/
|
|
83
|
-
export const genCrystals = <T extends TypedArray>(
|
|
84
|
-
opts: CrystalOpts<T>
|
|
85
|
-
): {
|
|
86
|
-
mod: (a: number, modulo?: number) => number;
|
|
87
|
-
smod: (a: number, modulo?: number) => number;
|
|
88
|
-
nttZetas: T;
|
|
89
|
-
NTT: {
|
|
90
|
-
/** Forward transform in place. Mutates and returns `r`. */
|
|
91
|
-
encode: (r: T) => T;
|
|
92
|
-
/** Inverse transform in place. Mutates and returns `r`. */
|
|
93
|
-
decode: (r: T) => T;
|
|
94
|
-
};
|
|
95
|
-
bitsCoder: (d: number, c: Coder<number, number>) => BytesCoderLen<T>;
|
|
96
|
-
} => {
|
|
109
|
+
export const genCrystals = <T extends TypedArray>(opts: CrystalOpts<T>): TRet<Crystals<T>> => {
|
|
97
110
|
// isKyber: true means Kyber, false means Dilithium
|
|
98
111
|
const { newPoly, N, Q, F, ROOT_OF_UNITY, brvBits, isKyber } = opts;
|
|
99
112
|
// Normalize JS `%` into the canonical Z_m representative `[0, modulo-1]` expected by
|
|
@@ -128,14 +141,34 @@ export const genCrystals = <T extends TypedArray>(
|
|
|
128
141
|
// Kyber has slightly different params, since there is no 512th primitive root of unity mod q,
|
|
129
142
|
// only 256th primitive root of unity mod. Which also complicates MultiplyNTT.
|
|
130
143
|
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
sub: (a: number, b: number) => mod((a | 0) - (b | 0)) | 0,
|
|
134
|
-
mul: (a: number, b: number) => mod((a | 0) * (b | 0)) | 0,
|
|
135
|
-
inv: (_a: number) => {
|
|
136
|
-
throw new Error('not implemented');
|
|
137
|
-
},
|
|
144
|
+
const inv = (_a: number) => {
|
|
145
|
+
throw new Error('not implemented');
|
|
138
146
|
};
|
|
147
|
+
// ML-KEM (Kyber) polynomials always enter the transform reduced to [0, Q), so add/sub only
|
|
148
|
+
// need one conditional correction instead of `%`; measured ~20% faster NTT there.
|
|
149
|
+
// ML-DSA keeps the generic mod() path on purpose: its first forward stage sees centered
|
|
150
|
+
// (negative) coefficients, and `sub(a, t)` can drop below -Q (t is a mul output in [0, Q)),
|
|
151
|
+
// so a single correction is not enough. A guarded fast path with mod() fallback was measured
|
|
152
|
+
// slower than plain `%` for the 23-bit Q (V8 int32 modulo is one div; the branches lose).
|
|
153
|
+
const field = isKyber
|
|
154
|
+
? {
|
|
155
|
+
add: (a: number, b: number) => {
|
|
156
|
+
const r = (a + b) | 0;
|
|
157
|
+
return r >= Q ? (r - Q) | 0 : r;
|
|
158
|
+
},
|
|
159
|
+
sub: (a: number, b: number) => {
|
|
160
|
+
const r = (a - b) | 0;
|
|
161
|
+
return r < 0 ? (r + Q) | 0 : r;
|
|
162
|
+
},
|
|
163
|
+
mul: (a: number, b: number) => mod((a | 0) * (b | 0)) | 0,
|
|
164
|
+
inv,
|
|
165
|
+
}
|
|
166
|
+
: {
|
|
167
|
+
add: (a: number, b: number) => mod((a | 0) + (b | 0)) | 0,
|
|
168
|
+
sub: (a: number, b: number) => mod((a | 0) - (b | 0)) | 0,
|
|
169
|
+
mul: (a: number, b: number) => mod((a | 0) * (b | 0)) | 0,
|
|
170
|
+
inv,
|
|
171
|
+
};
|
|
139
172
|
const nttOpts = {
|
|
140
173
|
N,
|
|
141
174
|
roots: nttZetas as any,
|
|
@@ -160,38 +193,56 @@ export const genCrystals = <T extends TypedArray>(
|
|
|
160
193
|
};
|
|
161
194
|
// Pack one little-endian `d`-bit word per coefficient, matching FIPS 203 ByteEncode /
|
|
162
195
|
// ByteDecode and the FIPS 204 BitsToBytes-based polynomial packing helpers.
|
|
163
|
-
const bitsCoder = (d: number, c: Coder<number, number>): BytesCoderLen<T
|
|
196
|
+
const bitsCoder = (d: number, c: Coder<number, number>): TRet<BytesCoderLen<T>> => {
|
|
197
|
+
// Validate the carry shape once: JS bitwise operations silently truncate wider accumulators.
|
|
198
|
+
for (let i = 0, bufLen = 0; i < N; i++) {
|
|
199
|
+
bufLen += d;
|
|
200
|
+
if (bufLen > 32) getMask(bufLen);
|
|
201
|
+
bufLen %= 8;
|
|
202
|
+
}
|
|
164
203
|
const mask = getMask(d);
|
|
165
204
|
const bytesLen = d * (N / 8);
|
|
166
205
|
return {
|
|
167
206
|
bytesLen,
|
|
168
|
-
encode: (
|
|
207
|
+
encode: (poly_: TArg<T>): TRet<Uint8Array> => {
|
|
208
|
+
const poly = poly_ as T;
|
|
169
209
|
const r = new Uint8Array(bytesLen);
|
|
170
210
|
for (let i = 0, buf = 0, bufLen = 0, pos = 0; i < poly.length; i++) {
|
|
171
211
|
buf |= (c.encode(poly[i]) & mask) << bufLen;
|
|
172
212
|
bufLen += d;
|
|
173
|
-
|
|
213
|
+
// Take the low byte directly: `& 0xff` matches the previous getMask(bufLen) result
|
|
214
|
+
// after Uint8Array truncation, without a validated function call per output byte.
|
|
215
|
+
for (; bufLen >= 8; bufLen -= 8, buf >>= 8) r[pos++] = buf & 0xff;
|
|
174
216
|
}
|
|
175
|
-
return r
|
|
217
|
+
return r as TRet<Uint8Array>;
|
|
176
218
|
},
|
|
177
|
-
decode: (bytes: Uint8Array): T => {
|
|
219
|
+
decode: (bytes: TArg<Uint8Array>): TRet<T> => {
|
|
178
220
|
const r = newPoly(N);
|
|
179
221
|
for (let i = 0, buf = 0, bufLen = 0, pos = 0; i < bytes.length; i++) {
|
|
180
222
|
buf |= bytes[i] << bufLen;
|
|
181
223
|
bufLen += 8;
|
|
182
224
|
for (; bufLen >= d; bufLen -= d, buf >>= d) r[pos++] = c.decode(buf & mask);
|
|
183
225
|
}
|
|
184
|
-
return r
|
|
226
|
+
return r as TRet<T>;
|
|
185
227
|
},
|
|
186
|
-
}
|
|
228
|
+
} as TRet<BytesCoderLen<T>>;
|
|
187
229
|
};
|
|
188
230
|
|
|
189
|
-
return {
|
|
231
|
+
return {
|
|
232
|
+
mod,
|
|
233
|
+
smod,
|
|
234
|
+
nttZetas: nttZetas as TRet<T>,
|
|
235
|
+
NTT: {
|
|
236
|
+
encode: (r: TArg<T>): TRet<T> => NTT.encode(r as T) as TRet<T>,
|
|
237
|
+
decode: (r: TArg<T>): TRet<T> => NTT.decode(r as T) as TRet<T>,
|
|
238
|
+
},
|
|
239
|
+
bitsCoder: bitsCoder as TRet<Crystals<T>>['bitsCoder'],
|
|
240
|
+
};
|
|
190
241
|
};
|
|
191
242
|
|
|
192
243
|
const createXofShake =
|
|
193
|
-
(shake: typeof shake128): XOF =>
|
|
194
|
-
(seed: Uint8Array
|
|
244
|
+
(shake: typeof shake128): TRet<XOF> =>
|
|
245
|
+
(seed: TArg<Uint8Array>, blockLen?: number) => {
|
|
195
246
|
if (!blockLen) blockLen = shake.blockLen;
|
|
196
247
|
// Optimizations that won't mater:
|
|
197
248
|
// - cached seed update (two .update(), on start and on the end)
|
|
@@ -217,7 +268,7 @@ const createXofShake =
|
|
|
217
268
|
calls++;
|
|
218
269
|
return () => {
|
|
219
270
|
xofs++;
|
|
220
|
-
return h.xofInto(buf)
|
|
271
|
+
return h.xofInto(buf) as TRet<Uint8Array>;
|
|
221
272
|
};
|
|
222
273
|
},
|
|
223
274
|
clean: () => {
|
|
@@ -243,7 +294,7 @@ const createXofShake =
|
|
|
243
294
|
* const block = reader.get(0, 0)();
|
|
244
295
|
* ```
|
|
245
296
|
*/
|
|
246
|
-
export const XOF128: XOF = /* @__PURE__ */ createXofShake(shake128);
|
|
297
|
+
export const XOF128: TRet<XOF> = /* @__PURE__ */ createXofShake(shake128);
|
|
247
298
|
/**
|
|
248
299
|
* SHAKE256-based extendable-output reader factory used by ML-DSA.
|
|
249
300
|
* `get(x, y)` appends raw one-byte coordinates to the seed, invalidates previously returned
|
|
@@ -260,4 +311,4 @@ export const XOF128: XOF = /* @__PURE__ */ createXofShake(shake128);
|
|
|
260
311
|
* const block = reader.get(0, 0)();
|
|
261
312
|
* ```
|
|
262
313
|
*/
|
|
263
|
-
export const XOF256: XOF = /* @__PURE__ */ createXofShake(shake256);
|
|
314
|
+
export const XOF256: TRet<XOF> = /* @__PURE__ */ createXofShake(shake256);
|