@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/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 (; !readBits(1); high++)
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
- 1283868770400643928n,
444
- 6416574995475331444n,
445
- 4078260278032692663n,
446
- 2353523259288686585n,
447
- 1227179971273316331n,
448
- 575931623374121527n,
449
- 242543240509105209n,
450
- 91437049221049666n,
451
- 30799446349977173n,
452
- 9255276791179340n,
453
- 2478152334826140n,
454
- 590642893610164n,
455
- 125206034929641n,
456
- 23590435911403n,
457
- 3948334035941n,
458
- 586753615614n,
459
- 77391054539n,
460
- 9056793210n,
461
- 940121950n,
462
- 86539696n,
463
- 7062824n,
464
- 510971n,
465
- 32764n,
466
- 1862n,
467
- 94n,
468
- 4n,
469
- 0n,
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 & 0x7fffffffffffffffn;
1151
- const r2 = (r128 >> 64n) & 0x7fffffffffffffffn;
1152
- const sign = Number((r128 >> 63n) & 1n);
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 = 0n;
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 = 0n;
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 = 0n;
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 = 0n;
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 = 31n * (FGlen - 10n);
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)) + 10n;
1351
+ const maxfgNew = scaleK + BigInt(Math.round(fgMaxBits)) + _10n;
1288
1352
  if (maxfgNew < maxFGBits)
1289
1353
  maxFGBits = maxfgNew;
1290
- if (FGlen > 1n && FGlen * 31n >= maxFGBits + 31n)
1354
+ if (FGlen > _1n && FGlen * _31n >= maxFGBits + _31n)
1291
1355
  FGlen--;
1292
- if (scaleK <= 0n)
1356
+ if (scaleK <= _0n)
1293
1357
  break;
1294
- scaleK -= 25n;
1295
- if (scaleK < 0n)
1296
- scaleK = 0n;
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 <= 0n || xg <= 0n)
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 = (1n - u1 * xf) / xg;
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 sigLen = opts.padded ? opts.sigLen - 1 - NONCELEN : opts.detachedLen;
1619
- const getSigLen = (s2) => (opts.padded ? sigLen : s2.length);
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(sigLen).encode(s2) : s2]);
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
- const [nonce, raw] = headerCoder(0x30 + logn, splitCoder('falcon.detachedSignature', NONCELEN, data.length - NONCELEN - 1)).decode(data);
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 = 0n;
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 & 0xffffffffn);
1764
- n[3] ^= Number(this.ctr >> 32n);
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 } = this.ldlFFT(logn, g00i, g01i, g11i);
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
- validateSigOpts(opts);
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 (opts.context !== undefined)
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
- abytes(pk);
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
- const { s2, nonce, msg } = SignatureCoder.decode(sig);
2271
- // Zero-copy API: returned message aliases the caller-provided signature buffer.
2272
- // Copy it if ownership is needed.
2273
- if (verifyRaw(pk, s2, nonce, msg))
2274
- return msg;
2275
- throw new Error('invalid signature');
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