@noble/post-quantum 0.6.0 → 0.7.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
@@ -8,10 +8,118 @@ import {
8
8
  type TypedArray,
9
9
  abytes,
10
10
  abytes as abytes_,
11
+ ahash as ahash_,
12
+ anumber,
11
13
  concatBytes,
14
+ isBytes,
12
15
  isLE,
13
16
  randomBytes as randb,
14
17
  } from '@noble/hashes/utils.js';
18
+ /**
19
+ * Bytes API type helpers for old + new TypeScript.
20
+ *
21
+ * TS 5.6 has `Uint8Array`, while TS 5.9+ made it generic `Uint8Array<ArrayBuffer>`.
22
+ * We can't use specific return type, because TS 5.6 will error.
23
+ * We can't use generic return type, because most TS 5.9 software will expect specific type.
24
+ *
25
+ * Maps typed-array input leaves to broad forms.
26
+ * These are compatibility adapters, not ownership guarantees.
27
+ *
28
+ * - `TArg` keeps byte inputs broad.
29
+ * - `TRet` marks byte outputs for TS 5.6 and TS 5.9+ compatibility.
30
+ */
31
+ export type TypedArg<T> = T extends BigInt64Array
32
+ ? BigInt64Array
33
+ : T extends BigUint64Array
34
+ ? BigUint64Array
35
+ : T extends Float32Array
36
+ ? Float32Array
37
+ : T extends Float64Array
38
+ ? Float64Array
39
+ : T extends Int16Array
40
+ ? Int16Array
41
+ : T extends Int32Array
42
+ ? Int32Array
43
+ : T extends Int8Array
44
+ ? Int8Array
45
+ : T extends Uint16Array
46
+ ? Uint16Array
47
+ : T extends Uint32Array
48
+ ? Uint32Array
49
+ : T extends Uint8ClampedArray
50
+ ? Uint8ClampedArray
51
+ : T extends Uint8Array
52
+ ? Uint8Array
53
+ : never;
54
+ /** Maps typed-array output leaves to narrow TS-compatible forms. */
55
+ export type TypedRet<T> = T extends BigInt64Array
56
+ ? ReturnType<typeof BigInt64Array.of>
57
+ : T extends BigUint64Array
58
+ ? ReturnType<typeof BigUint64Array.of>
59
+ : T extends Float32Array
60
+ ? ReturnType<typeof Float32Array.of>
61
+ : T extends Float64Array
62
+ ? ReturnType<typeof Float64Array.of>
63
+ : T extends Int16Array
64
+ ? ReturnType<typeof Int16Array.of>
65
+ : T extends Int32Array
66
+ ? ReturnType<typeof Int32Array.of>
67
+ : T extends Int8Array
68
+ ? ReturnType<typeof Int8Array.of>
69
+ : T extends Uint16Array
70
+ ? ReturnType<typeof Uint16Array.of>
71
+ : T extends Uint32Array
72
+ ? ReturnType<typeof Uint32Array.of>
73
+ : T extends Uint8ClampedArray
74
+ ? ReturnType<typeof Uint8ClampedArray.of>
75
+ : T extends Uint8Array
76
+ ? ReturnType<typeof Uint8Array.of>
77
+ : never;
78
+ /** Recursively adapts byte-carrying API input types. See {@link TypedArg}. */
79
+ export type TArg<T> =
80
+ | T
81
+ | ([TypedArg<T>] extends [never]
82
+ ? T extends (...args: infer A) => infer R
83
+ ? ((...args: { [K in keyof A]: TRet<A[K]> }) => TArg<R>) & {
84
+ [K in keyof T]: T[K] extends (...args: any) => any ? T[K] : TArg<T[K]>;
85
+ }
86
+ : T extends [infer A, ...infer R]
87
+ ? [TArg<A>, ...{ [K in keyof R]: TArg<R[K]> }]
88
+ : T extends readonly [infer A, ...infer R]
89
+ ? readonly [TArg<A>, ...{ [K in keyof R]: TArg<R[K]> }]
90
+ : T extends (infer A)[]
91
+ ? TArg<A>[]
92
+ : T extends readonly (infer A)[]
93
+ ? readonly TArg<A>[]
94
+ : T extends Promise<infer A>
95
+ ? Promise<TArg<A>>
96
+ : T extends object
97
+ ? { [K in keyof T]: TArg<T[K]> }
98
+ : T
99
+ : TypedArg<T>);
100
+ /** Recursively adapts byte-carrying API output types. See {@link TypedArg}. */
101
+ export type TRet<T> = T extends unknown
102
+ ? T &
103
+ ([TypedRet<T>] extends [never]
104
+ ? T extends (...args: infer A) => infer R
105
+ ? ((...args: { [K in keyof A]: TArg<A[K]> }) => TRet<R>) & {
106
+ [K in keyof T]: T[K] extends (...args: any) => any ? T[K] : TRet<T[K]>;
107
+ }
108
+ : T extends [infer A, ...infer R]
109
+ ? [TRet<A>, ...{ [K in keyof R]: TRet<R[K]> }]
110
+ : T extends readonly [infer A, ...infer R]
111
+ ? readonly [TRet<A>, ...{ [K in keyof R]: TRet<R[K]> }]
112
+ : T extends (infer A)[]
113
+ ? TRet<A>[]
114
+ : T extends readonly (infer A)[]
115
+ ? readonly TRet<A>[]
116
+ : T extends Promise<infer A>
117
+ ? Promise<TRet<A>>
118
+ : T extends object
119
+ ? { [K in keyof T]: TRet<T[K]> }
120
+ : T
121
+ : TypedRet<T>)
122
+ : never;
15
123
  /**
16
124
  * Asserts that a value is a byte array and optionally checks its length.
17
125
  * Returns the original reference unchanged on success, and currently also accepts Node `Buffer`
@@ -43,6 +151,8 @@ export { concatBytesDoc as concatBytes };
43
151
  * Requires `globalThis.crypto.getRandomValues` and throws if that API is unavailable.
44
152
  * `bytesLength` is validated by the upstream helper as a non-negative integer before allocation,
45
153
  * so negative and fractional values both throw instead of truncating through JS `ToIndex`.
154
+ * @param bytesLength - Number of random bytes to generate.
155
+ * @returns Fresh random bytes.
46
156
  * @example
47
157
  * Generate a fresh random seed.
48
158
  * ```ts
@@ -51,9 +161,30 @@ export { concatBytesDoc as concatBytes };
51
161
  */
