@noble/curves 0.9.1 → 1.0.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 (87) hide show
  1. package/README.md +197 -99
  2. package/abstract/bls.d.ts +43 -31
  3. package/abstract/bls.d.ts.map +1 -1
  4. package/abstract/bls.js +37 -28
  5. package/abstract/bls.js.map +1 -1
  6. package/abstract/edwards.d.ts +2 -2
  7. package/abstract/edwards.d.ts.map +1 -1
  8. package/abstract/edwards.js +30 -18
  9. package/abstract/edwards.js.map +1 -1
  10. package/abstract/hash-to-curve.d.ts +1 -1
  11. package/abstract/hash-to-curve.d.ts.map +1 -1
  12. package/abstract/hash-to-curve.js +3 -2
  13. package/abstract/hash-to-curve.js.map +1 -1
  14. package/abstract/utils.d.ts.map +1 -1
  15. package/abstract/utils.js +2 -2
  16. package/abstract/utils.js.map +1 -1
  17. package/abstract/weierstrass.d.ts +21 -0
  18. package/abstract/weierstrass.d.ts.map +1 -1
  19. package/abstract/weierstrass.js +27 -14
  20. package/abstract/weierstrass.js.map +1 -1
  21. package/bls12-381.d.ts.map +1 -1
  22. package/bls12-381.js +57 -61
  23. package/bls12-381.js.map +1 -1
  24. package/{bn.d.ts → bn254.d.ts} +1 -1
  25. package/bn254.d.ts.map +1 -0
  26. package/{bn.js → bn254.js} +1 -1
  27. package/bn254.js.map +1 -0
  28. package/ed25519.d.ts +9 -0
  29. package/ed25519.d.ts.map +1 -1
  30. package/ed25519.js +22 -8
  31. package/ed25519.js.map +1 -1
  32. package/ed448.d.ts +9 -0
  33. package/ed448.d.ts.map +1 -1
  34. package/ed448.js +16 -16
  35. package/ed448.js.map +1 -1
  36. package/esm/abstract/bls.js +37 -28
  37. package/esm/abstract/bls.js.map +1 -1
  38. package/esm/abstract/edwards.js +30 -18
  39. package/esm/abstract/edwards.js.map +1 -1
  40. package/esm/abstract/hash-to-curve.js +3 -2
  41. package/esm/abstract/hash-to-curve.js.map +1 -1
  42. package/esm/abstract/utils.js +2 -2
  43. package/esm/abstract/utils.js.map +1 -1
  44. package/esm/abstract/weierstrass.js +19 -6
  45. package/esm/abstract/weierstrass.js.map +1 -1
  46. package/esm/bls12-381.js +58 -62
  47. package/esm/bls12-381.js.map +1 -1
  48. package/esm/{bn.js → bn254.js} +1 -1
  49. package/esm/bn254.js.map +1 -0
  50. package/esm/ed25519.js +20 -7
  51. package/esm/ed25519.js.map +1 -1
  52. package/esm/ed448.js +14 -15
  53. package/esm/ed448.js.map +1 -1
  54. package/esm/p256.js +5 -6
  55. package/esm/p256.js.map +1 -1
  56. package/esm/p384.js +10 -12
  57. package/esm/p384.js.map +1 -1
  58. package/esm/p521.js +22 -18
  59. package/esm/p521.js.map +1 -1
  60. package/p256.d.ts +1 -1
  61. package/p256.d.ts.map +1 -1
  62. package/p256.js +6 -7
  63. package/p256.js.map +1 -1
  64. package/p384.d.ts +1 -1
  65. package/p384.d.ts.map +1 -1
  66. package/p384.js +11 -13
  67. package/p384.js.map +1 -1
  68. package/p521.d.ts +1 -1
  69. package/p521.d.ts.map +1 -1
  70. package/p521.js +23 -19
  71. package/p521.js.map +1 -1
  72. package/package.json +5 -8
  73. package/src/abstract/bls.ts +83 -61
  74. package/src/abstract/edwards.ts +38 -16
  75. package/src/abstract/hash-to-curve.ts +4 -3
  76. package/src/abstract/utils.ts +2 -2
  77. package/src/abstract/weierstrass.ts +18 -7
  78. package/src/bls12-381.ts +63 -67
  79. package/src/ed25519.ts +22 -8
  80. package/src/ed448.ts +15 -15
  81. package/src/p256.ts +15 -19
  82. package/src/p384.ts +17 -21
  83. package/src/p521.ts +34 -22
  84. package/bn.d.ts.map +0 -1
  85. package/bn.js.map +0 -1
  86. package/esm/bn.js.map +0 -1
  87. /package/src/{bn.ts → bn254.ts} +0 -0
@@ -18,10 +18,13 @@ export type CurveType = BasicCurve<bigint> & {
18
18
  adjustScalarBytes?: (bytes: Uint8Array) => Uint8Array; // clears bits to get valid field elemtn
19
19
  domain?: (data: Uint8Array, ctx: Uint8Array, phflag: boolean) => Uint8Array; // Used for hashing
20
20
  uvRatio?: (u: bigint, v: bigint) => { isValid: boolean; value: bigint }; // Ratio √(u/v)
21
- preHash?: FHash; // RFC 8032 pre-hashing of messages to sign() / verify()
21
+ prehash?: FHash; // RFC 8032 pre-hashing of messages to sign() / verify()
22
22
  mapToCurve?: (scalar: bigint[]) => AffinePoint<bigint>; // for hash-to-curve standard
23
23
  };
24
24
 
