@noble/post-quantum 0.7.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +90 -16
- package/_crystals.js +1 -1
- package/falcon.d.ts +1 -1
- package/falcon.js +121 -62
- package/hybrid.d.ts +33 -12
- package/hybrid.js +100 -41
- package/index.js +1 -1
- package/ml-dsa.d.ts +3 -3
- package/ml-dsa.js +57 -20
- package/ml-kem.js +85 -34
- package/package.json +15 -11
- package/slh-dsa.js +34 -11
- package/src/_crystals.ts +1 -1
- package/src/falcon.ts +127 -70
- package/src/hybrid.ts +108 -39
- package/src/index.ts +1 -1
- package/src/ml-dsa.ts +70 -25
- package/src/ml-kem.ts +76 -35
- package/src/slh-dsa.ts +44 -13
- package/src/utils.ts +115 -10
- package/src/webcrypto.ts +322 -0
- package/utils.d.ts +36 -2
- package/utils.js +105 -12
- package/webcrypto.d.ts +91 -0
- package/webcrypto.js +213 -0
package/src/falcon.ts
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
baswap64If,
|
|
28
28
|
type BytesCoderLen,
|
|
29
29
|
cleanBytes,
|
|
30
|
+
copyBytes,
|
|
30
31
|
type Coder,
|
|
31
32
|
type CryptoKeys,
|
|
32
33
|
getMask,
|
|
@@ -37,6 +38,7 @@ import {
|
|
|
37
38
|
type TRet,
|
|
38
39
|
validateSigOpts,
|
|
39
40
|
validateVerOpts,
|
|
41
|
+
SIG_OPT_KEYS,
|
|
40
42
|
type VerOpts,
|
|
41
43
|
} from './utils.ts';
|
|
42
44
|
/*
|
|
@@ -256,7 +258,12 @@ const compCoder = (n: number) => {
|
|
|
256
258
|
const sign = readBits(1);
|
|
257
259
|
const low = readBits(7);
|
|
258
260
|
let high = 0;
|
|
259
|
-
for
|
|
261
|
+
// Reference comp_decode adds 128 for each unary zero and rejects immediately above 2047.
|
|
262
|
+
// Waiting for the terminating one first lets an invalid coefficient scan the entire input.
|
|
263
|
+
while (!readBits(1)) {
|
|
264
|
+
high++;
|
|
265
|
+
if (high > LIMIT >>> 7) throw new Error(`limit: ${low | (high << 7)} > ${LIMIT}`);
|
|
266
|
+
}
|
|
260
267
|
const v = low | (high << 7);
|
|
261
268
|
if (sign && v === 0) throw new Error('negative zero encoding');
|
|
262
269
|
if (v > LIMIT) throw new Error(`limit: ${v} > ${LIMIT}`);
|
|
@@ -552,6 +559,9 @@ const SIGMA_MIN = /* @__PURE__ */ Object.freeze([
|
|
|
552
559
|
f64b(BigInt('4608433670533905013')),
|
|
553
560
|
f64b(BigInt('4608525754002622308')),
|
|
554
561
|
]);
|
|
562
|
+
// Upper end of the SamplerZ proof interval from Falcon section 3.9.1. The per-leaf sigma is
|
|
563
|
+
// derived from the reconstructed private basis, so imported keys must be checked against it.
|
|
564
|
+
const SIGMA_MAX = 1.8205;
|
|
555
565
|
|
|
556
566
|
// Falcon Table 3.1 RCDT values for chi, split into 24-bit limbs; storage is [high, mid, low],
|
|
557
567
|
// so gaussian0() intentionally compares them against v0, v1, v2 in reverse order. The final
|
|
@@ -1164,6 +1174,54 @@ function getFloatPoly(logn: number) {
|
|
|
1164
1174
|
};
|
|
1165
1175
|
}
|
|
1166
1176
|
|
|
1177
|
+
function ldlFFT(logn: number, g00t: CPoly, g01t: CPoly, g11t: CPoly) {
|
|
1178
|
+
// Algorithm 8: LDL*(G)
|
|
1179
|
+
// (Page 37)
|
|
1180
|
+
// Require: A full-rank self-adjoint matrix G = (Gᵢⱼ) ∈ FFT(Q[x]/(φ))²ˣ²
|
|
1181
|
+
// Ensure: The LDL* decomposition G = LDL* over FFT(Q[x]/(φ))
|
|
1182
|
+
// Format: All polynomials are in FFT representation.
|
|
1183
|
+
// 1: D₀₀ ← G₀₀
|
|
1184
|
+
// 2: L₁₀ ← G₁₀/G₀₀
|
|
1185
|
+
// 3: D₁₁ ← G₁₁ - L₁₀ ⊙ L₁₀* ⊙ G₀₀
|
|
1186
|
+
// 4: L ← [ 1 0 ; L₁₀ 1 ], D ← [ D₀₀ 0 ; 0 D₁₁ ]
|
|
1187
|
+
// 5: return (L, D)
|
|
1188
|
+
|
|
1189
|
+
// Algorithm 9: ffLDL*(G)
|
|
1190
|
+
// (Page 37)
|
|
1191
|
+
// Require: A full-rank Gram matrix G ∈ FFT(Q[x]/(xⁿ + 1))²ˣ²
|
|
1192
|
+
// Ensure: A binary tree T
|
|
1193
|
+
// Format: All polynomials are in FFT representation.
|
|
1194
|
+
// 1: (L, D) ← LDL*(G) ▷ L = [ 1 0 ; L₁₀ 1 ], D = [ D₀₀ 0 ; 0 D₁₁ ]
|
|
1195
|
+
// 2: T.value ← L₁₀
|
|
1196
|
+
// 3: if (n = 2) then
|
|
1197
|
+
// 4: T.leftchild ← D₀₀
|
|
1198
|
+
// 5: T.rightchild ← D₁₁
|
|
1199
|
+
// 6: return T
|
|
1200
|
+
// 7: else
|
|
1201
|
+
// 8: d₀₀, d₀₁ ← splitfft(D₀₀) ▷ dᵢⱼ ∈ FFT(Q[x]/(x^{n/2} + 1))
|
|
1202
|
+
// 9: d₁₀, d₁₁ ← splitfft(D₁₁)
|
|
1203
|
+
// 10: G₀ ← [ d₀₀ d₀₁ ; d₀₁* d₀₀ ], G₁ ← [ d₁₀ d₁₁ ; d₁₁* d₁₀ ]
|
|
1204
|
+
// ▷ Since D₀₀, D₁₁ are self-adjoint, (3.30) applies
|
|
1205
|
+
// 11: T.leftchild ← ffLDL*(G₀) ▷ Recursive calls
|
|
1206
|
+
// 12: T.rightchild ← ffLDL*(G₁)
|
|
1207
|
+
// 13: return T
|
|
1208
|
+
|
|
1209
|
+
// Recursive calls may alias g00t and g11t, and the top-level arrays persist across signing
|
|
1210
|
+
// retries. LDL replaces array entries, so shallow copies keep both kinds of caller state intact.
|
|
1211
|
+
g00t = g00t.slice();
|
|
1212
|
+
g01t = g01t.slice();
|
|
1213
|
+
g11t = g11t.slice();
|
|
1214
|
+
const hn = 1 << (logn - 1);
|
|
1215
|
+
for (let i = 0; i < hn; i++) {
|
|
1216
|
+
const g01 = g01t[i];
|
|
1217
|
+
const g11 = g11t[i];
|
|
1218
|
+
const mu = fComplex.scale(g01, 1.0 / g00t[i].re);
|
|
1219
|
+
g11t[i] = { re: g11.re - (mu.re * g01.re + mu.im * g01.im), im: g11.im };
|
|
1220
|
+
g01t[i] = fComplex.conj(mu);
|
|
1221
|
+
}
|
|
1222
|
+
return { g00: g00t, g01: g01t, g11: g11t };
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1167
1225
|
function ApproxExp(x: number, ccs: number): number {
|
|
1168
1226
|
// Algorithm 13: ApproxExp(x, ccs), (Page 42)
|
|
1169
1227
|
// Require: Floating-point values x ∈ [0, ln(2)] and ccs ∈ [0, 1]
|
|
@@ -1195,19 +1253,16 @@ function ApproxExp(x: number, ccs: number): number {
|
|
|
1195
1253
|
// Actual api
|
|
1196
1254
|
type FalconOpts = {
|
|
1197
1255
|
N: number;
|
|
1198
|
-
// Table 3.3 total padded detached bytes
|
|
1199
|
-
//
|
|
1200
|
-
// and the payload width `sigLen - 1 - NONCELEN`.
|
|
1256
|
+
// Table 3.3 total padded detached bytes. In padded mode it drives `.lengths.signature` and the
|
|
1257
|
+
// payload width `sigLen - 1 - NONCELEN`.
|
|
1201
1258
|
sigLen: number;
|
|
1202
|
-
padded
|
|
1259
|
+
padded: boolean;
|
|
1203
1260
|
fgBits: number;
|
|
1204
1261
|
FGBits: number;
|
|
1205
1262
|
// Compressed-s payload bytes only, excluding the detached header byte and 40-byte nonce.
|
|
1206
1263
|
paddedLen: number;
|
|
1207
|
-
//
|
|
1208
|
-
//
|
|
1209
|
-
// `s2` length, while signRaw() enforces `maxS2Len` separately.
|
|
1210
|
-
detachedLen: number;
|
|
1264
|
+
// Maximum compressed-s payload emitted by sign(). Unpadded detached verification enforces the
|
|
1265
|
+
// same Round-3 ceiling before decoding.
|
|
1211
1266
|
maxS2Len: number;
|
|
1212
1267
|
};
|
|
1213
1268
|
|
|
@@ -1230,7 +1285,7 @@ export type FalconAttached = CryptoKeys & {
|
|
|
1230
1285
|
* @param sig Attached Falcon signature bytes.
|
|
1231
1286
|
* @param publicKey Falcon public key bytes.
|
|
1232
1287
|
* @param opts Optional verification options.
|
|
1233
|
-
* @returns
|
|
1288
|
+
* @returns Fresh message bytes that do not alias either input when the signature is valid.
|
|
1234
1289
|
*/
|
|
1235
1290
|
open(sig: Uint8Array, publicKey: Uint8Array, opts?: VerOpts): Uint8Array;
|
|
1236
1291
|
};
|
|
@@ -1700,25 +1755,30 @@ function genFalcon(opts: FalconOpts): TRet<Falcon> {
|
|
|
1700
1755
|
};
|
|
1701
1756
|
// [ 1B header ] [ 40B nonce ] [ compressed_sig ]
|
|
1702
1757
|
const SignatureCoderDetached = (logn: number) => {
|
|
1703
|
-
const
|
|
1704
|
-
const getSigLen = (s2: TArg<Uint8Array>) => (opts.padded ?
|
|
1758
|
+
const paddedSigLen = opts.sigLen - 1 - NONCELEN;
|
|
1759
|
+
const getSigLen = (s2: TArg<Uint8Array>) => (opts.padded ? paddedSigLen : s2.length);
|
|
1705
1760
|
return {
|
|
1706
1761
|
encode({ nonce, s2 }: TArg<{ nonce: Uint8Array; s2: Uint8Array }>): TRet<Uint8Array> {
|
|
1707
1762
|
return headerCoder(
|
|
1708
1763
|
0x30 + logn,
|
|
1709
1764
|
splitCoder('falcon.detachedSignature', NONCELEN, getSigLen(s2))
|
|
1710
|
-
).encode([nonce, opts.padded ? pad(
|
|
1765
|
+
).encode([nonce, opts.padded ? pad(paddedSigLen).encode(s2) : s2]);
|
|
1711
1766
|
},
|
|
1712
1767
|
decode(data: TArg<Uint8Array>): TRet<{
|
|
1713
1768
|
nonce: Uint8Array;
|
|
1714
1769
|
s2: Uint8Array;
|
|
1715
1770
|
}> {
|
|
1771
|
+
// Unpadded Round-3 signatures have parameter-set maxima (header + nonce + s2):
|
|
1772
|
+
// 752 bytes for Falcon-512 and 1462 for Falcon-1024. Reject before creating views or
|
|
1773
|
+
// entering the bit decoder so attacker-sized inputs cannot cause proportional work.
|
|
1774
|
+
if (!opts.padded && data.length > 1 + NONCELEN + opts.maxS2Len)
|
|
1775
|
+
throw new Error('detached signature too long');
|
|
1716
1776
|
// Padded detached signatures are fixed-length (`lengths.signature`), so the payload width
|
|
1717
1777
|
// must come from the parameter set, not from the input: deriving it would accept appended
|
|
1718
1778
|
// zero bytes and truncated padding as extra valid encodings of the same signature.
|
|
1719
1779
|
// Unpadded signatures are variable-length; decodeUnpaddedSig() enforces the exact canonical
|
|
1720
1780
|
// bitlength of whatever remains.
|
|
1721
|
-
const payloadLen = opts.padded ?
|
|
1781
|
+
const payloadLen = opts.padded ? paddedSigLen : data.length - NONCELEN - 1;
|
|
1722
1782
|
const [nonce, raw] = headerCoder(
|
|
1723
1783
|
0x30 + logn,
|
|
1724
1784
|
splitCoder('falcon.detachedSignature', NONCELEN, payloadLen)
|
|
@@ -1792,7 +1852,7 @@ function genFalcon(opts: FalconOpts): TRet<Falcon> {
|
|
|
1792
1852
|
// Round-3 Falcon keeps 16-bit draws only in 0..61444, i.e. below 61445 = 5*q, the largest
|
|
1793
1853
|
// 16-bit multiple of q below 2^16; a literal ceil(2^16/q)*q would accept every sample.
|
|
1794
1854
|
const kQ = 5 * Q;
|
|
1795
|
-
for (let i = 0; i < N;
|
|
1855
|
+
for (let i = 0; i < N;) {
|
|
1796
1856
|
const tmp = h.xof(2); // 6: t ← SHAKE-256-Extract(ctx, 16)
|
|
1797
1857
|
let w = (tmp[0] << 8) | tmp[1];
|
|
1798
1858
|
if (w < kQ) c[i++] = w % Q; // 8: cᵢ ← t mod q
|
|
@@ -1970,48 +2030,6 @@ function genFalcon(opts: FalconOpts): TRet<Falcon> {
|
|
|
1970
2030
|
if (this.berExp(x, ccs)) return s + z;
|
|
1971
2031
|
}
|
|
1972
2032
|
}
|
|
1973
|
-
private ldlFFT(logn: number, g00t: CPoly, g01t: CPoly, g11t: CPoly) {
|
|
1974
|
-
// Algorithm 8: LDL*(G)
|
|
1975
|
-
// (Page 37)
|
|
1976
|
-
// Require: A full-rank self-adjoint matrix G = (Gᵢⱼ) ∈ FFT(Q[x]/(φ))²ˣ²
|
|
1977
|
-
// Ensure: The LDL* decomposition G = LDL* over FFT(Q[x]/(φ))
|
|
1978
|
-
// Format: All polynomials are in FFT representation.
|
|
1979
|
-
// 1: D₀₀ ← G₀₀
|
|
1980
|
-
// 2: L₁₀ ← G₁₀/G₀₀
|
|
1981
|
-
// 3: D₁₁ ← G₁₁ - L₁₀ ⊙ L₁₀* ⊙ G₀₀
|
|
1982
|
-
// 4: L ← [ 1 0 ; L₁₀ 1 ], D ← [ D₀₀ 0 ; 0 D₁₁ ]
|
|
1983
|
-
// 5: return (L, D)
|
|
1984
|
-
|
|
1985
|
-
// Algorithm 9: ffLDL*(G)
|
|
1986
|
-
// (Page 37)
|
|
1987
|
-
// Require: A full-rank Gram matrix G ∈ FFT(Q[x]/(xⁿ + 1))²ˣ²
|
|
1988
|
-
// Ensure: A binary tree T
|
|
1989
|
-
// Format: All polynomials are in FFT representation.
|
|
1990
|
-
// 1: (L, D) ← LDL*(G) ▷ L = [ 1 0 ; L₁₀ 1 ], D = [ D₀₀ 0 ; 0 D₁₁ ]
|
|
1991
|
-
// 2: T.value ← L₁₀
|
|
1992
|
-
// 3: if (n = 2) then
|
|
1993
|
-
// 4: T.leftchild ← D₀₀
|
|
1994
|
-
// 5: T.rightchild ← D₁₁
|
|
1995
|
-
// 6: return T
|
|
1996
|
-
// 7: else
|
|
1997
|
-
// 8: d₀₀, d₀₁ ← splitfft(D₀₀) ▷ dᵢⱼ ∈ FFT(Q[x]/(x^{n/2} + 1))
|
|
1998
|
-
// 9: d₁₀, d₁₁ ← splitfft(D₁₁)
|
|
1999
|
-
// 10: G₀ ← [ d₀₀ d₀₁ ; d₀₁* d₀₀ ], G₁ ← [ d₁₀ d₁₁ ; d₁₁* d₁₀ ]
|
|
2000
|
-
// ▷ Since D₀₀, D₁₁ are self-adjoint, (3.30) applies
|
|
2001
|
-
// 11: T.leftchild ← ffLDL*(G₀) ▷ Recursive calls
|
|
2002
|
-
// 12: T.rightchild ← ffLDL*(G₁)
|
|
2003
|
-
// 13: return T
|
|
2004
|
-
g00t = g00t.slice(); // can be same as g11t and everything will break!
|
|
2005
|
-
const hn = 1 << (logn - 1);
|
|
2006
|
-
for (let i = 0; i < hn; i++) {
|
|
2007
|
-
const g01 = g01t[i];
|
|
2008
|
-
const g11 = g11t[i];
|
|
2009
|
-
const mu = fComplex.scale(g01, 1.0 / g00t[i].re);
|
|
2010
|
-
g11t[i] = { re: g11.re - (mu.re * g01.re + mu.im * g01.im), im: g11.im };
|
|
2011
|
-
g01t[i] = fComplex.conj(mu);
|
|
2012
|
-
}
|
|
2013
|
-
return { g00: g00t, g01: g01t, g11: g11t };
|
|
2014
|
-
}
|
|
2015
2033
|
private splitFFT(logn: number, f: CPoly) {
|
|
2016
2034
|
// Algorithm 1: splitfft(FFT(f))
|
|
2017
2035
|
// (Page 29)
|
|
@@ -2127,7 +2145,13 @@ function genFalcon(opts: FalconOpts): TRet<Falcon> {
|
|
|
2127
2145
|
// 13: z₀ ← mergefft(z'₀)
|
|
2128
2146
|
// 14: return z = (z₀, z₁)
|
|
2129
2147
|
if (logn === 0) {
|
|
2148
|
+
// The dynamic sampler stores 1/σ' instead of σ'. Keygen guarantees this interval,
|
|
2149
|
+
// but an imported compact key may reconstruct an invalid basis. Check the actual LDL*
|
|
2150
|
+
// leaf before it can drive SamplerZ; the negated comparison also rejects NaN/infinity.
|
|
2130
2151
|
const leaf = Math.sqrt(g00i[0].re) * INV_SIGMA[this.logn];
|
|
2152
|
+
const sigmaPrime = 1 / leaf;
|
|
2153
|
+
if (!(sigmaPrime >= SIGMA_MIN[this.logn] && sigmaPrime <= SIGMA_MAX))
|
|
2154
|
+
throw new Error('invalid secretKey: sampler sigma out of range');
|
|
2131
2155
|
// 3: z₀ ← SamplerZ(t₀, σ')
|
|
2132
2156
|
// ▷ Since n=1, tᵢ = invFFT(tᵢ) ∈ Q and zᵢ = invFFT(zᵢ) ∈ Z
|
|
2133
2157
|
const t0re = this.samplerZ(t0[0].re, leaf);
|
|
@@ -2135,7 +2159,7 @@ function genFalcon(opts: FalconOpts): TRet<Falcon> {
|
|
|
2135
2159
|
return { t0: [{ re: t0re, im: 0.0 }], t1: [{ re: t1re, im: 0.0 }] };
|
|
2136
2160
|
}
|
|
2137
2161
|
// 6: (l, T₀, T₁) ← (T.value, T.leftchild, T.rightchild)
|
|
2138
|
-
const { g00, g01, g11 } =
|
|
2162
|
+
const { g00, g01, g11 } = ldlFFT(logn, g00i, g01i, g11i);
|
|
2139
2163
|
const { f0: g00f0, f1: g00f1 } = this.splitSelfAdjFFT(logn, g00);
|
|
2140
2164
|
const { f0: g11f0, f1: g11f1 } = this.splitSelfAdjFFT(logn, g11);
|
|
2141
2165
|
// 7: t'₁ ← splitfft(t₁)
|
|
@@ -2308,10 +2332,15 @@ function genFalcon(opts: FalconOpts): TRet<Falcon> {
|
|
|
2308
2332
|
publicKey: publicKeyCoder.bytesLen,
|
|
2309
2333
|
secretKey: secretKeyCoder.bytesLen,
|
|
2310
2334
|
});
|
|
2335
|
+
// Falcon takes a sampler callback the other schemes do not, and rejects `context`
|
|
2336
|
+
// with its own message; both stay in the accepted set so the specific errors fire.
|
|
2337
|
+
const FALCON_SIG_OPT_KEYS = [...SIG_OPT_KEYS, 'random'] as const;
|
|
2311
2338
|
// Noble exposes a 48-byte sampler-seed hook,
|
|
2312
2339
|
// but Falcon still samples/encodes a separate 40-byte nonce per signature.
|
|
2313
2340
|
const getRnd = (opts: TArg<FalconSigOpts> = {}): TRet<FalconRandom> => {
|
|
2314
|
-
|
|
2341
|
+
// `context` stays in the accepted set so the specific "not supported" error below
|
|
2342
|
+
// still fires, rather than the generic unexpected-option one.
|
|
2343
|
+
opts = validateSigOpts(opts, FALCON_SIG_OPT_KEYS);
|
|
2315
2344
|
if (opts.context !== undefined) throw new Error('context is not supported');
|
|
2316
2345
|
if (opts.random !== undefined && typeof opts.random !== 'function')
|
|
2317
2346
|
throw new TypeError('"opts.random" expected function, got type=' + typeof opts.random);
|
|
@@ -2323,8 +2352,8 @@ function genFalcon(opts: FalconOpts): TRet<Falcon> {
|
|
|
2323
2352
|
return (len = 0) => drbg.randomBytes(len) as TRet<Uint8Array>;
|
|
2324
2353
|
};
|
|
2325
2354
|
const checkVerOpts = (opts: TArg<VerOpts> = {}) => {
|
|
2326
|
-
validateVerOpts(opts);
|
|
2327
|
-
if (
|
|
2355
|
+
const normalized = validateVerOpts(opts);
|
|
2356
|
+
if (normalized.context !== undefined) throw new Error('context is not supported');
|
|
2328
2357
|
};
|
|
2329
2358
|
const tests = Object.freeze({
|
|
2330
2359
|
publicKeyCoder: Object.freeze(publicKeyCoder),
|
|
@@ -2407,11 +2436,39 @@ function genFalcon(opts: FalconOpts): TRet<Falcon> {
|
|
|
2407
2436
|
},
|
|
2408
2437
|
open(sig: TArg<Uint8Array>, pk: TArg<Uint8Array>, verOpts: TArg<VerOpts> = {}) {
|
|
2409
2438
|
checkVerOpts(verOpts);
|
|
2410
|
-
|
|
2411
|
-
//
|
|
2412
|
-
//
|
|
2413
|
-
|
|
2414
|
-
|
|
2439
|
+
// Wrong argument types are caller bugs and must stay TypeErrors; only what happens
|
|
2440
|
+
// after this is untrusted input. Detached verify() type-checks the public key the same
|
|
2441
|
+
// way, so open() does too: a wrong type is fatal, and a malformed (wrong-length or
|
|
2442
|
+
// non-canonical) key folds into the single rejection below rather than leaking a raw
|
|
2443
|
+
// codec error, exactly as detached verify() folds it into `false`.
|
|
2444
|
+
abytes(sig, undefined, 'signature');
|
|
2445
|
+
abytes(pk, undefined, 'publicKey');
|
|
2446
|
+
// Decode and verify owned snapshots. Apart from keeping the authenticated result stable
|
|
2447
|
+
// after open() returns, this ensures every verification step observes the same bytes when
|
|
2448
|
+
// an input is backed by SharedArrayBuffer or has subclass-overridden view methods.
|
|
2449
|
+
const ownedSig = copyBytes(sig);
|
|
2450
|
+
const ownedPk = copyBytes(pk);
|
|
2451
|
+
// Decode failures and malformed-key failures are rejected signatures, not internal
|
|
2452
|
+
// faults. Letting the codec's own errors out gave a caller handling untrusted input
|
|
2453
|
+
// several different messages for one corrupt byte, including "end of buffer: len=2
|
|
2454
|
+
// buf=0 lastByte=undefined", which reads as a library bug. Detached verify already
|
|
2455
|
+
// treats every such failure uniformly; open() collapses them into one Error (the
|
|
2456
|
+
// original preserved as `cause`). A well-formed signature that simply does not
|
|
2457
|
+
// validate falls through to the same message with no cause.
|
|
2458
|
+
try {
|
|
2459
|
+
let verifiedMsg: Uint8Array | undefined;
|
|
2460
|
+
try {
|
|
2461
|
+
const { s2, nonce, msg } = SignatureCoder.decode(ownedSig);
|
|
2462
|
+
if (verifyRaw(ownedPk, s2, nonce, msg)) verifiedMsg = msg;
|
|
2463
|
+
} catch (cause) {
|
|
2464
|
+
throw new Error('invalid signature', { cause });
|
|
2465
|
+
}
|
|
2466
|
+
if (verifiedMsg === undefined) throw new Error('invalid signature');
|
|
2467
|
+
// Do not retain or expose the full attached-signature allocation through `.buffer`.
|
|
2468
|
+
return copyBytes(verifiedMsg);
|
|
2469
|
+
} finally {
|
|
2470
|
+
cleanBytes(ownedSig, ownedPk);
|
|
2471
|
+
}
|
|
2415
2472
|
},
|
|
2416
2473
|
});
|
|
2417
2474
|
const res = {
|
|
@@ -2429,14 +2486,14 @@ function genFalcon(opts: FalconOpts): TRet<Falcon> {
|
|
|
2429
2486
|
|
|
2430
2487
|
const falcon512opts = {
|
|
2431
2488
|
N: 512,
|
|
2489
|
+
// Keep the mode an own property: omitted config fields must not inherit from Object.prototype.
|
|
2490
|
+
padded: false,
|
|
2432
2491
|
// Table 3.3 fixed padded detached bytes, including the detached header byte and 40-byte nonce.
|
|
2433
2492
|
sigLen: 666,
|
|
2434
2493
|
fgBits: 6,
|
|
2435
2494
|
FGBits: 8,
|
|
2436
2495
|
// Compressed-s payload bytes only, excluding the detached header byte and 40-byte nonce.
|
|
2437
2496
|
paddedLen: 625,
|
|
2438
|
-
// Payload-only budget: genFalcon() adds the detached header byte and 40-byte nonce around it.
|
|
2439
|
-
detachedLen: 690,
|
|
2440
2497
|
};
|
|
2441
2498
|
/**
|
|
2442
2499
|
* Falcon-512 detached-signature API with the attached helper exposed as `.attached`.
|
|
@@ -2471,14 +2528,13 @@ export const falcon512padded: TRet<Falcon> = /* @__PURE__ */ (() =>
|
|
|
2471
2528
|
|
|
2472
2529
|
const falcon1024opts = {
|
|
2473
2530
|
N: 1024,
|
|
2531
|
+
padded: false,
|
|
2474
2532
|
// Table 3.3 fixed padded detached bytes, including the detached header byte and 40-byte nonce.
|
|
2475
2533
|
sigLen: 1280,
|
|
2476
2534
|
fgBits: 5,
|
|
2477
2535
|
FGBits: 8,
|
|
2478
2536
|
// Compressed-s payload bytes only, excluding the detached header byte and 40-byte nonce.
|
|
2479
2537
|
paddedLen: 1239,
|
|
2480
|
-
// Payload-only budget: genFalcon() adds the detached header byte and 40-byte nonce around it.
|
|
2481
|
-
detachedLen: 1280,
|
|
2482
2538
|
};
|
|
2483
2539
|
/**
|
|
2484
2540
|
* Falcon-1024 detached-signature API with the attached helper exposed as `.attached`.
|
|
@@ -2523,6 +2579,7 @@ export const __tests: any = /* @__PURE__ */ (() =>
|
|
|
2523
2579
|
INV_SIGMA,
|
|
2524
2580
|
SIGMA_MIN,
|
|
2525
2581
|
getFloatPoly,
|
|
2582
|
+
ldlFFT,
|
|
2526
2583
|
cleanCPoly,
|
|
2527
2584
|
falcon512: (falcon512 as any).__test,
|
|
2528
2585
|
falcon512padded: (falcon512padded as any).__test,
|