@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
@@ -10,80 +10,161 @@
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.ts';
13
+ import {
14
+ abytes,
15
+ aInRange,
16
+ asafenumber,
17
+ bitGet,
18
+ bitLen,
19
+ concatBytes,
20
+ notImplemented,
21
+ validateObject,
22
+ type TArg,
23
+ type TRet,
24
+ } from '../utils.ts';
14
25
  import * as mod from './modular.ts';
15
26
  import type { WeierstrassPoint, WeierstrassPointCons } from './weierstrass.ts';
16
27
 
17
28
  // Be friendly to bad ECMAScript parsers by not using bigint literals
18
29
  // prettier-ignore
19
- const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3);
30
+ 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);
20
31
 
21
32
  // Fp₂ over complex plane
33
+ /** Pair of bigints used for quadratic-extension tuples. */
22
34
  export type BigintTuple = [bigint, bigint];
35
+ /** Prime-field element. */
23
36
  export type Fp = bigint;
24
37
  // Finite extension field over irreducible polynominal.
25
38
  // Fp(u) / (u² - β) where β = -1
26
- export type Fp2 = { c0: bigint; c1: bigint };
39
+ /** Quadratic-extension field element `c0 + c1 * u`. */
40
+ export type Fp2 = {
41
+ /** Real component. */
42
+ c0: bigint;
43
+ /** Imaginary component. */
44
+ c1: bigint;
45
+ };
46
+ /** Six bigints used for sextic-extension tuples. */
27
47
  export type BigintSix = [bigint, bigint, bigint, bigint, bigint, bigint];
28
- export type Fp6 = { c0: Fp2; c1: Fp2; c2: Fp2 };
29
- export type Fp12 = { c0: Fp6; c1: Fp6 }; // Fp₁₂ = Fp₆² => Fp₂³, Fp₆(w) / (w² - γ) where γ = v
48
+ /** Sextic-extension field element `c0 + c1 * v + c2 * v^2`. */
49
+ export type Fp6 = {
50
+ /** Constant coefficient. */
51
+ c0: Fp2;
52
+ /** Linear coefficient. */
53
+ c1: Fp2;
54
+ /** Quadratic coefficient. */
55
+ c2: Fp2;
56
+ };
57
+ /**
58
+ * Degree-12 extension field element `c0 + c1 * w`.
59
+ * Fp₁₂ = Fp₆² over Fp₂³, with Fp₆(w) / (w² - γ) where γ = v.
60
+ */
61
+ export type Fp12 = {
62
+ /** Constant coefficient. */
63
+ c0: Fp6;
64
+ /** Linear coefficient. */
65
+ c1: Fp6;
66
+ };
30
67
  // prettier-ignore
68
+ /** Twelve bigints used for degree-12 extension tuples. */
31
69
  export type BigintTwelve = [
32
70
  bigint, bigint, bigint, bigint, bigint, bigint,
33
71
  bigint, bigint, bigint, bigint, bigint, bigint
34
72
  ];
35
73
 
74
+ const isObj = (value: unknown): value is Record<string, unknown> =>
75
+ !!value && typeof value === 'object';
76
+
77
+ /** BLS-friendly helpers on top of the quadratic extension field. */
36
78
  export type Fp2Bls = mod.IField<Fp2> & {
79
+ /** Underlying prime field. */
37
80
  Fp: mod.IField<Fp>;
81
+ /** Apply one Frobenius map. */
38
82
  frobeniusMap(num: Fp2, power: number): Fp2;
83
+ /** Build one field element from a raw bigint tuple. */
39
84
  fromBigTuple(num: BigintTuple): Fp2;
85
+ /** Multiply by the curve `b` constant. */
40
86
  mulByB: (num: Fp2) => Fp2;
87
+ /** Multiply by the quadratic non-residue. */
41
88
  mulByNonresidue: (num: Fp2) => Fp2;
89
+ /** Split one quadratic element into real and imaginary components. */
42
90
  reim: (num: Fp2) => { re: Fp; im: Fp };
91
+ /** Specialized helper used by sextic squaring formulas. */
43
92
  Fp4Square: (a: Fp2, b: Fp2) => { first: Fp2; second: Fp2 };
93
+ /** Quadratic non-residue used by the extension. */
44
94
  NONRESIDUE: Fp2;
45
95
  };
46
96
 
97
+ /** BLS-friendly helpers on top of the sextic extension field. */
47
98
  export type Fp6Bls = mod.IField<Fp6> & {
99
+ /** Underlying quadratic extension field. */
48
100
  Fp2: Fp2Bls;
101
+ /** Apply one Frobenius map. */
49
102
  frobeniusMap(num: Fp6, power: number): Fp6;
103
+ /** Build one field element from a raw six-bigint tuple. */
50
104
  fromBigSix: (tuple: BigintSix) => Fp6;
105
+ /** Multiply by a sparse `(0, b1, 0)` sextic element. */
51
106
  mul1(num: Fp6, b1: Fp2): Fp6;
107
+ /** Multiply by a sparse `(b0, b1, 0)` sextic element. */
52
108
  mul01(num: Fp6, b0: Fp2, b1: Fp2): Fp6;
109
+ /** Multiply by one quadratic-extension element. */
53
110
  mulByFp2(lhs: Fp6, rhs: Fp2): Fp6;
111
+ /** Multiply by the sextic non-residue. */
54
112
  mulByNonresidue: (num: Fp6) => Fp6;
55
113
  };
56
114
 
115
+ /** BLS-friendly helpers on top of the degree-12 extension field. */
57
116
  export type Fp12Bls = mod.IField<Fp12> & {
117
+ /** Underlying sextic extension field. */
58
118
  Fp6: Fp6Bls;
119
+ /** Apply one Frobenius map. */
59
120
  frobeniusMap(num: Fp12, power: number): Fp12;
121
+ /** Build one field element from a raw twelve-bigint tuple. */
60
122
  fromBigTwelve: (t: BigintTwelve) => Fp12;
123
+ /** Multiply by a sparse `(o0, o1, 0, 0, o4, 0)` element. */
61
124
  mul014(num: Fp12, o0: Fp2, o1: Fp2, o4: Fp2): Fp12;
125
+ /** Multiply by a sparse `(o0, 0, 0, o3, o4, 0)` element. */
62
126
  mul034(num: Fp12, o0: Fp2, o3: Fp2, o4: Fp2): Fp12;
127
+ /** Multiply by one quadratic-extension element. */
63
128
  mulByFp2(lhs: Fp12, rhs: Fp2): Fp12;
129
+ /** Conjugate one degree-12 element. */
64
130
  conjugate(num: Fp12): Fp12;
131
+ /** Apply the final exponentiation from pairing arithmetic. */
65
132
  finalExponentiate(num: Fp12): Fp12;
133
+ /** Apply one cyclotomic square. */
66
134
  _cyclotomicSquare(num: Fp12): Fp12;
135
+ /** Apply one cyclotomic exponentiation. */
67
136
  _cyclotomicExp(num: Fp12, n: bigint): Fp12;
68
137
  };
69
138
 
