@noble/post-quantum 0.5.3 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/utils.ts CHANGED
@@ -9,14 +9,60 @@ import {
9
9
  abytes,
10
10
  abytes as abytes_,
11
11
  concatBytes,
12
- isBytes,
12
+ isLE,
13
13
  randomBytes as randb,
14
14
  } from '@noble/hashes/utils.js';
15
- export { abytes } from '@noble/hashes/utils.js';
16
- export { concatBytes };
15
+ /**
16
+ * Asserts that a value is a byte array and optionally checks its length.
17
+ * Returns the original reference unchanged on success, and currently also accepts Node `Buffer`
18
+ * values through the upstream validator.
19
+ * This helper throws on malformed input, so APIs that must return `false` need to guard lengths
20
+ * before decoding or before calling it.
21
+ * @example
22
+ * Validate that a value is a byte array with the expected length.
23
+ * ```ts
24
+ * abytes(new Uint8Array([1]), 1);
25
+ * ```
26
+ */
27
+ const abytesDoc: typeof abytes = abytes;
28
+ export { abytesDoc as abytes };
29
+ /**
30
+ * Concatenates byte arrays into a new `Uint8Array`.
31
+ * Zero arguments return an empty `Uint8Array`.
32
+ * Invalid segments throw before allocation because each argument is validated first.
33
+ * @example
34
+ * Concatenate two byte arrays into one result.
35
+ * ```ts
36
+ * concatBytes(new Uint8Array([1]), new Uint8Array([2]));
37
+ * ```
38
+ */
39
+ const concatBytesDoc: typeof concatBytes = concatBytes;
40
+ export { concatBytesDoc as concatBytes };
41
+ /**
42
+ * Returns cryptographically secure random bytes.
43
+ * Requires `globalThis.crypto.getRandomValues` and throws if that API is unavailable.
44
+ * `bytesLength` is validated by the upstream helper as a non-negative integer before allocation,
45
+ * so negative and fractional values both throw instead of truncating through JS `ToIndex`.
46
+ * @example
47
+ * Generate a fresh random seed.
48
+ * ```ts
49
+ * const seed = randomBytes(4);
50
+ * ```
51
+ */
17
52
  export const randomBytes: typeof randb = randb;
18
53
 
