@noble/post-quantum 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +106 -80
- package/_crystals.d.ts +23 -16
- package/_crystals.js +50 -12
- package/falcon.d.ts +7 -8
- package/falcon.js +109 -74
- package/hybrid.d.ts +21 -32
- package/hybrid.js +157 -78
- package/index.d.ts +0 -1
- package/index.js +8 -1
- package/ml-dsa.d.ts +35 -9
- package/ml-dsa.js +116 -43
- package/ml-kem.d.ts +46 -5
- package/ml-kem.js +175 -67
- package/package.json +10 -18
- package/slh-dsa.d.ts +44 -24
- package/slh-dsa.js +150 -84
- package/src/_crystals.ts +86 -35
- package/src/falcon.ts +230 -169
- package/src/hybrid.ts +243 -129
- package/src/index.ts +8 -0
- package/src/ml-dsa.ts +194 -87
- package/src/ml-kem.ts +283 -122
- package/src/slh-dsa.ts +310 -182
- package/src/utils.ts +244 -48
- package/utils.d.ts +99 -24
- package/utils.js +92 -24
- package/_crystals.d.ts.map +0 -1
- package/_crystals.js.map +0 -1
- package/falcon.d.ts.map +0 -1
- package/falcon.js.map +0 -1
- package/hybrid.d.ts.map +0 -1
- package/hybrid.js.map +0 -1
- package/index.d.ts.map +0 -1
- package/index.js.map +0 -1
- package/ml-dsa.d.ts.map +0 -1
- package/ml-dsa.js.map +0 -1
- package/ml-kem.d.ts.map +0 -1
- package/ml-kem.js.map +0 -1
- package/slh-dsa.d.ts.map +0 -1
- package/slh-dsa.js.map +0 -1
- package/utils.d.ts.map +0 -1
- package/utils.js.map +0 -1
package/src/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
|
|
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 +
|
|
156
|
-
encode(value: T): Uint8Array {
|
|
157
|
-
const body =
|
|
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
|
|
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
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
];
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 &
|
|
1250
|
-
const r2 = (r128 >>
|
|
1251
|
-
const sign = Number((r128 >>
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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)) +
|
|
1387
|
+
const maxfgNew = scaleK + BigInt(Math.round(fgMaxBits)) + _10n;
|
|
1375
1388
|
if (maxfgNew < maxFGBits) maxFGBits = maxfgNew;
|
|
1376
|
-
if (FGlen >
|
|
1377
|
-
if (scaleK <=
|
|
1378
|
-
scaleK -=
|
|
1379
|
-
if (scaleK <
|
|
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 <=
|
|
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 = (
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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 &
|
|
1837
|
-
n[3] ^= Number(this.ctr >>
|
|
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 = (
|
|
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 = (
|
|
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
|
|
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 = {
|
|
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
|
|
2296
|
-
|
|
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 = (
|
|
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 = (
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
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
|
+
}))();
|