52
162
  export const randomBytes: typeof randb = randb;
53
163
 
164
+ export function aarray<T>(
165
+ item: unknown,
166
+ title: string,
167
+ inner: (elm: T, title: string) => void = () => {}
168
+ ): T[] {
169
+ if (!Array.isArray(item))
170
+ throw new TypeError(`"${title}" expected array, got type=${typeof item}`);
171
+ for (let i = 0; i < item.length; i++) inner(item[i], `${title}[${i}]`);
172
+ return item;
173
+ }
174
+
175
+ export function aobject<T extends object>(value: unknown, title = 'object'): T {
176
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
177
+ throw new TypeError(
178
+ title === 'object'
179
+ ? 'expected valid options object'
180
+ : `"${title}" expected object, got type=${typeof value}`
181
+ );
182
+ return value as T;
183
+ }
184
+
54
185
  /**
55
186
  * 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.
187
+ * Inputs are validated as byte arrays; unequal lengths return `false` immediately.
57
188
  * @param a - First byte array.
58
189
  * @param b - Second byte array.
59
190
  * @returns Whether both arrays contain the same bytes.
@@ -63,7 +194,9 @@ export const randomBytes: typeof randb = randb;
63
194
  * equalBytes(new Uint8Array([1]), new Uint8Array([1]));
64
195
  * ```
65
196
  */
