@noble/post-quantum 0.6.1 → 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/utils.d.ts CHANGED
@@ -84,9 +84,11 @@ export { concatBytesDoc as concatBytes };
84
84
  * ```
85
85
  */
86
86
  export declare const randomBytes: typeof randb;
87
+ export declare function aarray<T>(item: unknown, title: string, inner?: (elm: T, title: string) => void): T[];
88
+ export declare function aobject<T extends object>(value: unknown, title?: string): T;
87
89
  /**
88
90
  * Compares two byte arrays in a length-constant way for equal lengths.
89
- * Unequal lengths return `false` immediately, and there is no runtime type validation.
91
+ * Inputs are validated as byte arrays; unequal lengths return `false` immediately.
90
92
  * @param a - First byte array.
91
93
  * @param b - Second byte array.
92
94
  * @returns Whether both arrays contain the same bytes.
@@ -184,32 +186,66 @@ export type SigOpts = VerOpts & {
184
186
  * ```
185
187
  */
186
188
  export declare function validateOpts(opts: object): void;
189
+ /** Keys accepted by `verify`. */
190
+ export declare const VER_OPT_KEYS: readonly ['context'];
191
+ /** Keys accepted by `sign`. */
192
+ export declare const SIG_OPT_KEYS: readonly ['context', 'extraEntropy'];
193
+ /**
194
+ * Rejects option keys the caller did not mean to set.
195
+ *
196
+ * Validating the types of known keys while ignoring unknown ones makes a typo
197
+ * indistinguishable from an omission, and for these options an omission is a
198
+ * security downgrade rather than a no-op: `{ ctx }` instead of `{ context }` signs
199
+ * with no domain separation, succeeds, and verifies for anyone who also supplies
200
+ * none. Nothing at any layer reports it. TypeScript catches this through excess
201
+ * property checks, so the exposure is JavaScript callers specifically.
202
+ *
203
+ * @param opts - Options object to check.
204
+ * @param allowed - The keys this call site accepts.
205
+ * Returns a frozen null-prototype snapshot so later reads cannot fall through to a polluted
206
+ * prototype. Like `checkOpts()` in noble-hashes, only enumerable own properties are copied.
207
+ * @throws If any other copied key is present or the bag has a custom prototype. {@link TypeError}
208
+ * @returns Sanitized snapshot of the enumerable own options.
209
+ * @example
210
+ * Accept a known option key. A key the list does not name, such as `ctx`, throws instead.
211
+ * ```ts
212
+ * import { checkOptKeys } from '@noble/post-quantum/utils.js';
213
+ * checkOptKeys({ context: new Uint8Array() }, ['context']);
214
+ * ```
215
+ */
216
+ export declare function checkOptKeys<T extends object>(opts: T, allowed: readonly string[]): T;
187
217
  /**
188
218
  * Validates common verification options.
189
219
  * `context` itself is validated with `abytes(...)`, and individual algorithms may narrow support
190
220
  * further after this shared plain-object gate.
191
221
  * @param opts - Verification options. See {@link VerOpts}.
222
+ * @param allowed - Keys this call site accepts. Defaults to {@link VER_OPT_KEYS}; surfaces that
223
+ * take extra keys, or take fewer, pass their own list.
192
224
  * @throws On wrong argument types. {@link TypeError}
225
+ * @returns Frozen null-prototype snapshot of the validated options.
193
226
  * @example
194
227
  * Validate common verification options.
195
228
  * ```ts
196
229
  * validateVerOpts({ context: new Uint8Array([1]) });
197
230
  * ```
198
231
  */
199
- export declare function validateVerOpts(opts: TArg<VerOpts>): void;
232
+ export declare function validateVerOpts<T extends TArg<VerOpts>>(opts: T, allowed?: readonly string[]): T;
200
233
  /**
201
234
  * Validates common signing options.
202
235
  * `extraEntropy` is validated with `abytes(...)`; exact lengths and extra algorithm-specific
203
236
  * restrictions are enforced later by callers.
204
237
  * @param opts - Signing options. See {@link SigOpts}.
238
+ * @param allowed - Keys this call site accepts. Defaults to {@link SIG_OPT_KEYS}; surfaces that
239
+ * take extra keys, or take fewer, pass their own list.
205
240
  * @throws On wrong argument types. {@link TypeError}
241
+ * @returns Frozen null-prototype snapshot of the validated options.
206
242
  * @example
207
243
  * Validate common signing options.
208
244
  * ```ts
209
245
  * validateSigOpts({ extraEntropy: new Uint8Array([1]) });
210
246
  * ```
211
247
  */
