@noble/curves 2.0.1 → 2.2.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.
Files changed (110) hide show
  1. package/README.md +214 -122
  2. package/abstract/bls.d.ts +299 -16
  3. package/abstract/bls.d.ts.map +1 -1
  4. package/abstract/bls.js +82 -22
  5. package/abstract/bls.js.map +1 -1
  6. package/abstract/curve.d.ts +274 -27
  7. package/abstract/curve.d.ts.map +1 -1
  8. package/abstract/curve.js +177 -23
  9. package/abstract/curve.js.map +1 -1
  10. package/abstract/edwards.d.ts +166 -30
  11. package/abstract/edwards.d.ts.map +1 -1
  12. package/abstract/edwards.js +221 -86
  13. package/abstract/edwards.js.map +1 -1
  14. package/abstract/fft.d.ts +322 -10
  15. package/abstract/fft.d.ts.map +1 -1
  16. package/abstract/fft.js +154 -12
  17. package/abstract/fft.js.map +1 -1
  18. package/abstract/frost.d.ts +293 -0
  19. package/abstract/frost.d.ts.map +1 -0
  20. package/abstract/frost.js +704 -0
  21. package/abstract/frost.js.map +1 -0
  22. package/abstract/hash-to-curve.d.ts +173 -24
  23. package/abstract/hash-to-curve.d.ts.map +1 -1
  24. package/abstract/hash-to-curve.js +170 -31
  25. package/abstract/hash-to-curve.js.map +1 -1
  26. package/abstract/modular.d.ts +429 -37
  27. package/abstract/modular.d.ts.map +1 -1
  28. package/abstract/modular.js +414 -119
  29. package/abstract/modular.js.map +1 -1
  30. package/abstract/montgomery.d.ts +83 -12
  31. package/abstract/montgomery.d.ts.map +1 -1
  32. package/abstract/montgomery.js +32 -7
  33. package/abstract/montgomery.js.map +1 -1
  34. package/abstract/oprf.d.ts +164 -91
  35. package/abstract/oprf.d.ts.map +1 -1
  36. package/abstract/oprf.js +88 -29
  37. package/abstract/oprf.js.map +1 -1
  38. package/abstract/poseidon.d.ts +138 -7
  39. package/abstract/poseidon.d.ts.map +1 -1
  40. package/abstract/poseidon.js +178 -15
  41. package/abstract/poseidon.js.map +1 -1
  42. package/abstract/tower.d.ts +122 -3
  43. package/abstract/tower.d.ts.map +1 -1
  44. package/abstract/tower.js +323 -139
  45. package/abstract/tower.js.map +1 -1
  46. package/abstract/weierstrass.d.ts +339 -76
  47. package/abstract/weierstrass.d.ts.map +1 -1
  48. package/abstract/weierstrass.js +395 -205
  49. package/abstract/weierstrass.js.map +1 -1
  50. package/bls12-381.d.ts +16 -2
  51. package/bls12-381.d.ts.map +1 -1
  52. package/bls12-381.js +199 -209
  53. package/bls12-381.js.map +1 -1
  54. package/bn254.d.ts +11 -2
  55. package/bn254.d.ts.map +1 -1
  56. package/bn254.js +93 -38
  57. package/bn254.js.map +1 -1
  58. package/ed25519.d.ts +125 -14
  59. package/ed25519.d.ts.map +1 -1
  60. package/ed25519.js +202 -40
  61. package/ed25519.js.map +1 -1
  62. package/ed448.d.ts +108 -14
  63. package/ed448.d.ts.map +1 -1
  64. package/ed448.js +194 -42
  65. package/ed448.js.map +1 -1
  66. package/index.js +7 -1
  67. package/index.js.map +1 -1
  68. package/misc.d.ts +106 -7
  69. package/misc.d.ts.map +1 -1
  70. package/misc.js +141 -32
  71. package/misc.js.map +1 -1
  72. package/nist.d.ts +112 -11
  73. package/nist.d.ts.map +1 -1
  74. package/nist.js +139 -17
  75. package/nist.js.map +1 -1
  76. package/package.json +11 -6
  77. package/secp256k1.d.ts +92 -15
  78. package/secp256k1.d.ts.map +1 -1
  79. package/secp256k1.js +211 -28
  80. package/secp256k1.js.map +1 -1
  81. package/src/abstract/bls.ts +350 -67
  82. package/src/abstract/curve.ts +327 -44
  83. package/src/abstract/edwards.ts +367 -143
  84. package/src/abstract/fft.ts +369 -36
  85. package/src/abstract/frost.ts +1092 -0
  86. package/src/abstract/hash-to-curve.ts +255 -56
  87. package/src/abstract/modular.ts +591 -144
  88. package/src/abstract/montgomery.ts +114 -30
  89. package/src/abstract/oprf.ts +383 -194
  90. package/src/abstract/poseidon.ts +235 -35
  91. package/src/abstract/tower.ts +428 -159
  92. package/src/abstract/weierstrass.ts +710 -312
  93. package/src/bls12-381.ts +239 -236
  94. package/src/bn254.ts +107 -46
  95. package/src/ed25519.ts +227 -55
  96. package/src/ed448.ts +227 -57
  97. package/src/index.ts +7 -1
  98. package/src/misc.ts +154 -35
  99. package/src/nist.ts +143 -20
  100. package/src/secp256k1.ts +284 -41
  101. package/src/utils.ts +583 -81
  102. package/src/webcrypto.ts +302 -73
  103. package/utils.d.ts +457 -24
  104. package/utils.d.ts.map +1 -1
  105. package/utils.js +410 -53
  106. package/utils.js.map +1 -1
  107. package/webcrypto.d.ts +167 -25
  108. package/webcrypto.d.ts.map +1 -1
  109. package/webcrypto.js +165 -58
  110. package/webcrypto.js.map +1 -1
