@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.
- package/README.md +214 -122
- package/abstract/bls.d.ts +299 -16
- package/abstract/bls.d.ts.map +1 -1
- package/abstract/bls.js +89 -24
- package/abstract/bls.js.map +1 -1
- package/abstract/curve.d.ts +274 -27
- package/abstract/curve.d.ts.map +1 -1
- package/abstract/curve.js +177 -23
- package/abstract/curve.js.map +1 -1
- package/abstract/edwards.d.ts +166 -30
- package/abstract/edwards.d.ts.map +1 -1
- package/abstract/edwards.js +221 -86
- package/abstract/edwards.js.map +1 -1
- package/abstract/fft.d.ts +327 -10
- package/abstract/fft.d.ts.map +1 -1
- package/abstract/fft.js +155 -12
- package/abstract/fft.js.map +1 -1
- package/abstract/frost.d.ts +293 -0
- package/abstract/frost.d.ts.map +1 -0
- package/abstract/frost.js +704 -0
- package/abstract/frost.js.map +1 -0
- package/abstract/hash-to-curve.d.ts +173 -24
- package/abstract/hash-to-curve.d.ts.map +1 -1
- package/abstract/hash-to-curve.js +170 -31
- package/abstract/hash-to-curve.js.map +1 -1
- package/abstract/modular.d.ts +429 -37
- package/abstract/modular.d.ts.map +1 -1
- package/abstract/modular.js +414 -119
- package/abstract/modular.js.map +1 -1
- package/abstract/montgomery.d.ts +83 -12
- package/abstract/montgomery.d.ts.map +1 -1
- package/abstract/montgomery.js +32 -7
- package/abstract/montgomery.js.map +1 -1
- package/abstract/oprf.d.ts +164 -91
- package/abstract/oprf.d.ts.map +1 -1
- package/abstract/oprf.js +88 -29
- package/abstract/oprf.js.map +1 -1
- package/abstract/poseidon.d.ts +138 -7
- package/abstract/poseidon.d.ts.map +1 -1
- package/abstract/poseidon.js +178 -15
- package/abstract/poseidon.js.map +1 -1
- package/abstract/tower.d.ts +122 -3
- package/abstract/tower.d.ts.map +1 -1
- package/abstract/tower.js +323 -139
- package/abstract/tower.js.map +1 -1
- package/abstract/weierstrass.d.ts +339 -76
- package/abstract/weierstrass.d.ts.map +1 -1
- package/abstract/weierstrass.js +395 -205
- package/abstract/weierstrass.js.map +1 -1
- package/bls12-381.d.ts +16 -2
- package/bls12-381.d.ts.map +1 -1
- package/bls12-381.js +199 -209
- package/bls12-381.js.map +1 -1
- package/bn254.d.ts +11 -2
- package/bn254.d.ts.map +1 -1
- package/bn254.js +93 -38
- package/bn254.js.map +1 -1
- package/ed25519.d.ts +135 -14
- package/ed25519.d.ts.map +1 -1
- package/ed25519.js +207 -41
- package/ed25519.js.map +1 -1
- package/ed448.d.ts +108 -14
- package/ed448.d.ts.map +1 -1
- package/ed448.js +194 -42
- package/ed448.js.map +1 -1
- package/index.js +7 -1
- package/index.js.map +1 -1
- package/misc.d.ts +106 -7
- package/misc.d.ts.map +1 -1
- package/misc.js +141 -32
- package/misc.js.map +1 -1
- package/nist.d.ts +112 -11
- package/nist.d.ts.map +1 -1
- package/nist.js +139 -17
- package/nist.js.map +1 -1
- package/package.json +34 -6
- package/secp256k1.d.ts +92 -15
- package/secp256k1.d.ts.map +1 -1
- package/secp256k1.js +211 -28
- package/secp256k1.js.map +1 -1
- package/src/abstract/bls.ts +356 -69
- package/src/abstract/curve.ts +327 -44
- package/src/abstract/edwards.ts +367 -143
- package/src/abstract/fft.ts +371 -36
- package/src/abstract/frost.ts +1092 -0
- package/src/abstract/hash-to-curve.ts +255 -56
- package/src/abstract/modular.ts +591 -144
- package/src/abstract/montgomery.ts +114 -30
- package/src/abstract/oprf.ts +383 -194
- package/src/abstract/poseidon.ts +235 -35
- package/src/abstract/tower.ts +428 -159
- package/src/abstract/weierstrass.ts +710 -312
- package/src/bls12-381.ts +239 -236
- package/src/bn254.ts +107 -46
- package/src/ed25519.ts +234 -56
- package/src/ed448.ts +227 -57
- package/src/index.ts +7 -1
- package/src/misc.ts +154 -35
- package/src/nist.ts +143 -20
- package/src/secp256k1.ts +284 -41
- package/src/utils.ts +583 -81
- package/src/webcrypto.ts +302 -73
- package/utils.d.ts +457 -24
- package/utils.d.ts.map +1 -1
- package/utils.js +410 -53
- package/utils.js.map +1 -1
- package/webcrypto.d.ts +167 -25
- package/webcrypto.d.ts.map +1 -1
- package/webcrypto.js +165 -58
- package/webcrypto.js.map +1 -1
package/src/abstract/fft.ts
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
-
|
|
55
|
-
|
|
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
|
-
/**
|
|
87
|
-
|
|
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
|
|
184
|
-
* - DIF (Decimation-in-Frequency): Top-Down (root
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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
|
-
|
|
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
|
-
|
|
305
|
-
|
|
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
|
-
|
|
315
|
-
|
|
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
|
|
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
|
|
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):
|
|
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
|
-
|
|
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
|
-
|
|
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);
|