@noble/post-quantum 0.6.0 → 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 +45 -32
- package/_crystals.d.ts +17 -15
- package/_crystals.d.ts.map +1 -1
- package/_crystals.js +13 -3
- package/_crystals.js.map +1 -1
- package/falcon.d.ts +7 -7
- package/falcon.d.ts.map +1 -1
- package/falcon.js +27 -20
- package/falcon.js.map +1 -1
- package/hybrid.d.ts +23 -23
- package/hybrid.d.ts.map +1 -1
- package/hybrid.js +37 -28
- package/hybrid.js.map +1 -1
- package/index.js +8 -0
- package/index.js.map +1 -1
- package/ml-dsa.d.ts +7 -7
- package/ml-dsa.d.ts.map +1 -1
- package/ml-dsa.js +54 -28
- package/ml-dsa.js.map +1 -1
- package/ml-kem.d.ts +4 -4
- package/ml-kem.d.ts.map +1 -1
- package/ml-kem.js +43 -26
- package/ml-kem.js.map +1 -1
- package/package.json +5 -5
- package/slh-dsa.d.ts +21 -21
- package/slh-dsa.d.ts.map +1 -1
- package/slh-dsa.js +57 -35
- package/slh-dsa.js.map +1 -1
- package/src/_crystals.ts +44 -27
- package/src/falcon.ts +150 -117
- package/src/hybrid.ts +145 -96
- package/src/index.ts +8 -0
- package/src/ml-dsa.ts +124 -69
- package/src/ml-kem.ts +114 -85
- package/src/slh-dsa.ts +211 -132
- package/src/utils.ts +174 -34
- package/utils.d.ts +81 -22
- package/utils.d.ts.map +1 -1
- package/utils.js +30 -8
- package/utils.js.map +1 -1
package/src/hybrid.ts
CHANGED
|
@@ -101,6 +101,8 @@ import {
|
|
|
101
101
|
type CryptoKeys,
|
|
102
102
|
type KEM,
|
|
103
103
|
type Signer,
|
|
104
|
+
type TArg,
|
|
105
|
+
type TRet,
|
|
104
106
|
} from './utils.ts';
|
|
105
107
|
|
|
106
108
|
type CurveAll = ECDSA | EdDSA | MontgomeryECDH;
|
|
@@ -126,18 +128,26 @@ function ecKeygen(curve: CurveAll, allowZeroKey: boolean = false) {
|
|
|
126
128
|
// Unlike noble-curves' seeded Weierstrass keygen, this path removes the post-reduction +1.
|
|
127
129
|
// That is enough to match exact reduced-vector bytes, but an all-zero seed still reduces to
|
|
128
130
|
// scalar 0 here and getPublicKey(secretKey) throws instead of "allowing zero".
|
|
129
|
-
keygen = (seed: Uint8Array = randomBytes(lengths.seed)) => {
|
|
131
|
+
keygen = (seed: TArg<Uint8Array> = randomBytes(lengths.seed)) => {
|
|
130
132
|
abytes(seed, lengths.seed!, 'seed');
|
|
131
133
|
const seedScalar = Fn.isLE ? bytesToNumberLE(seed) : bytesToNumberBE(seed);
|
|
132
134
|
// Reduce directly into [0, ORDER); scalar 0 still stays invalid.
|
|
133
135
|
const secretKey = Fn.toBytes(Fn.create(seedScalar));
|
|
134
|
-
return {
|
|
136
|
+
return {
|
|
137
|
+
secretKey: secretKey as TRet<Uint8Array>,
|
|
138
|
+
publicKey: curve.getPublicKey(secretKey) as TRet<Uint8Array>,
|
|
139
|
+
};
|
|
135
140
|
};
|
|
136
141
|
}
|
|
137
142
|
return {
|
|
138
143
|
lengths: { secretKey: lengths.secretKey, publicKey: lengths.publicKey, seed: lengths.seed },
|
|
139
|
-
keygen
|
|
140
|
-
|
|
144
|
+
keygen: (seed?: TArg<Uint8Array>) =>
|
|
145
|
+
keygen(seed) as TRet<{
|
|
146
|
+
secretKey: Uint8Array;
|
|
147
|
+
publicKey: Uint8Array;
|
|
148
|
+
}>,
|
|
149
|
+
getPublicKey: (secretKey: TArg<Uint8Array>) =>
|
|
150
|
+
curve.getPublicKey(secretKey) as TRet<Uint8Array>,
|
|
141
151
|
};
|
|
142
152
|
}
|
|
143
153
|
|
|
@@ -164,14 +174,17 @@ function ecKeygen(curve: CurveAll, allowZeroKey: boolean = false) {
|
|
|
164
174
|
* const publicKeyLen = kem.lengths.publicKey;
|
|
165
175
|
* ```
|
|
166
176
|
*/
|
|
167
|
-
export function ecdhKem(curve: CurveECDH, allowZeroKey: boolean = false): KEM {
|
|
177
|
+
export function ecdhKem(curve: CurveECDH, allowZeroKey: boolean = false): TRet<KEM> {
|
|
168
178
|
const kg = ecKeygen(curve, allowZeroKey);
|
|
169
179
|
if (!curve.getSharedSecret) throw new Error('wrong curve'); // ed25519 doesn't have one!
|
|
170
180
|
return {
|
|
171
181
|
lengths: { ...kg.lengths, msg: kg.lengths.seed, cipherText: kg.lengths.publicKey },
|
|
172
182
|
keygen: kg.keygen,
|
|
173
183
|
getPublicKey: kg.getPublicKey,
|
|
174
|
-
encapsulate(
|
|
184
|
+
encapsulate(
|
|
185
|
+
publicKey: TArg<Uint8Array>,
|
|
186
|
+
rand: TArg<Uint8Array> = randomBytes(curve.lengths.seed)
|
|
187
|
+
) {
|
|
175
188
|
// Some curve.keygen(seed) paths reuse the provided seed buffer as secretKey; detach caller
|
|
176
189
|
// randomness first so cleanBytes() only wipes wrapper-owned material.
|
|
177
190
|
const seed = copyBytes(rand);
|
|
@@ -179,7 +192,7 @@ export function ecdhKem(curve: CurveECDH, allowZeroKey: boolean = false): KEM {
|
|
|
179
192
|
try {
|
|
180
193
|
ek = this.keygen(seed).secretKey;
|
|
181
194
|
const sharedSecret = this.decapsulate(publicKey, ek);
|
|
182
|
-
const cipherText = curve.getPublicKey(ek)
|
|
195
|
+
const cipherText = curve.getPublicKey(ek) as TRet<Uint8Array>;
|
|
183
196
|
return { sharedSecret, cipherText };
|
|
184
197
|
} finally {
|
|
185
198
|
// Invalid peer public keys can make decapsulation throw; wipe both the detached seed and
|
|
@@ -188,9 +201,9 @@ export function ecdhKem(curve: CurveECDH, allowZeroKey: boolean = false): KEM {
|
|
|
188
201
|
if (ek) cleanBytes(ek);
|
|
189
202
|
}
|
|
190
203
|
},
|
|
191
|
-
decapsulate(cipherText: Uint8Array
|
|
204
|
+
decapsulate(cipherText: TArg<Uint8Array>, secretKey: TArg<Uint8Array>) {
|
|
192
205
|
const res = curve.getSharedSecret(secretKey, cipherText);
|
|
193
|
-
return curve.lengths.publicKeyHasPrefix ? res.subarray(1) : res
|
|
206
|
+
return (curve.lengths.publicKeyHasPrefix ? res.subarray(1) : res) as TRet<Uint8Array>;
|
|
194
207
|
},
|
|
195
208
|
};
|
|
196
209
|
}
|
|
@@ -216,7 +229,7 @@ export function ecdhKem(curve: CurveECDH, allowZeroKey: boolean = false): KEM {
|
|
|
216
229
|
* const sigLen = signer.lengths.signature;
|
|
217
230
|
* ```
|
|
218
231
|
*/
|
|
219
|
-
export function ecSigner(curve: CurveSign, allowZeroKey: boolean = false): Signer {
|
|
232
|
+
export function ecSigner(curve: CurveSign, allowZeroKey: boolean = false): TRet<Signer> {
|
|
220
233
|
const kg = ecKeygen(curve, allowZeroKey);
|
|
221
234
|
if (!curve.sign || !curve.verify) throw new Error('wrong curve'); // ed25519 doesn't have one!
|
|
222
235
|
return {
|
|
@@ -234,7 +247,7 @@ export function ecSigner(curve: CurveSign, allowZeroKey: boolean = false): Signe
|
|
|
234
247
|
);
|
|
235
248
|
if (opts.context !== undefined)
|
|
236
249
|
throw new Error('ecSigner does not support context; use the underlying curve directly');
|
|
237
|
-
return curve.sign(message, secretKey)
|
|
250
|
+
return curve.sign(message, secretKey) as TRet<Uint8Array>;
|
|
238
251
|
},
|
|
239
252
|
/** Verify one wrapped curve signature.
|
|
240
253
|
* Returns the wrapped curve's `verify()` result for well-formed inputs. Throws on unsupported
|
|
@@ -264,7 +277,7 @@ function splitLengths<K extends string, T extends { lengths: Partial<Record<K, n
|
|
|
264
277
|
}
|
|
265
278
|
|
|
266
279
|
/** Seed-expansion callback used by the hybrid combiners. */
|
|
267
|
-
export type ExpandSeed = (seed: Uint8Array
|
|
280
|
+
export type ExpandSeed = (seed: TArg<Uint8Array>, len: number) => TRet<Uint8Array>;
|
|
268
281
|
type XOF = CHashXOF<any, { dkLen: number }>;
|
|
269
282
|
|
|
270
283
|
// It is XOF for most cases, but can be more complex!
|
|
@@ -282,30 +295,36 @@ type XOF = CHashXOF<any, { dkLen: number }>;
|
|
|
282
295
|
* const seed = expandSeed(new Uint8Array([1]), 4);
|
|
283
296
|
* ```
|
|
284
297
|
*/
|
|
285
|
-
export function expandSeedXof(xof: XOF): ExpandSeed {
|
|
298
|
+
export function expandSeedXof(xof: TArg<XOF>): TRet<ExpandSeed> {
|
|
286
299
|
// Forward the caller seed directly: XOFs are expected to treat inputs as read-only, and this
|
|
287
300
|
// adapter only translates the requested byte length into the hash API's `dkLen` option.
|
|
288
|
-
return (seed: Uint8Array
|
|
301
|
+
return ((seed: TArg<Uint8Array>, seedLen: number): TRet<Uint8Array> =>
|
|
302
|
+
(xof as XOF)(seed, { dkLen: seedLen }) as TRet<Uint8Array>) as TRet<ExpandSeed>;
|
|
289
303
|
}
|
|
290
304
|
|
|
291
305
|
/** Combines public keys, ciphertexts, and shared secrets into one shared secret. */
|
|
292
306
|
export type Combiner = (
|
|
293
|
-
publicKeys: Uint8Array[]
|
|
294
|
-
cipherTexts: Uint8Array[]
|
|
295
|
-
sharedSecrets: Uint8Array[]
|
|
296
|
-
) => Uint8Array
|
|
307
|
+
publicKeys: TArg<Uint8Array[]>,
|
|
308
|
+
cipherTexts: TArg<Uint8Array[]>,
|
|
309
|
+
sharedSecrets: TArg<Uint8Array[]>
|
|
310
|
+
) => TRet<Uint8Array>;
|
|
297
311
|
|
|
298
312
|
function combineKeys(
|
|
299
313
|
realSeedLen: number | undefined, // how much bytes expandSeed expects
|
|
300
|
-
|
|
301
|
-
...
|
|
314
|
+
expandSeed_: TArg<ExpandSeed>,
|
|
315
|
+
...ck_: TArg<CryptoKeys[]>
|
|
302
316
|
) {
|
|
317
|
+
const expandSeed = expandSeed_ as ExpandSeed;
|
|
318
|
+
const ck = ck_ as CryptoKeys[];
|
|
303
319
|
const seedCoder = splitLengths(ck, 'seed');
|
|
304
320
|
const pkCoder = splitLengths(ck, 'publicKey');
|
|
305
321
|
// Allows to use identity functions for combiner/expandSeed
|
|
306
322
|
if (realSeedLen === undefined) realSeedLen = seedCoder.bytesLen;
|
|
307
323
|
anumber(realSeedLen);
|
|
308
|
-
function expandDecapsulationKey(seed: Uint8Array) {
|
|
324
|
+
function expandDecapsulationKey(seed: TArg<Uint8Array>): TRet<{
|
|
325
|
+
secretKey: Uint8Array[];
|
|
326
|
+
publicKey: Uint8Array[];
|
|
327
|
+
}> {
|
|
309
328
|
abytes(seed, realSeedLen!);
|
|
310
329
|
const expandedRaw = expandSeed(seed, seedCoder.bytesLen);
|
|
311
330
|
// Identity/subarray expanders can hand back caller-owned seed storage. Detach those outputs so
|
|
@@ -328,7 +347,10 @@ function combineKeys(
|
|
|
328
347
|
publicKey.push(keys.publicKey);
|
|
329
348
|
}
|
|
330
349
|
ok = true;
|
|
331
|
-
return { secretKey, publicKey }
|
|
350
|
+
return { secretKey, publicKey } as TRet<{
|
|
351
|
+
secretKey: Uint8Array[];
|
|
352
|
+
publicKey: Uint8Array[];
|
|
353
|
+
}>;
|
|
332
354
|
} finally {
|
|
333
355
|
// Child keygen() can throw after deriving only a prefix of the composite key schedule. Keep
|
|
334
356
|
// the exported copies on success, but wipe all temporary and partially built secret material
|
|
@@ -339,16 +361,16 @@ function combineKeys(
|
|
|
339
361
|
}
|
|
340
362
|
return {
|
|
341
363
|
info: { lengths: { seed: realSeedLen, publicKey: pkCoder.bytesLen, secretKey: realSeedLen } },
|
|
342
|
-
getPublicKey(secretKey: Uint8Array) {
|
|
364
|
+
getPublicKey(secretKey: TArg<Uint8Array>) {
|
|
343
365
|
// Composite secret keys are root seeds, so public-key derivation reruns key expansion from
|
|
344
366
|
// that seed instead of decoding a packed child-secret-key structure.
|
|
345
|
-
return this.keygen(secretKey).publicKey
|
|
367
|
+
return this.keygen(secretKey).publicKey as TRet<Uint8Array>;
|
|
346
368
|
},
|
|
347
|
-
keygen(seed: Uint8Array = randomBytes(realSeedLen)) {
|
|
369
|
+
keygen(seed: TArg<Uint8Array> = randomBytes(realSeedLen)) {
|
|
348
370
|
const { publicKey: pk, secretKey } = expandDecapsulationKey(seed);
|
|
349
371
|
try {
|
|
350
|
-
const publicKey = pkCoder.encode(pk)
|
|
351
|
-
return { secretKey: seed
|
|
372
|
+
const publicKey = pkCoder.encode(pk) as TRet<Uint8Array>;
|
|
373
|
+
return { secretKey: seed as TRet<Uint8Array>, publicKey };
|
|
352
374
|
} finally {
|
|
353
375
|
cleanBytes(pk);
|
|
354
376
|
// The exported secretKey is the caller/root seed itself; child secret keys are internal
|
|
@@ -390,41 +412,47 @@ function combineKeys(
|
|
|
390
412
|
export function combineKEMS(
|
|
391
413
|
realSeedLen: number | undefined, // how much bytes expandSeed expects
|
|
392
414
|
realMsgLen: number | undefined, // how much bytes combiner returns
|
|
393
|
-
expandSeed: ExpandSeed
|
|
394
|
-
combiner: Combiner
|
|
395
|
-
...kems: KEM[]
|
|
396
|
-
): KEM {
|
|
397
|
-
const
|
|
398
|
-
const
|
|
399
|
-
const
|
|
400
|
-
const
|
|
415
|
+
expandSeed: TArg<ExpandSeed>,
|
|
416
|
+
combiner: TArg<Combiner>,
|
|
417
|
+
...kems: TArg<KEM[]>
|
|
418
|
+
): TRet<KEM> {
|
|
419
|
+
const rawCombiner = combiner as Combiner;
|
|
420
|
+
const rawKems = kems as KEM[];
|
|
421
|
+
const keys = combineKeys(realSeedLen, expandSeed, ...rawKems);
|
|
422
|
+
const ctCoder = splitLengths(rawKems, 'cipherText');
|
|
423
|
+
const pkCoder = splitLengths(rawKems, 'publicKey');
|
|
424
|
+
const msgCoder = splitLengths(rawKems, 'msg');
|
|
401
425
|
if (realMsgLen === undefined) realMsgLen = msgCoder.bytesLen;
|
|
402
426
|
anumber(realMsgLen);
|
|
403
|
-
|
|
404
|
-
lengths
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
427
|
+
const lengths = Object.freeze({
|
|
428
|
+
...keys.info.lengths,
|
|
429
|
+
msg: realMsgLen,
|
|
430
|
+
msgRand: msgCoder.bytesLen,
|
|
431
|
+
cipherText: ctCoder.bytesLen,
|
|
432
|
+
});
|
|
433
|
+
return Object.freeze({
|
|
434
|
+
lengths,
|
|
410
435
|
getPublicKey: keys.getPublicKey,
|
|
411
436
|
keygen: keys.keygen,
|
|
412
|
-
encapsulate(
|
|
437
|
+
encapsulate(
|
|
438
|
+
pk: TArg<Uint8Array>,
|
|
439
|
+
randomness: TArg<Uint8Array> = randomBytes(msgCoder.bytesLen)
|
|
440
|
+
) {
|
|
413
441
|
const pks = pkCoder.decode(pk);
|
|
414
442
|
const rand = msgCoder.decode(randomness);
|
|
415
443
|
const sharedSecret: Uint8Array[] = [];
|
|
416
444
|
const cipherText: Uint8Array[] = [];
|
|
417
445
|
try {
|
|
418
|
-
for (let i = 0; i <
|
|
419
|
-
const enc =
|
|
446
|
+
for (let i = 0; i < rawKems.length; i++) {
|
|
447
|
+
const enc = rawKems[i].encapsulate(pks[i], rand[i]);
|
|
420
448
|
sharedSecret.push(enc.sharedSecret);
|
|
421
449
|
cipherText.push(enc.cipherText);
|
|
422
450
|
}
|
|
423
451
|
return {
|
|
424
452
|
// Detach the combiner result before cleanup: a caller-provided combiner may alias one of
|
|
425
453
|
// the child sharedSecret buffers, and those child buffers are zeroized immediately below.
|
|
426
|
-
sharedSecret: copyBytes(
|
|
427
|
-
cipherText: ctCoder.encode(cipherText)
|
|
454
|
+
sharedSecret: copyBytes(rawCombiner(pks, cipherText, sharedSecret)),
|
|
455
|
+
cipherText: ctCoder.encode(cipherText) as TRet<Uint8Array>,
|
|
428
456
|
};
|
|
429
457
|
} finally {
|
|
430
458
|
// Child encapsulation or combiner failures can happen after some components already
|
|
@@ -432,21 +460,21 @@ export function combineKEMS(
|
|
|
432
460
|
cleanBytes(sharedSecret, cipherText);
|
|
433
461
|
}
|
|
434
462
|
},
|
|
435
|
-
decapsulate(ct: Uint8Array
|
|
463
|
+
decapsulate(ct: TArg<Uint8Array>, seed: TArg<Uint8Array>) {
|
|
436
464
|
const cts = ctCoder.decode(ct);
|
|
437
465
|
const { publicKey, secretKey } = keys.expandDecapsulationKey(seed);
|
|
438
|
-
const sharedSecret =
|
|
466
|
+
const sharedSecret = rawKems.map((i, j) => i.decapsulate(cts[j], secretKey[j]));
|
|
439
467
|
try {
|
|
440
468
|
// Detach the decapsulation result before cleanup: the combiner may hand back one of the
|
|
441
469
|
// child shared-secret buffers, and those temporary buffers are zeroized below.
|
|
442
|
-
return copyBytes(
|
|
470
|
+
return copyBytes(rawCombiner(publicKey, cts, sharedSecret));
|
|
443
471
|
} finally {
|
|
444
472
|
// Decapsulation only needs the expanded child secret keys and child shared secrets for this
|
|
445
473
|
// call; keep the caller/root seed intact, but wipe all derived material even on errors.
|
|
446
474
|
cleanBytes(secretKey, sharedSecret);
|
|
447
475
|
}
|
|
448
476
|
},
|
|
449
|
-
};
|
|
477
|
+
});
|
|
450
478
|
}
|
|
451
479
|
// There is no specs for this, but can be useful
|
|
452
480
|
// realSeedLen: how much bytes expandSeed expects.
|
|
@@ -468,12 +496,13 @@ export function combineKEMS(
|
|
|
468
496
|
*/
|
|
469
497
|
export function combineSigners(
|
|
470
498
|
realSeedLen: number | undefined,
|
|
471
|
-
expandSeed: ExpandSeed
|
|
472
|
-
...signers: Signer[]
|
|
473
|
-
): Signer {
|
|
474
|
-
const
|
|
475
|
-
const
|
|
476
|
-
const
|
|
499
|
+
expandSeed: TArg<ExpandSeed>,
|
|
500
|
+
...signers: TArg<Signer[]>
|
|
501
|
+
): TRet<Signer> {
|
|
502
|
+
const rawSigners = signers as Signer[];
|
|
503
|
+
const keys = combineKeys(realSeedLen, expandSeed, ...rawSigners);
|
|
504
|
+
const sigCoder = splitLengths(rawSigners, 'signature');
|
|
505
|
+
const pkCoder = splitLengths(rawSigners, 'publicKey');
|
|
477
506
|
return {
|
|
478
507
|
lengths: { ...keys.info.lengths, signature: sigCoder.bytesLen, signRand: 0 },
|
|
479
508
|
getPublicKey: keys.getPublicKey,
|
|
@@ -493,8 +522,8 @@ export function combineSigners(
|
|
|
493
522
|
);
|
|
494
523
|
const { secretKey } = keys.expandDecapsulationKey(seed);
|
|
495
524
|
try {
|
|
496
|
-
const sigs =
|
|
497
|
-
return sigCoder.encode(sigs)
|
|
525
|
+
const sigs = rawSigners.map((i, j) => i.sign(message, secretKey[j]));
|
|
526
|
+
return sigCoder.encode(sigs) as TRet<Uint8Array>;
|
|
498
527
|
} finally {
|
|
499
528
|
// Composite secret keys are root seeds; the per-signer child secret keys are temporary
|
|
500
529
|
// expansion outputs and must not stay live after the combined signature is produced.
|
|
@@ -513,8 +542,8 @@ export function combineSigners(
|
|
|
513
542
|
);
|
|
514
543
|
const pks = pkCoder.decode(publicKey);
|
|
515
544
|
const sigs = sigCoder.decode(signature);
|
|
516
|
-
for (let i = 0; i <
|
|
517
|
-
if (!
|
|
545
|
+
for (let i = 0; i < rawSigners.length; i++) {
|
|
546
|
+
if (!rawSigners[i].verify(sigs[i], message, pks[i])) return false;
|
|
518
547
|
}
|
|
519
548
|
return true;
|
|
520
549
|
},
|
|
@@ -545,21 +574,28 @@ export function combineSigners(
|
|
|
545
574
|
* const publicKeyLen = kem.lengths.publicKey;
|
|
546
575
|
* ```
|
|
547
576
|
*/
|
|
548
|
-
export function QSF(
|
|
577
|
+
export function QSF(
|
|
578
|
+
label: string,
|
|
579
|
+
pqc: TArg<KEM>,
|
|
580
|
+
curveKEM: TArg<KEM>,
|
|
581
|
+
xof: TArg<XOF>,
|
|
582
|
+
kdf: CHash
|
|
583
|
+
): TRet<KEM> {
|
|
549
584
|
ahash(xof);
|
|
550
585
|
ahash(kdf);
|
|
551
586
|
return combineKEMS(
|
|
552
587
|
32,
|
|
553
588
|
kdf.outputLen,
|
|
554
589
|
expandSeedXof(xof),
|
|
555
|
-
(pk
|
|
590
|
+
(pk: TArg<Uint8Array[]>, ct: TArg<Uint8Array[]>, ss: TArg<Uint8Array[]>) =>
|
|
591
|
+
kdf(concatBytes(ss[0], ss[1], ct[1], pk[1], asciiToBytes(label))),
|
|
556
592
|
pqc,
|
|
557
593
|
curveKEM
|
|
558
594
|
);
|
|
559
595
|
}
|
|
560
596
|
|
|
561
597
|
/** QSF preset combining ML-KEM-768 with P-256. */
|
|
562
|
-
export const QSF_ml_kem768_p256: KEM = /* @__PURE__ */ (() =>
|
|
598
|
+
export const QSF_ml_kem768_p256: TRet<KEM> = /* @__PURE__ */ (() =>
|
|
563
599
|
QSF(
|
|
564
600
|
'QSF-KEM(ML-KEM-768,P-256)-XOF(SHAKE256)-KDF(SHA3-256)',
|
|
565
601
|
ml_kem768,
|
|
@@ -568,7 +604,7 @@ export const QSF_ml_kem768_p256: KEM = /* @__PURE__ */ (() =>
|
|
|
568
604
|
sha3_256
|
|
569
605
|
))();
|
|
570
606
|
/** QSF preset combining ML-KEM-1024 with P-384. */
|
|
571
|
-
export const QSF_ml_kem1024_p384: KEM = /* @__PURE__ */ (() =>
|
|
607
|
+
export const QSF_ml_kem1024_p384: TRet<KEM> = /* @__PURE__ */ (() =>
|
|
572
608
|
QSF(
|
|
573
609
|
'QSF-KEM(ML-KEM-1024,P-384)-XOF(SHAKE256)-KDF(SHA3-256)',
|
|
574
610
|
ml_kem1024,
|
|
@@ -605,18 +641,18 @@ export const QSF_ml_kem1024_p384: KEM = /* @__PURE__ */ (() =>
|
|
|
605
641
|
*/
|
|
606
642
|
export function createKitchenSink(
|
|
607
643
|
label: string,
|
|
608
|
-
pqc: KEM
|
|
609
|
-
curveKEM: KEM
|
|
610
|
-
xof: XOF
|
|
644
|
+
pqc: TArg<KEM>,
|
|
645
|
+
curveKEM: TArg<KEM>,
|
|
646
|
+
xof: TArg<XOF>,
|
|
611
647
|
hash: CHash
|
|
612
|
-
): KEM {
|
|
648
|
+
): TRet<KEM> {
|
|
613
649
|
ahash(xof);
|
|
614
650
|
ahash(hash);
|
|
615
651
|
return combineKEMS(
|
|
616
652
|
32,
|
|
617
653
|
32,
|
|
618
654
|
expandSeedXof(xof),
|
|
619
|
-
(pk
|
|
655
|
+
(pk: TArg<Uint8Array[]>, ct: TArg<Uint8Array[]>, ss: TArg<Uint8Array[]>) => {
|
|
620
656
|
const preimage = concatBytes(ss[0], ss[1], ct[0], pk[0], ct[1], pk[1], asciiToBytes(label));
|
|
621
657
|
const len = 32;
|
|
622
658
|
const ikm = concatBytes(asciiToBytes('hybrid_prk'), preimage);
|
|
@@ -641,7 +677,7 @@ const x25519kem = /* @__PURE__ */ ecdhKem(x25519);
|
|
|
641
677
|
/** KitchenSink preset combining ML-KEM-768 with X25519.
|
|
642
678
|
* Caller randomness splits into 32 ML-KEM coins plus a 32-byte X25519 ephemeral-secret seed.
|
|
643
679
|
*/
|
|
644
|
-
export const KitchenSink_ml_kem768_x25519: KEM = /* @__PURE__ */ (() =>
|
|
680
|
+
export const KitchenSink_ml_kem768_x25519: TRet<KEM> = /* @__PURE__ */ (() =>
|
|
645
681
|
createKitchenSink(
|
|
646
682
|
'KitchenSink-KEM(ML-KEM-768,X25519)-XOF(SHAKE256)-KDF(HKDF-SHA-256)',
|
|
647
683
|
ml_kem768,
|
|
@@ -655,13 +691,14 @@ export const KitchenSink_ml_kem768_x25519: KEM = /* @__PURE__ */ (() =>
|
|
|
655
691
|
* Uses the hard-coded domain-separation label `\\.//^\\` and hashes only `ct1 || pk1`
|
|
656
692
|
* from the X25519 side in addition to the two component shared secrets.
|
|
657
693
|
*/
|
|
658
|
-
export const ml_kem768_x25519: KEM = /* @__PURE__ */ (() =>
|
|
694
|
+
export const ml_kem768_x25519: TRet<KEM> = /* @__PURE__ */ (() =>
|
|
659
695
|
combineKEMS(
|
|
660
696
|
32,
|
|
661
697
|
32,
|
|
662
698
|
expandSeedXof(shake256),
|
|
663
699
|
// Awesome label, so much escaping hell in a single line.
|
|
664
|
-
(pk
|
|
700
|
+
(pk: TArg<Uint8Array[]>, ct: TArg<Uint8Array[]>, ss: TArg<Uint8Array[]>) =>
|
|
701
|
+
sha3_256(concatBytes(ss[0], ss[1], ct[1], pk[1], asciiToBytes('\\.//^\\'))),
|
|
665
702
|
ml_kem768,
|
|
666
703
|
x25519kem
|
|
667
704
|
))();
|
|
@@ -674,12 +711,15 @@ export const ml_kem768_x25519: KEM = /* @__PURE__ */ (() =>
|
|
|
674
711
|
* prefix, not the SEC 1 `x_P`-only primitive output, because current hybrid combiners hash
|
|
675
712
|
* both coordinates.
|
|
676
713
|
*/
|
|
677
|
-
function nistCurveKem(curve: ECDSA, scalarLen: number, elemLen: number, nseed: number): KEM {
|
|
714
|
+
function nistCurveKem(curve: ECDSA, scalarLen: number, elemLen: number, nseed: number): TRet<KEM> {
|
|
678
715
|
const Fn = curve.Point.Fn;
|
|
679
716
|
if (!Fn) throw new Error('no Point.Fn');
|
|
680
717
|
// Scan scalar-sized windows until one decodes to a nonzero scalar in `[1, n-1]`; if every
|
|
681
718
|
// window is zero or out of range, fail instead of silently reducing modulo `n`.
|
|
682
|
-
function rejectionSampling(seed: Uint8Array): {
|
|
719
|
+
function rejectionSampling(seed: TArg<Uint8Array>): TRet<{
|
|
720
|
+
secretKey: Uint8Array;
|
|
721
|
+
publicKey: Uint8Array;
|
|
722
|
+
}> {
|
|
683
723
|
let sk: bigint;
|
|
684
724
|
for (let start = 0, end = scalarLen; ; start = end, end += scalarLen) {
|
|
685
725
|
if (end > seed.length) throw new Error('rejection sampling failed');
|
|
@@ -688,7 +728,10 @@ function nistCurveKem(curve: ECDSA, scalarLen: number, elemLen: number, nseed: n
|
|
|
688
728
|
}
|
|
689
729
|
const secretKey = Fn.toBytes(Fn.create(sk));
|
|
690
730
|
const publicKey = curve.getPublicKey(secretKey, false);
|
|
691
|
-
return { secretKey, publicKey }
|
|
731
|
+
return { secretKey, publicKey } as TRet<{
|
|
732
|
+
secretKey: Uint8Array;
|
|
733
|
+
publicKey: Uint8Array;
|
|
734
|
+
}>;
|
|
692
735
|
}
|
|
693
736
|
|
|
694
737
|
return {
|
|
@@ -699,20 +742,20 @@ function nistCurveKem(curve: ECDSA, scalarLen: number, elemLen: number, nseed: n
|
|
|
699
742
|
msg: nseed,
|
|
700
743
|
cipherText: elemLen,
|
|
701
744
|
},
|
|
702
|
-
keygen(seed: Uint8Array = randomBytes(nseed)) {
|
|
745
|
+
keygen(seed: TArg<Uint8Array> = randomBytes(nseed)) {
|
|
703
746
|
abytes(seed, nseed, 'seed');
|
|
704
747
|
return rejectionSampling(seed);
|
|
705
748
|
},
|
|
706
|
-
getPublicKey(secretKey: Uint8Array) {
|
|
707
|
-
return curve.getPublicKey(secretKey, false)
|
|
749
|
+
getPublicKey(secretKey: TArg<Uint8Array>) {
|
|
750
|
+
return curve.getPublicKey(secretKey, false) as TRet<Uint8Array>;
|
|
708
751
|
},
|
|
709
|
-
encapsulate(publicKey: Uint8Array
|
|
752
|
+
encapsulate(publicKey: TArg<Uint8Array>, rand: TArg<Uint8Array> = randomBytes(nseed)) {
|
|
710
753
|
abytes(rand, nseed, 'rand');
|
|
711
754
|
let ek: Uint8Array | undefined = undefined;
|
|
712
755
|
try {
|
|
713
756
|
ek = rejectionSampling(rand).secretKey;
|
|
714
757
|
const sharedSecret = this.decapsulate(publicKey, ek);
|
|
715
|
-
const cipherText = curve.getPublicKey(ek, false)
|
|
758
|
+
const cipherText = curve.getPublicKey(ek, false) as TRet<Uint8Array>;
|
|
716
759
|
return { sharedSecret, cipherText };
|
|
717
760
|
} finally {
|
|
718
761
|
// Rejection-sampled NIST-curve ephemeral secret keys are temporary encapsulation state and
|
|
@@ -720,9 +763,9 @@ function nistCurveKem(curve: ECDSA, scalarLen: number, elemLen: number, nseed: n
|
|
|
720
763
|
if (ek) cleanBytes(ek);
|
|
721
764
|
}
|
|
722
765
|
},
|
|
723
|
-
decapsulate(cipherText: Uint8Array
|
|
766
|
+
decapsulate(cipherText: TArg<Uint8Array>, secretKey: TArg<Uint8Array>) {
|
|
724
767
|
const full = curve.getSharedSecret(secretKey, cipherText);
|
|
725
|
-
return full.subarray(1)
|
|
768
|
+
return full.subarray(1) as TRet<Uint8Array>;
|
|
726
769
|
},
|
|
727
770
|
};
|
|
728
771
|
}
|
|
@@ -735,7 +778,12 @@ function nistCurveKem(curve: ECDSA, scalarLen: number, elemLen: number, nseed: n
|
|
|
735
778
|
* `shake256(seed, { dkLen: 64 + nseed })`,
|
|
736
779
|
* and the combiner hashes `ss0 || ss1 || ct1 || pk1 || label`.
|
|
737
780
|
*/
|
|
738
|
-
function concreteHybridKem(
|
|
781
|
+
function concreteHybridKem(
|
|
782
|
+
label: string,
|
|
783
|
+
mlkem: TArg<KEM>,
|
|
784
|
+
curve: ECDSA,
|
|
785
|
+
nseed: number
|
|
786
|
+
): TRet<KEM> {
|
|
739
787
|
const { secretKey: scalarLen, publicKeyUncompressed: elemLen } = curve.lengths;
|
|
740
788
|
if (!scalarLen || !elemLen) throw new Error('wrong curve');
|
|
741
789
|
const curveKem = nistCurveKem(curve, scalarLen, elemLen, nseed);
|
|
@@ -745,40 +793,41 @@ function concreteHybridKem(label: string, mlkem: KEM, curve: ECDSA, nseed: numbe
|
|
|
745
793
|
return combineKEMS(
|
|
746
794
|
32,
|
|
747
795
|
32,
|
|
748
|
-
(seed: Uint8Array) => {
|
|
796
|
+
(seed: TArg<Uint8Array>): TRet<Uint8Array> => {
|
|
749
797
|
abytes(seed, 32);
|
|
750
798
|
const expanded = shake256(seed, { dkLen: totalSeedLen });
|
|
751
799
|
const mlkemSeed = expanded.subarray(0, mlkemSeedLen);
|
|
752
800
|
const curveSeed = expanded.subarray(mlkemSeedLen, totalSeedLen);
|
|
753
|
-
return concatBytes(mlkemSeed, curveSeed)
|
|
801
|
+
return concatBytes(mlkemSeed, curveSeed) as TRet<Uint8Array>;
|
|
754
802
|
},
|
|
755
|
-
(pk
|
|
803
|
+
(pk: TArg<Uint8Array[]>, ct: TArg<Uint8Array[]>, ss: TArg<Uint8Array[]>) =>
|
|
804
|
+
sha3_256(concatBytes(ss[0], ss[1], ct[1], pk[1], asciiToBytes(label))),
|
|
756
805
|
mlkem,
|
|
757
806
|
curveKem
|
|
758
807
|
);
|
|
759
808
|
}
|
|
760
809
|
|
|
761
810
|
/** P-256 + ML-KEM-768 hybrid preset. */
|
|
762
|
-
export const ml_kem768_p256: KEM = /* @__PURE__ */ (() =>
|
|
811
|
+
export const ml_kem768_p256: TRet<KEM> = /* @__PURE__ */ (() =>
|
|
763
812
|
concreteHybridKem('MLKEM768-P256', ml_kem768, p256, 128))();
|
|
764
813
|
|
|
765
814
|
/** P-384 + ML-KEM-1024 hybrid preset. */
|
|
766
|
-
export const ml_kem1024_p384: KEM = /* @__PURE__ */ (() =>
|
|
815
|
+
export const ml_kem1024_p384: TRet<KEM> = /* @__PURE__ */ (() =>
|
|
767
816
|
concreteHybridKem('MLKEM1024-P384', ml_kem1024, p384, 48))();
|
|
768
817
|
|
|
769
818
|
// Legacy aliases
|
|
770
819
|
/** Legacy alias for `ml_kem768_x25519`. */
|
|
771
|
-
export const XWing: KEM = /* @__PURE__ */ (() => ml_kem768_x25519)();
|
|
820
|
+
export const XWing: TRet<KEM> = /* @__PURE__ */ (() => ml_kem768_x25519)();
|
|
772
821
|
/** Legacy alias for `ml_kem768_x25519`. */
|
|
773
|
-
export const MLKEM768X25519: KEM = /* @__PURE__ */ (() => ml_kem768_x25519)();
|
|
822
|
+
export const MLKEM768X25519: TRet<KEM> = /* @__PURE__ */ (() => ml_kem768_x25519)();
|
|
774
823
|
/** Legacy alias for `ml_kem768_p256`. */
|
|
775
|
-
export const MLKEM768P256: KEM = /* @__PURE__ */ (() => ml_kem768_p256)();
|
|
824
|
+
export const MLKEM768P256: TRet<KEM> = /* @__PURE__ */ (() => ml_kem768_p256)();
|
|
776
825
|
/** Legacy alias for `ml_kem1024_p384`. */
|
|
777
|
-
export const MLKEM1024P384: KEM = /* @__PURE__ */ (() => ml_kem1024_p384)();
|
|
826
|
+
export const MLKEM1024P384: TRet<KEM> = /* @__PURE__ */ (() => ml_kem1024_p384)();
|
|
778
827
|
/** Legacy alias for `QSF_ml_kem768_p256`. */
|
|
779
|
-
export const QSFMLKEM768P256: KEM = /* @__PURE__ */ (() => QSF_ml_kem768_p256)();
|
|
828
|
+
export const QSFMLKEM768P256: TRet<KEM> = /* @__PURE__ */ (() => QSF_ml_kem768_p256)();
|
|
780
829
|
/** Legacy alias for `QSF_ml_kem1024_p384`. */
|
|
781
|
-
export const QSFMLKEM1024P384: KEM = /* @__PURE__ */ (() => QSF_ml_kem1024_p384)();
|
|
830
|
+
export const QSFMLKEM1024P384: TRet<KEM> = /* @__PURE__ */ (() => QSF_ml_kem1024_p384)();
|
|
782
831
|
/** Legacy alias for `KitchenSink_ml_kem768_x25519`. */
|
|
783
|
-
export const KitchenSinkMLKEM768X25519: KEM = /* @__PURE__ */ (() =>
|
|
832
|
+
export const KitchenSinkMLKEM768X25519: TRet<KEM> = /* @__PURE__ */ (() =>
|
|
784
833
|
KitchenSink_ml_kem768_x25519)();
|
package/src/index.ts
CHANGED
|
@@ -14,6 +14,14 @@ import {
|
|
|
14
14
|
slh_dsa_shake_192f, slh_dsa_shake_192s,
|
|
15
15
|
slh_dsa_shake_256f, slh_dsa_shake_256s,
|
|
16
16
|
} from '@noble/post-quantum/slh-dsa.js';
|
|
17
|
+
import {
|
|
18
|
+
falcon512, falcon512padded, falcon1024, falcon1024padded,
|
|
19
|
+
} from '@noble/post-quantum/falcon.js';
|
|
20
|
+
import {
|
|
21
|
+
ml_kem768_x25519, ml_kem768_p256, ml_kem1024_p384,
|
|
22
|
+
KitchenSink_ml_kem768_x25519, XWing,
|
|
23
|
+
QSF_ml_kem768_p256, QSF_ml_kem1024_p384,
|
|
24
|
+
} from '@noble/post-quantum/hybrid.js';
|
|
17
25
|
```
|
|
18
26
|
*/
|
|
19
27
|
throw new Error('root module cannot be imported: import submodules instead. Check out README');
|