70
139
  function calcFrobeniusCoefficients<T>(
71
- Fp: mod.IField<T>,
140
+ Fp: TArg<mod.IField<T>>,
72
141
  nonResidue: T,
73
142
  modulus: bigint,
74
143
  degree: number,
75
144
  num: number = 1,
76
145
  divisor?: number
77
- ) {
146
+ ): T[][] {
147
+ asafenumber(num, 'num');
148
+ const F = Fp as mod.IField<T>;
149
+ // Generic callers can hit empty / fractional row counts through `__TEST`; fail closed instead of
150
+ // silently returning `[]` or deriving extra Frobenius rows from a truncated loop bound.
151
+ if (num <= 0)
152
+ throw new Error('calcFrobeniusCoefficients: expected positive row count, got ' + num);
78
153
  const _divisor = BigInt(divisor === undefined ? degree : divisor);
79
154
  const towerModulus: any = modulus ** BigInt(degree);
80
155
  const res: T[][] = [];
156
+ // Derive tower-basis multipliers for the `p^k` Frobenius action. The
157
+ // divisions below are expected to be exact for the chosen tower parameters.
81
158
  for (let i = 0; i < num; i++) {
82
159
  const a = BigInt(i + 1);
83
160
  const powers: T[] = [];
84
161
  for (let j = 0, qPower = _1n; j < degree; j++) {
85
- const power = ((a * qPower - a) / _divisor) % towerModulus;
86
- powers.push(Fp.pow(nonResidue, power));
162
+ const numer = a * qPower - a;
163
+ // Shipped towers divide cleanly here, but generic callers can pick bad
164
+ // params. Bigint division would floor and derive the wrong Frobenius table.
165
+ if (numer % _divisor) throw new Error('calcFrobeniusCoefficients: inexact tower exponent');
166
+ const power = (numer / _divisor) % towerModulus;
167
+ powers.push(F.pow(nonResidue, power));
87
168
  qPower *= modulus;
88
169
  }
89
170
  res.push(powers);
@@ -91,11 +172,35 @@ function calcFrobeniusCoefficients<T>(
91
172
  return res;
92
173
  }
93
174
 
175
+ export const __TEST: { calcFrobeniusCoefficients: typeof calcFrobeniusCoefficients } =
176
+ /* @__PURE__ */ Object.freeze({
177
+ calcFrobeniusCoefficients,
178
+ });
179
+
94
180
  // This works same at least for bls12-381, bn254 and bls12-377
181
+ /**
182
+ * @param Fp - Base field implementation.
183
+ * @param Fp2 - Quadratic extension field.
184
+ * @param base - Twist-specific Frobenius base whose powers yield the `c1` / `c2` constants.
185
+ * BLS12-381 uses `1 / NONRESIDUE`; BN254 uses `NONRESIDUE`.
186
+ * @returns Frobenius endomorphism helpers.
187
+ * @throws If the derived Frobenius constants are inconsistent for the tower. {@link Error}
188
+ * @example
189
+ * Build Frobenius endomorphism helpers for a BLS extension tower.
190
+ *
191
+ * ```ts
192
+ * import { psiFrobenius } from '@noble/curves/abstract/tower.js';
193
+ * import { bls12_381 } from '@noble/curves/bls12-381.js';
194
+ * const Fp = bls12_381.fields.Fp;
195
+ * const Fp2 = bls12_381.fields.Fp2;
196
+ * const frob = psiFrobenius(Fp, Fp2, Fp2.div(Fp2.ONE, Fp2.NONRESIDUE));
197
+ * const point = frob.G2psi(bls12_381.G2.Point, bls12_381.G2.Point.BASE);
198
+ * ```
199
+ */
95
200
  export function psiFrobenius(
96
- Fp: mod.IField<Fp>,
97
- Fp2: Fp2Bls,
98
- base: Fp2
201
+ Fp: TArg<mod.IField<Fp>>,
202
+ Fp2: TArg<Fp2Bls>,
203
+ base: TArg<Fp2>
99
204
  ): {
100
205
  psi: (x: Fp2, y: Fp2) => [Fp2, Fp2];
101
206
  psi2: (x: Fp2, y: Fp2) => [Fp2, Fp2];
@@ -117,9 +222,9 @@ export function psiFrobenius(
117
222
  }
118
223
  // Ψ²(P) endomorphism (psi2(x) = psi(psi(x)))
119
224
  const PSI2_X = Fp2.pow(base, (Fp.ORDER ** _2n - _1n) / _3n); // u^((p^2 - 1)/3)
120
- // This equals -1, which causes y to be Fp2.neg(y).
121
- // But not sure if there are case when this is not true?
122
- const PSI2_Y = Fp2.pow(base, (Fp.ORDER ** _2n - _1n) / _2n); // u^((p^2 - 1)/3)
225
+ // Current towers rely on this landing on `-1`, which lets psi2 map `y` with
226
+ // one negation instead of carrying a separate Frobenius multiplier.
227
+ const PSI2_Y = Fp2.pow(base, (Fp.ORDER ** _2n - _1n) / _2n); // u^((p^2 - 1)/2)
123
228
  if (!Fp2.eql(PSI2_Y, Fp2.neg(Fp2.ONE))) throw new Error('psiFrobenius: PSI2_Y!==-1');
124
229
  function psi2(x: Fp2, y: Fp2): [Fp2, Fp2] {
125
230
  return [Fp2.mul(x, PSI2_X), Fp2.neg(y)];
@@ -137,22 +242,35 @@ export function psiFrobenius(
137
242
  return { psi, psi2, G2psi, G2psi2, PSI_X, PSI_Y, PSI2_X, PSI2_Y };
138
243
  }
139
244
 
245
+ /** Construction options for the BLS-style degree-12 tower. */
140
246
  export type Tower12Opts = {
247
+ /** Prime-field order. */
141
248
  ORDER: bigint;
249
+ /** Bit length of the BLS parameter `x`. */
142
250
  X_LEN: number;
251
+ /** Prime-field non-residue used by the quadratic extension. */
143
252
  NONRESIDUE?: Fp;
253
+ /** Quadratic-extension non-residue used by the sextic tower. */
144
254
  FP2_NONRESIDUE: BigintTuple;
255
+ /**
256
+ * Optional custom quadratic square-root helper.
257
+ * Receives one quadratic-extension element and returns one square root.
258
+ */
145
259
  Fp2sqrt?: (num: Fp2) => Fp2;
260
+ /**
261
+ * Multiply one quadratic element by the curve `b` constant.
262
+ * @param num - Quadratic-extension element to scale.
263
+ * @returns Product by the curve `b` constant.
264
+ */
146
265
  Fp2mulByB: (num: Fp2) => Fp2;
266
+ /**
267
+ * Final exponentiation used by pairing arithmetic.
268
+ * @param num - Degree-12 field element to exponentiate.
269
+ * @returns Pairing result after final exponentiation.
270
+ */
147
271
  Fp12finalExponentiate: (num: Fp12) => Fp12;
148
272
  };
149
273
 
150
- const Fp2fromBigTuple = (Fp: mod.IField<bigint>, tuple: BigintTuple | bigint[]) => {
151
- if (tuple.length !== 2) throw new Error('invalid tuple');
152
- const fps = tuple.map((n) => Fp.create(n)) as BigintTuple;
153
- return { c0: fps[0], c1: fps[1] };
154
- };
155
-
156
274
  class _Field2 implements mod.IField<Fp2> {
157
275
  readonly ORDER: bigint;
158
276
  readonly BITS: number;
@@ -167,7 +285,7 @@ class _Field2 implements mod.IField<Fp2> {
167
285
  readonly mulByB: Tower12Opts['Fp2mulByB'];
168
286
  readonly Fp_NONRESIDUE: bigint;
169
287
  readonly Fp_div2: bigint;
170
- readonly FROBENIUS_COEFFICIENTS: Fp[];
288
+ readonly FROBENIUS_COEFFICIENTS: readonly Fp[];
171
289
 
172
290
  constructor(
173
291
  Fp: mod.IField<bigint>,
@@ -177,6 +295,7 @@ class _Field2 implements mod.IField<Fp2> {
177
295
  Fp2mulByB: Tower12Opts['Fp2mulByB'];
178
296
  }> = {}
179
297
  ) {
298
+ const { NONRESIDUE = BigInt(-1), FP2_NONRESIDUE, Fp2mulByB } = opts;
180
299
  const ORDER = Fp.ORDER;
181
300
  const FP2_ORDER = ORDER * ORDER;
182
301
  this.Fp = Fp;
@@ -184,40 +303,67 @@ class _Field2 implements mod.IField<Fp2> {
184
303
  this.BITS = bitLen(FP2_ORDER);
185
304
  this.BYTES = Math.ceil(bitLen(FP2_ORDER) / 8);
186
305
  this.isLE = Fp.isLE;
187
- this.ZERO = { c0: Fp.ZERO, c1: Fp.ZERO };
188
- this.ONE = { c0: Fp.ONE, c1: Fp.ZERO };
306
+ this.ZERO = this.create({ c0: Fp.ZERO, c1: Fp.ZERO });
307
+ this.ONE = this.create({ c0: Fp.ONE, c1: Fp.ZERO });
189
308
 
190
- this.Fp_NONRESIDUE = Fp.create(opts.NONRESIDUE || BigInt(-1));
309
+ // These knobs only swap constants for the shipped quadratic tower shape:
310
+ // arithmetic below assumes `u^2 = -1`, and bytes are handled as two adjacent
311
+ // `Fp` limbs (`fromBytes` / `toBytes` expect the shipped `2 * Fp.BYTES` layout).
312
+ this.Fp_NONRESIDUE = Fp.create(NONRESIDUE);
191
313
  this.Fp_div2 = Fp.div(Fp.ONE, _2n); // 1/2
192
- this.NONRESIDUE = Fp2fromBigTuple(Fp, opts.FP2_NONRESIDUE!);
193
- // const Fp2Nonresidue = Fp2fromBigTuple(opts.FP2_NONRESIDUE);
194
- this.FROBENIUS_COEFFICIENTS = calcFrobeniusCoefficients(Fp, this.Fp_NONRESIDUE, Fp.ORDER, 2)[0];
195
- this.mulByB = opts.Fp2mulByB!;
196
- Object.seal(this);
314
+ this.NONRESIDUE = this.create({ c0: FP2_NONRESIDUE![0], c1: FP2_NONRESIDUE![1] });
315
+ // const Fp2Nonresidue = this.create({ c0: FP2_NONRESIDUE![0], c1: FP2_NONRESIDUE![1] });
316
+ this.FROBENIUS_COEFFICIENTS = Object.freeze(
317
+ calcFrobeniusCoefficients(Fp, this.Fp_NONRESIDUE, Fp.ORDER, 2)[0]
318
+ );
319
+ this.mulByB = (num) => {
320
+ // This config hook is trusted to return a canonical Fp2 value already.
321
+ // Copy+freeze it to keep the tower immutability invariant without mutating caller objects.
322
+ const { c0, c1 } = Fp2mulByB!(num);
323
+ return Object.freeze({ c0, c1 });
324
+ };
325
+ Object.freeze(this);
197
326
  }
198
327
  fromBigTuple(tuple: BigintTuple) {
199
- return Fp2fromBigTuple(this.Fp, tuple);
328
+ if (!Array.isArray(tuple) || tuple.length !== 2) throw new Error('invalid Fp2.fromBigTuple');
329
+ const [c0, c1] = tuple;
330
+ if (typeof c0 !== 'bigint' || typeof c1 !== 'bigint')
331
+ throw new Error('invalid Fp2.fromBigTuple');
332
+ return this.create({ c0, c1 });
200
333
  }
201
334
  create(num: Fp2) {
202
- return num;
203
- }
204
- isValid({ c0, c1 }: Fp2) {
205
- function isValidC(num: bigint, ORDER: bigint) {
206
- return typeof num === 'bigint' && _0n <= num && num < ORDER;
207
- }
208
- return isValidC(c0, this.ORDER) && isValidC(c1, this.ORDER);
335
+ const { Fp } = this;
336
+ const c0 = Fp.create(num.c0);
337
+ const c1 = Fp.create(num.c1);
338
+ // Bigint field elements are immutable values, and higher-level code relies on
339
+ // that invariant. Copy+freeze tower values too without mutating caller-owned objects.
340
+ return Object.freeze({ c0, c1 });
341
+ }
342
+ isValid(num: Fp2) {
343
+ if (!isObj(num))
344
+ throw new TypeError('invalid field element: expected object, got ' + typeof num);
345
+ const { c0, c1 } = num;
346
+ const { Fp } = this;
347
+ // Match base-field `isValid(...)`: malformed coordinate types are errors, not a `false`
348
+ // predicate result.
349
+ return Fp.isValid(c0) && Fp.isValid(c1);
209
350
  }
210
- is0({ c0, c1 }: Fp2) {
211
- return this.Fp.is0(c0) && this.Fp.is0(c1);
351
+ is0(num: Fp2) {
352
+ if (!isObj(num)) return false;
353
+ const { c0, c1 } = num;
354
+ const { Fp } = this;
355
+ return Fp.is0(c0) && Fp.is0(c1);
212
356
  }
213
357
  isValidNot0(num: Fp2) {
214
358
  return !this.is0(num) && this.isValid(num);
215
359
  }
216
360
  eql({ c0, c1 }: Fp2, { c0: r0, c1: r1 }: Fp2) {
217
- return this.Fp.eql(c0, r0) && this.Fp.eql(c1, r1);
361
+ const { Fp } = this;
362
+ return Fp.eql(c0, r0) && Fp.eql(c1, r1);
218
363
  }
219
364
  neg({ c0, c1 }: Fp2) {
220
- return { c0: this.Fp.neg(c0), c1: this.Fp.neg(c1) };
365
+ const { Fp } = this;
366
+ return Object.freeze({ c0: Fp.neg(c0), c1: Fp.neg(c1) });
221
367
  }
222
368
  pow(num: Fp2, power: bigint): Fp2 {
223
369
  return mod.FpPow(this, num, power);
@@ -227,22 +373,24 @@ class _Field2 implements mod.IField<Fp2> {
227
373
  }
228
374
  // Normalized
229
375
  add(f1: Fp2, f2: Fp2): Fp2 {
376
+ const { Fp } = this;
230
377
  const { c0, c1 } = f1;
231
378
  const { c0: r0, c1: r1 } = f2;
232
- return {
233
- c0: this.Fp.add(c0, r0),
234
- c1: this.Fp.add(c1, r1),
235
- };
379
+ return Object.freeze({
380
+ c0: Fp.add(c0, r0),
381
+ c1: Fp.add(c1, r1),
382
+ });
236
383
  }
237
384
  sub({ c0, c1 }: Fp2, { c0: r0, c1: r1 }: Fp2) {
238
- return {
239
- c0: this.Fp.sub(c0, r0),
240
- c1: this.Fp.sub(c1, r1),
241
- };
385
+ const { Fp } = this;
386
+ return Object.freeze({
387
+ c0: Fp.sub(c0, r0),
388
+ c1: Fp.sub(c1, r1),
389
+ });
242
390
  }
243
391
  mul({ c0, c1 }: Fp2, rhs: Fp2) {
244
392
  const { Fp } = this;
245
- if (typeof rhs === 'bigint') return { c0: Fp.mul(c0, rhs), c1: Fp.mul(c1, rhs) };
393
+ if (typeof rhs === 'bigint') return Object.freeze({ c0: Fp.mul(c0, rhs), c1: Fp.mul(c1, rhs) });
246
394
  // (a+bi)(c+di) = (ac−bd) + (ad+bc)i
247
395
  const { c0: r0, c1: r1 } = rhs;
248
396
  let t1 = Fp.mul(c0, r0); // c0 * o0
@@ -250,14 +398,14 @@ class _Field2 implements mod.IField<Fp2> {
250
398
  // (T1 - T2) + ((c0 + c1) * (r0 + r1) - (T1 + T2))*i
251
399
  const o0 = Fp.sub(t1, t2);
252
400
  const o1 = Fp.sub(Fp.mul(Fp.add(c0, c1), Fp.add(r0, r1)), Fp.add(t1, t2));
253
- return { c0: o0, c1: o1 };
401
+ return Object.freeze({ c0: o0, c1: o1 });
254
402
  }
255
403
  sqr({ c0, c1 }: Fp2) {
256
404
  const { Fp } = this;
257
405
  const a = Fp.add(c0, c1);
258
406
  const b = Fp.sub(c0, c1);
259
407
  const c = Fp.add(c0, c0);
260
- return { c0: Fp.mul(a, b), c1: Fp.mul(c, c1) };
408
+ return Object.freeze({ c0: Fp.mul(a, b), c1: Fp.mul(c, c1) });
261
409
  }
262
410
  // NonNormalized stuff
263
411
  addN(a: Fp2, b: Fp2): Fp2 {
@@ -294,7 +442,7 @@ class _Field2 implements mod.IField<Fp2> {
294
442
  // only a single inversion in Fp.
295
443
  const { Fp } = this;
296
444
  const factor = Fp.inv(Fp.create(a * a + b * b));
297
- return { c0: Fp.mul(factor, Fp.create(a)), c1: Fp.mul(factor, Fp.create(-b)) };
445
+ return Object.freeze({ c0: Fp.mul(factor, Fp.create(a)), c1: Fp.mul(factor, Fp.create(-b)) });
298
446
  }
299
447
  sqrt(num: Fp2) {
300
448
  // This is generic for all quadratic extensions (Fp2)
@@ -333,17 +481,22 @@ class _Field2 implements mod.IField<Fp2> {
333
481
  // Bytes util
334
482
  fromBytes(b: Uint8Array): Fp2 {
335
483
  const { Fp } = this;
484
+ abytes(b);
336
485
  if (b.length !== this.BYTES) throw new Error('fromBytes invalid length=' + b.length);
337
- return { c0: Fp.fromBytes(b.subarray(0, Fp.BYTES)), c1: Fp.fromBytes(b.subarray(Fp.BYTES)) };
486
+ return this.create({
487
+ c0: Fp.fromBytes(b.subarray(0, Fp.BYTES)),
488
+ c1: Fp.fromBytes(b.subarray(Fp.BYTES)),
489
+ });
338
490
  }
339
- toBytes({ c0, c1 }: Fp2) {
491
+ toBytes({ c0, c1 }: Fp2): Uint8Array {
340
492
  return concatBytes(this.Fp.toBytes(c0), this.Fp.toBytes(c1));
341
493
  }
342
494
  cmov({ c0, c1 }: Fp2, { c0: r0, c1: r1 }: Fp2, c: boolean) {
343
- return {
344
- c0: this.Fp.cmov(c0, r0, c),
345
- c1: this.Fp.cmov(c1, r1, c),
346
- };
495
+ const { Fp } = this;
496
+ return this.create({
497
+ c0: Fp.cmov(c0, r0, c),
498
+ c1: Fp.cmov(c1, r1, c),
499
+ });
347
500
  }
348
501
  reim({ c0, c1 }: Fp2) {
349
502
  return { re: c0, im: c1 };
@@ -362,10 +515,10 @@ class _Field2 implements mod.IField<Fp2> {
362
515
  return this.mul({ c0, c1 }, this.NONRESIDUE);
363
516
  }
364
517
  frobeniusMap({ c0, c1 }: Fp2, power: number): Fp2 {
365
- return {
518
+ return Object.freeze({
366
519
  c0,
367
520
  c1: this.Fp.mul(c1, this.FROBENIUS_COEFFICIENTS[power % 2]),
368
- };
521
+ });
369
522
  }
370
523
  }
371
524
 
@@ -378,53 +531,67 @@ class _Field6 implements Fp6Bls {
378
531
  readonly ZERO: Fp6;
379
532
  readonly ONE: Fp6;
380
533
  readonly Fp2: Fp2Bls;
381
- readonly FROBENIUS_COEFFICIENTS_1: Fp2[];
382
- readonly FROBENIUS_COEFFICIENTS_2: Fp2[];
383
534
 
384
535
  constructor(Fp2: Fp2Bls) {
385
536
  this.Fp2 = Fp2;
386
- this.ORDER = Fp2.ORDER; // TODO: unused, but need to verify
537
+ // `IField.ORDER` is the field cardinality `q`; for sextic extensions that is `p^6`.
538
+ // Generic helpers like Frobenius-style `x^q = x` checks rely on the literal field size here.
539
+ this.ORDER = Fp2.Fp.ORDER ** _6n;
387
540
  this.BITS = 3 * Fp2.BITS;
388
541
  this.BYTES = 3 * Fp2.BYTES;
389
542
  this.isLE = Fp2.isLE;
390
- this.ZERO = { c0: Fp2.ZERO, c1: Fp2.ZERO, c2: Fp2.ZERO };
391
- this.ONE = { c0: Fp2.ONE, c1: Fp2.ZERO, c2: Fp2.ZERO };
543
+ this.ZERO = this.create({ c0: Fp2.ZERO, c1: Fp2.ZERO, c2: Fp2.ZERO });
544
+ this.ONE = this.create({ c0: Fp2.ONE, c1: Fp2.ZERO, c2: Fp2.ZERO });
545
+ Object.freeze(this);
546
+ }
547
+ // Most callers never touch Frobenius maps, so keep the sextic tables lazy:
548
+ // eagerly deriving them dominates `bls12-381.js` / `bn254.js` import time.
549
+ get FROBENIUS_COEFFICIENTS_1(): readonly Fp2[] {
550
+ const frob = _FROBENIUS_COEFFICIENTS_6.get(this);
551
+ if (frob) return frob[0];
552
+ const { Fp2 } = this;
392
553
  const { Fp } = Fp2;
393
- const frob = calcFrobeniusCoefficients(Fp2, Fp2.NONRESIDUE, Fp.ORDER, 6, 2, 3);
394
- this.FROBENIUS_COEFFICIENTS_1 = frob[0];
395
- this.FROBENIUS_COEFFICIENTS_2 = frob[1];
396
- Object.seal(this);
554
+ const rows = calcFrobeniusCoefficients(Fp2, Fp2.NONRESIDUE, Fp.ORDER, 6, 2, 3);
555
+ const cache = [Object.freeze(rows[0]), Object.freeze(rows[1])] as const;
556
+ _FROBENIUS_COEFFICIENTS_6.set(this, cache);
557
+ return cache[0];
558
+ }
559
+ get FROBENIUS_COEFFICIENTS_2(): readonly Fp2[] {
560
+ const frob = _FROBENIUS_COEFFICIENTS_6.get(this);
561
+ if (frob) return frob[1];
562
+ void this.FROBENIUS_COEFFICIENTS_1;
563
+ return _FROBENIUS_COEFFICIENTS_6.get(this)![1];
397
564
  }
398
565
  add({ c0, c1, c2 }: Fp6, { c0: r0, c1: r1, c2: r2 }: Fp6) {
399
566
  const { Fp2 } = this;
400
- return {
567
+ return Object.freeze({
401
568
  c0: Fp2.add(c0, r0),
402
569
  c1: Fp2.add(c1, r1),
403
570
  c2: Fp2.add(c2, r2),
404
- };
571
+ });
405
572
  }
406
573
  sub({ c0, c1, c2 }: Fp6, { c0: r0, c1: r1, c2: r2 }: Fp6) {
407
574
  const { Fp2 } = this;
408
- return {
575
+ return Object.freeze({
409
576
  c0: Fp2.sub(c0, r0),
410
577
  c1: Fp2.sub(c1, r1),
411
578
  c2: Fp2.sub(c2, r2),
412
- };
579
+ });
413
580
  }
414
581
  mul({ c0, c1, c2 }: Fp6, rhs: Fp6 | bigint) {
415
582
  const { Fp2 } = this;
416
583
  if (typeof rhs === 'bigint') {
417
- return {
584
+ return Object.freeze({
418
585
  c0: Fp2.mul(c0, rhs),
419
586
  c1: Fp2.mul(c1, rhs),
420
587
  c2: Fp2.mul(c2, rhs),
421
- };
588
+ });
422
589
  }
423
590
  const { c0: r0, c1: r1, c2: r2 } = rhs;
424
591
  const t0 = Fp2.mul(c0, r0); // c0 * o0
425
592
  const t1 = Fp2.mul(c1, r1); // c1 * o1
426
593
  const t2 = Fp2.mul(c2, r2); // c2 * o2
427
- return {
594
+ return Object.freeze({
428
595
  // t0 + (c1 + c2) * (r1 * r2) - (T1 + T2) * (u + 1)
429
596
  c0: Fp2.add(
430
597
  t0,
@@ -437,7 +604,7 @@ class _Field6 implements Fp6Bls {
437
604
  ),
438
605
  // T1 + (c0 + c2) * (r0 + r2) - T0 + T2
439
606
  c2: Fp2.sub(Fp2.add(t1, Fp2.mul(Fp2.add(c0, c2), Fp2.add(r0, r2))), Fp2.add(t0, t2)),
440
- };
607
+ });
441
608
  }
442
609
  sqr({ c0, c1, c2 }: Fp6) {
443
610
  const { Fp2 } = this;
@@ -445,12 +612,12 @@ class _Field6 implements Fp6Bls {
445
612
  let t1 = Fp2.mul(Fp2.mul(c0, c1), _2n); // 2 * c0 * c1
446
613
  let t3 = Fp2.mul(Fp2.mul(c1, c2), _2n); // 2 * c1 * c2
447
614
  let t4 = Fp2.sqr(c2); // c2²
448
- return {
615
+ return Object.freeze({
449
616
  c0: Fp2.add(Fp2.mulByNonresidue(t3), t0), // T3 * (u + 1) + T0
450
617
  c1: Fp2.add(Fp2.mulByNonresidue(t4), t1), // T4 * (u + 1) + T1
451
618
  // T1 + (c0 - c1 + c2)² + T3 - T0 - T4
452
619
  c2: Fp2.sub(Fp2.sub(Fp2.add(Fp2.add(t1, Fp2.sqr(Fp2.add(Fp2.sub(c0, c1), c2))), t3), t0), t4),
453
- };
620
+ });
454
621
  }
455
622
  addN(a: Fp6, b: Fp6): Fp6 {
456
623
  return this.add(a, b);
@@ -466,14 +633,23 @@ class _Field6 implements Fp6Bls {
466
633
  }
467
634
 
468
635
  create(num: Fp6) {
469
- return num;
636
+ const { Fp2 } = this;
637
+ const c0 = Fp2.create(num.c0);
638
+ const c1 = Fp2.create(num.c1);
639
+ const c2 = Fp2.create(num.c2);
640
+ return Object.freeze({ c0, c1, c2 });
470
641
  }
471
642
 
472
- isValid({ c0, c1, c2 }: Fp6) {
643
+ isValid(num: Fp6) {
644
+ if (!isObj(num))
645
+ throw new TypeError('invalid field element: expected object, got ' + typeof num);
646
+ const { c0, c1, c2 } = num;
473
647
  const { Fp2 } = this;
474
648
  return Fp2.isValid(c0) && Fp2.isValid(c1) && Fp2.isValid(c2);
475
649
  }
476
- is0({ c0, c1, c2 }: Fp6) {
650
+ is0(num: Fp6) {
651
+ if (!isObj(num)) return false;
652
+ const { c0, c1, c2 } = num;
477
653
  const { Fp2 } = this;
478
654
  return Fp2.is0(c0) && Fp2.is0(c1) && Fp2.is0(c2);
479
655
  }
@@ -482,13 +658,16 @@ class _Field6 implements Fp6Bls {
482
658
  }
483
659
  neg({ c0, c1, c2 }: Fp6) {
484
660
  const { Fp2 } = this;
485
- return { c0: Fp2.neg(c0), c1: Fp2.neg(c1), c2: Fp2.neg(c2) };
661
+ return Object.freeze({ c0: Fp2.neg(c0), c1: Fp2.neg(c1), c2: Fp2.neg(c2) });
486
662
  }
487
663
  eql({ c0, c1, c2 }: Fp6, { c0: r0, c1: r1, c2: r2 }: Fp6) {
488
664
  const { Fp2 } = this;
489
665
  return Fp2.eql(c0, r0) && Fp2.eql(c1, r1) && Fp2.eql(c2, r2);
490
666
  }
491
667
  sqrt(_: Fp6) {
668
+ // Sextic extensions can use generic odd-field Tonelli-Shanks, but the helper must work
669
+ // over `IField<T>` with a quadratic non-residue from Fp6 itself. The current
670
+ // `mod.tonelliShanks(P)` precomputation only searches integer residues in the base field.
492
671
  return notImplemented();
493
672
  }
494
673
  // Do we need division by bigint at all? Should be done via order:
@@ -513,18 +692,19 @@ class _Field6 implements Fp6Bls {
513
692
  let t4 = Fp2.inv(
514
693
  Fp2.add(Fp2.mulByNonresidue(Fp2.add(Fp2.mul(c2, t1), Fp2.mul(c1, t2))), Fp2.mul(c0, t0))
515
694
  );
516
- return { c0: Fp2.mul(t4, t0), c1: Fp2.mul(t4, t1), c2: Fp2.mul(t4, t2) };
695
+ return Object.freeze({ c0: Fp2.mul(t4, t0), c1: Fp2.mul(t4, t1), c2: Fp2.mul(t4, t2) });
517
696
  }
518
697
  // Bytes utils
519
698
  fromBytes(b: Uint8Array): Fp6 {
520
699
  const { Fp2 } = this;
700
+ abytes(b);
521
701
  if (b.length !== this.BYTES) throw new Error('fromBytes invalid length=' + b.length);
522
702
  const B2 = Fp2.BYTES;
523
- return {
703
+ return this.create({
524
704
  c0: Fp2.fromBytes(b.subarray(0, B2)),
525
705
  c1: Fp2.fromBytes(b.subarray(B2, B2 * 2)),
526
706
  c2: Fp2.fromBytes(b.subarray(2 * B2)),
527
- };
707
+ });
528
708
  }
529
709
  toBytes({ c0, c1, c2 }: Fp6): Uint8Array {
530
710
  const { Fp2 } = this;
@@ -532,66 +712,73 @@ class _Field6 implements Fp6Bls {
532
712
  }
533
713
  cmov({ c0, c1, c2 }: Fp6, { c0: r0, c1: r1, c2: r2 }: Fp6, c: boolean) {
534
714
  const { Fp2 } = this;
535
- return {
715
+ return this.create({
536
716
  c0: Fp2.cmov(c0, r0, c),
537
717
  c1: Fp2.cmov(c1, r1, c),
538
718
  c2: Fp2.cmov(c2, r2, c),
539
- };
719
+ });
540
720
  }
541
- fromBigSix(t: BigintSix): Fp6 {
721
+ fromBigSix(tuple: BigintSix): Fp6 {
542
722
  const { Fp2 } = this;
543
- if (!Array.isArray(t) || t.length !== 6) throw new Error('invalid Fp6 usage');
544
- return {
723
+ if (!Array.isArray(tuple) || tuple.length !== 6) throw new Error('invalid Fp6.fromBigSix');
724
+ for (let i = 0; i < 6; i++)
725
+ if (typeof tuple[i] !== 'bigint') throw new Error('invalid Fp6.fromBigSix');
726
+ const t = tuple;
727
+ return this.create({
545
728
  c0: Fp2.fromBigTuple(t.slice(0, 2) as BigintTuple),
546
729
  c1: Fp2.fromBigTuple(t.slice(2, 4) as BigintTuple),
547
730
  c2: Fp2.fromBigTuple(t.slice(4, 6) as BigintTuple),
548
- };
731
+ });
549
732
  }
550
733
  frobeniusMap({ c0, c1, c2 }: Fp6, power: number) {
551
734
  const { Fp2 } = this;
552
- return {
735
+ return Object.freeze({
553
736
  c0: Fp2.frobeniusMap(c0, power),
554
737
  c1: Fp2.mul(Fp2.frobeniusMap(c1, power), this.FROBENIUS_COEFFICIENTS_1[power % 6]),
555
738
  c2: Fp2.mul(Fp2.frobeniusMap(c2, power), this.FROBENIUS_COEFFICIENTS_2[power % 6]),
556
- };
739
+ });
557
740
  }
558
741
  mulByFp2({ c0, c1, c2 }: Fp6, rhs: Fp2): Fp6 {
559
742
  const { Fp2 } = this;
560
- return {
743
+ return Object.freeze({
561
744
  c0: Fp2.mul(c0, rhs),
562
745
  c1: Fp2.mul(c1, rhs),
563
746
  c2: Fp2.mul(c2, rhs),
564
- };
747
+ });
565
748
  }
566
749
  mulByNonresidue({ c0, c1, c2 }: Fp6) {
567
750
  const { Fp2 } = this;
568
- return { c0: Fp2.mulByNonresidue(c2), c1: c0, c2: c1 };
751
+ return Object.freeze({ c0: Fp2.mulByNonresidue(c2), c1: c0, c2: c1 });
569
752
  }
570
753
  // Sparse multiplication
571
754
  mul1({ c0, c1, c2 }: Fp6, b1: Fp2): Fp6 {
572
755
  const { Fp2 } = this;
573
- return {
756
+ return Object.freeze({
574
757
  c0: Fp2.mulByNonresidue(Fp2.mul(c2, b1)),
575
758
  c1: Fp2.mul(c0, b1),
576
759
  c2: Fp2.mul(c1, b1),
577
- };
760
+ });
578
761
  }
579
762
  // Sparse multiplication
580
763
  mul01({ c0, c1, c2 }: Fp6, b0: Fp2, b1: Fp2): Fp6 {
581
764
  const { Fp2 } = this;
582
765
  let t0 = Fp2.mul(c0, b0); // c0 * b0
583
766
  let t1 = Fp2.mul(c1, b1); // c1 * b1
584
- return {
767
+ return Object.freeze({
585
768
  // ((c1 + c2) * b1 - T1) * (u + 1) + T0
586
769
  c0: Fp2.add(Fp2.mulByNonresidue(Fp2.sub(Fp2.mul(Fp2.add(c1, c2), b1), t1)), t0),
587
770
  // (b0 + b1) * (c0 + c1) - T0 - T1
588
771
  c1: Fp2.sub(Fp2.sub(Fp2.mul(Fp2.add(b0, b1), Fp2.add(c0, c1)), t0), t1),
589
772
  // (c0 + c2) * b0 - T0 + T1
590
773
  c2: Fp2.add(Fp2.sub(Fp2.mul(Fp2.add(c0, c2), b0), t0), t1),
591
- };
774
+ });
592
775
  }
593
776
  }
594
777
 
778
+ // Keep lazy tower caches off-object: field instances stay frozen, and debugger output
779
+ // stays readable without JS private slots while second/subsequent lookups still hit cache.
780
+ const _FROBENIUS_COEFFICIENTS_6 = new WeakMap<_Field6, readonly [readonly Fp2[], readonly Fp2[]]>();
781
+
595
782
  class _Field12 implements Fp12Bls {
596
783
  readonly ORDER: bigint;
597
784
  readonly BITS: number;
@@ -602,41 +789,66 @@ class _Field12 implements Fp12Bls {
602
789
  readonly ONE: Fp12;
603
790
 
604
791
  readonly Fp6: Fp6Bls;
605
- readonly FROBENIUS_COEFFICIENTS: Fp2[];
606
792
  readonly X_LEN: number;
607
793
  readonly finalExponentiate: Tower12Opts['Fp12finalExponentiate'];
608
794
 
609
795
  constructor(Fp6: Fp6Bls, opts: Tower12Opts) {
796
+ const { X_LEN, Fp12finalExponentiate } = opts;
610
797
  const { Fp2 } = Fp6;
611
798
  const { Fp } = Fp2;
612
799
  this.Fp6 = Fp6;
613
800
 
614
- this.ORDER = Fp2.ORDER; // TODO: verify if it's unuesd
801
+ // `IField.ORDER` is the field cardinality `q`; for degree-12 extensions that is `p^12`.
802
+ // Keeping `p^2` here breaks generic field identities like `x^q = x` on Fp12.
803
+ this.ORDER = Fp.ORDER ** _12n;
615
804
  this.BITS = 2 * Fp6.BITS;
616
805
  this.BYTES = 2 * Fp6.BYTES;
617
806
  this.isLE = Fp6.isLE;
618
- this.ZERO = { c0: Fp6.ZERO, c1: Fp6.ZERO };
619
- this.ONE = { c0: Fp6.ONE, c1: Fp6.ZERO };
620
-
621
- this.FROBENIUS_COEFFICIENTS = calcFrobeniusCoefficients(
622
- Fp2,
623
- Fp2.NONRESIDUE,
624
- Fp.ORDER,
625
- 12,
626
- 1,
627
- 6
628
- )[0];
629
- this.X_LEN = opts.X_LEN;
630
- this.finalExponentiate = opts.Fp12finalExponentiate;
807
+ // Returned tower values are frozen, so larger constants can safely reuse
808
+ // already-frozen child coefficients instead of cloning them.
809
+ this.ZERO = this.create({ c0: Fp6.ZERO, c1: Fp6.ZERO });
810
+ this.ONE = this.create({ c0: Fp6.ONE, c1: Fp6.ZERO });
811
+ this.X_LEN = X_LEN;
812
+ this.finalExponentiate = (num) => {
813
+ const copy2 = ({ c0, c1 }: Fp2): Fp2 => Object.freeze({ c0, c1 });
814
+ const copy6 = ({ c0, c1, c2 }: Fp6): Fp6 =>
815
+ Object.freeze({ c0: copy2(c0), c1: copy2(c1), c2: copy2(c2) });
816
+ // This config hook is trusted to return a canonical Fp12 value already.
817
+ // Copy+freeze it to keep the tower immutability invariant without mutating caller objects.
818
+ const res = Fp12finalExponentiate(num);
819
+ return Object.freeze({ c0: copy6(res.c0), c1: copy6(res.c1) });
820
+ };
821
+ Object.freeze(this);
822
+ }
823
+ // Keep the degree-12 Frobenius row lazy too; after the first lookup the cached
824
+ // array is reused exactly like the old eager table.
825
+ get FROBENIUS_COEFFICIENTS(): readonly Fp2[] {
826
+ const frob = _FROBENIUS_COEFFICIENTS_12.get(this);
827
+ if (frob) return frob;
828
+ const { Fp2 } = this.Fp6;
829
+ const { Fp } = Fp2;
830
+ const cache = Object.freeze(
831
+ calcFrobeniusCoefficients(Fp2, Fp2.NONRESIDUE, Fp.ORDER, 12, 1, 6)[0]
832
+ );
833
+ _FROBENIUS_COEFFICIENTS_12.set(this, cache);
834
+ return cache;
631
835
  }
632
836
  create(num: Fp12) {
633
- return num;
837
+ const { Fp6 } = this;
838
+ const c0 = Fp6.create(num.c0);
839
+ const c1 = Fp6.create(num.c1);
840
+ return Object.freeze({ c0, c1 });
634
841
  }
635
- isValid({ c0, c1 }: Fp12) {
842
+ isValid(num: Fp12) {
843
+ if (!isObj(num))
844
+ throw new TypeError('invalid field element: expected object, got ' + typeof num);
845
+ const { c0, c1 } = num;
636
846
  const { Fp6 } = this;
637
847
  return Fp6.isValid(c0) && Fp6.isValid(c1);
638
848
  }
639
- is0({ c0, c1 }: Fp12) {
849
+ is0(num: Fp12) {
850
+ if (!isObj(num)) return false;
851
+ const { c0, c1 } = num;
640
852
  const { Fp6 } = this;
641
853
  return Fp6.is0(c0) && Fp6.is0(c1);
642
854
  }
@@ -645,19 +857,23 @@ class _Field12 implements Fp12Bls {
645
857
  }
646
858
  neg({ c0, c1 }: Fp12) {
647
859
  const { Fp6 } = this;
648
- return { c0: Fp6.neg(c0), c1: Fp6.neg(c1) };
860
+ return Object.freeze({ c0: Fp6.neg(c0), c1: Fp6.neg(c1) });
649
861
  }
650
862
  eql({ c0, c1 }: Fp12, { c0: r0, c1: r1 }: Fp12) {
651
863
  const { Fp6 } = this;
652
864
  return Fp6.eql(c0, r0) && Fp6.eql(c1, r1);
653
865
  }
654
- sqrt(_: any): any {
655
- notImplemented();
866
+ sqrt(_: Fp12): Fp12 {
867
+ // Fp12 is quadratic over Fp6, so a dedicated quadratic-extension sqrt is possible here
868
+ // once Fp6.sqrt() exists. Without that lower-level sqrt, only a field-generic
869
+ // Tonelli-Shanks path over Fp12 itself would work.
870
+ return notImplemented();
656
871
  }
657
872
  inv({ c0, c1 }: Fp12) {
658
873
  const { Fp6 } = this;
659
874
  let t = Fp6.inv(Fp6.sub(Fp6.sqr(c0), Fp6.mulByNonresidue(Fp6.sqr(c1)))); // 1 / (c0² - c1² * v)
660
- return { c0: Fp6.mul(c0, t), c1: Fp6.neg(Fp6.mul(c1, t)) }; // ((C0 * T) * T) + (-C1 * T) * w
875
+ // ((C0 * T) * T) + (-C1 * T) * w
876
+ return Object.freeze({ c0: Fp6.mul(c0, t), c1: Fp6.neg(Fp6.mul(c1, t)) });
661
877
  }
662
878
  div(lhs: Fp12, rhs: Fp12) {
663
879
  const { Fp6 } = this;
@@ -675,41 +891,42 @@ class _Field12 implements Fp12Bls {
675
891
  // Normalized
676
892
  add({ c0, c1 }: Fp12, { c0: r0, c1: r1 }: Fp12) {
677
893
  const { Fp6 } = this;
678
- return {
894
+ return Object.freeze({
679
895
  c0: Fp6.add(c0, r0),
680
896
  c1: Fp6.add(c1, r1),
681
- };
897
+ });
682
898
  }
683
899
  sub({ c0, c1 }: Fp12, { c0: r0, c1: r1 }: Fp12) {
684
900
  const { Fp6 } = this;
685
- return {
901
+ return Object.freeze({
686
902
  c0: Fp6.sub(c0, r0),
687
903
  c1: Fp6.sub(c1, r1),
688
- };
904
+ });
689
905
  }
690
906
  mul({ c0, c1 }: Fp12, rhs: Fp12 | bigint) {
691
907
  const { Fp6 } = this;
692
- if (typeof rhs === 'bigint') return { c0: Fp6.mul(c0, rhs), c1: Fp6.mul(c1, rhs) };
908
+ if (typeof rhs === 'bigint')
909
+ return Object.freeze({ c0: Fp6.mul(c0, rhs), c1: Fp6.mul(c1, rhs) });
693
910
  let { c0: r0, c1: r1 } = rhs;
694
911
  let t1 = Fp6.mul(c0, r0); // c0 * r0
695
912
  let t2 = Fp6.mul(c1, r1); // c1 * r1
696
- return {
913
+ return Object.freeze({
697
914
  c0: Fp6.add(t1, Fp6.mulByNonresidue(t2)), // T1 + T2 * v
698
915
  // (c0 + c1) * (r0 + r1) - (T1 + T2)
699
916
  c1: Fp6.sub(Fp6.mul(Fp6.add(c0, c1), Fp6.add(r0, r1)), Fp6.add(t1, t2)),
700
- };
917
+ });
701
918
  }
702
919
  sqr({ c0, c1 }: Fp12) {
703
920
  const { Fp6 } = this;
704
921
  let ab = Fp6.mul(c0, c1); // c0 * c1
705
- return {
922
+ return Object.freeze({
706
923
  // (c1 * v + c0) * (c0 + c1) - AB - AB * v
707
924
  c0: Fp6.sub(
708
925
  Fp6.sub(Fp6.mul(Fp6.add(Fp6.mulByNonresidue(c1), c0), Fp6.add(c0, c1)), ab),
709
926
  Fp6.mulByNonresidue(ab)
710
927
  ),
711
928
  c1: Fp6.add(ab, ab),
712
- }; // AB + AB
929
+ }); // AB + AB
713
930
  }
714
931
  // NonNormalized stuff
715
932
  addN(a: Fp12, b: Fp12): Fp12 {
@@ -728,11 +945,12 @@ class _Field12 implements Fp12Bls {
728
945
  // Bytes utils
729
946
  fromBytes(b: Uint8Array): Fp12 {
730
947
  const { Fp6 } = this;
948
+ abytes(b);
731
949
  if (b.length !== this.BYTES) throw new Error('fromBytes invalid length=' + b.length);
732
- return {
950
+ return this.create({
733
951
  c0: Fp6.fromBytes(b.subarray(0, Fp6.BYTES)),
734
952
  c1: Fp6.fromBytes(b.subarray(Fp6.BYTES)),
735
- };
953
+ });
736
954
  }
737
955
  toBytes({ c0, c1 }: Fp12): Uint8Array {
738
956
  const { Fp6 } = this;
@@ -740,10 +958,10 @@ class _Field12 implements Fp12Bls {
740
958
  }
741
959
  cmov({ c0, c1 }: Fp12, { c0: r0, c1: r1 }: Fp12, c: boolean) {
742
960
  const { Fp6 } = this;
743
- return {
961
+ return this.create({
744
962
  c0: Fp6.cmov(c0, r0, c),
745
963
  c1: Fp6.cmov(c1, r1, c),
746
- };
964
+ });
747
965
  }
748
966
  // Utils
749
967
  // toString() {
@@ -752,12 +970,16 @@ class _Field12 implements Fp12Bls {
752
970
  // fromTuple(c: [Fp6, Fp6]) {
753
971
  // return new Fp12(...c);
754
972
  // }
755
- fromBigTwelve(t: BigintTwelve): Fp12 {
973
+ fromBigTwelve(tuple: BigintTwelve): Fp12 {
756
974
  const { Fp6 } = this;
757
- return {
975
+ if (!Array.isArray(tuple) || tuple.length !== 12) throw new Error('invalid Fp12.fromBigTwelve');
976
+ for (let i = 0; i < 12; i++)
977
+ if (typeof tuple[i] !== 'bigint') throw new Error('invalid Fp12.fromBigTwelve');
978
+ const t = tuple;
979
+ return this.create({
758
980
  c0: Fp6.fromBigSix(t.slice(0, 6) as BigintSix),
759
981
  c1: Fp6.fromBigSix(t.slice(6, 12) as BigintSix),
760
- };
982
+ });
761
983
  }
762
984
  // Raises to q**i -th power
763
985
  frobeniusMap(lhs: Fp12, power: number) {
@@ -765,24 +987,25 @@ class _Field12 implements Fp12Bls {
765
987
  const { Fp2 } = Fp6;
766
988
  const { c0, c1, c2 } = Fp6.frobeniusMap(lhs.c1, power);
767
989
  const coeff = this.FROBENIUS_COEFFICIENTS[power % 12];
768
- return {
990
+ return Object.freeze({
769
991
  c0: Fp6.frobeniusMap(lhs.c0, power),
770
- c1: Fp6.create({
992
+ c1: Object.freeze({
771
993
  c0: Fp2.mul(c0, coeff),
772
994
  c1: Fp2.mul(c1, coeff),
773
995
  c2: Fp2.mul(c2, coeff),
774
996
  }),
775
- };
997
+ });
776
998
  }
777
999
  mulByFp2({ c0, c1 }: Fp12, rhs: Fp2): Fp12 {
778
1000
  const { Fp6 } = this;
779
- return {
1001
+ return Object.freeze({
780
1002
  c0: Fp6.mulByFp2(c0, rhs),
781
1003
  c1: Fp6.mulByFp2(c1, rhs),
782
- };
1004
+ });
783
1005
  }
784
1006
  conjugate({ c0, c1 }: Fp12): Fp12 {
785
- return { c0, c1: this.Fp6.neg(c1) };
1007
+ // Reuse `c0` by reference and only negate the `w` coefficient.
1008
+ return Object.freeze({ c0, c1: this.Fp6.neg(c1) });
786
1009
  }
787
1010
  // Sparse multiplication
788
1011
  mul014({ c0, c1 }: Fp12, o0: Fp2, o1: Fp2, o4: Fp2) {
@@ -790,26 +1013,26 @@ class _Field12 implements Fp12Bls {
790
1013
  const { Fp2 } = Fp6;
791
1014
  let t0 = Fp6.mul01(c0, o0, o1);
792
1015
  let t1 = Fp6.mul1(c1, o4);
793
- return {
1016
+ return Object.freeze({
794
1017
  c0: Fp6.add(Fp6.mulByNonresidue(t1), t0), // T1 * v + T0
795
1018
  // (c1 + c0) * [o0, o1+o4] - T0 - T1
796
1019
  c1: Fp6.sub(Fp6.sub(Fp6.mul01(Fp6.add(c1, c0), o0, Fp2.add(o1, o4)), t0), t1),
797
- };
1020
+ });
798
1021
  }
799
1022
  mul034({ c0, c1 }: Fp12, o0: Fp2, o3: Fp2, o4: Fp2) {
800
1023
  const { Fp6 } = this;
801
1024
  const { Fp2 } = Fp6;
802
- const a = Fp6.create({
1025
+ const a = Object.freeze({
803
1026
  c0: Fp2.mul(c0.c0, o0),
804
1027
  c1: Fp2.mul(c0.c1, o0),
805
1028
  c2: Fp2.mul(c0.c2, o0),
806
1029
  });
807
1030
  const b = Fp6.mul01(c1, o3, o4);
808
1031
  const e = Fp6.mul01(Fp6.add(c0, c1), Fp2.add(o0, o3), o4);
809
- return {
1032
+ return Object.freeze({
810
1033
  c0: Fp6.add(Fp6.mulByNonresidue(b), a),
811
1034
  c1: Fp6.sub(e, Fp6.add(a, b)),
812
- };
1035
+ });
813
1036
  }
814
1037
 
815
1038
  // A cyclotomic group is a subgroup of Fp^n defined by
@@ -826,21 +1049,24 @@ class _Field12 implements Fp12Bls {
826
1049
  const { first: t5, second: t6 } = Fp2.Fp4Square(c1c0, c0c2);
827
1050
  const { first: t7, second: t8 } = Fp2.Fp4Square(c0c1, c1c2);
828
1051
  const t9 = Fp2.mulByNonresidue(t8); // T8 * (u + 1)
829
- return {
830
- c0: Fp6.create({
1052
+ return Object.freeze({
1053
+ c0: Object.freeze({
831
1054
  c0: Fp2.add(Fp2.mul(Fp2.sub(t3, c0c0), _2n), t3), // 2 * (T3 - c0c0) + T3
832
1055
  c1: Fp2.add(Fp2.mul(Fp2.sub(t5, c0c1), _2n), t5), // 2 * (T5 - c0c1) + T5
833
1056
  c2: Fp2.add(Fp2.mul(Fp2.sub(t7, c0c2), _2n), t7),
834
1057
  }), // 2 * (T7 - c0c2) + T7
835
- c1: Fp6.create({
1058
+ c1: Object.freeze({
836
1059
  c0: Fp2.add(Fp2.mul(Fp2.add(t9, c1c0), _2n), t9), // 2 * (T9 + c1c0) + T9
837
1060
  c1: Fp2.add(Fp2.mul(Fp2.add(t4, c1c1), _2n), t4), // 2 * (T4 + c1c1) + T4
838
1061
  c2: Fp2.add(Fp2.mul(Fp2.add(t6, c1c2), _2n), t6),
839
1062
  }),
840
- }; // 2 * (T6 + c1c2) + T6
1063
+ }); // 2 * (T6 + c1c2) + T6
841
1064
  }
842
1065
  // https://eprint.iacr.org/2009/565.pdf
843
1066
  _cyclotomicExp(num: Fp12, n: bigint): Fp12 {
1067
+ // The loop only consumes `X_LEN` bits, so out-of-range exponents would otherwise get silently
1068
+ // truncated (or sign-extended for negatives) instead of matching the caller's requested power.
1069
+ aInRange('cyclotomic exponent', n, _0n, _1n << BigInt(this.X_LEN));
844
1070
  let z = this.ONE;
845
1071
  for (let i = this.X_LEN - 1; i >= 0; i--) {
846
1072
  z = this._cyclotomicSquare(z);
@@ -850,15 +1076,58 @@ class _Field12 implements Fp12Bls {
850
1076
  }
851
1077
  }
852
1078
 
853
- export function tower12(opts: Tower12Opts): {
1079
+ const _FROBENIUS_COEFFICIENTS_12 = new WeakMap<_Field12, readonly Fp2[]>();
1080
+
1081
+ /**
1082
+ * @param opts - Tower construction options. See {@link Tower12Opts}.
1083
+ * @returns BLS tower fields.
1084
+ * @throws If the tower options or derived Frobenius helpers are invalid. {@link Error}
1085
+ * @example
1086
+ * Construct the Fp2/Fp6/Fp12 tower used by a pairing-friendly curve.
1087
+ *
1088
+ * ```ts
1089
+ * const fields = tower12({
1090
+ * ORDER: 17n,
1091
+ * X_LEN: 4,
1092
+ * FP2_NONRESIDUE: [1n, 1n],
1093
+ * Fp2mulByB: (num) => num,
1094
+ * Fp12finalExponentiate: (num) => num,
1095
+ * });
1096
+ * const fp12 = fields.Fp12.ONE;
1097
+ * ```
1098
+ */
1099
+ export function tower12(opts: TArg<Tower12Opts>): TRet<{
854
1100
  Fp: Readonly<mod.IField<bigint> & Required<Pick<mod.IField<bigint>, 'isOdd'>>>;
855
1101
  Fp2: Fp2Bls;
856
1102
  Fp6: Fp6Bls;
857
1103
  Fp12: Fp12Bls;
858
- } {
1104
+ }> {
1105
+ validateObject(
1106
+ opts,
1107
+ {
1108
+ ORDER: 'bigint',
1109
+ X_LEN: 'number',
1110
+ FP2_NONRESIDUE: 'object',
1111
+ Fp2mulByB: 'function',
1112
+ Fp12finalExponentiate: 'function',
1113
+ },
1114
+ { NONRESIDUE: 'bigint' }
1115
+ );
1116
+ asafenumber(opts.X_LEN, 'X_LEN');
1117
+ if (opts.X_LEN < 1) throw new Error('invalid X_LEN');
1118
+ const nonresidue = opts.FP2_NONRESIDUE as bigint[];
1119
+ if (!Array.isArray(nonresidue) || nonresidue.length !== 2)
1120
+ throw new Error('invalid FP2_NONRESIDUE');
1121
+ if (typeof nonresidue[0] !== 'bigint' || typeof nonresidue[1] !== 'bigint')
1122
+ throw new Error('invalid FP2_NONRESIDUE');
859
1123
  const Fp = mod.Field(opts.ORDER);
860
1124
  const Fp2 = new _Field2(Fp, opts);
861
1125
  const Fp6 = new _Field6(Fp2);
862
1126
  const Fp12 = new _Field12(Fp6, opts);
863
- return { Fp, Fp2, Fp6, Fp12 };
1127
+ return { Fp, Fp2, Fp6, Fp12 } as TRet<{
1128
+ Fp: Readonly<mod.IField<bigint> & Required<Pick<mod.IField<bigint>, 'isOdd'>>>;
1129
+ Fp2: Fp2Bls;
1130
+ Fp6: Fp6Bls;
1131
+ Fp12: Fp12Bls;
1132
+ }>;
864
1133
  }