@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.
- package/README.md +214 -122
- package/abstract/bls.d.ts +299 -16
- package/abstract/bls.d.ts.map +1 -1
- package/abstract/bls.js +82 -22
- 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 +322 -10
- package/abstract/fft.d.ts.map +1 -1
- package/abstract/fft.js +154 -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 +125 -14
- package/ed25519.d.ts.map +1 -1
- package/ed25519.js +202 -40
- 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 +11 -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 +350 -67
- package/src/abstract/curve.ts +327 -44
- package/src/abstract/edwards.ts +367 -143
- package/src/abstract/fft.ts +369 -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 +227 -55
- 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,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
|
-
/**
|
|
88
|
-
|
|
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
|
|
186
|
-
* - 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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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
|
-
|
|
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
|
-
|
|
307
|
-
|
|
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
|
-
|
|
317
|
-
|
|
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
|
|
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
|
|
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):
|
|
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
|
-
|
|
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
|
-
|
|
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);
|