@noble/curves 2.0.0 → 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 +89 -24
  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 +327 -10
  15. package/abstract/fft.d.ts.map +1 -1
  16. package/abstract/fft.js +155 -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 +135 -14
  59. package/ed25519.d.ts.map +1 -1
  60. package/ed25519.js +207 -41
  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 +34 -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 +356 -69
  82. package/src/abstract/curve.ts +327 -44
  83. package/src/abstract/edwards.ts +367 -143
  84. package/src/abstract/fft.ts +371 -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 +234 -56
  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,26 +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. */
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
+ */
80
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
+ */
81
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
+ */
82
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
+ */
83
199
  omega: (bits: number) => bigint;
200
+ /**
201
+ * Drop all cached root tables.
202
+ * @returns Nothing.
203
+ */
84
204
  clear: () => void;
85
205
  };
86
- /** We limit roots up to 2**31, which is a lot: 2-billion polynomimal should be rare. */
87
- 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 {
88
222
  // Factor field.ORDER-1 as oddFactor * 2^powerOfTwo
89
223
  let oddFactor = field.ORDER - _1n;
90
224
  let powerOfTwo = 0;
@@ -117,10 +251,12 @@ export function rootsOfUnity(field: IField<bigint>, generator?: bigint): RootsOf
117
251
  };
118
252
  const brpCache = new Map<number, bigint[]>();
119
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.
120
255
 
121
256
  // NOTE: we use bits instead of power, because power = 2**bits,
122
257
  // but power is not neccesary isPowerOfTwo(power)!
123
258
  return {
259
+ info: { G, powerOfTwo, oddFactor },
124
260
  roots: (bits: number): bigint[] => {
125
261
  const b = checkBits(bits);
126
262
  return precomputeRoots(b);
@@ -147,41 +283,80 @@ export function rootsOfUnity(field: IField<bigint>, generator?: bigint): RootsOf
147
283
  clear: (): void => {
148
284
  rootsCache.splice(0, rootsCache.length);
149
285
  brpCache.clear();
286
+ inverseCache.clear();
150
287
  },
151
288
  };
152
289
  }
153
290
 
291
+ /** Polynomial coefficient container used by the FFT helpers. */
154
292
  export type Polynomial<T> = MutableArrayLike<T>;
155
293
 
156
294
  /**
295
+ * Arithmetic operations used by the generic FFT implementation.
296
+ *
157
297
  * Maps great to Field<bigint>, but not to Group (EC points):
158
298
  * - inv from scalar field
159
299
  * - we need multiplyUnsafe here, instead of multiply for speed
160
300
  * - multiplyUnsafe is safe in the context: we do mul(rootsOfUnity), which are public and sparse
161
301
  */
162
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
+ */
163
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
+ */
164
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
+ */
165
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
+ */
166
329
  inv: (a: R) => R;
167
330
  };
168
331
 
332
+ /** Configuration for one low-level FFT loop. */
169
333
  export type FFTCoreOpts<R> = {
334
+ /** Transform size. Must be a power of two. */
170
335
  N: number;
336
+ /** Stage roots for the selected transform size. */
171
337
  roots: Polynomial<R>;
338
+ /** Whether to run the DIT variant instead of DIF. */
172
339
  dit: boolean;
340
+ /** Whether to invert butterfly placement for decode-oriented layouts. */
173
341
  invertButterflies?: boolean;
342
+ /** Number of initial stages to skip. */
174
343
  skipStages?: number;
344
+ /** Whether to apply bit-reversal permutation at the boundary. */
175
345
  brp?: boolean;
176
346
  };
177
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
+ */
178
353
  export type FFTCoreLoop<T> = <P extends Polynomial<T>>(values: P) => P;
179
354
 
