@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.
Files changed (110) hide show
  1. package/README.md +214 -122
  2. package/abstract/bls.d.ts +299 -16
  3. package/abstract/bls.d.ts.map +1 -1
  4. package/abstract/bls.js +82 -22
  5. package/abstract/bls.js.map +1 -1
  6. package/abstract/curve.d.ts +274 -27
  7. package/abstract/curve.d.ts.map +1 -1
  8. package/abstract/curve.js +177 -23
  9. package/abstract/curve.js.map +1 -1
  10. package/abstract/edwards.d.ts +166 -30
  11. package/abstract/edwards.d.ts.map +1 -1
  12. package/abstract/edwards.js +221 -86
  13. package/abstract/edwards.js.map +1 -1
  14. package/abstract/fft.d.ts +322 -10
  15. package/abstract/fft.d.ts.map +1 -1
  16. package/abstract/fft.js +154 -12
  17. package/abstract/fft.js.map +1 -1
  18. package/abstract/frost.d.ts +293 -0
  19. package/abstract/frost.d.ts.map +1 -0
  20. package/abstract/frost.js +704 -0
  21. package/abstract/frost.js.map +1 -0
  22. package/abstract/hash-to-curve.d.ts +173 -24
  23. package/abstract/hash-to-curve.d.ts.map +1 -1
  24. package/abstract/hash-to-curve.js +170 -31
  25. package/abstract/hash-to-curve.js.map +1 -1
  26. package/abstract/modular.d.ts +429 -37
  27. package/abstract/modular.d.ts.map +1 -1
  28. package/abstract/modular.js +414 -119
  29. package/abstract/modular.js.map +1 -1
  30. package/abstract/montgomery.d.ts +83 -12
  31. package/abstract/montgomery.d.ts.map +1 -1
  32. package/abstract/montgomery.js +32 -7
  33. package/abstract/montgomery.js.map +1 -1
  34. package/abstract/oprf.d.ts +164 -91
  35. package/abstract/oprf.d.ts.map +1 -1
  36. package/abstract/oprf.js +88 -29
  37. package/abstract/oprf.js.map +1 -1
  38. package/abstract/poseidon.d.ts +138 -7
  39. package/abstract/poseidon.d.ts.map +1 -1
  40. package/abstract/poseidon.js +178 -15
  41. package/abstract/poseidon.js.map +1 -1
  42. package/abstract/tower.d.ts +122 -3
  43. package/abstract/tower.d.ts.map +1 -1
  44. package/abstract/tower.js +323 -139
  45. package/abstract/tower.js.map +1 -1
  46. package/abstract/weierstrass.d.ts +339 -76
  47. package/abstract/weierstrass.d.ts.map +1 -1
  48. package/abstract/weierstrass.js +395 -205
  49. package/abstract/weierstrass.js.map +1 -1
  50. package/bls12-381.d.ts +16 -2
  51. package/bls12-381.d.ts.map +1 -1
  52. package/bls12-381.js +199 -209
  53. package/bls12-381.js.map +1 -1
  54. package/bn254.d.ts +11 -2
  55. package/bn254.d.ts.map +1 -1
  56. package/bn254.js +93 -38
  57. package/bn254.js.map +1 -1
  58. package/ed25519.d.ts +125 -14
  59. package/ed25519.d.ts.map +1 -1
  60. package/ed25519.js +202 -40
  61. package/ed25519.js.map +1 -1
  62. package/ed448.d.ts +108 -14
  63. package/ed448.d.ts.map +1 -1
  64. package/ed448.js +194 -42
  65. package/ed448.js.map +1 -1
  66. package/index.js +7 -1
  67. package/index.js.map +1 -1
  68. package/misc.d.ts +106 -7
  69. package/misc.d.ts.map +1 -1
  70. package/misc.js +141 -32
  71. package/misc.js.map +1 -1
  72. package/nist.d.ts +112 -11
  73. package/nist.d.ts.map +1 -1
  74. package/nist.js +139 -17
  75. package/nist.js.map +1 -1
  76. package/package.json +11 -6
  77. package/secp256k1.d.ts +92 -15
  78. package/secp256k1.d.ts.map +1 -1
  79. package/secp256k1.js +211 -28
  80. package/secp256k1.js.map +1 -1
  81. package/src/abstract/bls.ts +350 -67
  82. package/src/abstract/curve.ts +327 -44
  83. package/src/abstract/edwards.ts +367 -143
  84. package/src/abstract/fft.ts +369 -36
  85. package/src/abstract/frost.ts +1092 -0
  86. package/src/abstract/hash-to-curve.ts +255 -56
  87. package/src/abstract/modular.ts +591 -144
  88. package/src/abstract/montgomery.ts +114 -30
  89. package/src/abstract/oprf.ts +383 -194
  90. package/src/abstract/poseidon.ts +235 -35
  91. package/src/abstract/tower.ts +428 -159
  92. package/src/abstract/weierstrass.ts +710 -312
  93. package/src/bls12-381.ts +239 -236
  94. package/src/bn254.ts +107 -46
  95. package/src/ed25519.ts +227 -55
  96. package/src/ed448.ts +227 -57
  97. package/src/index.ts +7 -1
  98. package/src/misc.ts +154 -35
  99. package/src/nist.ts +143 -20
  100. package/src/secp256k1.ts +284 -41
  101. package/src/utils.ts +583 -81
  102. package/src/webcrypto.ts +302 -73
  103. package/utils.d.ts +457 -24
  104. package/utils.d.ts.map +1 -1
  105. package/utils.js +410 -53
  106. package/utils.js.map +1 -1
  107. package/webcrypto.d.ts +167 -25
  108. package/webcrypto.d.ts.map +1 -1
  109. package/webcrypto.js +165 -58
  110. package/webcrypto.js.map +1 -1
package/abstract/tower.js CHANGED
@@ -10,28 +10,65 @@
10
10
  * @module
11
11
  */
12
12
  /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
13
- import { bitGet, bitLen, concatBytes, notImplemented } from "../utils.js";
13
+ import { abytes, aInRange, asafenumber, bitGet, bitLen, concatBytes, notImplemented, validateObject, } from "../utils.js";
14
14
  import * as mod from "./modular.js";
15
15
  // Be friendly to bad ECMAScript parsers by not using bigint literals
16
16
  // prettier-ignore
