@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/src/bls12-381.ts CHANGED
@@ -91,6 +91,8 @@ import {
91
91
  hexToBytes,
92
92
  numberToBytesBE,
93
93
  randomBytes,
94
+ type TArg,
95
+ type TRet,
94
96
  } from './utils.ts';
95
97
  // Types
96
98
  import { isogenyMap } from './abstract/hash-to-curve.ts';
@@ -112,7 +114,8 @@ const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3), _4n =
112
114
  // To verify math:
113
115
  // https://tools.ietf.org/html/draft-irtf-cfrg-pairing-friendly-curves-11
114
116
 
115
- // The BLS parameter x (seed) for BLS12-381. NOTE: it is negative!
117
+ // The BLS parameter x (seed) for BLS12-381. The stored constant is `|x|`; call
118
+ // sites that need the signed parameter apply the minus sign themselves.
116
119
  // x = -2^63 - 2^62 - 2^60 - 2^57 - 2^48 - 2^16
117
120
  const BLS_X = BigInt('0xd201000000010000');
118
121
  // t = x (called differently in different places)
@@ -127,7 +130,7 @@ const BLS_X_LEN = bitLen(BLS_X);
127
130
  // where r is order of prime subgroup and h is cofactor.
128
131
  // r = t⁴-t²+1
129
132
  // r = (t**4n - t**2n + 1n)
130
- // cofactor h of G1: (t - 1)²/3
133
+ // cofactor h of G1: (t - 1)²/3, with the signed convention `t = -x`
131
134
  // cofactorG1 = (t-1n)**2n/3n
132
135
  // x = 3685416753713387016781088315183077757961620795782546409894578378688607592378376318836054947676345821548104185464507
133
136
  // y = 1339506544944476473020471379941921221584933875938349620426543736416511423956333506472724655353366534992391756441569