212
- export declare function validateSigOpts(opts: TArg<SigOpts>): void;
248
+ export declare function validateSigOpts<T extends TArg<SigOpts>>(opts: T, allowed?: readonly string[]): T;
213
249
  /** Generic signature interface with key generation, signing, and verification. */
214
250
  export type Signer = CryptoKeys & {
215
251
  /** Public byte lengths for signatures and signing randomness. */
@@ -356,6 +392,7 @@ export declare function cleanBytes(...list: (TypedArray | TypedArray[])[]): void
356
392
  * Creates a 32-bit mask with the lowest `bits` bits set.
357
393
  * @param bits - Number of low bits to keep.
358
394
  * @returns Bit mask with `bits` ones.
395
+ * @throws On wrong argument types. {@link TypeError}
359
396
  * @throws On wrong argument ranges or values. {@link RangeError}
360
397
  * @example
361
398
  * Create a low-bit mask for packed-field operations.
@@ -417,4 +454,17 @@ export declare function checkHash(hash: CHash, requiredStrength?: number): void;
417
454
  * ```
418
455
  */
419
456
  export declare function getMessagePrehash(hash: CHash, msg: TArg<Uint8Array>, ctx?: TArg<Uint8Array>): TRet<Uint8Array>;
420
- //# sourceMappingURL=utils.d.ts.map
457
+ /**
458
+ * Asserts something is a string.
459
+ * @param value - Value to validate.
460
+ * @param title - Label included in thrown errors.
461
+ * @returns The validated string.
462
+ * @throws On wrong argument types. {@link TypeError}
463
+ * @example
464
+ * Validate a label string.
465
+ *
466
+ * ```ts
467
+ * astring('example', 'label');
468
+ * ```
469
+ */
470
+ export declare function astring(value: unknown, title?: string): string;
package/utils.js CHANGED
@@ -3,7 +3,7 @@
3
3
  * @module
4
4
  */
5
5
  /*! noble-post-quantum - MIT License (c) 2024 Paul Miller (paulmillr.com) */
6
- import { abytes, abytes as abytes_, concatBytes, isLE, randomBytes as randb, } from '@noble/hashes/utils.js';
6
+ import { abytes, abytes as abytes_, ahash as ahash_, anumber, bytesToHex, concatBytes, isBytes, isLE, randomBytes as randb, } from '@noble/hashes/utils.js';
7
7
  /**
8
8
  * Asserts that a value is a byte array and optionally checks its length.
9
9
  * Returns the original reference unchanged on success, and currently also accepts Node `Buffer`
@@ -44,9 +44,23 @@ export { concatBytesDoc as concatBytes };
44
44
  * ```
45
45
  */
46
46
  export const randomBytes = randb;
47
+ export function aarray(item, title, inner = () => { }) {
48
+ if (!Array.isArray(item))
49
+ throw new TypeError(`"${title}" expected array, got type=${typeof item}`);
50
+ for (let i = 0; i < item.length; i++)
51
+ inner(item[i], `${title}[${i}]`);
52
+ return item;
53
+ }
54
+ export function aobject(value, title = 'object') {
55
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
56
+ throw new TypeError(title === 'object'
57
+ ? 'expected valid options object'
58
+ : `"${title}" expected object, got type=${typeof value}`);
59
+ return value;
60
+ }
47
61
  /**
48
62
  * Compares two byte arrays in a length-constant way for equal lengths.
49
- * Unequal lengths return `false` immediately, and there is no runtime type validation.
63
+ * Inputs are validated as byte arrays; unequal lengths return `false` immediately.
50
64
  * @param a - First byte array.
51
65
  * @param b - Second byte array.
52
66
  * @returns Whether both arrays contain the same bytes.
@@ -57,6 +71,8 @@ export const randomBytes = randb;
57
71
  * ```
58
72
  */
59
73
  export function equalBytes(a, b) {
74
+ a = abytes(a);
75
+ b = abytes(b);
60
76
  if (a.length !== b.length)
61
77
  return false;
62
78
  let diff = 0;
@@ -76,9 +92,10 @@ export function equalBytes(a, b) {
76
92
  * ```
77
93
  */
78
94
  export function copyBytes(bytes) {
79
- // `Uint8Array.from(...)` would also accept arrays / other typed arrays. Keep this helper strict
80
- // because callers use it at byte-validation boundaries before mutating the detached copy.
81
- return Uint8Array.from(abytes(bytes));
95
+ // The typed-array constructor copies a typed-array source through its internal byte storage.
96
+ // Unlike `Uint8Array.from`, it does not invoke a subclass-overridden iterator. Keep the explicit
97
+ // validation because the constructor itself would also accept arrays and other typed arrays.
98
+ return new Uint8Array(abytes(bytes));
82
99
  }
83
100
  /**
84
101
  * Byte-swaps each 64-bit lane in place.
@@ -137,42 +154,110 @@ export const baswap64If = isLE
137
154
  */
138
155
  export function validateOpts(opts) {
139
156
  // Arrays silently passed here before, but these call sites expect named option-bag fields.
140
- if (Object.prototype.toString.call(opts) !== '[object Object]')
141
- throw new TypeError('expected valid options object');
157
+ if (isBytes(opts))
158
+ throw new TypeError('"opts" expected object, got Uint8Array');
159
+ aobject(opts, 'opts');
160
+ const proto = Object.getPrototypeOf(opts);
161
+ // Options are security parameters, not general class instances. Restricting the bag to own
162
+ // properties prevents values injected through Object.prototype (or a custom shared prototype)
163
+ // from silently changing signing behavior. Null-prototype records remain supported.
164
+ if (proto !== null && proto !== Object.prototype)
165
+ throw new TypeError('"opts" expected a plain object');
166
+ }
167
+ // Frozen because they are exported: an unfrozen array export lets anything in the
168
+ // process push a key onto the accepted set and silently re-open exactly the hole this
169
+ // validation closes.
170
+ /** Keys accepted by `verify`. */
171
+ export const VER_OPT_KEYS = /* @__PURE__ */ Object.freeze([
172
+ 'context',
173
+ ]);
174
+ /** Keys accepted by `sign`. */
175
+ export const SIG_OPT_KEYS = /* @__PURE__ */ Object.freeze([
176
+ 'context',
177
+ 'extraEntropy',
178
+ ]);
179
+ /**
180
+ * Rejects option keys the caller did not mean to set.
181
+ *
182
+ * Validating the types of known keys while ignoring unknown ones makes a typo
183
+ * indistinguishable from an omission, and for these options an omission is a
184
+ * security downgrade rather than a no-op: `{ ctx }` instead of `{ context }` signs
185
+ * with no domain separation, succeeds, and verifies for anyone who also supplies
186
+ * none. Nothing at any layer reports it. TypeScript catches this through excess
187
+ * property checks, so the exposure is JavaScript callers specifically.
188
+ *
189
+ * @param opts - Options object to check.
190
+ * @param allowed - The keys this call site accepts.
191
+ * Returns a frozen null-prototype snapshot so later reads cannot fall through to a polluted
192
+ * prototype. Like `checkOpts()` in noble-hashes, only enumerable own properties are copied.
193
+ * @throws If any other copied key is present or the bag has a custom prototype. {@link TypeError}
194
+ * @returns Sanitized snapshot of the enumerable own options.
195
+ * @example
196
+ * Accept a known option key. A key the list does not name, such as `ctx`, throws instead.
197
+ * ```ts
198
+ * import { checkOptKeys } from '@noble/post-quantum/utils.js';
199
+ * checkOptKeys({ context: new Uint8Array() }, ['context']);
200
+ * ```
201
+ */
202
+ export function checkOptKeys(opts, allowed) {
203
+ validateOpts(opts);
204
+ // Snapshot once before validation: Object.assign follows the same own-enumerable option-bag
205
+ // semantics as noble-hashes, while the null prototype keeps omitted fields immune to pollution.
206
+ const normalized = Object.assign(Object.create(null), opts);
207
+ for (const [k, v] of Object.entries(normalized)) {
208
+ // `undefined` means unset everywhere else in these validators, and building an options bag by
209
+ // spread is a normal way to reach these calls, so present-but-undefined stays equivalent to
210
+ // omission.
211
+ if (v === undefined)
212
+ continue;
213
+ if (!allowed.includes(k))
214
+ throw new TypeError('unexpected option "' + String(k) + '"; expected one of: ' + allowed.join(', '));
215
+ }
216
+ return Object.freeze(normalized);
142
217
  }
143
218
  /**
144
219
  * Validates common verification options.
145
220
  * `context` itself is validated with `abytes(...)`, and individual algorithms may narrow support
146
221
  * further after this shared plain-object gate.
147
222
  * @param opts - Verification options. See {@link VerOpts}.
223
+ * @param allowed - Keys this call site accepts. Defaults to {@link VER_OPT_KEYS}; surfaces that
224
+ * take extra keys, or take fewer, pass their own list.
148
225
  * @throws On wrong argument types. {@link TypeError}
226
+ * @returns Frozen null-prototype snapshot of the validated options.
149
227
  * @example
150
228
  * Validate common verification options.
151
229
  * ```ts
152
230
  * validateVerOpts({ context: new Uint8Array([1]) });
153
231
  * ```
154
232
  */
155
- export function validateVerOpts(opts) {
156
- validateOpts(opts);
157
- if (opts.context !== undefined)
158
- abytes(opts.context, undefined, 'opts.context');
233
+ export function validateVerOpts(opts, allowed = VER_OPT_KEYS) {
234
+ const normalized = checkOptKeys(opts, allowed);
235
+ if (normalized.context !== undefined)
236
+ abytes(normalized.context, undefined, 'opts.context');
237
+ return normalized;
159
238
  }
160
239
  /**
161
240
  * Validates common signing options.
162
241
  * `extraEntropy` is validated with `abytes(...)`; exact lengths and extra algorithm-specific
163
242
  * restrictions are enforced later by callers.
164
243
  * @param opts - Signing options. See {@link SigOpts}.
244
+ * @param allowed - Keys this call site accepts. Defaults to {@link SIG_OPT_KEYS}; surfaces that
245
+ * take extra keys, or take fewer, pass their own list.
165
246
  * @throws On wrong argument types. {@link TypeError}
247
+ * @returns Frozen null-prototype snapshot of the validated options.
166
248
  * @example
167
249
  * Validate common signing options.
168
250
  * ```ts
169
251
  * validateSigOpts({ extraEntropy: new Uint8Array([1]) });
170
252
  * ```
171
253
  */
172
- export function validateSigOpts(opts) {
173
- validateVerOpts(opts);
174
- if (opts.extraEntropy !== false && opts.extraEntropy !== undefined)
175
- abytes(opts.extraEntropy, undefined, 'opts.extraEntropy');
254
+ export function validateSigOpts(opts, allowed = SIG_OPT_KEYS) {
255
+ const normalized = checkOptKeys(opts, allowed);
256
+ if (normalized.context !== undefined)
257
+ abytes(normalized.context, undefined, 'opts.context');
258
+ if (normalized.extraEntropy !== false && normalized.extraEntropy !== undefined)
259
+ abytes(normalized.extraEntropy, undefined, 'opts.extraEntropy');
260
+ return normalized;
176
261
  }
177
262
  /**
178
263
  * Builds a fixed-layout coder from byte lengths and nested coders.
@@ -247,11 +332,12 @@ export function vecCoder(c, vecLen) {
247
332
  return {
248
333
  bytesLen,
249
334
  encode: (u) => {
250
- if (u.length !== vecLen)
251
- throw new RangeError(`vecCoder.encode: wrong length=${u.length}. Expected: ${vecLen}`);
335
+ const uArr = aarray(u, 'u');
336
+ if (uArr.length !== vecLen)
337
+ throw new RangeError(`vecCoder.encode: wrong length=${uArr.length}. Expected: ${vecLen}`);
252
338
  const res = new Uint8Array(bytesLen);
253
- for (let i = 0, pos = 0; i < u.length; i++) {
254
- const b = coder.encode(u[i]);
339
+ for (let i = 0, pos = 0; i < uArr.length; i++) {
340
+ const b = coder.encode(uArr[i]);
255
341
  res.set(b, pos);
256
342
  b.fill(0); // clean
257
343
  pos += b.length;
@@ -291,6 +377,7 @@ export function cleanBytes(...list) {
291
377
  * Creates a 32-bit mask with the lowest `bits` bits set.
292
378
  * @param bits - Number of low bits to keep.
293
379
  * @returns Bit mask with `bits` ones.
380
+ * @throws On wrong argument types. {@link TypeError}
294
381
  * @throws On wrong argument ranges or values. {@link RangeError}
295
382
  * @example
296
383
  * Create a low-bit mask for packed-field operations.
@@ -299,8 +386,9 @@ export function cleanBytes(...list) {
299
386
  * ```
300
387
  */
301
388
  export function getMask(bits) {
302
- if (!Number.isSafeInteger(bits) || bits < 0 || bits > 32)
303
- throw new RangeError(`expected bits in [0..32], got ${bits}`);
389
+ anumber(bits, 'bits');
390
+ if (bits > 32)
391
+ throw new RangeError('"bits" expected <= 32, got ' + bits);
304
392
  // JS shifts are modulo 32, so bit 32 needs an explicit full-width mask.
305
393
  return bits === 32 ? 0xffffffff : ~(-1 << bits) >>> 0;
306
394
  }
@@ -320,8 +408,8 @@ export const EMPTY = /* @__PURE__ */ Uint8Array.of();
320
408
  * ```
321
409
  */
322
410
  export function getMessage(msg, ctx = EMPTY) {
323
- abytes_(msg);
324
- abytes_(ctx);
411
+ abytes_(msg, undefined, 'msg');
412
+ abytes_(ctx, undefined, 'ctx');
325
413
  if (ctx.length > 255)
326
414
  throw new RangeError('context should be 255 bytes or less');
327
415
  return concatBytes(new Uint8Array([0, ctx.length]), ctx, msg);
@@ -331,6 +419,19 @@ export function getMessage(msg, ctx = EMPTY) {
331
419
  // SHAKE256, or another approved hash/XOF under that subtree.
332
420
  // 06 09 60 86 48 01 65 03 04 02
333
421
  const oidNistP = /* @__PURE__ */ Uint8Array.from([6, 9, 0x60, 0x86, 0x48, 1, 0x65, 3, 4, 2]);
422
+ /**
423
+ * Output length, in bytes, that each XOF OID under this arc denotes.
424
+ *
425
+ * Unlike a fixed hash, an XOF's OID is a promise about the digest length: RFC 8702
426
+ * defines id-shake128 as SHAKE128 with 256-bit output and id-shake256 as SHAKE256 with
427
+ * 512-bit output, and FIPS 204 / FIPS 205 use exactly those pairings for pre-hash. Both
428
+ * bare noble-hashes defaults are half these values, so neither can be signed under its
429
+ * own OID.
430
+ */
431
+ const XOF_OID_OUTPUT_LEN = /* @__PURE__ */ (() => ({
432
+ '060960864801650304020b': 32, // id-shake128, SHAKE128(M, 256)
433
+ '060960864801650304020c': 64, // id-shake256, SHAKE256(M, 512)
434
+ }))();
334
435
  /**
335
436
  * Validates that a hash exposes a NIST hash OID and enough collision resistance.
336
437
  * Current accepted surface is broader than the FIPS algorithm tables: any hash/XOF under the NIST
@@ -349,11 +450,29 @@ const oidNistP = /* @__PURE__ */ Uint8Array.from([6, 9, 0x60, 0x86, 0x48, 1, 0x6
349
450
  * ```
350
451
  */
351
452
  export function checkHash(hash, requiredStrength = 0) {
352
- if (!hash.oid || !equalBytes(hash.oid.subarray(0, 10), oidNistP))
353
- throw new Error('hash.oid is invalid: expected NIST hash');
453
+ if (typeof hash !== 'function' || typeof hash.create !== 'function')
454
+ throw new TypeError('"hash" expected hash function, got type=' + typeof hash);
455
+ ahash_(hash);
456
+ anumber(requiredStrength, 'requiredStrength');
457
+ const oid = hash.oid;
458
+ abytes_(oid, undefined, 'hash.oid');
459
+ if (!equalBytes(oid.subarray(0, 10), oidNistP))
460
+ throw new Error('"hash.oid" is invalid: expected NIST hash');
354
461
  // FIPS 204 / FIPS 205 require both collision and second-preimage strength; for approved NIST
355
462
  // hashes/XOFs under this OID subtree, the collision bound from the configured digest length is
356
463
  // the tighter runtime check, so enforce that lower bound here.
464
+ // XOFs under this arc are identified by an OID that fixes their output length:
465
+ // FIPS 204 §5.4.1 (SHAKE128) and FIPS 205 §10.2.2 (both SHAKEs), matching RFC 8702, pair
466
+ // id-shake128 with SHAKE128(M, 256) and id-shake256 with SHAKE256(M, 512). getMessagePrehash embeds
467
+ // hash.oid beside hash(msg), so a shorter digest signs an M' that claims a length
468
+ // it does not have: noble-hashes' bare shake256 defaults to 32 bytes and cleared
469
+ // the collision bound at the 128-bit level, producing signatures a conformant
470
+ // verifier rejects because it recomputes 512 bits. Check the length the OID
471
+ // denotes rather than the generic bound.
472
+ const xofLen = XOF_OID_OUTPUT_LEN[bytesToHex(oid)];
473
+ if (xofLen !== undefined && hash.outputLen !== xofLen) {
474
+ throw new Error('Pre-hash XOF output length must be ' + xofLen + ' bytes for this OID, got: ' + hash.outputLen);
475
+ }
357
476
  const collisionResistance = (hash.outputLen * 8) / 2;
358
477
  if (requiredStrength > collisionResistance) {
359
478
  throw new Error('Pre-hash security strength too low: ' +
@@ -381,11 +500,31 @@ export function checkHash(hash, requiredStrength = 0) {
381
500
  * ```
382
501
  */
383
502
  export function getMessagePrehash(hash, msg, ctx = EMPTY) {
384
- abytes_(msg);
385
- abytes_(ctx);
503
+ checkHash(hash);
504
+ abytes_(msg, undefined, 'msg');
505
+ abytes_(ctx, undefined, 'ctx');
386
506
  if (ctx.length > 255)
387
507
  throw new RangeError('context should be 255 bytes or less');
388
508
  const hashed = hash(msg);
389
509
  return concatBytes(new Uint8Array([1, ctx.length]), ctx, hash.oid, hashed);
390
510
  }
391
- //# sourceMappingURL=utils.js.map
511
+ /**
512
+ * Asserts something is a string.
513
+ * @param value - Value to validate.
514
+ * @param title - Label included in thrown errors.
515
+ * @returns The validated string.
516
+ * @throws On wrong argument types. {@link TypeError}
517
+ * @example
518
+ * Validate a label string.
519
+ *
520
+ * ```ts
521
+ * astring('example', 'label');
522
+ * ```
523
+ */
524
+ export function astring(value, title = '') {
525
+ if (typeof value !== 'string') {
526
+ const prefix = title && `"${title}" `;
527
+ throw new TypeError(prefix + 'expected string, got type=' + typeof value);
528
+ }
529
+ return value;
530
+ }
package/webcrypto.d.ts ADDED
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Friendly async wrappers over ML-KEM and ML-KEM-768 + X25519 from built-in WebCrypto.
3
+ * Private keys use the same raw seed accepted by the synchronous implementations' `keygen(seed)`;
4
+ * they are not expanded decapsulation keys.
5
+ *
6
+ * # WebCrypto quirks
7
+ *
8
+ * - The algorithms are experimental: a runtime can expose `encapsulateBits` and friends while
9
+ * implementing none of them, so support is probed with a full round-trip in `isSupported()`.
10
+ * - `MLKEM768-X25519` accepts `raw-seed` on import, but has no `raw-seed` / `raw-public` export.
11
+ * Its key bytes are read out of the JWK `priv` / `pub` members instead.
12
+ * - base64url is hand-rolled: scure-base's `base64urlnopad` would do, but this module must not add
13
+ * dependencies, and importing the synchronous implementations for four byte lengths would pull
14
+ * the whole lattice math into a WebCrypto-only entrypoint.
15
+ * @module
16
+ */
17
+ /*! noble-post-quantum - MIT License (c) 2024 Paul Miller (paulmillr.com) */
18
+ import { type TArg, type TRet } from './utils.ts';
19
+ type MLKEMName = 'ML-KEM-512' | 'ML-KEM-768' | 'ML-KEM-1024';
20
+ /** Byte lengths for a WebCrypto wrapper's serialized keys and ciphertexts. */
21
+ type KEMLengths = {
22
+ /** Deterministic key-generation seed length. */
23
+ seed: number;
24
+ /**
25
+ * Raw seed private-key length. Note this is the *seed*, not the expanded decapsulation key:
26
+ * for ML-KEM the synchronous `lengths.secretKey` is much larger (1632 / 2400 / 3168 bytes), so
27
+ * these private keys only fit the synchronous `keygen(seed)`, never its `decapsulate(ct, sk)`.
28
+ */
29
+ secretKey: number;
30
+ /** Serialized public-key length. */
31
+ publicKey: number;
32
+ /** Encapsulated ciphertext length. */
33
+ cipherText: number;
34
+ };
35
+ /** Async KEM interface backed by the current runtime's WebCrypto implementation. */
36
+ export type WebCryptoKEM = {
37
+ /** WebCrypto algorithm name passed to `crypto.subtle`. */
38
+ webCryptoName: string;
39
+ /** Byte lengths for this WebCrypto wrapper's serialized keys and ciphertexts. */
40
+ lengths: KEMLengths;
41
+ /**
42
+ * Checks whether the runtime implements the complete WebCrypto surface used by this wrapper.
43
+ * Probes with a real key generation and encapsulation round-trip, and memoizes the result.
44
+ * @returns Whether key generation, serialization, encapsulation, and decapsulation are supported.
45
+ */
46
+ isSupported(): Promise<boolean>;
47
+ /**
48
+ * Generates a KEM key pair.
49
+ * @param seed - Optional raw seed for deterministic key generation.
50
+ * @returns Raw seed private key and serialized public key.
51
+ */
52
+ keygen(seed?: TArg<Uint8Array>): TRet<Promise<{
53
+ secretKey: Uint8Array;
54
+ publicKey: Uint8Array;
55
+ }>>;
56
+ /**
57
+ * Derives a serialized public key from a raw seed private key.
58
+ * @param secretKey - Raw seed private key.
59
+ * @returns Serialized public key.
60
+ */
61
+ getPublicKey(secretKey: TArg<Uint8Array>): TRet<Promise<Uint8Array>>;
62
+ /**
63
+ * Encapsulates a new random shared secret to a serialized public key.
64
+ * @param publicKey - Recipient public key.
65
+ * @returns Ciphertext and 32-byte shared secret.
66
+ */
67
+ encapsulate(publicKey: TArg<Uint8Array>): TRet<Promise<{
68
+ cipherText: Uint8Array;
69
+ sharedSecret: Uint8Array;
70
+ }>>;
71
+ /**
72
+ * Decapsulates a ciphertext with a raw seed private key.
73
+ * @param cipherText - Encapsulated ciphertext bytes.
74
+ * @param secretKey - Private key in WebCrypto `raw-seed` format.
75
+ * @returns Decapsulated 32-byte shared secret.
76
+ */
77
+ decapsulate(cipherText: TArg<Uint8Array>, secretKey: TArg<Uint8Array>): TRet<Promise<Uint8Array>>;
78
+ };
79
+ /** Async ML-KEM interface backed by the current runtime's WebCrypto implementation. */
80
+ export type WebCryptoMLKEM = WebCryptoKEM & {
81
+ webCryptoName: MLKEMName;
82
+ };
83
+ /** WebCrypto ML-KEM-512 wrapper. */
84
+ export declare const ml_kem512: TRet<WebCryptoMLKEM>;
85
+ /** WebCrypto ML-KEM-768 wrapper. */
86
+ export declare const ml_kem768: TRet<WebCryptoMLKEM>;
87
+ /** WebCrypto ML-KEM-1024 wrapper. */
88
+ export declare const ml_kem1024: TRet<WebCryptoMLKEM>;
89
+ /** WebCrypto ML-KEM-768 + X25519 (X-Wing) wrapper. */
90
+ export declare const ml_kem768_x25519: TRet<WebCryptoKEM>;
91
+ export {};