180
355
  /**
181
356
  * Constructs different flavors of FFT. radix2 implementation of low level mutating API. Flavors:
182
357
  *
183
- * - DIT (Decimation-in-Time): Bottom-Up (leaves -> root), Cool-Turkey
184
- * - 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
185
360
  *
186
361
  * DIT takes brp input, returns natural output.
187
362
  * DIF takes natural input, returns brp output.
@@ -192,11 +367,35 @@ export type FFTCoreLoop<T> = <P extends Polynomial<T>>(values: P) => P;
192
367
  *
193
368
  * Cyclic NTT: Rq = Zq[x]/(x^n-1). butterfly_DIT+loop_DIT OR butterfly_DIF+loop_DIT, roots are omega
194
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
+ * ```
195
391
  */
196
392
  export const FFTCore = <T, R>(F: FFTOpts<T, R>, coreOpts: FFTCoreOpts<R>): FFTCoreLoop<T> => {
197
393
  const { N, roots, dit, invertButterflies = false, skipStages = 0, brp = true } = coreOpts;
198
394
  const bits = log2(N);
199
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}`);
200
399
  const isDit = dit !== invertButterflies;
201
400
  isDit;
202
401
  return <P extends Polynomial<T>>(values: P): P => {
@@ -238,14 +437,42 @@ export const FFTCore = <T, R>(F: FFTOpts<T, R>, coreOpts: FFTCoreOpts<R>): FFTCo
238
437
  };
239
438
  };
240
439
 
440
+ /** Forward and inverse FFT helpers for one coefficient domain. */
241
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
+ */
242
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
+ */
243
457
  inverse<P extends Polynomial<T>>(values: P, brpInput?: boolean, brpOutput?: boolean): P;
244
458
  };
245
459
 
246
460
  /**
247
461
  * NTT aka FFT over finite field (NOT over complex numbers).
248
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
+ * ```
249
476
  */
250
477
  export function FFT<T>(roots: RootsOfUnity, opts: FFTOpts<T, bigint>): FFTMethods<T> {
251
478
  const getLoop = (
@@ -272,6 +499,7 @@ export function FFT<T>(roots: RootsOfUnity, opts: FFTOpts<T, bigint>): FFTMethod
272
499
  },
273
500
  inverse<P extends Polynomial<T>>(values: P, brpInput = false, brpOutput = false): P {
274
501
  const N = values.length;
502
+ if (!isPowerOfTwo(N)) throw new Error('FFT: Polynomial size should be power of two');
275
503
  const bits = log2(N);
276
504
  const res = getLoop(N, roots.inverse(bits), brpInput, brpOutput)(values.slice());
277
505
  const ivm = opts.inv(BigInt(values.length)); // scale
@@ -285,34 +513,114 @@ export function FFT<T>(roots: RootsOfUnity, opts: FFTOpts<T, bigint>): FFTMethod
285
513
  };
286
514
  }
287
515
 
288
- 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;
289
526
 
290
- 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. */
291
530
  roots: RootsOfUnity;
531
+ /** Factory used to allocate new polynomial containers. */
292
532
  create: CreatePolyFn<P, T>;
293
- length?: number; // optional enforced size
533
+ /** Optional enforced polynomial length. */
534
+ length?: number;
294
535
 
536
+ /**
537
+ * Compute the polynomial degree.
538
+ * @param a - Polynomial coefficients.
539
+ * @returns Polynomial degree.
540
+ */
295
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
+ */
296
548
  extend: (a: P, len: number) => P;
297
- add: (a: P, b: P) => P; // fc(x) = fa(x) + fb(x)
298
- sub: (a: P, b: P) => P; // fc(x) = fa(x) - fb(x)
299
- mul: (a: P, b: P | T) => P; // fc(x) = fa(x) * fb(x) OR fc(x) = fa(x) * scalar (same as field)
300
- 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
+ */
301
583
  convolve: (a: P, b: P) => P;
302
- 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
+ */
303
596
  clone: (a: P) => P;
304
- // Eval
305
- 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. */
306
605
  monomial: {
606
+ /** Build the monomial basis vector for one evaluation point. */
307
607
  basis: (x: T, n: number) => P;
608
+ /** Evaluate a polynomial in the monomial basis. */
308
609
  eval: (a: P, x: T) => T;
309
610
  };