@@ -3,15 +3,36 @@
3
3
  * API may change at any time. The code has not been audited. Feature requests are welcome.
4
4
  * @module
5
5
  */
6
+ import type { TArg } from '../utils.ts';
6
7
  import type { IField } from './modular.ts';
7
8
 
9
+ /** Array-like coefficient storage that can be mutated in place. */
8
10
  export interface MutableArrayLike<T> {
11
+ /** Element access by numeric index. */
9
12
  [index: number]: T;
13
+ /** Current amount of stored coefficients. */
10
14
  length: number;
15
+ /**
16
+ * Return a sliced copy using the same storage shape.
17
+ * @param start - Inclusive start index.
18
+ * @param end - Exclusive end index.
19
+ * @returns Sliced copy.
20
+ */
11
21
  slice(start?: number, end?: number): this;
22
+ /**
23
+ * Iterate over stored coefficients in order.
24
+ * @returns Coefficient iterator.
25
+ */
12
26
  [Symbol.iterator](): Iterator<T>;
13
27
  }
14
28
 
29
+ /**
30
+ * Concrete polynomial containers accepted by the high-level `poly(...)` helpers.
31
+ * Lower-level FFT helpers can work with structural `MutableArrayLike`, but `poly(...)`
32
+ * intentionally keeps runtime dispatch on plain arrays and typed-array views.
33
+ */
34
+ export type PolyStorage<T> = T[] | (MutableArrayLike<T> & ArrayBufferView);
35
+
15
36
  function checkU32(n: number) {
16
37
  // 0xff_ff_ff_ff
17
38
  if (!Number.isSafeInteger(n) || n < 0 || n > 0xffffffff)
@@ -19,26 +40,77 @@ function checkU32(n: number) {
19
40
  return n;
20
41
  }
21
42
 
22
- /** Checks if integer is in form of `1 << X` */
43
+ /**
44
+ * Checks if integer is in form of `1 << X`.
45
+ * @param x - Integer to inspect.
46
+ * @returns `true` when the value is a power of two.
47
+ * @throws If `x` is not a valid unsigned 32-bit integer. {@link Error}
48
+ * @example
49
+ * Validate that an FFT size is a power of two.
50
+ *
51
+ * ```ts
52
+ * isPowerOfTwo(8);
53
+ * ```
54
+ */
23
55
  export function isPowerOfTwo(x: number): boolean {
24
56
  checkU32(x);
25
57
  return (x & (x - 1)) === 0 && x !== 0;
26
58
  }
27
59
 
60
+ /**
61
+ * @param n - Input value.
62
+ * @returns Next power of two within the u32/array-length domain.
63
+ * @throws If `n` is not a valid unsigned 32-bit integer. {@link Error}
64
+ * @example
65
+ * Round an integer up to the FFT size it needs.
66
+ *
67
+ * ```ts
68
+ * nextPowerOfTwo(9);
69
+ * ```
70
+ */
28
71
  export function nextPowerOfTwo(n: number): number {
29
72
  checkU32(n);
30
73
  if (n <= 1) return 1;
74
+ // FFT sizes here are used as JS array lengths, so `2^32` is not a meaningful result:
75
+ // keep the fast u32 bit-twiddling path and fail explicitly instead of wrapping to 1.
76
+ if (n > 0x8000_0000) throw new Error('nextPowerOfTwo overflow: result does not fit u32');
31
77
  return (1 << (log2(n - 1) + 1)) >>> 0;
32
78
  }
33
79
 
80
+ /**
81
+ * @param n - Value to reverse.
82
+ * @param bits - Number of bits to use.
83
+ * @returns Bit-reversed integer.
84
+ * @throws If `n` is not a valid unsigned 32-bit integer. {@link Error}
85
+ * @example
86
+ * Reverse the low `bits` bits of one index.
87
+ *
88
+ * ```ts
89
+ * reverseBits(3, 3);
90
+ * ```
91
+ */
34
92
  export function reverseBits(n: number, bits: number): number {
35
93
  checkU32(n);
94
+ if (!Number.isSafeInteger(bits) || bits < 0 || bits > 32)
95
+ throw new Error(`expected integer 0 <= bits <= 32, got ${bits}`);
36
96
  let reversed = 0;
37
97
  for (let i = 0; i < bits; i++, n >>>= 1) reversed = (reversed << 1) | (n & 1);
38
- return reversed;
98
+ // JS bitwise ops are signed i32; cast back so 32-bit reversals stay in the unsigned u32 domain.
99
+ return reversed >>> 0;
39
100
  }
40
101
 
41
- /** Similar to `bitLen(x)-1` but much faster for small integers, like indices */
102
+ /**
103
+ * Similar to `bitLen(x)-1` but much faster for small integers, like indices.
104
+ * @param n - Input value.
105
+ * @returns Base-2 logarithm. For `n = 0`, the current implementation returns `-1`.
106
+ * @throws If `n` is not a valid unsigned 32-bit integer. {@link Error}
107
+ * @example
108
+ * Compute the radix-2 stage count for one transform size.
109
+ *
110
+ * ```ts
111
+ * log2(8);
112
+ * ```
113
+ */
42
114
  export function log2(n: number): number {
43
115
  checkU32(n);
44
116
  return 31 - Math.clz32(n);
@@ -48,11 +120,21 @@ export function log2(n: number): number {
48
120
  * Moves lowest bit to highest position, which at first step splits
49
121
  * array on even and odd indices, then it applied again to each part,
50
122
  * which is core of fft
123
+ * @param values - Mutable coefficient array.
124
+ * @returns Mutated input array.
125
+ * @throws If the array length is not a positive power of two. {@link Error}
126
+ * @example
127
+ * Reorder coefficients into bit-reversed order in place.
128
+ *
129
+ * ```ts
130
+ * const values = Uint8Array.from([0, 1, 2, 3]);
131
+ * bitReversalInplace(values);
132
+ * ```
51
133
  */
52
134
  export function bitReversalInplace<T extends MutableArrayLike<any>>(values: T): T {
53
135
  const n = values.length;
54
- if (n < 2 || !isPowerOfTwo(n))
55
- throw new Error('n must be a power of 2 and greater than 1. Got ' + n);
136
+ // Size-1 FFT is the identity, so bit-reversal must stay a no-op there instead of rejecting it.
137
+ if (!isPowerOfTwo(n)) throw new Error('expected positive power-of-two length, got ' + n);
56
138
  const bits = log2(n);
57
139
  for (let i = 0; i < n; i++) {
58
140
  const j = reverseBits(i, bits);
@@ -65,27 +147,78 @@ export function bitReversalInplace<T extends MutableArrayLike<any>>(values: T):
65
147
  return values;
66
148
  }
67
149
 
150
+ /**
151
+ * @param values - Input values.
152
+ * @returns Reordered copy.
153
+ * @throws If the array length is not a positive power of two. {@link Error}
154
+ * @example
155
+ * Return a reordered copy instead of mutating the input in place.
156
+ *
157
+ * ```ts
158
+ * const reordered = bitReversalPermutation([0, 1, 2, 3]);
159
+ * ```
160
+ */
68
161
  export function bitReversalPermutation<T>(values: T[]): T[] {
69
162
  return bitReversalInplace(values.slice()) as T[];
70
163
  }
71
164
 
72
165
  const _1n = /** @__PURE__ */ BigInt(1);
73
- function findGenerator(field: IField<bigint>) {
166
+ function findGenerator(field: TArg<IField<bigint>>) {
74
167
  let G = BigInt(2);
75
168
  for (; field.eql(field.pow(G, field.ORDER >> _1n), field.ONE); G++);
76
169
  return G;
77
170
  }
78
171
 
172
+ /** Cached roots-of-unity tables derived from one finite field. */
79
173
  export type RootsOfUnity = {
174
+ /** Generator and 2-adicity metadata for the cached field. */
80
175
  info: { G: bigint; oddFactor: bigint; powerOfTwo: number };
176
+ /**
177
+ * Return the natural-order roots of unity for one radix-2 size.
178
+ * @param bits - Transform size as `log2(N)`.
179
+ * @returns Natural-order roots for that size.
180
+ */
81
181
  roots: (bits: number) => bigint[];
182
+ /**
183
+ * Return the bit-reversal permutation of the roots for one radix-2 size.
184
+ * @param bits - Transform size as `log2(N)`.
185
+ * @returns Bit-reversed roots.
186
+ */
82
187
  brp(bits: number): bigint[];
188
+ /**
189
+ * Return the inverse roots of unity for one radix-2 size.
190
+ * @param bits - Transform size as `log2(N)`.
191
+ * @returns Inverse roots.
192
+ */
83
193
  inverse(bits: number): bigint[];
194
+ /**
195
+ * Return one primitive root used by a radix-2 stage.
196
+ * @param bits - Transform size as `log2(N)`.
197
+ * @returns Primitive root for that stage.
198
+ */
84
199
  omega: (bits: number) => bigint;
200
+ /**
201
+ * Drop all cached root tables.
202
+ * @returns Nothing.
203
+ */
85
204
  clear: () => void;
86
205
  };
87
- /** We limit roots up to 2**31, which is a lot: 2-billion polynomimal should be rare. */
88
- export function rootsOfUnity(field: IField<bigint>, generator?: bigint): RootsOfUnity {
206
+ /**
207
+ * We limit roots up to 2**31, which is a lot: 2-billion polynomimal should be rare.
208
+ * @param field - Field implementation.
209
+ * @param generator - Optional generator override.
210
+ * @returns Roots-of-unity cache.
211
+ * @example
212
+ * Cache roots once, then ask for the omega table of one FFT size.
213
+ *
214
+ * ```ts
215
+ * import { rootsOfUnity } from '@noble/curves/abstract/fft.js';
216
+ * import { Field } from '@noble/curves/abstract/modular.js';
217
+ * const roots = rootsOfUnity(Field(17n));
218
+ * const omega = roots.omega(4);
219
+ * ```
220
+ */
221
+ export function rootsOfUnity(field: TArg<IField<bigint>>, generator?: bigint): RootsOfUnity {
89
222
  // Factor field.ORDER-1 as oddFactor * 2^powerOfTwo
90
223
  let oddFactor = field.ORDER - _1n;
91
224
  let powerOfTwo = 0;
@@ -118,6 +251,7 @@ export function rootsOfUnity(field: IField<bigint>, generator?: bigint): RootsOf
118
251
  };
119
252
  const brpCache = new Map<number, bigint[]>();
120
253
  const inverseCache = new Map<number, bigint[]>();
254
+ // roots()/brp()/inverse() expose shared cached arrays by reference for speed; callers must treat them as read-only.
121
255
 
122
256
  // NOTE: we use bits instead of power, because power = 2**bits,
123
257
  // but power is not neccesary isPowerOfTwo(power)!
@@ -149,41 +283,80 @@ export function rootsOfUnity(field: IField<bigint>, generator?: bigint): RootsOf
149
283
  clear: (): void => {
150
284
  rootsCache.splice(0, rootsCache.length);
151
285
  brpCache.clear();
286
+ inverseCache.clear();
152
287
  },
153
288
  };
154
289
  }
155
290
 
291
+ /** Polynomial coefficient container used by the FFT helpers. */
156
292
  export type Polynomial<T> = MutableArrayLike<T>;
157
293
 
158
294
  /**
295
+ * Arithmetic operations used by the generic FFT implementation.
296
+ *
159
297
  * Maps great to Field<bigint>, but not to Group (EC points):
160
298
  * - inv from scalar field
161
299
  * - we need multiplyUnsafe here, instead of multiply for speed
162
300
  * - multiplyUnsafe is safe in the context: we do mul(rootsOfUnity), which are public and sparse
163
301
  */
164
302
  export type FFTOpts<T, R> = {
303
+ /**
304
+ * Add two coefficients.
305
+ * @param a - Left coefficient.
306
+ * @param b - Right coefficient.
307
+ * @returns Sum coefficient.
308
+ */
165
309
  add: (a: T, b: T) => T;
310
+ /**
311
+ * Subtract two coefficients.
312
+ * @param a - Left coefficient.
313
+ * @param b - Right coefficient.
314
+ * @returns Difference coefficient.
315
+ */
166
316
  sub: (a: T, b: T) => T;
317
+ /**
318
+ * Multiply one coefficient by a scalar/root factor.
319
+ * @param a - Coefficient value.
320
+ * @param scalar - Scalar/root factor.
321
+ * @returns Scaled coefficient.
322
+ */
167
323
  mul: (a: T, scalar: R) => T;
324
+ /**
325
+ * Invert one scalar/root factor.
326
+ * @param a - Scalar/root factor.
327
+ * @returns Inverse factor.
328
+ */
168
329
  inv: (a: R) => R;
169
330
  };
170
331
 
332
+ /** Configuration for one low-level FFT loop. */
171
333
  export type FFTCoreOpts<R> = {
334
+ /** Transform size. Must be a power of two. */
172
335
  N: number;
336
+ /** Stage roots for the selected transform size. */
173
337
  roots: Polynomial<R>;
338
+ /** Whether to run the DIT variant instead of DIF. */
174
339
  dit: boolean;
340
+ /** Whether to invert butterfly placement for decode-oriented layouts. */
175
341
  invertButterflies?: boolean;
342
+ /** Number of initial stages to skip. */
176
343
  skipStages?: number;
344
+ /** Whether to apply bit-reversal permutation at the boundary. */
177
345
  brp?: boolean;
178
346
  };
179
347
 
348
+ /**
349
+ * Callable low-level FFT loop over one polynomial storage shape.
350
+ * @param values - Polynomial coefficients to transform in place.
351
+ * @returns The mutated input polynomial.
352
+ */
180
353
  export type FFTCoreLoop<T> = <P extends Polynomial<T>>(values: P) => P;
181
354
 
182
355
  /**
183
356
  * Constructs different flavors of FFT. radix2 implementation of low level mutating API. Flavors:
184
357
  *
185
- * - DIT (Decimation-in-Time): Bottom-Up (leaves -> root), Cool-Turkey
186
- * - DIF (Decimation-in-Frequency): Top-Down (root -> leaves), GentlemanSande
358
+ * - DIT (Decimation-in-Time): Bottom-Up (leaves to root), Cool-Turkey
359
+ * - DIF (Decimation-in-Frequency): Top-Down (root to leaves), Gentleman-Sande
187
360
  *
188
361
  * DIT takes brp input, returns natural output.
189
362
  * DIF takes natural input, returns brp output.
@@ -194,11 +367,35 @@ export type FFTCoreLoop<T> = <P extends Polynomial<T>>(values: P) => P;
194
367
  *
195
368
  * Cyclic NTT: Rq = Zq[x]/(x^n-1). butterfly_DIT+loop_DIT OR butterfly_DIF+loop_DIT, roots are omega
196
369
  * Negacyclic NTT: Rq = Zq[x]/(x^n+1). butterfly_DIT+loop_DIF, at least for mlkem / mldsa
370
+ * @param F - Field operations.
371
+ * @param coreOpts - FFT configuration:
372
+ * - `N`: Transform size. Must be a power of two.
373
+ * - `roots`: Stage roots for the selected transform size.
374
+ * - `dit`: Whether to run the DIT variant instead of DIF.
375
+ * - `invertButterflies` (optional): Whether to invert butterfly placement.
376
+ * - `skipStages` (optional): Number of initial stages to skip.
377
+ * - `brp` (optional): Whether to apply bit-reversal permutation at the boundary.
378
+ * @returns Low-level FFT loop.
379
+ * @throws If the FFT options or cached roots are invalid for the requested size. {@link Error}
380
+ * @example
381
+ * Constructs different flavors of FFT.
382
+ *
383
+ * ```ts
384
+ * import { FFTCore, rootsOfUnity } from '@noble/curves/abstract/fft.js';
385
+ * import { Field } from '@noble/curves/abstract/modular.js';
386
+ * const Fp = Field(17n);
387
+ * const roots = rootsOfUnity(Fp).roots(2);
388
+ * const loop = FFTCore(Fp, { N: 4, roots, dit: true });
389
+ * const values = loop([1n, 2n, 3n, 4n]);
390
+ * ```
197
391
  */
198
392
  export const FFTCore = <T, R>(F: FFTOpts<T, R>, coreOpts: FFTCoreOpts<R>): FFTCoreLoop<T> => {
199
393
  const { N, roots, dit, invertButterflies = false, skipStages = 0, brp = true } = coreOpts;
200
394
  const bits = log2(N);
201
395
  if (!isPowerOfTwo(N)) throw new Error('FFT: Polynomial size should be power of two');
396
+ // Wrong-sized root tables can stay in-bounds for some loop shapes and silently compute nonsense.
397
+ if (roots.length !== N)
398
+ throw new Error(`FFT: wrong roots length: expected ${N}, got ${roots.length}`);
202
399
  const isDit = dit !== invertButterflies;
203
400
  isDit;
204
401
  return <P extends Polynomial<T>>(values: P): P => {
@@ -240,14 +437,42 @@ export const FFTCore = <T, R>(F: FFTOpts<T, R>, coreOpts: FFTCoreOpts<R>): FFTCo
240
437
  };
241
438
  };
242
439
 
440
+ /** Forward and inverse FFT helpers for one coefficient domain. */
243
441
  export type FFTMethods<T> = {
442
+ /**
443
+ * Apply the forward transform.
444
+ * @param values - Polynomial coefficients to transform.
445
+ * @param brpInput - Whether the input is already bit-reversed.
446
+ * @param brpOutput - Whether to keep the output bit-reversed.
447
+ * @returns Transformed copy.
448
+ */
244
449
  direct<P extends Polynomial<T>>(values: P, brpInput?: boolean, brpOutput?: boolean): P;
450
+ /**
451
+ * Apply the inverse transform.
452
+ * @param values - Polynomial coefficients to transform.
453
+ * @param brpInput - Whether the input is already bit-reversed.
454
+ * @param brpOutput - Whether to keep the output bit-reversed.
455
+ * @returns Inverse-transformed copy.
456
+ */
245
457
  inverse<P extends Polynomial<T>>(values: P, brpInput?: boolean, brpOutput?: boolean): P;
246
458
  };
247
459
 
248
460
  /**
249
461
  * NTT aka FFT over finite field (NOT over complex numbers).
250
462
  * Naming mirrors other libraries.
463
+ * @param roots - Roots-of-unity cache.
464
+ * @param opts - Field operations. See {@link FFTOpts}.
465
+ * @returns Forward and inverse FFT helpers.
466
+ * @example
467
+ * NTT aka FFT over finite field (NOT over complex numbers).
468
+ *
469
+ * ```ts
470
+ * import { FFT, rootsOfUnity } from '@noble/curves/abstract/fft.js';
471
+ * import { Field } from '@noble/curves/abstract/modular.js';
472
+ * const Fp = Field(17n);
473
+ * const fft = FFT(rootsOfUnity(Fp), Fp);
474
+ * const values = fft.direct([1n, 2n, 3n, 4n]);
475
+ * ```
251
476
  */
252
477
  export function FFT<T>(roots: RootsOfUnity, opts: FFTOpts<T, bigint>): FFTMethods<T> {
253
478
  const getLoop = (
@@ -274,6 +499,7 @@ export function FFT<T>(roots: RootsOfUnity, opts: FFTOpts<T, bigint>): FFTMethod
274
499
  },
275
500
  inverse<P extends Polynomial<T>>(values: P, brpInput = false, brpOutput = false): P {
276
501
  const N = values.length;
502
+ if (!isPowerOfTwo(N)) throw new Error('FFT: Polynomial size should be power of two');
277
503
  const bits = log2(N);
278
504
  const res = getLoop(N, roots.inverse(bits), brpInput, brpOutput)(values.slice());
279
505
  const ivm = opts.inv(BigInt(values.length)); // scale
@@ -287,34 +513,114 @@ export function FFT<T>(roots: RootsOfUnity, opts: FFTOpts<T, bigint>): FFTMethod
287
513
  };
288
514
  }
289
515
 
290
- export type CreatePolyFn<P extends Polynomial<T>, T> = (len: number, elm?: T) => P;
516
+ /**
517
+ * Factory that allocates one polynomial storage container.
518
+ * Callers must ensure `_create(len)` returns field-zero-filled storage when `elm` is omitted,
519
+ * because the quadratic `mul()` / `convolve()` paths and the Kronecker-δ shortcut in
520
+ * `lagrange.basis()` rely on that default instead of always passing `field.ZERO` explicitly.
521
+ * @param len - Requested amount of coefficients.
522
+ * @param elm - Optional fill value.
523
+ * @returns Newly allocated polynomial container.
524
+ */
525
+ export type CreatePolyFn<P extends PolyStorage<T>, T> = (len: number, elm?: T) => P;
291
526
 
292
- export type PolyFn<P extends Polynomial<T>, T> = {
527
+ /** High-level polynomial helpers layered on top of FFT and field arithmetic. */
528
+ export type PolyFn<P extends PolyStorage<T>, T> = {
529
+ /** Roots-of-unity cache used by the helper namespace. */
293
530
  roots: RootsOfUnity;
531
+ /** Factory used to allocate new polynomial containers. */
294
532
  create: CreatePolyFn<P, T>;
295
- length?: number; // optional enforced size
533
+ /** Optional enforced polynomial length. */
534
+ length?: number;
296
535
 
536
+ /**
537
+ * Compute the polynomial degree.
538
+ * @param a - Polynomial coefficients.
539
+ * @returns Polynomial degree.
540
+ */
297
541
  degree: (a: P) => number;
542
+ /**
543
+ * Extend or truncate one polynomial to a requested length.
544
+ * @param a - Polynomial coefficients.
545
+ * @param len - Target length.
546
+ * @returns Resized polynomial.
547
+ */
298
548
  extend: (a: P, len: number) => P;
299
- add: (a: P, b: P) => P; // fc(x) = fa(x) + fb(x)
300
- sub: (a: P, b: P) => P; // fc(x) = fa(x) - fb(x)
301
- mul: (a: P, b: P | T) => P; // fc(x) = fa(x) * fb(x) OR fc(x) = fa(x) * scalar (same as field)
302
- dot: (a: P, b: P) => P; // point-wise coeff multiplication
549
+ /**
550
+ * Add two polynomials coefficient-wise.
551
+ * @param a - Left polynomial.
552
+ * @param b - Right polynomial.
553
+ * @returns Sum polynomial.
554
+ */
555
+ add: (a: P, b: P) => P;
556
+ /**
557
+ * Subtract two polynomials coefficient-wise.
558
+ * @param a - Left polynomial.
559
+ * @param b - Right polynomial.
560
+ * @returns Difference polynomial.
561
+ */
562
+ sub: (a: P, b: P) => P;
563
+ /**
564
+ * Multiply by another polynomial or by one scalar.
565
+ * @param a - Left polynomial.
566
+ * @param b - Right polynomial or scalar.
567
+ * @returns Product polynomial.
568
+ */
569
+ mul: (a: P, b: P | T) => P;
570
+ /**
571
+ * Multiply coefficients point-wise.
572
+ * @param a - Left polynomial.
573
+ * @param b - Right polynomial.
574
+ * @returns Point-wise product polynomial.
575
+ */
576
+ dot: (a: P, b: P) => P;
577
+ /**
578
+ * Multiply two polynomials with convolution.
579
+ * @param a - Left polynomial.
580
+ * @param b - Right polynomial.
581
+ * @returns Convolution product.
582
+ */
303
583
  convolve: (a: P, b: P) => P;
304
- shift: (p: P, factor: bigint) => P; // point-wise coeffcient shift
584
+ /**
585
+ * Apply a point-wise coefficient shift by powers of one factor.
586
+ * @param p - Polynomial coefficients.
587
+ * @param factor - Shift factor.
588
+ * @returns Shifted polynomial.
589
+ */
590
+ shift: (p: P, factor: bigint) => P;
591
+ /**
592
+ * Clone one polynomial container.
593
+ * @param a - Polynomial coefficients.
594
+ * @returns Cloned polynomial.
595
+ */
305
596
  clone: (a: P) => P;
306
- // Eval
307
- eval: (a: P, basis: P) => T; // y = fc(x)
597
+ /**
598
+ * Evaluate one polynomial on a basis vector.
599
+ * @param a - Polynomial coefficients.
600
+ * @param basis - Basis vector.
601
+ * @returns Evaluated field element.
602
+ */
603
+ eval: (a: P, basis: P) => T;
604
+ /** Helpers for monomial-basis polynomials. */
308
605
  monomial: {
606
+ /** Build the monomial basis vector for one evaluation point. */
309
607
  basis: (x: T, n: number) => P;
608
+ /** Evaluate a polynomial in the monomial basis. */
310
609
  eval: (a: P, x: T) => T;
311
610
  };
611
+ /** Helpers for Lagrange-basis polynomials. */
312
612
  lagrange: {
613
+ /** Build the Lagrange basis vector for one evaluation point. */
313
614
  basis: (x: T, n: number, brp?: boolean) => P;
615
+ /** Evaluate a polynomial in the Lagrange basis. */
314
616
  eval: (a: P, x: T, brp?: boolean) => T;
315
617
  };
316
- // Complex
317
- vanishing: (roots: P) => P; // f(x) = 0 for every x in roots
618
+ /**
619
+ * Build the vanishing polynomial for a root set.
620
+ * @param roots - Root set.
621
+ * @returns Vanishing polynomial.
622
+ */
623
+ vanishing: (roots: P) => P;
318
624
  };
319
625
 
320
626
  /**
@@ -328,37 +634,62 @@ export type PolyFn<P extends Polynomial<T>, T> = {
328
634
  * - **Monominal** is Polynomial where `basis[i](x) == x**i` (powers)
329
635
  * - **Array size** is domain size
330
636
  * - **Lattice** is matrix (Polynomial of Polynomials)
637
+ * @param field - Field implementation.
638
+ * @param roots - Roots-of-unity cache.
639
+ * @param create - Optional polynomial factory. Runtime input validation accepts only plain `Array`
640
+ * and typed-array polynomial containers; arbitrary structural wrappers are intentionally rejected.
641
+ * @param fft - Optional FFT implementation.
642
+ * @param length - Optional fixed polynomial length.
643
+ * @returns Polynomial helper namespace.
644
+ * @example
645
+ * Build polynomial helpers, then convolve two coefficient arrays.
646
+ *
647
+ * ```ts
648
+ * import { poly, rootsOfUnity } from '@noble/curves/abstract/fft.js';
649
+ * import { Field } from '@noble/curves/abstract/modular.js';
650
+ * const Fp = Field(17n);
651
+ * const poly17 = poly(Fp, rootsOfUnity(Fp));
652
+ * const product = poly17.convolve([1n, 2n], [3n, 4n]);
653
+ * ```
331
654
  */
332
655
  export function poly<T>(
333
- field: IField<T>,
656
+ field: TArg<IField<T>>,
334
657
  roots: RootsOfUnity,
335
658
  create?: undefined,
336
659
  fft?: FFTMethods<T>,
337
660
  length?: number
338
661
  ): PolyFn<T[], T>;
339
- export function poly<T, P extends Polynomial<T>>(
340
- field: IField<T>,
662
+ export function poly<T, P extends PolyStorage<T>>(
663
+ field: TArg<IField<T>>,
341
664
  roots: RootsOfUnity,
342
665
  create: CreatePolyFn<P, T>,
343
666
  fft?: FFTMethods<T>,
344
667
  length?: number
345
668
  ): PolyFn<P, T>;
346
- export function poly<T, P extends Polynomial<T>>(
347
- field: IField<T>,
669
+ export function poly<T, P extends PolyStorage<T>>(
670
+ field: TArg<IField<T>>,
348
671
  roots: RootsOfUnity,
349
672
  create?: CreatePolyFn<P, T>,
350
673
  fft?: FFTMethods<T>,
351
674
  length?: number
352
675
  ): PolyFn<any, T> {
353
- const F = field;
676
+ const F = field as IField<T>;
354
677
  const _create =
355
678
  create ||
356
- (((len: number, elm?: T): Polynomial<T> => new Array(len).fill(elm ?? F.ZERO)) as CreatePolyFn<
357
- P,
358
- T
359
- >);
679
+ (((len: number, elm?: T): T[] => new Array(len).fill(elm ?? F.ZERO)) as CreatePolyFn<P, T>);
360
680
 
361
- const isPoly = (x: any): x is P => Array.isArray(x) || ArrayBuffer.isView(x);
681
+ // `poly.mul(a, b)` distinguishes polynomial-vs-scalar at runtime, so keep accepted
682
+ // polynomial containers concrete instead of trying to support arbitrary wrappers.
683
+ const isPoly = (x: any): x is P => {
684
+ if (Array.isArray(x)) return true;
685
+ if (!ArrayBuffer.isView(x)) return false;
686
+ const v = x as unknown as ArrayLike<unknown> & { slice?: unknown; [Symbol.iterator]?: unknown };
687
+ return (
688
+ typeof v.length === 'number' &&
689
+ typeof v.slice === 'function' &&
690
+ typeof v[Symbol.iterator] === 'function'
691
+ );
692
+ };
362
693
  const checkLength = (...lst: P[]): number => {
363
694
  if (!lst.length) return 0;
364
695
  for (const i of lst) if (!isPoly(i)) throw new Error('poly: not polynomial: ' + i);
@@ -383,7 +714,9 @@ export function poly<T, P extends Polynomial<T>>(
383
714
  extend: (a: P, len: number): P => {
384
715
  checkLength(a);
385
716
  const out = _create(len, F.ZERO);
386
- for (let i = 0; i < a.length; i++) out[i] = a[i];
717
+ // Plain arrays grow when writing past `out.length`, so cap the copy explicitly to keep
718
+ // `extend()` consistent with typed arrays and with its documented truncate behavior.
719
+ for (let i = 0; i < Math.min(a.length, len); i++) out[i] = a[i];
387
720
  return out;
388
721
  },
389
722
  degree: (a: P): number => {
@@ -454,7 +787,7 @@ export function poly<T, P extends Polynomial<T>>(
454
787
  return out;
455
788
  },
456
789
  eval: (a: P, basis: P): T => {
457
- checkLength(a);
790
+ checkLength(a, basis);
458
791
  let acc = F.ZERO;
459
792
  for (let i = 0; i < a.length; i++) acc = F.add(acc, F.mul(a[i], basis[i]));
460
793
  return acc;
@@ -480,7 +813,7 @@ export function poly<T, P extends Polynomial<T>>(
480
813
  lagrange: {
481
814
  basis: (x: T, n: number, brp = false, weights?: P): P => {
482
815
  const bits = log2(n);
483
- const cache = weights || brp ? roots.brp(bits) : roots.roots(bits); // [ω⁰, ω¹, ..., ωⁿ⁻¹]
816
+ const cache = weights || (brp ? roots.brp(bits) : roots.roots(bits)); // [ω⁰, ω¹, ..., ωⁿ⁻¹]
484
817
  const out = _create(n);
485
818
  // Fast Kronecker-δ shortcut
486
819
  const idx = findOmegaIndex(x, n, brp);