@noble/curves 1.8.2 → 1.9.1

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 (199) hide show
  1. package/README.md +49 -24
  2. package/abstract/bls.js +1 -1
  3. package/abstract/bls.js.map +1 -1
  4. package/abstract/curve.d.ts +1 -1
  5. package/abstract/curve.d.ts.map +1 -1
  6. package/abstract/curve.js +13 -4
  7. package/abstract/curve.js.map +1 -1
  8. package/abstract/edwards.d.ts.map +1 -1
  9. package/abstract/edwards.js +17 -3
  10. package/abstract/edwards.js.map +1 -1
  11. package/abstract/fft.d.ts +120 -0
  12. package/abstract/fft.d.ts.map +1 -0
  13. package/abstract/fft.js +439 -0
  14. package/abstract/fft.js.map +1 -0
  15. package/abstract/hash-to-curve.d.ts +10 -5
  16. package/abstract/hash-to-curve.d.ts.map +1 -1
  17. package/abstract/hash-to-curve.js +31 -23
  18. package/abstract/hash-to-curve.js.map +1 -1
  19. package/abstract/modular.d.ts +13 -12
  20. package/abstract/modular.d.ts.map +1 -1
  21. package/abstract/modular.js +158 -158
  22. package/abstract/modular.js.map +1 -1
  23. package/abstract/montgomery.d.ts +4 -9
  24. package/abstract/montgomery.d.ts.map +1 -1
  25. package/abstract/montgomery.js +70 -90
  26. package/abstract/montgomery.js.map +1 -1
  27. package/abstract/poseidon.d.ts +39 -2
  28. package/abstract/poseidon.d.ts.map +1 -1
  29. package/abstract/poseidon.js +183 -4
  30. package/abstract/poseidon.js.map +1 -1
  31. package/abstract/tower.d.ts.map +1 -1
  32. package/abstract/tower.js +4 -5
  33. package/abstract/tower.js.map +1 -1
  34. package/abstract/utils.d.ts +1 -0
  35. package/abstract/utils.d.ts.map +1 -1
  36. package/abstract/utils.js +2 -0
  37. package/abstract/utils.js.map +1 -1
  38. package/abstract/weierstrass.d.ts +31 -9
  39. package/abstract/weierstrass.d.ts.map +1 -1
  40. package/abstract/weierstrass.js +67 -48
  41. package/abstract/weierstrass.js.map +1 -1
  42. package/bls12-381.d.ts.map +1 -1
  43. package/bls12-381.js +9 -23
  44. package/bls12-381.js.map +1 -1
  45. package/bn254.d.ts +1 -0
  46. package/bn254.d.ts.map +1 -1
  47. package/bn254.js +10 -0
  48. package/bn254.js.map +1 -1
  49. package/ed25519.d.ts +19 -5
  50. package/ed25519.d.ts.map +1 -1
  51. package/ed25519.js +29 -18
  52. package/ed25519.js.map +1 -1
  53. package/ed448.d.ts +21 -5
  54. package/ed448.d.ts.map +1 -1
  55. package/ed448.js +46 -34
  56. package/ed448.js.map +1 -1
  57. package/esm/abstract/bls.js +1 -1
  58. package/esm/abstract/bls.js.map +1 -1
  59. package/esm/abstract/curve.d.ts +1 -1
  60. package/esm/abstract/curve.d.ts.map +1 -1
  61. package/esm/abstract/curve.js +13 -4
  62. package/esm/abstract/curve.js.map +1 -1
  63. package/esm/abstract/edwards.d.ts.map +1 -1
  64. package/esm/abstract/edwards.js +19 -5
  65. package/esm/abstract/edwards.js.map +1 -1
  66. package/esm/abstract/fft.d.ts +120 -0
  67. package/esm/abstract/fft.d.ts.map +1 -0
  68. package/esm/abstract/fft.js +426 -0
  69. package/esm/abstract/fft.js.map +1 -0
  70. package/esm/abstract/hash-to-curve.d.ts +10 -5
  71. package/esm/abstract/hash-to-curve.d.ts.map +1 -1
  72. package/esm/abstract/hash-to-curve.js +32 -24
  73. package/esm/abstract/hash-to-curve.js.map +1 -1
  74. package/esm/abstract/modular.d.ts +13 -12
  75. package/esm/abstract/modular.d.ts.map +1 -1
  76. package/esm/abstract/modular.js +158 -158
  77. package/esm/abstract/modular.js.map +1 -1
  78. package/esm/abstract/montgomery.d.ts +4 -9
  79. package/esm/abstract/montgomery.d.ts.map +1 -1
  80. package/esm/abstract/montgomery.js +71 -91
  81. package/esm/abstract/montgomery.js.map +1 -1
  82. package/esm/abstract/poseidon.d.ts +39 -2
  83. package/esm/abstract/poseidon.d.ts.map +1 -1
  84. package/esm/abstract/poseidon.js +180 -5
  85. package/esm/abstract/poseidon.js.map +1 -1
  86. package/esm/abstract/tower.d.ts.map +1 -1
  87. package/esm/abstract/tower.js +4 -5
  88. package/esm/abstract/tower.js.map +1 -1
  89. package/esm/abstract/utils.d.ts +1 -0
  90. package/esm/abstract/utils.d.ts.map +1 -1
  91. package/esm/abstract/utils.js +2 -0
  92. package/esm/abstract/utils.js.map +1 -1
  93. package/esm/abstract/weierstrass.d.ts +31 -9
  94. package/esm/abstract/weierstrass.d.ts.map +1 -1
  95. package/esm/abstract/weierstrass.js +69 -50
  96. package/esm/abstract/weierstrass.js.map +1 -1
  97. package/esm/bls12-381.d.ts.map +1 -1
  98. package/esm/bls12-381.js +9 -23
  99. package/esm/bls12-381.js.map +1 -1
  100. package/esm/bn254.d.ts +1 -0
  101. package/esm/bn254.d.ts.map +1 -1
  102. package/esm/bn254.js +10 -0
  103. package/esm/bn254.js.map +1 -1
  104. package/esm/ed25519.d.ts +19 -5
  105. package/esm/ed25519.d.ts.map +1 -1
  106. package/esm/ed25519.js +29 -18
  107. package/esm/ed25519.js.map +1 -1
  108. package/esm/ed448.d.ts +21 -5
  109. package/esm/ed448.d.ts.map +1 -1
  110. package/esm/ed448.js +47 -35
  111. package/esm/ed448.js.map +1 -1
  112. package/esm/jubjub.d.ts +11 -1
  113. package/esm/jubjub.d.ts.map +1 -1
  114. package/esm/jubjub.js +11 -1
  115. package/esm/jubjub.js.map +1 -1
  116. package/esm/misc.d.ts +8 -2
  117. package/esm/misc.d.ts.map +1 -1
  118. package/esm/misc.js +10 -4
  119. package/esm/misc.js.map +1 -1
  120. package/esm/nist.d.ts +30 -0
  121. package/esm/nist.d.ts.map +1 -0
  122. package/esm/nist.js +121 -0
  123. package/esm/nist.js.map +1 -0
  124. package/esm/p256.d.ts +7 -9
  125. package/esm/p256.d.ts.map +1 -1
  126. package/esm/p256.js +6 -44
  127. package/esm/p256.js.map +1 -1
  128. package/esm/p384.d.ts +9 -10
  129. package/esm/p384.d.ts.map +1 -1
  130. package/esm/p384.js +7 -46
  131. package/esm/p384.js.map +1 -1
  132. package/esm/p521.d.ts +7 -8
  133. package/esm/p521.d.ts.map +1 -1
  134. package/esm/p521.js +6 -46
  135. package/esm/p521.js.map +1 -1
  136. package/esm/pasta.d.ts +9 -1
  137. package/esm/pasta.d.ts.map +1 -1
  138. package/esm/pasta.js +9 -1
  139. package/esm/pasta.js.map +1 -1
  140. package/esm/secp256k1.d.ts +3 -3
  141. package/esm/secp256k1.d.ts.map +1 -1
  142. package/esm/secp256k1.js +8 -9
  143. package/esm/secp256k1.js.map +1 -1
  144. package/jubjub.d.ts +11 -1
  145. package/jubjub.d.ts.map +1 -1
  146. package/jubjub.js +12 -5
  147. package/jubjub.js.map +1 -1
  148. package/misc.d.ts +8 -2
  149. package/misc.d.ts.map +1 -1
  150. package/misc.js +11 -5
  151. package/misc.js.map +1 -1
  152. package/nist.d.ts +30 -0
  153. package/nist.d.ts.map +1 -0
  154. package/nist.js +124 -0
  155. package/nist.js.map +1 -0
  156. package/p256.d.ts +7 -9
  157. package/p256.d.ts.map +1 -1
  158. package/p256.js +5 -49
  159. package/p256.js.map +1 -1
  160. package/p384.d.ts +9 -10
  161. package/p384.d.ts.map +1 -1
  162. package/p384.js +6 -51
  163. package/p384.js.map +1 -1
  164. package/p521.d.ts +7 -8
  165. package/p521.d.ts.map +1 -1
  166. package/p521.js +5 -51
  167. package/p521.js.map +1 -1
  168. package/package.json +117 -8
  169. package/pasta.d.ts +9 -1
  170. package/pasta.d.ts.map +1 -1
  171. package/pasta.js +9 -3
  172. package/pasta.js.map +1 -1
  173. package/secp256k1.d.ts +3 -3
  174. package/secp256k1.d.ts.map +1 -1
  175. package/secp256k1.js +9 -10
  176. package/secp256k1.js.map +1 -1
  177. package/src/abstract/bls.ts +1 -1
  178. package/src/abstract/curve.ts +11 -6
  179. package/src/abstract/edwards.ts +26 -12
  180. package/src/abstract/fft.ts +508 -0
  181. package/src/abstract/hash-to-curve.ts +44 -36
  182. package/src/abstract/modular.ts +154 -153
  183. package/src/abstract/montgomery.ts +78 -109
  184. package/src/abstract/poseidon.ts +208 -13
  185. package/src/abstract/tower.ts +4 -5
  186. package/src/abstract/utils.ts +2 -0
  187. package/src/abstract/weierstrass.ts +109 -61
  188. package/src/bls12-381.ts +11 -27
  189. package/src/bn254.ts +10 -0
  190. package/src/ed25519.ts +32 -19
  191. package/src/ed448.ts +91 -75
  192. package/src/jubjub.ts +12 -5
  193. package/src/misc.ts +10 -4
  194. package/src/nist.ts +155 -0
  195. package/src/p256.ts +6 -50
  196. package/src/p384.ts +8 -56
  197. package/src/p521.ts +6 -65
  198. package/src/pasta.ts +9 -1
  199. package/src/secp256k1.ts +12 -11