25
+ // verification rule is either zip215 or rfc8032 / nist186-5. Consult fromHex:
26
+ const VERIFY_DEFAULT = { zip215: true };
27
+
25
28
  function validateOpts(curve: CurveType) {
26
29
  const opts = validateBasic(curve);
27
30
  ut.validateObject(
@@ -90,7 +93,15 @@ export type CurveFn = {
90
93
  // It is not generic twisted curve for now, but ed25519/ed448 generic implementation
91
94
  export function twistedEdwards(curveDef: CurveType): CurveFn {
92
95
  const CURVE = validateOpts(curveDef) as ReturnType<typeof validateOpts>;
93
- const { Fp, n: CURVE_ORDER, preHash, hash: cHash, randomBytes, nByteLength, h: cofactor } = CURVE;
96
+ const {
97
+ Fp,
98
+ n: CURVE_ORDER,
99
+ prehash: prehash,
100
+ hash: cHash,
101
+ randomBytes,
102
+ nByteLength,
103
+ h: cofactor,
104
+ } = CURVE;
94
105
  const MASK = _2n ** BigInt(nByteLength * 8);
95
106
  const modP = Fp.create; // Function overrides
96
107
 
@@ -344,7 +355,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
344
355
 
345
356
  // Converts hash string or Uint8Array to Point.
346
357
  // Uses algo from RFC8032 5.1.3.
347
- static fromHex(hex: Hex, strict = true): Point {
358
+ static fromHex(hex: Hex, zip215 = false): Point {
348
359
  const { d, a } = CURVE;
349
360
  const len = Fp.BYTES;
350
361
  hex = ensureBytes('pointHex', hex, len); // copy hex to a new array
@@ -356,8 +367,8 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
356
367
  // y=0 is allowed
357
368
  } else {
358
369
  // RFC8032 prohibits >= p, but ZIP215 doesn't
359
- if (strict) assertInRange(y, Fp.ORDER); // strict=true [1..P-1] (2^255-19-1 for ed25519)
360
- else assertInRange(y, MASK); // strict=false [1..MASK-1] (2^256-1 for ed25519)
370
+ if (zip215) assertInRange(y, MASK); // zip215=true [1..P-1] (2^255-19-1 for ed25519)
371
+ else assertInRange(y, Fp.ORDER); // zip215=false [1..MASK-1] (2^256-1 for ed25519)
361
372
  }
362
373
 
363
374
  // Ed25519: x² = (y²-1)/(dy²+1) mod p. Ed448: x² = (y²-1)/(dy²-1) mod p. Generic case:
@@ -419,32 +430,43 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
419
430
  // int('LE', SHA512(dom2(F, C) || msgs)) mod N
420
431
  function hashDomainToScalar(context: Hex = new Uint8Array(), ...msgs: Uint8Array[]) {
421
432
  const msg = ut.concatBytes(...msgs);
422
- return modN_LE(cHash(domain(msg, ensureBytes('context', context), !!preHash)));
433
+ return modN_LE(cHash(domain(msg, ensureBytes('context', context), !!prehash)));
423
434
  }
424
435
 
425
436
  /** Signs message with privateKey. RFC8032 5.1.6 */
426
- function sign(msg: Hex, privKey: Hex, context?: Hex): Uint8Array {
437
+ function sign(msg: Hex, privKey: Hex, options: { context?: Hex } = {}): Uint8Array {
427
438
  msg = ensureBytes('message', msg);
428
- if (preHash) msg = preHash(msg); // for ed25519ph etc.
439
+ if (prehash) msg = prehash(msg); // for ed25519ph etc.
429
440
  const { prefix, scalar, pointBytes } = getExtendedPublicKey(privKey);
430
- const r = hashDomainToScalar(context, prefix, msg); // r = dom2(F, C) || prefix || PH(M)
441
+ const r = hashDomainToScalar(options.context, prefix, msg); // r = dom2(F, C) || prefix || PH(M)
431
442
  const R = G.multiply(r).toRawBytes(); // R = rG
432
- const k = hashDomainToScalar(context, R, pointBytes, msg); // R || A || PH(M)
443
+ const k = hashDomainToScalar(options.context, R, pointBytes, msg); // R || A || PH(M)
433
444
  const s = modN(r + k * scalar); // S = (r + k * s) mod L
434
445
  assertGE0(s); // 0 <= s < l
435
446
  const res = ut.concatBytes(R, ut.numberToBytesLE(s, Fp.BYTES));
436
447
  return ensureBytes('result', res, nByteLength * 2); // 64-byte signature
437
448
  }
438
449
 
439
- function verify(sig: Hex, msg: Hex, publicKey: Hex, context?: Hex): boolean {
450
+ const verifyOpts: { context?: Hex; zip215?: boolean } = VERIFY_DEFAULT;
451
+ function verify(sig: Hex, msg: Hex, publicKey: Hex, options = verifyOpts): boolean {
452
+ const { context, zip215 } = options;
440
453
  const len = Fp.BYTES; // Verifies EdDSA signature against message and public key. RFC8032 5.1.7.
441
454
  sig = ensureBytes('signature', sig, 2 * len); // An extended group equation is checked.
442
- msg = ensureBytes('message', msg); // ZIP215 compliant, which means not fully RFC8032 compliant.
443
- if (preHash) msg = preHash(msg); // for ed25519ph, etc
444
- const A = Point.fromHex(publicKey, false); // Check for s bounds, hex validity
445
- const R = Point.fromHex(sig.slice(0, len), false); // 0 <= R < 2^256: ZIP215 R can be >= P
455
+ msg = ensureBytes('message', msg);
456
+ if (prehash) msg = prehash(msg); // for ed25519ph, etc
457
+
446
458
  const s = ut.bytesToNumberLE(sig.slice(len, 2 * len));
447
- const SB = G.multiplyUnsafe(s); // 0 <= s < l is done inside
459
+ // zip215: true is good for consensus-critical apps and allows points < 2^256
460
+ // zip215: false follows RFC8032 / NIST186-5 and restricts points to CURVE.p
461
+ let A, R, SB;
462
+ try {
463
+ A = Point.fromHex(publicKey, zip215);
464
+ R = Point.fromHex(sig.slice(0, len), zip215);
465
+ SB = G.multiplyUnsafe(s); // 0 <= s < l is done inside
466
+ } catch (error) {
467
+ return false;
468
+ }
469
+
448
470
  const k = hashDomainToScalar(context, R.toRawBytes(), A.toRawBytes(), msg);
449
471
  const RkA = R.add(A.multiplyUnsafe(k));
450
472
  // [8][S]B = [8]R + [8][k]A'
@@ -17,7 +17,7 @@ export type Opts = {
17
17
  p: bigint;
18
18
  m: number;
19
19
  k: number;
20
- expand?: 'xmd' | 'xof';
20
+ expand: 'xmd' | 'xof';
21
21
  hash: CHash;
22
22
  };
23
23
 
@@ -145,10 +145,11 @@ export function hash_to_field(msg: Uint8Array, count: number, options: Opts): bi
145
145
  prb = expand_message_xmd(msg, DST, len_in_bytes, hash);
146
146
  } else if (expand === 'xof') {
147
147
  prb = expand_message_xof(msg, DST, len_in_bytes, k, hash);
148
- } else if (expand === undefined) {
148
+ } else if (expand === '_internal_pass') {
149
+ // for internal tests only
149
150
  prb = msg;
150
151
  } else {
151
- throw new Error('expand must be "xmd", "xof" or undefined');
152
+ throw new Error('expand must be "xmd" or "xof"');
152
153
  }
153
154
  const u = new Array(count);
154
155
  for (let i = 0; i < count; i++) {
@@ -123,12 +123,12 @@ export function utf8ToBytes(str: string): Uint8Array {
123
123
  // Amount of bits inside bigint (Same as n.toString(2).length)
124
124
  export function bitLen(n: bigint) {
125
125
  let len;
126
- for (len = 0; n > 0n; n >>= _1n, len += 1);
126
+ for (len = 0; n > _0n; n >>= _1n, len += 1);
127
127
  return len;
128
128
  }
129
129
  // Gets single bit at position. NOTE: first bit position is 0 (same as arrays)
130
130
  // Same as !!+Array.from(n.toString(2)).reverse()[pos]
131
- export const bitGet = (n: bigint, pos: number) => (n >> BigInt(pos)) & 1n;
131
+ export const bitGet = (n: bigint, pos: number) => (n >> BigInt(pos)) & _1n;
132
132
  // Sets single bit at position
133
133
  export const bitSet = (n: bigint, pos: number, value: boolean) =>
134
134
  n | ((value ? _1n : _0n) << BigInt(pos));
@@ -131,7 +131,7 @@ export type CurvePointsRes<T> = {
131
131
 
132
132
  // ASN.1 DER encoding utilities
133
133
  const { bytesToNumberBE: b2n, hexToBytes: h2b } = ut;
134
- const DER = {
134
+ export const DER = {
135
135
  // asn.1 DER encoding utils
136
136
  Err: class DERErr extends Error {
137
137
  constructor(m = '') {
@@ -144,9 +144,13 @@ const DER = {
144
144
  const len = data[1];
145
145
  const res = data.subarray(2, len + 2);
146
146
  if (!len || res.length !== len) throw new E('Invalid signature integer: wrong length');
147
- if (res[0] === 0x00 && res[1] <= 0x7f)
148
- throw new E('Invalid signature integer: trailing length');
149
- // ^ Weird condition: not about length, but about first bytes of number.
147
+ // https://crypto.stackexchange.com/a/57734 Leftmost bit of first byte is 'negative' flag,
148
+ // since we always use positive integers here. It must always be empty:
149
+ // - add zero byte if exists
150
+ // - if next byte doesn't have a flag, leading zero is not allowed (minimal encoding)
151
+ if (res[0] & 0b10000000) throw new E('Invalid signature integer: negative');
152
+ if (res[0] === 0x00 && !(res[1] & 0b10000000))
153
+ throw new E('Invalid signature integer: unnecessary leading zero');
150
154
  return { d: b2n(res), l: data.subarray(len + 2) }; // d is data, l is left
151
155
  },
152
156
  toSig(hex: string | Uint8Array): { r: bigint; s: bigint } {
@@ -163,7 +167,8 @@ const DER = {
163
167
  return { r, s };
164
168
  },
165
169
  hexFromSig(sig: { r: bigint; s: bigint }): string {
166
- const slice = (s: string): string => (Number.parseInt(s[0], 16) >= 8 ? '00' + s : s); // slice DER
170
+ // Add leading zero if first byte has negative bit enabled. More details in '_parseInt'
171
+ const slice = (s: string): string => (Number.parseInt(s[0], 16) & 0b1000 ? '00' + s : s);
167
172
  const h = (num: number | bigint) => {
168
173
  const hex = num.toString(16);
169
174
  return hex.length & 1 ? `0${hex}` : hex;
@@ -213,6 +218,12 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
213
218
  const x3 = Fp.mul(x2, x); // x2 * x
214
219
  return Fp.add(Fp.add(x3, Fp.mul(x, a)), b); // x3 + a * x + b
215
220
  }
221
+ // Validate whether the passed curve params are valid.
222
+ // We check if curve equation works for generator point.
223
+ // `assertValidity()` won't work: `isTorsionFree()` is not available at this point in bls12-381.
224
+ // ProjectivePoint class has not been initialized yet.
225
+ if (!Fp.eql(Fp.sqr(CURVE.Gy), weierstrassEquation(CURVE.Gx)))
226
+ throw new Error('bad generator point: equation left != right');
216
227
 
217
228
  // Valid group elements reside in range 1..n-1
218
229
  function isWithinCurveOrder(num: bigint): boolean {
@@ -591,7 +602,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
591
602
  }
592
603
  const _bits = CURVE.nBitLength;
593
604
  const wnaf = wNAF(Point, CURVE.endo ? Math.ceil(_bits / 2) : _bits);
594
-
605
+ // Validate if generator point is on curve
595
606
  return {
596
607
  CURVE,
597
608
  ProjectivePoint: Point as ProjConstructor<T>,
@@ -1107,7 +1118,7 @@ export function SWUFpSqrtRatio<T>(Fp: mod.IField<T>, Z: T) {
1107
1118
  tv3 = Fp.cmov(tv2, tv3, isQR); // 15. tv3 = CMOV(tv2, tv3, isQR)
1108
1119
  tv4 = Fp.cmov(tv5, tv4, isQR); // 16. tv4 = CMOV(tv5, tv4, isQR)
1109
1120
  // 17. for i in (c1, c1 - 1, ..., 2):
1110
- for (let i = c1; i > 1; i--) {
1121
+ for (let i = c1; i > _1n; i--) {
1111
1122
  let tv5 = _2n ** (i - _2n); // 18. tv5 = i - 2; 19. tv5 = 2^tv5
1112
1123
  let tvv5 = Fp.pow(tv4, tv5); // 20. tv5 = tv4^tv5
1113
1124
  const e1 = Fp.eql(tvv5, Fp.ONE); // 21. e1 = tv5 == 1
package/src/bls12-381.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  //
8
8
  // The library uses G1 for public keys and G2 for signatures. Support for G1 signatures is planned.
9
9
  // Compatible with Algorand, Chia, Dfinity, Ethereum, FIL, Zcash. Matches specs
10
- // [pairing-curves-10](https://tools.ietf.org/html/draft-irtf-cfrg-pairing-friendly-curves-10),
10
+ // [pairing-curves-11](https://tools.ietf.org/html/draft-irtf-cfrg-pairing-friendly-curves-11),
11
11
  // [bls-sigs-04](https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-04),
12
12
  // [hash-to-curve-12](https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-12).
13
13
  //
@@ -27,24 +27,6 @@
27
27
  // - `e(G, S) = e(G, SUM(n)(Si)) = MUL(n)(e(G, Si))` - signature aggregation
28
28
  // Filecoin uses little endian byte arrays for private keys -
29
29
  // so ensure to reverse byte order if you'll use it with FIL.
30
- //
31
- // ### Resources
32
- // - [BLS12-381 for the rest of us](https://hackmd.io/@benjaminion/bls12-381)
33
- // - [Key concepts of pairings](https://medium.com/@alonmuroch_65570/bls-signatures-part-2-key-concepts-of-pairings-27a8a9533d0c)
34
- // - Pairing over bls12-381:
35
- // [part 1](https://research.nccgroup.com/2020/07/06/pairing-over-bls12-381-part-1-fields/),
36
- // [part 2](https://research.nccgroup.com/2020/07/13/pairing-over-bls12-381-part-2-curves/),
37
- // [part 3](https://research.nccgroup.com/2020/08/13/pairing-over-bls12-381-part-3-pairing/)
38
- // - [Estimating the bit security of pairing-friendly curves](https://research.nccgroup.com/2022/02/03/estimating-the-bit-security-of-pairing-friendly-curves/)
39
- //
40
- // ### Differences from @noble/bls12-381 1.4
41
- // - PointG1 -> G1.Point
42
- // - PointG2 -> G2.Point
43
- // - PointG2.fromSignature -> Signature.decode
44
- // - PointG2.toSignature -> Signature.encode
45
- // - Fixed Fp2 ORDER
46
- // - Points now have only two coordinates
47
-
48
30
  import { sha256 } from '@noble/hashes/sha256';
49
31
  import { randomBytes } from '@noble/hashes/utils';
50
32
  import { bls, CurveFn } from './abstract/bls.js';
@@ -59,6 +41,7 @@ import {
59
41
  bitGet,
60
42
  Hex,
61
43
  bitMask,
44
+ bytesToHex,
62
45
  } from './abstract/utils.js';
63
46
  // Types
64
47
  import {
@@ -72,8 +55,8 @@ import { isogenyMap } from './abstract/hash-to-curve.js';
72
55
  // Be friendly to bad ECMAScript parsers by not using bigint literals
73
56
  // prettier-ignore
74
57
  const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3), _4n = BigInt(4);
75
- const _8n = BigInt(8),
76
- _16n = BigInt(16);
58
+ // prettier-ignore
59
+ const _8n = BigInt(8), _16n = BigInt(16);
77
60
 
78
61
  // CURVE FIELDS
79
62
  // Finite field over p.
@@ -950,9 +933,9 @@ const isogenyMapG1 = isogenyMap(
950
933
 
951
934
  // SWU Map - Fp2 to G2': y² = x³ + 240i * x + 1012 + 1012i
952
935
  const G2_SWU = mapToCurveSimpleSWU(Fp2, {
953
- A: Fp2.create({ c0: Fp.create(_0n), c1: Fp.create(240n) }), // A' = 240 * I
954
- B: Fp2.create({ c0: Fp.create(1012n), c1: Fp.create(1012n) }), // B' = 1012 * (1 + I)
955
- Z: Fp2.create({ c0: Fp.create(-2n), c1: Fp.create(-1n) }), // Z: -(2 + I)
936
+ A: Fp2.create({ c0: Fp.create(_0n), c1: Fp.create(BigInt(240)) }), // A' = 240 * I
937
+ B: Fp2.create({ c0: Fp.create(BigInt(1012)), c1: Fp.create(BigInt(1012)) }), // B' = 1012 * (1 + I)
938
+ Z: Fp2.create({ c0: Fp.create(BigInt(-2)), c1: Fp.create(BigInt(-1)) }), // Z: -(2 + I)
956
939
  });
957
940
  // Optimized SWU Map - Fp to G1
958
941
  const G1_SWU = mapToCurveSimpleSWU(Fp, {
@@ -966,7 +949,7 @@ const G1_SWU = mapToCurveSimpleSWU(Fp, {
966
949
  '0x12e2908d11688030018b12e8753eee3b2016c1f0f24f4070a0b9c14fcef35ef55a23215a316ceaa5d1cc48e98e172be0'
967
950
  )
968
951
  ),
969
- Z: Fp.create(11n),
952
+ Z: Fp.create(BigInt(11)),
970
953
  });
971
954
 
972
955
  // Endomorphisms (for fast cofactor clearing)
@@ -1042,7 +1025,23 @@ const C_BIT_POS = Fp.BITS; // C_bit, compression bit for serialization flag
1042
1025
  const I_BIT_POS = Fp.BITS + 1; // I_bit, point-at-infinity bit for serialization flag
1043
1026
  const S_BIT_POS = Fp.BITS + 2; // S_bit, sign bit for serialization flag
1044
1027
  // Compressed point of infinity
1045
- const COMPRESSED_ZERO = Fp.toBytes(bitSet(bitSet(0n, I_BIT_POS, true), S_BIT_POS, true)); // set compressed & point-at-infinity bits
1028
+ const COMPRESSED_ZERO = Fp.toBytes(bitSet(bitSet(_0n, I_BIT_POS, true), S_BIT_POS, true)); // set compressed & point-at-infinity bits
1029
+
1030
+ function signatureG2ToRawBytes(point: ProjPointType<Fp2>) {
1031
+ // NOTE: by some reasons it was missed in bls12-381, looks like bug
1032
+ point.assertValidity();
1033
+ const len = Fp.BYTES;
1034
+ if (point.equals(bls12_381.G2.ProjectivePoint.ZERO))
1035
+ return concatB(COMPRESSED_ZERO, numberToBytesBE(_0n, len));
1036
+ const { x, y } = point.toAffine();
1037
+ const { re: x0, im: x1 } = Fp2.reim(x);
1038
+ const { re: y0, im: y1 } = Fp2.reim(y);
1039
+ const tmp = y1 > _0n ? y1 * _2n : y0 * _2n;
1040
+ const aflag1 = Boolean((tmp / Fp.ORDER) & _1n);
1041
+ const z1 = bitSet(bitSet(x1, 381, aflag1), S_BIT_POS, true);
1042
+ const z2 = x0;
1043
+ return concatB(numberToBytesBE(z1, len), numberToBytesBE(z2, len));
1044
+ }
1046
1045
 
1047
1046
  // To verify curve parameters, see pairing-friendly-curves spec:
1048
1047
  // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-pairing-friendly-curves-09
@@ -1056,13 +1055,13 @@ const COMPRESSED_ZERO = Fp.toBytes(bitSet(bitSet(0n, I_BIT_POS, true), S_BIT_POS
1056
1055
  // Here goes constants && point encoding format
1057
1056
  export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
1058
1057
  // Fields
1059
- Fr,
1060
- Fp,
1061
- Fp2,
1062
- Fp6,
1063
- Fp12,
1064
- // order; z⁴ − z² + 1
1065
- r: Fr.ORDER, // Same as N in other curves
1058
+ fields: {
1059
+ Fp,
1060
+ Fp2,
1061
+ Fp6,
1062
+ Fp12,
1063
+ Fr,
1064
+ },
1066
1065
  // G1 is the order-q subgroup of E1(Fp) : y² = x³ + 4, #E1(Fp) = h1q, where
1067
1066
  // characteristic; z + (z⁴ - z² + 1)(z - 1)²/3
1068
1067
  G1: {
@@ -1095,8 +1094,8 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
1095
1094
  const phi = new c(Fp.mul(point.px, cubicRootOfUnityModP), point.py, point.pz);
1096
1095
 
1097
1096
  // todo: unroll
1098
- const xP = point.multiplyUnsafe(bls12_381.CURVE.x).negate(); // [x]P
1099
- const u2P = xP.multiplyUnsafe(bls12_381.CURVE.x); // [u2]P
1097
+ const xP = point.multiplyUnsafe(bls12_381.params.x).negate(); // [x]P
1098
+ const u2P = xP.multiplyUnsafe(bls12_381.params.x); // [u2]P
1100
1099
  return u2P.equals(phi);
1101
1100
 
1102
1101
  // https://eprint.iacr.org/2019/814.pdf
@@ -1115,21 +1114,23 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
1115
1114
  // https://eprint.iacr.org/2019/403
1116
1115
  clearCofactor: (c, point) => {
1117
1116
  // return this.multiplyUnsafe(CURVE.h);
1118
- return point.multiplyUnsafe(bls12_381.CURVE.x).add(point); // x*P + P
1117
+ return point.multiplyUnsafe(bls12_381.params.x).add(point); // x*P + P
1119
1118
  },
1120
1119
  mapToCurve: (scalars: bigint[]) => {
1121
1120
  const { x, y } = G1_SWU(Fp.create(scalars[0]));
1122
1121
  return isogenyMapG1(x, y);
1123
1122
  },
1124
1123
  fromBytes: (bytes: Uint8Array): AffinePoint<Fp> => {
1124
+ bytes = bytes.slice();
1125
1125
  if (bytes.length === 48) {
1126
+ // TODO: Fp.bytes
1126
1127
  const P = Fp.ORDER;
1127
1128
  const compressedValue = bytesToNumberBE(bytes);
1128
1129
  const bflag = bitGet(compressedValue, I_BIT_POS);
1129
1130
  // Zero
1130
1131
  if (bflag === _1n) return { x: _0n, y: _0n };
1131
1132
  const x = Fp.create(compressedValue & Fp.MASK);
1132
- const right = Fp.add(Fp.pow(x, _3n), Fp.create(bls12_381.CURVE.G1.b)); // y² = x³ + b
1133
+ const right = Fp.add(Fp.pow(x, _3n), Fp.create(bls12_381.params.G1b)); // y² = x³ + b
1133
1134
  let y = Fp.sqrt(right);
1134
1135
  if (!y) throw new Error('Invalid compressed G1 point');
1135
1136
  const aflag = bitGet(compressedValue, C_BIT_POS);
@@ -1138,8 +1139,8 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
1138
1139
  } else if (bytes.length === 96) {
1139
1140
  // Check if the infinity flag is set
1140
1141
  if ((bytes[0] & (1 << 6)) !== 0) return bls12_381.G1.ProjectivePoint.ZERO.toAffine();
1141
- const x = bytesToNumberBE(bytes.slice(0, Fp.BYTES));
1142
- const y = bytesToNumberBE(bytes.slice(Fp.BYTES));
1142
+ const x = bytesToNumberBE(bytes.subarray(0, Fp.BYTES));
1143
+ const y = bytesToNumberBE(bytes.subarray(Fp.BYTES));
1143
1144
  return { x: Fp.create(x), y: Fp.create(y) };
1144
1145
  } else {
1145
1146
  throw new Error('Invalid point G1, expected 48/96 bytes');
@@ -1212,7 +1213,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
1212
1213
  // It returns false for shitty points.
1213
1214
  // https://eprint.iacr.org/2021/1130.pdf
1214
1215
  isTorsionFree: (c, P): boolean => {
1215
- return P.multiplyUnsafe(bls12_381.CURVE.x).negate().equals(G2psi(c, P)); // ψ(P) == [u](P)
1216
+ return P.multiplyUnsafe(bls12_381.params.x).negate().equals(G2psi(c, P)); // ψ(P) == [u](P)
1216
1217
  // Older version: https://eprint.iacr.org/2019/814.pdf
1217
1218
  // Ψ²(P) => Ψ³(P) => [z]Ψ³(P) where z = -x => [z]Ψ³(P) - Ψ²(P) + P == O
1218
1219
  // return P.psi2().psi().mulNegX().subtract(psi2).add(P).isZero();
@@ -1222,7 +1223,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
1222
1223
  // https://eprint.iacr.org/2017/419.pdf
1223
1224
  // prettier-ignore
1224
1225
  clearCofactor: (c, P) => {
1225
- const { x } = bls12_381.CURVE;
1226
+ const x = bls12_381.params.x;
1226
1227
  let t1 = P.multiplyUnsafe(x).negate(); // [-x]P
1227
1228
  let t2 = G2psi(c, P); // Ψ(P)
1228
1229
  let t3 = P.double(); // 2P
@@ -1236,6 +1237,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
1236
1237
  return Q; // [x²-x-1]P + [x-1]Ψ(P) + Ψ²(2P)
1237
1238
  },
1238
1239
  fromBytes: (bytes: Uint8Array): AffinePoint<Fp2> => {
1240
+ bytes = bytes.slice();
1239
1241
  const m_byte = bytes[0] & 0xe0;
1240
1242
  if (m_byte === 0x20 || m_byte === 0x60 || m_byte === 0xe0) {
1241
1243
  throw new Error('Invalid encoding flag: ' + m_byte);
@@ -1246,7 +1248,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
1246
1248
  const L = Fp.BYTES;
1247
1249
  const slc = (b: Uint8Array, from: number, to?: number) => bytesToNumberBE(b.slice(from, to));
1248
1250
  if (bytes.length === 96 && bitC) {
1249
- const { b } = bls12_381.CURVE.G2;
1251
+ const b = bls12_381.params.G2b;
1250
1252
  const P = Fp.ORDER;
1251
1253
 
1252
1254
  bytes[0] = bytes[0] & 0x1f; // clear flags
@@ -1280,31 +1282,31 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
1280
1282
  }
1281
1283
  },
1282
1284
  toBytes: (c, point, isCompressed) => {
1285
+ const { BYTES: len, ORDER: P } = Fp;
1283
1286
  const isZero = point.equals(c.ZERO);
1284
1287
  const { x, y } = point.toAffine();
1285
1288
  if (isCompressed) {
1286
- const P = Fp.ORDER;
1287
- if (isZero) return concatB(COMPRESSED_ZERO, numberToBytesBE(0n, Fp.BYTES));
1289
+ if (isZero) return concatB(COMPRESSED_ZERO, numberToBytesBE(_0n, len));
1288
1290
  const flag = Boolean(y.c1 === _0n ? (y.c0 * _2n) / P : (y.c1 * _2n) / P);
1289
1291
  // set compressed & sign bits (looks like different offsets than for G1/Fp?)
1290
1292
  let x_1 = bitSet(x.c1, C_BIT_POS, flag);
1291
1293
  x_1 = bitSet(x_1, S_BIT_POS, true);
1292
- return concatB(numberToBytesBE(x_1, Fp.BYTES), numberToBytesBE(x.c0, Fp.BYTES));
1294
+ return concatB(numberToBytesBE(x_1, len), numberToBytesBE(x.c0, len));
1293
1295
  } else {
1294
- if (isZero) return concatB(new Uint8Array([0x40]), new Uint8Array(4 * Fp.BYTES - 1)); // bytes[0] |= 1 << 6;
1296
+ if (isZero) return concatB(new Uint8Array([0x40]), new Uint8Array(4 * len - 1)); // bytes[0] |= 1 << 6;
1295
1297
  const { re: x0, im: x1 } = Fp2.reim(x);
1296
1298
  const { re: y0, im: y1 } = Fp2.reim(y);
1297
1299
  return concatB(
1298
- numberToBytesBE(x1, Fp.BYTES),
1299
- numberToBytesBE(x0, Fp.BYTES),
1300
- numberToBytesBE(y1, Fp.BYTES),
1301
- numberToBytesBE(y0, Fp.BYTES)
1300
+ numberToBytesBE(x1, len),
1301
+ numberToBytesBE(x0, len),
1302
+ numberToBytesBE(y1, len),
1303
+ numberToBytesBE(y0, len)
1302
1304
  );
1303
1305
  }
1304
1306
  },
1305
1307
  Signature: {
1306
1308
  // TODO: Optimize, it's very slow because of sqrt.
1307
- decode(hex: Hex): ProjPointType<Fp2> {
1309
+ fromHex(hex: Hex): ProjPointType<Fp2> {
1308
1310
  hex = ensureBytes('signatureHex', hex);
1309
1311
  const P = Fp.ORDER;
1310
1312
  const half = hex.length / 2;
@@ -1319,7 +1321,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
1319
1321
  const x1 = Fp.create(z1 & Fp.MASK);
1320
1322
  const x2 = Fp.create(z2);
1321
1323
  const x = Fp2.create({ c0: x2, c1: x1 });
1322
- const y2 = Fp2.add(Fp2.pow(x, _3n), bls12_381.CURVE.G2.b); // y² = x³ + 4
1324
+ const y2 = Fp2.add(Fp2.pow(x, _3n), bls12_381.params.G2b); // y² = x³ + 4
1323
1325
  // The slow part
1324
1326
  let y = Fp2.sqrt(y2);
1325
1327
  if (!y) throw new Error('Failed to find a square root');
@@ -1335,24 +1337,18 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
1335
1337
  point.assertValidity();
1336
1338
  return point;
1337
1339
  },
1338
- encode(point: ProjPointType<Fp2>) {
1339
- // NOTE: by some reasons it was missed in bls12-381, looks like bug
1340
- point.assertValidity();
1341
- if (point.equals(bls12_381.G2.ProjectivePoint.ZERO))
1342
- return concatB(COMPRESSED_ZERO, numberToBytesBE(0n, Fp.BYTES));
1343
- const a = point.toAffine();
1344
- const { re: x0, im: x1 } = Fp2.reim(a.x);
1345
- const { re: y0, im: y1 } = Fp2.reim(a.y);
1346
- const tmp = y1 > _0n ? y1 * _2n : y0 * _2n;
1347
- const aflag1 = Boolean((tmp / Fp.ORDER) & _1n);
1348
- const z1 = bitSet(bitSet(x1, 381, aflag1), S_BIT_POS, true);
1349
- const z2 = x0;
1350
- return concatB(numberToBytesBE(z1, Fp.BYTES), numberToBytesBE(z2, Fp.BYTES));
1340
+ toRawBytes(point: ProjPointType<Fp2>) {
1341
+ return signatureG2ToRawBytes(point);
1342
+ },
1343
+ toHex(point: ProjPointType<Fp2>) {
1344
+ return bytesToHex(signatureG2ToRawBytes(point));
1351
1345
  },
1352
1346
  },
1353
1347
  },
1354
- // The BLS parameter x for BLS12-381
1355
- x: BLS_X,
1348
+ params: {
1349
+ x: BLS_X, // The BLS parameter x for BLS12-381
1350
+ r: Fr.ORDER, // order; z⁴ − z² + 1; CURVE.n from other curves
1351
+ },
1356
1352
  htfDefaults,
1357
1353
  hash: sha256,
1358
1354
  randomBytes,
package/src/ed25519.ts CHANGED
@@ -95,15 +95,15 @@ export const ED25519_TORSION_SUBGROUP = [
95
95
 
96
96
  const Fp = Field(ED25519_P, undefined, true);
97
97
 
98
- const ED25519_DEF = {
98
+ const ed25519Defaults = {
99
99
  // Param: a
100
- a: BigInt(-1),
101
- // Equal to -121665/121666 over finite field.
100
+ a: BigInt(-1), // Fp.create(-1) is proper; our way still works and is faster
101
+ // d is equal to -121665/121666 over finite field.
102
102
  // Negative number is P - number, and division is invert(number, P)
103
103
  d: BigInt('37095705934669439343138083508754565189542113879843219016388785533085940283555'),
104
104
  // Finite field 𝔽p over which we'll do calculations; 2n ** 255n - 19n
105
105
  Fp,
106
- // Subgroup order: how many points ed25519 has
106
+ // Subgroup order: how many points curve has
107
107
  // 2n ** 252n + 27742317777372353535851937790883648493n;
108
108
  n: BigInt('7237005577332262213973186563042994240857116359379907606001950938285454250989'),
109
109
  // Cofactor
@@ -120,7 +120,7 @@ const ED25519_DEF = {
120
120
  uvRatio,
121
121
  } as const;
122
122
 
123
- export const ed25519 = twistedEdwards(ED25519_DEF);
123
+ export const ed25519 = twistedEdwards(ed25519Defaults);
124
124
  function ed25519_domain(data: Uint8Array, ctx: Uint8Array, phflag: boolean) {
125
125
  if (ctx.length > 255) throw new Error('Context is too big');
126
126
  return concatBytes(
@@ -130,11 +130,11 @@ function ed25519_domain(data: Uint8Array, ctx: Uint8Array, phflag: boolean) {
130
130
  data
131
131
  );
132
132
  }
133
- export const ed25519ctx = twistedEdwards({ ...ED25519_DEF, domain: ed25519_domain });
133
+ export const ed25519ctx = twistedEdwards({ ...ed25519Defaults, domain: ed25519_domain });
134
134
  export const ed25519ph = twistedEdwards({
135
- ...ED25519_DEF,
135
+ ...ed25519Defaults,
136
136
  domain: ed25519_domain,
137
- preHash: sha512,
137
+ prehash: sha512,
138
138
  });
139
139
 
140
140
  export const x25519 = montgomery({
@@ -153,6 +153,20 @@ export const x25519 = montgomery({
153
153
  randomBytes,
154
154
  });
155
155
 
156
+ /**
157
+ * Converts ed25519 public key to x25519 public key. Uses formula:
158
+ * * `(u, v) = ((1+y)/(1-y), sqrt(-486664)*u/x)`
159
+ * * `(x, y) = (sqrt(-486664)*u/v, (u-1)/(u+1))`
160
+ * @example
161
+ * const aPub = ed25519.getPublicKey(utils.randomPrivateKey());
162
+ * x25519.getSharedSecret(edwardsToMontgomery(aPub), edwardsToMontgomery(someonesPub))
163
+ */
164
+ export function edwardsToMontgomery(edwardsPub: Hex): Uint8Array {
165
+ const { y } = ed25519.ExtendedPoint.fromHex(edwardsPub);
166
+ const _1n = BigInt(1);
167
+ return Fp.toBytes(Fp.create((y - _1n) * Fp.inv(y + _1n)));
168
+ }
169
+
156
170
  // Hash To Curve Elligator2 Map (NOTE: different from ristretto255 elligator)
157
171
  // NOTE: very important part is usage of FpSqrtEven for ELL2_C1_EDWARDS, since
158
172
  // SageMath returns different root first and everything falls apart
package/src/ed448.ts CHANGED
@@ -120,7 +120,7 @@ const ED448_DEF = {
120
120
 
121
121
  export const ed448 = twistedEdwards(ED448_DEF);
122
122
  // NOTE: there is no ed448ctx, since ed448 supports ctx by default
123
- export const ed448ph = twistedEdwards({ ...ED448_DEF, preHash: shake256_64 });
123
+ export const ed448ph = twistedEdwards({ ...ED448_DEF, prehash: shake256_64 });
124
124
 
125
125
  export const x448 = montgomery({
126
126
  a: BigInt(156326),
@@ -136,22 +136,22 @@ export const x448 = montgomery({
136
136
  },
137
137
  adjustScalarBytes,
138
138
  randomBytes,
139
- // The 4-isogeny maps between the Montgomery curve and this Edwards
140
- // curve are:
141
- // (u, v) = (y^2/x^2, (2 - x^2 - y^2)*y/x^3)
142
- // (x, y) = (4*v*(u^2 - 1)/(u^4 - 2*u^2 + 4*v^2 + 1),
143
- // -(u^5 - 2*u^3 - 4*u*v^2 + u)/
144
- // (u^5 - 2*u^2*v^2 - 2*u^3 - 2*v^2 + u))
145
- // xyToU: (p: PointType) => {
146
- // const P = ed448P;
147
- // const { x, y } = p;
148
- // if (x === _0n) throw new Error(`Point with x=0 doesn't have mapping`);
149
- // const invX = invert(x * x, P); // x^2
150
- // const u = mod(y * y * invX, P); // (y^2/x^2)
151
- // return numberToBytesLE(u, 56);
152
- // },
153
139
  });
154
140
 
141
+ /**
142
+ * Converts edwards448 public key to x448 public key. Uses formula:
143
+ * * `(u, v) = ((y-1)/(y+1), sqrt(156324)*u/x)`
144
+ * * `(x, y) = (sqrt(156324)*u/v, (1+u)/(1-u))`
145
+ * @example
146
+ * const aPub = ed448.getPublicKey(utils.randomPrivateKey());
147
+ * x448.getSharedSecret(edwardsToMontgomery(aPub), edwardsToMontgomery(someonesPub))
148
+ */
149
+ export function edwardsToMontgomery(edwardsPub: string | Uint8Array): Uint8Array {
150
+ const { y } = ed448.ExtendedPoint.fromHex(edwardsPub);
151
+ const _1n = BigInt(1);
152
+ return Fp.toBytes(Fp.create((y - _1n) * Fp.inv(y + _1n)));
153
+ }
154
+
155
155
  // Hash To Curve Elligator2 Map
156
156
  const ELL2_C1 = (Fp.ORDER - BigInt(3)) / BigInt(4); // 1. c1 = (q - 3) / 4 # Integer arithmetic
157
157
  const ELL2_J = BigInt(156326);