19
- // Compares 2 u8a-s in kinda constant time
54
+ /**
55
+ * Compares two byte arrays in a length-constant way for equal lengths.
56
+ * Unequal lengths return `false` immediately, and there is no runtime type validation.
57
+ * @param a - First byte array.
58
+ * @param b - Second byte array.
59
+ * @returns Whether both arrays contain the same bytes.
60
+ * @example
61
+ * Compare two byte arrays for equality.
62
+ * ```ts
63
+ * equalBytes(new Uint8Array([1]), new Uint8Array([1]));
64
+ * ```
65
+ */
20
66
  export function equalBytes(a: Uint8Array, b: Uint8Array): boolean {
21
67
  if (a.length !== b.length) return false;
22
68
  let diff = 0;
@@ -24,52 +70,170 @@ export function equalBytes(a: Uint8Array, b: Uint8Array): boolean {
24
70
  return diff === 0;
25
71
  }
26
72
 
27
- // copy bytes to new u8a (aligned). Because Buffer.slice is broken.
73
+ /**
74
+ * Copies bytes into a fresh `Uint8Array`.
75
+ * Returns a detached plain `Uint8Array`, and currently accepts broader array-like / iterable
76
+ * inputs because it delegates directly to `Uint8Array.from(...)`.
77
+ * @param bytes - Source bytes.
78
+ * @returns Copy of the input bytes.
79
+ * @example
80
+ * Copy bytes into a fresh array.
81
+ * ```ts
82
+ * copyBytes(new Uint8Array([1, 2]));
83
+ * ```
84
+ */
28
85
  export function copyBytes(bytes: Uint8Array): Uint8Array {
29
86
  return Uint8Array.from(bytes);
30
87
  }
31
88
 
89
+ /**
90
+ * Byte-swaps each 64-bit lane in place.
91
+ * Falcon's exact binary64 tables are stored as little-endian byte payloads, so BE runtimes need
92
+ * this boundary helper before aliasing them as host `Float64Array` lanes.
93
+ * @param arr - Byte buffer whose length is a multiple of 8.
94
+ * @returns The same buffer after in-place 64-bit lane byte swaps.
95
+ */
96
+ export function byteSwap64<T extends ArrayBufferView>(arr: T): T {
97
+ const bytes = new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength);
98
+ for (let i = 0; i < bytes.length; i += 8) {
99
+ const a0 = bytes[i + 0];
100
+ const a1 = bytes[i + 1];
101
+ const a2 = bytes[i + 2];
102
+ const a3 = bytes[i + 3];
103
+ bytes[i + 0] = bytes[i + 7];
104
+ bytes[i + 1] = bytes[i + 6];
105
+ bytes[i + 2] = bytes[i + 5];
106
+ bytes[i + 3] = bytes[i + 4];
107
+ bytes[i + 4] = a3;
108
+ bytes[i + 5] = a2;
109
+ bytes[i + 6] = a1;
110
+ bytes[i + 7] = a0;
111
+ }
112
+ return arr;
113
+ }
114
+ export const baswap64If: <T extends ArrayBufferView>(arr: T) => T = isLE
115
+ ? (arr) => arr
116
+ : byteSwap64;
117
+
118
+ /** Shared key-generation surface for signers and KEMs. */
32
119
  export type CryptoKeys = {
120
+ /** Optional metadata about the algorithm family or variant. */
33
121
  info?: { type?: string };
122
+ /** Public byte lengths for the exported key material. */
34
123
  lengths: { seed?: number; publicKey?: number; secretKey?: number };
124
+ /**
125
+ * Generate one secret/public keypair.
126
+ * @param seed - Optional seed bytes for deterministic key generation.
127
+ * @returns Fresh secret/public keypair.
128
+ */
35
129
  keygen: (seed?: Uint8Array) => { secretKey: Uint8Array; publicKey: Uint8Array };
130
+ /**
131
+ * Derive one public key from a secret key.
132
+ * @param secretKey - Secret key bytes.
133
+ * @returns Public key bytes.
134
+ */
36
135
  getPublicKey: (secretKey: Uint8Array) => Uint8Array;
37
136
  };
38
137
 
138
+ /** Verification options shared by the signature APIs. */
39
139
  export type VerOpts = {
140
+ /** Optional application-defined context string. */
40
141
  context?: Uint8Array;
41
142
  };
143
+ /** Signing options shared by the signature APIs. */
42
144
  export type SigOpts = VerOpts & {
43
145
  // Compatibility with @noble/curves: false to disable, enabled by default, user can pass U8A
146
+ /** Optional extra entropy or `false` to disable randomized signing. */
44
147
  extraEntropy?: Uint8Array | false;
45
148
  };
46
149
 
150
+ /**
151
+ * Validates that an options bag is a plain object.
152
+ * @param opts - Options object to validate.
153
+ * @throws On wrong argument types. {@link TypeError}
154
+ * @example
155
+ * Validate that an options bag is a plain object.
156
+ * ```ts
157
+ * validateOpts({});
158
+ * ```
159
+ */
47
160
  export function validateOpts(opts: object): void {
48
- // We try to catch u8a, since it was previously valid argument at this position
49
- if (typeof opts !== 'object' || opts === null || isBytes(opts))
50
- throw new Error('expected opts to be an object');
161
+ // Arrays silently passed here before, but these call sites expect named option-bag fields.
162
+ if (Object.prototype.toString.call(opts) !== '[object Object]')
163
+ throw new TypeError('expected valid options object');
51
164
  }
52
165
 
166
+ /**
167
+ * Validates common verification options.
168
+ * `context` itself is validated with `abytes(...)`, and individual algorithms may narrow support
169
+ * further after this shared plain-object gate.
170
+ * @param opts - Verification options. See {@link VerOpts}.
171
+ * @throws On wrong argument types. {@link TypeError}
172
+ * @example
173
+ * Validate common verification options.
174
+ * ```ts
175
+ * validateVerOpts({ context: new Uint8Array([1]) });
176
+ * ```
177
+ */
53
178
  export function validateVerOpts(opts: VerOpts): void {
54
179
  validateOpts(opts);
55
180
  if (opts.context !== undefined) abytes(opts.context, undefined, 'opts.context');
56
181
  }