@@ -0,0 +1,508 @@
1
+ /**
2
+ * Experimental implementation of NTT / FFT (Fast Fourier Transform) over finite fields.
3
+ * API may change at any time. The code has not been audited. Feature requests are welcome.
4
+ * @module
5
+ */
6
+ import type { IField } from './modular.ts';
7
+
8
+ export interface MutableArrayLike<T> {
9
+ [index: number]: T;
10
+ length: number;
11
+ slice(start?: number, end?: number): this;
12
+ [Symbol.iterator](): Iterator<T>;
13
+ }
14
+
15
+ function checkU32(n: number) {
16
+ // 0xff_ff_ff_ff
17
+ if (!Number.isSafeInteger(n) || n < 0 || n > 0xffffffff)
18
+ throw new Error('wrong u32 integer:' + n);
19
+ return n;
20
+ }
21
+
22
+ /** Checks if integer is in form of `1 << X` */
23
+ export function isPowerOfTwo(x: number): boolean {
24
+ checkU32(x);
25
+ return (x & (x - 1)) === 0 && x !== 0;
26
+ }
27
+
28
+ export function nextPowerOfTwo(n: number): number {
29
+ checkU32(n);
30
+ if (n <= 1) return 1;
31
+ return (1 << (log2(n - 1) + 1)) >>> 0;
32
+ }
33
+
34
+ export function reverseBits(n: number, bits: number): number {
35
+ checkU32(n);
36
+ let reversed = 0;
37
+ for (let i = 0; i < bits; i++, n >>>= 1) reversed = (reversed << 1) | (n & 1);
38
+ return reversed;
39
+ }
40
+
41
+ /** Similar to `bitLen(x)-1` but much faster for small integers, like indices */
42
+ export function log2(n: number): number {
43
+ checkU32(n);
44
+ return 31 - Math.clz32(n);
45
+ }
46
+
47
+ /**
48
+ * Moves lowest bit to highest position, which at first step splits
49
+ * array on even and odd indices, then it applied again to each part,
50
+ * which is core of fft
51
+ */
52
+ export function bitReversalInplace<T extends MutableArrayLike<any>>(values: T): T {
53
+ 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);
56
+ const bits = log2(n);
57
+ for (let i = 0; i < n; i++) {
58
+ const j = reverseBits(i, bits);
59
+ if (i < j) {
60
+ const tmp = values[i];
61
+ values[i] = values[j];
62
+ values[j] = tmp;
63
+ }
64
+ }
65
+ return values;
66
+ }
67
+
68
+ export function bitReversalPermutation<T>(values: T[]): T[] {
69
+ return bitReversalInplace(values.slice()) as T[];
70
+ }
71
+
72
+ const _1n = /** @__PURE__ */ BigInt(1);
73
+ function findGenerator(field: IField<bigint>) {
74
+ let G = BigInt(2);
75
+ for (; field.eql(field.pow(G, field.ORDER >> _1n), field.ONE); G++);
76
+ return G;
77
+ }
78
+
79
+ /** We limit roots up to 2**31, which is a lot: 2-billion polynomimal should be rare. */
80
+ export function rootsOfUnity(field: IField<bigint>, generator?: bigint) {
81
+ // Factor field.ORDER-1 as oddFactor * 2^powerOfTwo
82
+ let oddFactor = field.ORDER - _1n;
83
+ let powerOfTwo = 0;
84
+ for (; (oddFactor & _1n) !== _1n; powerOfTwo++, oddFactor >>= _1n);
85
+
86
+ // Find non quadratic residue
87
+ let G = generator !== undefined ? BigInt(generator) : findGenerator(field);
88
+ // Powers of generator
89
+ const omegas: bigint[] = new Array(powerOfTwo + 1);
90
+ omegas[powerOfTwo] = field.pow(G, oddFactor);
91
+ for (let i = powerOfTwo; i > 0; i--) omegas[i - 1] = field.sqr(omegas[i]);
92
+ // Compute all roots of unity for powers up to maxPower
93
+ const rootsCache: bigint[][] = [];
94
+ const checkBits = (bits: number) => {
95
+ checkU32(bits);
96
+ if (bits > 31 || bits > powerOfTwo)
97
+ throw new Error('rootsOfUnity: wrong bits ' + bits + ' powerOfTwo=' + powerOfTwo);
98
+ return bits;
99
+ };
100
+ const precomputeRoots = (maxPower: number) => {
101
+ checkBits(maxPower);
102
+ for (let power = maxPower; power >= 0; power--) {
103
+ if (rootsCache[power]) continue; // Skip if we've already computed roots for this power
104
+ const rootsAtPower: bigint[] = [];
105
+ for (let j = 0, cur = field.ONE; j < 2 ** power; j++, cur = field.mul(cur, omegas[power]))
106
+ rootsAtPower.push(cur);
107
+ rootsCache[power] = rootsAtPower;
108
+ }
109
+ return rootsCache[maxPower];
110
+ };
111
+ const brpCache = new Map<number, bigint[]>();
112
+ const inverseCache = new Map<number, bigint[]>();
113
+
114
+ // NOTE: we use bits instead of power, because power = 2**bits,
115
+ // but power is not neccesary isPowerOfTwo(power)!
116
+ return {
117
+ roots: (bits: number): bigint[] => {
118
+ const b = checkBits(bits);
119
+ return precomputeRoots(b);
120
+ },
121
+ brp(bits: number): bigint[] {
122
+ const b = checkBits(bits);
123
+ if (brpCache.has(b)) return brpCache.get(b)!;
124
+ else {
125
+ const res = bitReversalPermutation(this.roots(b));
126
+ brpCache.set(b, res);
127
+ return res;
128
+ }
129
+ },
130
+ inverse(bits: number): bigint[] {
131
+ const b = checkBits(bits);
132
+ if (inverseCache.has(b)) return inverseCache.get(b)!;
133
+ else {
134
+ const res = field.invertBatch(this.roots(b));
135
+ inverseCache.set(b, res);
136
+ return res;
137
+ }
138
+ },
139
+ omega: (bits: number): bigint => omegas[checkBits(bits)],
140
+ clear: (): void => {
141
+ rootsCache.splice(0, rootsCache.length);
142
+ brpCache.clear();
143
+ },
144
+ };
145
+ }
146
+
147
+ export type Polynomial<T> = MutableArrayLike<T>;
148
+
149
+ /**
150
+ * Maps great to Field<bigint>, but not to Group (EC points):
151
+ * - inv from scalar field
152
+ * - we need multiplyUnsafe here, instead of multiply for speed
153
+ * - multiplyUnsafe is safe in the context: we do mul(rootsOfUnity), which are public and sparse
154
+ */
155
+ export type FFTOpts<T, R> = {
156
+ add: (a: T, b: T) => T;
157
+ sub: (a: T, b: T) => T;
158
+ mul: (a: T, scalar: R) => T;
159
+ inv: (a: R) => R;
160
+ };
161
+
162
+ export type FFTCoreOpts<R> = {
163
+ N: number;
164
+ roots: Polynomial<R>;
165
+ dit: boolean;
166
+ invertButterflies?: boolean;
167
+ skipStages?: number;
168
+ brp?: boolean;
169
+ };
170
+
171
+ export type FFTCoreLoop<T> = <P extends Polynomial<T>>(values: P) => P;
172
+
173
+ /**
174
+ * Constructs different flavors of FFT. radix2 implementation of low level mutating API. Flavors:
175
+ *
176
+ * - DIT (Decimation-in-Time): Bottom-Up (leaves -> root), Cool-Turkey
177
+ * - DIF (Decimation-in-Frequency): Top-Down (root -> leaves), Gentleman–Sande
178
+ *
179
+ * DIT takes brp input, returns natural output.
180
+ * DIF takes natural input, returns brp output.
181
+ *
182
+ * The output is actually identical. Time / frequence distinction is not meaningful
183
+ * for Polynomial multiplication in fields.
184
+ * Which means if protocol supports/needs brp output/inputs, then we can skip this step.
185
+ *
186
+ * Cyclic NTT: Rq = Zq[x]/(x^n-1). butterfly_DIT+loop_DIT OR butterfly_DIF+loop_DIT, roots are omega
187
+ * Negacyclic NTT: Rq = Zq[x]/(x^n+1). butterfly_DIT+loop_DIF, at least for mlkem / mldsa
188
+ */
189
+ export const FFTCore = <T, R>(opts: FFTOpts<T, R>, coreOpts: FFTCoreOpts<R>): FFTCoreLoop<T> => {
190
+ const { add, sub, mul } = opts; // inline to butteflies
191
+ const { N, roots, dit, invertButterflies = false, skipStages = 0, brp = true } = coreOpts;
192
+ const bits = log2(N);
193
+ if (!isPowerOfTwo(N)) throw new Error('FFT: Polynomial size should be power of two');
194
+ const isDit = dit !== invertButterflies;
195
+ isDit;
196
+ return <P extends Polynomial<T>>(values: P): P => {
197
+ if (values.length !== N) throw new Error('FFT: wrong Polynomial length');
198
+ if (dit && brp) bitReversalInplace(values);
199
+ for (let i = 0, g = 1; i < bits - skipStages; i++) {
200
+ // For each stage s (sub-FFT length m = 2^s)
201
+ const s = dit ? i + 1 + skipStages : bits - i;
202
+ const m = 1 << s;
203
+ const m2 = m >> 1;
204
+ const stride = N >> s;
205
+ // Loop over each subarray of length m
206
+ for (let k = 0; k < N; k += m) {
207
+ // Loop over each butterfly within the subarray
208
+ for (let j = 0, grp = g++; j < m2; j++) {
209
+ const rootPos = invertButterflies ? (dit ? N - grp : grp) : j * stride;
210
+ const i0 = k + j;
211
+ const i1 = k + j + m2;
212
+ const omega = roots[rootPos];
213
+ const b = values[i1];
214
+ const a = values[i0];
215
+ // Inlining gives us 10% perf in kyber vs functions
216
+ if (isDit) {
217
+ const t = mul(b, omega); // Standard DIT butterfly
218
+ values[i0] = add(a, t);
219
+ values[i1] = sub(a, t);
220
+ } else if (invertButterflies) {
221
+ values[i0] = add(b, a); // DIT loop + inverted butterflies (Kyber decode)
222
+ values[i1] = mul(sub(b, a), omega);
223
+ } else {
224
+ values[i0] = add(a, b); // Standard DIF butterfly
225
+ values[i1] = mul(sub(a, b), omega);
226
+ }
227
+ }
228
+ }
229
+ }
230
+ if (!dit && brp) bitReversalInplace(values);
231
+ return values;
232
+ };
233
+ };
234
+
235
+ /**
236
+ * NTT aka FFT over finite field (NOT over complex numbers).
237
+ * Naming mirrors other libraries.
238
+ */
239
+ export const FFT = <T>(roots: ReturnType<typeof rootsOfUnity>, opts: FFTOpts<T, bigint>) => {
240
+ const getLoop = (
241
+ N: number,
242
+ roots: Polynomial<bigint>,
243
+ brpInput = false,
244
+ brpOutput = false
245
+ ): (<P extends Polynomial<T>>(values: P) => P) => {
246
+ if (brpInput && brpOutput) {
247
+ // we cannot optimize this case, but lets support it anyway
248
+ return (values) =>
249
+ FFTCore(opts, { N, roots, dit: false, brp: false })(bitReversalInplace(values));
250
+ }
251
+ if (brpInput) return FFTCore(opts, { N, roots, dit: true, brp: false });
252
+ if (brpOutput) return FFTCore(opts, { N, roots, dit: false, brp: false });
253
+ return FFTCore(opts, { N, roots, dit: true, brp: true }); // all natural
254
+ };
255
+ return {
256
+ direct<P extends Polynomial<T>>(values: P, brpInput = false, brpOutput = false): P {
257
+ const N = values.length;
258
+ if (!isPowerOfTwo(N)) throw new Error('FFT: Polynomial size should be power of two');
259
+ const bits = log2(N);
260
+ return getLoop(N, roots.roots(bits), brpInput, brpOutput)<P>(values.slice());
261
+ },
262
+ inverse<P extends Polynomial<T>>(values: P, brpInput = false, brpOutput = false): P {
263
+ const N = values.length;
264
+ const bits = log2(N);
265
+ const res = getLoop(N, roots.inverse(bits), brpInput, brpOutput)(values.slice());
266
+ const ivm = opts.inv(BigInt(values.length)); // scale
267
+ // we can get brp output if we use dif instead of dit!
268
+ for (let i = 0; i < res.length; i++) res[i] = opts.mul(res[i], ivm);
269
+ // Allows to re-use non-inverted roots, but is VERY fragile
270
+ // return [res[0]].concat(res.slice(1).reverse());
271
+ // inverse calculated as pow(-1), which transforms into ω^{-kn} (-> reverses indices)
272
+ return res;
273
+ },
274
+ };
275
+ };
276
+
277
+ export type CreatePolyFn<P extends Polynomial<T>, T> = (len: number, elm?: T) => P;
278
+
279
+ export type PolyFn<P extends Polynomial<T>, T> = {
280
+ roots: ReturnType<typeof rootsOfUnity>;
281
+ create: CreatePolyFn<P, T>;
282
+ length?: number; // optional enforced size
283
+
284
+ degree: (a: P) => number;
285
+ extend: (a: P, len: number) => P;
286
+ add: (a: P, b: P) => P; // fc(x) = fa(x) + fb(x)
287
+ sub: (a: P, b: P) => P; // fc(x) = fa(x) - fb(x)
288
+ mul: (a: P, b: P | T) => P; // fc(x) = fa(x) * fb(x) OR fc(x) = fa(x) * scalar (same as field)
289
+ dot: (a: P, b: P) => P; // point-wise coeff multiplication
290
+ convolve: (a: P, b: P) => P;
291
+ shift: (p: P, factor: bigint) => P; // point-wise coeffcient shift
292
+ clone: (a: P) => P;
293
+ // Eval
294
+ eval: (a: P, basis: P) => T; // y = fc(x)
295
+ monomial: {
296
+ basis: (x: T, n: number) => P;
297
+ eval: (a: P, x: T) => T;
298
+ };
299
+ lagrange: {
300
+ basis: (x: T, n: number, brp?: boolean) => P;
301
+ eval: (a: P, x: T, brp?: boolean) => T;
302
+ };
303
+ // Complex
304
+ vanishing: (roots: P) => P; // f(x) = 0 for every x in roots
305
+ };
306
+
307
+ /**
308
+ * Poly wants a cracker.
309
+ *
310
+ * Polynomials are functions like `y=f(x)`, which means when we multiply two polynomials, result is
311
+ * function `f3(x) = f1(x) * f2(x)`, we don't multiply values. Key takeaways:
312
+ *
313
+ * - **Polynomial** is an array of coefficients: `f(x) = sum(coeff[i] * basis[i](x))`
314
+ * - **Basis** is array of functions
315
+ * - **Monominal** is Polynomial where `basis[i](x) == x**i` (powers)
316
+ * - **Array size** is domain size
317
+ * - **Lattice** is matrix (Polynomial of Polynomials)
318
+ */
319
+ export function poly<T>(
320
+ field: IField<T>,
321
+ roots: ReturnType<typeof rootsOfUnity>,
322
+ create?: undefined,
323
+ fft?: ReturnType<typeof FFT<T>>,
324
+ length?: number
325
+ ): PolyFn<T[], T>;
326
+ export function poly<T, P extends Polynomial<T>>(
327
+ field: IField<T>,
328
+ roots: ReturnType<typeof rootsOfUnity>,
329
+ create: CreatePolyFn<P, T>,
330
+ fft?: ReturnType<typeof FFT<T>>,
331
+ length?: number
332
+ ): PolyFn<P, T>;
333
+ export function poly<T, P extends Polynomial<T>>(
334
+ field: IField<T>,
335
+ roots: ReturnType<typeof rootsOfUnity>,
336
+ create?: CreatePolyFn<P, T>,
337
+ fft?: ReturnType<typeof FFT<T>>,
338
+ length?: number
339
+ ): PolyFn<any, T> {
340
+ const F = field;
341
+ const _create =
342
+ create ||
343
+ (((len: number, elm?: T): Polynomial<T> => new Array(len).fill(elm ?? F.ZERO)) as CreatePolyFn<
344
+ P,
345
+ T
346
+ >);
347
+
348
+ const isPoly = (x: any): x is P => Array.isArray(x) || ArrayBuffer.isView(x);
349
+ const checkLength = (...lst: P[]): number => {
350
+ if (!lst.length) return 0;
351
+ for (const i of lst) if (!isPoly(i)) throw new Error('poly: not polynomial: ' + i);
352
+ const L = lst[0].length;
353
+ for (let i = 1; i < lst.length; i++)
354
+ if (lst[i].length !== L) throw new Error(`poly: mismatched lengths ${L} vs ${lst[i].length}`);
355
+ if (length !== undefined && L !== length)
356
+ throw new Error(`poly: expected fixed length ${length}, got ${L}`);
357
+ return L;
358
+ };
359
+ function findOmegaIndex(x: T, n: number, brp = false): number {
360
+ const bits = log2(n);
361
+ const omega = brp ? roots.brp(bits) : roots.roots(bits);
362
+ for (let i = 0; i < n; i++) if (F.eql(x, omega[i] as T)) return i;
363
+ return -1;
364
+ }
365
+ // TODO: mutating versions for mlkem/mldsa
366
+ return {
367
+ roots,
368
+ create: _create,
369
+ length,
370
+ extend: (a: P, len: number): P => {
371
+ checkLength(a);
372
+ const out = _create(len, F.ZERO);
373
+ for (let i = 0; i < a.length; i++) out[i] = a[i];
374
+ return out;
375
+ },
376
+ degree: (a: P): number => {
377
+ checkLength(a);
378
+ for (let i = a.length - 1; i >= 0; i--) if (!F.is0(a[i])) return i;
379
+ return -1;
380
+ },
381
+ add: (a: P, b: P): P => {
382
+ const len = checkLength(a, b);
383
+ const out = _create(len);
384
+ for (let i = 0; i < len; i++) out[i] = F.add(a[i], b[i]);
385
+ return out;
386
+ },
387
+ sub: (a: P, b: P): P => {
388
+ const len = checkLength(a, b);
389
+ const out = _create(len);
390
+ for (let i = 0; i < len; i++) out[i] = F.sub(a[i], b[i]);
391
+ return out;
392
+ },
393
+ dot: (a: P, b: P): P => {
394
+ const len = checkLength(a, b);
395
+ const out = _create(len);
396
+ for (let i = 0; i < len; i++) out[i] = F.mul(a[i], b[i]);
397
+ return out;
398
+ },
399
+ mul: (a: P, b: P | T): P => {
400
+ if (isPoly(b)) {
401
+ const len = checkLength(a, b);
402
+ if (fft) {
403
+ const A = fft.direct(a, false, true);
404
+ const B = fft.direct(b, false, true);
405
+ for (let i = 0; i < A.length; i++) A[i] = F.mul(A[i], B[i]);
406
+ return fft.inverse(A, true, false) as P;
407
+ } else {
408
+ // NOTE: this is quadratic and mostly for compat tests with FFT
409
+ const res = _create(len);
410
+ for (let i = 0; i < len; i++) {
411
+ for (let j = 0; j < len; j++) {
412
+ const k = (i + j) % len; // wrap mod length
413
+ res[k] = F.add(res[k], F.mul(a[i], b[j]));
414
+ }
415
+ }
416
+ return res;
417
+ }
418
+ } else {
419
+ const out = _create(checkLength(a));
420
+ for (let i = 0; i < out.length; i++) out[i] = F.mul(a[i], b);
421
+ return out;
422
+ }
423
+ },
424
+ convolve(a: P, b: P): P {
425
+ const len = nextPowerOfTwo(a.length + b.length - 1);
426
+ return this.mul(this.extend(a, len), this.extend(b, len));
427
+ },
428
+ shift(p: P, factor: bigint): P {
429
+ const out = _create(checkLength(p));
430
+ out[0] = p[0];
431
+ for (let i = 1, power = F.ONE; i < p.length; i++) {
432
+ power = F.mul(power, factor);
433
+ out[i] = F.mul(p[i], power);
434
+ }
435
+ return out;
436
+ },
437
+ clone: (a: P): P => {
438
+ checkLength(a);
439
+ const out = _create(a.length);
440
+ for (let i = 0; i < a.length; i++) out[i] = a[i];
441
+ return out;
442
+ },
443
+ eval: (a: P, basis: P): T => {
444
+ checkLength(a);
445
+ let acc = F.ZERO;
446
+ for (let i = 0; i < a.length; i++) acc = F.add(acc, F.mul(a[i], basis[i]));
447
+ return acc;
448
+ },
449
+ monomial: {
450
+ basis: (x: T, n: number): P => {
451
+ const out = _create(n);
452
+ let pow = F.ONE;
453
+ for (let i = 0; i < n; i++) {
454
+ out[i] = pow;
455
+ pow = F.mul(pow, x);
456
+ }
457
+ return out;
458
+ },
459
+ eval: (a: P, x: T): T => {
460
+ checkLength(a);
461
+ // Same as eval(a, monomialBasis(x, a.length)), but it is faster this way
462
+ let acc = F.ZERO;
463
+ for (let i = a.length - 1; i >= 0; i--) acc = F.add(F.mul(acc, x), a[i]);
464
+ return acc;
465
+ },
466
+ },
467
+ lagrange: {
468
+ basis: (x: T, n: number, brp = false, weights?: P): P => {
469
+ const bits = log2(n);
470
+ const cache = weights || brp ? roots.brp(bits) : roots.roots(bits); // [ω⁰, ω¹, ..., ωⁿ⁻¹]
471
+ const out = _create(n);
472
+ // Fast Kronecker-δ shortcut
473
+ const idx = findOmegaIndex(x, n, brp);
474
+ if (idx !== -1) {
475
+ out[idx] = F.ONE;
476
+ return out;
477
+ }
478
+ const tm = F.pow(x, BigInt(n));
479
+ const c = F.mul(F.sub(tm, F.ONE), F.inv(BigInt(n) as T)); // c = (xⁿ - 1)/n
480
+ const denom = _create(n);
481
+ for (let i = 0; i < n; i++) denom[i] = F.sub(x, cache[i] as T);
482
+ const inv = F.invertBatch(denom as any as T[]);
483
+ for (let i = 0; i < n; i++) out[i] = F.mul(c, F.mul(cache[i] as T, inv[i]));
484
+ return out;
485
+ },
486
+ eval(a: P, x: T, brp = false): T {
487
+ checkLength(a);
488
+ const idx = findOmegaIndex(x, a.length, brp);
489
+ if (idx !== -1) return a[idx]; // fast path
490
+ const L = this.basis(x, a.length, brp); // Lᵢ(x)
491
+ let acc = F.ZERO;
492
+ for (let i = 0; i < a.length; i++) if (!F.is0(a[i])) acc = F.add(acc, F.mul(a[i], L[i]));
493
+ return acc;
494
+ },
495
+ },
496
+ vanishing(roots: P): P {
497
+ checkLength(roots);
498
+ const out = _create(roots.length + 1, F.ZERO);
499
+ out[0] = F.ONE;
500
+ for (const r of roots) {
501
+ const neg = F.neg(r);
502
+ for (let j = out.length - 1; j > 0; j--) out[j] = F.add(F.mul(out[j], neg), out[j - 1]);
503
+ out[0] = F.mul(out[0], neg);
504
+ }
505
+ return out;
506
+ },
507
+ };
508
+ }
@@ -6,7 +6,7 @@
6
6
  */
