@noble/curves 1.5.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/README.md +76 -25
  2. package/_shortw_utils.d.ts.map +1 -1
  3. package/abstract/bls.d.ts +7 -5
  4. package/abstract/bls.d.ts.map +1 -1
  5. package/abstract/bls.js +9 -9
  6. package/abstract/bls.js.map +1 -1
  7. package/abstract/curve.d.ts +37 -2
  8. package/abstract/curve.d.ts.map +1 -1
  9. package/abstract/curve.js +220 -22
  10. package/abstract/curve.js.map +1 -1
  11. package/abstract/edwards.d.ts +3 -0
  12. package/abstract/edwards.d.ts.map +1 -1
  13. package/abstract/edwards.js +25 -9
  14. package/abstract/edwards.js.map +1 -1
  15. package/abstract/hash-to-curve.d.ts.map +1 -1
  16. package/abstract/hash-to-curve.js +7 -6
  17. package/abstract/hash-to-curve.js.map +1 -1
  18. package/abstract/modular.d.ts.map +1 -1
  19. package/abstract/modular.js +32 -21
  20. package/abstract/modular.js.map +1 -1
  21. package/abstract/montgomery.d.ts.map +1 -1
  22. package/abstract/montgomery.js +5 -3
  23. package/abstract/montgomery.js.map +1 -1
  24. package/abstract/poseidon.d.ts.map +1 -1
  25. package/abstract/poseidon.js +22 -22
  26. package/abstract/poseidon.js.map +1 -1
  27. package/abstract/tower.d.ts +2 -0
  28. package/abstract/tower.d.ts.map +1 -1
  29. package/abstract/tower.js +7 -6
  30. package/abstract/tower.js.map +1 -1
  31. package/abstract/utils.d.ts.map +1 -1
  32. package/abstract/utils.js +21 -23
  33. package/abstract/utils.js.map +1 -1
  34. package/abstract/weierstrass.d.ts +19 -3
  35. package/abstract/weierstrass.d.ts.map +1 -1
  36. package/abstract/weierstrass.js +149 -71
  37. package/abstract/weierstrass.js.map +1 -1
  38. package/bls12-381.js +8 -8
  39. package/bn254.d.ts +2 -1
  40. package/bn254.d.ts.map +1 -1
  41. package/bn254.js +9 -7
  42. package/bn254.js.map +1 -1
  43. package/ed448.js +1 -1
  44. package/ed448.js.map +1 -1
  45. package/esm/_shortw_utils.d.ts.map +1 -1
  46. package/esm/abstract/bls.d.ts +7 -5
  47. package/esm/abstract/bls.d.ts.map +1 -1
  48. package/esm/abstract/bls.js +9 -9
  49. package/esm/abstract/bls.js.map +1 -1
  50. package/esm/abstract/curve.d.ts +37 -2
  51. package/esm/abstract/curve.d.ts.map +1 -1
  52. package/esm/abstract/curve.js +219 -23
  53. package/esm/abstract/curve.js.map +1 -1
  54. package/esm/abstract/edwards.d.ts +3 -0
  55. package/esm/abstract/edwards.d.ts.map +1 -1
  56. package/esm/abstract/edwards.js +27 -11
  57. package/esm/abstract/edwards.js.map +1 -1
  58. package/esm/abstract/hash-to-curve.d.ts.map +1 -1
  59. package/esm/abstract/hash-to-curve.js +7 -6
  60. package/esm/abstract/hash-to-curve.js.map +1 -1
  61. package/esm/abstract/modular.d.ts.map +1 -1
  62. package/esm/abstract/modular.js +32 -21
  63. package/esm/abstract/modular.js.map +1 -1
  64. package/esm/abstract/montgomery.d.ts.map +1 -1
  65. package/esm/abstract/montgomery.js +5 -3
  66. package/esm/abstract/montgomery.js.map +1 -1
  67. package/esm/abstract/poseidon.d.ts.map +1 -1
  68. package/esm/abstract/poseidon.js +22 -22
  69. package/esm/abstract/poseidon.js.map +1 -1
  70. package/esm/abstract/tower.d.ts +2 -0
  71. package/esm/abstract/tower.d.ts.map +1 -1
  72. package/esm/abstract/tower.js +7 -6
  73. package/esm/abstract/tower.js.map +1 -1
  74. package/esm/abstract/utils.d.ts.map +1 -1
  75. package/esm/abstract/utils.js +21 -23
  76. package/esm/abstract/utils.js.map +1 -1
  77. package/esm/abstract/weierstrass.d.ts +19 -3
  78. package/esm/abstract/weierstrass.d.ts.map +1 -1
  79. package/esm/abstract/weierstrass.js +150 -72
  80. package/esm/abstract/weierstrass.js.map +1 -1
  81. package/esm/bls12-381.js +8 -8
  82. package/esm/bn254.d.ts +2 -1
  83. package/esm/bn254.d.ts.map +1 -1
  84. package/esm/bn254.js +7 -6
  85. package/esm/bn254.js.map +1 -1
  86. package/esm/ed448.js +1 -1
  87. package/esm/ed448.js.map +1 -1
  88. package/esm/jubjub.d.ts.map +1 -1
  89. package/esm/jubjub.js +8 -2
  90. package/esm/jubjub.js.map +1 -1
  91. package/esm/p256.d.ts.map +1 -1
  92. package/esm/p256.js +6 -6
  93. package/esm/p256.js.map +1 -1
  94. package/esm/p384.d.ts.map +1 -1
  95. package/esm/p384.js +6 -6
  96. package/esm/p384.js.map +1 -1
  97. package/esm/p521.d.ts.map +1 -1
  98. package/esm/p521.js +7 -7
  99. package/esm/p521.js.map +1 -1
  100. package/esm/secp256k1.d.ts.map +1 -1
  101. package/esm/secp256k1.js +8 -8
  102. package/esm/secp256k1.js.map +1 -1
  103. package/jubjub.d.ts.map +1 -1
  104. package/jubjub.js +8 -2
  105. package/jubjub.js.map +1 -1
  106. package/p256.d.ts.map +1 -1
  107. package/p256.js +6 -6
  108. package/p256.js.map +1 -1
  109. package/p384.d.ts.map +1 -1
  110. package/p384.js +6 -6
  111. package/p384.js.map +1 -1
  112. package/p521.d.ts.map +1 -1
  113. package/p521.js +7 -7
  114. package/p521.js.map +1 -1
  115. package/package.json +28 -20
  116. package/secp256k1.d.ts.map +1 -1
  117. package/secp256k1.js +8 -8
  118. package/secp256k1.js.map +1 -1
  119. package/src/abstract/bls.ts +25 -13
  120. package/src/abstract/curve.ts +228 -23
  121. package/src/abstract/edwards.ts +40 -11
  122. package/src/abstract/hash-to-curve.ts +5 -6
  123. package/src/abstract/modular.ts +29 -19
  124. package/src/abstract/montgomery.ts +5 -3
  125. package/src/abstract/poseidon.ts +20 -24
  126. package/src/abstract/tower.ts +8 -6
  127. package/src/abstract/utils.ts +18 -24
  128. package/src/abstract/weierstrass.ts +144 -64
  129. package/src/bls12-381.ts +9 -9
  130. package/src/bn254.ts +16 -7
  131. package/src/ed448.ts +1 -1
  132. package/src/jubjub.ts +7 -2
  133. package/src/p256.ts +6 -6
  134. package/src/p384.ts +6 -6
  135. package/src/p521.ts +7 -7
  136. package/src/secp256k1.ts +8 -8