57
182
 
183
+ /**
184
+ * Validates common signing options.
185
+ * `extraEntropy` is validated with `abytes(...)`; exact lengths and extra algorithm-specific
186
+ * restrictions are enforced later by callers.
187
+ * @param opts - Signing options. See {@link SigOpts}.
188
+ * @throws On wrong argument types. {@link TypeError}
189
+ * @example
190
+ * Validate common signing options.
191
+ * ```ts
192
+ * validateSigOpts({ extraEntropy: new Uint8Array([1]) });
193
+ * ```
194
+ */
58
195
  export function validateSigOpts(opts: SigOpts): void {
59
196
  validateVerOpts(opts);
60
197
  if (opts.extraEntropy !== false && opts.extraEntropy !== undefined)
61
198
  abytes(opts.extraEntropy, undefined, 'opts.extraEntropy');
62
199
  }
63
200
 
64
- /** Generic interface for signatures. Has keygen, sign and verify. */
201
+ /** Generic signature interface with key generation, signing, and verification. */
65
202
  export type Signer = CryptoKeys & {
203
+ /** Public byte lengths for signatures and signing randomness. */
66
204
  lengths: { signRand?: number; signature?: number };
205
+ /**
206
+ * Sign one message.
207
+ * @param msg - Message bytes to sign.
208
+ * @param secretKey - Secret key bytes.
209
+ * @param opts - Optional signing options.
210
+ * @returns Signature bytes.
211
+ */
67
212
  sign: (msg: Uint8Array, secretKey: Uint8Array, opts?: SigOpts) => Uint8Array;
213
+ /**
214
+ * Verify one signature.
215
+ * @param sig - Signature bytes.
216
+ * @param msg - Signed message bytes.
217
+ * @param publicKey - Public key bytes.
218
+ * @param opts - Optional verification options.
219
+ * @returns `true` when the signature is valid, `false` when all inputs are well-formed but the
220
+ * signature check does not pass. Some implementations also treat malformed signature encodings as
221
+ * a verification failure and return `false`.
222
+ * @throws On malformed API arguments or unsupported verification options.
223
+ */
68
224
  verify: (sig: Uint8Array, msg: Uint8Array, publicKey: Uint8Array, opts?: VerOpts) => boolean;
69
225
  };
70
226
 
