@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/falcon.js
CHANGED
|
@@ -12,7 +12,7 @@ import { bytesToNumberLE, numberToHexUnpadded } from '@noble/curves/utils.js';
|
|
|
12
12
|
import { shake256 } from '@noble/hashes/sha3.js';
|
|
13
13
|
import { abytes, bytesToHex, createView, hexToBytes, randomBytes, swap32IfBE, u32, u8, } from '@noble/hashes/utils.js';
|
|
14
14
|
import { genCrystals } from "./_crystals.js";
|
|
15
|
-
import { baswap64If, cleanBytes, getMask, splitCoder, validateSigOpts, validateVerOpts, } from "./utils.js";
|
|
15
|
+
import { baswap64If, cleanBytes, copyBytes, getMask, splitCoder, validateSigOpts, validateVerOpts, SIG_OPT_KEYS, } from "./utils.js";
|
|
16
16
|
/*
|
|
17
17
|
FIPS-206 would likely improve the situation with spec.
|
|
18
18
|
|
|
@@ -230,8 +230,13 @@ const compCoder = (n) => {
|
|
|
230
230
|
const sign = readBits(1);
|
|
231
231
|
const low = readBits(7);
|
|
232
232
|
let high = 0;
|
|
233
|
-
for
|
|
234
|
-
|
|
233
|
+
// Reference comp_decode adds 128 for each unary zero and rejects immediately above 2047.
|
|
234
|
+
// Waiting for the terminating one first lets an invalid coefficient scan the entire input.
|
|
235
|
+
while (!readBits(1)) {
|
|
236
|
+
high++;
|
|
237
|
+
if (high > LIMIT >>> 7)
|
|
238
|
+
throw new Error(`limit: ${low | (high << 7)} > ${LIMIT}`);
|
|
239
|
+
}
|
|
235
240
|
const v = low | (high << 7);
|
|
236
241
|
if (sign && v === 0)
|
|
237
242
|
throw new Error('negative zero encoding');
|
|
@@ -403,6 +408,17 @@ const Q = 12289; // 12 * 1024 + 1
|
|
|
403
408
|
// Falcon's midpoint floor(q/2); the only live use is the mirrored G-reconstruction reduction below.
|
|
404
409
|
const Qhalf = Q >> 1;
|
|
405
410
|
const QBig = BigInt(Q);
|
|
411
|
+
const _0n = /* @__PURE__ */ BigInt(0);
|
|
412
|
+
const _1n = /* @__PURE__ */ BigInt(1);
|
|
413
|
+
const _10n = /* @__PURE__ */ BigInt(10);
|
|
414
|
+
const _25n = /* @__PURE__ */ BigInt(25);
|
|
415
|
+
const _31n = /* @__PURE__ */ BigInt(31);
|
|
416
|
+
const _32n = /* @__PURE__ */ BigInt(32);
|
|
417
|
+
const _63n = /* @__PURE__ */ BigInt(63);
|
|
418
|
+
const _64n = /* @__PURE__ */ BigInt(64);
|
|
419
|
+
// Low 32 bits and low 63 bits of a bigint, used by the chacha20 counter and gaussian sampler.
|
|
420
|
+
const MASK_32n = /* @__PURE__ */ BigInt('0xffffffff');
|
|
421
|
+
const MASK_63n = /* @__PURE__ */ BigInt('0x7fffffffffffffff');
|
|
406
422
|
//const R = 4091; // 2^16 mod q
|
|
407
423
|
// This 16-bit Montgomery kernel uses R = 2^16, so mul(x, R2) converts x into Montgomery form.
|
|
408
424
|
const R2 = 10952; // 2^32 mod q
|
|
@@ -439,35 +455,35 @@ const BITLENGTH = [
|
|
|
439
455
|
// Smaller Falcon dimensions reuse the N = 1024, q = 12289 table by summing 2^(10-logn) draws.
|
|
440
456
|
// The trailing 0 sentinel guarantees gaussSingle()
|
|
441
457
|
// always selects a tail bucket when x = 0 is missed.
|
|
442
|
-
const gauss_1024_12289 = [
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
];
|
|
458
|
+
const gauss_1024_12289 = /* @__PURE__ */ [
|
|
459
|
+
'1283868770400643928',
|
|
460
|
+
'6416574995475331444',
|
|
461
|
+
'4078260278032692663',
|
|
462
|
+
'2353523259288686585',
|
|
463
|
+
'1227179971273316331',
|
|
464
|
+
'575931623374121527',
|
|
465
|
+
'242543240509105209',
|
|
466
|
+
'91437049221049666',
|
|
467
|
+
'30799446349977173',
|
|
468
|
+
'9255276791179340',
|
|
469
|
+
'2478152334826140',
|
|
470
|
+
'590642893610164',
|
|
471
|
+
'125206034929641',
|
|
472
|
+
'23590435911403',
|
|
473
|
+
'3948334035941',
|
|
474
|
+
'586753615614',
|
|
475
|
+
'77391054539',
|
|
476
|
+
'9056793210',
|
|
477
|
+
'940121950',
|
|
478
|
+
'86539696',
|
|
479
|
+
'7062824',
|
|
480
|
+
'510971',
|
|
481
|
+
'32764',
|
|
482
|
+
'1862',
|
|
483
|
+
'94',
|
|
484
|
+
'4',
|
|
485
|
+
'0',
|
|
486
|
+
].map(BigInt);
|
|
471
487
|
// Exact binary64 1/sigma payloads from round-3 fpr.h. Nearby decimal spellings round 1 ULP low in
|
|
472
488
|
// JS, so keep these as decoded bit patterns and recheck the raw payloads after edits.
|
|
473
489
|
const INV_SIGMA = /* @__PURE__ */ Object.freeze([
|
|
@@ -499,6 +515,9 @@ const SIGMA_MIN = /* @__PURE__ */ Object.freeze([
|
|
|
499
515
|
f64b(BigInt('4608433670533905013')),
|
|
500
516
|
f64b(BigInt('4608525754002622308')),
|
|
501
517
|
]);
|
|
518
|
+
// Upper end of the SamplerZ proof interval from Falcon section 3.9.1. The per-leaf sigma is
|
|
519
|
+
// derived from the reconstructed private basis, so imported keys must be checked against it.
|
|
520
|
+
const SIGMA_MAX = 1.8205;
|
|
502
521
|
// Falcon Table 3.1 RCDT values for chi, split into 24-bit limbs; storage is [high, mid, low],
|
|
503
522
|
// so gaussian0() intentionally compares them against v0, v1, v2 in reverse order. The final
|
|
504
523
|
// RCDT[18] = 0 row is omitted because the algorithm iterates only over i = 0..17.
|
|
@@ -1100,6 +1119,51 @@ function getFloatPoly(logn) {
|
|
|
1100
1119
|
},
|
|
1101
1120
|
};
|
|
1102
1121
|
}
|
|
1122
|
+
function ldlFFT(logn, g00t, g01t, g11t) {
|
|
1123
|
+
// Algorithm 8: LDL*(G)
|
|
1124
|
+
// (Page 37)
|
|
1125
|
+
// Require: A full-rank self-adjoint matrix G = (Gᵢⱼ) ∈ FFT(Q[x]/(φ))²ˣ²
|
|
1126
|
+
// Ensure: The LDL* decomposition G = LDL* over FFT(Q[x]/(φ))
|
|
1127
|
+
// Format: All polynomials are in FFT representation.
|
|
1128
|
+
// 1: D₀₀ ← G₀₀
|
|
1129
|
+
// 2: L₁₀ ← G₁₀/G₀₀
|
|
1130
|
+
// 3: D₁₁ ← G₁₁ - L₁₀ ⊙ L₁₀* ⊙ G₀₀
|
|
1131
|
+
// 4: L ← [ 1 0 ; L₁₀ 1 ], D ← [ D₀₀ 0 ; 0 D₁₁ ]
|
|
1132
|
+
// 5: return (L, D)
|
|
1133
|
+
// Algorithm 9: ffLDL*(G)
|
|
1134
|
+
// (Page 37)
|
|
1135
|
+
// Require: A full-rank Gram matrix G ∈ FFT(Q[x]/(xⁿ + 1))²ˣ²
|
|
1136
|
+
// Ensure: A binary tree T
|
|
1137
|
+
// Format: All polynomials are in FFT representation.
|
|
1138
|
+
// 1: (L, D) ← LDL*(G) ▷ L = [ 1 0 ; L₁₀ 1 ], D = [ D₀₀ 0 ; 0 D₁₁ ]
|
|
1139
|
+
// 2: T.value ← L₁₀
|
|
1140
|
+
// 3: if (n = 2) then
|
|
1141
|
+
// 4: T.leftchild ← D₀₀
|
|
1142
|
+
// 5: T.rightchild ← D₁₁
|
|
1143
|
+
// 6: return T
|
|
1144
|
+
// 7: else
|
|
1145
|
+
// 8: d₀₀, d₀₁ ← splitfft(D₀₀) ▷ dᵢⱼ ∈ FFT(Q[x]/(x^{n/2} + 1))
|
|
1146
|
+
// 9: d₁₀, d₁₁ ← splitfft(D₁₁)
|
|
1147
|
+
// 10: G₀ ← [ d₀₀ d₀₁ ; d₀₁* d₀₀ ], G₁ ← [ d₁₀ d₁₁ ; d₁₁* d₁₀ ]
|
|
1148
|
+
// ▷ Since D₀₀, D₁₁ are self-adjoint, (3.30) applies
|
|
1149
|
+
// 11: T.leftchild ← ffLDL*(G₀) ▷ Recursive calls
|
|
1150
|
+
// 12: T.rightchild ← ffLDL*(G₁)
|
|
1151
|
+
// 13: return T
|
|
1152
|
+
// Recursive calls may alias g00t and g11t, and the top-level arrays persist across signing
|
|
1153
|
+
// retries. LDL replaces array entries, so shallow copies keep both kinds of caller state intact.
|
|
1154
|
+
g00t = g00t.slice();
|
|
1155
|
+
g01t = g01t.slice();
|
|
1156
|
+
g11t = g11t.slice();
|
|
1157
|
+
const hn = 1 << (logn - 1);
|
|
1158
|
+
for (let i = 0; i < hn; i++) {
|
|
1159
|
+
const g01 = g01t[i];
|
|
1160
|
+
const g11 = g11t[i];
|
|
1161
|
+
const mu = fComplex.scale(g01, 1.0 / g00t[i].re);
|
|
1162
|
+
g11t[i] = { re: g11.re - (mu.re * g01.re + mu.im * g01.im), im: g11.im };
|
|
1163
|
+
g01t[i] = fComplex.conj(mu);
|
|
1164
|
+
}
|
|
1165
|
+
return { g00: g00t, g01: g01t, g11: g11t };
|
|
1166
|
+
}
|
|
1103
1167
|
function ApproxExp(x, ccs) {
|
|
1104
1168
|
// Algorithm 13: ApproxExp(x, ccs), (Page 42)
|
|
1105
1169
|
// Require: Floating-point values x ∈ [0, ln(2)] and ccs ∈ [0, 1]
|
|
@@ -1147,9 +1211,9 @@ function genFalcon(opts) {
|
|
|
1147
1211
|
let val = 0;
|
|
1148
1212
|
for (let i = 0; i < g; i++) {
|
|
1149
1213
|
const r128 = bytesToNumberLE(this.shake.xof(16));
|
|
1150
|
-
const r1 = r128 &
|
|
1151
|
-
const r2 = (r128 >>
|
|
1152
|
-
const sign = Number((r128 >>
|
|
1214
|
+
const r1 = r128 & MASK_63n;
|
|
1215
|
+
const r2 = (r128 >> _64n) & MASK_63n;
|
|
1216
|
+
const sign = Number((r128 >> _63n) & _1n);
|
|
1153
1217
|
let f = r1 < gauss_1024_12289[0] ? 1 : 0;
|
|
1154
1218
|
let v = 0;
|
|
1155
1219
|
for (let k = 1; k < gauss_1024_12289.length; k++) {
|
|
@@ -1186,7 +1250,7 @@ function genFalcon(opts) {
|
|
|
1186
1250
|
const n = 1 << logn;
|
|
1187
1251
|
const d = new Array(n >> 1);
|
|
1188
1252
|
for (let k = 0; k < n; k += 2) {
|
|
1189
|
-
let s =
|
|
1253
|
+
let s = _0n;
|
|
1190
1254
|
for (let i = 0; i <= k; i += 2)
|
|
1191
1255
|
s += a[i] * a[k - i];
|
|
1192
1256
|
for (let i = k + 2; i < n; i += 2)
|
|
@@ -1194,7 +1258,7 @@ function genFalcon(opts) {
|
|
|
1194
1258
|
d[k >>> 1] = s;
|
|
1195
1259
|
}
|
|
1196
1260
|
for (let k = 0; k < n; k += 2) {
|
|
1197
|
-
let s =
|
|
1261
|
+
let s = _0n;
|
|
1198
1262
|
for (let i = 1; i < k; i += 2)
|
|
1199
1263
|
s += a[i] * a[k - i];
|
|
1200
1264
|
for (let i = k + 1; i < n; i += 2)
|
|
@@ -1206,7 +1270,7 @@ function genFalcon(opts) {
|
|
|
1206
1270
|
mulConjD(logn, d, a, b) {
|
|
1207
1271
|
const n = 1 << logn;
|
|
1208
1272
|
for (let k = 0; k < n; k++) {
|
|
1209
|
-
let s =
|
|
1273
|
+
let s = _0n;
|
|
1210
1274
|
for (let i = 0; i <= k; i += 2)
|
|
1211
1275
|
s += b[i >>> 1] * a[k - i];
|
|
1212
1276
|
for (let i = k + 2 - (k & 1); i < n; i += 2)
|
|
@@ -1221,7 +1285,7 @@ function genFalcon(opts) {
|
|
|
1221
1285
|
subMul(logn, a, b, c, e) {
|
|
1222
1286
|
const n = 1 << logn;
|
|
1223
1287
|
for (let k = 0; k < n; k++) {
|
|
1224
|
-
let s =
|
|
1288
|
+
let s = _0n;
|
|
1225
1289
|
for (let i = 0; i <= k; i++)
|
|
1226
1290
|
s += b[i] * c[k - i];
|
|
1227
1291
|
for (let i = k + 1; i < n; i++)
|
|
@@ -1265,7 +1329,7 @@ function genFalcon(opts) {
|
|
|
1265
1329
|
const Gx = new Float64Array(n);
|
|
1266
1330
|
const k = new Array(n);
|
|
1267
1331
|
while (true) {
|
|
1268
|
-
let scaleFG =
|
|
1332
|
+
let scaleFG = _31n * (FGlen - _10n);
|
|
1269
1333
|
for (let i = 0; i < n; i++) {
|
|
1270
1334
|
Fx[i] = Number(F[i] >> scaleFG);
|
|
1271
1335
|
Gx[i] = Number(G[i] >> scaleFG);
|
|
@@ -1284,16 +1348,16 @@ function genFalcon(opts) {
|
|
|
1284
1348
|
}
|
|
1285
1349
|
F = this.subMul(logn, F, f, k, scaleK); // 3: F ← F - kf
|
|
1286
1350
|
G = this.subMul(logn, G, g, k, scaleK); // 4: G ← G - kg
|
|
1287
|
-
const maxfgNew = scaleK + BigInt(Math.round(fgMaxBits)) +
|
|
1351
|
+
const maxfgNew = scaleK + BigInt(Math.round(fgMaxBits)) + _10n;
|
|
1288
1352
|
if (maxfgNew < maxFGBits)
|
|
1289
1353
|
maxFGBits = maxfgNew;
|
|
1290
|
-
if (FGlen >
|
|
1354
|
+
if (FGlen > _1n && FGlen * _31n >= maxFGBits + _31n)
|
|
1291
1355
|
FGlen--;
|
|
1292
|
-
if (scaleK <=
|
|
1356
|
+
if (scaleK <= _0n)
|
|
1293
1357
|
break;
|
|
1294
|
-
scaleK -=
|
|
1295
|
-
if (scaleK <
|
|
1296
|
-
scaleK =
|
|
1358
|
+
scaleK -= _25n;
|
|
1359
|
+
if (scaleK < _0n)
|
|
1360
|
+
scaleK = _0n;
|
|
1297
1361
|
}
|
|
1298
1362
|
return true;
|
|
1299
1363
|
}
|
|
@@ -1321,11 +1385,11 @@ function genFalcon(opts) {
|
|
|
1321
1385
|
const xf = f[0];
|
|
1322
1386
|
const xg = g[0];
|
|
1323
1387
|
// We can rely on 'invert' to throw if they are not coprime.
|
|
1324
|
-
if (xf <=
|
|
1388
|
+
if (xf <= _0n || xg <= _0n)
|
|
1325
1389
|
return false;
|
|
1326
1390
|
try {
|
|
1327
1391
|
const u1 = invert(xf, xg); // if gcd(f, g) ≠ 1 then
|
|
1328
|
-
const v1 = (
|
|
1392
|
+
const v1 = (_1n - u1 * xf) / xg;
|
|
1329
1393
|
F[0] = -v1 * QBig; // 5: (F, G) ← (vq, uq)
|
|
1330
1394
|
G[0] = u1 * QBig;
|
|
1331
1395
|
return true;
|
|
@@ -1607,7 +1671,14 @@ function genFalcon(opts) {
|
|
|
1607
1671
|
return headerCoder(0x30 + logn, splitCoder('falcon.signature', NONCELEN, sigLen, msg.length)).encode([nonce, pad(sigLen).encode(s2), msg]);
|
|
1608
1672
|
},
|
|
1609
1673
|
decode(data) {
|
|
1674
|
+
// Keep API misuse on the coder's TypeError path before reading the container length.
|
|
1675
|
+
abytes(data, undefined, 'signature');
|
|
1676
|
+
// The compressed-signature field is fixed-width here; only the message is variable. A
|
|
1677
|
+
// container shorter than the fixed part would make the s2 field borrow bytes from nowhere
|
|
1678
|
+
// and let a truncated encoding open to the same message.
|
|
1610
1679
|
const msgLen = data.length - NONCELEN - sigLen - 1;
|
|
1680
|
+
if (msgLen < 0)
|
|
1681
|
+
throw new Error('signature coder: wrong length');
|
|
1611
1682
|
const [nonce, s2, msg] = headerCoder(0x30 + logn, splitCoder('falcon.signature', NONCELEN, sigLen, msgLen)).decode(data);
|
|
1612
1683
|
return { nonce, s2: decodeSig(s2), msg };
|
|
1613
1684
|
},
|
|
@@ -1615,14 +1686,25 @@ function genFalcon(opts) {
|
|
|
1615
1686
|
};
|
|
1616
1687
|
// [ 1B header ] [ 40B nonce ] [ compressed_sig ]
|
|
1617
1688
|
const SignatureCoderDetached = (logn) => {
|
|
1618
|
-
const
|
|
1619
|
-
const getSigLen = (s2) => (opts.padded ?
|
|
1689
|
+
const paddedSigLen = opts.sigLen - 1 - NONCELEN;
|
|
1690
|
+
const getSigLen = (s2) => (opts.padded ? paddedSigLen : s2.length);
|
|
1620
1691
|
return {
|
|
1621
1692
|
encode({ nonce, s2 }) {
|
|
1622
|
-
return headerCoder(0x30 + logn, splitCoder('falcon.detachedSignature', NONCELEN, getSigLen(s2))).encode([nonce, opts.padded ? pad(
|
|
1693
|
+
return headerCoder(0x30 + logn, splitCoder('falcon.detachedSignature', NONCELEN, getSigLen(s2))).encode([nonce, opts.padded ? pad(paddedSigLen).encode(s2) : s2]);
|
|
1623
1694
|
},
|
|
1624
1695
|
decode(data) {
|
|
1625
|
-
|
|
1696
|
+
// Unpadded Round-3 signatures have parameter-set maxima (header + nonce + s2):
|
|
1697
|
+
// 752 bytes for Falcon-512 and 1462 for Falcon-1024. Reject before creating views or
|
|
1698
|
+
// entering the bit decoder so attacker-sized inputs cannot cause proportional work.
|
|
1699
|
+
if (!opts.padded && data.length > 1 + NONCELEN + opts.maxS2Len)
|
|
1700
|
+
throw new Error('detached signature too long');
|
|
1701
|
+
// Padded detached signatures are fixed-length (`lengths.signature`), so the payload width
|
|
1702
|
+
// must come from the parameter set, not from the input: deriving it would accept appended
|
|
1703
|
+
// zero bytes and truncated padding as extra valid encodings of the same signature.
|
|
1704
|
+
// Unpadded signatures are variable-length; decodeUnpaddedSig() enforces the exact canonical
|
|
1705
|
+
// bitlength of whatever remains.
|
|
1706
|
+
const payloadLen = opts.padded ? paddedSigLen : data.length - NONCELEN - 1;
|
|
1707
|
+
const [nonce, raw] = headerCoder(0x30 + logn, splitCoder('falcon.detachedSignature', NONCELEN, payloadLen)).decode(data);
|
|
1626
1708
|
const s2 = decodeSig(raw);
|
|
1627
1709
|
return { nonce, s2 };
|
|
1628
1710
|
},
|
|
@@ -1710,7 +1792,7 @@ function genFalcon(opts) {
|
|
|
1710
1792
|
shakeBuf;
|
|
1711
1793
|
ctrView;
|
|
1712
1794
|
// ChaCha
|
|
1713
|
-
ctr =
|
|
1795
|
+
ctr = _0n;
|
|
1714
1796
|
buf;
|
|
1715
1797
|
buf32;
|
|
1716
1798
|
pos;
|
|
@@ -1760,8 +1842,8 @@ function genFalcon(opts) {
|
|
|
1760
1842
|
const out32 = swap32IfBE(this.buf32);
|
|
1761
1843
|
for (let i = 0; i < 8; i++, this.ctr++) {
|
|
1762
1844
|
const n = swap32IfBE(this.nonce32.slice()); // [n0, n1, n2, n3]
|
|
1763
|
-
n[2] ^= Number(this.ctr &
|
|
1764
|
-
n[3] ^= Number(this.ctr >>
|
|
1845
|
+
n[2] ^= Number(this.ctr & MASK_32n);
|
|
1846
|
+
n[3] ^= Number(this.ctr >> _32n);
|
|
1765
1847
|
// chacha20() takes raw nonce bytes; on BE the word-normalized temp must be swapped back.
|
|
1766
1848
|
swap32IfBE(n.subarray(1));
|
|
1767
1849
|
chacha20(this.key, u8(n.subarray(1)), EMPTY_CHACHA20_BLOCK, this.curBlock, n[0]);
|
|
@@ -1875,47 +1957,6 @@ function genFalcon(opts) {
|
|
|
1875
1957
|
return s + z;
|
|
1876
1958
|
}
|
|
1877
1959
|
}
|
|
1878
|
-
ldlFFT(logn, g00t, g01t, g11t) {
|
|
1879
|
-
// Algorithm 8: LDL*(G)
|
|
1880
|
-
// (Page 37)
|
|
1881
|
-
// Require: A full-rank self-adjoint matrix G = (Gᵢⱼ) ∈ FFT(Q[x]/(φ))²ˣ²
|
|
1882
|
-
// Ensure: The LDL* decomposition G = LDL* over FFT(Q[x]/(φ))
|
|
1883
|
-
// Format: All polynomials are in FFT representation.
|
|
1884
|
-
// 1: D₀₀ ← G₀₀
|
|
1885
|
-
// 2: L₁₀ ← G₁₀/G₀₀
|
|
1886
|
-
// 3: D₁₁ ← G₁₁ - L₁₀ ⊙ L₁₀* ⊙ G₀₀
|
|
1887
|
-
// 4: L ← [ 1 0 ; L₁₀ 1 ], D ← [ D₀₀ 0 ; 0 D₁₁ ]
|
|
1888
|
-
// 5: return (L, D)
|
|
1889
|
-
// Algorithm 9: ffLDL*(G)
|
|
1890
|
-
// (Page 37)
|
|
1891
|
-
// Require: A full-rank Gram matrix G ∈ FFT(Q[x]/(xⁿ + 1))²ˣ²
|
|
1892
|
-
// Ensure: A binary tree T
|
|
1893
|
-
// Format: All polynomials are in FFT representation.
|
|
1894
|
-
// 1: (L, D) ← LDL*(G) ▷ L = [ 1 0 ; L₁₀ 1 ], D = [ D₀₀ 0 ; 0 D₁₁ ]
|
|
1895
|
-
// 2: T.value ← L₁₀
|
|
1896
|
-
// 3: if (n = 2) then
|
|
1897
|
-
// 4: T.leftchild ← D₀₀
|
|
1898
|
-
// 5: T.rightchild ← D₁₁
|
|
1899
|
-
// 6: return T
|
|
1900
|
-
// 7: else
|
|
1901
|
-
// 8: d₀₀, d₀₁ ← splitfft(D₀₀) ▷ dᵢⱼ ∈ FFT(Q[x]/(x^{n/2} + 1))
|
|
1902
|
-
// 9: d₁₀, d₁₁ ← splitfft(D₁₁)
|
|
1903
|
-
// 10: G₀ ← [ d₀₀ d₀₁ ; d₀₁* d₀₀ ], G₁ ← [ d₁₀ d₁₁ ; d₁₁* d₁₀ ]
|
|
1904
|
-
// ▷ Since D₀₀, D₁₁ are self-adjoint, (3.30) applies
|
|
1905
|
-
// 11: T.leftchild ← ffLDL*(G₀) ▷ Recursive calls
|
|
1906
|
-
// 12: T.rightchild ← ffLDL*(G₁)
|
|
1907
|
-
// 13: return T
|
|
1908
|
-
g00t = g00t.slice(); // can be same as g11t and everything will break!
|
|
1909
|
-
const hn = 1 << (logn - 1);
|
|
1910
|
-
for (let i = 0; i < hn; i++) {
|
|
1911
|
-
const g01 = g01t[i];
|
|
1912
|
-
const g11 = g11t[i];
|
|
1913
|
-
const mu = fComplex.scale(g01, 1.0 / g00t[i].re);
|
|
1914
|
-
g11t[i] = { re: g11.re - (mu.re * g01.re + mu.im * g01.im), im: g11.im };
|
|
1915
|
-
g01t[i] = fComplex.conj(mu);
|
|
1916
|
-
}
|
|
1917
|
-
return { g00: g00t, g01: g01t, g11: g11t };
|
|
1918
|
-
}
|
|
1919
1960
|
splitFFT(logn, f) {
|
|
1920
1961
|
// Algorithm 1: splitfft(FFT(f))
|
|
1921
1962
|
// (Page 29)
|
|
@@ -2021,7 +2062,13 @@ function genFalcon(opts) {
|
|
|
2021
2062
|
// 13: z₀ ← mergefft(z'₀)
|
|
2022
2063
|
// 14: return z = (z₀, z₁)
|
|
2023
2064
|
if (logn === 0) {
|
|
2065
|
+
// The dynamic sampler stores 1/σ' instead of σ'. Keygen guarantees this interval,
|
|
2066
|
+
// but an imported compact key may reconstruct an invalid basis. Check the actual LDL*
|
|
2067
|
+
// leaf before it can drive SamplerZ; the negated comparison also rejects NaN/infinity.
|
|
2024
2068
|
const leaf = Math.sqrt(g00i[0].re) * INV_SIGMA[this.logn];
|
|
2069
|
+
const sigmaPrime = 1 / leaf;
|
|
2070
|
+
if (!(sigmaPrime >= SIGMA_MIN[this.logn] && sigmaPrime <= SIGMA_MAX))
|
|
2071
|
+
throw new Error('invalid secretKey: sampler sigma out of range');
|
|
2025
2072
|
// 3: z₀ ← SamplerZ(t₀, σ')
|
|
2026
2073
|
// ▷ Since n=1, tᵢ = invFFT(tᵢ) ∈ Q and zᵢ = invFFT(zᵢ) ∈ Z
|
|
2027
2074
|
const t0re = this.samplerZ(t0[0].re, leaf);
|
|
@@ -2029,7 +2076,7 @@ function genFalcon(opts) {
|
|
|
2029
2076
|
return { t0: [{ re: t0re, im: 0.0 }], t1: [{ re: t1re, im: 0.0 }] };
|
|
2030
2077
|
}
|
|
2031
2078
|
// 6: (l, T₀, T₁) ← (T.value, T.leftchild, T.rightchild)
|
|
2032
|
-
const { g00, g01, g11 } =
|
|
2079
|
+
const { g00, g01, g11 } = ldlFFT(logn, g00i, g01i, g11i);
|
|
2033
2080
|
const { f0: g00f0, f1: g00f1 } = this.splitSelfAdjFFT(logn, g00);
|
|
2034
2081
|
const { f0: g11f0, f1: g11f1 } = this.splitSelfAdjFFT(logn, g11);
|
|
2035
2082
|
// 7: t'₁ ← splitfft(t₁)
|
|
@@ -2078,7 +2125,8 @@ function genFalcon(opts) {
|
|
|
2078
2125
|
// ▷ Remove 1 byte for the header, and 40 bytes for r
|
|
2079
2126
|
// 11: while (s = ⊥)
|
|
2080
2127
|
// 12: return sig = (r, s)
|
|
2081
|
-
abytes(msg);
|
|
2128
|
+
abytes(msg, undefined, 'msg');
|
|
2129
|
+
abytes(sk, secretKeyCoder.bytesLen, 'secretKey');
|
|
2082
2130
|
// One RNG stream drives both the public 40-byte nonce and the 48-byte sampler seed, so
|
|
2083
2131
|
// deterministic rnd hooks make signatures deterministic for fixed secretKey/message inputs.
|
|
2084
2132
|
const nonce = rnd(40);
|
|
@@ -2178,12 +2226,19 @@ function genFalcon(opts) {
|
|
|
2178
2226
|
publicKey: publicKeyCoder.bytesLen,
|
|
2179
2227
|
secretKey: secretKeyCoder.bytesLen,
|
|
2180
2228
|
});
|
|
2229
|
+
// Falcon takes a sampler callback the other schemes do not, and rejects `context`
|
|
2230
|
+
// with its own message; both stay in the accepted set so the specific errors fire.
|
|
2231
|
+
const FALCON_SIG_OPT_KEYS = [...SIG_OPT_KEYS, 'random'];
|
|
2181
2232
|
// Noble exposes a 48-byte sampler-seed hook,
|
|
2182
2233
|
// but Falcon still samples/encodes a separate 40-byte nonce per signature.
|
|
2183
2234
|
const getRnd = (opts = {}) => {
|
|
2184
|
-
|
|
2235
|
+
// `context` stays in the accepted set so the specific "not supported" error below
|
|
2236
|
+
// still fires, rather than the generic unexpected-option one.
|
|
2237
|
+
opts = validateSigOpts(opts, FALCON_SIG_OPT_KEYS);
|
|
2185
2238
|
if (opts.context !== undefined)
|
|
2186
2239
|
throw new Error('context is not supported');
|
|
2240
|
+
if (opts.random !== undefined && typeof opts.random !== 'function')
|
|
2241
|
+
throw new TypeError('"opts.random" expected function, got type=' + typeof opts.random);
|
|
2187
2242
|
if (opts.random !== undefined)
|
|
2188
2243
|
return opts.random;
|
|
2189
2244
|
if (opts.extraEntropy === undefined)
|
|
@@ -2194,8 +2249,8 @@ function genFalcon(opts) {
|
|
|
2194
2249
|
return (len = 0) => drbg.randomBytes(len);
|
|
2195
2250
|
};
|
|
2196
2251
|
const checkVerOpts = (opts = {}) => {
|
|
2197
|
-
validateVerOpts(opts);
|
|
2198
|
-
if (
|
|
2252
|
+
const normalized = validateVerOpts(opts);
|
|
2253
|
+
if (normalized.context !== undefined)
|
|
2199
2254
|
throw new Error('context is not supported');
|
|
2200
2255
|
};
|
|
2201
2256
|
const tests = Object.freeze({
|
|
@@ -2223,6 +2278,7 @@ function genFalcon(opts) {
|
|
|
2223
2278
|
return { publicKey: pk, secretKey: sk };
|
|
2224
2279
|
};
|
|
2225
2280
|
const getPublicKey = (sk) => {
|
|
2281
|
+
abytes(sk, secretKeyCoder.bytesLen, 'secretKey');
|
|
2226
2282
|
const [f, g, F] = secretKeyCoder.decode(sk);
|
|
2227
2283
|
try {
|
|
2228
2284
|
const h = computePublic(f, g);
|
|
@@ -2245,9 +2301,10 @@ function genFalcon(opts) {
|
|
|
2245
2301
|
*/
|
|
2246
2302
|
const verify = (sig, msg, pk, verOpts = {}) => {
|
|
2247
2303
|
checkVerOpts(verOpts);
|
|
2248
|
-
abytes(sig);
|
|
2249
|
-
abytes(msg);
|
|
2250
|
-
|
|
2304
|
+
abytes(sig, undefined, 'signature');
|
|
2305
|
+
abytes(msg, undefined, 'msg');
|
|
2306
|
+
// Length/canonical public-key failures are decoded below and return false; only type is fatal.
|
|
2307
|
+
abytes(pk, undefined, 'publicKey');
|
|
2251
2308
|
try {
|
|
2252
2309
|
const { s2, nonce } = SignatureCoderDetached(logn).decode(sig);
|
|
2253
2310
|
return verifyRaw(pk, s2, nonce, msg);
|
|
@@ -2267,12 +2324,43 @@ function genFalcon(opts) {
|
|
|
2267
2324
|
},
|
|
2268
2325
|
open(sig, pk, verOpts = {}) {
|
|
2269
2326
|
checkVerOpts(verOpts);
|
|
2270
|
-
|
|
2271
|
-
//
|
|
2272
|
-
//
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2327
|
+
// Wrong argument types are caller bugs and must stay TypeErrors; only what happens
|
|
2328
|
+
// after this is untrusted input. Detached verify() type-checks the public key the same
|
|
2329
|
+
// way, so open() does too: a wrong type is fatal, and a malformed (wrong-length or
|
|
2330
|
+
// non-canonical) key folds into the single rejection below rather than leaking a raw
|
|
2331
|
+
// codec error, exactly as detached verify() folds it into `false`.
|
|
2332
|
+
abytes(sig, undefined, 'signature');
|
|
2333
|
+
abytes(pk, undefined, 'publicKey');
|
|
2334
|
+
// Decode and verify owned snapshots. Apart from keeping the authenticated result stable
|
|
2335
|
+
// after open() returns, this ensures every verification step observes the same bytes when
|
|
2336
|
+
// an input is backed by SharedArrayBuffer or has subclass-overridden view methods.
|
|
2337
|
+
const ownedSig = copyBytes(sig);
|
|
2338
|
+
const ownedPk = copyBytes(pk);
|
|
2339
|
+
// Decode failures and malformed-key failures are rejected signatures, not internal
|
|
2340
|
+
// faults. Letting the codec's own errors out gave a caller handling untrusted input
|
|
2341
|
+
// several different messages for one corrupt byte, including "end of buffer: len=2
|
|
2342
|
+
// buf=0 lastByte=undefined", which reads as a library bug. Detached verify already
|
|
2343
|
+
// treats every such failure uniformly; open() collapses them into one Error (the
|
|
2344
|
+
// original preserved as `cause`). A well-formed signature that simply does not
|
|
2345
|
+
// validate falls through to the same message with no cause.
|
|
2346
|
+
try {
|
|
2347
|
+
let verifiedMsg;
|
|
2348
|
+
try {
|
|
2349
|
+
const { s2, nonce, msg } = SignatureCoder.decode(ownedSig);
|
|
2350
|
+
if (verifyRaw(ownedPk, s2, nonce, msg))
|
|
2351
|
+
verifiedMsg = msg;
|
|
2352
|
+
}
|
|
2353
|
+
catch (cause) {
|
|
2354
|
+
throw new Error('invalid signature', { cause });
|
|
2355
|
+
}
|
|
2356
|
+
if (verifiedMsg === undefined)
|
|
2357
|
+
throw new Error('invalid signature');
|
|
2358
|
+
// Do not retain or expose the full attached-signature allocation through `.buffer`.
|
|
2359
|
+
return copyBytes(verifiedMsg);
|
|
2360
|
+
}
|
|
2361
|
+
finally {
|
|
2362
|
+
cleanBytes(ownedSig, ownedPk);
|
|
2363
|
+
}
|
|
2276
2364
|
},
|
|
2277
2365
|
});
|
|
2278
2366
|
const res = {
|
|
@@ -2289,14 +2377,14 @@ function genFalcon(opts) {
|
|
|
2289
2377
|
}
|
|
2290
2378
|
const falcon512opts = {
|
|
2291
2379
|
N: 512,
|
|
2380
|
+
// Keep the mode an own property: omitted config fields must not inherit from Object.prototype.
|
|
2381
|
+
padded: false,
|
|
2292
2382
|
// Table 3.3 fixed padded detached bytes, including the detached header byte and 40-byte nonce.
|
|
2293
2383
|
sigLen: 666,
|
|
2294
2384
|
fgBits: 6,
|
|
2295
2385
|
FGBits: 8,
|
|
2296
2386
|
// Compressed-s payload bytes only, excluding the detached header byte and 40-byte nonce.
|
|
2297
2387
|
paddedLen: 625,
|
|
2298
|
-
// Payload-only budget: genFalcon() adds the detached header byte and 40-byte nonce around it.
|
|
2299
|
-
detachedLen: 690,
|
|
2300
2388
|
};
|
|
2301
2389
|
/**
|
|
2302
2390
|
* Falcon-512 detached-signature API with the attached helper exposed as `.attached`.
|
|
@@ -2328,14 +2416,13 @@ export const falcon512padded = /* @__PURE__ */ (() => genFalcon({
|
|
|
2328
2416
|
}))();
|
|
2329
2417
|
const falcon1024opts = {
|
|
2330
2418
|
N: 1024,
|
|
2419
|
+
padded: false,
|
|
2331
2420
|
// Table 3.3 fixed padded detached bytes, including the detached header byte and 40-byte nonce.
|
|
2332
2421
|
sigLen: 1280,
|
|
2333
2422
|
fgBits: 5,
|
|
2334
2423
|
FGBits: 8,
|
|
2335
2424
|
// Compressed-s payload bytes only, excluding the detached header byte and 40-byte nonce.
|
|
2336
2425
|
paddedLen: 1239,
|
|
2337
|
-
// Payload-only budget: genFalcon() adds the detached header byte and 40-byte nonce around it.
|
|
2338
|
-
detachedLen: 1280,
|
|
2339
2426
|
};
|
|
2340
2427
|
/**
|
|
2341
2428
|
* Falcon-1024 detached-signature API with the attached helper exposed as `.attached`.
|
|
@@ -2376,10 +2463,10 @@ export const __tests = /* @__PURE__ */ (() => Object.freeze({
|
|
|
2376
2463
|
INV_SIGMA,
|
|
2377
2464
|
SIGMA_MIN,
|
|
2378
2465
|
getFloatPoly,
|
|
2466
|
+
ldlFFT,
|
|
2379
2467
|
cleanCPoly,
|
|
2380
2468
|
falcon512: falcon512.__test,
|
|
2381
2469
|
falcon512padded: falcon512padded.__test,
|
|
2382
2470
|
falcon1024: falcon1024.__test,
|
|
2383
2471
|
falcon1024padded: falcon1024padded.__test,
|
|
2384
2472
|
}))();
|
|
2385
|
-
//# sourceMappingURL=falcon.js.map
|