611
+ /** Helpers for Lagrange-basis polynomials. */
310
612
  lagrange: {
613
+ /** Build the Lagrange basis vector for one evaluation point. */
311
614
  basis: (x: T, n: number, brp?: boolean) => P;
615
+ /** Evaluate a polynomial in the Lagrange basis. */
312
616
  eval: (a: P, x: T, brp?: boolean) => T;
313
617
  };
314
- // Complex
315
- 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;
316
624
  };
317
625
 
318
626
  /**
@@ -326,37 +634,62 @@ export type PolyFn<P extends Polynomial<T>, T> = {
326
634
  * - **Monominal** is Polynomial where `basis[i](x) == x**i` (powers)
327
635
  * - **Array size** is domain size
328
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
+ * ```
329
654
  */
330
655
  export function poly<T>(
331
- field: IField<T>,
656
+ field: TArg<IField<T>>,
332
657
  roots: RootsOfUnity,
333
658
  create?: undefined,
334
659
  fft?: FFTMethods<T>,
335
660
  length?: number
336
661
  ): PolyFn<T[], T>;
337
- export function poly<T, P extends Polynomial<T>>(
338
- field: IField<T>,
662
+ export function poly<T, P extends PolyStorage<T>>(
663
+ field: TArg<IField<T>>,
339
664
  roots: RootsOfUnity,
340
665
  create: CreatePolyFn<P, T>,
341
666
  fft?: FFTMethods<T>,
342
667
  length?: number
343
668
  ): PolyFn<P, T>;
344
- export function poly<T, P extends Polynomial<T>>(
345
- field: IField<T>,
669
+ export function poly<T, P extends PolyStorage<T>>(
670
+ field: TArg<IField<T>>,
346
671
  roots: RootsOfUnity,
347
672
  create?: CreatePolyFn<P, T>,
348
673
  fft?: FFTMethods<T>,
349
674
  length?: number
350
675
  ): PolyFn<any, T> {
351
- const F = field;
676
+ const F = field as IField<T>;
352
677
  const _create =
353
678
  create ||
354
- (((len: number, elm?: T): Polynomial<T> => new Array(len).fill(elm ?? F.ZERO)) as CreatePolyFn<
355
- P,
356
- T
357
- >);
679
+ (((len: number, elm?: T): T[] => new Array(len).fill(elm ?? F.ZERO)) as CreatePolyFn<P, T>);
358
680
 
359
- 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
+ };
360
693
  const checkLength = (...lst: P[]): number => {
361
694
  if (!lst.length) return 0;
362
695
  for (const i of lst) if (!isPoly(i)) throw new Error('poly: not polynomial: ' + i);
@@ -381,7 +714,9 @@ export function poly<T, P extends Polynomial<T>>(
381
714
  extend: (a: P, len: number): P => {
382
715
  checkLength(a);
383
716
  const out = _create(len, F.ZERO);
384
- 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];
385
720
  return out;
386
721
  },
387
722
  degree: (a: P): number => {
@@ -452,7 +787,7 @@ export function poly<T, P extends Polynomial<T>>(
452
787
  return out;
453
788
  },
454
789
  eval: (a: P, basis: P): T => {
455
- checkLength(a);
790
+ checkLength(a, basis);
456
791
  let acc = F.ZERO;
457
792
  for (let i = 0; i < a.length; i++) acc = F.add(acc, F.mul(a[i], basis[i]));
458
793
  return acc;
@@ -478,7 +813,7 @@ export function poly<T, P extends Polynomial<T>>(
478
813
  lagrange: {
479
814
  basis: (x: T, n: number, brp = false, weights?: P): P => {
480
815
  const bits = log2(n);
481
- const cache = weights || brp ? roots.brp(bits) : roots.roots(bits); // [ω⁰, ω¹, ..., ωⁿ⁻¹]
816
+ const cache = weights || (brp ? roots.brp(bits) : roots.roots(bits)); // [ω⁰, ω¹, ..., ωⁿ⁻¹]
482
817
  const out = _create(n);
483
818
  // Fast Kronecker-δ shortcut
484
819
  const idx = findOmegaIndex(x, n, brp);