17
- const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3);
17
+ const _0n = /* @__PURE__ */ BigInt(0), _1n = /* @__PURE__ */ BigInt(1), _2n = /* @__PURE__ */ BigInt(2), _3n = /* @__PURE__ */ BigInt(3), _6n = /* @__PURE__ */ BigInt(6), _12n = /* @__PURE__ */ BigInt(12);
18
+ const isObj = (value) => !!value && typeof value === 'object';
18
19
  function calcFrobeniusCoefficients(Fp, nonResidue, modulus, degree, num = 1, divisor) {
20
+ asafenumber(num, 'num');
21
+ const F = Fp;
22
+ // Generic callers can hit empty / fractional row counts through `__TEST`; fail closed instead of
23
+ // silently returning `[]` or deriving extra Frobenius rows from a truncated loop bound.
24
+ if (num <= 0)
25
+ throw new Error('calcFrobeniusCoefficients: expected positive row count, got ' + num);
19
26
  const _divisor = BigInt(divisor === undefined ? degree : divisor);
20
27
  const towerModulus = modulus ** BigInt(degree);
21
28
  const res = [];
29
+ // Derive tower-basis multipliers for the `p^k` Frobenius action. The
30
+ // divisions below are expected to be exact for the chosen tower parameters.
22
31
  for (let i = 0; i < num; i++) {
23
32
  const a = BigInt(i + 1);
24
33
  const powers = [];
25
34
  for (let j = 0, qPower = _1n; j < degree; j++) {
26
- const power = ((a * qPower - a) / _divisor) % towerModulus;
27
- powers.push(Fp.pow(nonResidue, power));
35
+ const numer = a * qPower - a;
36
+ // Shipped towers divide cleanly here, but generic callers can pick bad
37
+ // params. Bigint division would floor and derive the wrong Frobenius table.
38
+ if (numer % _divisor)
39
+ throw new Error('calcFrobeniusCoefficients: inexact tower exponent');
40
+ const power = (numer / _divisor) % towerModulus;
41
+ powers.push(F.pow(nonResidue, power));
28
42
  qPower *= modulus;
29
43
  }
30
44
  res.push(powers);
31
45
  }
32
46
  return res;
33
47
  }
48
+ export const __TEST =
49
+ /* @__PURE__ */ Object.freeze({
50
+ calcFrobeniusCoefficients,
51
+ });
34
52
  // This works same at least for bls12-381, bn254 and bls12-377