66
- export function equalBytes(a: Uint8Array, b: Uint8Array): boolean {
197
+ export function equalBytes(a: TArg<Uint8Array>, b: TArg<Uint8Array>): boolean {
198
+ a = abytes(a);
199
+ b = abytes(b);
67
200
  if (a.length !== b.length) return false;
68
201
  let diff = 0;
69
202
  for (let i = 0; i < a.length; i++) diff |= a[i] ^ b[i];
@@ -72,8 +205,7 @@ export function equalBytes(a: Uint8Array, b: Uint8Array): boolean {
72
205
 
73
206
  /**
74
207
  * 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(...)`.
208
+ * Returns a detached plain `Uint8Array` after validating that the input is real bytes.
77
209
  * @param bytes - Source bytes.
78
210
  * @returns Copy of the input bytes.
79
211
  * @example
@@ -82,8 +214,10 @@ export function equalBytes(a: Uint8Array, b: Uint8Array): boolean {
82
214
  * copyBytes(new Uint8Array([1, 2]));
83
215
  * ```
84
216
  */
85
- export function copyBytes(bytes: Uint8Array): Uint8Array {
86
- return Uint8Array.from(bytes);
217
+ export function copyBytes(bytes: TArg<Uint8Array>): TRet<Uint8Array> {
218
+ // `Uint8Array.from(...)` would also accept arrays / other typed arrays. Keep this helper strict
219
+ // because callers use it at byte-validation boundaries before mutating the detached copy.
220
+ return Uint8Array.from(abytes(bytes)) as TRet<Uint8Array>;
87
221
  }
88
222
 
89
223
  /**
@@ -92,6 +226,11 @@ export function copyBytes(bytes: Uint8Array): Uint8Array {
92
226
  * this boundary helper before aliasing them as host `Float64Array` lanes.
93
227
  * @param arr - Byte buffer whose length is a multiple of 8.
94
228
  * @returns The same buffer after in-place 64-bit lane byte swaps.
229
+ * @example
230
+ * Byte-swap one 64-bit lane in place.
231
+ * ```ts
232
+ * byteSwap64(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]));
233
+ * ```
95
234
  */
96
235
  export function byteSwap64<T extends ArrayBufferView>(arr: T): T {
97
236
  const bytes = new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength);
@@ -111,6 +250,18 @@ export function byteSwap64<T extends ArrayBufferView>(arr: T): T {
111
250
  }
112
251
  return arr;
113
252
  }
253
+ /**
254
+ * Byte-swaps 64-bit lanes on big-endian runtimes and returns the input unchanged on little-endian.
255
+ * This keeps Falcon's binary64 tables in canonical little-endian order before aliasing them as
256
+ * `Float64Array` lanes on the current host.
257
+ * @param arr - Buffer to pass through or swap in place.
258
+ * @returns The same buffer, normalized for Falcon's little-endian table layout.
259
+ * @example
260
+ * Normalize one host-endian buffer for Falcon's float tables.
261
+ * ```ts
262
+ * baswap64If(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]));
263
+ * ```
264
+ */
114
265
  export const baswap64If: <T extends ArrayBufferView>(arr: T) => T = isLE
115
266
  ? (arr) => arr
116
267
  : byteSwap64;
@@ -126,13 +277,16 @@ export type CryptoKeys = {
126
277
  * @param seed - Optional seed bytes for deterministic key generation.
127
278
  * @returns Fresh secret/public keypair.
128
279
  */
129
- keygen: (seed?: Uint8Array) => { secretKey: Uint8Array; publicKey: Uint8Array };
280
+ keygen: (seed?: TArg<Uint8Array>) => {
281
+ secretKey: TRet<Uint8Array>;
282
+ publicKey: TRet<Uint8Array>;
283
+ };
130
284
  /**
131
285
  * Derive one public key from a secret key.
132
286
  * @param secretKey - Secret key bytes.
133
287
  * @returns Public key bytes.
134
288
  */
135
- getPublicKey: (secretKey: Uint8Array) => Uint8Array;
289
+ getPublicKey: (secretKey: TArg<Uint8Array>) => TRet<Uint8Array>;
136
290
  };
137
291
 
138
292
  /** Verification options shared by the signature APIs. */
@@ -159,8 +313,8 @@ export type SigOpts = VerOpts & {
159
313
  */
160
314
  export function validateOpts(opts: object): void {
161
315
  // 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');
316
+ if (isBytes(opts)) throw new TypeError('"opts" expected object, got Uint8Array');
317
+ aobject(opts, 'opts');
164
318
  }
165
319
 
166
320
  /**
@@ -175,7 +329,7 @@ export function validateOpts(opts: object): void {
175
329
  * validateVerOpts({ context: new Uint8Array([1]) });
176
330
  * ```
177
331
  */
178
- export function validateVerOpts(opts: VerOpts): void {
332
+ export function validateVerOpts(opts: TArg<VerOpts>): void {
179
333
  validateOpts(opts);
180
334
  if (opts.context !== undefined) abytes(opts.context, undefined, 'opts.context');
181
335
  }
@@ -192,7 +346,7 @@ export function validateVerOpts(opts: VerOpts): void {
192
346
  * validateSigOpts({ extraEntropy: new Uint8Array([1]) });
193
347
  * ```
194
348
  */
195
- export function validateSigOpts(opts: SigOpts): void {
349
+ export function validateSigOpts(opts: TArg<SigOpts>): void {
196
350
  validateVerOpts(opts);
197
351
  if (opts.extraEntropy !== false && opts.extraEntropy !== undefined)
198
352
  abytes(opts.extraEntropy, undefined, 'opts.extraEntropy');
@@ -209,7 +363,11 @@ export type Signer = CryptoKeys & {
209
363
  * @param opts - Optional signing options.
210
364
  * @returns Signature bytes.
211
365
  */
212
- sign: (msg: Uint8Array, secretKey: Uint8Array, opts?: SigOpts) => Uint8Array;
366
+ sign: (
367
+ msg: TArg<Uint8Array>,
368
+ secretKey: TArg<Uint8Array>,
369
+ opts?: TArg<SigOpts>
370
+ ) => TRet<Uint8Array>;
213
371
  /**
214
372
  * Verify one signature.
215
373
  * @param sig - Signature bytes.
@@ -221,7 +379,12 @@ export type Signer = CryptoKeys & {
221
379
  * a verification failure and return `false`.
222
380
  * @throws On malformed API arguments or unsupported verification options.
223
381
  */
224
- verify: (sig: Uint8Array, msg: Uint8Array, publicKey: Uint8Array, opts?: VerOpts) => boolean;
382
+ verify: (
383
+ sig: TArg<Uint8Array>,
384
+ msg: TArg<Uint8Array>,
385
+ publicKey: TArg<Uint8Array>,
386
+ opts?: TArg<VerOpts>
387
+ ) => boolean;
225
388
  };
226
389
 
227
390
  /** Generic key encapsulation mechanism interface. */
@@ -235,11 +398,11 @@ export type KEM = CryptoKeys & {
235
398
  * @returns Ciphertext plus shared secret.
236
399
  */
237
400
  encapsulate: (
238
- publicKey: Uint8Array,
239
- msg?: Uint8Array
401
+ publicKey: TArg<Uint8Array>,
402
+ msg?: TArg<Uint8Array>
240
403
  ) => {
241
- cipherText: Uint8Array;
242
- sharedSecret: Uint8Array;
404
+ cipherText: TRet<Uint8Array>;
405
+ sharedSecret: TRet<Uint8Array>;
243
406
  };
244
407
  /**
245
408
  * Recover the shared secret from a ciphertext and recipient secret key.
@@ -247,7 +410,7 @@ export type KEM = CryptoKeys & {
247
410
  * @param secretKey - Recipient secret key bytes.
248
411
  * @returns Decapsulated shared secret.
249
412
  */
250
- decapsulate: (cipherText: Uint8Array, secretKey: Uint8Array) => Uint8Array;
413
+ decapsulate: (cipherText: TArg<Uint8Array>, secretKey: TArg<Uint8Array>) => TRet<Uint8Array>;
251
414
  };
252
415
 
253
416
  /** Bidirectional encoder/decoder interface. */
@@ -308,8 +471,9 @@ type SplitOut<T extends (number | BytesCoderLen<any>)[]> = {
308
471
  export function splitCoder<T extends (number | BytesCoderLen<any>)[]>(
309
472
  label: string,
310
473
  ...lengths: T
311
- ): BytesCoder<SplitOut<T>> & { bytesLen: number } {
312
- const getLength = (c: number | BytesCoderLen<any>) => (typeof c === 'number' ? c : c.bytesLen);
474
+ ): TRet<BytesCoder<SplitOut<T>> & { bytesLen: number }> {
475
+ const getLength = (c: TArg<number | BytesCoderLen<any>>) =>
476
+ typeof c === 'number' ? c : (c as BytesCoderLen<any>).bytesLen;
313
477
  const bytesLen: number = lengths.reduce((sum: number, a) => sum + getLength(a), 0);
314
478
  return {
315
479
  bytesLen,
@@ -326,7 +490,7 @@ export function splitCoder<T extends (number | BytesCoderLen<any>)[]>(
326
490
  }
327
491
  return res;
328
492
  },
329
- decode: (buf: Uint8Array) => {
493
+ decode: (buf: TArg<Uint8Array>) => {
330
494
  abytes_(buf, bytesLen, label);
331
495
  const res = [];
332
496
  for (const c of lengths) {
@@ -359,30 +523,32 @@ export function splitCoder<T extends (number | BytesCoderLen<any>)[]>(
359
523
  * ).encode([1, 2]);
360
524
  * ```
361
525
  */
362
- export function vecCoder<T>(c: BytesCoderLen<T>, vecLen: number): BytesCoderLen<T[]> {
363
- const bytesLen = vecLen * c.bytesLen;
526
+ export function vecCoder<T>(c: TArg<BytesCoderLen<T>>, vecLen: number): TRet<BytesCoderLen<T[]>> {
527
+ const coder = c as BytesCoderLen<T>;
528
+ const bytesLen = vecLen * coder.bytesLen;
364
529
  return {
365
530
  bytesLen,
366
- encode: (u: T[]): Uint8Array => {
367
- if (u.length !== vecLen)
368
- throw new RangeError(`vecCoder.encode: wrong length=${u.length}. Expected: ${vecLen}`);
531
+ encode: (u: TArg<T[]>): TRet<Uint8Array> => {
532
+ const uArr = aarray<T>(u, 'u');
533
+ if (uArr.length !== vecLen)
534
+ throw new RangeError(`vecCoder.encode: wrong length=${uArr.length}. Expected: ${vecLen}`);
369
535
  const res = new Uint8Array(bytesLen);
370
- for (let i = 0, pos = 0; i < u.length; i++) {
371
- const b = c.encode(u[i]);
536
+ for (let i = 0, pos = 0; i < uArr.length; i++) {
537
+ const b = coder.encode(uArr[i] as T);
372
538
  res.set(b, pos);
373
539
  b.fill(0); // clean
374
540
  pos += b.length;
375
541
  }
376
- return res;
542
+ return res as TRet<Uint8Array>;
377
543
  },
378
- decode: (a: Uint8Array): T[] => {
544
+ decode: (a: TArg<Uint8Array>): TRet<T[]> => {
379
545
  abytes_(a, bytesLen);
380
546
  const r: T[] = [];
381
- for (let i = 0; i < a.length; i += c.bytesLen)
382
- r.push(c.decode(a.subarray(i, i + c.bytesLen)));
383
- return r;
547
+ for (let i = 0; i < a.length; i += coder.bytesLen)
548
+ r.push(coder.decode(a.subarray(i, i + coder.bytesLen)));
549
+ return r as TRet<T[]>;
384
550
  },
385
- };
551
+ } as any;
386
552
  }
387
553
 
388
554
  /**
@@ -407,6 +573,8 @@ export function cleanBytes(...list: (TypedArray | TypedArray[])[]): void {
407
573
  * Creates a 32-bit mask with the lowest `bits` bits set.
408
574
  * @param bits - Number of low bits to keep.
409
575
  * @returns Bit mask with `bits` ones.
576
+ * @throws On wrong argument types. {@link TypeError}
577
+ * @throws On wrong argument ranges or values. {@link RangeError}
410
578
  * @example
411
579
  * Create a low-bit mask for packed-field operations.
412
580
  * ```ts
@@ -414,14 +582,14 @@ export function cleanBytes(...list: (TypedArray | TypedArray[])[]): void {
414
582
  * ```
415
583
  */
416
584
  export function getMask(bits: number): number {
417
- if (!Number.isSafeInteger(bits) || bits < 0 || bits > 32)
418
- throw new RangeError(`expected bits in [0..32], got ${bits}`);
585
+ anumber(bits, 'bits');
586
+ if (bits > 32) throw new RangeError('"bits" expected <= 32, got ' + bits);
419
587
  // JS shifts are modulo 32, so bit 32 needs an explicit full-width mask.
420
588
  return bits === 32 ? 0xffffffff : ~(-1 << bits) >>> 0;
421
589
  }
422
590
 
423
591
  /** Shared empty byte array used as the default context. */
424
- export const EMPTY: Uint8Array = /* @__PURE__ */ Uint8Array.of();
592
+ export const EMPTY: TRet<Uint8Array> = /* @__PURE__ */ Uint8Array.of();
425
593
 
426
594
  /**
427
595
  * Builds the domain-separated message payload for the pure sign/verify paths.
@@ -436,9 +604,9 @@ export const EMPTY: Uint8Array = /* @__PURE__ */ Uint8Array.of();
436
604
  * const payload = getMessage(new Uint8Array([1, 2]));
437
605
  * ```
438
606
  */
439
- export function getMessage(msg: Uint8Array, ctx: Uint8Array = EMPTY): Uint8Array {
440
- abytes_(msg);
441
- abytes_(ctx);
607
+ export function getMessage(msg: TArg<Uint8Array>, ctx: TArg<Uint8Array> = EMPTY): TRet<Uint8Array> {
608
+ abytes_(msg, undefined, 'msg');
609
+ abytes_(ctx, undefined, 'ctx');
442
610
  if (ctx.length > 255) throw new RangeError('context should be 255 bytes or less');
443
611
  return concatBytes(new Uint8Array([0, ctx.length]), ctx, msg);
444
612
  }
@@ -467,8 +635,14 @@ const oidNistP = /* @__PURE__ */ Uint8Array.from([6, 9, 0x60, 0x86, 0x48, 1, 0x6
467
635
  * ```
468
636
  */
469
637
  export function checkHash(hash: CHash, requiredStrength: number = 0): void {
470
- if (!hash.oid || !equalBytes(hash.oid.subarray(0, 10), oidNistP))
471
- throw new Error('hash.oid is invalid: expected NIST hash');
638
+ if (typeof hash !== 'function' || typeof (hash as any).create !== 'function')
639
+ throw new TypeError('"hash" expected hash function, got type=' + typeof hash);
640
+ ahash_(hash);
641
+ anumber(requiredStrength, 'requiredStrength');
642
+ const oid = hash.oid as unknown as TArg<Uint8Array>;
643
+ abytes_(oid, undefined, 'hash.oid');
644
+ if (!equalBytes(oid.subarray(0, 10), oidNistP))
645
+ throw new Error('"hash.oid" is invalid: expected NIST hash');
472
646
  // FIPS 204 / FIPS 205 require both collision and second-preimage strength; for approved NIST
473
647
  // hashes/XOFs under this OID subtree, the collision bound from the configured digest length is
474
648
  // the tighter runtime check, so enforce that lower bound here.
@@ -503,12 +677,34 @@ export function checkHash(hash: CHash, requiredStrength: number = 0): void {
503
677
  */
504
678
  export function getMessagePrehash(
505
679
  hash: CHash,
506
- msg: Uint8Array,
507
- ctx: Uint8Array = EMPTY
508
- ): Uint8Array {
509
- abytes_(msg);
510
- abytes_(ctx);
680
+ msg: TArg<Uint8Array>,
681
+ ctx: TArg<Uint8Array> = EMPTY
682
+ ): TRet<Uint8Array> {
683
+ checkHash(hash);
684
+ abytes_(msg, undefined, 'msg');
685
+ abytes_(ctx, undefined, 'ctx');
511
686
  if (ctx.length > 255) throw new RangeError('context should be 255 bytes or less');
512
687
  const hashed = hash(msg);
513
688
  return concatBytes(new Uint8Array([1, ctx.length]), ctx, hash.oid!, hashed);
514
689
  }
690
+
691
+ /**
692
+ * Asserts something is a string.
693
+ * @param value - Value to validate.
694
+ * @param title - Label included in thrown errors.
695
+ * @returns The validated string.
696
+ * @throws On wrong argument types. {@link TypeError}
697
+ * @example
698
+ * Validate a label string.
699
+ *
700
+ * ```ts
701
+ * astring('example', 'label');
702
+ * ```
703
+ */
704
+ export function astring(value: unknown, title: string = ''): string {
705
+ if (typeof value !== 'string') {
706
+ const prefix = title && `"${title}" `;
707
+ throw new TypeError(prefix + 'expected string, got type=' + typeof value);
708
+ }
709
+ return value;
710
+ }