@noble/post-quantum 0.5.4 → 0.6.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 +86 -42
- package/_crystals.d.ts +90 -4
- package/_crystals.d.ts.map +1 -1
- package/_crystals.js +77 -6
- package/_crystals.js.map +1 -1
- package/falcon.d.ts +84 -0
- package/falcon.d.ts.map +1 -0
- package/falcon.js +2385 -0
- package/falcon.js.map +1 -0
- package/hybrid.d.ts +194 -24
- package/hybrid.d.ts.map +1 -1
- package/hybrid.js +401 -77
- package/hybrid.js.map +1 -1
- package/index.js +8 -0
- package/index.js.map +1 -1
- package/ml-dsa.d.ts +29 -8
- package/ml-dsa.d.ts.map +1 -1
- package/ml-dsa.js +154 -78
- package/ml-dsa.js.map +1 -1
- package/ml-kem.d.ts +31 -7
- package/ml-kem.d.ts.map +1 -1
- package/ml-kem.js +194 -75
- package/ml-kem.js.map +1 -1
- package/package.json +15 -8
- package/slh-dsa.d.ts +137 -34
- package/slh-dsa.d.ts.map +1 -1
- package/slh-dsa.js +189 -68
- package/slh-dsa.js.map +1 -1
- package/src/_crystals.ts +135 -24
- package/src/falcon.ts +2503 -0
- package/src/hybrid.ts +515 -144
- package/src/index.ts +8 -0
- package/src/ml-dsa.ts +263 -138
- package/src/ml-kem.ts +240 -97
- package/src/slh-dsa.ts +391 -153
- package/src/utils.ts +491 -46
- package/utils.d.ts +362 -24
- package/utils.d.ts.map +1 -1
- package/utils.js +273 -20
- package/utils.js.map +1 -1
package/src/slh-dsa.ts
CHANGED
|
@@ -53,6 +53,8 @@ import {
|
|
|
53
53
|
vecCoder,
|
|
54
54
|
type Signer,
|
|
55
55
|
type SigOpts,
|
|
56
|
+
type TArg,
|
|
57
|
+
type TRet,
|
|
56
58
|
type VerOpts,
|
|
57
59
|
} from './utils.ts';
|
|
58
60
|
|
|
@@ -62,30 +64,52 @@ import {
|
|
|
62
64
|
* * K: FORS trees numbers. A: FORS trees height
|
|
63
65
|
*/
|
|
64
66
|
export type SphincsOpts = {
|
|
67
|
+
/** Security parameter in bytes. */
|
|
65
68
|
N: number;
|
|
69
|
+
/** Winternitz parameter. */
|
|
66
70
|
W: number;
|
|
71
|
+
/** Total hypertree height. */
|
|
67
72
|
H: number;
|
|
73
|
+
/** Number of hypertree layers. */
|
|
68
74
|
D: number;
|
|
75
|
+
/** Number of FORS trees. */
|
|
69
76
|
K: number;
|
|
77
|
+
/** Height of each FORS tree. */
|
|
70
78
|
A: number;
|
|
79
|
+
/** Target security level in bits. */
|
|
71
80
|
securityLevel: number;
|
|
72
81
|
};
|
|
73
82
|
|
|
83
|
+
/** Hash customization options for SLH-DSA context creation. */
|
|
74
84
|
export type SphincsHashOpts = {
|
|
85
|
+
/** Whether to use the compressed-address variant from the standard. */
|
|
75
86
|
isCompressed?: boolean;
|
|
87
|
+
/** Factory that binds one parameter set to one per-key hash context generator. */
|
|
76
88
|
getContext: GetContext;
|
|
77
89
|
};
|
|
78
90
|
|
|
79
91
|
/** Winternitz signature params. */
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
92
|
+
/**
|
|
93
|
+
* Built-in SLH-DSA Table 2 subset keyed by strength/profile.
|
|
94
|
+
* SHA2 and SHAKE pairs share the same numeric rows here, so the hash family is chosen separately.
|
|
95
|
+
* `securityLevel` stores 128/192/256-bit strengths for `checkHash(...)`,
|
|
96
|
+
* not Table 2's category labels 1/3/5.
|
|
97
|
+
* Other Table 2 columns such as `m`, public-key bytes, and signature bytes
|
|
98
|
+
* stay derived at the export layer.
|
|
99
|
+
*/
|
|
100
|
+
export const PARAMS: Record<string, SphincsOpts> = /* @__PURE__ */ (() =>
|
|
101
|
+
Object.freeze({
|
|
102
|
+
'128f': Object.freeze({ W: 16, N: 16, H: 66, D: 22, K: 33, A: 6, securityLevel: 128 }),
|
|
103
|
+
'128s': Object.freeze({ W: 16, N: 16, H: 63, D: 7, K: 14, A: 12, securityLevel: 128 }),
|
|
104
|
+
'192f': Object.freeze({ W: 16, N: 24, H: 66, D: 22, K: 33, A: 8, securityLevel: 192 }),
|
|
105
|
+
'192s': Object.freeze({ W: 16, N: 24, H: 63, D: 7, K: 17, A: 14, securityLevel: 192 }),
|
|
106
|
+
'256f': Object.freeze({ W: 16, N: 32, H: 68, D: 17, K: 35, A: 9, securityLevel: 256 }),
|
|
107
|
+
'256s': Object.freeze({ W: 16, N: 32, H: 64, D: 8, K: 22, A: 14, securityLevel: 256 }),
|
|
108
|
+
} as const))();
|
|
109
|
+
|
|
110
|
+
// FIPS 205 `ADRS.setTypeAndClear(...)` selectors. Local names shorten the spec labels
|
|
111
|
+
// (`WOTS_HASH` -> `WOTS`, `TREE` -> `HASHTREE`, `FORS_ROOTS` -> `FORSPK`), and `setAddr({ type })`
|
|
112
|
+
// below only writes the type word; callers still need to preserve or overwrite the trailing words.
|
|
89
113
|
const AddressType = {
|
|
90
114
|
WOTS: 0,
|
|
91
115
|
WOTSPK: 1,
|
|
@@ -96,39 +120,87 @@ const AddressType = {
|
|
|
96
120
|
FORSPRF: 6,
|
|
97
121
|
} as const;
|
|
98
122
|
|
|
99
|
-
/** Address
|
|
123
|
+
/** Address byte array of size `ADDR_BYTES`. */
|
|
100
124
|
export type ADRS = Uint8Array;
|
|
101
125
|
|
|
126
|
+
/** Hash and tweakable-hash callbacks bound to one SLH-DSA keypair context. */
|
|
102
127
|
export type Context = {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
128
|
+
/**
|
|
129
|
+
* Derive a PRF output for one address.
|
|
130
|
+
* @param addr - Address bytes.
|
|
131
|
+
* @returns PRF output bytes.
|
|
132
|
+
*/
|
|
133
|
+
PRFaddr: (addr: TArg<ADRS>) => TRet<Uint8Array>;
|
|
134
|
+
/**
|
|
135
|
+
* Derive the randomized message hash prefix.
|
|
136
|
+
* @param skPRF - Secret PRF seed.
|
|
137
|
+
* @param random - Per-signature randomness.
|
|
138
|
+
* @param msg - Message bytes.
|
|
139
|
+
* @returns PRF output bytes.
|
|
140
|
+
*/
|
|
141
|
+
PRFmsg: (
|
|
142
|
+
skPRF: TArg<Uint8Array>,
|
|
143
|
+
random: TArg<Uint8Array>,
|
|
144
|
+
msg: TArg<Uint8Array>
|
|
145
|
+
) => TRet<Uint8Array>;
|
|
146
|
+
/**
|
|
147
|
+
* Hash one randomized message transcript.
|
|
148
|
+
* @param R - Randomized message prefix.
|
|
149
|
+
* @param pk - Public key bytes.
|
|
150
|
+
* @param m - Message bytes.
|
|
151
|
+
* @param outLen - Output length in bytes.
|
|
152
|
+
* @returns Transcript hash bytes.
|
|
153
|
+
*/
|
|
154
|
+
Hmsg: (
|
|
155
|
+
R: TArg<Uint8Array>,
|
|
156
|
+
pk: TArg<Uint8Array>,
|
|
157
|
+
m: TArg<Uint8Array>,
|
|
158
|
+
outLen: number
|
|
159
|
+
) => TRet<Uint8Array>;
|
|
160
|
+
/**
|
|
161
|
+
* Tweakable hash over one input block.
|
|
162
|
+
* @param input - Input block.
|
|
163
|
+
* @param addr - Address bytes.
|
|
164
|
+
* @returns Hash output bytes.
|
|
165
|
+
*/
|
|
166
|
+
thash1: (input: TArg<Uint8Array>, addr: TArg<ADRS>) => TRet<Uint8Array>;
|
|
167
|
+
/**
|
|
168
|
+
* Tweakable hash over multiple input blocks.
|
|
169
|
+
* @param blocks - Number of input blocks.
|
|
170
|
+
* @param input - Concatenated input bytes.
|
|
171
|
+
* @param addr - Address bytes.
|
|
172
|
+
* @returns Hash output bytes.
|
|
173
|
+
*/
|
|
174
|
+
thashN: (blocks: number, input: TArg<Uint8Array>, addr: TArg<ADRS>) => TRet<Uint8Array>;
|
|
175
|
+
/** Wipe any buffered hash state for the current context. */
|
|
108
176
|
clean: () => void;
|
|
109
177
|
};
|
|
178
|
+
/** Factory that creates a context generator for one SLH-DSA parameter set. */
|
|
110
179
|
export type GetContext = (
|
|
111
180
|
opts: SphincsOpts
|
|
112
|
-
) => (pub_seed: Uint8Array
|
|
181
|
+
) => (pub_seed: TArg<Uint8Array>, sk_seed?: TArg<Uint8Array>) => TRet<Context>;
|
|
113
182
|
|
|
114
183
|
function hexToNumber(hex: string): bigint {
|
|
115
184
|
if (typeof hex !== 'string') throw new Error('hex string expected, got ' + typeof hex);
|
|
116
185
|
return BigInt(hex === '' ? '0' : '0x' + hex); // Big Endian
|
|
117
186
|
}
|
|
118
187
|
|
|
119
|
-
// BE: Big Endian, LE: Little Endian
|
|
120
|
-
function bytesToNumberBE(bytes: Uint8Array): bigint {
|
|
188
|
+
// BE: Big Endian, LE: Little Endian. This is the local FIPS 205 `toInt(...)` equivalent.
|
|
189
|
+
function bytesToNumberBE(bytes: TArg<Uint8Array>): bigint {
|
|
121
190
|
return hexToNumber(bytesToHex(bytes));
|
|
122
191
|
}
|
|
123
192
|
|
|
124
|
-
|
|
193
|
+
// Local in-range FIPS 205 `toByte(x, n)` equivalent; callers must keep `n < 256^len`.
|
|
194
|
+
function numberToBytesBE(n: number | bigint, len: number): TRet<Uint8Array> {
|
|
125
195
|
return hexToBytes(n.toString(16).padStart(len * 2, '0'));
|
|
126
196
|
}
|
|
127
197
|
|
|
128
|
-
//
|
|
198
|
+
// Local FIPS 205 Algorithm 4 `base_2^b(...)` implementation. Bits are consumed in big-endian
|
|
199
|
+
// order within each input byte, and callers must provide at least `ceil(outLen * b / 8)` bytes;
|
|
200
|
+
// short inputs are not rejected and would zero-extend implicitly.
|
|
129
201
|
const base2b = (outLen: number, b: number) => {
|
|
130
202
|
const mask = getMask(b);
|
|
131
|
-
return (bytes: Uint8Array) => {
|
|
203
|
+
return (bytes: TArg<Uint8Array>): TRet<Uint32Array> => {
|
|
132
204
|
const baseB = new Uint32Array(outLen);
|
|
133
205
|
for (let out = 0, pos = 0, bits = 0, total = 0; out < outLen; out++) {
|
|
134
206
|
while (bits < b) {
|
|
@@ -138,7 +210,7 @@ const base2b = (outLen: number, b: number) => {
|
|
|
138
210
|
bits -= b;
|
|
139
211
|
baseB[out] = (total >>> bits) & mask;
|
|
140
212
|
}
|
|
141
|
-
return baseB
|
|
213
|
+
return baseB as TRet<Uint32Array>;
|
|
142
214
|
};
|
|
143
215
|
};
|
|
144
216
|
|
|
@@ -146,13 +218,20 @@ function getMaskBig(bits: number) {
|
|
|
146
218
|
return (1n << BigInt(bits)) - 1n; // 4 -> 0b1111
|
|
147
219
|
}
|
|
148
220
|
|
|
221
|
+
/** Public SLH-DSA signer with prehash customization. */
|
|
149
222
|
export type SphincsSigner = Signer & {
|
|
150
|
-
internal: Signer
|
|
223
|
+
internal: TRet<Signer>;
|
|
151
224
|
securityLevel: number;
|
|
152
|
-
prehash: (hash: CHash) => Signer
|
|
225
|
+
prehash: (hash: TArg<CHash>) => TRet<Signer>;
|
|
153
226
|
};
|
|
154
227
|
|
|
155
|
-
|
|
228
|
+
/** One parameter/hash instantiation of the public SLH-DSA API.
|
|
229
|
+
* `keygen(seed)` is a deterministic 3N-byte library hook around the internal keygen flow,
|
|
230
|
+
* and `getPublicKey(secretKey)` only extracts the embedded public key
|
|
231
|
+
* instead of recomputing `PK.root`.
|
|
232
|
+
*/
|
|
233
|
+
function gen(opts: SphincsOpts, hashOpts_: TArg<SphincsHashOpts>): TRet<SphincsSigner> {
|
|
234
|
+
const hashOpts = hashOpts_ as SphincsHashOpts;
|
|
156
235
|
const { N, W, H, D, K, A, securityLevel: securityLevel } = opts;
|
|
157
236
|
const getContext = hashOpts.getContext(opts);
|
|
158
237
|
if (W !== 16) throw new Error('Unsupported Winternitz parameter');
|
|
@@ -183,8 +262,13 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
|
|
|
183
262
|
OFFSET_HASH_ADDR += 10;
|
|
184
263
|
}
|
|
185
264
|
|
|
265
|
+
// Mutates and returns `addr` in place. For the built-in parameter sets, the layer / chain /
|
|
266
|
+
// hash / height / keypair values fit in the low byte(s), and the tree value fits in 64 bits,
|
|
267
|
+
// so the untouched leading bytes in the wider FIPS 205 ADRS / ADRS_c fields stay zero.
|
|
268
|
+
// `height` / `chain` and `index` / `hash` share the same spec words, so callers must use the
|
|
269
|
+
// address-type-specific combinations instead of mixing both meanings in one call.
|
|
186
270
|
const setAddr = (
|
|
187
|
-
opts: {
|
|
271
|
+
opts: TArg<{
|
|
188
272
|
type?: (typeof AddressType)[keyof typeof AddressType];
|
|
189
273
|
height?: number;
|
|
190
274
|
tree?: bigint;
|
|
@@ -195,8 +279,8 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
|
|
|
195
279
|
keypair?: number;
|
|
196
280
|
subtreeAddr?: ADRS;
|
|
197
281
|
keypairAddr?: ADRS;
|
|
198
|
-
}
|
|
199
|
-
addr: ADRS = new Uint8Array(ADDR_BYTES)
|
|
282
|
+
}>,
|
|
283
|
+
addr: TArg<ADRS> = new Uint8Array(ADDR_BYTES)
|
|
200
284
|
) => {
|
|
201
285
|
const { type, height, tree, layer, index, chain, hash, keypair } = opts;
|
|
202
286
|
const { subtreeAddr, keypairAddr } = opts;
|
|
@@ -223,11 +307,12 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
|
|
|
223
307
|
};
|
|
224
308
|
|
|
225
309
|
const chainCoder = base2b(WOTS_LEN2, WOTS_LOGW);
|
|
226
|
-
const chainLengths = (msg: Uint8Array) => {
|
|
310
|
+
const chainLengths = (msg: TArg<Uint8Array>) => {
|
|
227
311
|
const W1 = base2b(WOTS_LEN1, WOTS_LOGW)(msg);
|
|
228
312
|
let csum = 0;
|
|
229
313
|
for (let i = 0; i < W1.length; i++) csum += W - 1 - W1[i]; // ▷ Compute checksum
|
|
230
|
-
|
|
314
|
+
// csum ← csum ≪ ((8 − ((len2 · lg(w)) mod 8)) mod 8
|
|
315
|
+
csum <<= (8 - ((WOTS_LEN2 * WOTS_LOGW) % 8)) % 8;
|
|
231
316
|
// Checksum to base(LOG_W)
|
|
232
317
|
const W2 = chainCoder(numberToBytesBE(csum, Math.ceil((WOTS_LEN2 * WOTS_LOGW) / 8)));
|
|
233
318
|
// W1 || W2 (concatBytes cannot concat TypedArrays)
|
|
@@ -246,25 +331,44 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
|
|
|
246
331
|
Math.ceil(TREE_BITS / 8),
|
|
247
332
|
Math.ceil(TREE_HEIGHT / 8)
|
|
248
333
|
);
|
|
249
|
-
|
|
250
|
-
|
|
334
|
+
// `pkSeed` is the full public key byte string `PK.seed || PK.root`; after splitting `Hmsg`,
|
|
335
|
+
// mask away any spare high bits so `idx_tree` / `idx_leaf` match the spec's final mod-2^k steps.
|
|
336
|
+
const hashMessage = (
|
|
337
|
+
R: TArg<Uint8Array>,
|
|
338
|
+
pkSeed: TArg<Uint8Array>,
|
|
339
|
+
msg: TArg<Uint8Array>,
|
|
340
|
+
context: TArg<Context>
|
|
341
|
+
) => {
|
|
342
|
+
const rawContext = context as Context;
|
|
343
|
+
// digest ← Hmsg(R, PK.seed, PK.root, M)
|
|
344
|
+
const digest = rawContext.Hmsg(R, pkSeed, msg, hashMsgCoder.bytesLen);
|
|
251
345
|
const [md, tmpIdxTree, tmpIdxLeaf] = hashMsgCoder.decode(digest);
|
|
252
346
|
const tree = bytesToNumberBE(tmpIdxTree) & getMaskBig(TREE_BITS);
|
|
253
347
|
const leafIdx = Number(bytesToNumberBE(tmpIdxLeaf)) & getMask(LEAF_BITS);
|
|
254
348
|
return { tree, leafIdx, md };
|
|
255
349
|
};
|
|
256
350
|
|
|
351
|
+
// Iterative `xmss_node` / `xmss_sign` core: mutate `treeAddr` in place, collapse completed
|
|
352
|
+
// sibling pairs on `stack`, and record the sibling whenever the current subtree is the auth-path
|
|
353
|
+
// neighbor of the target leaf at that height.
|
|
257
354
|
const treehash = <T>(
|
|
258
355
|
height: number,
|
|
259
|
-
fn: (leafIdx: number, addrOffset: number, context: Context, info: T) => Uint8Array
|
|
356
|
+
fn: TArg<(leafIdx: number, addrOffset: number, context: Context, info: T) => Uint8Array>
|
|
260
357
|
) =>
|
|
261
358
|
function treehash_i(
|
|
262
|
-
context: Context
|
|
359
|
+
context: TArg<Context>,
|
|
263
360
|
leafIdx: number,
|
|
264
361
|
idxOffset: number,
|
|
265
|
-
treeAddr: ADRS
|
|
362
|
+
treeAddr: TArg<ADRS>,
|
|
266
363
|
info: T
|
|
267
364
|
) {
|
|
365
|
+
const rawContext = context as Context;
|
|
366
|
+
const leafFn = fn as (
|
|
367
|
+
leafIdx: number,
|
|
368
|
+
addrOffset: number,
|
|
369
|
+
context: Context,
|
|
370
|
+
info: T
|
|
371
|
+
) => Uint8Array;
|
|
268
372
|
const maxIdx = (1 << height) - 1;
|
|
269
373
|
const stack = new Uint8Array(height * N);
|
|
270
374
|
const authPath = new Uint8Array(height * N);
|
|
@@ -273,7 +377,7 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
|
|
|
273
377
|
const cur0 = current.subarray(0, N);
|
|
274
378
|
const cur1 = current.subarray(N);
|
|
275
379
|
const addrOffset = idx + idxOffset;
|
|
276
|
-
cur1.set(
|
|
380
|
+
cur1.set(leafFn(leafIdx, addrOffset, rawContext, info));
|
|
277
381
|
let h = 0;
|
|
278
382
|
for (let i = idx, o = idxOffset, l = leafIdx; ; h++, i >>>= 1, l >>>= 1, o >>>= 1) {
|
|
279
383
|
if (h === height) return { root: cur1, authPath }; // Returns from here
|
|
@@ -281,7 +385,7 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
|
|
|
281
385
|
if ((i & 1) === 0 && idx < maxIdx) break;
|
|
282
386
|
setAddr({ height: h + 1, index: (i >> 1) + (o >> 1) }, treeAddr);
|
|
283
387
|
cur0.set(stack.subarray(h * N).subarray(0, N));
|
|
284
|
-
cur1.set(
|
|
388
|
+
cur1.set(rawContext.thashN(2, current, treeAddr));
|
|
285
389
|
}
|
|
286
390
|
stack.subarray(h * N).set(cur1); // stack.push(cur1)
|
|
287
391
|
}
|
|
@@ -295,41 +399,53 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
|
|
|
295
399
|
leafAddr: ADRS;
|
|
296
400
|
pkAddr: ADRS;
|
|
297
401
|
};
|
|
298
|
-
const wotsTreehash = treehash(
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
const
|
|
306
|
-
setAddr({
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
402
|
+
const wotsTreehash = treehash(
|
|
403
|
+
TREE_HEIGHT,
|
|
404
|
+
(leafIdx: number, addrOffset: number, context: TArg<Context>, info: TArg<LeafInfo>) => {
|
|
405
|
+
const rawContext = context as Context;
|
|
406
|
+
const wotsPk = new Uint8Array(WOTS_LEN * N);
|
|
407
|
+
// `keygen()` passes `leafIdx = ~0 >>> 0`, so no real XMSS leaf matches and this suppresses
|
|
408
|
+
// WOTS signature capture while still hashing every chain to its public-key endpoint.
|
|
409
|
+
const wotsKmask = addrOffset === leafIdx ? 0 : ~0 >>> 0;
|
|
410
|
+
setAddr({ keypair: addrOffset }, info.leafAddr);
|
|
411
|
+
setAddr({ keypair: addrOffset }, info.pkAddr);
|
|
412
|
+
for (let i = 0; i < WOTS_LEN; i++) {
|
|
413
|
+
const wotsK = info.wotsSteps[i] | wotsKmask;
|
|
414
|
+
const pk = wotsPk.subarray(i * N, (i + 1) * N);
|
|
415
|
+
setAddr({ chain: i, hash: 0, type: AddressType.WOTSPRF }, info.leafAddr);
|
|
416
|
+
pk.set(rawContext.PRFaddr(info.leafAddr));
|
|
417
|
+
setAddr({ type: AddressType.WOTS }, info.leafAddr);
|
|
418
|
+
for (let k = 0; ; k++) {
|
|
419
|
+
if (k === wotsK) info.wotsSig.subarray(i * N).set(pk); //wotsSig.push()
|
|
420
|
+
if (k === W - 1) break;
|
|
421
|
+
setAddr({ hash: k }, info.leafAddr);
|
|
422
|
+
pk.set(rawContext.thash1(pk, info.leafAddr));
|
|
423
|
+
}
|
|
314
424
|
}
|
|
425
|
+
return rawContext.thashN(WOTS_LEN, wotsPk, info.pkAddr);
|
|
315
426
|
}
|
|
316
|
-
|
|
317
|
-
});
|
|
427
|
+
);
|
|
318
428
|
|
|
319
|
-
const forsTreehash = treehash(
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
429
|
+
const forsTreehash = treehash(
|
|
430
|
+
A,
|
|
431
|
+
(_: number, addrOffset: number, context: TArg<Context>, forsLeafAddr: TArg<ForsLeafInfo>) => {
|
|
432
|
+
const rawContext = context as Context;
|
|
433
|
+
setAddr({ type: AddressType.FORSPRF, index: addrOffset }, forsLeafAddr);
|
|
434
|
+
const prf = rawContext.PRFaddr(forsLeafAddr);
|
|
435
|
+
setAddr({ type: AddressType.FORSTREE }, forsLeafAddr);
|
|
436
|
+
return rawContext.thash1(prf, forsLeafAddr);
|
|
437
|
+
}
|
|
438
|
+
);
|
|
325
439
|
|
|
440
|
+
// Fuse `xmss_sign` with the subtree-root computation needed by `ht_sign`, so one tree walk
|
|
441
|
+
// yields both the WOTS/auth-path signature and the root that the next hypertree layer signs.
|
|
326
442
|
const merkleSign = (
|
|
327
|
-
context: Context
|
|
328
|
-
wotsAddr: ADRS
|
|
329
|
-
treeAddr: ADRS
|
|
443
|
+
context: TArg<Context>,
|
|
444
|
+
wotsAddr: TArg<ADRS>,
|
|
445
|
+
treeAddr: TArg<ADRS>,
|
|
330
446
|
leafIdx: number,
|
|
331
|
-
prevRoot: Uint8Array = new Uint8Array(N)
|
|
332
|
-
) => {
|
|
447
|
+
prevRoot: TArg<Uint8Array> = new Uint8Array(N)
|
|
448
|
+
): TRet<{ root: Uint8Array; sigWots: Uint8Array; sigAuth: Uint8Array }> => {
|
|
333
449
|
setAddr({ type: AddressType.HASHTREE }, treeAddr);
|
|
334
450
|
// State variables
|
|
335
451
|
const info = {
|
|
@@ -343,23 +459,28 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
|
|
|
343
459
|
root,
|
|
344
460
|
sigWots: info.wotsSig.subarray(0, WOTS_LEN * N),
|
|
345
461
|
sigAuth: authPath,
|
|
346
|
-
};
|
|
462
|
+
} as TRet<{ root: Uint8Array; sigWots: Uint8Array; sigAuth: Uint8Array }>;
|
|
347
463
|
};
|
|
348
464
|
|
|
349
465
|
type ForsLeafInfo = ADRS;
|
|
350
466
|
|
|
351
467
|
const computeRoot = (
|
|
352
|
-
leaf: Uint8Array
|
|
468
|
+
leaf: TArg<Uint8Array>,
|
|
353
469
|
leafIdx: number,
|
|
354
470
|
idxOffset: number,
|
|
355
|
-
authPath: Uint8Array
|
|
471
|
+
authPath: TArg<Uint8Array>,
|
|
356
472
|
treeHeight: number,
|
|
357
|
-
context: Context
|
|
358
|
-
addr: ADRS
|
|
473
|
+
context: TArg<Context>,
|
|
474
|
+
addr: TArg<ADRS>
|
|
359
475
|
) => {
|
|
476
|
+
const rawContext = context as Context;
|
|
360
477
|
const buffer = new Uint8Array(2 * N);
|
|
361
478
|
const b0 = buffer.subarray(0, N);
|
|
362
479
|
const b1 = buffer.subarray(N, 2 * N);
|
|
480
|
+
// Algorithm 11 hashes `node || AUTH[k]` for even nodes and `AUTH[k] || node` for odd ones,
|
|
481
|
+
// so reuse one `2N` buffer and just swap which half receives the sibling at each level.
|
|
482
|
+
// `idxOffset` carries the subtree base for the shared FORS path, so `leafIdx + idxOffset`
|
|
483
|
+
// tracks the same tree-global index updates that Algorithms 11 and 17 apply to ADRS.
|
|
363
484
|
// First iter
|
|
364
485
|
if ((leafIdx & 1) !== 0) {
|
|
365
486
|
b1.set(leaf.subarray(0, N));
|
|
@@ -375,16 +496,16 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
|
|
|
375
496
|
setAddr({ height: i + 1, index: leafIdx + idxOffset }, addr);
|
|
376
497
|
const a = authPath.subarray((i + 1) * N, (i + 2) * N);
|
|
377
498
|
if ((leafIdx & 1) !== 0) {
|
|
378
|
-
b1.set(
|
|
499
|
+
b1.set(rawContext.thashN(2, buffer, addr));
|
|
379
500
|
b0.set(a);
|
|
380
501
|
} else {
|
|
381
|
-
buffer.set(
|
|
502
|
+
buffer.set(rawContext.thashN(2, buffer, addr));
|
|
382
503
|
b1.set(a);
|
|
383
504
|
}
|
|
384
505
|
}
|
|
385
506
|
// Root
|
|
386
507
|
setAddr({ height: treeHeight, index: leafIdx + idxOffset }, addr);
|
|
387
|
-
return
|
|
508
|
+
return rawContext.thashN(2, buffer, addr);
|
|
388
509
|
};
|
|
389
510
|
|
|
390
511
|
const seedCoder = splitCoder('seed', N, N, N);
|
|
@@ -393,16 +514,16 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
|
|
|
393
514
|
const forsCoder = vecCoder(splitCoder('fors', N, N * A), K);
|
|
394
515
|
const wotsCoder = vecCoder(splitCoder('wots', WOTS_LEN * N, TREE_HEIGHT * N), D);
|
|
395
516
|
const sigCoder = splitCoder('signature', N, forsCoder, wotsCoder); // random || fors || wots
|
|
396
|
-
const internal: Signer = {
|
|
397
|
-
info: { type: 'internal-slh-dsa' },
|
|
398
|
-
lengths: {
|
|
517
|
+
const internal: TRet<Signer> = Object.freeze({
|
|
518
|
+
info: Object.freeze({ type: 'internal-slh-dsa' }),
|
|
519
|
+
lengths: Object.freeze({
|
|
399
520
|
publicKey: publicCoder.bytesLen,
|
|
400
521
|
secretKey: secretCoder.bytesLen,
|
|
401
522
|
signature: sigCoder.bytesLen,
|
|
402
523
|
seed: seedCoder.bytesLen,
|
|
403
524
|
signRand: N,
|
|
404
|
-
},
|
|
405
|
-
keygen(seed?: Uint8Array) {
|
|
525
|
+
}),
|
|
526
|
+
keygen(seed?: TArg<Uint8Array>) {
|
|
406
527
|
if (seed !== undefined) abytes(seed, seedCoder.bytesLen, 'seed');
|
|
407
528
|
seed = seed === undefined ? randomBytes(seedCoder.bytesLen) : copyBytes(seed);
|
|
408
529
|
// Set SK.seed, SK.prf, and PK.seed to random n-byte
|
|
@@ -417,13 +538,16 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
|
|
|
417
538
|
const secretKey = secretCoder.encode([secretSeed, secretPRF, publicKey]);
|
|
418
539
|
context.clean();
|
|
419
540
|
cleanBytes(secretSeed, secretPRF, root, wotsAddr, topTreeAddr);
|
|
420
|
-
return {
|
|
541
|
+
return {
|
|
542
|
+
publicKey: publicKey as TRet<Uint8Array>,
|
|
543
|
+
secretKey: secretKey as TRet<Uint8Array>,
|
|
544
|
+
};
|
|
421
545
|
},
|
|
422
|
-
getPublicKey: (secretKey: Uint8Array) => {
|
|
546
|
+
getPublicKey: (secretKey: TArg<Uint8Array>): TRet<Uint8Array> => {
|
|
423
547
|
const [_skSeed, _skPRF, pk] = secretCoder.decode(secretKey);
|
|
424
|
-
return Uint8Array.from(pk)
|
|
548
|
+
return Uint8Array.from(pk) as TRet<Uint8Array>;
|
|
425
549
|
},
|
|
426
|
-
sign: (msg: Uint8Array
|
|
550
|
+
sign: (msg: TArg<Uint8Array>, sk: TArg<Uint8Array>, opts: TArg<SigOpts> = {}) => {
|
|
427
551
|
validateSigOpts(opts);
|
|
428
552
|
let { extraEntropy: random } = opts;
|
|
429
553
|
const [skSeed, skPRF, pk] = secretCoder.decode(sk); // todo: fix
|
|
@@ -494,9 +618,9 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
|
|
|
494
618
|
context.clean();
|
|
495
619
|
const SIG = sigCoder.encode([R, fors, wots]);
|
|
496
620
|
cleanBytes(R, random, treeAddr, wotsAddr, forsLeaf, forsTreeAddr, indices, roots);
|
|
497
|
-
return SIG
|
|
621
|
+
return SIG as TRet<Uint8Array>;
|
|
498
622
|
},
|
|
499
|
-
verify: (sig: Uint8Array
|
|
623
|
+
verify: (sig: TArg<Uint8Array>, msg: TArg<Uint8Array>, publicKey: TArg<Uint8Array>) => {
|
|
500
624
|
const [pkSeed, pubRoot] = publicCoder.decode(publicKey);
|
|
501
625
|
const [random, forsVec, wotsVec] = sigCoder.decode(sig);
|
|
502
626
|
const pk = publicKey;
|
|
@@ -555,74 +679,106 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
|
|
|
555
679
|
}
|
|
556
680
|
return equalBytes(root, pubRoot);
|
|
557
681
|
},
|
|
558
|
-
};
|
|
559
|
-
return {
|
|
560
|
-
info: { type: 'slh-dsa' },
|
|
682
|
+
});
|
|
683
|
+
return Object.freeze({
|
|
684
|
+
info: Object.freeze({ type: 'slh-dsa' }),
|
|
561
685
|
internal,
|
|
562
686
|
securityLevel: securityLevel,
|
|
563
687
|
lengths: internal.lengths,
|
|
564
688
|
keygen: internal.keygen,
|
|
565
689
|
getPublicKey: internal.getPublicKey,
|
|
566
|
-
sign: (msg: Uint8Array
|
|
690
|
+
sign: (msg: TArg<Uint8Array>, secretKey: TArg<Uint8Array>, opts: TArg<SigOpts> = {}) => {
|
|
567
691
|
validateSigOpts(opts);
|
|
568
692
|
const M = getMessage(msg, opts.context);
|
|
569
693
|
const res = internal.sign(M, secretKey, opts);
|
|
570
694
|
cleanBytes(M);
|
|
571
|
-
return res
|
|
695
|
+
return res as TRet<Uint8Array>;
|
|
572
696
|
},
|
|
573
|
-
verify: (
|
|
697
|
+
verify: (
|
|
698
|
+
sig: TArg<Uint8Array>,
|
|
699
|
+
msg: TArg<Uint8Array>,
|
|
700
|
+
publicKey: TArg<Uint8Array>,
|
|
701
|
+
opts: TArg<VerOpts> = {}
|
|
702
|
+
) => {
|
|
574
703
|
validateVerOpts(opts);
|
|
575
704
|
return internal.verify(sig, getMessage(msg, opts.context), publicKey);
|
|
576
705
|
},
|
|
577
|
-
prehash: (hash: CHash) => {
|
|
578
|
-
checkHash(hash, securityLevel);
|
|
579
|
-
|
|
580
|
-
|
|
706
|
+
prehash: (hash: TArg<CHash>): TRet<Signer> => {
|
|
707
|
+
checkHash(hash as CHash, securityLevel);
|
|
708
|
+
const rawHash = hash as CHash;
|
|
709
|
+
return Object.freeze({
|
|
710
|
+
info: Object.freeze({ type: 'hashslh-dsa' }),
|
|
581
711
|
lengths: internal.lengths,
|
|
582
712
|
keygen: internal.keygen,
|
|
583
713
|
getPublicKey: internal.getPublicKey,
|
|
584
|
-
sign: (msg: Uint8Array
|
|
714
|
+
sign: (msg: TArg<Uint8Array>, secretKey: TArg<Uint8Array>, opts: TArg<SigOpts> = {}) => {
|
|
585
715
|
validateSigOpts(opts);
|
|
586
|
-
const M = getMessagePrehash(
|
|
716
|
+
const M = getMessagePrehash(rawHash, msg, opts.context);
|
|
587
717
|
const res = internal.sign(M, secretKey, opts);
|
|
588
718
|
cleanBytes(M);
|
|
589
|
-
return res
|
|
719
|
+
return res as TRet<Uint8Array>;
|
|
590
720
|
},
|
|
591
|
-
verify: (
|
|
721
|
+
verify: (
|
|
722
|
+
sig: TArg<Uint8Array>,
|
|
723
|
+
msg: TArg<Uint8Array>,
|
|
724
|
+
publicKey: TArg<Uint8Array>,
|
|
725
|
+
opts: TArg<VerOpts> = {}
|
|
726
|
+
) => {
|
|
592
727
|
validateVerOpts(opts);
|
|
593
|
-
return internal.verify(sig, getMessagePrehash(
|
|
728
|
+
return internal.verify(sig, getMessagePrehash(rawHash, msg, opts.context), publicKey);
|
|
594
729
|
},
|
|
595
|
-
};
|
|
730
|
+
});
|
|
596
731
|
},
|
|
597
|
-
};
|
|
732
|
+
});
|
|
598
733
|
}
|
|
599
734
|
|
|
735
|
+
// FIPS 205 §11.1 SHAKE instantiation: this path hashes the full uncompressed address bytes,
|
|
736
|
+
// unlike the compressed 22-byte SHA2 path in §11.2.
|
|
600
737
|
const genShake =
|
|
601
|
-
(): GetContext =>
|
|
738
|
+
(): TRet<GetContext> =>
|
|
739
|
+
(opts: SphincsOpts) =>
|
|
740
|
+
(pubSeed: TArg<Uint8Array>, skSeed?: TArg<Uint8Array>): TRet<Context> => {
|
|
602
741
|
const { N } = opts;
|
|
603
742
|
const stats = { prf: 0, thash: 0, hmsg: 0, gen_message_random: 0 };
|
|
743
|
+
// §11.1 prefixes PRF/F/H/T_l with `PK.seed`, so cache that absorbed prefix once and clone it
|
|
744
|
+
// for each address-bound call instead of reabsorbing the same seed every time.
|
|
604
745
|
const h0 = shake256.create({}).update(pubSeed);
|
|
605
746
|
const h0tmp = h0.clone();
|
|
606
|
-
const thash = (blocks: number, input: Uint8Array
|
|
747
|
+
const thash = (blocks: number, input: TArg<Uint8Array>, addr: TArg<ADRS>): TRet<Uint8Array> => {
|
|
607
748
|
stats.thash++;
|
|
608
749
|
return h0
|
|
609
750
|
._cloneInto(h0tmp)
|
|
610
751
|
.update(addr)
|
|
611
752
|
.update(input.subarray(0, blocks * N))
|
|
612
|
-
.xof(N)
|
|
753
|
+
.xof(N) as TRet<Uint8Array>;
|
|
613
754
|
};
|
|
614
755
|
return {
|
|
615
|
-
PRFaddr: (addr: ADRS) => {
|
|
756
|
+
PRFaddr: (addr: TArg<ADRS>): TRet<Uint8Array> => {
|
|
616
757
|
if (!skSeed) throw new Error('no sk seed');
|
|
617
758
|
stats.prf++;
|
|
618
759
|
const res = h0._cloneInto(h0tmp).update(addr).update(skSeed).xof(N);
|
|
619
|
-
return res
|
|
760
|
+
return res as TRet<Uint8Array>;
|
|
620
761
|
},
|
|
621
|
-
PRFmsg: (
|
|
762
|
+
PRFmsg: (
|
|
763
|
+
skPRF: TArg<Uint8Array>,
|
|
764
|
+
random: TArg<Uint8Array>,
|
|
765
|
+
msg: TArg<Uint8Array>
|
|
766
|
+
): TRet<Uint8Array> => {
|
|
622
767
|
stats.gen_message_random++;
|
|
623
|
-
return shake256
|
|
768
|
+
return shake256
|
|
769
|
+
.create({})
|
|
770
|
+
.update(skPRF)
|
|
771
|
+
.update(random)
|
|
772
|
+
.update(msg)
|
|
773
|
+
.digest()
|
|
774
|
+
.subarray(0, N) as TRet<Uint8Array>;
|
|
624
775
|
},
|
|
625
|
-
Hmsg: (
|
|
776
|
+
Hmsg: (
|
|
777
|
+
R: TArg<Uint8Array>,
|
|
778
|
+
pk: TArg<Uint8Array>,
|
|
779
|
+
m: TArg<Uint8Array>,
|
|
780
|
+
outLen
|
|
781
|
+
): TRet<Uint8Array> => {
|
|
626
782
|
stats.hmsg++;
|
|
627
783
|
return shake256.create({}).update(R.subarray(0, N)).update(pk).update(m).xof(outLen);
|
|
628
784
|
},
|
|
@@ -633,29 +789,62 @@ const genShake =
|
|
|
633
789
|
h0tmp.destroy();
|
|
634
790
|
//console.log(stats);
|
|
635
791
|
},
|
|
636
|
-
}
|
|
792
|
+
} as TRet<Context>;
|
|
637
793
|
};
|
|
638
794
|
|
|
639
|
-
const SHAKE_SIMPLE = { getContext: genShake() };
|
|
640
|
-
|
|
641
|
-
/**
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
export const
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
795
|
+
const SHAKE_SIMPLE = /* @__PURE__ */ (() => ({ getContext: genShake() }))();
|
|
796
|
+
|
|
797
|
+
/**
|
|
798
|
+
* SLH-DSA-SHAKE-128f: Table 2 row `n=16, h=66, d=22, h'=3, a=6, k=33, lg w=4, m=34`;
|
|
799
|
+
* lengths `publicKey=32`, `secretKey=64`, `signature=17088`, `seed=48`, `signRand=16`.
|
|
800
|
+
* Also exposes `.prehash(...)`.
|
|
801
|
+
*/
|
|
802
|
+
export const slh_dsa_shake_128f: TRet<SphincsSigner> = /* @__PURE__ */ (() =>
|
|
803
|
+
gen(PARAMS['128f'], SHAKE_SIMPLE))();
|
|
804
|
+
/**
|
|
805
|
+
* SLH-DSA-SHAKE-128s: Table 2 row `n=16, h=63, d=7, h'=9, a=12, k=14, lg w=4, m=30`;
|
|
806
|
+
* lengths `publicKey=32`, `secretKey=64`, `signature=7856`, `seed=48`, `signRand=16`.
|
|
807
|
+
* Also exposes `.prehash(...)`.
|
|
808
|
+
*/
|
|
809
|
+
export const slh_dsa_shake_128s: TRet<SphincsSigner> = /* @__PURE__ */ (() =>
|
|
810
|
+
gen(PARAMS['128s'], SHAKE_SIMPLE))();
|
|
811
|
+
/**
|
|
812
|
+
* SLH-DSA-SHAKE-192f: Table 2 row `n=24, h=66, d=22, h'=3, a=8, k=33, lg w=4, m=42`;
|
|
813
|
+
* lengths `publicKey=48`, `secretKey=96`, `signature=35664`, `seed=72`, `signRand=24`.
|
|
814
|
+
* Also exposes `.prehash(...)`.
|
|
815
|
+
*/
|
|
816
|
+
export const slh_dsa_shake_192f: TRet<SphincsSigner> = /* @__PURE__ */ (() =>
|
|
817
|
+
gen(PARAMS['192f'], SHAKE_SIMPLE))();
|
|
818
|
+
/**
|
|
819
|
+
* SLH-DSA-SHAKE-192s: Table 2 row `n=24, h=63, d=7, h'=9, a=14, k=17, lg w=4, m=39`;
|
|
820
|
+
* lengths `publicKey=48`, `secretKey=96`, `signature=16224`, `seed=72`, `signRand=24`.
|
|
821
|
+
* Also exposes `.prehash(...)`.
|
|
822
|
+
*/
|
|
823
|
+
export const slh_dsa_shake_192s: TRet<SphincsSigner> = /* @__PURE__ */ (() =>
|
|
824
|
+
gen(PARAMS['192s'], SHAKE_SIMPLE))();
|
|
825
|
+
/**
|
|
826
|
+
* SLH-DSA-SHAKE-256f: Table 2 row `n=32, h=68, d=17, h'=4, a=9, k=35, lg w=4, m=49`;
|
|
827
|
+
* lengths `publicKey=64`, `secretKey=128`, `signature=49856`, `seed=96`, `signRand=32`.
|
|
828
|
+
* Also exposes `.prehash(...)`.
|
|
829
|
+
*/
|
|
830
|
+
export const slh_dsa_shake_256f: TRet<SphincsSigner> = /* @__PURE__ */ (() =>
|
|
831
|
+
gen(PARAMS['256f'], SHAKE_SIMPLE))();
|
|
832
|
+
/**
|
|
833
|
+
* SLH-DSA-SHAKE-256s: Table 2 row `n=32, h=64, d=8, h'=8, a=14, k=22, lg w=4, m=47`;
|
|
834
|
+
* lengths `publicKey=64`, `secretKey=128`, `signature=29792`, `seed=96`, `signRand=32`.
|
|
835
|
+
* Also exposes `.prehash(...)`.
|
|
836
|
+
*/
|
|
837
|
+
export const slh_dsa_shake_256s: TRet<SphincsSigner> = /* @__PURE__ */ (() =>
|
|
838
|
+
gen(PARAMS['256s'], SHAKE_SIMPLE))();
|
|
653
839
|
|
|
654
840
|
type ShaType = typeof sha256 | typeof sha512;
|
|
841
|
+
// FIPS 205 §11.2 SHA2 instantiation. The `h0` / `h1` split is intentional:
|
|
842
|
+
// category-1 keeps everything on SHA-256, while category-3/5 keep `PRFaddr` / `thash1`
|
|
843
|
+
// on SHA-256 but switch `PRFmsg`, `Hmsg`, and multi-block `thashN` to SHA-512.
|
|
655
844
|
const genSha =
|
|
656
|
-
(h0: ShaType, h1: ShaType): GetContext =>
|
|
845
|
+
(h0: ShaType, h1: ShaType): TRet<GetContext> =>
|
|
657
846
|
(opts) =>
|
|
658
|
-
(pub_seed
|
|
847
|
+
(pub_seed: TArg<Uint8Array>, sk_seed?: TArg<Uint8Array>): TRet<Context> => {
|
|
659
848
|
const { N } = opts;
|
|
660
849
|
/*
|
|
661
850
|
Perf debug stats, how much hashes we call?
|
|
@@ -667,6 +856,8 @@ const genSha =
|
|
|
667
856
|
|
|
668
857
|
const counterB = new Uint8Array(4);
|
|
669
858
|
const counterV = createView(counterB);
|
|
859
|
+
// §11.2 prefixes SHA2 PRF/F/H/T_l with `PK.seed || toByte(0, blockLen-N)`, so cache the
|
|
860
|
+
// zero-padded seed block once for the SHA-256 lane and once for the SHA-512 lane.
|
|
670
861
|
const h0ps = h0
|
|
671
862
|
.create()
|
|
672
863
|
.update(pub_seed)
|
|
@@ -680,7 +871,10 @@ const genSha =
|
|
|
680
871
|
const h1tmp = h1ps.clone();
|
|
681
872
|
|
|
682
873
|
// https://www.rfc-editor.org/rfc/rfc8017.html#appendix-B.2.1
|
|
683
|
-
|
|
874
|
+
// This local helper is intentionally stricter than generic MGF1 reuse: current SLH-DSA callers
|
|
875
|
+
// only request tiny `m`-byte outputs, but the guard below rejects `length > 2^32` instead of
|
|
876
|
+
// RFC 8017's broader `maskLen > 2^32 * hLen` bound.
|
|
877
|
+
function mgf1(seed: TArg<Uint8Array>, length: number, hash: ShaType): TRet<Uint8Array> {
|
|
684
878
|
stats.mgf1++;
|
|
685
879
|
const out = new Uint8Array(Math.ceil(length / hash.outputLen) * hash.outputLen);
|
|
686
880
|
// NOT 2^32-1
|
|
@@ -691,22 +885,22 @@ const genSha =
|
|
|
691
885
|
o = o.subarray(hash.outputLen);
|
|
692
886
|
}
|
|
693
887
|
cleanBytes(out.subarray(length));
|
|
694
|
-
return out.subarray(0, length)
|
|
888
|
+
return out.subarray(0, length) as TRet<Uint8Array>;
|
|
695
889
|
}
|
|
696
890
|
|
|
697
891
|
const thash =
|
|
698
892
|
(_: ShaType, h: typeof h0ps, hTmp: typeof h0ps) =>
|
|
699
|
-
(blocks: number, input: Uint8Array
|
|
893
|
+
(blocks: number, input: TArg<Uint8Array>, addr: TArg<ADRS>): TRet<Uint8Array> => {
|
|
700
894
|
stats.thash++;
|
|
701
895
|
const d = h
|
|
702
896
|
._cloneInto(hTmp as any)
|
|
703
897
|
.update(addr)
|
|
704
898
|
.update(input.subarray(0, blocks * N))
|
|
705
899
|
.digest();
|
|
706
|
-
return d.subarray(0, N)
|
|
900
|
+
return d.subarray(0, N) as TRet<Uint8Array>;
|
|
707
901
|
};
|
|
708
902
|
return {
|
|
709
|
-
PRFaddr: (addr: ADRS) => {
|
|
903
|
+
PRFaddr: (addr: TArg<ADRS>): TRet<Uint8Array> => {
|
|
710
904
|
if (!sk_seed) throw new Error('No sk seed');
|
|
711
905
|
stats.prf++;
|
|
712
906
|
const res = h0ps
|
|
@@ -715,13 +909,27 @@ const genSha =
|
|
|
715
909
|
.update(sk_seed)
|
|
716
910
|
.digest()
|
|
717
911
|
.subarray(0, N);
|
|
718
|
-
return res
|
|
912
|
+
return res as TRet<Uint8Array>;
|
|
719
913
|
},
|
|
720
|
-
PRFmsg: (
|
|
914
|
+
PRFmsg: (
|
|
915
|
+
skPRF: TArg<Uint8Array>,
|
|
916
|
+
random: TArg<Uint8Array>,
|
|
917
|
+
msg: TArg<Uint8Array>
|
|
918
|
+
): TRet<Uint8Array> => {
|
|
721
919
|
stats.gen_message_random++;
|
|
722
|
-
return hmac
|
|
920
|
+
return hmac
|
|
921
|
+
.create(h1, skPRF)
|
|
922
|
+
.update(random)
|
|
923
|
+
.update(msg)
|
|
924
|
+
.digest()
|
|
925
|
+
.subarray(0, N) as TRet<Uint8Array>;
|
|
723
926
|
},
|
|
724
|
-
Hmsg: (
|
|
927
|
+
Hmsg: (
|
|
928
|
+
R: TArg<Uint8Array>,
|
|
929
|
+
pk: TArg<Uint8Array>,
|
|
930
|
+
m: TArg<Uint8Array>,
|
|
931
|
+
outLen
|
|
932
|
+
): TRet<Uint8Array> => {
|
|
725
933
|
stats.hmsg++;
|
|
726
934
|
const seed = concatBytes(
|
|
727
935
|
R.subarray(0, N),
|
|
@@ -739,27 +947,57 @@ const genSha =
|
|
|
739
947
|
h1tmp.destroy();
|
|
740
948
|
//console.log(stats);
|
|
741
949
|
},
|
|
742
|
-
}
|
|
950
|
+
} as TRet<Context>;
|
|
743
951
|
};
|
|
744
952
|
|
|
745
|
-
const SHA256_SIMPLE = {
|
|
953
|
+
const SHA256_SIMPLE = /* @__PURE__ */ (() => ({
|
|
746
954
|
isCompressed: true,
|
|
747
955
|
getContext: genSha(sha256, sha256),
|
|
748
|
-
};
|
|
749
|
-
const SHA512_SIMPLE = {
|
|
956
|
+
}))();
|
|
957
|
+
const SHA512_SIMPLE = /* @__PURE__ */ (() => ({
|
|
750
958
|
isCompressed: true,
|
|
751
959
|
getContext: genSha(sha256, sha512),
|
|
752
|
-
};
|
|
960
|
+
}))();
|
|
753
961
|
|
|
754
|
-
/**
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
export const
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
962
|
+
/**
|
|
963
|
+
* SLH-DSA-SHA2-128f: Table 2 row `n=16, h=66, d=22, h'=3, a=6, k=33, lg w=4, m=34`;
|
|
964
|
+
* lengths `publicKey=32`, `secretKey=64`, `signature=17088`, `seed=48`, `signRand=16`.
|
|
965
|
+
* Also exposes `.prehash(...)`.
|
|
966
|
+
*/
|
|
967
|
+
export const slh_dsa_sha2_128f: TRet<SphincsSigner> = /* @__PURE__ */ (() =>
|
|
968
|
+
gen(PARAMS['128f'], SHA256_SIMPLE))();
|
|
969
|
+
/**
|
|
970
|
+
* SLH-DSA-SHA2-128s: Table 2 row `n=16, h=63, d=7, h'=9, a=12, k=14, lg w=4, m=30`;
|
|
971
|
+
* lengths `publicKey=32`, `secretKey=64`, `signature=7856`, `seed=48`, `signRand=16`.
|
|
972
|
+
* Also exposes `.prehash(...)`.
|
|
973
|
+
*/
|
|
974
|
+
export const slh_dsa_sha2_128s: TRet<SphincsSigner> = /* @__PURE__ */ (() =>
|
|
975
|
+
gen(PARAMS['128s'], SHA256_SIMPLE))();
|
|
976
|
+
/**
|
|
977
|
+
* SLH-DSA-SHA2-192f: Table 2 row `n=24, h=66, d=22, h'=3, a=8, k=33, lg w=4, m=42`;
|
|
978
|
+
* lengths `publicKey=48`, `secretKey=96`, `signature=35664`, `seed=72`, `signRand=24`.
|
|
979
|
+
* Also exposes `.prehash(...)`.
|
|
980
|
+
*/
|
|
981
|
+
export const slh_dsa_sha2_192f: TRet<SphincsSigner> = /* @__PURE__ */ (() =>
|
|
982
|
+
gen(PARAMS['192f'], SHA512_SIMPLE))();
|
|
983
|
+
/**
|
|
984
|
+
* SLH-DSA-SHA2-192s: Table 2 row `n=24, h=63, d=7, h'=9, a=14, k=17, lg w=4, m=39`;
|
|
985
|
+
* lengths `publicKey=48`, `secretKey=96`, `signature=16224`, `seed=72`, `signRand=24`.
|
|
986
|
+
* Also exposes `.prehash(...)`.
|
|
987
|
+
*/
|
|
988
|
+
export const slh_dsa_sha2_192s: TRet<SphincsSigner> = /* @__PURE__ */ (() =>
|
|
989
|
+
gen(PARAMS['192s'], SHA512_SIMPLE))();
|
|
990
|
+
/**
|
|
991
|
+
* SLH-DSA-SHA2-256f: Table 2 row `n=32, h=68, d=17, h'=4, a=9, k=35, lg w=4, m=49`;
|
|
992
|
+
* lengths `publicKey=64`, `secretKey=128`, `signature=49856`, `seed=96`, `signRand=32`.
|
|
993
|
+
* Also exposes `.prehash(...)`.
|
|
994
|
+
*/
|
|
995
|
+
export const slh_dsa_sha2_256f: TRet<SphincsSigner> = /* @__PURE__ */ (() =>
|
|
996
|
+
gen(PARAMS['256f'], SHA512_SIMPLE))();
|
|
997
|
+
/**
|
|
998
|
+
* SLH-DSA-SHA2-256s: Table 2 row `n=32, h=64, d=8, h'=8, a=14, k=22, lg w=4, m=47`;
|
|
999
|
+
* lengths `publicKey=64`, `secretKey=128`, `signature=29792`, `seed=96`, `signRand=32`.
|
|
1000
|
+
* Also exposes `.prehash(...)`.
|
|
1001
|
+
*/
|
|
1002
|
+
export const slh_dsa_sha2_256s: TRet<SphincsSigner> = /* @__PURE__ */ (() =>
|
|
1003
|
+
gen(PARAMS['256s'], SHA512_SIMPLE))();
|