@@ -1,7 +1,7 @@
1
1
  /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
2
2
  // Abelian group utilities
3
3
  import { IField, validateField, nLength } from './modular.js';
4
- import { validateObject } from './utils.js';
4
+ import { validateObject, bitLen } from './utils.js';
5
5
  const _0n = BigInt(0);
6
6
  const _1n = BigInt(1);
7
7
 
@@ -25,11 +25,45 @@ export type GroupConstructor<T> = {
25
25
  };
26
26
  export type Mapper<T> = (i: T[]) => T[];
27
27
 
28
+ function constTimeNegate<T extends Group<T>>(condition: boolean, item: T): T {
29
+ const neg = item.negate();
30
+ return condition ? neg : item;
31
+ }
32
+
33
+ function validateW(W: number, bits: number) {
34
+ if (!Number.isSafeInteger(W) || W <= 0 || W > bits)
35
+ throw new Error('invalid window size, expected [1..' + bits + '], got W=' + W);
36
+ }
37
+
38
+ function calcWOpts(W: number, bits: number) {
39
+ validateW(W, bits);
40
+ const windows = Math.ceil(bits / W) + 1; // +1, because
41
+ const windowSize = 2 ** (W - 1); // -1 because we skip zero
42
+ return { windows, windowSize };
43
+ }
44
+
45
+ function validateMSMPoints(points: any[], c: any) {
46
+ if (!Array.isArray(points)) throw new Error('array expected');
47
+ points.forEach((p, i) => {
48
+ if (!(p instanceof c)) throw new Error('invalid point at index ' + i);
49
+ });
50
+ }
51
+ function validateMSMScalars(scalars: any[], field: any) {
52
+ if (!Array.isArray(scalars)) throw new Error('array of scalars expected');
53
+ scalars.forEach((s, i) => {
54
+ if (!field.isValid(s)) throw new Error('invalid scalar at index ' + i);
55
+ });
56
+ }
57
+
28
58
  // Since points in different groups cannot be equal (different object constructor),
29
59
  // we can have single place to store precomputes
30
60
  const pointPrecomputes = new WeakMap<any, any[]>();
31
61
  const pointWindowSizes = new WeakMap<any, number>(); // This allows use make points immutable (nothing changes inside)
32
62
 
63
+ function getW(P: any): number {
64
+ return pointWindowSizes.get(P) || 1;
65
+ }
66
+
33
67
  // Elliptic curve multiplication of Point by scalar. Fragile.
34
68
  // Scalars should always be less than curve order: this should be checked inside of a curve itself.
35
69
  // Creates precomputation tables for fast multiplication:
@@ -42,25 +76,15 @@ const pointWindowSizes = new WeakMap<any, number>(); // This allows use make poi
42
76
  // TODO: Research returning 2d JS array of windows, instead of a single window. This would allow
43
77
  // windows to be in different memory locations