7
7
  /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
8
8
  import type { AffinePoint, Group, GroupConstructor } from './curve.ts';
9
- import { type IField, mod } from './modular.ts';
9
+ import { FpInvertBatch, type IField, mod } from './modular.ts';
10
10
  import type { CHash } from './utils.ts';
11
11
  import { abytes, bytesToNumberBE, concatBytes, utf8ToBytes, validateObject } from './utils.ts';
12
12
 
@@ -172,24 +172,23 @@ export function hash_to_field(msg: Uint8Array, count: number, options: Opts): bi
172
172
  return u;
173
173
  }
174
174
 
175
- export type XY<T> = (
176
- x: T,
177
- y: T
178
- ) => {
179
- x: T;
180
- y: T;
181
- };
182
- export function isogenyMap<T, F extends IField<T>>(field: F, map: [T[], T[], T[], T[]]): XY<T> {
175
+ export type XY<T> = (x: T, y: T) => { x: T; y: T };
176
+ export type XYRatio<T> = [T[], T[], T[], T[]]; // xn/xd, yn/yd
177
+ export function isogenyMap<T, F extends IField<T>>(field: F, map: XYRatio<T>): XY<T> {
183
178
  // Make same order as in spec
184
- const COEFF = map.map((i) => Array.from(i).reverse());
179
+ const coeff = map.map((i) => Array.from(i).reverse());
185
180
  return (x: T, y: T) => {
186
- const [xNum, xDen, yNum, yDen] = COEFF.map((val) =>
181
+ const [xn, xd, yn, yd] = coeff.map((val) =>
187
182
  val.reduce((acc, i) => field.add(field.mul(acc, x), i))
188
183
  );
189
- if (field.is0(xDen) || field.is0(yDen)) throw new Error('bad point: ZERO');
190
- x = field.div(xNum, xDen); // xNum / xDen
191
- y = field.mul(y, field.div(yNum, yDen)); // y * (yNum / yDev)
192
- return { x: x, y: y };
184
+ // 6.6.3
185
+ // Exceptional cases of iso_map are inputs that cause the denominator of
186
+ // either rational function to evaluate to zero; such cases MUST return
187
+ // the identity point on E.
188
+ const [xd_inv, yd_inv] = FpInvertBatch(field, [xd, yd], true);
189
+ x = field.mul(xn, xd_inv); // xNum / xDen
190
+ y = field.mul(y, field.mul(yn, yd_inv)); // y * (yNum / yDev)
191
+ return { x, y };
193
192
  };
194
193
  }
195
194
 
@@ -212,46 +211,55 @@ export type MapToCurve<T> = (scalar: bigint[]) => AffinePoint<T>;
212
211
  export type htfBasicOpts = { DST: UnicodeOrBytes };
213
212
  export type HTFMethod<T> = (msg: Uint8Array, options?: htfBasicOpts) => H2CPoint<T>;
214
213
  export type MapMethod<T> = (scalars: bigint[]) => H2CPoint<T>;
214
+ export type Hasher<T> = {
215
+ hashToCurve: HTFMethod<T>;
216
+ encodeToCurve: HTFMethod<T>;
217
+ mapToCurve: MapMethod<T>;
218
+ defaults: Opts & { encodeDST?: UnicodeOrBytes };
219
+ };
215
220
 
216
221
  /** Creates hash-to-curve methods from EC Point and mapToCurve function. */
217
222
  export function createHasher<T>(
218
223
  Point: H2CPointConstructor<T>,
219
224
  mapToCurve: MapToCurve<T>,
220
- def: Opts & { encodeDST?: UnicodeOrBytes }
221
- ): {
222
- hashToCurve: HTFMethod<T>;
223
- encodeToCurve: HTFMethod<T>;
224
- mapToCurve: MapMethod<T>;
225
- } {
225
+ defaults: Opts & { encodeDST?: UnicodeOrBytes }
226
+ ): Hasher<T> {
226
227
  if (typeof mapToCurve !== 'function') throw new Error('mapToCurve() must be defined');
228
+ function map(num: bigint[]) {
229
+ return Point.fromAffine(mapToCurve(num));
230
+ }
231
+ function clear(initial: H2CPoint<T>) {
232
+ const P = initial.clearCofactor();
233
+ if (P.equals(Point.ZERO)) return Point.ZERO; // zero will throw in assert
234
+ P.assertValidity();
235
+ return P;
236
+ }
237
+
227
238
  return {
239
+ defaults,
240
+
228
241
  // Encodes byte string to elliptic curve.
229
242
  // hash_to_curve from https://www.rfc-editor.org/rfc/rfc9380#section-3
230
243
  hashToCurve(msg: Uint8Array, options?: htfBasicOpts): H2CPoint<T> {
231
- const u = hash_to_field(msg, 2, { ...def, DST: def.DST, ...options } as Opts);
232
- const u0 = Point.fromAffine(mapToCurve(u[0]));
233
- const u1 = Point.fromAffine(mapToCurve(u[1]));
234
- const P = u0.add(u1).clearCofactor();
235
- P.assertValidity();
236
- return P;
244
+ const u = hash_to_field(msg, 2, { ...defaults, DST: defaults.DST, ...options } as Opts);
245
+ const u0 = map(u[0]);
246
+ const u1 = map(u[1]);
247
+ return clear(u0.add(u1));
237
248
  },
238
249
 
239
250
  // Encodes byte string to elliptic curve.
240
251
  // encode_to_curve from https://www.rfc-editor.org/rfc/rfc9380#section-3
241
252
  encodeToCurve(msg: Uint8Array, options?: htfBasicOpts): H2CPoint<T> {
242
- const u = hash_to_field(msg, 1, { ...def, DST: def.encodeDST, ...options } as Opts);
243
- const P = Point.fromAffine(mapToCurve(u[0])).clearCofactor();
244
- P.assertValidity();
245
- return P;
253
+ const u = hash_to_field(msg, 1, { ...defaults, DST: defaults.encodeDST, ...options } as Opts);
254
+ return clear(map(u[0]));
246
255
  },
256
+
247
257
  // Same as encodeToCurve, but without hash
248
258
  mapToCurve(scalars: bigint[]): H2CPoint<T> {
249
- if (!Array.isArray(scalars)) throw new Error('mapToCurve: expected array of bigints');
259
+ if (!Array.isArray(scalars)) throw new Error('expected array of bigints');
250
260
  for (const i of scalars)
251
- if (typeof i !== 'bigint') throw new Error('mapToCurve: expected array of bigints');
252
- const P = Point.fromAffine(mapToCurve(scalars)).clearCofactor();
253
- P.assertValidity();
254
- return P;
261
+ if (typeof i !== 'bigint') throw new Error('expected array of bigints');
262
+ return clear(map(scalars));
255
263
  },
256
264
  };
257
265
  }