@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/src/falcon.ts CHANGED
@@ -24,6 +24,7 @@ import {
24
24
  } from '@noble/hashes/utils.js';
25
25
  import { genCrystals, type TypedCons } from './_crystals.ts';
26
26
  import {
27
+ baswap64If,
27
28
  type BytesCoderLen,
28
29
  cleanBytes,
29
30
  type Coder,
@@ -31,8 +32,9 @@ import {
31
32
  getMask,
32
33
  type Signer,
33
34
  type SigOpts,
34
- baswap64If,
35
35
  splitCoder,
36
+ type TArg,
37
+ type TRet,
36
38
  validateSigOpts,
37
39
  validateVerOpts,
38
40
  type VerOpts,
@@ -121,12 +123,12 @@ const bitsCoderMSB = <T extends TypedArray>(
121
123
  N: number,
122
124
  d: number,
123
125
  c: Coder<number, number>
124
- ): BytesCoderLen<T> => {
126
+ ): TRet<BytesCoderLen<T>> => {
125
127
  const mask = getMask(d);
126
128
  const bytesLen = d * (N / 8);
127
129
  return {
128
130
  bytesLen,
129
- encode: (poly: T): Uint8Array => {
131
+ encode: (poly: TArg<T>): TRet<Uint8Array> => {
130
132
  if (poly.length !== N) throw new Error(`wrong length: expected ${N}, got ${poly.length}`);
131
133
  const r = new Uint8Array(bytesLen);
132
134
  for (let i = 0, buf = 0, bufLen = 0, pos = 0; i < poly.length; i++) {
@@ -134,38 +136,39 @@ const bitsCoderMSB = <T extends TypedArray>(
134
136
  bufLen += d;
135
137
  for (; bufLen >= 8; bufLen -= 8) r[pos++] = (buf >>> (bufLen - 8)) & 0xff;
136
138
  }
137
- return r;
139
+ return r as TRet<Uint8Array>;
138
140
  },
139
- decode: (bytes: Uint8Array): T => {
141
+ decode: (bytes: TArg<Uint8Array>): TRet<T> => {
140
142
  const r = newPoly(N);
141
143
  for (let i = 0, buf = 0, bufLen = 0, pos = 0; i < bytes.length; i++) {
142
144
  buf = (buf << 8) | bytes[i];
143
145
  bufLen += 8;
144
146
  for (; bufLen >= d; bufLen -= d) r[pos++] = c.decode((buf >>> (bufLen - d)) & mask);
145
147
  }
146
- return r;
148
+ return r as TRet<T>;
147
149
  },
148
- };
150
+ } as TRet<BytesCoderLen<T>>;
149
151
  };
150
152
  // Adds a single leading tag byte. Exact body validation is delegated to `restCoder.decode()`.
151
153
  // encode() zeroizes the temporary encoded body after copying, so wrapped encoders must return
152
154
  // owned scratch bytes rather than caller-owned buffers.
153
- const headerCoder = <T>(tag: number, restCoder: BytesCoderLen<T>): BytesCoderLen<T> => {
155
+ const headerCoder = <T>(tag: number, restCoder: TArg<BytesCoderLen<T>>): TRet<BytesCoderLen<T>> => {
156
+ const coder = restCoder as BytesCoderLen<T>;
154
157
  return {
155
- bytesLen: 1 + restCoder.bytesLen,
156
- encode(value: T): Uint8Array {
157
- const body = restCoder.encode(value);
158
+ bytesLen: 1 + coder.bytesLen,
159
+ encode(value: TArg<T>): TRet<Uint8Array> {
160
+ const body = coder.encode(value as T);
158
161
  const out = new Uint8Array(1 + body.length);
159
162
  out[0] = tag;
160
163
  out.set(body, 1);
161
164
  cleanBytes(body);
162
- return out;
165
+ return out as TRet<Uint8Array>;
163
166
  },
164
- decode(data: Uint8Array): T {
167
+ decode(data: TArg<Uint8Array>): TRet<T> {
165
168
  if (data[0] !== tag) throw new Error(`wrong tag: expected ${tag}, got 0x${data[0]}`);
166
- return restCoder.decode(data.subarray(1));
169
+ return coder.decode(data.subarray(1)) as TRet<T>;
167
170
  },
168
- };
171
+ } as TRet<BytesCoderLen<T>>;
169
172
  };
170
173
 
171
174
  // Fun, but overengineered. Hoping FIPS would fix this.
@@ -176,7 +179,7 @@ const headerCoder = <T>(tag: number, restCoder: BytesCoderLen<T>): BytesCoderLen
176
179
  const compCoder = (n: number) => {
177
180
  const LIMIT = 2047;
178
181
  return {
179
- encode(data: Int16Array): Uint8Array {
182
+ encode(data: TArg<Int16Array>): TRet<Uint8Array> {
180
183
  // Algorithm 17: Compress(s, slen) (Page 47)
181
184
  // Require: A polynomial s = Σ sᵢxⁱ ∈ Z[x] of degree < n, a string bitlength slen
182
185
  // Ensure: A compressed representation str of s of slen bits, or ⊥
@@ -216,9 +219,9 @@ const compCoder = (n: number) => {
216
219
  writeBits((v >>> 7) + 1, 1); // high (unary)
217
220
  }
218
221
  if (bufLen > 0) res.push((buf << (8 - bufLen)) & 0xff);
219
- return new Uint8Array(res);
222
+ return new Uint8Array(res) as TRet<Uint8Array>;
220
223
  },
221
- decode(data: Uint8Array): Int16Array {
224
+ decode(data: TArg<Uint8Array>): TRet<Int16Array> {
222
225
  // Algorithm 18: Decompress(str, slen), (Page 48)
223
226
  // Require: A bitstring str = (str[i])_{i=0,...,slen-1}, a bitlength slen
224
227
  // Ensure: A polynomial s = Σ sᵢxⁱ ∈ Z[x], or ⊥
@@ -260,7 +263,7 @@ const compCoder = (n: number) => {
260
263
  res[resPos] = sign ? -v : v;
261
264
  }
262
265
  if (buf) throw new Error('non-empty accumulator');
263
- return res;
266
+ return res as TRet<Int16Array>;
264
267
  },
265
268
  };
266
269
  };
@@ -268,12 +271,12 @@ const compCoder = (n: number) => {
268
271
  // Falcon padded-signature helper. encode() assumes `data.length <= len`; decode() strips trailing
269
272
  // zero padding and returns a subarray view, so it is not a generic byte-string codec.
270
273
  const pad = (len: number) => ({
271
- encode(data: Uint8Array) {
274
+ encode(data: TArg<Uint8Array>) {
272
275
  const res = new Uint8Array(len);
273
276
  res.set(data);
274
277
  return res;
275
278
  },
276
- decode(data: Uint8Array) {
279
+ decode(data: TArg<Uint8Array>) {
277
280
  let end = data.length;
278
281
  while (end > 0 && data[end - 1] === 0) end--;
279
282
  return data.subarray(0, end);
@@ -391,22 +394,22 @@ const ComplexArrInterleaved = {
391
394
  },
392
395
  };
393
396
  // Alias a Float64Array as bytes for the root-table hash pin; not a portable serialization.
394
- const u8f = (arr: Float64Array): Uint8Array =>
395
- new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength);
397
+ const u8f = (arr: TArg<Float64Array>): TRet<Uint8Array> =>
398
+ new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength) as TRet<Uint8Array>;
396
399
 
397
400
  // Alias bytes as Float64Array lanes. Falcon's exact binary64 tables are stored as little-endian
398
401
  // payload bytes, so BE runtimes must decode lane-by-lane instead of aliasing host-endian floats.
399
402
  // Copy/truncate to whole 8-byte lanes first
400
403
  // so BE byte swaps cannot mutate caller-owned bytes
401
404
  // or read a partial float.
402
- const f64a = (arr: Uint8Array): Float64Array =>
405
+ const f64a = (arr: TArg<Uint8Array>): TRet<Float64Array> =>
403
406
  new Float64Array(
404
407
  baswap64If(Uint8Array.from(arr.subarray(0, Math.floor(arr.byteLength / 8) * 8))).buffer
405
- );
408
+ ) as TRet<Float64Array>;
406
409
 
407
410
  // Exact big-endian binary64 hex helper for constants. Only decode() is currently used; malformed
408
411
  // inputs fail through lower-level hex / DataView checks instead of an explicit wrapper guard.
409
- const Float = {
412
+ const Float = /* @__PURE__ */ Object.freeze({
410
413
  encode(n: number): string {
411
414
  const bytes = new Uint8Array(8);
412
415
  const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
@@ -418,7 +421,7 @@ const Float = {
418
421
  const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
419
422
  return view.getFloat64(0, false);
420
423
  },
421
- };
424
+ });
422
425
  // Decode a 64-bit bigint bit pattern into the exact binary64 value.
423
426
  const f64b = (n: bigint): number => Float.decode(numberToHexUnpadded(n));
424
427
 
@@ -426,8 +429,6 @@ const f64b = (n: bigint): number => Float.decode(numberToHexUnpadded(n));
426
429
  type SignatureRaw = { msg: Uint8Array; nonce: Uint8Array; s2: Uint8Array };
427
430
  type BPoly = bigint[];
428
431
  type FPoly = Float64Array;
429
- // Keep these non-generic to match the rest of noble;
430
- // newer typed-array defs get handled at call sites.
431
432
  type SPoly = Int8Array; // Small poly (f/g/F/G)
432
433
  type IPoly = Uint16Array; // Integer poly mod Q
433
434
 
@@ -442,6 +443,17 @@ const Q: number = 12289; // 12 * 1024 + 1
442
443
  // Falcon's midpoint floor(q/2); the only live use is the mirrored G-reconstruction reduction below.
443
444
  const Qhalf: number = Q >> 1;
444
445
  const QBig = BigInt(Q);
446
+ const _0n = /* @__PURE__ */ BigInt(0);
447
+ const _1n = /* @__PURE__ */ BigInt(1);
448
+ const _10n = /* @__PURE__ */ BigInt(10);
449
+ const _25n = /* @__PURE__ */ BigInt(25);
450
+ const _31n = /* @__PURE__ */ BigInt(31);
451
+ const _32n = /* @__PURE__ */ BigInt(32);
452
+ const _63n = /* @__PURE__ */ BigInt(63);
453
+ const _64n = /* @__PURE__ */ BigInt(64);
454
+ // Low 32 bits and low 63 bits of a bigint, used by the chacha20 counter and gaussian sampler.
455
+ const MASK_32n = /* @__PURE__ */ BigInt('0xffffffff');
456
+ const MASK_63n = /* @__PURE__ */ BigInt('0x7fffffffffffffff');
445
457
  //const R = 4091; // 2^16 mod q
446
458
  // This 16-bit Montgomery kernel uses R = 2^16, so mul(x, R2) converts x into Montgomery form.
447
459
  const R2 = 10952; // 2^32 mod q
@@ -478,39 +490,39 @@ const BITLENGTH = [
478
490
  // Smaller Falcon dimensions reuse the N = 1024, q = 12289 table by summing 2^(10-logn) draws.
479
491
  // The trailing 0 sentinel guarantees gaussSingle()
480
492
  // always selects a tail bucket when x = 0 is missed.
481
- const gauss_1024_12289 = [
482
- 1283868770400643928n,
483
- 6416574995475331444n,
484
- 4078260278032692663n,
485
- 2353523259288686585n,
486
- 1227179971273316331n,
487
- 575931623374121527n,
488
- 242543240509105209n,
489
- 91437049221049666n,
490
- 30799446349977173n,
491
- 9255276791179340n,
492
- 2478152334826140n,
493
- 590642893610164n,
494
- 125206034929641n,
495
- 23590435911403n,
496
- 3948334035941n,
497
- 586753615614n,
498
- 77391054539n,
499
- 9056793210n,
500
- 940121950n,
501
- 86539696n,
502
- 7062824n,
503
- 510971n,
504
- 32764n,
505
- 1862n,
506
- 94n,
507
- 4n,
508
- 0n,
509
- ];
493
+ const gauss_1024_12289 = /* @__PURE__ */ [
494
+ '1283868770400643928',
495
+ '6416574995475331444',
496
+ '4078260278032692663',
497
+ '2353523259288686585',
498
+ '1227179971273316331',
499
+ '575931623374121527',
500
+ '242543240509105209',
501
+ '91437049221049666',
502
+ '30799446349977173',
503
+ '9255276791179340',
504
+ '2478152334826140',
505
+ '590642893610164',
506
+ '125206034929641',
507
+ '23590435911403',
508
+ '3948334035941',
509
+ '586753615614',
510
+ '77391054539',
511
+ '9056793210',
512
+ '940121950',
513
+ '86539696',
514
+ '7062824',
515
+ '510971',
516
+ '32764',
517
+ '1862',
518
+ '94',
519
+ '4',
520
+ '0',
521
+ ].map(BigInt);
510
522
 
511
523
  // Exact binary64 1/sigma payloads from round-3 fpr.h. Nearby decimal spellings round 1 ULP low in
512
524
  // JS, so keep these as decoded bit patterns and recheck the raw payloads after edits.
513
- const INV_SIGMA = [
525
+ const INV_SIGMA = /* @__PURE__ */ Object.freeze([
514
526
  0.0, // unused
515
527
  f64b(BigInt('4574611497772390042')),
516
528
  f64b(BigInt('4574501679055810265')),
@@ -522,12 +534,12 @@ const INV_SIGMA = [
522
534
  f64b(BigInt('4573721358406441454')),
523
535
  f64b(BigInt('4573606369665796042')),
524
536
  f64b(BigInt('4573496814039276259')),
525
- ];
537
+ ]);
526
538
 
527
539
  // Exact binary64 sigma_min constants from round-3 fpr.h indexed by logn; despite one PQClean
528
540
  // summary comment, these are sigma_min itself, not 1/sigma_min, which is why this table stays
529
541
  // separate from INV_SIGMA.
530
- const SIGMA_MIN = [
542
+ const SIGMA_MIN = /* @__PURE__ */ Object.freeze([
531
543
  0.0, // unused
532
544
  f64b(BigInt('4607707126469777035')),
533
545
  f64b(BigInt('4607777455861499430')),
@@ -539,7 +551,7 @@ const SIGMA_MIN = [
539
551
  f64b(BigInt('4608340089478362016')),
540
552
  f64b(BigInt('4608433670533905013')),
541
553
  f64b(BigInt('4608525754002622308')),
542
- ];
554
+ ]);
543
555
 
544
556
  // Falcon Table 3.1 RCDT values for chi, split into 24-bit limbs; storage is [high, mid, low],
545
557
  // so gaussian0() intentionally compares them against v0, v1, v2 in reverse order. The final
@@ -1018,19 +1030,19 @@ function getIntPoly(logn: number) {
1018
1030
  });
1019
1031
  // Keep Falcon source compatible with older TS parsers: avoid spelling newer
1020
1032
  // `Uint16Array<ArrayBuffer>` syntax directly and cast the callee side at the boundary.
1021
- const ntt = (r: IPoly): IPoly => (NTT.encode as any)(r);
1022
- const intt = (r: IPoly): IPoly => (NTT.decode as any)(r);
1033
+ const ntt = (r: TArg<IPoly>): TRet<IPoly> => (NTT.encode as any)(r);
1034
+ const intt = (r: TArg<IPoly>): TRet<IPoly> => (NTT.decode as any)(r);
1023
1035
  // Falcon integer helpers mutate their first argument in place; div() also performs intt()
1024
1036
  // before returning, so callers must treat these as owned-temporary transforms, not pure helpers.
1025
1037
  // Centered representatives are in [-6144, 6144] for odd q = 12289,
1026
1038
  // not a generic [-q/2, q/2] range.
1027
1039
  const signedCoder = {
1028
- encode: (p: IPoly) => Int16Array.from(p, (x) => smod(x)),
1029
- decode: (p: SPoly | Int16Array) => Uint16Array.from(p, (x) => mod(x)),
1040
+ encode: (p: TArg<IPoly>) => Int16Array.from(p, (x) => smod(x)),
1041
+ decode: (p: TArg<SPoly | Int16Array>) => Uint16Array.from(p, (x) => mod(x)),
1030
1042
  };
1031
1043
  const intPoly = {
1032
1044
  create: newPoly,
1033
- smallSqnorm(f: SPoly) {
1045
+ smallSqnorm(f: TArg<SPoly>) {
1034
1046
  let s = 0;
1035
1047
  let ng = 0;
1036
1048
  for (let u = 0; u < n; u++) {
@@ -1040,7 +1052,7 @@ function getIntPoly(logn: number) {
1040
1052
  }
1041
1053
  return (s | -(ng >>> 31)) >>> 0;
1042
1054
  },
1043
- isShort(s1: Int16Array, s2: Int16Array) {
1055
+ isShort(s1: TArg<Int16Array>, s2: TArg<Int16Array>) {
1044
1056
  let s = 0 >>> 0;
1045
1057
  let ng = 0 >>> 0;
1046
1058
  for (let u = 0; u < n; u++) {
@@ -1054,24 +1066,24 @@ function getIntPoly(logn: number) {
1054
1066
  if (ng & 0x80000000) s = 0xffffffff;
1055
1067
  return s <= L2BOUND[logn];
1056
1068
  },
1057
- sub(a: IPoly, b: IPoly): IPoly {
1069
+ sub(a: TArg<IPoly>, b: TArg<IPoly>): TRet<IPoly> {
1058
1070
  for (let i = 0; i < n; i++) a[i] = mod(a[i] - b[i]);
1059
- return a;
1071
+ return a as TRet<IPoly>;
1060
1072
  },
1061
1073
  ntt,
1062
1074
  intt,
1063
- toMontgomery(d: IPoly): IPoly {
1075
+ toMontgomery(d: TArg<IPoly>): TRet<IPoly> {
1064
1076
  for (let i = 0; i < n; i++) d[i] = intField.mul(d[i], R2);
1065
- return d;
1077
+ return d as TRet<IPoly>;
1066
1078
  },
1067
- mul(f: IPoly, d: IPoly): IPoly {
1079
+ mul(f: TArg<IPoly>, d: TArg<IPoly>): TRet<IPoly> {
1068
1080
  for (let i = 0; i < n; i++) f[i] = intField.mul(f[i], d[i]);
1069
- return f;
1081
+ return f as TRet<IPoly>;
1070
1082
  },
1071
- div(f: IPoly, d: IPoly): IPoly {
1083
+ div(f: TArg<IPoly>, d: TArg<IPoly>): TRet<IPoly> {
1072
1084
  for (let i = 0; i < n; i++) f[i] = intField.div(f[i], d[i]);
1073
1085
  this.intt(f);
1074
- return f;
1086
+ return f as TRet<IPoly>;
1075
1087
  },
1076
1088
  };
1077
1089
  return { newPoly, intPoly, signedCoder };
@@ -1127,19 +1139,19 @@ function getFloatPoly(logn: number) {
1127
1139
  const fftOpts = { N: N_COMPLEX, invertButterflies: true, skipStages: 0, brp: false };
1128
1140
  const inv = 1.0 / N_COMPLEX;
1129
1141
  return {
1130
- to: (f: FPoly) => ComplexArr.decode(Array.from(f)),
1131
- from: (f: CPoly): FPoly => new Float64Array(ComplexArr.encode(f)),
1142
+ to: (f: TArg<FPoly>) => ComplexArr.decode(Array.from(f)),
1143
+ from: (f: CPoly): TRet<FPoly> => new Float64Array(ComplexArr.encode(f)) as TRet<FPoly>,
1132
1144
  // Runtime callers also pass HashToPoint's Uint16Array output here;
1133
1145
  // the implementation only needs a numeric typed-array shape,
1134
1146
  // even though the local type is narrower.
1135
- convSmall: (f: SPoly): CPoly => ComplexArr.decode(Array.from(f)),
1147
+ convSmall: (f: TArg<SPoly>): CPoly => ComplexArr.decode(Array.from(f)),
1136
1148
  add: (a: CPoly, b: CPoly): CPoly => a.map((i, j) => fComplex.add(i, b[j])),
1137
1149
  sub: (a: CPoly, b: CPoly): CPoly => a.map((i, j) => fComplex.sub(i, b[j])),
1138
1150
  neg: (a: CPoly): CPoly => a.map((i) => fComplex.neg(i)),
1139
1151
  mul: (a: CPoly, b: CPoly): CPoly => a.map((i, j) => fComplex.mul(i, b[j])),
1140
1152
  conj: (a: CPoly): CPoly => a.map((i) => fComplex.conj(i)),
1141
1153
  mulConst: (a: CPoly, x: number): CPoly => a.map((i) => fComplex.scale(i, x)),
1142
- scaleNorm: (a: CPoly, b: FPoly): CPoly => a.map((i, j) => fComplex.scale(i, b[j])),
1154
+ scaleNorm: (a: CPoly, b: TArg<FPoly>): CPoly => a.map((i, j) => fComplex.scale(i, b[j])),
1143
1155
  invNorm: (a: CPoly, b: CPoly) =>
1144
1156
  new Float64Array(a.map((i, j) => 1.0 / fComplex.magSqSum(i, b[j]))),
1145
1157
  FFT: (f: CPoly): CPoly =>
@@ -1199,7 +1211,8 @@ type FalconOpts = {
1199
1211
  maxS2Len: number;
1200
1212
  };
1201
1213
 
1202
- type FalconSigOpts = SigOpts & { random?: typeof randomBytes };
1214
+ type FalconRandom = (bytesLength?: number) => TRet<Uint8Array>;
1215
+ type FalconSigOpts = SigOpts & { random?: FalconRandom };
1203
1216
  /** Falcon attached-signature API. */
1204
1217
  export type FalconAttached = CryptoKeys & {
1205
1218
  /** Key lengths plus the 48-byte sampler-seed hook for signing. */
@@ -1227,7 +1240,7 @@ export type Falcon = Signer & {
1227
1240
  attached: FalconAttached;
1228
1241
  };
1229
1242
 
1230
- function genFalcon(opts: FalconOpts): Falcon {
1243
+ function genFalcon(opts: FalconOpts): TRet<Falcon> {
1231
1244
  const { N } = opts;
1232
1245
  const logn = Math.log2(N);
1233
1246
  const id = <T>(n: T): T => n;
@@ -1246,9 +1259,9 @@ function genFalcon(opts: FalconOpts): Falcon {
1246
1259
  let val = 0;
1247
1260
  for (let i = 0; i < g; i++) {
1248
1261
  const r128 = bytesToNumberLE(this.shake.xof(16));
1249
- const r1 = r128 & 0x7fffffffffffffffn;
1250
- const r2 = (r128 >> 64n) & 0x7fffffffffffffffn;
1251
- const sign = Number((r128 >> 63n) & 1n);
1262
+ const r1 = r128 & MASK_63n;
1263
+ const r2 = (r128 >> _64n) & MASK_63n;
1264
+ const sign = Number((r128 >> _63n) & _1n);
1252
1265
  let f = r1 < gauss_1024_12289[0] ? 1 : 0;
1253
1266
  let v = 0;
1254
1267
  for (let k = 1; k < gauss_1024_12289.length; k++) {
@@ -1281,13 +1294,13 @@ function genFalcon(opts: FalconOpts): Falcon {
1281
1294
  const n = 1 << logn;
1282
1295
  const d = new Array(n >> 1);
1283
1296
  for (let k = 0; k < n; k += 2) {
1284
- let s: bigint = 0n;
1297
+ let s: bigint = _0n;
1285
1298
  for (let i = 0; i <= k; i += 2) s += a[i] * a[k - i];
1286
1299
  for (let i = k + 2; i < n; i += 2) s -= a[i] * a[k + n - i];
1287
1300
  d[k >>> 1] = s;
1288
1301
  }
1289
1302
  for (let k = 0; k < n; k += 2) {
1290
- let s: bigint = 0n;
1303
+ let s: bigint = _0n;
1291
1304
  for (let i = 1; i < k; i += 2) s += a[i] * a[k - i];
1292
1305
  for (let i = k + 1; i < n; i += 2) s -= a[i] * a[k + n - i];
1293
1306
  d[k >>> 1] -= s;
@@ -1297,7 +1310,7 @@ function genFalcon(opts: FalconOpts): Falcon {
1297
1310
  private mulConjD(logn: number, d: BPoly, a: BPoly, b: BPoly): BPoly {
1298
1311
  const n = 1 << logn;
1299
1312
  for (let k = 0; k < n; k++) {
1300
- let s: bigint = 0n;
1313
+ let s: bigint = _0n;
1301
1314
  for (let i = 0; i <= k; i += 2) s += b[i >>> 1] * a[k - i];
1302
1315
  for (let i = k + 2 - (k & 1); i < n; i += 2) s -= b[i >>> 1] * a[k + n - i];
1303
1316
  if ((k & 1) === 0) d[k] = s;
@@ -1308,7 +1321,7 @@ function genFalcon(opts: FalconOpts): Falcon {
1308
1321
  private subMul(logn: number, a: BPoly, b: BPoly, c: BPoly, e: bigint): BPoly {
1309
1322
  const n = 1 << logn;
1310
1323
  for (let k = 0; k < n; k++) {
1311
- let s: bigint = 0n;
1324
+ let s: bigint = _0n;
1312
1325
  for (let i = 0; i <= k; i++) s += b[i] * c[k - i];
1313
1326
  for (let i = k + 1; i < n; i++) s -= b[i] * c[k + n - i];
1314
1327
  a[k] -= s << e;
@@ -1351,7 +1364,7 @@ function genFalcon(opts: FalconOpts): Falcon {
1351
1364
  const Gx = new Float64Array(n);
1352
1365
  const k = new Array(n);
1353
1366
  while (true) {
1354
- let scaleFG = 31n * (FGlen - 10n);
1367
+ let scaleFG = _31n * (FGlen - _10n);
1355
1368
  for (let i = 0; i < n; i++) {
1356
1369
  Fx[i] = Number(F[i] >> scaleFG);
1357
1370
  Gx[i] = Number(G[i] >> scaleFG);
@@ -1371,12 +1384,12 @@ function genFalcon(opts: FalconOpts): Falcon {
1371
1384
  }
1372
1385
  F = this.subMul(logn, F, f, k, scaleK); // 3: F ← F - kf
1373
1386
  G = this.subMul(logn, G, g, k, scaleK); // 4: G ← G - kg
1374
- const maxfgNew = scaleK + BigInt(Math.round(fgMaxBits)) + 10n;
1387
+ const maxfgNew = scaleK + BigInt(Math.round(fgMaxBits)) + _10n;
1375
1388
  if (maxfgNew < maxFGBits) maxFGBits = maxfgNew;
1376
- if (FGlen > 1n && FGlen * 31n >= maxFGBits + 31n) FGlen--;
1377
- if (scaleK <= 0n) break;
1378
- scaleK -= 25n;
1379
- if (scaleK < 0n) scaleK = 0n;
1389
+ if (FGlen > _1n && FGlen * _31n >= maxFGBits + _31n) FGlen--;
1390
+ if (scaleK <= _0n) break;
1391
+ scaleK -= _25n;
1392
+ if (scaleK < _0n) scaleK = _0n;
1380
1393
  }
1381
1394
  return true;
1382
1395
  }
@@ -1404,10 +1417,10 @@ function genFalcon(opts: FalconOpts): Falcon {
1404
1417
  const xf = f[0];
1405
1418
  const xg = g[0];
1406
1419
  // We can rely on 'invert' to throw if they are not coprime.
1407
- if (xf <= 0n || xg <= 0n) return false;
1420
+ if (xf <= _0n || xg <= _0n) return false;
1408
1421
  try {
1409
1422
  const u1 = invert(xf, xg); // if gcd(f, g) ≠ 1 then
1410
- const v1 = (1n - u1 * xf) / xg;
1423
+ const v1 = (_1n - u1 * xf) / xg;
1411
1424
  F[0] = -v1 * QBig; // 5: (F, G) ← (vq, uq)
1412
1425
  G[0] = u1 * QBig;
1413
1426
  return true;
@@ -1544,14 +1557,14 @@ function genFalcon(opts: FalconOpts): Falcon {
1544
1557
  }) as BytesCoderLen<IPoly>;
1545
1558
  return {
1546
1559
  bytesLen: coder.bytesLen,
1547
- encode(poly: Uint16Array) {
1560
+ encode(poly: TArg<Uint16Array>) {
1548
1561
  // Keep these raw checks in sync with Q:
1549
1562
  // Falcon public-key coefficients must stay in [0, q - 1].
1550
1563
  for (let i = 0; i < poly.length; i++)
1551
1564
  if (poly[i] >= 12289) throw new Error('public key coeff out of range');
1552
1565
  return coder.encode(poly);
1553
1566
  },
1554
- decode(bytes: Uint8Array) {
1567
+ decode(bytes: TArg<Uint8Array>) {
1555
1568
  // Round-3 Falcon requires exact body length here;
1556
1569
  // otherwise truncated keys decode as zero-padded
1557
1570
  // and overlong keys silently ignore the tail in this generic bit decoder.
@@ -1577,7 +1590,7 @@ function genFalcon(opts: FalconOpts): Falcon {
1577
1590
  }) as BytesCoderLen<SPoly>;
1578
1591
  return {
1579
1592
  bytesLen: coder.bytesLen,
1580
- encode(poly: Int8Array) {
1593
+ encode(poly: TArg<Int8Array>) {
1581
1594
  // Secret-key trim encodings keep a symmetric signed range and reserve the most-negative
1582
1595
  // value as a non-canonical sentinel,
1583
1596
  // so encode() and decode() intentionally use different bounds.
@@ -1587,7 +1600,7 @@ function genFalcon(opts: FalconOpts): Falcon {
1587
1600
  if (poly[i] < min || poly[i] > max) throw new Error('private key coeff out of range');
1588
1601
  return coder.encode(poly);
1589
1602
  },
1590
- decode(bytes: Uint8Array) {
1603
+ decode(bytes: TArg<Uint8Array>) {
1591
1604
  const poly = coder.decode(bytes);
1592
1605
  const min = -(1 << (bits - 1));
1593
1606
  for (let i = 0; i < poly.length; i++)
@@ -1606,7 +1619,7 @@ function genFalcon(opts: FalconOpts): Falcon {
1606
1619
  splitCoder('falcon.secretKey', fgCoder, fgCoder, FGCoder)
1607
1620
  ) as BytesCoderLen<[Int8Array, Int8Array, Int8Array]>;
1608
1621
  const publicKeyCoder = headerCoder(0x00 + logn, modqCoder()) as BytesCoderLen<Uint16Array>;
1609
- const decodePaddedSig = (s2: Uint8Array) => {
1622
+ const decodePaddedSig = (s2: TArg<Uint8Array>) => {
1610
1623
  // The fixed padded form accepts only a canonical compressed payload
1611
1624
  // followed by an all-zero tail.
1612
1625
  const normalized = compCoder(N).encode(compCoder(N).decode(s2));
@@ -1614,7 +1627,7 @@ function genFalcon(opts: FalconOpts): Falcon {
1614
1627
  if (s2[i] !== 0) throw new Error('non-zero padding');
1615
1628
  return normalized;
1616
1629
  };
1617
- const decodeUnpaddedSig = (s2: Uint8Array) => {
1630
+ const decodeUnpaddedSig = (s2: TArg<Uint8Array>) => {
1618
1631
  // Unpadded attached and detached signatures require the compressed payload to use its exact
1619
1632
  // canonical bitlength. Appending a zero tail and adjusting the outer container length must
1620
1633
  // still be rejected.
@@ -1628,7 +1641,7 @@ function genFalcon(opts: FalconOpts): Falcon {
1628
1641
  const SignatureCoderBasic = (logn: number) => {
1629
1642
  const TYPE_BYTE = 0x20 + logn;
1630
1643
  return {
1631
- encode({ msg, nonce, s2 }: SignatureRaw): Uint8Array {
1644
+ encode({ msg, nonce, s2 }: TArg<SignatureRaw>): TRet<Uint8Array> {
1632
1645
  let compressed: Uint8Array = s2;
1633
1646
  const payloadLen = 1 + compressed.length;
1634
1647
  const totalLen = 2 + NONCELEN + msg.length + payloadLen;
@@ -1642,9 +1655,9 @@ function genFalcon(opts: FalconOpts): Falcon {
1642
1655
  i += msg.length;
1643
1656
  out[i++] = TYPE_BYTE;
1644
1657
  out.set(compressed, i);
1645
- return out;
1658
+ return out as TRet<Uint8Array>;
1646
1659
  },
1647
- decode(data: Uint8Array): SignatureRaw {
1660
+ decode(data: TArg<Uint8Array>): TRet<SignatureRaw> {
1648
1661
  if (!data || data.length < NONCELEN + 3) throw new Error('signature coder: wrong length');
1649
1662
  const len = (data[0] << 8) | data[1];
1650
1663
  const s2Len = len - 1;
@@ -1656,50 +1669,62 @@ function genFalcon(opts: FalconOpts): Falcon {
1656
1669
  const msg = data.subarray(2 + NONCELEN, 2 + NONCELEN + msgLen);
1657
1670
  const s2 = decodeUnpaddedSig(data.subarray(2 + NONCELEN + msgLen + 1));
1658
1671
  if (s2.length !== s2Len) throw new Error('signature coder: wrong s2 length');
1659
- return { msg, nonce, s2 };
1672
+ return { msg, nonce, s2 } as TRet<SignatureRaw>;
1660
1673
  },
1661
1674
  };
1662
1675
  };
1663
1676
  const SignatureCoderPadded = (logn: number) => {
1664
1677
  const sigLen = opts.paddedLen;
1665
1678
  return {
1666
- encode({ msg, nonce, s2 }: SignatureRaw): Uint8Array {
1679
+ encode({ msg, nonce, s2 }: TArg<SignatureRaw>): TRet<Uint8Array> {
1667
1680
  return headerCoder(
1668
1681
  0x30 + logn,
1669
1682
  splitCoder('falcon.signature', NONCELEN, sigLen, msg.length)
1670
1683
  ).encode([nonce, pad(sigLen).encode(s2), msg]);
1671
1684
  },
1672
- decode(data: Uint8Array): SignatureRaw {
1685
+ decode(data: TArg<Uint8Array>): TRet<SignatureRaw> {
1686
+ // Keep API misuse on the coder's TypeError path before reading the container length.
1687
+ abytes(data, undefined, 'signature');
1688
+ // The compressed-signature field is fixed-width here; only the message is variable. A
1689
+ // container shorter than the fixed part would make the s2 field borrow bytes from nowhere
1690
+ // and let a truncated encoding open to the same message.
1673
1691
  const msgLen = data.length - NONCELEN - sigLen - 1;
1692
+ if (msgLen < 0) throw new Error('signature coder: wrong length');
1674
1693
  const [nonce, s2, msg] = headerCoder(
1675
1694
  0x30 + logn,
1676
1695
  splitCoder('falcon.signature', NONCELEN, sigLen, msgLen)
1677
1696
  ).decode(data);
1678
- return { nonce, s2: decodeSig(s2), msg };
1697
+ return { nonce, s2: decodeSig(s2), msg } as TRet<SignatureRaw>;
1679
1698
  },
1680
1699
  };
1681
1700
  };
1682
1701
  // [ 1B header ] [ 40B nonce ] [ compressed_sig ]
1683
1702
  const SignatureCoderDetached = (logn: number) => {
1684
1703
  const sigLen = opts.padded ? opts.sigLen - 1 - NONCELEN : opts.detachedLen;
1685
- const getSigLen = (s2: Uint8Array) => (opts.padded ? sigLen : s2.length);
1704
+ const getSigLen = (s2: TArg<Uint8Array>) => (opts.padded ? sigLen : s2.length);
1686
1705
  return {
1687
- encode({ nonce, s2 }: { nonce: Uint8Array; s2: Uint8Array }): Uint8Array {
1706
+ encode({ nonce, s2 }: TArg<{ nonce: Uint8Array; s2: Uint8Array }>): TRet<Uint8Array> {
1688
1707
  return headerCoder(
1689
1708
  0x30 + logn,
1690
1709
  splitCoder('falcon.detachedSignature', NONCELEN, getSigLen(s2))
1691
1710
  ).encode([nonce, opts.padded ? pad(sigLen).encode(s2) : s2]);
1692
1711
  },
1693
- decode(data: Uint8Array): {
1712
+ decode(data: TArg<Uint8Array>): TRet<{
1694
1713
  nonce: Uint8Array;
1695
1714
  s2: Uint8Array;
1696
- } {
1715
+ }> {
1716
+ // Padded detached signatures are fixed-length (`lengths.signature`), so the payload width
1717
+ // must come from the parameter set, not from the input: deriving it would accept appended
1718
+ // zero bytes and truncated padding as extra valid encodings of the same signature.
1719
+ // Unpadded signatures are variable-length; decodeUnpaddedSig() enforces the exact canonical
1720
+ // bitlength of whatever remains.
1721
+ const payloadLen = opts.padded ? sigLen : data.length - NONCELEN - 1;
1697
1722
  const [nonce, raw] = headerCoder(
1698
1723
  0x30 + logn,
1699
- splitCoder('falcon.detachedSignature', NONCELEN, data.length - NONCELEN - 1)
1724
+ splitCoder('falcon.detachedSignature', NONCELEN, payloadLen)
1700
1725
  ).decode(data);
1701
1726
  const s2 = decodeSig(raw);
1702
- return { nonce, s2 };
1727
+ return { nonce, s2 } as TRet<{ nonce: Uint8Array; s2: Uint8Array }>;
1703
1728
  },
1704
1729
  };
1705
1730
  };
@@ -1708,13 +1733,13 @@ function genFalcon(opts: FalconOpts): Falcon {
1708
1733
  // otherwise malformed secret keys leak a raw arithmetic error.
1709
1734
  // Returns NTT(f) after the nonzero-lane check;
1710
1735
  // callers still apply f^{-1} via coefficient-wise division.
1711
- const invertF = (f: SPoly) => {
1736
+ const invertF = (f: TArg<SPoly>) => {
1712
1737
  const tt = intPoly.ntt(signedCoder.decode(f));
1713
1738
  for (let u = 0; u < N; u++)
1714
1739
  if (tt[u] === 0) throw new Error('invalid secretKey: non-invertible f');
1715
1740
  return tt;
1716
1741
  };
1717
- function computePublic(f: SPoly, g: SPoly) {
1742
+ function computePublic(f: TArg<SPoly>, g: TArg<SPoly>) {
1718
1743
  const tt = invertF(f);
1719
1744
  const h = intPoly.ntt(signedCoder.decode(g));
1720
1745
  // intPoly.div() returns to coefficient form via intt(), so public keys are encoded from the
@@ -1725,7 +1750,7 @@ function genFalcon(opts: FalconOpts): Falcon {
1725
1750
  }
1726
1751
  // Reconstruct the omitted secret-key limb G as g*F/f mod q, then mirror round-3 Falcon's centered
1727
1752
  // reduction and small-coefficient check before using the completed basis for signing.
1728
- function completePrivate(f: SPoly, g: SPoly, F: SPoly) {
1753
+ function completePrivate(f: TArg<SPoly>, g: TArg<SPoly>, F: TArg<SPoly>) {
1729
1754
  let t1 = intPoly.toMontgomery(intPoly.ntt(signedCoder.decode(g)));
1730
1755
  const t2 = intPoly.ntt(signedCoder.decode(F));
1731
1756
  const tt = invertF(f);
@@ -1747,7 +1772,7 @@ function genFalcon(opts: FalconOpts): Falcon {
1747
1772
  cleanBytes(t1, t2, tt);
1748
1773
  return G;
1749
1774
  }
1750
- function HashToPoint(nonce: Uint8Array, msg: Uint8Array) {
1775
+ function HashToPoint(nonce: TArg<Uint8Array>, msg: TArg<Uint8Array>): TRet<IPoly> {
1751
1776
  // Algorithm 3: HashToPoint(str, q, n)
1752
1777
  // (Page 31)
1753
1778
  // Require: A string str, a modulus q ≤ 2¹⁶, a degree n ∈ N*
@@ -1772,7 +1797,7 @@ function genFalcon(opts: FalconOpts): Falcon {
1772
1797
  let w = (tmp[0] << 8) | tmp[1];
1773
1798
  if (w < kQ) c[i++] = w % Q; // 8: cᵢ ← t mod q
1774
1799
  }
1775
- return c;
1800
+ return c as TRet<IPoly>;
1776
1801
  }
1777
1802
  // This is basically one sampling routine,
1778
1803
  // but it carries a lot of internal state and gets complex quickly.
@@ -1783,7 +1808,7 @@ function genFalcon(opts: FalconOpts): Falcon {
1783
1808
  private shakeBuf: Uint8Array;
1784
1809
  private ctrView: DataView;
1785
1810
  // ChaCha
1786
- private ctr: bigint = 0n;
1811
+ private ctr: bigint = _0n;
1787
1812
  private buf: Uint8Array;
1788
1813
  private buf32: Uint32Array;
1789
1814
  private pos: number;
@@ -1833,8 +1858,8 @@ function genFalcon(opts: FalconOpts): Falcon {
1833
1858
  const out32 = swap32IfBE(this.buf32);
1834
1859
  for (let i = 0; i < 8; i++, this.ctr++) {
1835
1860
  const n = swap32IfBE(this.nonce32.slice()); // [n0, n1, n2, n3]
1836
- n[2] ^= Number(this.ctr & 0xffffffffn);
1837
- n[3] ^= Number(this.ctr >> 32n);
1861
+ n[2] ^= Number(this.ctr & MASK_32n);
1862
+ n[3] ^= Number(this.ctr >> _32n);
1838
1863
  // chacha20() takes raw nonce bytes; on BE the word-normalized temp must be swapped back.
1839
1864
  swap32IfBE(n.subarray(1));
1840
1865
  chacha20(this.key, u8(n.subarray(1)), EMPTY_CHACHA20_BLOCK, this.curBlock, n[0]);
@@ -2154,7 +2179,12 @@ function genFalcon(opts: FalconOpts): Falcon {
2154
2179
  }
2155
2180
  }
2156
2181
 
2157
- const signRaw = (sk: Uint8Array, msg: Uint8Array, maxLen: number, rnd = randomBytes) => {
2182
+ const signRaw = (
2183
+ sk: TArg<Uint8Array>,
2184
+ msg: TArg<Uint8Array>,
2185
+ maxLen: number,
2186
+ rnd: TArg<FalconRandom> = randomBytes
2187
+ ): TRet<SignatureRaw> => {
2158
2188
  // Algorithm 10: Sign(m, sk, [β²]), (Page 39)
2159
2189
  // Require: A message m, a secret key sk, a bound [β²]
2160
2190
  // Ensure: A signature sig of m
@@ -2174,7 +2204,8 @@ function genFalcon(opts: FalconOpts): Falcon {
2174
2204
  // ▷ Remove 1 byte for the header, and 40 bytes for r
2175
2205
  // 11: while (s = ⊥)
2176
2206
  // 12: return sig = (r, s)
2177
- abytes(msg);
2207
+ abytes(msg, undefined, 'msg');
2208
+ abytes(sk, secretKeyCoder.bytesLen, 'secretKey');
2178
2209
  // One RNG stream drives both the public 40-byte nonce and the 48-byte sampler seed, so
2179
2210
  // deterministic rnd hooks make signatures deterministic for fixed secretKey/message inputs.
2180
2211
  const nonce = rnd(40);
@@ -2226,7 +2257,7 @@ function genFalcon(opts: FalconOpts): Falcon {
2226
2257
  cleanBytes(s2comp);
2227
2258
  continue;
2228
2259
  }
2229
- return { s2: s2comp, nonce, msg };
2260
+ return { s2: s2comp, nonce, msg } as TRet<SignatureRaw>;
2230
2261
  }
2231
2262
  } finally {
2232
2263
  cleanBytes(s2);
@@ -2244,7 +2275,12 @@ function genFalcon(opts: FalconOpts): Falcon {
2244
2275
 
2245
2276
  // Raw helper: malformed encodings or wrong lengths still throw here; the public verify()/open()
2246
2277
  // wrappers decide whether to translate those failures into false or an exception.
2247
- const verifyRaw = (pk: Uint8Array, s2comp: Uint8Array, nonce: Uint8Array, msg: Uint8Array) => {
2278
+ const verifyRaw = (
2279
+ pk: TArg<Uint8Array>,
2280
+ s2comp: TArg<Uint8Array>,
2281
+ nonce: TArg<Uint8Array>,
2282
+ msg: TArg<Uint8Array>
2283
+ ) => {
2248
2284
  // Algorithm 16: Verify(m, sig, pk, [β²])
2249
2285
  // (Page 45)
2250
2286
  // Require: A message m, a signature sig = (r, s), a public key pk = h ∈ Zq[x]/(φ), a bound [β²]
@@ -2266,34 +2302,44 @@ function genFalcon(opts: FalconOpts): Falcon {
2266
2302
  return intPoly.isShort(signedCoder.encode(s1), s2); // 6: if ||(s₁, s₂)||² < [β²] then
2267
2303
  };
2268
2304
 
2269
- const info = { type: 'falcon' };
2270
- const keyLengths = {
2305
+ const info = Object.freeze({ type: 'falcon' });
2306
+ const keyLengths = Object.freeze({
2271
2307
  seed: 48,
2272
2308
  publicKey: publicKeyCoder.bytesLen,
2273
2309
  secretKey: secretKeyCoder.bytesLen,
2274
- };
2310
+ });
2275
2311
  // Noble exposes a 48-byte sampler-seed hook,
2276
2312
  // but Falcon still samples/encodes a separate 40-byte nonce per signature.
2277
- const getRnd = (opts: FalconSigOpts = {}) => {
2313
+ const getRnd = (opts: TArg<FalconSigOpts> = {}): TRet<FalconRandom> => {
2278
2314
  validateSigOpts(opts);
2279
2315
  if (opts.context !== undefined) throw new Error('context is not supported');
2280
- if (opts.random !== undefined) return opts.random;
2316
+ if (opts.random !== undefined && typeof opts.random !== 'function')
2317
+ throw new TypeError('"opts.random" expected function, got type=' + typeof opts.random);
2318
+ if (opts.random !== undefined) return opts.random as TRet<FalconRandom>;
2281
2319
  if (opts.extraEntropy === undefined) return randomBytes;
2282
2320
  const seed = opts.extraEntropy === false ? new Uint8Array(48) : opts.extraEntropy;
2283
2321
  abytes(seed, 48, 'opts.extraEntropy');
2284
2322
  const drbg = rngAesCtrDrbg256(seed);
2285
- return (len = 0) => drbg.randomBytes(len);
2323
+ return (len = 0) => drbg.randomBytes(len) as TRet<Uint8Array>;
2286
2324
  };
2287
- const checkVerOpts = (opts: VerOpts = {}) => {
2325
+ const checkVerOpts = (opts: TArg<VerOpts> = {}) => {
2288
2326
  validateVerOpts(opts);
2289
2327
  if (opts.context !== undefined) throw new Error('context is not supported');
2290
2328
  };
2291
- const tests = { publicKeyCoder, privateKeyCoder: secretKeyCoder, maxS2Len: opts.maxS2Len };
2329
+ const tests = Object.freeze({
2330
+ publicKeyCoder: Object.freeze(publicKeyCoder),
2331
+ privateKeyCoder: Object.freeze(secretKeyCoder),
2332
+ maxS2Len: opts.maxS2Len,
2333
+ });
2292
2334
  // `signRand` documents only the sampler-seed input length;
2293
2335
  // detached/attached signatures still include their own 40-byte nonce.
2294
- const attachedLengths = { ...keyLengths, signRand: 48 };
2295
- const lengths = opts.padded ? { ...attachedLengths, signature: opts.sigLen } : attachedLengths;
2296
- const keygen = (seed?: Uint8Array) => {
2336
+ const attachedLengths = Object.freeze({ ...keyLengths, signRand: 48 });
2337
+ const lengths = opts.padded
2338
+ ? Object.freeze({ ...attachedLengths, signature: opts.sigLen })
2339
+ : attachedLengths;
2340
+ const keygen = (
2341
+ seed?: TArg<Uint8Array>
2342
+ ): TRet<{ publicKey: Uint8Array; secretKey: Uint8Array }> => {
2297
2343
  const randSeed = seed === undefined;
2298
2344
  if (randSeed) seed = randomBytes(48);
2299
2345
  abytes(seed!, 48, 'seed');
@@ -2302,20 +2348,28 @@ function genFalcon(opts: FalconOpts): Falcon {
2302
2348
  const pk = publicKeyCoder.encode(pub);
2303
2349
  if (randSeed) cleanBytes(seed!);
2304
2350
  cleanBytes(f, g, F, _G);
2305
- return { publicKey: pk, secretKey: sk };
2351
+ return { publicKey: pk, secretKey: sk } as TRet<{
2352
+ publicKey: Uint8Array;
2353
+ secretKey: Uint8Array;
2354
+ }>;
2306
2355
  };
2307
- const getPublicKey = (sk: Uint8Array) => {
2356
+ const getPublicKey = (sk: TArg<Uint8Array>): TRet<Uint8Array> => {
2357
+ abytes(sk, secretKeyCoder.bytesLen, 'secretKey');
2308
2358
  const [f, g, F] = secretKeyCoder.decode(sk);
2309
2359
  try {
2310
2360
  const h = computePublic(f, g);
2311
2361
  cleanBytes(f, g, F);
2312
- return publicKeyCoder.encode(h);
2362
+ return publicKeyCoder.encode(h) as TRet<Uint8Array>;
2313
2363
  } catch (e) {
2314
2364
  cleanBytes(f, g, F);
2315
2365
  throw e;
2316
2366
  }
2317
2367
  };
2318
- const sign = (msg: Uint8Array, sk: Uint8Array, sigOpts: FalconSigOpts = {}) => {
2368
+ const sign = (
2369
+ msg: TArg<Uint8Array>,
2370
+ sk: TArg<Uint8Array>,
2371
+ sigOpts: TArg<FalconSigOpts> = {}
2372
+ ): TRet<Uint8Array> => {
2319
2373
  const { s2, nonce } = signRaw(sk, msg, opts.maxS2Len, getRnd(sigOpts));
2320
2374
  return SignatureCoderDetached(logn).encode({ nonce, s2 });
2321
2375
  };
@@ -2324,11 +2378,17 @@ function genFalcon(opts: FalconOpts): Falcon {
2324
2378
  * and well-formed signatures that do not validate. Throws on malformed API argument types or
2325
2379
  * unsupported verification options.
2326
2380
  */
2327
- const verify = (sig: Uint8Array, msg: Uint8Array, pk: Uint8Array, verOpts: VerOpts = {}) => {
2381
+ const verify = (
2382
+ sig: TArg<Uint8Array>,
2383
+ msg: TArg<Uint8Array>,
2384
+ pk: TArg<Uint8Array>,
2385
+ verOpts: TArg<VerOpts> = {}
2386
+ ) => {
2328
2387
  checkVerOpts(verOpts);
2329
- abytes(sig);
2330
- abytes(msg);
2331
- abytes(pk);
2388
+ abytes(sig, undefined, 'signature');
2389
+ abytes(msg, undefined, 'msg');
2390
+ // Length/canonical public-key failures are decoded below and return false; only type is fatal.
2391
+ abytes(pk, undefined, 'publicKey');
2332
2392
  try {
2333
2393
  const { s2, nonce } = SignatureCoderDetached(logn).decode(sig);
2334
2394
  return verifyRaw(pk, s2, nonce, msg);
@@ -2336,16 +2396,16 @@ function genFalcon(opts: FalconOpts): Falcon {
2336
2396
  return false;
2337
2397
  }
2338
2398
  };
2339
- const attached: FalconAttached = {
2399
+ const attached: TRet<FalconAttached> = Object.freeze({
2340
2400
  info,
2341
2401
  lengths: attachedLengths,
2342
2402
  keygen,
2343
2403
  getPublicKey,
2344
- seal(msg: Uint8Array, sk: Uint8Array, sigOpts: FalconSigOpts = {}) {
2404
+ seal(msg: TArg<Uint8Array>, sk: TArg<Uint8Array>, sigOpts: TArg<FalconSigOpts> = {}) {
2345
2405
  const { s2, nonce } = signRaw(sk, msg, opts.maxS2Len, getRnd(sigOpts));
2346
2406
  return SignatureCoder.encode({ msg, nonce, s2 });
2347
2407
  },
2348
- open(sig: Uint8Array, pk: Uint8Array, verOpts: VerOpts = {}) {
2408
+ open(sig: TArg<Uint8Array>, pk: TArg<Uint8Array>, verOpts: TArg<VerOpts> = {}) {
2349
2409
  checkVerOpts(verOpts);
2350
2410
  const { s2, nonce, msg } = SignatureCoder.decode(sig);
2351
2411
  // Zero-copy API: returned message aliases the caller-provided signature buffer.
@@ -2353,7 +2413,7 @@ function genFalcon(opts: FalconOpts): Falcon {
2353
2413
  if (verifyRaw(pk, s2, nonce, msg)) return msg;
2354
2414
  throw new Error('invalid signature');
2355
2415
  },
2356
- };
2416
+ });
2357
2417
  const res = {
2358
2418
  info,
2359
2419
  lengths,
@@ -2364,7 +2424,7 @@ function genFalcon(opts: FalconOpts): Falcon {
2364
2424
  verify,
2365
2425
  };
2366
2426
  (res as any).__test = tests;
2367
- return res;
2427
+ return Object.freeze(res);
2368
2428
  }
2369
2429
 
2370
2430
  const falcon512opts = {
@@ -2389,7 +2449,7 @@ const falcon512opts = {
2389
2449
  * falcon512.verify(sig, msg, publicKey);
2390
2450
  * ```
2391
2451
  */
2392
- export const falcon512: Falcon = /* @__PURE__ */ (() =>
2452
+ export const falcon512: TRet<Falcon> = /* @__PURE__ */ (() =>
2393
2453
  genFalcon({ ...falcon512opts, maxS2Len: 711 }))();
2394
2454
  /**
2395
2455
  * Falcon-512 padded detached-signature API with the attached helper exposed as `.attached`.
@@ -2402,7 +2462,7 @@ export const falcon512: Falcon = /* @__PURE__ */ (() =>
2402
2462
  * falcon512padded.verify(sig, msg, publicKey);
2403
2463
  * ```
2404
2464
  */
2405
- export const falcon512padded: Falcon = /* @__PURE__ */ (() =>
2465
+ export const falcon512padded: TRet<Falcon> = /* @__PURE__ */ (() =>
2406
2466
  genFalcon({
2407
2467
  ...falcon512opts,
2408
2468
  padded: true,
@@ -2431,7 +2491,7 @@ const falcon1024opts = {
2431
2491
  * falcon1024.verify(sig, msg, publicKey);
2432
2492
  * ```
2433
2493
  */
2434
- export const falcon1024: Falcon = /* @__PURE__ */ (() =>
2494
+ export const falcon1024: TRet<Falcon> = /* @__PURE__ */ (() =>
2435
2495
  genFalcon({
2436
2496
  ...falcon1024opts,
2437
2497
  maxS2Len: 1421,
@@ -2447,7 +2507,7 @@ export const falcon1024: Falcon = /* @__PURE__ */ (() =>
2447
2507
  * falcon1024padded.verify(sig, msg, publicKey);
2448
2508
  * ```
2449
2509
  */
2450
- export const falcon1024padded: Falcon = /* @__PURE__ */ (() =>
2510
+ export const falcon1024padded: TRet<Falcon> = /* @__PURE__ */ (() =>
2451
2511
  genFalcon({
2452
2512
  ...falcon1024opts,
2453
2513
  padded: true,
@@ -2455,16 +2515,17 @@ export const falcon1024padded: Falcon = /* @__PURE__ */ (() =>
2455
2515
  }))();
2456
2516
 
2457
2517
  // NOTE: for tests only, don't use
2458
- export const __tests: any = /* @__PURE__ */ (() => ({
2459
- BNORM_MAX,
2460
- COMPLEX_ROOTS,
2461
- Float,
2462
- INV_SIGMA,
2463
- SIGMA_MIN,
2464
- getFloatPoly,
2465
- cleanCPoly,
2466
- falcon512: (falcon512 as any).__test,
2467
- falcon512padded: (falcon512padded as any).__test,
2468
- falcon1024: (falcon1024 as any).__test,
2469
- falcon1024padded: (falcon1024padded as any).__test,
2470
- }))();
2518
+ export const __tests: any = /* @__PURE__ */ (() =>
2519
+ Object.freeze({
2520
+ BNORM_MAX,
2521
+ COMPLEX_ROOTS,
2522
+ Float,
2523
+ INV_SIGMA,
2524
+ SIGMA_MIN,
2525
+ getFloatPoly,
2526
+ cleanCPoly,
2527
+ falcon512: (falcon512 as any).__test,
2528
+ falcon512padded: (falcon512padded as any).__test,
2529
+ falcon1024: (falcon1024 as any).__test,
2530
+ falcon1024padded: (falcon1024padded as any).__test,
2531
+ }))();