44
78
  export function wNAF<T extends Group<T>>(c: GroupConstructor<T>, bits: number) {
45
- const constTimeNegate = (condition: boolean, item: T): T => {
46
- const neg = item.negate();
47
- return condition ? neg : item;
48
- };
49
- const validateW = (W: number) => {
50
- if (!Number.isSafeInteger(W) || W <= 0 || W > bits)
51
- throw new Error(`Wrong window size=${W}, should be [1..${bits}]`);
52
- };
53
- const opts = (W: number) => {
54
- validateW(W);
55
- const windows = Math.ceil(bits / W) + 1; // +1, because
56
- const windowSize = 2 ** (W - 1); // -1 because we skip zero
57
- return { windows, windowSize };
58
- };
59
79
  return {
60
80
  constTimeNegate,
81
+
82
+ hasPrecomputes(elm: T) {
83
+ return getW(elm) !== 1;
84
+ },
85
+
61
86
  // non-const time multiplication ladder
62
- unsafeLadder(elm: T, n: bigint) {
63
- let p = c.ZERO;
87
+ unsafeLadder(elm: T, n: bigint, p = c.ZERO) {
64
88
  let d: T = elm;
65
89
  while (n > _0n) {
66
90
  if (n & _1n) p = p.add(d);
@@ -78,10 +102,12 @@ export function wNAF<T extends Group<T>>(c: GroupConstructor<T>, bits: number) {
78
102
  * - 𝑊 is the window size
79
103
  * - 𝑛 is the bitlength of the curve order.
80
104
  * For a 256-bit curve and window size 8, the number of precomputed points is 128 * 33 = 4224.
105
+ * @param elm Point instance
106
+ * @param W window size
81
107
  * @returns precomputed point tables flattened to a single array
82
108
  */
83
109
  precomputeWindow(elm: T, W: number): Group<T>[] {
84
- const { windows, windowSize } = opts(W);
110
+ const { windows, windowSize } = calcWOpts(W, bits);
85
111
  const points: T[] = [];
86
112
  let p: T = elm;
87
113
  let base = p;
@@ -108,7 +134,7 @@ export function wNAF<T extends Group<T>>(c: GroupConstructor<T>, bits: number) {
108
134
  wNAF(W: number, precomputes: T[], n: bigint): { p: T; f: T } {
109
135
  // TODO: maybe check that scalar is less than group order? wNAF behavious is undefined otherwise
110
136
  // But need to carefully remove other checks before wNAF. ORDER == bits here
111
- const { windows, windowSize } = opts(W);
137
+ const { windows, windowSize } = calcWOpts(W, bits);
112
138
 
113
139
  let p = c.ZERO;
114
140
  let f = c.BASE;
@@ -159,28 +185,207 @@ export function wNAF<T extends Group<T>>(c: GroupConstructor<T>, bits: number) {
159
185
  return { p, f };
160
186
  },
161
187
 
162
- wNAFCached(P: T, n: bigint, transform: Mapper<T>): { p: T; f: T } {
163
- const W: number = pointWindowSizes.get(P) || 1;
188
+ /**
189
+ * Implements ec unsafe (non const-time) multiplication using precomputed tables and w-ary non-adjacent form.
190
+ * @param W window size
191
+ * @param precomputes precomputed tables
192
+ * @param n scalar (we don't check here, but should be less than curve order)
193
+ * @param acc accumulator point to add result of multiplication
194
+ * @returns point
195
+ */
196
+ wNAFUnsafe(W: number, precomputes: T[], n: bigint, acc: T = c.ZERO): T {
197
+ const { windows, windowSize } = calcWOpts(W, bits);
198
+ const mask = BigInt(2 ** W - 1); // Create mask with W ones: 0b1111 for W=4 etc.
199
+ const maxNumber = 2 ** W;
200
+ const shiftBy = BigInt(W);
201
+ for (let window = 0; window < windows; window++) {
202
+ const offset = window * windowSize;
203
+ if (n === _0n) break; // No need to go over empty scalar
204
+ // Extract W bits.
205
+ let wbits = Number(n & mask);
206
+ // Shift number by W bits.
207
+ n >>= shiftBy;
208
+ // If the bits are bigger than max size, we'll split those.
209
+ // +224 => 256 - 32
210
+ if (wbits > windowSize) {
211
+ wbits -= maxNumber;
212
+ n += _1n;
213
+ }
214
+ if (wbits === 0) continue;
215
+ let curr = precomputes[offset + Math.abs(wbits) - 1]; // -1 because we skip zero
216
+ if (wbits < 0) curr = curr.negate();
217
+ // NOTE: by re-using acc, we can save a lot of additions in case of MSM
218
+ acc = acc.add(curr);
219
+ }
220
+ return acc;
221
+ },
222
+
223
+ getPrecomputes(W: number, P: T, transform: Mapper<T>): T[] {
164
224
  // Calculate precomputes on a first run, reuse them after
165
225
  let comp = pointPrecomputes.get(P);
166
226
  if (!comp) {
167
227
  comp = this.precomputeWindow(P, W) as T[];
168
228
  if (W !== 1) pointPrecomputes.set(P, transform(comp));
169
229
  }
170
- return this.wNAF(W, comp, n);
230
+ return comp;
171
231
  },
232
+
233
+ wNAFCached(P: T, n: bigint, transform: Mapper<T>): { p: T; f: T } {
234
+ const W = getW(P);
235
+ return this.wNAF(W, this.getPrecomputes(W, P, transform), n);
236
+ },
237
+
238
+ wNAFCachedUnsafe(P: T, n: bigint, transform: Mapper<T>, prev?: T): T {
239
+ const W = getW(P);
240
+ if (W === 1) return this.unsafeLadder(P, n, prev); // For W=1 ladder is ~x2 faster
241
+ return this.wNAFUnsafe(W, this.getPrecomputes(W, P, transform), n, prev);
242
+ },
243
+
172
244
  // We calculate precomputes for elliptic curve point multiplication
173
245
  // using windowed method. This specifies window size and
174
246
  // stores precomputed values. Usually only base point would be precomputed.
175
247
 
176
248
  setWindowSize(P: T, W: number) {
177
- validateW(W);
249
+ validateW(W, bits);
178
250
  pointWindowSizes.set(P, W);
179
251
  pointPrecomputes.delete(P);
180
252
  },
181
253
  };
182
254
  }
183
255
 
256
+ /**
257
+ * Pippenger algorithm for multi-scalar multiplication (MSM, Pa + Qb + Rc + ...).
258
+ * 30x faster vs naive addition on L=4096, 10x faster with precomputes.
259
+ * For N=254bit, L=1, it does: 1024 ADD + 254 DBL. For L=5: 1536 ADD + 254 DBL.
260
+ * Algorithmically constant-time (for same L), even when 1 point + scalar, or when scalar = 0.
261
+ * @param c Curve Point constructor
262
+ * @param fieldN field over CURVE.N - important that it's not over CURVE.P
263
+ * @param points array of L curve points
264
+ * @param scalars array of L scalars (aka private keys / bigints)
265
+ */
266
+ export function pippenger<T extends Group<T>>(
267
+ c: GroupConstructor<T>,
268
+ fieldN: IField<bigint>,
269
+ points: T[],
270
+ scalars: bigint[]
271
+ ): T {
272
+ // If we split scalars by some window (let's say 8 bits), every chunk will only
273
+ // take 256 buckets even if there are 4096 scalars, also re-uses double.
274
+ // TODO:
275
+ // - https://eprint.iacr.org/2024/750.pdf
276
+ // - https://tches.iacr.org/index.php/TCHES/article/view/10287
277
+ // 0 is accepted in scalars
278
+ validateMSMPoints(points, c);
279
+ validateMSMScalars(scalars, fieldN);
280
+ if (points.length !== scalars.length)
281
+ throw new Error('arrays of points and scalars must have equal length');
282
+ const zero = c.ZERO;
283
+ const wbits = bitLen(BigInt(points.length));
284
+ const windowSize = wbits > 12 ? wbits - 3 : wbits > 4 ? wbits - 2 : wbits ? 2 : 1; // in bits
285
+ const MASK = (1 << windowSize) - 1;
286
+ const buckets = new Array(MASK + 1).fill(zero); // +1 for zero array
287
+ const lastBits = Math.floor((fieldN.BITS - 1) / windowSize) * windowSize;
288
+ let sum = zero;
289
+ for (let i = lastBits; i >= 0; i -= windowSize) {
290
+ buckets.fill(zero);
291
+ for (let j = 0; j < scalars.length; j++) {
292
+ const scalar = scalars[j];
293
+ const wbits = Number((scalar >> BigInt(i)) & BigInt(MASK));
294
+ buckets[wbits] = buckets[wbits].add(points[j]);
295
+ }
296
+ let resI = zero; // not using this will do small speed-up, but will lose ct
297
+ // Skip first bucket, because it is zero
298
+ for (let j = buckets.length - 1, sumI = zero; j > 0; j--) {
299
+ sumI = sumI.add(buckets[j]);
300
+ resI = resI.add(sumI);
301
+ }
302
+ sum = sum.add(resI);
303
+ if (i !== 0) for (let j = 0; j < windowSize; j++) sum = sum.double();
304
+ }
305
+ return sum as T;
306
+ }
307
+ /**
308
+ * Precomputed multi-scalar multiplication (MSM, Pa + Qb + Rc + ...).
309
+ * @param c Curve Point constructor
310
+ * @param fieldN field over CURVE.N - important that it's not over CURVE.P
311
+ * @param points array of L curve points
312
+ * @returns function which multiplies points with scaars
313
+ */
314
+ export function precomputeMSMUnsafe<T extends Group<T>>(
315
+ c: GroupConstructor<T>,
316
+ fieldN: IField<bigint>,
317
+ points: T[],
318
+ windowSize: number
319
+ ) {
320
+ /**
321
+ * Performance Analysis of Window-based Precomputation
322
+ *
323
+ * Base Case (256-bit scalar, 8-bit window):
324
+ * - Standard precomputation requires:
325
+ * - 31 additions per scalar × 256 scalars = 7,936 ops
326
+ * - Plus 255 summary additions = 8,191 total ops
327
+ * Note: Summary additions can be optimized via accumulator
328
+ *
329
+ * Chunked Precomputation Analysis:
330
+ * - Using 32 chunks requires:
331
+ * - 255 additions per chunk
332
+ * - 256 doublings
333
+ * - Total: (255 × 32) + 256 = 8,416 ops
334
+ *
335
+ * Memory Usage Comparison:
336
+ * Window Size | Standard Points | Chunked Points
337
+ * ------------|-----------------|---------------
338
+ * 4-bit | 520 | 15
339
+ * 8-bit | 4,224 | 255
340
+ * 10-bit | 13,824 | 1,023
341
+ * 16-bit | 557,056 | 65,535
342
+ *
343
+ * Key Advantages:
344
+ * 1. Enables larger window sizes due to reduced memory overhead
345
+ * 2. More efficient for smaller scalar counts:
346
+ * - 16 chunks: (16 × 255) + 256 = 4,336 ops
347
+ * - ~2x faster than standard 8,191 ops
348
+ *
349
+ * Limitations:
350
+ * - Not suitable for plain precomputes (requires 256 constant doublings)
351
+ * - Performance degrades with larger scalar counts:
352
+ * - Optimal for ~256 scalars
353
+ * - Less efficient for 4096+ scalars (Pippenger preferred)
354
+ */
355
+ validateW(windowSize, fieldN.BITS);
356
+ validateMSMPoints(points, c);
357
+ const zero = c.ZERO;
358
+ const tableSize = 2 ** windowSize - 1; // table size (without zero)
359
+ const chunks = Math.ceil(fieldN.BITS / windowSize); // chunks of item
360
+ const MASK = BigInt((1 << windowSize) - 1);
361
+ const tables = points.map((p: T) => {
362
+ const res = [];
363
+ for (let i = 0, acc = p; i < tableSize; i++) {
364
+ res.push(acc);
365
+ acc = acc.add(p);
366
+ }
367
+ return res;
368
+ });
369
+ return (scalars: bigint[]): T => {
370
+ validateMSMScalars(scalars, fieldN);
371
+ if (scalars.length > points.length)
372
+ throw new Error('array of scalars must be smaller than array of points');
373
+ let res = zero;
374
+ for (let i = 0; i < chunks; i++) {
375
+ // No need to double if accumulator is still zero.
376
+ if (res !== zero) for (let j = 0; j < windowSize; j++) res = res.double();
377
+ const shiftBy = BigInt(chunks * windowSize - (i + 1) * windowSize);
378
+ for (let j = 0; j < scalars.length; j++) {
379
+ const n = scalars[j];
380
+ const curr = Number((n >> shiftBy) & MASK);
381
+ if (!curr) continue; // skip zero scalars chunks
382
+ res = res.add(tables[j][curr - 1]);
383
+ }
384
+ }
385
+ return res;
386
+ };
387
+ }
388
+
184
389
  // Generic BasicCurve interface: works even for polynomial fields (BLS): P, n, h would be ok.
185
390
  // Though generator can be different (Fp2 / Fp6 for BLS).
186
391
  export type BasicCurve<T> = {
@@ -1,7 +1,15 @@
1
1
  /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
2
2
  // Twisted Edwards curve. The formula is: ax² + y² = 1 + dx²y²
3
- import { AffinePoint, BasicCurve, Group, GroupConstructor, validateBasic, wNAF } from './curve.js';
4
- import { mod } from './modular.js';
3
+ import {
4
+ AffinePoint,
5
+ BasicCurve,
6
+ Group,
7
+ GroupConstructor,
8
+ validateBasic,
9
+ wNAF,
10
+ pippenger,
11
+ } from './curve.js';
12
+ import { mod, Field } from './modular.js';
5
13
  import * as ut from './utils.js';
6
14
  import { ensureBytes, FHash, Hex, memoized, abool } from './utils.js';
7
15
 
@@ -63,6 +71,7 @@ export interface ExtPointType extends Group<ExtPointType> {
63
71
  toAffine(iz?: bigint): AffinePoint<bigint>;
64
72
  toRawBytes(isCompressed?: boolean): Uint8Array;
65
73
  toHex(isCompressed?: boolean): string;
74
+ _setWindowSize(windowSize: number): void;
66
75
  }
67
76
  // Static methods of Extended Point with coordinates in X, Y, Z, T
68
77
  export interface ExtPointConstructor extends GroupConstructor<ExtPointType> {
@@ -70,6 +79,7 @@ export interface ExtPointConstructor extends GroupConstructor<ExtPointType> {
70
79
  fromAffine(p: AffinePoint<bigint>): ExtPointType;
71
80
  fromHex(hex: Hex): ExtPointType;
72
81
  fromPrivateKey(privateKey: Hex): ExtPointType;
82
+ msm(points: ExtPointType[], scalars: bigint[]): ExtPointType;
73
83
  }
74
84
 
75
85
  /**
@@ -96,6 +106,7 @@ export type CurveFn = {
96
106
  point: ExtPointType;
97
107
  pointBytes: Uint8Array;
98
108
  };
109
+ precompute: (windowSize?: number, point?: ExtPointType) => ExtPointType;
99
110
  };
100
111
  };
101
112
 
@@ -117,8 +128,13 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
117
128
  nByteLength,
118
129
  h: cofactor,
119
130
  } = CURVE;
131
+ // Important:
132
+ // There are some places where Fp.BYTES is used instead of nByteLength.
133
+ // So far, everything has been tested with curves of Fp.BYTES == nByteLength.
134
+ // TODO: test and find curves which behave otherwise.
120
135
  const MASK = _2n << (BigInt(nByteLength * 8) - _1n);
121
136
  const modP = Fp.create; // Function overrides
137
+ const Fn = Field(CURVE.n, CURVE.nBitLength);
122
138
 
123
139
  // sqrt(u/v)
124
140
  const uvRatio =
@@ -218,6 +234,10 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
218
234
  const toInv = Fp.invertBatch(points.map((p) => p.ez));
219
235
  return points.map((p, i) => p.toAffine(toInv[i])).map(Point.fromAffine);
220
236
  }
237
+ // Multiscalar Multiplication
238
+ static msm(points: Point[], scalars: bigint[]): Point {
239
+ return pippenger(Point, Fn, points, scalars);
240
+ }
221
241
 
222
242
  // "Private method", don't use it directly
223
243
  _setWindowSize(windowSize: number) {
@@ -336,13 +356,13 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
336
356
  // It's faster, but should only be used when you don't care about
337
357
  // an exposed private key e.g. sig verification.
338
358
  // Does NOT allow scalars higher than CURVE.n.
339
- multiplyUnsafe(scalar: bigint): Point {
359
+ // Accepts optional accumulator to merge with multiply (important for sparse scalars)
360
+ multiplyUnsafe(scalar: bigint, acc = Point.ZERO): Point {
340
361
  const n = scalar;
341
362
  ut.aInRange('scalar', n, _0n, CURVE_ORDER); // 0 <= scalar < L
342
363
  if (n === _0n) return I;
343
- if (this.equals(I) || n === _1n) return this;
344
- if (this.equals(G)) return this.wNAF(n).p;
345
- return wnaf.unsafeLadder(this, n);
364
+ if (this.is0() || n === _1n) return this;
365
+ return wnaf.wNAFCachedUnsafe(this, n, Point.normalizeZ, acc);
346
366
  }
347
367
 
348
368
  // Checks if point is of small order.
@@ -383,6 +403,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
383
403
  normed[len - 1] = lastByte & ~0x80; // clear last bit
384
404
  const y = ut.bytesToNumberLE(normed);
385
405
 
406
+ // zip215=true is good for consensus-critical apps. =false follows RFC8032 / NIST186-5.
386
407
  // RFC8032 prohibits >= p, but ZIP215 doesn't
387
408
  // zip215=true: 0 <= y < MASK (2^256 for ed25519)
388
409
  // zip215=false: 0 <= y < P (2^255-19 for ed25519)
@@ -430,7 +451,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
430
451
 
431
452
  /** Convenience method that creates public key and other stuff. RFC8032 5.1.5 */
432
453
  function getExtendedPublicKey(key: Hex) {
433
- const len = nByteLength;
454
+ const len = Fp.BYTES;
434
455
  key = ensureBytes('private key', key, len);
435
456
  // Hash private key with curve's hash function to produce uniformingly random input
436
457
  // Check byte lengths: ensure(64, h(ensure(32, key)))
@@ -465,23 +486,30 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
465
486
  const s = modN(r + k * scalar); // S = (r + k * s) mod L
466
487
  ut.aInRange('signature.s', s, _0n, CURVE_ORDER); // 0 <= s < l
467
488
  const res = ut.concatBytes(R, ut.numberToBytesLE(s, Fp.BYTES));
468
- return ensureBytes('result', res, nByteLength * 2); // 64-byte signature
489
+ return ensureBytes('result', res, Fp.BYTES * 2); // 64-byte signature
469
490
  }
470
491
 
471
492
  const verifyOpts: { context?: Hex; zip215?: boolean } = VERIFY_DEFAULT;
493
+
494
+ /**
495
+ * Verifies EdDSA signature against message and public key. RFC8032 5.1.7.
496
+ * An extended group equation is checked.
497
+ */
472
498
  function verify(sig: Hex, msg: Hex, publicKey: Hex, options = verifyOpts): boolean {
473
499
  const { context, zip215 } = options;
474
500
  const len = Fp.BYTES; // Verifies EdDSA signature against message and public key. RFC8032 5.1.7.
475
501
  sig = ensureBytes('signature', sig, 2 * len); // An extended group equation is checked.
476
502
  msg = ensureBytes('message', msg);
503
+ publicKey = ensureBytes('publicKey', publicKey, len);
477
504
  if (zip215 !== undefined) abool('zip215', zip215);
478
505
  if (prehash) msg = prehash(msg); // for ed25519ph, etc
479
506
 
480
507
  const s = ut.bytesToNumberLE(sig.slice(len, 2 * len));
481
- // zip215: true is good for consensus-critical apps and allows points < 2^256
482
- // zip215: false follows RFC8032 / NIST186-5 and restricts points to CURVE.p
483
508
  let A, R, SB;
484
509
  try {
510
+ // zip215=true is good for consensus-critical apps. =false follows RFC8032 / NIST186-5.
511
+ // zip215=true: 0 <= y < MASK (2^256 for ed25519)
512
+ // zip215=false: 0 <= y < P (2^255-19 for ed25519)
485
513
  A = Point.fromHex(publicKey, zip215);
486
514
  R = Point.fromHex(sig.slice(0, len), zip215);
487
515
  SB = G.multiplyUnsafe(s); // 0 <= s < l is done inside
@@ -492,6 +520,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
492
520
 
493
521
  const k = hashDomainToScalar(context, R.toRawBytes(), A.toRawBytes(), msg);
494
522
  const RkA = R.add(A.multiplyUnsafe(k));
523
+ // Extended group equation
495
524
  // [8][S]B = [8]R + [8][k]A'
496
525
  return RkA.subtract(SB).clearCofactor().equals(Point.ZERO);
497
526
  }
@@ -509,7 +538,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
509
538
  * but allows to speed-up subsequent getPublicKey() calls up to 20x.
510
539
  * @param windowSize 2, 4, 8, 16
511
540
  */
512
- precompute(windowSize = 8, point = Point.BASE): typeof Point.BASE {
541
+ precompute(windowSize = 8, point: ExtPointType = Point.BASE): ExtPointType {
513
542
  point._setWindowSize(windowSize);
514
543
  point.multiply(BigInt(3));
515
544
  return point;
@@ -27,9 +27,9 @@ const os2ip = bytesToNumberBE;
27
27
 
28
28
  // Integer to Octet Stream (numberToBytesBE)
29
29
  function i2osp(value: number, length: number): Uint8Array {
30
- if (value < 0 || value >= 1 << (8 * length)) {
31
- throw new Error(`bad I2OSP call: value=${value} length=${length}`);
32
- }
30
+ anum(value);
31
+ anum(length);
32
+ if (value < 0 || value >= 1 << (8 * length)) throw new Error('invalid I2OSP input: ' + value);
33
33
  const res = Array.from({ length }).fill(0) as number[];
34
34
  for (let i = length - 1; i >= 0; i--) {
35
35
  res[i] = value & 0xff;
@@ -65,7 +65,7 @@ export function expand_message_xmd(
65
65
  if (DST.length > 255) DST = H(concatBytes(utf8ToBytes('H2C-OVERSIZE-DST-'), DST));
66
66
  const { outputLen: b_in_bytes, blockLen: r_in_bytes } = H;
67
67
  const ell = Math.ceil(lenInBytes / b_in_bytes);
68
- if (ell > 255) throw new Error('Invalid xmd length');
68
+ if (lenInBytes > 65535 || ell > 255) throw new Error('expand_message_xmd: invalid lenInBytes');
69
69
  const DST_prime = concatBytes(DST, i2osp(DST.length, 1));
70
70
  const Z_pad = i2osp(0, r_in_bytes);
71
71
  const l_i_b_str = i2osp(lenInBytes, 2); // len_in_bytes_str
@@ -221,8 +221,7 @@ export function createHasher<T>(
221
221
  mapToCurve(scalars: bigint[]) {
222
222
  if (!Array.isArray(scalars)) throw new Error('mapToCurve: expected array of bigints');
223
223
  for (const i of scalars)
224
- if (typeof i !== 'bigint')
225
- throw new Error(`mapToCurve: expected array of bigints, got ${i} in array`);
224
+ if (typeof i !== 'bigint') throw new Error('mapToCurve: expected array of bigints');
226
225
  const P = Point.fromAffine(mapToCurve(scalars)).clearCofactor();
227
226
  P.assertValidity();
228
227
  return P;
@@ -10,11 +10,11 @@ import {
10
10
  validateObject,
11
11
  } from './utils.js';
12
12
  // prettier-ignore
13
- const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3);
13
+ const _0n = BigInt(0), _1n = BigInt(1), _2n = /* @__PURE__ */ BigInt(2), _3n = /* @__PURE__ */ BigInt(3);
14
14
  // prettier-ignore
15
- const _4n = BigInt(4), _5n = BigInt(5), _8n = BigInt(8);
15
+ const _4n = /* @__PURE__ */ BigInt(4), _5n = /* @__PURE__ */ BigInt(5), _8n = /* @__PURE__ */ BigInt(8);
16
16
  // prettier-ignore
17
- const _9n = BigInt(9), _16n = BigInt(16);
17
+ const _9n =/* @__PURE__ */ BigInt(9), _16n = /* @__PURE__ */ BigInt(16);
18
18
 
19
19
  // Calculates a modulo b
20
20
  export function mod(a: bigint, b: bigint): bigint {
@@ -29,7 +29,8 @@ export function mod(a: bigint, b: bigint): bigint {
29
29
  */
30
30
  // TODO: use field version && remove
31
31
  export function pow(num: bigint, power: bigint, modulo: bigint): bigint {
32
- if (modulo <= _0n || power < _0n) throw new Error('Expected power/modulo > 0');
32
+ if (power < _0n) throw new Error('invalid exponent, negatives unsupported');
33
+ if (modulo <= _0n) throw new Error('invalid modulus');
33
34
  if (modulo === _1n) return _0n;
34
35
  let res = _1n;
35
36
  while (power > _0n) {
@@ -52,9 +53,8 @@ export function pow2(x: bigint, power: bigint, modulo: bigint): bigint {
52
53
 
53
54
  // Inverses number over modulo
54
55
  export function invert(number: bigint, modulo: bigint): bigint {
55
- if (number === _0n || modulo <= _0n) {
56
- throw new Error(`invert: expected positive integers, got n=${number} mod=${modulo}`);
57
- }
56
+ if (number === _0n) throw new Error('invert: expected non-zero number');
57
+ if (modulo <= _0n) throw new Error('invert: expected positive modulus, got ' + modulo);
58
58
  // Euclidean GCD https://brilliant.org/wiki/extended-euclidean-algorithm/
59
59
  // Fermat's little theorem "CT-like" version inv(n) = n^(m-2) mod m is 30x slower.
60
60
  let a = mod(number, modulo);
@@ -97,7 +97,10 @@ export function tonelliShanks(P: bigint) {
97
97
  for (Q = P - _1n, S = 0; Q % _2n === _0n; Q /= _2n, S++);
98
98
 
99
99
  // Step 2: Select a non-square z such that (z | p) ≡ -1 and set c ≡ zq
100
- for (Z = _2n; Z < P && pow(Z, legendreC, P) !== P - _1n; Z++);
100
+ for (Z = _2n; Z < P && pow(Z, legendreC, P) !== P - _1n; Z++) {
101
+ // Crash instead of infinity loop, we cannot reasonable count until P.
102
+ if (Z > 1000) throw new Error('Cannot find square root: likely non-prime P');
103
+ }
101
104
 
102
105
  // Fast-path
103
106
  if (S === 1) {
@@ -273,7 +276,7 @@ export function validateField<T>(field: IField<T>) {
273
276
  export function FpPow<T>(f: IField<T>, num: T, power: bigint): T {
274
277
  // Should have same speed as pow for bigints
275
278
  // TODO: benchmark!
276
- if (power < _0n) throw new Error('Expected power > 0');
279
+ if (power < _0n) throw new Error('invalid exponent, negatives unsupported');
277
280
  if (power === _0n) return f.ONE;
278
281
  if (power === _1n) return num;
279
282
  let p = f.ONE;
@@ -360,10 +363,10 @@ export function Field(
360
363
  isLE = false,
361
364
  redef: Partial<IField<bigint>> = {}
362
365
  ): Readonly<FpField> {
363
- if (ORDER <= _0n) throw new Error(`Expected Field ORDER > 0, got ${ORDER}`);
366
+ if (ORDER <= _0n) throw new Error('invalid field: expected ORDER > 0, got ' + ORDER);
364
367
  const { nBitLength: BITS, nByteLength: BYTES } = nLength(ORDER, bitLen);
365
- if (BYTES > 2048) throw new Error('Field lengths over 2048 bytes are not supported');
366
- const sqrtP = FpSqrt(ORDER);
368
+ if (BYTES > 2048) throw new Error('invalid field: expected ORDER of <= 2048 bytes');
369
+ let sqrtP: ReturnType<typeof FpSqrt>; // cached sqrtP
367
370
  const f: Readonly<FpField> = Object.freeze({
368
371
  ORDER,
369
372
  BITS,
@@ -374,7 +377,7 @@ export function Field(
374
377
  create: (num) => mod(num, ORDER),
375
378
  isValid: (num) => {
376
379
  if (typeof num !== 'bigint')
377
- throw new Error(`Invalid field element: expected bigint, got ${typeof num}`);
380
+ throw new Error('invalid field element: expected bigint, got ' + typeof num);
378
381
  return _0n <= num && num < ORDER; // 0 is valid element, but it's not invertible
379
382
  },
380
383
  is0: (num) => num === _0n,
@@ -396,7 +399,12 @@ export function Field(
396
399
  mulN: (lhs, rhs) => lhs * rhs,
397
400
 
398
401
  inv: (num) => invert(num, ORDER),
399
- sqrt: redef.sqrt || ((n) => sqrtP(f, n)),
402
+ sqrt:
403
+ redef.sqrt ||
404
+ ((n) => {
405
+ if (!sqrtP) sqrtP = FpSqrt(ORDER);
406
+ return sqrtP(f, n);
407
+ }),
400
408
  invertBatch: (lst) => FpInvertBatch(f, lst),
401
409
  // TODO: do we really need constant cmov?
402
410
  // We don't have const-time bigints anyway, so probably will be not very useful
@@ -404,7 +412,7 @@ export function Field(
404
412
  toBytes: (num) => (isLE ? numberToBytesLE(num, BYTES) : numberToBytesBE(num, BYTES)),
405
413
  fromBytes: (bytes) => {
406
414
  if (bytes.length !== BYTES)
407
- throw new Error(`Fp.fromBytes: expected ${BYTES}, got ${bytes.length}`);
415
+ throw new Error('Field.fromBytes: expected ' + BYTES + ' bytes, got ' + bytes.length);
408
416
  return isLE ? bytesToNumberLE(bytes) : bytesToNumberBE(bytes);
409
417
  },
410
418
  } as FpField);
@@ -412,13 +420,13 @@ export function Field(
412
420
  }
413
421
 
414
422
  export function FpSqrtOdd<T>(Fp: IField<T>, elm: T) {
415
- if (!Fp.isOdd) throw new Error(`Field doesn't have isOdd`);
423
+ if (!Fp.isOdd) throw new Error("Field doesn't have isOdd");
416
424
  const root = Fp.sqrt(elm);
417
425
  return Fp.isOdd(root) ? root : Fp.neg(root);
418
426
  }
419
427
 
420
428
  export function FpSqrtEven<T>(Fp: IField<T>, elm: T) {
421
- if (!Fp.isOdd) throw new Error(`Field doesn't have isOdd`);
429
+ if (!Fp.isOdd) throw new Error("Field doesn't have isOdd");
422
430
  const root = Fp.sqrt(elm);
423
431
  return Fp.isOdd(root) ? Fp.neg(root) : root;
424
432
  }
@@ -438,7 +446,9 @@ export function hashToPrivateScalar(
438
446
  const hashLen = hash.length;
439
447
  const minLen = nLength(groupOrder).nByteLength + 8;
440
448
  if (minLen < 24 || hashLen < minLen || hashLen > 1024)
441
- throw new Error(`hashToPrivateScalar: expected ${minLen}-1024 bytes of input, got ${hashLen}`);
449
+ throw new Error(
450
+ 'hashToPrivateScalar: expected ' + minLen + '-1024 bytes of input, got ' + hashLen
451
+ );
442
452
  const num = isLE ? bytesToNumberLE(hash) : bytesToNumberBE(hash);
443
453
  return mod(num, groupOrder - _1n) + _1n;
444
454
  }
@@ -486,7 +496,7 @@ export function mapHashToField(key: Uint8Array, fieldOrder: bigint, isLE = false
486
496
  const minLen = getMinHashLength(fieldOrder);
487
497
  // No small numbers: need to understand bias story. No huge numbers: easier to detect JS timings.
488
498
  if (len < 16 || len < minLen || len > 1024)
489
- throw new Error(`expected ${minLen}-1024 bytes of input, got ${len}`);
499
+ throw new Error('expected ' + minLen + '-1024 bytes of input, got ' + len);
490
500
  const num = isLE ? bytesToNumberBE(key) : bytesToNumberLE(key);
491
501
  // `mod(x, 11)` can sometimes produce 0. `mod(x, 10) + 1` is the same, but no 0
492
502
  const reduced = mod(num, fieldOrder - _1n) + _1n;
@@ -158,8 +158,10 @@ export function montgomery(curveDef: CurveType): CurveFn {
158
158
  function decodeScalar(n: Hex): bigint {
159
159
  const bytes = ensureBytes('scalar', n);
160
160
  const len = bytes.length;
161
- if (len !== montgomeryBytes && len !== fieldLen)
162
- throw new Error(`Expected ${montgomeryBytes} or ${fieldLen} bytes, got ${len}`);
161
+ if (len !== montgomeryBytes && len !== fieldLen) {
162
+ let valid = '' + montgomeryBytes + ' or ' + fieldLen;
163
+ throw new Error('invalid scalar, expected ' + valid + ' bytes, got ' + len);
164
+ }
163
165
  return bytesToNumberLE(adjustScalarBytes(bytes));
164
166
  }
165
167
  function scalarMult(scalar: Hex, u: Hex): Uint8Array {
@@ -168,7 +170,7 @@ export function montgomery(curveDef: CurveType): CurveFn {
168
170
  const pu = montgomeryLadder(pointU, _scalar);
169
171
  // The result was not contributory
170
172
  // https://cr.yp.to/ecdh.html#validate
171
- if (pu === _0n) throw new Error('Invalid private or public key received');
173
+ if (pu === _0n) throw new Error('invalid private or public key received');
172
174
  return encodeUCoordinate(pu);
173
175
  }
174
176
  // Computes public key from private. By doing scalar multiplication of base point.