@@ -149,23 +152,28 @@ const bls12_381_CURVE_G1: WeierstrassOpts<bigint> = {
149
152
 
150
153
  // CURVE FIELDS
151
154
  // r = z⁴ − z² + 1; CURVE.n from other curves
152
- /** bls12-381 Fr (Fn) field. Note: does mod() on fromBytes, due to modFromBytes option. */
153
- export const bls12_381_Fr: IField<bigint> = Field(bls12_381_CURVE_G1.n, {
155
+ /**
156
+ * bls12-381 Fr (Fn) field.
157
+ * `fromBytes()` reduces modulo `q` instead of rejecting non-canonical encodings.
158
+ */
159
+ export const bls12_381_Fr: TRet<IField<bigint>> = Field(bls12_381_CURVE_G1.n, {
154
160
  modFromBytes: true,
155
- });
161
+ }) as TRet<IField<bigint>>;
156
162
  const { Fp, Fp2, Fp6, Fp12 } = tower12({
157
163
  ORDER: bls12_381_CURVE_G1.p,
158
164
  X_LEN: BLS_X_LEN,
159
165
  // Finite extension field over irreducible polynominal.
160
166
  // Fp(u) / (u² - β) where β = -1
167
+ // Public `Fp2.NONRESIDUE` below is the sextic-tower value `(1, 1) = u + 1`;
168
+ // the quadratic non-residue for the base Fp2 construction is still `-1`.
161
169
  FP2_NONRESIDUE: [_1n, _1n],
162
- Fp2mulByB: ({ c0, c1 }) => {
170
+ Fp2mulByB: ({ c0, c1 }: Fp2) => {
163
171
  const t0 = Fp.mul(c0, _4n); // 4 * c0
164
172
  const t1 = Fp.mul(c1, _4n); // 4 * c1
165
173
  // (T0-T1) + (T0+T1)*i
166
174
  return { c0: Fp.sub(t0, t1), c1: Fp.add(t0, t1) };
167
175
  },
168
- Fp12finalExponentiate: (num) => {
176
+ Fp12finalExponentiate: (num: Fp12) => {
169
177
  const x = BLS_X;
170
178
  // this^(q⁶) / this
171
179
  const t0 = Fp12.div(Fp12.frobeniusMap(num, 6), num);
@@ -186,15 +194,31 @@ const { Fp, Fp2, Fp6, Fp12 } = tower12({
186
194
  },
187
195
  });
188
196
 
189
- // GLV endomorphism Ψ(P), for fast cofactor clearing
190
- const { G2psi, G2psi2 } = psiFrobenius(Fp, Fp2, Fp2.div(Fp2.ONE, Fp2.NONRESIDUE)); // 1/(u+1)
197
+ // GLV endomorphism Ψ(P), for fast cofactor clearing. `Fp2.NONRESIDUE` here is
198
+ // the tower value `u + 1`, so the Frobenius base passed to psiFrobenius is
199
+ // `1 / (u + 1)`, and psi2 derives the published `1 / 2^((p - 1) / 3)` constant internally.
200
+ let frob: ReturnType<typeof psiFrobenius> | undefined;
201
+ const getFrob = () => frob || (frob = psiFrobenius(Fp, Fp2, Fp2.div(Fp2.ONE, Fp2.NONRESIDUE)));
202
+ // Eager psiFrobenius setup now dominates `bls12-381.js` import, so defer it to
203
+ // first use. After that these locals are rewritten to the direct helper refs.
204
+ let G2psi: ReturnType<typeof psiFrobenius>['G2psi'] = (c, P) => {
205
+ const fn = getFrob().G2psi;
206
+ G2psi = fn;
207
+ return fn(c, P);
208
+ };
209
+ let G2psi2: ReturnType<typeof psiFrobenius>['G2psi2'] = (c, P) => {
210
+ const fn = getFrob().G2psi2;
211
+ G2psi2 = fn;
212
+ return fn(c, P);
213
+ };
191
214
 
192
215
  /**
193
216
  * Default hash_to_field / hash-to-curve for BLS.
194
217
  * m: 1 for G1, 2 for G2
195
218
  * k: target security level in bits
196
219
  * hash: any function, e.g. BBS+ uses BLAKE2: see [github](https://github.com/hyperledger/aries-framework-go/issues/2247).
197
- * Parameter values come from [section 8.8.2 of RFC 9380](https://www.rfc-editor.org/rfc/rfc9380#section-8.8.2).
220
+ * Field/hash parameters come from [section 8.8.2 of RFC 9380](https://www.rfc-editor.org/rfc/rfc9380#section-8.8.2),
221
+ * but the `DST` / `encodeDST` strings below are the BLS-signature-suite override.
198
222
  */
199
223
  const hasher_opts = Object.freeze({
200
224
  DST: 'BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_',
@@ -207,7 +231,7 @@ const hasher_opts = Object.freeze({
207
231
  });
208
232
 
209
233
  // a=0, b=4
210
- // cofactor h of G2
234
+ // cofactor h of G2, derived with the signed convention `t = -x`
211
235
  // (t^8 - 4t^7 + 5t^6 - 4t^4 + 6t^3 - 4t^2 - 4t + 13)/9
212
236
  // cofactorG2 = (t**8n - 4n*t**7n + 5n*t**6n - 4n*t**4n + 6n*t**3n - 4n*t**2n - 4n*t+13n)/9n
213
237
  // x = 3059144344244213709971259814753781636986470325476647558659373206291635324768958432433509563104347017837885763365758*u + 352701069587466618187139116011060144890029952792775240219908644239793785735715026873347600343865175952761926303160
@@ -239,11 +263,102 @@ const bls12_381_CURVE_G2 = {
239
263
  };
240
264
 
241
265
  // Encoding utils
242
- // Compressed point of infinity
243
- // Set compressed & point-at-infinity bits
244
- const COMPZERO = setMask(Fp.toBytes(_0n), { infinity: true, compressed: true });
266
+ const sortBit = (parts: bigint[], p: bigint) => {
267
+ for (const part of parts) {
268
+ if (part !== _0n) return Boolean((part * _2n) / p);
269
+ }
270
+ return false;
271
+ };
272
+ const fp2 = {
273
+ // Generic tower bytes use `c0 || c1`, but the BLS12-381 G2 point/signature wire encoding uses
274
+ // `c1 || c0`, so keep this local wrapper instead of changing generic field serialization.
275
+ encode({ c0, c1 }: Fp2): TRet<Uint8Array> {
276
+ const { BYTES: L } = Fp;
277
+ return concatBytes(numberToBytesBE(c1, L), numberToBytesBE(c0, L)) as TRet<Uint8Array>;
278
+ },
279
+ decode(bytes: TArg<Uint8Array>) {
280
+ const { BYTES: L } = Fp;
281
+ return Fp2.create({
282
+ c0: Fp.create(bytesToNumberBE(bytes.subarray(L))),
283
+ c1: Fp.create(bytesToNumberBE(bytes.subarray(0, L))),
284
+ });
285
+ },
286
+ };
287
+ const BaseFp = Fp;
288
+ type Mask = { compressed: boolean; infinity: boolean; sort: boolean };
289
+ // Keep BLS12-381 point/signature codecs on one control-flow skeleton: the G1/G2
290
+ // and point/signature variants differ only in field packing, subgroup bytes, and
291
+ // whether uncompressed form is allowed. Copy-paste decoders were diverging.
292
+ const coder = <T>(
293
+ name: 'G1' | 'G2',
294
+ Fp: TArg<IField<T>>,
295
+ b: T,
296
+ encode: TArg<(v: T) => TRet<Uint8Array>>,
297
+ decode: TArg<(bytes: TArg<Uint8Array>) => T>,
298
+ yparts: (y: T) => bigint[]
299
+ ) => {
300
+ const F = Fp as IField<T>;
301
+ const enc = encode as (v: T) => TRet<Uint8Array>;
302
+ const dec = decode as (bytes: TArg<Uint8Array>) => T;
303
+ const W = F.BYTES;
304
+ return (allowUncompressed: boolean) => ({
305
+ encode(point: WeierstrassPoint<T>, compressed = true): TRet<Uint8Array> {
306
+ if (!compressed && !allowUncompressed)
307
+ throw new Error('invalid signature: expected compressed encoding');
308
+ const infinity = point.is0();
309
+ const { x, y } = point.toAffine();
310
+ const bytes = compressed ? enc(x) : concatBytes(enc(x), enc(y));
311
+ let sort;
312
+ if (compressed && !infinity) sort = sortBit(yparts(y), BaseFp.ORDER);
313
+ return setMask(bytes, { compressed, infinity, sort }) as TRet<Uint8Array>;
314
+ },
315
+ decode(bytes: TArg<Uint8Array>): AffinePoint<T> {
316
+ const raw = allowUncompressed
317
+ ? abytes(bytes, undefined, 'point')
318
+ : abytes(bytes, W, 'signature');
319
+ const { compressed, infinity, sort, value } = parseMask(raw);
320
+ if (!allowUncompressed && !compressed)
321
+ throw new Error('invalid signature: expected compressed encoding');
322
+ const len = compressed ? W : 2 * W;
323
+ if (value.length !== len) throw new Error(`invalid ${name} point: expected ${len} bytes`);
324
+ if (infinity) {
325
+ // Infinity canonicality has to be checked on raw bytes before decode()
326
+ // reduces coordinates modulo p and turns non-empty payloads into zero.
327
+ for (const b of value) {
328
+ if (b) throw new Error(`invalid ${name} point: non-canonical zero`);
329
+ }
330
+ return { x: F.ZERO, y: F.ZERO };
331
+ }
332
+ const x = dec(compressed ? value : value.subarray(0, W));
333
+ let y;
334
+ if (compressed) {
335
+ y = F.sqrt(F.add(F.pow(x, _3n), b));
336
+ if (!y) throw new Error(`invalid ${name} point: compressed`);
337
+ if (sortBit(yparts(y), BaseFp.ORDER) !== sort) y = F.neg(y);
338
+ } else {
339
+ y = dec(value.subarray(W));
340
+ }
341
+ // Noble keeps the permissive coordinate reduction path here, but an
342
+ // omitted infinity flag must not still decode to ZERO afterwards.
343
+ if (!compressed && F.is0(x) && F.is0(y))
344
+ throw new Error(`invalid ${name} point: uncompressed`);
345
+ return { x, y };
346
+ },
347
+ });
348
+ };
245
349
 
246
- function parseMask(bytes: Uint8Array) {
350
+ // Internal helper only: it copies before clearing the top flag bits. The
351
+ // pairing-friendly-curves draft C.2 step 1 rejects 0x20 / 0x60 / 0xe0 because
352
+ // S_bit must be zero for infinity and for all uncompressed encodings.
353
+ function validateMask({ compressed, infinity, sort }: Mask) {
354
+ if (
355
+ (!compressed && !infinity && sort) || // 0010_0000 = 0x20
356
+ (!compressed && infinity && sort) || // 0110_0000 = 0x60
357
+ (compressed && infinity && sort) // 1110_0000 = 0xe0
358
+ )
359
+ throw new Error('invalid encoding flag');
360
+ }
361
+ function parseMask(bytes: TArg<Uint8Array>) {
247
362
  // Copy, so we can remove mask data.
248
363
  // It will be removed also later, when Fp.create will call modulo.
249
364
  bytes = copyBytes(bytes);
@@ -251,222 +366,62 @@ function parseMask(bytes: Uint8Array) {
251
366
  const compressed = !!((mask >> 7) & 1); // compression bit (0b1000_0000)
252
367
  const infinity = !!((mask >> 6) & 1); // point at infinity bit (0b0100_0000)
253
368
  const sort = !!((mask >> 5) & 1); // sort bit (0b0010_0000)
369
+ validateMask({ compressed, infinity, sort });
254
370
  bytes[0] &= 0b0001_1111; // clear mask (zero first 3 bits)
255
371
  return { compressed, infinity, sort, value: bytes };
256
372
  }
257
373
 
258
- function setMask(
259
- bytes: Uint8Array,
260
- mask: { compressed?: boolean; infinity?: boolean; sort?: boolean }
261
- ) {
374
+ // Internal helper only: mutates a non-empty fresh buffer in place and just
375
+ // sets bits. Keep the same invalid-flag guard as parseMask() so encoders cannot
376
+ // manufacture states that decoders already reject.
377
+ function setMask(bytes: TArg<Uint8Array>, mask: Partial<Mask>) {
262
378
  if (bytes[0] & 0b1110_0000) throw new Error('setMask: non-empty mask');
379
+ validateMask({ compressed: !!mask.compressed, infinity: !!mask.infinity, sort: !!mask.sort });
263
380
  if (mask.compressed) bytes[0] |= 0b1000_0000;
264
381
  if (mask.infinity) bytes[0] |= 0b0100_0000;
265
382
  if (mask.sort) bytes[0] |= 0b0010_0000;
266
383
  return bytes;
267
384
  }
268
385
 
269
- function pointG1ToBytes(
270
- _c: WeierstrassPointCons<Fp>,
271
- point: WeierstrassPoint<Fp>,
272
- isComp: boolean
273
- ) {
274
- const { BYTES: L, ORDER: P } = Fp;
275
- const is0 = point.is0();
276
- const { x, y } = point.toAffine();
277
- if (isComp) {
278
- if (is0) return COMPZERO.slice();
279
- const sort = Boolean((y * _2n) / P);
280
- return setMask(numberToBytesBE(x, L), { compressed: true, sort });
281
- } else {
282
- if (is0) {
283
- return concatBytes(Uint8Array.of(0x40), new Uint8Array(2 * L - 1));
284
- } else {
285
- return concatBytes(numberToBytesBE(x, L), numberToBytesBE(y, L));
286
- }
287
- }
288
- }
289
-
290
- function signatureG1ToBytes(point: WeierstrassPoint<Fp>) {
386
+ const g1coder = coder(
387
+ 'G1',
388
+ Fp,
389
+ Fp.create(bls12_381_CURVE_G1.b),
390
+ (x: Fp) => numberToBytesBE(x, Fp.BYTES),
391
+ (bytes: TArg<Uint8Array>) => Fp.create(bytesToNumberBE(bytes) & bitMask(Fp.BITS)),
392
+ (y: Fp) => [y]
393
+ );
394
+ const g1 = { point: g1coder(true), sig: g1coder(false) };
395
+ const signatureG1ToBytes = (point: WeierstrassPoint<Fp>): TRet<Uint8Array> => {
291
396
  point.assertValidity();
292
- const { BYTES: L, ORDER: P } = Fp;
293
- const { x, y } = point.toAffine();
294
- if (point.is0()) return COMPZERO.slice();
295
- const sort = Boolean((y * _2n) / P);
296
- return setMask(numberToBytesBE(x, L), { compressed: true, sort });
297
- }
298
-
299
- function pointG1FromBytes(bytes: Uint8Array): AffinePoint<Fp> {
300
- const { compressed, infinity, sort, value } = parseMask(bytes);
301
- const { BYTES: L, ORDER: P } = Fp;
302
- if (value.length === 48 && compressed) {
303
- const compressedValue = bytesToNumberBE(value);
304
- // Zero
305
- const x = Fp.create(compressedValue & bitMask(Fp.BITS));
306
- if (infinity) {
307
- if (x !== _0n) throw new Error('invalid G1 point: non-empty, at infinity, with compression');
308
- return { x: _0n, y: _0n };
309
- }
310
- const right = Fp.add(Fp.pow(x, _3n), Fp.create(bls12_381_CURVE_G1.b)); // y² = x³ + b
311
- let y = Fp.sqrt(right);
312
- if (!y) throw new Error('invalid G1 point: compressed point');
313
- if ((y * _2n) / P !== BigInt(sort)) y = Fp.neg(y);
314
- return { x: Fp.create(x), y: Fp.create(y) };
315
- } else if (value.length === 96 && !compressed) {
316
- // Check if the infinity flag is set
317
- const x = bytesToNumberBE(value.subarray(0, L));
318
- const y = bytesToNumberBE(value.subarray(L));
319
- if (infinity) {
320
- if (x !== _0n || y !== _0n) throw new Error('G1: non-empty point at infinity');
321
- return bls12_381.G1.Point.ZERO.toAffine();
322
- }
323
- return { x: Fp.create(x), y: Fp.create(y) };
324
- } else {
325
- throw new Error('invalid G1 point: expected 48/96 bytes');
326
- }
327
- }
328
-
329
- function signatureG1FromBytes(bytes: Uint8Array): WeierstrassPoint<Fp> {
330
- const { infinity, sort, value } = parseMask(abytes(bytes, 48, 'signature'));
331
- const P = Fp.ORDER;
397
+ return g1.sig.encode(point);
398
+ };
399
+ function signatureG1FromBytes(bytes: TArg<Uint8Array>): WeierstrassPoint<Fp> {
332
400
  const Point = bls12_381.G1.Point;
333
- const compressedValue = bytesToNumberBE(value);
334
- // Zero
335
- if (infinity) return Point.ZERO;
336
- const x = Fp.create(compressedValue & bitMask(Fp.BITS));
337
- const right = Fp.add(Fp.pow(x, _3n), Fp.create(bls12_381_CURVE_G1.b)); // y² = x³ + b
338
- let y = Fp.sqrt(right);
339
- if (!y) throw new Error('invalid G1 point: compressed');
340
- const aflag = BigInt(sort);
341
- if ((y * _2n) / P !== aflag) y = Fp.neg(y);
342
- const point = Point.fromAffine({ x, y });
401
+ const point = Point.fromAffine(g1.sig.decode(bytes));
343
402
  point.assertValidity();
344
403
  return point;
345
404
  }
346
405
 
347
- function pointG2ToBytes(
348
- _c: WeierstrassPointCons<Fp2>,
349
- point: WeierstrassPoint<Fp2>,
350
- isComp: boolean
351
- ) {
352
- const { BYTES: L, ORDER: P } = Fp;
353
- const is0 = point.is0();
354
- const { x, y } = point.toAffine();
355
- if (isComp) {
356
- if (is0) return concatBytes(COMPZERO, numberToBytesBE(_0n, L));
357
- const flag = Boolean(y.c1 === _0n ? (y.c0 * _2n) / P : (y.c1 * _2n) / P);
358
- return concatBytes(
359
- setMask(numberToBytesBE(x.c1, L), { compressed: true, sort: flag }),
360
- numberToBytesBE(x.c0, L)
361
- );
362
- } else {
363
- if (is0) return concatBytes(Uint8Array.of(0x40), new Uint8Array(4 * L - 1));
364
- const { re: x0, im: x1 } = Fp2.reim(x);
365
- const { re: y0, im: y1 } = Fp2.reim(y);
366
- return concatBytes(
367
- numberToBytesBE(x1, L),
368
- numberToBytesBE(x0, L),
369
- numberToBytesBE(y1, L),
370
- numberToBytesBE(y0, L)
371
- );
372
- }
373
- }
374
-
375
- function signatureG2ToBytes(point: WeierstrassPoint<Fp2>) {
406
+ const g2coder = coder('G2', Fp2, bls12_381_CURVE_G2.b, fp2.encode, fp2.decode, (y: Fp2) => [
407
+ y.c1,
408
+ y.c0,
409
+ ]);
410
+ const g2 = { point: g2coder(true), sig: g2coder(false) };
411
+ const signatureG2ToBytes = (point: WeierstrassPoint<Fp2>): TRet<Uint8Array> => {
376
412
  point.assertValidity();
377
- const { BYTES: L } = Fp;
378
- if (point.is0()) return concatBytes(COMPZERO, numberToBytesBE(_0n, L));
379
- const { x, y } = point.toAffine();
380
- const { re: x0, im: x1 } = Fp2.reim(x);
381
- const { re: y0, im: y1 } = Fp2.reim(y);
382
- const tmp = y1 > _0n ? y1 * _2n : y0 * _2n;
383
- const sort = Boolean((tmp / Fp.ORDER) & _1n);
384
- const z2 = x0;
385
- return concatBytes(
386
- setMask(numberToBytesBE(x1, L), { sort, compressed: true }),
387
- numberToBytesBE(z2, L)
388
- );
389
- }
390
-
391
- function pointG2FromBytes(bytes: Uint8Array): AffinePoint<Fp2> {
392
- const { BYTES: L, ORDER: P } = Fp;
393
- const { compressed, infinity, sort, value } = parseMask(bytes);
394
- if (
395
- (!compressed && !infinity && sort) || // 00100000
396
- (!compressed && infinity && sort) || // 01100000
397
- (sort && infinity && compressed) // 11100000
398
- ) {
399
- throw new Error('invalid encoding flag: ' + (bytes[0] & 0b1110_0000));
400
- }
401
- const slc = (b: Uint8Array, from: number, to?: number) => bytesToNumberBE(b.slice(from, to));
402
- if (value.length === 96 && compressed) {
403
- if (infinity) {
404
- // check that all bytes are 0
405
- if (value.reduce((p, c) => (p !== 0 ? c + 1 : c), 0) > 0) {
406
- throw new Error('invalid G2 point: compressed');
407
- }
408
- return { x: Fp2.ZERO, y: Fp2.ZERO };
409
- }
410
- const x_1 = slc(value, 0, L);
411
- const x_0 = slc(value, L, 2 * L);
412
- const x = Fp2.create({ c0: Fp.create(x_0), c1: Fp.create(x_1) });
413
- const right = Fp2.add(Fp2.pow(x, _3n), bls12_381_CURVE_G2.b); // y² = x³ + 4 * (u+1) = x³ + b
414
- let y = Fp2.sqrt(right);
415
- const Y_bit = y.c1 === _0n ? (y.c0 * _2n) / P : (y.c1 * _2n) / P ? _1n : _0n;
416
- y = sort && Y_bit > 0 ? y : Fp2.neg(y);
417
- return { x, y };
418
- } else if (value.length === 192 && !compressed) {
419
- if (infinity) {
420
- if (value.reduce((p, c) => (p !== 0 ? c + 1 : c), 0) > 0) {
421
- throw new Error('invalid G2 point: uncompressed');
422
- }
423
- return { x: Fp2.ZERO, y: Fp2.ZERO };
424
- }
425
- const x1 = slc(value, 0 * L, 1 * L);
426
- const x0 = slc(value, 1 * L, 2 * L);
427
- const y1 = slc(value, 2 * L, 3 * L);
428
- const y0 = slc(value, 3 * L, 4 * L);
429
- return { x: Fp2.fromBigTuple([x0, x1]), y: Fp2.fromBigTuple([y0, y1]) };
430
- } else {
431
- throw new Error('invalid G2 point: expected 96/192 bytes');
432
- }
433
- }
434
-
435
- function signatureG2FromBytes(bytes: Uint8Array) {
436
- const { ORDER: P } = Fp;
437
- // TODO: Optimize, it's very slow because of sqrt.
438
- const { infinity, sort, value } = parseMask(abytes(bytes));
413
+ return g2.sig.encode(point);
414
+ };
415
+ function signatureG2FromBytes(bytes: TArg<Uint8Array>) {
439
416
  const Point = bls12_381.G2.Point;
440
- const half = value.length / 2;
441
- if (half !== 48 && half !== 96)
442
- throw new Error('invalid compressed signature length, expected 96/192 bytes');
443
- const z1 = bytesToNumberBE(value.slice(0, half));
444
- const z2 = bytesToNumberBE(value.slice(half));
445
- // Indicates the infinity point
446
- if (infinity) return Point.ZERO;
447
- const x1 = Fp.create(z1 & bitMask(Fp.BITS));
448
- const x2 = Fp.create(z2);
449
- const x = Fp2.create({ c0: x2, c1: x1 });
450
- const y2 = Fp2.add(Fp2.pow(x, _3n), bls12_381_CURVE_G2.b); // y² = x³ + 4
451
- // The slow part
452
- let y = Fp2.sqrt(y2);
453
- if (!y) throw new Error('Failed to find a square root');
454
-
455
- // Choose the y whose leftmost bit of the imaginary part is equal to the a_flag1
456
- // If y1 happens to be zero, then use the bit of y0
457
- const { re: y0, im: y1 } = Fp2.reim(y);
458
- const aflag1 = BigInt(sort);
459
- const isGreater = y1 > _0n && (y1 * _2n) / P !== aflag1;
460
- const is0 = y1 === _0n && (y0 * _2n) / P !== aflag1;
461
- if (isGreater || is0) y = Fp2.neg(y);
462
- const point = Point.fromAffine({ x, y });
417
+ const point = Point.fromAffine(g2.sig.decode(bytes));
463
418
  point.assertValidity();
464
419
  return point;
465
420
  }
466
421
 
467
422
  const signatureCoders = {
468
423
  ShortSignature: {
469
- fromBytes(bytes: Uint8Array) {
424
+ fromBytes(bytes: TArg<Uint8Array>) {
470
425
  return signatureG1FromBytes(abytes(bytes));
471
426
  },
472
427
  fromHex(hex: string): WeierstrassPoint<Fp> {
@@ -475,6 +430,7 @@ const signatureCoders = {
475
430
  toBytes(point: WeierstrassPoint<Fp>) {
476
431
  return signatureG1ToBytes(point);
477
432
  },
433
+ // Historical alias: BLS signatures have a single compressed byte format here.
478
434
  toRawBytes(point: WeierstrassPoint<Fp>) {
479
435
  return signatureG1ToBytes(point);
480
436
  },
@@ -483,7 +439,7 @@ const signatureCoders = {
483
439
  },
484
440
  },
485
441
  LongSignature: {
486
- fromBytes(bytes: Uint8Array): WeierstrassPoint<Fp2> {
442
+ fromBytes(bytes: TArg<Uint8Array>): WeierstrassPoint<Fp2> {
487
443
  return signatureG2FromBytes(abytes(bytes));
488
444
  },
489
445
  fromHex(hex: string): WeierstrassPoint<Fp2> {
@@ -492,6 +448,7 @@ const signatureCoders = {
492
448
  toBytes(point: WeierstrassPoint<Fp2>) {
493
449
  return signatureG2ToBytes(point);
494
450
  },
451
+ // Historical alias: BLS signatures have a single compressed byte format here.
495
452
  toRawBytes(point: WeierstrassPoint<Fp2>) {
496
453
  return signatureG2ToBytes(point);
497
454
  },
@@ -509,10 +466,16 @@ const fields = {
509
466
  Fr: bls12_381_Fr,
510
467
  };
511
468
  const G1_Point = weierstrass(bls12_381_CURVE_G1, {
469
+ // Public point APIs still accept infinity, even though the Zcash proof
470
+ // encoding rules cited above only define nonzero point encodings.
512
471
  allowInfinityPoint: true,
513
472
  Fn: bls12_381_Fr,
514
- fromBytes: pointG1FromBytes,
515
- toBytes: pointG1ToBytes,
473
+ fromBytes: g1.point.decode,
474
+ toBytes: (
475
+ _c: WeierstrassPointCons<Fp>,
476
+ point: WeierstrassPoint<Fp>,
477
+ isComp: boolean
478
+ ): TRet<Uint8Array> => g1.point.encode(point, isComp) as TRet<Uint8Array>,
516
479
  // Checks is the point resides in prime-order subgroup.
517
480
  // point.isTorsionFree() should return true for valid points
518
481
  // It returns false for shitty points.
@@ -537,10 +500,16 @@ const G1_Point = weierstrass(bls12_381_CURVE_G1, {
537
500
  });
538
501
  const G2_Point = weierstrass(bls12_381_CURVE_G2, {
539
502
  Fp: Fp2,
503
+ // Public point APIs still accept infinity, even though the Zcash proof
504
+ // encoding rules cited above only define nonzero point encodings.
540
505
  allowInfinityPoint: true,
541
506
  Fn: bls12_381_Fr,
542
- fromBytes: pointG2FromBytes,
543
- toBytes: pointG2ToBytes,
507
+ fromBytes: g2.point.decode,
508
+ toBytes: (
509
+ _c: WeierstrassPointCons<Fp2>,
510
+ point: WeierstrassPoint<Fp2>,
511
+ isComp: boolean
512
+ ): TRet<Uint8Array> => g2.point.encode(point, isComp) as TRet<Uint8Array>,
544
513
  // https://eprint.iacr.org/2021/1130.pdf
545
514
  // Older version: https://eprint.iacr.org/2019/814.pdf
546
515
  isTorsionFree: (c, P): boolean => {
@@ -569,7 +538,15 @@ const bls12_hasher_opts = {
569
538
  mapToG1: mapToG1,
570
539
  mapToG2: mapToG2,
571
540
  hasherOpts: hasher_opts,
572
- hasherOptsG1: { ...hasher_opts, m: 1, DST: 'BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_' },
541
+ // RFC 9380 Appendix J defines distinct G1/G2 RO and NU suite IDs, and
542
+ // draft-irtf-cfrg-bls-signature-06 §4.2.1 gives separate G1/G2 `_NUL_` DSTs.
543
+ // Keep G1 encode-to-curve on the G1 domain instead of inheriting G2's `encodeDST`.
544
+ hasherOptsG1: {
545
+ ...hasher_opts,
546
+ m: 1,
547
+ DST: 'BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_',
548
+ encodeDST: 'BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_',
549
+ },
573
550
  hasherOptsG2: { ...hasher_opts },
574
551
  } as const;
575
552
 
@@ -583,6 +560,16 @@ const bls12_params = {
583
560
  /**
584
561
  * bls12-381 pairing-friendly curve construction.
585
562
  * Provides both longSignatures and shortSignatures.
563
+ * @example
564
+ * bls12-381 pairing-friendly curve construction.
565
+ *
566
+ * ```ts
567
+ * const bls = bls12_381.longSignatures;
568
+ * const { secretKey, publicKey } = bls.keygen();
569
+ * const msg = bls.hash(new TextEncoder().encode('hello noble'));
570
+ * const sig = bls.sign(msg, secretKey);
571
+ * const isValid = bls.verify(sig, msg, publicKey);
572
+ * ```
586
573
  */
587
574
  export const bls12_381: BlsCurvePairWithSignatures = bls(
588
575
  fields,
@@ -594,6 +581,8 @@ export const bls12_381: BlsCurvePairWithSignatures = bls(
594
581
  );
595
582
 
596
583
  // 3-isogeny map from E' to E https://www.rfc-editor.org/rfc/rfc9380#appendix-E.3
584
+ // Coefficients stay in ascending `k_(?,0)`..`k_(?,d)` order; isogenyMap()
585
+ // reverses them internally for Horner evaluation.
597
586
  const isogenyMapG2 = isogenyMap(
598
587
  Fp2,
599
588
  [
@@ -670,7 +659,8 @@ const isogenyMapG2 = isogenyMap(
670
659
  Fp2[],
671
660
  ]
672
661
  );
673
- // 11-isogeny map from E' to E
662
+ // 11-isogeny map from E' to E. Coefficients stay in ascending
663
+ // `k_(?,0)`..`k_(?,d)` order; isogenyMap() reverses them for Horner evaluation.
674
664
  const isogenyMapG1 = isogenyMap(
675
665
  Fp,
676
666
  [
@@ -744,32 +734,45 @@ const isogenyMapG1 = isogenyMap(
744
734
  ].map((i) => i.map((j) => BigInt(j))) as [Fp[], Fp[], Fp[], Fp[]]
745
735
  );
746
736
 
747
- // Optimized SWU Map - Fp to G1
748
- const G1_SWU = mapToCurveSimpleSWU(Fp, {
749
- A: Fp.create(
750
- BigInt(
751
- '0x144698a3b8e9433d693a02c96d4982b0ea985383ee66a8d8e8981aefd881ac98936f8da0e0f97f5cf428082d584c1d'
752
- )
753
- ),
754
- B: Fp.create(
755
- BigInt(
756
- '0x12e2908d11688030018b12e8753eee3b2016c1f0f24f4070a0b9c14fcef35ef55a23215a316ceaa5d1cc48e98e172be0'
757
- )
758
- ),
759
- Z: Fp.create(BigInt(11)),
760
- });
761
- // SWU Map - Fp2 to G2': y² = x³ + 240i * x + 1012 + 1012i
762
- const G2_SWU = mapToCurveSimpleSWU(Fp2, {
763
- A: Fp2.create({ c0: Fp.create(_0n), c1: Fp.create(BigInt(240)) }), // A' = 240 * I
764
- B: Fp2.create({ c0: Fp.create(BigInt(1012)), c1: Fp.create(BigInt(1012)) }), // B' = 1012 * (1 + I)
765
- Z: Fp2.create({ c0: Fp.create(BigInt(-2)), c1: Fp.create(BigInt(-1)) }), // Z: -(2 + I)
766
- });
767
-
737
+ let G1_SWU: ((u: bigint) => { x: bigint; y: bigint }) | undefined;
738
+ let G2_SWU: ((u: Fp2) => { x: Fp2; y: Fp2 }) | undefined;
739
+ // SWU setup validates the pre-isogeny curve parameters and builds sqrt-ratio helpers.
740
+ // Doing that eagerly adds about 10ms to `bls12-381.js` import here, so keep it lazy; after the
741
+ // first map call the cached mapper is reused directly.
742
+ const getG1_SWU = () =>
743
+ G1_SWU ||
744
+ (G1_SWU = mapToCurveSimpleSWU(Fp, {
745
+ A: Fp.create(
746
+ BigInt(
747
+ '0x144698a3b8e9433d693a02c96d4982b0ea985383ee66a8d8e8981aefd881ac98936f8da0e0f97f5cf428082d584c1d'
748
+ )
749
+ ),
750
+ B: Fp.create(
751
+ BigInt(
752
+ '0x12e2908d11688030018b12e8753eee3b2016c1f0f24f4070a0b9c14fcef35ef55a23215a316ceaa5d1cc48e98e172be0'
753
+ )
754
+ ),
755
+ Z: Fp.create(BigInt(11)),
756
+ }));
757
+ const getG2_SWU = () =>
758
+ G2_SWU ||
759
+ (G2_SWU = mapToCurveSimpleSWU(Fp2, {
760
+ // SWU map for the RFC 9380 §8.8.2 pre-isogeny G2 curve E':
761
+ // y² = x³ + 240i * x + 1012 + 1012i
762
+ A: Fp2.create({ c0: Fp.create(_0n), c1: Fp.create(BigInt(240)) }), // A' = 240 * I
763
+ B: Fp2.create({ c0: Fp.create(BigInt(1012)), c1: Fp.create(BigInt(1012)) }), // B' = 1012 * (1 + I)
764
+ Z: Fp2.create({ c0: Fp.create(BigInt(-2)), c1: Fp.create(BigInt(-1)) }), // Z: -(2 + I)
765
+ }));
766
+
767
+ // Internal hash-to-curve step: G1 uses `m = 1`, so only `scalars[0]` is read,
768
+ // and the result is the isogeny image on E before the subgroup clear.
768
769
  function mapToG1(scalars: bigint[]) {
769
- const { x, y } = G1_SWU(Fp.create(scalars[0]));
770
+ const { x, y } = getG1_SWU()(Fp.create(scalars[0]));
770
771
  return isogenyMapG1(x, y);
771
772
  }
773
+ // Internal hash-to-curve step: G2 expects the RFC `m = 2` pair, and the result
774
+ // is the isogeny image on E before the subgroup clear.
772
775
  function mapToG2(scalars: bigint[]) {
773
- const { x, y } = G2_SWU(Fp2.fromBigTuple(scalars as BigintTuple));
776
+ const { x, y } = getG2_SWU()(Fp2.fromBigTuple(scalars as BigintTuple));
774
777
  return isogenyMapG2(x, y);
775
778
  }