@noble/post-quantum 0.6.1 → 0.7.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 +191 -104
- package/_crystals.d.ts +8 -3
- package/_crystals.js +38 -10
- package/falcon.d.ts +1 -2
- package/falcon.js +202 -115
- package/hybrid.d.ts +38 -28
- package/hybrid.js +215 -86
- package/index.d.ts +0 -1
- package/index.js +1 -2
- package/ml-dsa.d.ts +31 -5
- package/ml-dsa.js +118 -34
- package/ml-kem.d.ts +45 -4
- package/ml-kem.js +206 -64
- package/package.json +16 -20
- package/slh-dsa.d.ts +23 -3
- package/slh-dsa.js +127 -60
- package/src/_crystals.ts +45 -11
- package/src/falcon.ts +206 -121
- package/src/hybrid.ts +217 -83
- package/src/index.ts +1 -1
- package/src/ml-dsa.ts +140 -43
- package/src/ml-kem.ts +239 -66
- package/src/slh-dsa.ts +149 -69
- package/src/utils.ts +186 -25
- package/src/webcrypto.ts +322 -0
- package/utils.d.ts +54 -4
- package/utils.js +167 -28
- package/webcrypto.d.ts +91 -0
- package/webcrypto.js +213 -0
- 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/falcon.ts
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
baswap64If,
|
|
28
28
|
type BytesCoderLen,
|
|
29
29
|
cleanBytes,
|
|
30
|
+
copyBytes,
|
|
30
31
|
type Coder,
|
|
31
32
|
type CryptoKeys,
|
|
32
33
|
getMask,
|
|
@@ -37,6 +38,7 @@ import {
|
|
|
37
38
|
type TRet,
|
|
38
39
|
validateSigOpts,
|
|
39
40
|
validateVerOpts,
|
|
41
|
+
SIG_OPT_KEYS,
|
|
40
42
|
type VerOpts,
|
|
41
43
|
} from './utils.ts';
|
|
42
44
|
/*
|
|
@@ -256,7 +258,12 @@ const compCoder = (n: number) => {
|
|
|
256
258
|
const sign = readBits(1);
|
|
257
259
|
const low = readBits(7);
|
|
258
260
|
let high = 0;
|
|
259
|
-
for
|
|
261
|
+
// Reference comp_decode adds 128 for each unary zero and rejects immediately above 2047.
|
|
262
|
+
// Waiting for the terminating one first lets an invalid coefficient scan the entire input.
|
|
263
|
+
while (!readBits(1)) {
|
|
264
|
+
high++;
|
|
265
|
+
if (high > LIMIT >>> 7) throw new Error(`limit: ${low | (high << 7)} > ${LIMIT}`);
|
|
266
|
+
}
|
|
260
267
|
const v = low | (high << 7);
|
|
261
268
|
if (sign && v === 0) throw new Error('negative zero encoding');
|
|
262
269
|
if (v > LIMIT) throw new Error(`limit: ${v} > ${LIMIT}`);
|
|
@@ -443,6 +450,17 @@ const Q: number = 12289; // 12 * 1024 + 1
|
|
|
443
450
|
// Falcon's midpoint floor(q/2); the only live use is the mirrored G-reconstruction reduction below.
|
|
444
451
|
const Qhalf: number = Q >> 1;
|
|
445
452
|
const QBig = BigInt(Q);
|
|
453
|
+
const _0n = /* @__PURE__ */ BigInt(0);
|
|
454
|
+
const _1n = /* @__PURE__ */ BigInt(1);
|
|
455
|
+
const _10n = /* @__PURE__ */ BigInt(10);
|
|
456
|
+
const _25n = /* @__PURE__ */ BigInt(25);
|
|
457
|
+
const _31n = /* @__PURE__ */ BigInt(31);
|
|
458
|
+
const _32n = /* @__PURE__ */ BigInt(32);
|
|
459
|
+
const _63n = /* @__PURE__ */ BigInt(63);
|
|
460
|
+
const _64n = /* @__PURE__ */ BigInt(64);
|
|
461
|
+
// Low 32 bits and low 63 bits of a bigint, used by the chacha20 counter and gaussian sampler.
|
|
462
|
+
const MASK_32n = /* @__PURE__ */ BigInt('0xffffffff');
|
|
463
|
+
const MASK_63n = /* @__PURE__ */ BigInt('0x7fffffffffffffff');
|
|
446
464
|
//const R = 4091; // 2^16 mod q
|
|
447
465
|
// This 16-bit Montgomery kernel uses R = 2^16, so mul(x, R2) converts x into Montgomery form.
|
|
448
466
|
const R2 = 10952; // 2^32 mod q
|
|
@@ -479,35 +497,35 @@ const BITLENGTH = [
|
|
|
479
497
|
// Smaller Falcon dimensions reuse the N = 1024, q = 12289 table by summing 2^(10-logn) draws.
|
|
480
498
|
// The trailing 0 sentinel guarantees gaussSingle()
|
|
481
499
|
// always selects a tail bucket when x = 0 is missed.
|
|
482
|
-
const gauss_1024_12289 = [
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
];
|
|
500
|
+
const gauss_1024_12289 = /* @__PURE__ */ [
|
|
501
|
+
'1283868770400643928',
|
|
502
|
+
'6416574995475331444',
|
|
503
|
+
'4078260278032692663',
|
|
504
|
+
'2353523259288686585',
|
|
505
|
+
'1227179971273316331',
|
|
506
|
+
'575931623374121527',
|
|
507
|
+
'242543240509105209',
|
|
508
|
+
'91437049221049666',
|
|
509
|
+
'30799446349977173',
|
|
510
|
+
'9255276791179340',
|
|
511
|
+
'2478152334826140',
|
|
512
|
+
'590642893610164',
|
|
513
|
+
'125206034929641',
|
|
514
|
+
'23590435911403',
|
|
515
|
+
'3948334035941',
|
|
516
|
+
'586753615614',
|
|
517
|
+
'77391054539',
|
|
518
|
+
'9056793210',
|
|
519
|
+
'940121950',
|
|
520
|
+
'86539696',
|
|
521
|
+
'7062824',
|
|
522
|
+
'510971',
|
|
523
|
+
'32764',
|
|
524
|
+
'1862',
|
|
525
|
+
'94',
|
|
526
|
+
'4',
|
|
527
|
+
'0',
|
|
528
|
+
].map(BigInt);
|
|
511
529
|
|
|
512
530
|
// Exact binary64 1/sigma payloads from round-3 fpr.h. Nearby decimal spellings round 1 ULP low in
|
|
513
531
|
// JS, so keep these as decoded bit patterns and recheck the raw payloads after edits.
|
|
@@ -541,6 +559,9 @@ const SIGMA_MIN = /* @__PURE__ */ Object.freeze([
|
|
|
541
559
|
f64b(BigInt('4608433670533905013')),
|
|
542
560
|
f64b(BigInt('4608525754002622308')),
|
|
543
561
|
]);
|
|
562
|
+
// Upper end of the SamplerZ proof interval from Falcon section 3.9.1. The per-leaf sigma is
|
|
563
|
+
// derived from the reconstructed private basis, so imported keys must be checked against it.
|
|
564
|
+
const SIGMA_MAX = 1.8205;
|
|
544
565
|
|
|
545
566
|
// Falcon Table 3.1 RCDT values for chi, split into 24-bit limbs; storage is [high, mid, low],
|
|
546
567
|
// so gaussian0() intentionally compares them against v0, v1, v2 in reverse order. The final
|
|
@@ -1153,6 +1174,54 @@ function getFloatPoly(logn: number) {
|
|
|
1153
1174
|
};
|
|
1154
1175
|
}
|
|
1155
1176
|
|
|
1177
|
+
function ldlFFT(logn: number, g00t: CPoly, g01t: CPoly, g11t: CPoly) {
|
|
1178
|
+
// Algorithm 8: LDL*(G)
|
|
1179
|
+
// (Page 37)
|
|
1180
|
+
// Require: A full-rank self-adjoint matrix G = (Gᵢⱼ) ∈ FFT(Q[x]/(φ))²ˣ²
|
|
1181
|
+
// Ensure: The LDL* decomposition G = LDL* over FFT(Q[x]/(φ))
|
|
1182
|
+
// Format: All polynomials are in FFT representation.
|
|
1183
|
+
// 1: D₀₀ ← G₀₀
|
|
1184
|
+
// 2: L₁₀ ← G₁₀/G₀₀
|
|
1185
|
+
// 3: D₁₁ ← G₁₁ - L₁₀ ⊙ L₁₀* ⊙ G₀₀
|
|
1186
|
+
// 4: L ← [ 1 0 ; L₁₀ 1 ], D ← [ D₀₀ 0 ; 0 D₁₁ ]
|
|
1187
|
+
// 5: return (L, D)
|
|
1188
|
+
|
|
1189
|
+
// Algorithm 9: ffLDL*(G)
|
|
1190
|
+
// (Page 37)
|
|
1191
|
+
// Require: A full-rank Gram matrix G ∈ FFT(Q[x]/(xⁿ + 1))²ˣ²
|
|
1192
|
+
// Ensure: A binary tree T
|
|
1193
|
+
// Format: All polynomials are in FFT representation.
|
|
1194
|
+
// 1: (L, D) ← LDL*(G) ▷ L = [ 1 0 ; L₁₀ 1 ], D = [ D₀₀ 0 ; 0 D₁₁ ]
|
|
1195
|
+
// 2: T.value ← L₁₀
|
|
1196
|
+
// 3: if (n = 2) then
|
|
1197
|
+
// 4: T.leftchild ← D₀₀
|
|
1198
|
+
// 5: T.rightchild ← D₁₁
|
|
1199
|
+
// 6: return T
|
|
1200
|
+
// 7: else
|
|
1201
|
+
// 8: d₀₀, d₀₁ ← splitfft(D₀₀) ▷ dᵢⱼ ∈ FFT(Q[x]/(x^{n/2} + 1))
|
|
1202
|
+
// 9: d₁₀, d₁₁ ← splitfft(D₁₁)
|
|
1203
|
+
// 10: G₀ ← [ d₀₀ d₀₁ ; d₀₁* d₀₀ ], G₁ ← [ d₁₀ d₁₁ ; d₁₁* d₁₀ ]
|
|
1204
|
+
// ▷ Since D₀₀, D₁₁ are self-adjoint, (3.30) applies
|
|
1205
|
+
// 11: T.leftchild ← ffLDL*(G₀) ▷ Recursive calls
|
|
1206
|
+
// 12: T.rightchild ← ffLDL*(G₁)
|
|
1207
|
+
// 13: return T
|
|
1208
|
+
|
|
1209
|
+
// Recursive calls may alias g00t and g11t, and the top-level arrays persist across signing
|
|
1210
|
+
// retries. LDL replaces array entries, so shallow copies keep both kinds of caller state intact.
|
|
1211
|
+
g00t = g00t.slice();
|
|
1212
|
+
g01t = g01t.slice();
|
|
1213
|
+
g11t = g11t.slice();
|
|
1214
|
+
const hn = 1 << (logn - 1);
|
|
1215
|
+
for (let i = 0; i < hn; i++) {
|
|
1216
|
+
const g01 = g01t[i];
|
|
1217
|
+
const g11 = g11t[i];
|
|
1218
|
+
const mu = fComplex.scale(g01, 1.0 / g00t[i].re);
|
|
1219
|
+
g11t[i] = { re: g11.re - (mu.re * g01.re + mu.im * g01.im), im: g11.im };
|
|
1220
|
+
g01t[i] = fComplex.conj(mu);
|
|
1221
|
+
}
|
|
1222
|
+
return { g00: g00t, g01: g01t, g11: g11t };
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1156
1225
|
function ApproxExp(x: number, ccs: number): number {
|
|
1157
1226
|
// Algorithm 13: ApproxExp(x, ccs), (Page 42)
|
|
1158
1227
|
// Require: Floating-point values x ∈ [0, ln(2)] and ccs ∈ [0, 1]
|
|
@@ -1184,19 +1253,16 @@ function ApproxExp(x: number, ccs: number): number {
|
|
|
1184
1253
|
// Actual api
|
|
1185
1254
|
type FalconOpts = {
|
|
1186
1255
|
N: number;
|
|
1187
|
-
// Table 3.3 total padded detached bytes
|
|
1188
|
-
//
|
|
1189
|
-
// and the payload width `sigLen - 1 - NONCELEN`.
|
|
1256
|
+
// Table 3.3 total padded detached bytes. In padded mode it drives `.lengths.signature` and the
|
|
1257
|
+
// payload width `sigLen - 1 - NONCELEN`.
|
|
1190
1258
|
sigLen: number;
|
|
1191
|
-
padded
|
|
1259
|
+
padded: boolean;
|
|
1192
1260
|
fgBits: number;
|
|
1193
1261
|
FGBits: number;
|
|
1194
1262
|
// Compressed-s payload bytes only, excluding the detached header byte and 40-byte nonce.
|
|
1195
1263
|
paddedLen: number;
|
|
1196
|
-
//
|
|
1197
|
-
//
|
|
1198
|
-
// `s2` length, while signRaw() enforces `maxS2Len` separately.
|
|
1199
|
-
detachedLen: number;
|
|
1264
|
+
// Maximum compressed-s payload emitted by sign(). Unpadded detached verification enforces the
|
|
1265
|
+
// same Round-3 ceiling before decoding.
|
|
1200
1266
|
maxS2Len: number;
|
|
1201
1267
|
};
|
|
1202
1268
|
|
|
@@ -1219,7 +1285,7 @@ export type FalconAttached = CryptoKeys & {
|
|
|
1219
1285
|
* @param sig Attached Falcon signature bytes.
|
|
1220
1286
|
* @param publicKey Falcon public key bytes.
|
|
1221
1287
|
* @param opts Optional verification options.
|
|
1222
|
-
* @returns
|
|
1288
|
+
* @returns Fresh message bytes that do not alias either input when the signature is valid.
|
|
1223
1289
|
*/
|
|
1224
1290
|
open(sig: Uint8Array, publicKey: Uint8Array, opts?: VerOpts): Uint8Array;
|
|
1225
1291
|
};
|
|
@@ -1248,9 +1314,9 @@ function genFalcon(opts: FalconOpts): TRet<Falcon> {
|
|
|
1248
1314
|
let val = 0;
|
|
1249
1315
|
for (let i = 0; i < g; i++) {
|
|
1250
1316
|
const r128 = bytesToNumberLE(this.shake.xof(16));
|
|
1251
|
-
const r1 = r128 &
|
|
1252
|
-
const r2 = (r128 >>
|
|
1253
|
-
const sign = Number((r128 >>
|
|
1317
|
+
const r1 = r128 & MASK_63n;
|
|
1318
|
+
const r2 = (r128 >> _64n) & MASK_63n;
|
|
1319
|
+
const sign = Number((r128 >> _63n) & _1n);
|
|
1254
1320
|
let f = r1 < gauss_1024_12289[0] ? 1 : 0;
|
|
1255
1321
|
let v = 0;
|
|
1256
1322
|
for (let k = 1; k < gauss_1024_12289.length; k++) {
|
|
@@ -1283,13 +1349,13 @@ function genFalcon(opts: FalconOpts): TRet<Falcon> {
|
|
|
1283
1349
|
const n = 1 << logn;
|
|
1284
1350
|
const d = new Array(n >> 1);
|
|
1285
1351
|
for (let k = 0; k < n; k += 2) {
|
|
1286
|
-
let s: bigint =
|
|
1352
|
+
let s: bigint = _0n;
|
|
1287
1353
|
for (let i = 0; i <= k; i += 2) s += a[i] * a[k - i];
|
|
1288
1354
|
for (let i = k + 2; i < n; i += 2) s -= a[i] * a[k + n - i];
|
|
1289
1355
|
d[k >>> 1] = s;
|
|
1290
1356
|
}
|
|
1291
1357
|
for (let k = 0; k < n; k += 2) {
|
|
1292
|
-
let s: bigint =
|
|
1358
|
+
let s: bigint = _0n;
|
|
1293
1359
|
for (let i = 1; i < k; i += 2) s += a[i] * a[k - i];
|
|
1294
1360
|
for (let i = k + 1; i < n; i += 2) s -= a[i] * a[k + n - i];
|
|
1295
1361
|
d[k >>> 1] -= s;
|
|
@@ -1299,7 +1365,7 @@ function genFalcon(opts: FalconOpts): TRet<Falcon> {
|
|
|
1299
1365
|
private mulConjD(logn: number, d: BPoly, a: BPoly, b: BPoly): BPoly {
|
|
1300
1366
|
const n = 1 << logn;
|
|
1301
1367
|
for (let k = 0; k < n; k++) {
|
|
1302
|
-
let s: bigint =
|
|
1368
|
+
let s: bigint = _0n;
|
|
1303
1369
|
for (let i = 0; i <= k; i += 2) s += b[i >>> 1] * a[k - i];
|
|
1304
1370
|
for (let i = k + 2 - (k & 1); i < n; i += 2) s -= b[i >>> 1] * a[k + n - i];
|
|
1305
1371
|
if ((k & 1) === 0) d[k] = s;
|
|
@@ -1310,7 +1376,7 @@ function genFalcon(opts: FalconOpts): TRet<Falcon> {
|
|
|
1310
1376
|
private subMul(logn: number, a: BPoly, b: BPoly, c: BPoly, e: bigint): BPoly {
|
|
1311
1377
|
const n = 1 << logn;
|
|
1312
1378
|
for (let k = 0; k < n; k++) {
|
|
1313
|
-
let s: bigint =
|
|
1379
|
+
let s: bigint = _0n;
|
|
1314
1380
|
for (let i = 0; i <= k; i++) s += b[i] * c[k - i];
|
|
1315
1381
|
for (let i = k + 1; i < n; i++) s -= b[i] * c[k + n - i];
|
|
1316
1382
|
a[k] -= s << e;
|
|
@@ -1353,7 +1419,7 @@ function genFalcon(opts: FalconOpts): TRet<Falcon> {
|
|
|
1353
1419
|
const Gx = new Float64Array(n);
|
|
1354
1420
|
const k = new Array(n);
|
|
1355
1421
|
while (true) {
|
|
1356
|
-
let scaleFG =
|
|
1422
|
+
let scaleFG = _31n * (FGlen - _10n);
|
|
1357
1423
|
for (let i = 0; i < n; i++) {
|
|
1358
1424
|
Fx[i] = Number(F[i] >> scaleFG);
|
|
1359
1425
|
Gx[i] = Number(G[i] >> scaleFG);
|
|
@@ -1373,12 +1439,12 @@ function genFalcon(opts: FalconOpts): TRet<Falcon> {
|
|
|
1373
1439
|
}
|
|
1374
1440
|
F = this.subMul(logn, F, f, k, scaleK); // 3: F ← F - kf
|
|
1375
1441
|
G = this.subMul(logn, G, g, k, scaleK); // 4: G ← G - kg
|
|
1376
|
-
const maxfgNew = scaleK + BigInt(Math.round(fgMaxBits)) +
|
|
1442
|
+
const maxfgNew = scaleK + BigInt(Math.round(fgMaxBits)) + _10n;
|
|
1377
1443
|
if (maxfgNew < maxFGBits) maxFGBits = maxfgNew;
|
|
1378
|
-
if (FGlen >
|
|
1379
|
-
if (scaleK <=
|
|
1380
|
-
scaleK -=
|
|
1381
|
-
if (scaleK <
|
|
1444
|
+
if (FGlen > _1n && FGlen * _31n >= maxFGBits + _31n) FGlen--;
|
|
1445
|
+
if (scaleK <= _0n) break;
|
|
1446
|
+
scaleK -= _25n;
|
|
1447
|
+
if (scaleK < _0n) scaleK = _0n;
|
|
1382
1448
|
}
|
|
1383
1449
|
return true;
|
|
1384
1450
|
}
|
|
@@ -1406,10 +1472,10 @@ function genFalcon(opts: FalconOpts): TRet<Falcon> {
|
|
|
1406
1472
|
const xf = f[0];
|
|
1407
1473
|
const xg = g[0];
|
|
1408
1474
|
// We can rely on 'invert' to throw if they are not coprime.
|
|
1409
|
-
if (xf <=
|
|
1475
|
+
if (xf <= _0n || xg <= _0n) return false;
|
|
1410
1476
|
try {
|
|
1411
1477
|
const u1 = invert(xf, xg); // if gcd(f, g) ≠ 1 then
|
|
1412
|
-
const v1 = (
|
|
1478
|
+
const v1 = (_1n - u1 * xf) / xg;
|
|
1413
1479
|
F[0] = -v1 * QBig; // 5: (F, G) ← (vq, uq)
|
|
1414
1480
|
G[0] = u1 * QBig;
|
|
1415
1481
|
return true;
|
|
@@ -1672,7 +1738,13 @@ function genFalcon(opts: FalconOpts): TRet<Falcon> {
|
|
|
1672
1738
|
).encode([nonce, pad(sigLen).encode(s2), msg]);
|
|
1673
1739
|
},
|
|
1674
1740
|
decode(data: TArg<Uint8Array>): TRet<SignatureRaw> {
|
|
1741
|
+
// Keep API misuse on the coder's TypeError path before reading the container length.
|
|
1742
|
+
abytes(data, undefined, 'signature');
|
|
1743
|
+
// The compressed-signature field is fixed-width here; only the message is variable. A
|
|
1744
|
+
// container shorter than the fixed part would make the s2 field borrow bytes from nowhere
|
|
1745
|
+
// and let a truncated encoding open to the same message.
|
|
1675
1746
|
const msgLen = data.length - NONCELEN - sigLen - 1;
|
|
1747
|
+
if (msgLen < 0) throw new Error('signature coder: wrong length');
|
|
1676
1748
|
const [nonce, s2, msg] = headerCoder(
|
|
1677
1749
|
0x30 + logn,
|
|
1678
1750
|
splitCoder('falcon.signature', NONCELEN, sigLen, msgLen)
|
|
@@ -1683,22 +1755,33 @@ function genFalcon(opts: FalconOpts): TRet<Falcon> {
|
|
|
1683
1755
|
};
|
|
1684
1756
|
// [ 1B header ] [ 40B nonce ] [ compressed_sig ]
|
|
1685
1757
|
const SignatureCoderDetached = (logn: number) => {
|
|
1686
|
-
const
|
|
1687
|
-
const getSigLen = (s2: TArg<Uint8Array>) => (opts.padded ?
|
|
1758
|
+
const paddedSigLen = opts.sigLen - 1 - NONCELEN;
|
|
1759
|
+
const getSigLen = (s2: TArg<Uint8Array>) => (opts.padded ? paddedSigLen : s2.length);
|
|
1688
1760
|
return {
|
|
1689
1761
|
encode({ nonce, s2 }: TArg<{ nonce: Uint8Array; s2: Uint8Array }>): TRet<Uint8Array> {
|
|
1690
1762
|
return headerCoder(
|
|
1691
1763
|
0x30 + logn,
|
|
1692
1764
|
splitCoder('falcon.detachedSignature', NONCELEN, getSigLen(s2))
|
|
1693
|
-
).encode([nonce, opts.padded ? pad(
|
|
1765
|
+
).encode([nonce, opts.padded ? pad(paddedSigLen).encode(s2) : s2]);
|
|
1694
1766
|
},
|
|
1695
1767
|
decode(data: TArg<Uint8Array>): TRet<{
|
|
1696
1768
|
nonce: Uint8Array;
|
|
1697
1769
|
s2: Uint8Array;
|
|
1698
1770
|
}> {
|
|
1771
|
+
// Unpadded Round-3 signatures have parameter-set maxima (header + nonce + s2):
|
|
1772
|
+
// 752 bytes for Falcon-512 and 1462 for Falcon-1024. Reject before creating views or
|
|
1773
|
+
// entering the bit decoder so attacker-sized inputs cannot cause proportional work.
|
|
1774
|
+
if (!opts.padded && data.length > 1 + NONCELEN + opts.maxS2Len)
|
|
1775
|
+
throw new Error('detached signature too long');
|
|
1776
|
+
// Padded detached signatures are fixed-length (`lengths.signature`), so the payload width
|
|
1777
|
+
// must come from the parameter set, not from the input: deriving it would accept appended
|
|
1778
|
+
// zero bytes and truncated padding as extra valid encodings of the same signature.
|
|
1779
|
+
// Unpadded signatures are variable-length; decodeUnpaddedSig() enforces the exact canonical
|
|
1780
|
+
// bitlength of whatever remains.
|
|
1781
|
+
const payloadLen = opts.padded ? paddedSigLen : data.length - NONCELEN - 1;
|
|
1699
1782
|
const [nonce, raw] = headerCoder(
|
|
1700
1783
|
0x30 + logn,
|
|
1701
|
-
splitCoder('falcon.detachedSignature', NONCELEN,
|
|
1784
|
+
splitCoder('falcon.detachedSignature', NONCELEN, payloadLen)
|
|
1702
1785
|
).decode(data);
|
|
1703
1786
|
const s2 = decodeSig(raw);
|
|
1704
1787
|
return { nonce, s2 } as TRet<{ nonce: Uint8Array; s2: Uint8Array }>;
|
|
@@ -1769,7 +1852,7 @@ function genFalcon(opts: FalconOpts): TRet<Falcon> {
|
|
|
1769
1852
|
// Round-3 Falcon keeps 16-bit draws only in 0..61444, i.e. below 61445 = 5*q, the largest
|
|
1770
1853
|
// 16-bit multiple of q below 2^16; a literal ceil(2^16/q)*q would accept every sample.
|
|
1771
1854
|
const kQ = 5 * Q;
|
|
1772
|
-
for (let i = 0; i < N;
|
|
1855
|
+
for (let i = 0; i < N;) {
|
|
1773
1856
|
const tmp = h.xof(2); // 6: t ← SHAKE-256-Extract(ctx, 16)
|
|
1774
1857
|
let w = (tmp[0] << 8) | tmp[1];
|
|
1775
1858
|
if (w < kQ) c[i++] = w % Q; // 8: cᵢ ← t mod q
|
|
@@ -1785,7 +1868,7 @@ function genFalcon(opts: FalconOpts): TRet<Falcon> {
|
|
|
1785
1868
|
private shakeBuf: Uint8Array;
|
|
1786
1869
|
private ctrView: DataView;
|
|
1787
1870
|
// ChaCha
|
|
1788
|
-
private ctr: bigint =
|
|
1871
|
+
private ctr: bigint = _0n;
|
|
1789
1872
|
private buf: Uint8Array;
|
|
1790
1873
|
private buf32: Uint32Array;
|
|
1791
1874
|
private pos: number;
|
|
@@ -1835,8 +1918,8 @@ function genFalcon(opts: FalconOpts): TRet<Falcon> {
|
|
|
1835
1918
|
const out32 = swap32IfBE(this.buf32);
|
|
1836
1919
|
for (let i = 0; i < 8; i++, this.ctr++) {
|
|
1837
1920
|
const n = swap32IfBE(this.nonce32.slice()); // [n0, n1, n2, n3]
|
|
1838
|
-
n[2] ^= Number(this.ctr &
|
|
1839
|
-
n[3] ^= Number(this.ctr >>
|
|
1921
|
+
n[2] ^= Number(this.ctr & MASK_32n);
|
|
1922
|
+
n[3] ^= Number(this.ctr >> _32n);
|
|
1840
1923
|
// chacha20() takes raw nonce bytes; on BE the word-normalized temp must be swapped back.
|
|
1841
1924
|
swap32IfBE(n.subarray(1));
|
|
1842
1925
|
chacha20(this.key, u8(n.subarray(1)), EMPTY_CHACHA20_BLOCK, this.curBlock, n[0]);
|
|
@@ -1947,48 +2030,6 @@ function genFalcon(opts: FalconOpts): TRet<Falcon> {
|
|
|
1947
2030
|
if (this.berExp(x, ccs)) return s + z;
|
|
1948
2031
|
}
|
|
1949
2032
|
}
|
|
1950
|
-
private ldlFFT(logn: number, g00t: CPoly, g01t: CPoly, g11t: CPoly) {
|
|
1951
|
-
// Algorithm 8: LDL*(G)
|
|
1952
|
-
// (Page 37)
|
|
1953
|
-
// Require: A full-rank self-adjoint matrix G = (Gᵢⱼ) ∈ FFT(Q[x]/(φ))²ˣ²
|
|
1954
|
-
// Ensure: The LDL* decomposition G = LDL* over FFT(Q[x]/(φ))
|
|
1955
|
-
// Format: All polynomials are in FFT representation.
|
|
1956
|
-
// 1: D₀₀ ← G₀₀
|
|
1957
|
-
// 2: L₁₀ ← G₁₀/G₀₀
|
|
1958
|
-
// 3: D₁₁ ← G₁₁ - L₁₀ ⊙ L₁₀* ⊙ G₀₀
|
|
1959
|
-
// 4: L ← [ 1 0 ; L₁₀ 1 ], D ← [ D₀₀ 0 ; 0 D₁₁ ]
|
|
1960
|
-
// 5: return (L, D)
|
|
1961
|
-
|
|
1962
|
-
// Algorithm 9: ffLDL*(G)
|
|
1963
|
-
// (Page 37)
|
|
1964
|
-
// Require: A full-rank Gram matrix G ∈ FFT(Q[x]/(xⁿ + 1))²ˣ²
|
|
1965
|
-
// Ensure: A binary tree T
|
|
1966
|
-
// Format: All polynomials are in FFT representation.
|
|
1967
|
-
// 1: (L, D) ← LDL*(G) ▷ L = [ 1 0 ; L₁₀ 1 ], D = [ D₀₀ 0 ; 0 D₁₁ ]
|
|
1968
|
-
// 2: T.value ← L₁₀
|
|
1969
|
-
// 3: if (n = 2) then
|
|
1970
|
-
// 4: T.leftchild ← D₀₀
|
|
1971
|
-
// 5: T.rightchild ← D₁₁
|
|
1972
|
-
// 6: return T
|
|
1973
|
-
// 7: else
|
|
1974
|
-
// 8: d₀₀, d₀₁ ← splitfft(D₀₀) ▷ dᵢⱼ ∈ FFT(Q[x]/(x^{n/2} + 1))
|
|
1975
|
-
// 9: d₁₀, d₁₁ ← splitfft(D₁₁)
|
|
1976
|
-
// 10: G₀ ← [ d₀₀ d₀₁ ; d₀₁* d₀₀ ], G₁ ← [ d₁₀ d₁₁ ; d₁₁* d₁₀ ]
|
|
1977
|
-
// ▷ Since D₀₀, D₁₁ are self-adjoint, (3.30) applies
|
|
1978
|
-
// 11: T.leftchild ← ffLDL*(G₀) ▷ Recursive calls
|
|
1979
|
-
// 12: T.rightchild ← ffLDL*(G₁)
|
|
1980
|
-
// 13: return T
|
|
1981
|
-
g00t = g00t.slice(); // can be same as g11t and everything will break!
|
|
1982
|
-
const hn = 1 << (logn - 1);
|
|
1983
|
-
for (let i = 0; i < hn; i++) {
|
|
1984
|
-
const g01 = g01t[i];
|
|
1985
|
-
const g11 = g11t[i];
|
|
1986
|
-
const mu = fComplex.scale(g01, 1.0 / g00t[i].re);
|
|
1987
|
-
g11t[i] = { re: g11.re - (mu.re * g01.re + mu.im * g01.im), im: g11.im };
|
|
1988
|
-
g01t[i] = fComplex.conj(mu);
|
|
1989
|
-
}
|
|
1990
|
-
return { g00: g00t, g01: g01t, g11: g11t };
|
|
1991
|
-
}
|
|
1992
2033
|
private splitFFT(logn: number, f: CPoly) {
|
|
1993
2034
|
// Algorithm 1: splitfft(FFT(f))
|
|
1994
2035
|
// (Page 29)
|
|
@@ -2104,7 +2145,13 @@ function genFalcon(opts: FalconOpts): TRet<Falcon> {
|
|
|
2104
2145
|
// 13: z₀ ← mergefft(z'₀)
|
|
2105
2146
|
// 14: return z = (z₀, z₁)
|
|
2106
2147
|
if (logn === 0) {
|
|
2148
|
+
// The dynamic sampler stores 1/σ' instead of σ'. Keygen guarantees this interval,
|
|
2149
|
+
// but an imported compact key may reconstruct an invalid basis. Check the actual LDL*
|
|
2150
|
+
// leaf before it can drive SamplerZ; the negated comparison also rejects NaN/infinity.
|
|
2107
2151
|
const leaf = Math.sqrt(g00i[0].re) * INV_SIGMA[this.logn];
|
|
2152
|
+
const sigmaPrime = 1 / leaf;
|
|
2153
|
+
if (!(sigmaPrime >= SIGMA_MIN[this.logn] && sigmaPrime <= SIGMA_MAX))
|
|
2154
|
+
throw new Error('invalid secretKey: sampler sigma out of range');
|
|
2108
2155
|
// 3: z₀ ← SamplerZ(t₀, σ')
|
|
2109
2156
|
// ▷ Since n=1, tᵢ = invFFT(tᵢ) ∈ Q and zᵢ = invFFT(zᵢ) ∈ Z
|
|
2110
2157
|
const t0re = this.samplerZ(t0[0].re, leaf);
|
|
@@ -2112,7 +2159,7 @@ function genFalcon(opts: FalconOpts): TRet<Falcon> {
|
|
|
2112
2159
|
return { t0: [{ re: t0re, im: 0.0 }], t1: [{ re: t1re, im: 0.0 }] };
|
|
2113
2160
|
}
|
|
2114
2161
|
// 6: (l, T₀, T₁) ← (T.value, T.leftchild, T.rightchild)
|
|
2115
|
-
const { g00, g01, g11 } =
|
|
2162
|
+
const { g00, g01, g11 } = ldlFFT(logn, g00i, g01i, g11i);
|
|
2116
2163
|
const { f0: g00f0, f1: g00f1 } = this.splitSelfAdjFFT(logn, g00);
|
|
2117
2164
|
const { f0: g11f0, f1: g11f1 } = this.splitSelfAdjFFT(logn, g11);
|
|
2118
2165
|
// 7: t'₁ ← splitfft(t₁)
|
|
@@ -2181,7 +2228,8 @@ function genFalcon(opts: FalconOpts): TRet<Falcon> {
|
|
|
2181
2228
|
// ▷ Remove 1 byte for the header, and 40 bytes for r
|
|
2182
2229
|
// 11: while (s = ⊥)
|
|
2183
2230
|
// 12: return sig = (r, s)
|
|
2184
|
-
abytes(msg);
|
|
2231
|
+
abytes(msg, undefined, 'msg');
|
|
2232
|
+
abytes(sk, secretKeyCoder.bytesLen, 'secretKey');
|
|
2185
2233
|
// One RNG stream drives both the public 40-byte nonce and the 48-byte sampler seed, so
|
|
2186
2234
|
// deterministic rnd hooks make signatures deterministic for fixed secretKey/message inputs.
|
|
2187
2235
|
const nonce = rnd(40);
|
|
@@ -2284,11 +2332,18 @@ function genFalcon(opts: FalconOpts): TRet<Falcon> {
|
|
|
2284
2332
|
publicKey: publicKeyCoder.bytesLen,
|
|
2285
2333
|
secretKey: secretKeyCoder.bytesLen,
|
|
2286
2334
|
});
|
|
2335
|
+
// Falcon takes a sampler callback the other schemes do not, and rejects `context`
|
|
2336
|
+
// with its own message; both stay in the accepted set so the specific errors fire.
|
|
2337
|
+
const FALCON_SIG_OPT_KEYS = [...SIG_OPT_KEYS, 'random'] as const;
|
|
2287
2338
|
// Noble exposes a 48-byte sampler-seed hook,
|
|
2288
2339
|
// but Falcon still samples/encodes a separate 40-byte nonce per signature.
|
|
2289
2340
|
const getRnd = (opts: TArg<FalconSigOpts> = {}): TRet<FalconRandom> => {
|
|
2290
|
-
|
|
2341
|
+
// `context` stays in the accepted set so the specific "not supported" error below
|
|
2342
|
+
// still fires, rather than the generic unexpected-option one.
|
|
2343
|
+
opts = validateSigOpts(opts, FALCON_SIG_OPT_KEYS);
|
|
2291
2344
|
if (opts.context !== undefined) throw new Error('context is not supported');
|
|
2345
|
+
if (opts.random !== undefined && typeof opts.random !== 'function')
|
|
2346
|
+
throw new TypeError('"opts.random" expected function, got type=' + typeof opts.random);
|
|
2292
2347
|
if (opts.random !== undefined) return opts.random as TRet<FalconRandom>;
|
|
2293
2348
|
if (opts.extraEntropy === undefined) return randomBytes;
|
|
2294
2349
|
const seed = opts.extraEntropy === false ? new Uint8Array(48) : opts.extraEntropy;
|
|
@@ -2297,8 +2352,8 @@ function genFalcon(opts: FalconOpts): TRet<Falcon> {
|
|
|
2297
2352
|
return (len = 0) => drbg.randomBytes(len) as TRet<Uint8Array>;
|
|
2298
2353
|
};
|
|
2299
2354
|
const checkVerOpts = (opts: TArg<VerOpts> = {}) => {
|
|
2300
|
-
validateVerOpts(opts);
|
|
2301
|
-
if (
|
|
2355
|
+
const normalized = validateVerOpts(opts);
|
|
2356
|
+
if (normalized.context !== undefined) throw new Error('context is not supported');
|
|
2302
2357
|
};
|
|
2303
2358
|
const tests = Object.freeze({
|
|
2304
2359
|
publicKeyCoder: Object.freeze(publicKeyCoder),
|
|
@@ -2328,6 +2383,7 @@ function genFalcon(opts: FalconOpts): TRet<Falcon> {
|
|
|
2328
2383
|
}>;
|
|
2329
2384
|
};
|
|
2330
2385
|
const getPublicKey = (sk: TArg<Uint8Array>): TRet<Uint8Array> => {
|
|
2386
|
+
abytes(sk, secretKeyCoder.bytesLen, 'secretKey');
|
|
2331
2387
|
const [f, g, F] = secretKeyCoder.decode(sk);
|
|
2332
2388
|
try {
|
|
2333
2389
|
const h = computePublic(f, g);
|
|
@@ -2358,9 +2414,10 @@ function genFalcon(opts: FalconOpts): TRet<Falcon> {
|
|
|
2358
2414
|
verOpts: TArg<VerOpts> = {}
|
|
2359
2415
|
) => {
|
|
2360
2416
|
checkVerOpts(verOpts);
|
|
2361
|
-
abytes(sig);
|
|
2362
|
-
abytes(msg);
|
|
2363
|
-
|
|
2417
|
+
abytes(sig, undefined, 'signature');
|
|
2418
|
+
abytes(msg, undefined, 'msg');
|
|
2419
|
+
// Length/canonical public-key failures are decoded below and return false; only type is fatal.
|
|
2420
|
+
abytes(pk, undefined, 'publicKey');
|
|
2364
2421
|
try {
|
|
2365
2422
|
const { s2, nonce } = SignatureCoderDetached(logn).decode(sig);
|
|
2366
2423
|
return verifyRaw(pk, s2, nonce, msg);
|
|
@@ -2379,11 +2436,39 @@ function genFalcon(opts: FalconOpts): TRet<Falcon> {
|
|
|
2379
2436
|
},
|
|
2380
2437
|
open(sig: TArg<Uint8Array>, pk: TArg<Uint8Array>, verOpts: TArg<VerOpts> = {}) {
|
|
2381
2438
|
checkVerOpts(verOpts);
|
|
2382
|
-
|
|
2383
|
-
//
|
|
2384
|
-
//
|
|
2385
|
-
|
|
2386
|
-
|
|
2439
|
+
// Wrong argument types are caller bugs and must stay TypeErrors; only what happens
|
|
2440
|
+
// after this is untrusted input. Detached verify() type-checks the public key the same
|
|
2441
|
+
// way, so open() does too: a wrong type is fatal, and a malformed (wrong-length or
|
|
2442
|
+
// non-canonical) key folds into the single rejection below rather than leaking a raw
|
|
2443
|
+
// codec error, exactly as detached verify() folds it into `false`.
|
|
2444
|
+
abytes(sig, undefined, 'signature');
|
|
2445
|
+
abytes(pk, undefined, 'publicKey');
|
|
2446
|
+
// Decode and verify owned snapshots. Apart from keeping the authenticated result stable
|
|
2447
|
+
// after open() returns, this ensures every verification step observes the same bytes when
|
|
2448
|
+
// an input is backed by SharedArrayBuffer or has subclass-overridden view methods.
|
|
2449
|
+
const ownedSig = copyBytes(sig);
|
|
2450
|
+
const ownedPk = copyBytes(pk);
|
|
2451
|
+
// Decode failures and malformed-key failures are rejected signatures, not internal
|
|
2452
|
+
// faults. Letting the codec's own errors out gave a caller handling untrusted input
|
|
2453
|
+
// several different messages for one corrupt byte, including "end of buffer: len=2
|
|
2454
|
+
// buf=0 lastByte=undefined", which reads as a library bug. Detached verify already
|
|
2455
|
+
// treats every such failure uniformly; open() collapses them into one Error (the
|
|
2456
|
+
// original preserved as `cause`). A well-formed signature that simply does not
|
|
2457
|
+
// validate falls through to the same message with no cause.
|
|
2458
|
+
try {
|
|
2459
|
+
let verifiedMsg: Uint8Array | undefined;
|
|
2460
|
+
try {
|
|
2461
|
+
const { s2, nonce, msg } = SignatureCoder.decode(ownedSig);
|
|
2462
|
+
if (verifyRaw(ownedPk, s2, nonce, msg)) verifiedMsg = msg;
|
|
2463
|
+
} catch (cause) {
|
|
2464
|
+
throw new Error('invalid signature', { cause });
|
|
2465
|
+
}
|
|
2466
|
+
if (verifiedMsg === undefined) throw new Error('invalid signature');
|
|
2467
|
+
// Do not retain or expose the full attached-signature allocation through `.buffer`.
|
|
2468
|
+
return copyBytes(verifiedMsg);
|
|
2469
|
+
} finally {
|
|
2470
|
+
cleanBytes(ownedSig, ownedPk);
|
|
2471
|
+
}
|
|
2387
2472
|
},
|
|
2388
2473
|
});
|
|
2389
2474
|
const res = {
|
|
@@ -2401,14 +2486,14 @@ function genFalcon(opts: FalconOpts): TRet<Falcon> {
|
|
|
2401
2486
|
|
|
2402
2487
|
const falcon512opts = {
|
|
2403
2488
|
N: 512,
|
|
2489
|
+
// Keep the mode an own property: omitted config fields must not inherit from Object.prototype.
|
|
2490
|
+
padded: false,
|
|
2404
2491
|
// Table 3.3 fixed padded detached bytes, including the detached header byte and 40-byte nonce.
|
|
2405
2492
|
sigLen: 666,
|
|
2406
2493
|
fgBits: 6,
|
|
2407
2494
|
FGBits: 8,
|
|
2408
2495
|
// Compressed-s payload bytes only, excluding the detached header byte and 40-byte nonce.
|
|
2409
2496
|
paddedLen: 625,
|
|
2410
|
-
// Payload-only budget: genFalcon() adds the detached header byte and 40-byte nonce around it.
|
|
2411
|
-
detachedLen: 690,
|
|
2412
2497
|
};
|
|
2413
2498
|
/**
|
|
2414
2499
|
* Falcon-512 detached-signature API with the attached helper exposed as `.attached`.
|
|
@@ -2443,14 +2528,13 @@ export const falcon512padded: TRet<Falcon> = /* @__PURE__ */ (() =>
|
|
|
2443
2528
|
|
|
2444
2529
|
const falcon1024opts = {
|
|
2445
2530
|
N: 1024,
|
|
2531
|
+
padded: false,
|
|
2446
2532
|
// Table 3.3 fixed padded detached bytes, including the detached header byte and 40-byte nonce.
|
|
2447
2533
|
sigLen: 1280,
|
|
2448
2534
|
fgBits: 5,
|
|
2449
2535
|
FGBits: 8,
|
|
2450
2536
|
// Compressed-s payload bytes only, excluding the detached header byte and 40-byte nonce.
|
|
2451
2537
|
paddedLen: 1239,
|
|
2452
|
-
// Payload-only budget: genFalcon() adds the detached header byte and 40-byte nonce around it.
|
|
2453
|
-
detachedLen: 1280,
|
|
2454
2538
|
};
|
|
2455
2539
|
/**
|
|
2456
2540
|
* Falcon-1024 detached-signature API with the attached helper exposed as `.attached`.
|
|
@@ -2495,6 +2579,7 @@ export const __tests: any = /* @__PURE__ */ (() =>
|
|
|
2495
2579
|
INV_SIGMA,
|
|
2496
2580
|
SIGMA_MIN,
|
|
2497
2581
|
getFloatPoly,
|
|
2582
|
+
ldlFFT,
|
|
2498
2583
|
cleanCPoly,
|
|
2499
2584
|
falcon512: (falcon512 as any).__test,
|
|
2500
2585
|
falcon512padded: (falcon512padded as any).__test,
|