227
+ /** Generic key encapsulation mechanism interface. */
71
228
  export type KEM = CryptoKeys & {
229
+ /** Public byte lengths for ciphertexts and optional message randomness. */
72
230
  lengths: { cipherText?: number; msg?: number; msgRand?: number };
231
+ /**
232
+ * Encapsulate one shared secret to a recipient public key.
233
+ * @param publicKey - Recipient public key bytes.
234
+ * @param msg - Optional caller-provided randomness/message seed.
235
+ * @returns Ciphertext plus shared secret.
236
+ */
73
237
  encapsulate: (
74
238
  publicKey: Uint8Array,
75
239
  msg?: Uint8Array
@@ -77,19 +241,48 @@ export type KEM = CryptoKeys & {
77
241
  cipherText: Uint8Array;
78
242
  sharedSecret: Uint8Array;
79
243
  };
244
+ /**
245
+ * Recover the shared secret from a ciphertext and recipient secret key.
246
+ * @param cipherText - Ciphertext bytes.
247
+ * @param secretKey - Recipient secret key bytes.
248
+ * @returns Decapsulated shared secret.
249
+ */
80
250
  decapsulate: (cipherText: Uint8Array, secretKey: Uint8Array) => Uint8Array;
81
251
  };
82
252
 
253
+ /** Bidirectional encoder/decoder interface. */
83
254
  export interface Coder<F, T> {
255
+ /**
256
+ * Serialize one value.
257
+ * @param from - Value to encode.
258
+ * @returns Encoded representation.
259
+ */
84
260
  encode(from: F): T;
261
+ /**
262
+ * Parse one serialized value.
263
+ * @param to - Encoded representation.
264
+ * @returns Decoded value.
265
+ */
85
266
  decode(to: T): F;
86
267
  }
87
268
 
269
+ /** Encoder/decoder interface specialized for byte arrays. */
88
270
  export interface BytesCoder<T> extends Coder<T, Uint8Array> {
271
+ /**
272
+ * Serialize one value into bytes.
273
+ * @param data - Value to encode.
274
+ * @returns Encoded bytes.
275
+ */
89
276
  encode: (data: T) => Uint8Array;
277
+ /**
278
+ * Parse one byte array into a value.
279
+ * @param bytes - Encoded bytes.
280
+ * @returns Decoded value.
281
+ */
90
282
  decode: (bytes: Uint8Array) => T;
91
283
  }
92
284
 
285
+ /** Fixed-length byte encoder/decoder. */
93
286
  export type BytesCoderLen<T> = BytesCoder<T> & { bytesLen: number };
94
287
 
95
288
  // nano-packed, because struct encoding is hard.
@@ -97,6 +290,21 @@ type UnCoder<T> = T extends BytesCoder<infer U> ? U : never;
97
290
  type SplitOut<T extends (number | BytesCoderLen<any>)[]> = {
98
291
  [K in keyof T]: T[K] extends number ? Uint8Array : UnCoder<T[K]>;
99
292
  };
293
+ /**
294
+ * Builds a fixed-layout coder from byte lengths and nested coders.
295
+ * Raw-length fields decode as zero-copy `subarray(...)` views, and nested coders may preserve that
296
+ * aliasing too. Nested coder `encode(...)` results are treated as owned scratch: `splitCoder`
297
+ * copies them into the output and then zeroizes them with `fill(0)`. If a nested encoder forwards
298
+ * caller-owned bytes, it must do so only after detaching them into a disposable copy.
299
+ * @param label - Label used in validation errors.
300
+ * @param lengths - Field lengths or nested coders.
301
+ * @returns Composite fixed-length coder.
302
+ * @example
303
+ * Build a fixed-layout coder from byte lengths and nested coders.
304
+ * ```ts
305
+ * splitCoder('demo', 1, 2).encode([new Uint8Array([1]), new Uint8Array([2, 3])]);
306
+ * ```
307
+ */
100
308
  export function splitCoder<T extends (number | BytesCoderLen<any>)[]>(
101
309
  label: string,
102
310
  ...lengths: T
@@ -132,13 +340,32 @@ export function splitCoder<T extends (number | BytesCoderLen<any>)[]>(
132
340
  } as any;
133
341
  }
134
342
  // nano-packed.array (fixed size)
343
+ /**
344
+ * Builds a fixed-length vector coder from another fixed-length coder.
345
+ * Element decoding receives `subarray(...)` views, so aliasing depends on the element coder.
346
+ * Element coder `encode(...)` results are treated as owned scratch: `vecCoder` copies them into
347
+ * the output and then zeroizes them with `fill(0)`. If an element encoder forwards caller-owned
348
+ * bytes, it must do so only after detaching them into a disposable copy. `vecCoder` also trusts
349
+ * the `BytesCoderLen` contract: each encoded element must already be exactly `c.bytesLen` bytes.
350
+ * @param c - Element coder.
351
+ * @param vecLen - Number of elements in the vector.
352
+ * @returns Fixed-length vector coder.
353
+ * @example
354
+ * Build a fixed-length vector coder from another fixed-length coder.
355
+ * ```ts
356
+ * vecCoder(
357
+ * { bytesLen: 1, encode: (n: number) => Uint8Array.of(n), decode: (b: Uint8Array) => b[0] || 0 },
358
+ * 2
359
+ * ).encode([1, 2]);
360
+ * ```
361
+ */
135
362
  export function vecCoder<T>(c: BytesCoderLen<T>, vecLen: number): BytesCoderLen<T[]> {
136
363
  const bytesLen = vecLen * c.bytesLen;
137
364
  return {
138
365
  bytesLen,
139
366
  encode: (u: T[]): Uint8Array => {
140
367
  if (u.length !== vecLen)
141
- throw new Error(`vecCoder.encode: wrong length=${u.length}. Expected: ${vecLen}`);
368
+ throw new RangeError(`vecCoder.encode: wrong length=${u.length}. Expected: ${vecLen}`);
142
369
  const res = new Uint8Array(bytesLen);
143
370
  for (let i = 0, pos = 0; i < u.length; i++) {
144
371
  const b = c.encode(u[i]);
@@ -158,7 +385,17 @@ export function vecCoder<T>(c: BytesCoderLen<T>, vecLen: number): BytesCoderLen<
158
385
  };
159
386
  }
160
387
 
161
- // cleanBytes(Uint8Array.of(), [Uint16Array.of(), Uint32Array.of()])
388
+ /**
389
+ * Overwrites supported typed-array inputs with zeroes in place.
390
+ * Accepts direct typed arrays and one-level arrays of them.
391
+ * @param list - Typed arrays or one-level lists of typed arrays to clear.
392
+ * @example
393
+ * Overwrite typed arrays with zeroes.
394
+ * ```ts
395
+ * const buf = Uint8Array.of(1, 2, 3);
396
+ * cleanBytes(buf);
397
+ * ```
398
+ */
162
399
  export function cleanBytes(...list: (TypedArray | TypedArray[])[]): void {
163
400
  for (const t of list) {
164
401
  if (Array.isArray(t)) for (const b of t) b.fill(0);
@@ -166,25 +403,75 @@ export function cleanBytes(...list: (TypedArray | TypedArray[])[]): void {
166
403
  }
167
404
  }
168
405
 
406
+ /**
407
+ * Creates a 32-bit mask with the lowest `bits` bits set.
408
+ * @param bits - Number of low bits to keep.
409
+ * @returns Bit mask with `bits` ones.
410
+ * @example
411
+ * Create a low-bit mask for packed-field operations.
412
+ * ```ts
413
+ * const mask = getMask(4);
414
+ * ```
415
+ */
169
416
  export function getMask(bits: number): number {
170
- return (1 << bits) - 1; // 4 -> 0b1111
417
+ if (!Number.isSafeInteger(bits) || bits < 0 || bits > 32)
418
+ throw new RangeError(`expected bits in [0..32], got ${bits}`);
419
+ // JS shifts are modulo 32, so bit 32 needs an explicit full-width mask.
420
+ return bits === 32 ? 0xffffffff : ~(-1 << bits) >>> 0;
171
421
  }
172
422
 
173
- export const EMPTY: Uint8Array = Uint8Array.of();
423
+ /** Shared empty byte array used as the default context. */
424
+ export const EMPTY: Uint8Array = /* @__PURE__ */ Uint8Array.of();
174
425
 
426
+ /**
427
+ * Builds the domain-separated message payload for the pure sign/verify paths.
428
+ * Context length `255` is valid; only `ctx.length > 255` is rejected.
429
+ * @param msg - Message bytes.
430
+ * @param ctx - Optional context bytes.
431
+ * @returns Domain-separated message payload.
432
+ * @throws On wrong argument ranges or values. {@link RangeError}
433
+ * @example
434
+ * Build the domain-separated payload before direct signing.
435
+ * ```ts
436
+ * const payload = getMessage(new Uint8Array([1, 2]));
437
+ * ```
438
+ */
175
439
  export function getMessage(msg: Uint8Array, ctx: Uint8Array = EMPTY): Uint8Array {
176
440
  abytes_(msg);
177
441
  abytes_(ctx);
178
- if (ctx.length > 255) throw new Error('context should be less than 255 bytes');
442
+ if (ctx.length > 255) throw new RangeError('context should be 255 bytes or less');
179
443
  return concatBytes(new Uint8Array([0, ctx.length]), ctx, msg);
180
444
  }
181
445
 
446
+ // DER tag+length plus the shared NIST hash OID arc 2.16.840.1.101.3.4.2.* used by the
447
+ // FIPS 204 / FIPS 205 pre-hash wrappers; the final byte selects SHA-256, SHA-512, SHAKE128,
448
+ // SHAKE256, or another approved hash/XOF under that subtree.
182
449
  // 06 09 60 86 48 01 65 03 04 02
183
450
  const oidNistP = /* @__PURE__ */ Uint8Array.from([6, 9, 0x60, 0x86, 0x48, 1, 0x65, 3, 4, 2]);
184
451
 
452
+ /**
453
+ * Validates that a hash exposes a NIST hash OID and enough collision resistance.
454
+ * Current accepted surface is broader than the FIPS algorithm tables: any hash/XOF under the NIST
455
+ * `2.16.840.1.101.3.4.2.*` subtree is accepted if its effective `outputLen` is strong enough.
456
+ * XOF callers must pass a callable whose `outputLen` matches the digest length they actually intend
457
+ * to sign; bare `shake128` / `shake256` defaults are too short for the stronger prehash modes.
458
+ * @param hash - Hash function to validate.
459
+ * @param requiredStrength - Minimum required collision-resistance strength in bits.
460
+ * @throws If the hash metadata or collision resistance is insufficient. {@link Error}
461
+ * @example
462
+ * Validate that a hash exposes a NIST hash OID and enough collision resistance.
463
+ * ```ts
464
+ * import { sha256 } from '@noble/hashes/sha2.js';
465
+ * import { checkHash } from '@noble/post-quantum/utils.js';
466
+ * checkHash(sha256, 128);
467
+ * ```
468
+ */
185
469
  export function checkHash(hash: CHash, requiredStrength: number = 0): void {
186
470
  if (!hash.oid || !equalBytes(hash.oid.subarray(0, 10), oidNistP))
187
471
  throw new Error('hash.oid is invalid: expected NIST hash');
472
+ // FIPS 204 / FIPS 205 require both collision and second-preimage strength; for approved NIST
473
+ // hashes/XOFs under this OID subtree, the collision bound from the configured digest length is
474
+ // the tighter runtime check, so enforce that lower bound here.
188
475
  const collisionResistance = (hash.outputLen * 8) / 2;
189
476
  if (requiredStrength > collisionResistance) {
190
477
  throw new Error(
@@ -196,6 +483,24 @@ export function checkHash(hash: CHash, requiredStrength: number = 0): void {
196
483
  }
197
484
  }
198
485
 
486
+ /**
487
+ * Builds the domain-separated prehash payload for the prehash sign/verify paths.
488
+ * Callers are expected to vet `hash.oid` first, e.g. via `checkHash(...)`; calling this helper
489
+ * directly with a hash object that lacks `oid` currently throws later inside `concatBytes(...)`.
490
+ * Context length `255` is valid; only `ctx.length > 255` is rejected.
491
+ * @param hash - Prehash function.
492
+ * @param msg - Message bytes.
493
+ * @param ctx - Optional context bytes.
494
+ * @returns Domain-separated prehash payload.
495
+ * @throws On wrong argument ranges or values. {@link RangeError}
496
+ * @example
497
+ * Build the domain-separated prehash payload for external hashing.
498
+ * ```ts
499
+ * import { sha256 } from '@noble/hashes/sha2.js';
500
+ * import { getMessagePrehash } from '@noble/post-quantum/utils.js';
501
+ * getMessagePrehash(sha256, new Uint8Array([1, 2]));
502
+ * ```
503
+ */
199
504
  export function getMessagePrehash(
200
505
  hash: CHash,
201
506
  msg: Uint8Array,
@@ -203,7 +508,7 @@ export function getMessagePrehash(
203
508
  ): Uint8Array {
204
509
  abytes_(msg);
205
510
  abytes_(ctx);
206
- if (ctx.length > 255) throw new Error('context should be less than 255 bytes');
511
+ if (ctx.length > 255) throw new RangeError('context should be 255 bytes or less');
207
512
  const hashed = hash(msg);
208
513
  return concatBytes(new Uint8Array([1, ctx.length]), ctx, hash.oid!, hashed);
209
514
  }