53
+ /**
54
+ * @param Fp - Base field implementation.
55
+ * @param Fp2 - Quadratic extension field.
56
+ * @param base - Twist-specific Frobenius base whose powers yield the `c1` / `c2` constants.
57
+ * BLS12-381 uses `1 / NONRESIDUE`; BN254 uses `NONRESIDUE`.
58
+ * @returns Frobenius endomorphism helpers.
59
+ * @throws If the derived Frobenius constants are inconsistent for the tower. {@link Error}
60
+ * @example
61
+ * Build Frobenius endomorphism helpers for a BLS extension tower.
62
+ *
63
+ * ```ts
64
+ * import { psiFrobenius } from '@noble/curves/abstract/tower.js';
65
+ * import { bls12_381 } from '@noble/curves/bls12-381.js';
66
+ * const Fp = bls12_381.fields.Fp;
67
+ * const Fp2 = bls12_381.fields.Fp2;
68
+ * const frob = psiFrobenius(Fp, Fp2, Fp2.div(Fp2.ONE, Fp2.NONRESIDUE));
69
+ * const point = frob.G2psi(bls12_381.G2.Point, bls12_381.G2.Point.BASE);
70
+ * ```
71
+ */
35
72
  export function psiFrobenius(Fp, Fp2, base) {
36
73
  // GLV endomorphism Ψ(P)
37
74
  const PSI_X = Fp2.pow(base, (Fp.ORDER - _1n) / _3n); // u^((p-1)/3)
@@ -44,9 +81,9 @@ export function psiFrobenius(Fp, Fp2, base) {
44
81
  }
45
82
  // Ψ²(P) endomorphism (psi2(x) = psi(psi(x)))
46
83
  const PSI2_X = Fp2.pow(base, (Fp.ORDER ** _2n - _1n) / _3n); // u^((p^2 - 1)/3)
47
- // This equals -1, which causes y to be Fp2.neg(y).
48
- // But not sure if there are case when this is not true?
49
- const PSI2_Y = Fp2.pow(base, (Fp.ORDER ** _2n - _1n) / _2n); // u^((p^2 - 1)/3)
84
+ // Current towers rely on this landing on `-1`, which lets psi2 map `y` with
85
+ // one negation instead of carrying a separate Frobenius multiplier.
86
+ const PSI2_Y = Fp2.pow(base, (Fp.ORDER ** _2n - _1n) / _2n); // u^((p^2 - 1)/2)
50
87
  if (!Fp2.eql(PSI2_Y, Fp2.neg(Fp2.ONE)))
51
88
  throw new Error('psiFrobenius: PSI2_Y!==-1');
52
89
  function psi2(x, y) {
@@ -62,12 +99,6 @@ export function psiFrobenius(Fp, Fp2, base) {
62
99
  const G2psi2 = mapAffine(psi2);
63
100
  return { psi, psi2, G2psi, G2psi2, PSI_X, PSI_Y, PSI2_X, PSI2_Y };
64
101
  }
65
- const Fp2fromBigTuple = (Fp, tuple) => {
66
- if (tuple.length !== 2)
67
- throw new Error('invalid tuple');
68
- const fps = tuple.map((n) => Fp.create(n));
69
- return { c0: fps[0], c1: fps[1] };
70
- };
71
102
  class _Field2 {
72
103
  ORDER;
73
104
  BITS;
@@ -82,6 +113,7 @@ class _Field2 {
82
113
  Fp_div2;
83
114
  FROBENIUS_COEFFICIENTS;
84
115
  constructor(Fp, opts = {}) {
116
+ const { NONRESIDUE = BigInt(-1), FP2_NONRESIDUE, Fp2mulByB } = opts;
85
117
  const ORDER = Fp.ORDER;
86
118
  const FP2_ORDER = ORDER * ORDER;
87
119
  this.Fp = Fp;
@@ -89,39 +121,66 @@ class _Field2 {
89
121
  this.BITS = bitLen(FP2_ORDER);
90
122
  this.BYTES = Math.ceil(bitLen(FP2_ORDER) / 8);
91
123
  this.isLE = Fp.isLE;
92
- this.ZERO = { c0: Fp.ZERO, c1: Fp.ZERO };
93
- this.ONE = { c0: Fp.ONE, c1: Fp.ZERO };
94
- this.Fp_NONRESIDUE = Fp.create(opts.NONRESIDUE || BigInt(-1));
124
+ this.ZERO = this.create({ c0: Fp.ZERO, c1: Fp.ZERO });
125
+ this.ONE = this.create({ c0: Fp.ONE, c1: Fp.ZERO });
126
+ // These knobs only swap constants for the shipped quadratic tower shape:
127
+ // arithmetic below assumes `u^2 = -1`, and bytes are handled as two adjacent
128
+ // `Fp` limbs (`fromBytes` / `toBytes` expect the shipped `2 * Fp.BYTES` layout).
129
+ this.Fp_NONRESIDUE = Fp.create(NONRESIDUE);
95
130
  this.Fp_div2 = Fp.div(Fp.ONE, _2n); // 1/2
96
- this.NONRESIDUE = Fp2fromBigTuple(Fp, opts.FP2_NONRESIDUE);
97
- // const Fp2Nonresidue = Fp2fromBigTuple(opts.FP2_NONRESIDUE);
98
- this.FROBENIUS_COEFFICIENTS = calcFrobeniusCoefficients(Fp, this.Fp_NONRESIDUE, Fp.ORDER, 2)[0];
99
- this.mulByB = opts.Fp2mulByB;
100
- Object.seal(this);
131
+ this.NONRESIDUE = this.create({ c0: FP2_NONRESIDUE[0], c1: FP2_NONRESIDUE[1] });
132
+ // const Fp2Nonresidue = this.create({ c0: FP2_NONRESIDUE![0], c1: FP2_NONRESIDUE![1] });
133
+ this.FROBENIUS_COEFFICIENTS = Object.freeze(calcFrobeniusCoefficients(Fp, this.Fp_NONRESIDUE, Fp.ORDER, 2)[0]);
134
+ this.mulByB = (num) => {
135
+ // This config hook is trusted to return a canonical Fp2 value already.
136
+ // Copy+freeze it to keep the tower immutability invariant without mutating caller objects.
137
+ const { c0, c1 } = Fp2mulByB(num);
138
+ return Object.freeze({ c0, c1 });
139
+ };
140
+ Object.freeze(this);
101
141
  }
102
142
  fromBigTuple(tuple) {
103
- return Fp2fromBigTuple(this.Fp, tuple);
143
+ if (!Array.isArray(tuple) || tuple.length !== 2)
144
+ throw new Error('invalid Fp2.fromBigTuple');
145
+ const [c0, c1] = tuple;
146
+ if (typeof c0 !== 'bigint' || typeof c1 !== 'bigint')
147
+ throw new Error('invalid Fp2.fromBigTuple');
148
+ return this.create({ c0, c1 });
104
149
  }
105
150
  create(num) {
106
- return num;
107
- }
108
- isValid({ c0, c1 }) {
109
- function isValidC(num, ORDER) {
110
- return typeof num === 'bigint' && _0n <= num && num < ORDER;
111
- }
112
- return isValidC(c0, this.ORDER) && isValidC(c1, this.ORDER);
151
+ const { Fp } = this;
152
+ const c0 = Fp.create(num.c0);
153
+ const c1 = Fp.create(num.c1);
154
+ // Bigint field elements are immutable values, and higher-level code relies on
155
+ // that invariant. Copy+freeze tower values too without mutating caller-owned objects.
156
+ return Object.freeze({ c0, c1 });
157
+ }
158
+ isValid(num) {
159
+ if (!isObj(num))
160
+ throw new TypeError('invalid field element: expected object, got ' + typeof num);
161
+ const { c0, c1 } = num;
162
+ const { Fp } = this;
163
+ // Match base-field `isValid(...)`: malformed coordinate types are errors, not a `false`
164
+ // predicate result.
165
+ return Fp.isValid(c0) && Fp.isValid(c1);
113
166
  }
114
- is0({ c0, c1 }) {
115
- return this.Fp.is0(c0) && this.Fp.is0(c1);
167
+ is0(num) {
168
+ if (!isObj(num))
169
+ return false;
170
+ const { c0, c1 } = num;
171
+ const { Fp } = this;
172
+ return Fp.is0(c0) && Fp.is0(c1);
116
173
  }
117
174
  isValidNot0(num) {
118
175
  return !this.is0(num) && this.isValid(num);
119
176
  }
120
177
  eql({ c0, c1 }, { c0: r0, c1: r1 }) {
121
- return this.Fp.eql(c0, r0) && this.Fp.eql(c1, r1);
178
+ const { Fp } = this;
179
+ return Fp.eql(c0, r0) && Fp.eql(c1, r1);
122
180
  }
123
181
  neg({ c0, c1 }) {
124
- return { c0: this.Fp.neg(c0), c1: this.Fp.neg(c1) };
182
+ const { Fp } = this;
183
+ return Object.freeze({ c0: Fp.neg(c0), c1: Fp.neg(c1) });
125
184
  }
126
185
  pow(num, power) {
127
186
  return mod.FpPow(this, num, power);
@@ -131,23 +190,25 @@ class _Field2 {
131
190
  }
132
191
  // Normalized
133
192
  add(f1, f2) {
193
+ const { Fp } = this;
134
194
  const { c0, c1 } = f1;
135
195
  const { c0: r0, c1: r1 } = f2;
136
- return {
137
- c0: this.Fp.add(c0, r0),
138
- c1: this.Fp.add(c1, r1),
139
- };
196
+ return Object.freeze({
197
+ c0: Fp.add(c0, r0),
198
+ c1: Fp.add(c1, r1),
199
+ });
140
200
  }
141
201
  sub({ c0, c1 }, { c0: r0, c1: r1 }) {
142
- return {
143
- c0: this.Fp.sub(c0, r0),
144
- c1: this.Fp.sub(c1, r1),
145
- };
202
+ const { Fp } = this;
203
+ return Object.freeze({
204
+ c0: Fp.sub(c0, r0),
205
+ c1: Fp.sub(c1, r1),
206
+ });
146
207
  }
147
208
  mul({ c0, c1 }, rhs) {
148
209
  const { Fp } = this;
149
210
  if (typeof rhs === 'bigint')
150
- return { c0: Fp.mul(c0, rhs), c1: Fp.mul(c1, rhs) };
211
+ return Object.freeze({ c0: Fp.mul(c0, rhs), c1: Fp.mul(c1, rhs) });
151
212
  // (a+bi)(c+di) = (ac−bd) + (ad+bc)i
152
213
  const { c0: r0, c1: r1 } = rhs;
153
214
  let t1 = Fp.mul(c0, r0); // c0 * o0
@@ -155,14 +216,14 @@ class _Field2 {
155
216
  // (T1 - T2) + ((c0 + c1) * (r0 + r1) - (T1 + T2))*i
156
217
  const o0 = Fp.sub(t1, t2);
157
218
  const o1 = Fp.sub(Fp.mul(Fp.add(c0, c1), Fp.add(r0, r1)), Fp.add(t1, t2));
158
- return { c0: o0, c1: o1 };
219
+ return Object.freeze({ c0: o0, c1: o1 });
159
220
  }
160
221
  sqr({ c0, c1 }) {
161
222
  const { Fp } = this;
162
223
  const a = Fp.add(c0, c1);
163
224
  const b = Fp.sub(c0, c1);
164
225
  const c = Fp.add(c0, c0);
165
- return { c0: Fp.mul(a, b), c1: Fp.mul(c, c1) };
226
+ return Object.freeze({ c0: Fp.mul(a, b), c1: Fp.mul(c, c1) });
166
227
  }
167
228
  // NonNormalized stuff
168
229
  addN(a, b) {
@@ -199,7 +260,7 @@ class _Field2 {
199
260
  // only a single inversion in Fp.
200
261
  const { Fp } = this;
201
262
  const factor = Fp.inv(Fp.create(a * a + b * b));
202
- return { c0: Fp.mul(factor, Fp.create(a)), c1: Fp.mul(factor, Fp.create(-b)) };
263
+ return Object.freeze({ c0: Fp.mul(factor, Fp.create(a)), c1: Fp.mul(factor, Fp.create(-b)) });
203
264
  }
204
265
  sqrt(num) {
205
266
  // This is generic for all quadratic extensions (Fp2)
@@ -243,18 +304,23 @@ class _Field2 {
243
304
  // Bytes util
244
305
  fromBytes(b) {
245
306
  const { Fp } = this;
307
+ abytes(b);
246
308
  if (b.length !== this.BYTES)
247
309
  throw new Error('fromBytes invalid length=' + b.length);
248
- return { c0: Fp.fromBytes(b.subarray(0, Fp.BYTES)), c1: Fp.fromBytes(b.subarray(Fp.BYTES)) };
310
+ return this.create({
311
+ c0: Fp.fromBytes(b.subarray(0, Fp.BYTES)),
312
+ c1: Fp.fromBytes(b.subarray(Fp.BYTES)),
313
+ });
249
314
  }
250
315
  toBytes({ c0, c1 }) {
251
316
  return concatBytes(this.Fp.toBytes(c0), this.Fp.toBytes(c1));
252
317
  }
253
318
  cmov({ c0, c1 }, { c0: r0, c1: r1 }, c) {
254
- return {
255
- c0: this.Fp.cmov(c0, r0, c),
256
- c1: this.Fp.cmov(c1, r1, c),
257
- };
319
+ const { Fp } = this;
320
+ return this.create({
321
+ c0: Fp.cmov(c0, r0, c),
322
+ c1: Fp.cmov(c1, r1, c),
323
+ });
258
324
  }
259
325
  reim({ c0, c1 }) {
260
326
  return { re: c0, im: c1 };
@@ -273,10 +339,10 @@ class _Field2 {
273
339
  return this.mul({ c0, c1 }, this.NONRESIDUE);
274
340
  }
275
341
  frobeniusMap({ c0, c1 }, power) {
276
- return {
342
+ return Object.freeze({
277
343
  c0,
278
344
  c1: this.Fp.mul(c1, this.FROBENIUS_COEFFICIENTS[power % 2]),
279
- };
345
+ });
280
346
  }
281
347
  }
282
348
  class _Field6 {
@@ -287,59 +353,75 @@ class _Field6 {
287
353
  ZERO;
288
354
  ONE;
289
355
  Fp2;
290
- FROBENIUS_COEFFICIENTS_1;
291
- FROBENIUS_COEFFICIENTS_2;
292
356
  constructor(Fp2) {
293
357
  this.Fp2 = Fp2;
294
- this.ORDER = Fp2.ORDER; // TODO: unused, but need to verify
358
+ // `IField.ORDER` is the field cardinality `q`; for sextic extensions that is `p^6`.
359
+ // Generic helpers like Frobenius-style `x^q = x` checks rely on the literal field size here.
360
+ this.ORDER = Fp2.Fp.ORDER ** _6n;
295
361
  this.BITS = 3 * Fp2.BITS;
296
362
  this.BYTES = 3 * Fp2.BYTES;
297
363
  this.isLE = Fp2.isLE;
298
- this.ZERO = { c0: Fp2.ZERO, c1: Fp2.ZERO, c2: Fp2.ZERO };
299
- this.ONE = { c0: Fp2.ONE, c1: Fp2.ZERO, c2: Fp2.ZERO };
364
+ this.ZERO = this.create({ c0: Fp2.ZERO, c1: Fp2.ZERO, c2: Fp2.ZERO });
365
+ this.ONE = this.create({ c0: Fp2.ONE, c1: Fp2.ZERO, c2: Fp2.ZERO });
366
+ Object.freeze(this);
367
+ }
368
+ // Most callers never touch Frobenius maps, so keep the sextic tables lazy:
369
+ // eagerly deriving them dominates `bls12-381.js` / `bn254.js` import time.
370
+ get FROBENIUS_COEFFICIENTS_1() {
371
+ const frob = _FROBENIUS_COEFFICIENTS_6.get(this);
372
+ if (frob)
373
+ return frob[0];
374
+ const { Fp2 } = this;
300
375
  const { Fp } = Fp2;
301
- const frob = calcFrobeniusCoefficients(Fp2, Fp2.NONRESIDUE, Fp.ORDER, 6, 2, 3);
302
- this.FROBENIUS_COEFFICIENTS_1 = frob[0];
303
- this.FROBENIUS_COEFFICIENTS_2 = frob[1];
304
- Object.seal(this);
376
+ const rows = calcFrobeniusCoefficients(Fp2, Fp2.NONRESIDUE, Fp.ORDER, 6, 2, 3);
377
+ const cache = [Object.freeze(rows[0]), Object.freeze(rows[1])];
378
+ _FROBENIUS_COEFFICIENTS_6.set(this, cache);
379
+ return cache[0];
380
+ }
381
+ get FROBENIUS_COEFFICIENTS_2() {
382
+ const frob = _FROBENIUS_COEFFICIENTS_6.get(this);
383
+ if (frob)
384
+ return frob[1];
385
+ void this.FROBENIUS_COEFFICIENTS_1;
386
+ return _FROBENIUS_COEFFICIENTS_6.get(this)[1];
305
387
  }
306
388
  add({ c0, c1, c2 }, { c0: r0, c1: r1, c2: r2 }) {
307
389
  const { Fp2 } = this;
308
- return {
390
+ return Object.freeze({
309
391
  c0: Fp2.add(c0, r0),
310
392
  c1: Fp2.add(c1, r1),
311
393
  c2: Fp2.add(c2, r2),
312
- };
394
+ });
313
395
  }
314
396
  sub({ c0, c1, c2 }, { c0: r0, c1: r1, c2: r2 }) {
315
397
  const { Fp2 } = this;
316
- return {
398
+ return Object.freeze({
317
399
  c0: Fp2.sub(c0, r0),
318
400
  c1: Fp2.sub(c1, r1),
319
401
  c2: Fp2.sub(c2, r2),
320
- };
402
+ });
321
403
  }
322
404
  mul({ c0, c1, c2 }, rhs) {
323
405
  const { Fp2 } = this;
324
406
  if (typeof rhs === 'bigint') {
325
- return {
407
+ return Object.freeze({
326
408
  c0: Fp2.mul(c0, rhs),
327
409
  c1: Fp2.mul(c1, rhs),
328
410
  c2: Fp2.mul(c2, rhs),
329
- };
411
+ });
330
412
  }
331
413
  const { c0: r0, c1: r1, c2: r2 } = rhs;
332
414
  const t0 = Fp2.mul(c0, r0); // c0 * o0
333
415
  const t1 = Fp2.mul(c1, r1); // c1 * o1
334
416
  const t2 = Fp2.mul(c2, r2); // c2 * o2
335
- return {
417
+ return Object.freeze({
336
418
  // t0 + (c1 + c2) * (r1 * r2) - (T1 + T2) * (u + 1)
337
419
  c0: Fp2.add(t0, Fp2.mulByNonresidue(Fp2.sub(Fp2.mul(Fp2.add(c1, c2), Fp2.add(r1, r2)), Fp2.add(t1, t2)))),
338
420
  // (c0 + c1) * (r0 + r1) - (T0 + T1) + T2 * (u + 1)
339
421
  c1: Fp2.add(Fp2.sub(Fp2.mul(Fp2.add(c0, c1), Fp2.add(r0, r1)), Fp2.add(t0, t1)), Fp2.mulByNonresidue(t2)),
340
422
  // T1 + (c0 + c2) * (r0 + r2) - T0 + T2
341
423
  c2: Fp2.sub(Fp2.add(t1, Fp2.mul(Fp2.add(c0, c2), Fp2.add(r0, r2))), Fp2.add(t0, t2)),
342
- };
424
+ });
343
425
  }
344
426
  sqr({ c0, c1, c2 }) {
345
427
  const { Fp2 } = this;
@@ -347,12 +429,12 @@ class _Field6 {
347
429
  let t1 = Fp2.mul(Fp2.mul(c0, c1), _2n); // 2 * c0 * c1
348
430
  let t3 = Fp2.mul(Fp2.mul(c1, c2), _2n); // 2 * c1 * c2
349
431
  let t4 = Fp2.sqr(c2); // c2²
350
- return {
432
+ return Object.freeze({
351
433
  c0: Fp2.add(Fp2.mulByNonresidue(t3), t0), // T3 * (u + 1) + T0
352
434
  c1: Fp2.add(Fp2.mulByNonresidue(t4), t1), // T4 * (u + 1) + T1
353
435
  // T1 + (c0 - c1 + c2)² + T3 - T0 - T4
354
436
  c2: Fp2.sub(Fp2.sub(Fp2.add(Fp2.add(t1, Fp2.sqr(Fp2.add(Fp2.sub(c0, c1), c2))), t3), t0), t4),
355
- };
437
+ });
356
438
  }
357
439
  addN(a, b) {
358
440
  return this.add(a, b);
@@ -367,13 +449,23 @@ class _Field6 {
367
449
  return this.sqr(a);
368
450
  }
369
451
  create(num) {
370
- return num;
371
- }
372
- isValid({ c0, c1, c2 }) {
452
+ const { Fp2 } = this;
453
+ const c0 = Fp2.create(num.c0);
454
+ const c1 = Fp2.create(num.c1);
455
+ const c2 = Fp2.create(num.c2);
456
+ return Object.freeze({ c0, c1, c2 });
457
+ }
458
+ isValid(num) {
459
+ if (!isObj(num))
460
+ throw new TypeError('invalid field element: expected object, got ' + typeof num);
461
+ const { c0, c1, c2 } = num;
373
462
  const { Fp2 } = this;
374
463
  return Fp2.isValid(c0) && Fp2.isValid(c1) && Fp2.isValid(c2);
375
464
  }
376
- is0({ c0, c1, c2 }) {
465
+ is0(num) {
466
+ if (!isObj(num))
467
+ return false;
468
+ const { c0, c1, c2 } = num;
377
469
  const { Fp2 } = this;
378
470
  return Fp2.is0(c0) && Fp2.is0(c1) && Fp2.is0(c2);
379
471
  }
@@ -382,13 +474,16 @@ class _Field6 {
382
474
  }
383
475
  neg({ c0, c1, c2 }) {
384
476
  const { Fp2 } = this;
385
- return { c0: Fp2.neg(c0), c1: Fp2.neg(c1), c2: Fp2.neg(c2) };
477
+ return Object.freeze({ c0: Fp2.neg(c0), c1: Fp2.neg(c1), c2: Fp2.neg(c2) });
386
478
  }
387
479
  eql({ c0, c1, c2 }, { c0: r0, c1: r1, c2: r2 }) {
388
480
  const { Fp2 } = this;
389
481
  return Fp2.eql(c0, r0) && Fp2.eql(c1, r1) && Fp2.eql(c2, r2);
390
482
  }
391
483
  sqrt(_) {
484
+ // Sextic extensions can use generic odd-field Tonelli-Shanks, but the helper must work
485
+ // over `IField<T>` with a quadratic non-residue from Fp6 itself. The current
486
+ // `mod.tonelliShanks(P)` precomputation only searches integer residues in the base field.
392
487
  return notImplemented();
393
488
  }
394
489
  // Do we need division by bigint at all? Should be done via order:
@@ -410,19 +505,20 @@ class _Field6 {
410
505
  let t2 = Fp2.sub(Fp2.sqr(c1), Fp2.mul(c0, c2)); // c1² - c0 * c2
411
506
  // 1/(((c2 * T1 + c1 * T2) * v) + c0 * T0)
412
507
  let t4 = Fp2.inv(Fp2.add(Fp2.mulByNonresidue(Fp2.add(Fp2.mul(c2, t1), Fp2.mul(c1, t2))), Fp2.mul(c0, t0)));
413
- return { c0: Fp2.mul(t4, t0), c1: Fp2.mul(t4, t1), c2: Fp2.mul(t4, t2) };
508
+ return Object.freeze({ c0: Fp2.mul(t4, t0), c1: Fp2.mul(t4, t1), c2: Fp2.mul(t4, t2) });
414
509
  }
415
510
  // Bytes utils
416
511
  fromBytes(b) {
417
512
  const { Fp2 } = this;
513
+ abytes(b);
418
514
  if (b.length !== this.BYTES)
419
515
  throw new Error('fromBytes invalid length=' + b.length);
420
516
  const B2 = Fp2.BYTES;
421
- return {
517
+ return this.create({
422
518
  c0: Fp2.fromBytes(b.subarray(0, B2)),
423
519
  c1: Fp2.fromBytes(b.subarray(B2, B2 * 2)),
424
520
  c2: Fp2.fromBytes(b.subarray(2 * B2)),
425
- };
521
+ });
426
522
  }
427
523
  toBytes({ c0, c1, c2 }) {
428
524
  const { Fp2 } = this;
@@ -430,66 +526,73 @@ class _Field6 {
430
526
  }
431
527
  cmov({ c0, c1, c2 }, { c0: r0, c1: r1, c2: r2 }, c) {
432
528
  const { Fp2 } = this;
433
- return {
529
+ return this.create({
434
530
  c0: Fp2.cmov(c0, r0, c),
435
531
  c1: Fp2.cmov(c1, r1, c),
436
532
  c2: Fp2.cmov(c2, r2, c),
437
- };
533
+ });
438
534
  }
439
- fromBigSix(t) {
535
+ fromBigSix(tuple) {
440
536
  const { Fp2 } = this;
441
- if (!Array.isArray(t) || t.length !== 6)
442
- throw new Error('invalid Fp6 usage');
443
- return {
537
+ if (!Array.isArray(tuple) || tuple.length !== 6)
538
+ throw new Error('invalid Fp6.fromBigSix');
539
+ for (let i = 0; i < 6; i++)
540
+ if (typeof tuple[i] !== 'bigint')
541
+ throw new Error('invalid Fp6.fromBigSix');
542
+ const t = tuple;
543
+ return this.create({
444
544
  c0: Fp2.fromBigTuple(t.slice(0, 2)),
445
545
  c1: Fp2.fromBigTuple(t.slice(2, 4)),
446
546
  c2: Fp2.fromBigTuple(t.slice(4, 6)),
447
- };
547
+ });
448
548
  }
449
549
  frobeniusMap({ c0, c1, c2 }, power) {
450
550
  const { Fp2 } = this;
451
- return {
551
+ return Object.freeze({
452
552
  c0: Fp2.frobeniusMap(c0, power),
453
553
  c1: Fp2.mul(Fp2.frobeniusMap(c1, power), this.FROBENIUS_COEFFICIENTS_1[power % 6]),
454
554
  c2: Fp2.mul(Fp2.frobeniusMap(c2, power), this.FROBENIUS_COEFFICIENTS_2[power % 6]),
455
- };
555
+ });
456
556
  }
457
557
  mulByFp2({ c0, c1, c2 }, rhs) {
458
558
  const { Fp2 } = this;
459
- return {
559
+ return Object.freeze({
460
560
  c0: Fp2.mul(c0, rhs),
461
561
  c1: Fp2.mul(c1, rhs),
462
562
  c2: Fp2.mul(c2, rhs),
463
- };
563
+ });
464
564
  }
465
565
  mulByNonresidue({ c0, c1, c2 }) {
466
566
  const { Fp2 } = this;
467
- return { c0: Fp2.mulByNonresidue(c2), c1: c0, c2: c1 };
567
+ return Object.freeze({ c0: Fp2.mulByNonresidue(c2), c1: c0, c2: c1 });
468
568
  }
469
569
  // Sparse multiplication
470
570
  mul1({ c0, c1, c2 }, b1) {
471
571
  const { Fp2 } = this;
472
- return {
572
+ return Object.freeze({
473
573
  c0: Fp2.mulByNonresidue(Fp2.mul(c2, b1)),
474
574
  c1: Fp2.mul(c0, b1),
475
575
  c2: Fp2.mul(c1, b1),
476
- };
576
+ });
477
577
  }
478
578
  // Sparse multiplication
479
579
  mul01({ c0, c1, c2 }, b0, b1) {
480
580
  const { Fp2 } = this;
481
581
  let t0 = Fp2.mul(c0, b0); // c0 * b0
482
582
  let t1 = Fp2.mul(c1, b1); // c1 * b1
483
- return {
583
+ return Object.freeze({
484
584
  // ((c1 + c2) * b1 - T1) * (u + 1) + T0
485
585
  c0: Fp2.add(Fp2.mulByNonresidue(Fp2.sub(Fp2.mul(Fp2.add(c1, c2), b1), t1)), t0),
486
586
  // (b0 + b1) * (c0 + c1) - T0 - T1
487
587
  c1: Fp2.sub(Fp2.sub(Fp2.mul(Fp2.add(b0, b1), Fp2.add(c0, c1)), t0), t1),
488
588
  // (c0 + c2) * b0 - T0 + T1
489
589
  c2: Fp2.add(Fp2.sub(Fp2.mul(Fp2.add(c0, c2), b0), t0), t1),
490
- };
590
+ });
491
591
  }
492
592
  }
593
+ // Keep lazy tower caches off-object: field instances stay frozen, and debugger output
594
+ // stays readable without JS private slots while second/subsequent lookups still hit cache.
595
+ const _FROBENIUS_COEFFICIENTS_6 = new WeakMap();
493
596
  class _Field12 {
494
597
  ORDER;
495
598
  BITS;
@@ -498,31 +601,63 @@ class _Field12 {
498
601
  ZERO;
499
602
  ONE;
500
603
  Fp6;
501
- FROBENIUS_COEFFICIENTS;
502
604
  X_LEN;
503
605
  finalExponentiate;
504
606
  constructor(Fp6, opts) {
607
+ const { X_LEN, Fp12finalExponentiate } = opts;
505
608
  const { Fp2 } = Fp6;
506
609
  const { Fp } = Fp2;
507
610
  this.Fp6 = Fp6;
508
- this.ORDER = Fp2.ORDER; // TODO: verify if it's unuesd
611
+ // `IField.ORDER` is the field cardinality `q`; for degree-12 extensions that is `p^12`.
612
+ // Keeping `p^2` here breaks generic field identities like `x^q = x` on Fp12.
613
+ this.ORDER = Fp.ORDER ** _12n;
509
614
  this.BITS = 2 * Fp6.BITS;
510
615
  this.BYTES = 2 * Fp6.BYTES;
511
616
  this.isLE = Fp6.isLE;
512
- this.ZERO = { c0: Fp6.ZERO, c1: Fp6.ZERO };
513
- this.ONE = { c0: Fp6.ONE, c1: Fp6.ZERO };
514
- this.FROBENIUS_COEFFICIENTS = calcFrobeniusCoefficients(Fp2, Fp2.NONRESIDUE, Fp.ORDER, 12, 1, 6)[0];
515
- this.X_LEN = opts.X_LEN;
516
- this.finalExponentiate = opts.Fp12finalExponentiate;
617
+ // Returned tower values are frozen, so larger constants can safely reuse
618
+ // already-frozen child coefficients instead of cloning them.
619
+ this.ZERO = this.create({ c0: Fp6.ZERO, c1: Fp6.ZERO });
620
+ this.ONE = this.create({ c0: Fp6.ONE, c1: Fp6.ZERO });
621
+ this.X_LEN = X_LEN;
622
+ this.finalExponentiate = (num) => {
623
+ const copy2 = ({ c0, c1 }) => Object.freeze({ c0, c1 });
624
+ const copy6 = ({ c0, c1, c2 }) => Object.freeze({ c0: copy2(c0), c1: copy2(c1), c2: copy2(c2) });
625
+ // This config hook is trusted to return a canonical Fp12 value already.
626
+ // Copy+freeze it to keep the tower immutability invariant without mutating caller objects.
627
+ const res = Fp12finalExponentiate(num);
628
+ return Object.freeze({ c0: copy6(res.c0), c1: copy6(res.c1) });
629
+ };
630
+ Object.freeze(this);
631
+ }
632
+ // Keep the degree-12 Frobenius row lazy too; after the first lookup the cached
633
+ // array is reused exactly like the old eager table.
634
+ get FROBENIUS_COEFFICIENTS() {
635
+ const frob = _FROBENIUS_COEFFICIENTS_12.get(this);
636
+ if (frob)
637
+ return frob;
638
+ const { Fp2 } = this.Fp6;
639
+ const { Fp } = Fp2;
640
+ const cache = Object.freeze(calcFrobeniusCoefficients(Fp2, Fp2.NONRESIDUE, Fp.ORDER, 12, 1, 6)[0]);
641
+ _FROBENIUS_COEFFICIENTS_12.set(this, cache);
642
+ return cache;
517
643
  }
518
644
  create(num) {
519
- return num;
645
+ const { Fp6 } = this;
646
+ const c0 = Fp6.create(num.c0);
647
+ const c1 = Fp6.create(num.c1);
648
+ return Object.freeze({ c0, c1 });
520
649
  }
521
- isValid({ c0, c1 }) {
650
+ isValid(num) {
651
+ if (!isObj(num))
652
+ throw new TypeError('invalid field element: expected object, got ' + typeof num);
653
+ const { c0, c1 } = num;
522
654
  const { Fp6 } = this;
523
655
  return Fp6.isValid(c0) && Fp6.isValid(c1);
524
656
  }
525
- is0({ c0, c1 }) {
657
+ is0(num) {
658
+ if (!isObj(num))
659
+ return false;
660
+ const { c0, c1 } = num;
526
661
  const { Fp6 } = this;
527
662
  return Fp6.is0(c0) && Fp6.is0(c1);
528
663
  }
@@ -531,19 +666,23 @@ class _Field12 {
531
666
  }
532
667
  neg({ c0, c1 }) {
533
668
  const { Fp6 } = this;
534
- return { c0: Fp6.neg(c0), c1: Fp6.neg(c1) };
669
+ return Object.freeze({ c0: Fp6.neg(c0), c1: Fp6.neg(c1) });
535
670
  }
536
671
  eql({ c0, c1 }, { c0: r0, c1: r1 }) {
537
672
  const { Fp6 } = this;
538
673
  return Fp6.eql(c0, r0) && Fp6.eql(c1, r1);
539
674
  }
540
675
  sqrt(_) {
541
- notImplemented();
676
+ // Fp12 is quadratic over Fp6, so a dedicated quadratic-extension sqrt is possible here
677
+ // once Fp6.sqrt() exists. Without that lower-level sqrt, only a field-generic
678
+ // Tonelli-Shanks path over Fp12 itself would work.
679
+ return notImplemented();
542
680
  }
543
681
  inv({ c0, c1 }) {
544
682
  const { Fp6 } = this;
545
683
  let t = Fp6.inv(Fp6.sub(Fp6.sqr(c0), Fp6.mulByNonresidue(Fp6.sqr(c1)))); // 1 / (c0² - c1² * v)
546
- return { c0: Fp6.mul(c0, t), c1: Fp6.neg(Fp6.mul(c1, t)) }; // ((C0 * T) * T) + (-C1 * T) * w
684
+ // ((C0 * T) * T) + (-C1 * T) * w
685
+ return Object.freeze({ c0: Fp6.mul(c0, t), c1: Fp6.neg(Fp6.mul(c1, t)) });
547
686
  }
548
687
  div(lhs, rhs) {
549
688
  const { Fp6 } = this;
@@ -560,39 +699,39 @@ class _Field12 {
560
699
  // Normalized
561
700
  add({ c0, c1 }, { c0: r0, c1: r1 }) {
562
701
  const { Fp6 } = this;
563
- return {
702
+ return Object.freeze({
564
703
  c0: Fp6.add(c0, r0),
565
704
  c1: Fp6.add(c1, r1),
566
- };
705
+ });
567
706
  }
568
707
  sub({ c0, c1 }, { c0: r0, c1: r1 }) {
569
708
  const { Fp6 } = this;
570
- return {
709
+ return Object.freeze({
571
710
  c0: Fp6.sub(c0, r0),
572
711
  c1: Fp6.sub(c1, r1),
573
- };
712
+ });
574
713
  }
575
714
  mul({ c0, c1 }, rhs) {
576
715
  const { Fp6 } = this;
577
716
  if (typeof rhs === 'bigint')
578
- return { c0: Fp6.mul(c0, rhs), c1: Fp6.mul(c1, rhs) };
717
+ return Object.freeze({ c0: Fp6.mul(c0, rhs), c1: Fp6.mul(c1, rhs) });
579
718
  let { c0: r0, c1: r1 } = rhs;
580
719
  let t1 = Fp6.mul(c0, r0); // c0 * r0
581
720
  let t2 = Fp6.mul(c1, r1); // c1 * r1
582
- return {
721
+ return Object.freeze({
583
722
  c0: Fp6.add(t1, Fp6.mulByNonresidue(t2)), // T1 + T2 * v
584
723
  // (c0 + c1) * (r0 + r1) - (T1 + T2)
585
724
  c1: Fp6.sub(Fp6.mul(Fp6.add(c0, c1), Fp6.add(r0, r1)), Fp6.add(t1, t2)),
586
- };
725
+ });
587
726
  }
588
727
  sqr({ c0, c1 }) {
589
728
  const { Fp6 } = this;
590
729
  let ab = Fp6.mul(c0, c1); // c0 * c1
591
- return {
730
+ return Object.freeze({
592
731
  // (c1 * v + c0) * (c0 + c1) - AB - AB * v
593
732
  c0: Fp6.sub(Fp6.sub(Fp6.mul(Fp6.add(Fp6.mulByNonresidue(c1), c0), Fp6.add(c0, c1)), ab), Fp6.mulByNonresidue(ab)),
594
733
  c1: Fp6.add(ab, ab),
595
- }; // AB + AB
734
+ }); // AB + AB
596
735
  }
597
736
  // NonNormalized stuff
598
737
  addN(a, b) {
@@ -610,12 +749,13 @@ class _Field12 {
610
749
  // Bytes utils
611
750
  fromBytes(b) {
612
751
  const { Fp6 } = this;
752
+ abytes(b);
613
753
  if (b.length !== this.BYTES)
614
754
  throw new Error('fromBytes invalid length=' + b.length);
615
- return {
755
+ return this.create({
616
756
  c0: Fp6.fromBytes(b.subarray(0, Fp6.BYTES)),
617
757
  c1: Fp6.fromBytes(b.subarray(Fp6.BYTES)),
618
- };
758
+ });
619
759
  }
620
760
  toBytes({ c0, c1 }) {
621
761
  const { Fp6 } = this;
@@ -623,10 +763,10 @@ class _Field12 {
623
763
  }
624
764
  cmov({ c0, c1 }, { c0: r0, c1: r1 }, c) {
625
765
  const { Fp6 } = this;
626
- return {
766
+ return this.create({
627
767
  c0: Fp6.cmov(c0, r0, c),
628
768
  c1: Fp6.cmov(c1, r1, c),
629
- };
769
+ });
630
770
  }
631
771
  // Utils
632
772
  // toString() {
@@ -635,12 +775,18 @@ class _Field12 {
635
775
  // fromTuple(c: [Fp6, Fp6]) {
636
776
  // return new Fp12(...c);
637
777
  // }
638
- fromBigTwelve(t) {
778
+ fromBigTwelve(tuple) {
639
779
  const { Fp6 } = this;
640
- return {
780
+ if (!Array.isArray(tuple) || tuple.length !== 12)
781
+ throw new Error('invalid Fp12.fromBigTwelve');
782
+ for (let i = 0; i < 12; i++)
783
+ if (typeof tuple[i] !== 'bigint')
784
+ throw new Error('invalid Fp12.fromBigTwelve');
785
+ const t = tuple;
786
+ return this.create({
641
787
  c0: Fp6.fromBigSix(t.slice(0, 6)),
642
788
  c1: Fp6.fromBigSix(t.slice(6, 12)),
643
- };
789
+ });
644
790
  }
645
791
  // Raises to q**i -th power
646
792
  frobeniusMap(lhs, power) {
@@ -648,24 +794,25 @@ class _Field12 {
648
794
  const { Fp2 } = Fp6;
649
795
  const { c0, c1, c2 } = Fp6.frobeniusMap(lhs.c1, power);
650
796
  const coeff = this.FROBENIUS_COEFFICIENTS[power % 12];
651
- return {
797
+ return Object.freeze({
652
798
  c0: Fp6.frobeniusMap(lhs.c0, power),
653
- c1: Fp6.create({
799
+ c1: Object.freeze({
654
800
  c0: Fp2.mul(c0, coeff),
655
801
  c1: Fp2.mul(c1, coeff),
656
802
  c2: Fp2.mul(c2, coeff),
657
803
  }),
658
- };
804
+ });
659
805
  }
660
806
  mulByFp2({ c0, c1 }, rhs) {
661
807
  const { Fp6 } = this;
662
- return {
808
+ return Object.freeze({
663
809
  c0: Fp6.mulByFp2(c0, rhs),
664
810
  c1: Fp6.mulByFp2(c1, rhs),
665
- };
811
+ });
666
812
  }
667
813
  conjugate({ c0, c1 }) {
668
- return { c0, c1: this.Fp6.neg(c1) };
814
+ // Reuse `c0` by reference and only negate the `w` coefficient.
815
+ return Object.freeze({ c0, c1: this.Fp6.neg(c1) });
669
816
  }
670
817
  // Sparse multiplication
671
818
  mul014({ c0, c1 }, o0, o1, o4) {
@@ -673,26 +820,26 @@ class _Field12 {
673
820
  const { Fp2 } = Fp6;
674
821
  let t0 = Fp6.mul01(c0, o0, o1);
675
822
  let t1 = Fp6.mul1(c1, o4);
676
- return {
823
+ return Object.freeze({
677
824
  c0: Fp6.add(Fp6.mulByNonresidue(t1), t0), // T1 * v + T0
678
825
  // (c1 + c0) * [o0, o1+o4] - T0 - T1
679
826
  c1: Fp6.sub(Fp6.sub(Fp6.mul01(Fp6.add(c1, c0), o0, Fp2.add(o1, o4)), t0), t1),
680
- };
827
+ });
681
828
  }
682
829
  mul034({ c0, c1 }, o0, o3, o4) {
683
830
  const { Fp6 } = this;
684
831
  const { Fp2 } = Fp6;
685
- const a = Fp6.create({
832
+ const a = Object.freeze({
686
833
  c0: Fp2.mul(c0.c0, o0),
687
834
  c1: Fp2.mul(c0.c1, o0),
688
835
  c2: Fp2.mul(c0.c2, o0),
689
836
  });
690
837
  const b = Fp6.mul01(c1, o3, o4);
691
838
  const e = Fp6.mul01(Fp6.add(c0, c1), Fp2.add(o0, o3), o4);
692
- return {
839
+ return Object.freeze({
693
840
  c0: Fp6.add(Fp6.mulByNonresidue(b), a),
694
841
  c1: Fp6.sub(e, Fp6.add(a, b)),
695
- };
842
+ });
696
843
  }
697
844
  // A cyclotomic group is a subgroup of Fp^n defined by
698
845
  // GΦₙ(p) = {α ∈ Fpⁿ : α^Φₙ(p) = 1}
@@ -708,21 +855,24 @@ class _Field12 {
708
855
  const { first: t5, second: t6 } = Fp2.Fp4Square(c1c0, c0c2);
709
856
  const { first: t7, second: t8 } = Fp2.Fp4Square(c0c1, c1c2);
710
857
  const t9 = Fp2.mulByNonresidue(t8); // T8 * (u + 1)
711
- return {
712
- c0: Fp6.create({
858
+ return Object.freeze({
859
+ c0: Object.freeze({
713
860
  c0: Fp2.add(Fp2.mul(Fp2.sub(t3, c0c0), _2n), t3), // 2 * (T3 - c0c0) + T3
714
861
  c1: Fp2.add(Fp2.mul(Fp2.sub(t5, c0c1), _2n), t5), // 2 * (T5 - c0c1) + T5
715
862
  c2: Fp2.add(Fp2.mul(Fp2.sub(t7, c0c2), _2n), t7),
716
863
  }), // 2 * (T7 - c0c2) + T7
717
- c1: Fp6.create({
864
+ c1: Object.freeze({
718
865
  c0: Fp2.add(Fp2.mul(Fp2.add(t9, c1c0), _2n), t9), // 2 * (T9 + c1c0) + T9
719
866
  c1: Fp2.add(Fp2.mul(Fp2.add(t4, c1c1), _2n), t4), // 2 * (T4 + c1c1) + T4
720
867
  c2: Fp2.add(Fp2.mul(Fp2.add(t6, c1c2), _2n), t6),
721
868
  }),
722
- }; // 2 * (T6 + c1c2) + T6
869
+ }); // 2 * (T6 + c1c2) + T6
723
870
  }
724
871
  // https://eprint.iacr.org/2009/565.pdf
725
872
  _cyclotomicExp(num, n) {
873
+ // The loop only consumes `X_LEN` bits, so out-of-range exponents would otherwise get silently
874
+ // truncated (or sign-extended for negatives) instead of matching the caller's requested power.
875
+ aInRange('cyclotomic exponent', n, _0n, _1n << BigInt(this.X_LEN));
726
876
  let z = this.ONE;
727
877
  for (let i = this.X_LEN - 1; i >= 0; i--) {
728
878
  z = this._cyclotomicSquare(z);
@@ -732,7 +882,41 @@ class _Field12 {
732
882
  return z;
733
883
  }
734
884
  }
885
+ const _FROBENIUS_COEFFICIENTS_12 = new WeakMap();
886
+ /**
887
+ * @param opts - Tower construction options. See {@link Tower12Opts}.
888
+ * @returns BLS tower fields.
889
+ * @throws If the tower options or derived Frobenius helpers are invalid. {@link Error}
890
+ * @example
891
+ * Construct the Fp2/Fp6/Fp12 tower used by a pairing-friendly curve.
892
+ *
893
+ * ```ts
894
+ * const fields = tower12({
895
+ * ORDER: 17n,
896
+ * X_LEN: 4,
897
+ * FP2_NONRESIDUE: [1n, 1n],
898
+ * Fp2mulByB: (num) => num,
899
+ * Fp12finalExponentiate: (num) => num,
900
+ * });
901
+ * const fp12 = fields.Fp12.ONE;
902
+ * ```
903
+ */
735
904
  export function tower12(opts) {
905
+ validateObject(opts, {
906
+ ORDER: 'bigint',
907
+ X_LEN: 'number',
908
+ FP2_NONRESIDUE: 'object',
909
+ Fp2mulByB: 'function',
910
+ Fp12finalExponentiate: 'function',
911
+ }, { NONRESIDUE: 'bigint' });
912
+ asafenumber(opts.X_LEN, 'X_LEN');
913
+ if (opts.X_LEN < 1)
914
+ throw new Error('invalid X_LEN');
915
+ const nonresidue = opts.FP2_NONRESIDUE;
916
+ if (!Array.isArray(nonresidue) || nonresidue.length !== 2)
917
+ throw new Error('invalid FP2_NONRESIDUE');
918
+ if (typeof nonresidue[0] !== 'bigint' || typeof nonresidue[1] !== 'bigint')
919
+ throw new Error('invalid FP2_NONRESIDUE');
736
920
  const Fp = mod.Field(opts.ORDER);
737
921
  const Fp2 = new _Field2(Fp, opts);
738
922
  const Fp6 = new _Field6(Fp2);