@noble/curves 1.2.0 → 1.3.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 (89) hide show
  1. package/README.md +125 -191
  2. package/abstract/bls.d.ts +27 -10
  3. package/abstract/bls.d.ts.map +1 -1
  4. package/abstract/bls.js +58 -8
  5. package/abstract/bls.js.map +1 -1
  6. package/abstract/curve.js.map +1 -1
  7. package/abstract/edwards.js.map +1 -1
  8. package/abstract/hash-to-curve.d.ts +1 -1
  9. package/abstract/hash-to-curve.d.ts.map +1 -1
  10. package/abstract/hash-to-curve.js +8 -8
  11. package/abstract/hash-to-curve.js.map +1 -1
  12. package/abstract/modular.js.map +1 -1
  13. package/abstract/montgomery.d.ts.map +1 -1
  14. package/abstract/montgomery.js +5 -7
  15. package/abstract/montgomery.js.map +1 -1
  16. package/abstract/poseidon.js.map +1 -1
  17. package/abstract/utils.d.ts +2 -1
  18. package/abstract/utils.d.ts.map +1 -1
  19. package/abstract/utils.js +55 -31
  20. package/abstract/utils.js.map +1 -1
  21. package/abstract/weierstrass.d.ts +23 -27
  22. package/abstract/weierstrass.d.ts.map +1 -1
  23. package/abstract/weierstrass.js +4 -4
  24. package/abstract/weierstrass.js.map +1 -1
  25. package/bls12-381.d.ts.map +1 -1
  26. package/bls12-381.js +137 -82
  27. package/bls12-381.js.map +1 -1
  28. package/bn254.d.ts +3 -2
  29. package/bn254.d.ts.map +1 -1
  30. package/bn254.js +3 -2
  31. package/bn254.js.map +1 -1
  32. package/ed25519.d.ts +4 -2
  33. package/ed25519.d.ts.map +1 -1
  34. package/ed25519.js +8 -2
  35. package/ed25519.js.map +1 -1
  36. package/ed448.d.ts +4 -2
  37. package/ed448.d.ts.map +1 -1
  38. package/ed448.js +10 -1
  39. package/ed448.js.map +1 -1
  40. package/esm/abstract/bls.js +58 -8
  41. package/esm/abstract/bls.js.map +1 -1
  42. package/esm/abstract/curve.js.map +1 -1
  43. package/esm/abstract/edwards.js.map +1 -1
  44. package/esm/abstract/hash-to-curve.js +9 -9
  45. package/esm/abstract/hash-to-curve.js.map +1 -1
  46. package/esm/abstract/modular.js.map +1 -1
  47. package/esm/abstract/montgomery.js +5 -7
  48. package/esm/abstract/montgomery.js.map +1 -1
  49. package/esm/abstract/poseidon.js.map +1 -1
  50. package/esm/abstract/utils.js +53 -30
  51. package/esm/abstract/utils.js.map +1 -1
  52. package/esm/abstract/weierstrass.js +4 -4
  53. package/esm/abstract/weierstrass.js.map +1 -1
  54. package/esm/bls12-381.js +138 -83
  55. package/esm/bls12-381.js.map +1 -1
  56. package/esm/bn254.js +3 -2
  57. package/esm/bn254.js.map +1 -1
  58. package/esm/ed25519.js +8 -2
  59. package/esm/ed25519.js.map +1 -1
  60. package/esm/ed448.js +10 -1
  61. package/esm/ed448.js.map +1 -1
  62. package/esm/jubjub.js.map +1 -1
  63. package/esm/p256.js +2 -2
  64. package/esm/p256.js.map +1 -1
  65. package/esm/p384.js +2 -2
  66. package/esm/p384.js.map +1 -1
  67. package/esm/p521.js +3 -3
  68. package/esm/p521.js.map +1 -1
  69. package/esm/secp256k1.js +6 -6
  70. package/esm/secp256k1.js.map +1 -1
  71. package/jubjub.js.map +1 -1
  72. package/p256.js +2 -2
  73. package/p256.js.map +1 -1
  74. package/p384.js +2 -2
  75. package/p384.js.map +1 -1
  76. package/p521.js +3 -3
  77. package/p521.js.map +1 -1
  78. package/package.json +5 -5
  79. package/secp256k1.js +6 -6
  80. package/secp256k1.js.map +1 -1
  81. package/src/abstract/bls.ts +117 -19
  82. package/src/abstract/hash-to-curve.ts +10 -9
  83. package/src/abstract/montgomery.ts +4 -6
  84. package/src/abstract/utils.ts +52 -26
  85. package/src/abstract/weierstrass.ts +16 -7
  86. package/src/bls12-381.ts +127 -69
  87. package/src/bn254.ts +3 -2
  88. package/src/ed25519.ts +10 -2
  89. package/src/ed448.ts +18 -3
@@ -150,17 +150,15 @@ export function montgomery(curveDef: CurveType): CurveFn {
150
150
  function decodeUCoordinate(uEnc: Hex): bigint {
151
151
  // Section 5: When receiving such an array, implementations of X25519
152
152
  // MUST mask the most significant bit in the final byte.
153
- // This is very ugly way, but it works because fieldLen-1 is outside of bounds for X448, so this becomes NOOP
154
- // fieldLen - scalaryBytes = 1 for X448 and = 0 for X25519
155
153
  const u = ensureBytes('u coordinate', uEnc, montgomeryBytes);
156
- // u[fieldLen-1] crashes QuickJS (TypeError: out-of-bound numeric index)
157
- if (fieldLen === montgomeryBytes) u[fieldLen - 1] &= 127; // 0b0111_1111
154
+ if (fieldLen === 32) u[31] &= 127; // 0b0111_1111
158
155
  return bytesToNumberLE(u);
159
156
  }
160
157
  function decodeScalar(n: Hex): bigint {
161
158
  const bytes = ensureBytes('scalar', n);
162
- if (bytes.length !== montgomeryBytes && bytes.length !== fieldLen)
163
- throw new Error(`Expected ${montgomeryBytes} or ${fieldLen} bytes, got ${bytes.length}`);
159
+ const len = bytes.length;
160
+ if (len !== montgomeryBytes && len !== fieldLen)
161
+ throw new Error(`Expected ${montgomeryBytes} or ${fieldLen} bytes, got ${len}`);
164
162
  return bytesToNumberLE(adjustScalarBytes(bytes));
165
163
  }
166
164
  function scalarMult(scalar: Hex, u: Hex): Uint8Array {
@@ -6,7 +6,6 @@
6
6
  const _0n = BigInt(0);
7
7
  const _1n = BigInt(1);
8
8
  const _2n = BigInt(2);
9
- const u8a = (a: any): a is Uint8Array => a instanceof Uint8Array;
10
9
  export type Hex = Uint8Array | string; // hex strings are accepted for simplicity
11
10
  export type PrivKey = Hex | bigint; // bigints are accepted to ease learning curve
12
11
  export type CHash = {
@@ -17,6 +16,14 @@ export type CHash = {
17
16
  };
18
17
  export type FHash = (message: Uint8Array | string) => Uint8Array;
19
18
 
19
+ export function isBytes(a: unknown): a is Uint8Array {
20
+ return (
21
+ a instanceof Uint8Array ||
22
+ (a != null && typeof a === 'object' && a.constructor.name === 'Uint8Array')
23
+ );
24
+ }
25
+
26
+ // Array where index 0xf0 (240) is mapped to string 'f0'
20
27
  const hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) =>
21
28
  i.toString(16).padStart(2, '0')
22
29
  );
@@ -24,7 +31,7 @@ const hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) =>
24
31
  * @example bytesToHex(Uint8Array.from([0xca, 0xfe, 0x01, 0x23])) // 'cafe0123'
25
32
  */
26
33
  export function bytesToHex(bytes: Uint8Array): string {
27
- if (!u8a(bytes)) throw new Error('Uint8Array expected');
34
+ if (!isBytes(bytes)) throw new Error('Uint8Array expected');
28
35
  // pre-caching improves the speed 6x
29
36
  let hex = '';
30
37
  for (let i = 0; i < bytes.length; i++) {
@@ -44,20 +51,32 @@ export function hexToNumber(hex: string): bigint {
44
51
  return BigInt(hex === '' ? '0' : `0x${hex}`);
45
52
  }
46
53
 
54
+ // We use optimized technique to convert hex string to byte array
55
+ const asciis = { _0: 48, _9: 57, _A: 65, _F: 70, _a: 97, _f: 102 } as const;
56
+ function asciiToBase16(char: number): number | undefined {
57
+ if (char >= asciis._0 && char <= asciis._9) return char - asciis._0;
58
+ if (char >= asciis._A && char <= asciis._F) return char - (asciis._A - 10);
59
+ if (char >= asciis._a && char <= asciis._f) return char - (asciis._a - 10);
60
+ return;
61
+ }
62
+
47
63
  /**
48
64
  * @example hexToBytes('cafe0123') // Uint8Array.from([0xca, 0xfe, 0x01, 0x23])
49
65
  */
50
66
  export function hexToBytes(hex: string): Uint8Array {
51
67
  if (typeof hex !== 'string') throw new Error('hex string expected, got ' + typeof hex);
52
- const len = hex.length;
53
- if (len % 2) throw new Error('padded hex string expected, got unpadded hex of length ' + len);
54
- const array = new Uint8Array(len / 2);
55
- for (let i = 0; i < array.length; i++) {
56
- const j = i * 2;
57
- const hexByte = hex.slice(j, j + 2);
58
- const byte = Number.parseInt(hexByte, 16);
59
- if (Number.isNaN(byte) || byte < 0) throw new Error('Invalid byte sequence');
60
- array[i] = byte;
68
+ const hl = hex.length;
69
+ const al = hl / 2;
70
+ if (hl % 2) throw new Error('padded hex string expected, got unpadded hex of length ' + hl);
71
+ const array = new Uint8Array(al);
72
+ for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {
73
+ const n1 = asciiToBase16(hex.charCodeAt(hi));
74
+ const n2 = asciiToBase16(hex.charCodeAt(hi + 1));
75
+ if (n1 === undefined || n2 === undefined) {
76
+ const char = hex[hi] + hex[hi + 1];
77
+ throw new Error('hex string expected, got non-hex character "' + char + '" at index ' + hi);
78
+ }
79
+ array[ai] = n1 * 16 + n2;
61
80
  }
62
81
  return array;
63
82
  }
@@ -67,7 +86,7 @@ export function bytesToNumberBE(bytes: Uint8Array): bigint {
67
86
  return hexToNumber(bytesToHex(bytes));
68
87
  }
69
88
  export function bytesToNumberLE(bytes: Uint8Array): bigint {
70
- if (!u8a(bytes)) throw new Error('Uint8Array expected');
89
+ if (!isBytes(bytes)) throw new Error('Uint8Array expected');
71
90
  return hexToNumber(bytesToHex(Uint8Array.from(bytes).reverse()));
72
91
  }
73
92
 
@@ -99,7 +118,7 @@ export function ensureBytes(title: string, hex: Hex, expectedLength?: number): U
99
118
  } catch (e) {
100
119
  throw new Error(`${title} must be valid hex string, got "${hex}". Cause: ${e}`);
101
120
  }
102
- } else if (u8a(hex)) {
121
+ } else if (isBytes(hex)) {
103
122
  // Uint8Array.from() instead of hash.slice() because node.js Buffer
104
123
  // is instance of Uint8Array, and its slice() creates **mutable** copy
105
124
  res = Uint8Array.from(hex);
@@ -116,21 +135,28 @@ export function ensureBytes(title: string, hex: Hex, expectedLength?: number): U
116
135
  * Copies several Uint8Arrays into one.
117
136
  */
118
137
  export function concatBytes(...arrays: Uint8Array[]): Uint8Array {
119
- const r = new Uint8Array(arrays.reduce((sum, a) => sum + a.length, 0));
120
- let pad = 0; // walk through each item, ensure they have proper type
121
- arrays.forEach((a) => {
122
- if (!u8a(a)) throw new Error('Uint8Array expected');
123
- r.set(a, pad);
138
+ let sum = 0;
139
+ for (let i = 0; i < arrays.length; i++) {
140
+ const a = arrays[i];
141
+ if (!isBytes(a)) throw new Error('Uint8Array expected');
142
+ sum += a.length;
143
+ }
144
+ let res = new Uint8Array(sum);
145
+ let pad = 0;
146
+ for (let i = 0; i < arrays.length; i++) {
147
+ const a = arrays[i];
148
+ res.set(a, pad);
124
149
  pad += a.length;
125
- });
126
- return r;
150
+ }
151
+ return res;
127
152
  }
128
153
 
129
- export function equalBytes(b1: Uint8Array, b2: Uint8Array) {
130
- // We don't care about timing attacks here
131
- if (b1.length !== b2.length) return false;
132
- for (let i = 0; i < b1.length; i++) if (b1[i] !== b2[i]) return false;
133
- return true;
154
+ // Compares 2 u8a-s in kinda constant time
155
+ export function equalBytes(a: Uint8Array, b: Uint8Array) {
156
+ if (a.length !== b.length) return false;
157
+ let diff = 0;
158
+ for (let i = 0; i < a.length; i++) diff |= a[i] ^ b[i];
159
+ return diff === 0;
134
160
  }
135
161
 
136
162
  // Global symbols in both browsers and Node.js since v11
@@ -248,7 +274,7 @@ const validatorFns = {
248
274
  function: (val: any) => typeof val === 'function',
249
275
  boolean: (val: any) => typeof val === 'boolean',
250
276
  string: (val: any) => typeof val === 'string',
251
- stringOrUint8Array: (val: any) => typeof val === 'string' || val instanceof Uint8Array,
277
+ stringOrUint8Array: (val: any) => typeof val === 'string' || isBytes(val),
252
278
  isSafeInteger: (val: any) => Number.isSafeInteger(val),
253
279
  array: (val: any) => Array.isArray(val),
254
280
  field: (val: any, object: any) => (object as any).Fp.isValid(val),
@@ -123,6 +123,7 @@ function validatePointOpts<T>(curve: CurvePointsType<T>) {
123
123
  }
124
124
 
125
125
  export type CurvePointsRes<T> = {
126
+ CURVE: ReturnType<typeof validatePointOpts<T>>;
126
127
  ProjectivePoint: ProjConstructor<T>;
127
128
  normPrivateKeyToScalar: (key: PrivKey) => bigint;
128
129
  weierstrassEquation: (x: T) => T;
@@ -157,7 +158,7 @@ export const DER = {
157
158
  // parse DER signature
158
159
  const { Err: E } = DER;
159
160
  const data = typeof hex === 'string' ? h2b(hex) : hex;
160
- if (!(data instanceof Uint8Array)) throw new Error('ui8a expected');
161
+ if (!ut.isBytes(data)) throw new Error('ui8a expected');
161
162
  let l = data.length;
162
163
  if (l < 2 || data[0] != 0x30) throw new E('Invalid signature tag');
163
164
  if (data[1] !== l - 2) throw new E('Invalid signature: incorrect length');
@@ -187,7 +188,7 @@ export const DER = {
187
188
  // prettier-ignore
188
189
  const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3), _4n = BigInt(4);
189
190
 
190
- export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
191
+ export function weierstrassPoints<T>(opts: CurvePointsType<T>): CurvePointsRes<T> {
191
192
  const CURVE = validatePointOpts(opts);
192
193
  const { Fp } = CURVE; // All curves has same field / group length as for now, but they can differ
193
194
 
@@ -237,7 +238,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
237
238
  function normPrivateKeyToScalar(key: PrivKey): bigint {
238
239
  const { allowedPrivateKeyLengths: lengths, nByteLength, wrapPrivateKey, n } = CURVE;
239
240
  if (lengths && typeof key !== 'bigint') {
240
- if (key instanceof Uint8Array) key = ut.bytesToHex(key);
241
+ if (ut.isBytes(key)) key = ut.bytesToHex(key);
241
242
  // Normalize to hex string, pad. E.g. P521 would norm 130-132 char hex to 132-char bytes
242
243
  if (typeof key !== 'string' || !lengths.includes(key.length)) throw new Error('Invalid key');
243
244
  key = key.padStart(nByteLength * 2, '0');
@@ -269,7 +270,11 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
269
270
  static readonly BASE = new Point(CURVE.Gx, CURVE.Gy, Fp.ONE);
270
271
  static readonly ZERO = new Point(Fp.ZERO, Fp.ONE, Fp.ZERO);
271
272
 
272
- constructor(readonly px: T, readonly py: T, readonly pz: T) {
273
+ constructor(
274
+ readonly px: T,
275
+ readonly py: T,
276
+ readonly pz: T
277
+ ) {
273
278
  if (px == null || !Fp.isValid(px)) throw new Error('x required');
274
279
  if (py == null || !Fp.isValid(py)) throw new Error('y required');
275
280
  if (pz == null || !Fp.isValid(pz)) throw new Error('z required');
@@ -763,7 +768,11 @@ export function weierstrass(curveDef: CurveType): CurveFn {
763
768
  * ECDSA signature with its (r, s) properties. Supports DER & compact representations.
764
769
  */
765
770
  class Signature implements SignatureType {
766
- constructor(readonly r: bigint, readonly s: bigint, readonly recovery?: number) {
771
+ constructor(
772
+ readonly r: bigint,
773
+ readonly s: bigint,
774
+ readonly recovery?: number
775
+ ) {
767
776
  this.assertValidity();
768
777
  }
769
778
 
@@ -884,7 +893,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
884
893
  * Quick and dirty check for item being public key. Does not validate hex, or being on-curve.
885
894
  */
886
895
  function isProbPub(item: PrivKey | PubKey): boolean {
887
- const arr = item instanceof Uint8Array;
896
+ const arr = ut.isBytes(item);
888
897
  const str = typeof item === 'string';
889
898
  const len = (arr || str) && (item as Hex).length;
890
899
  if (arr) return len === compressedLen || len === uncompressedLen;
@@ -1048,7 +1057,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
1048
1057
  let _sig: Signature | undefined = undefined;
1049
1058
  let P: ProjPointType<bigint>;
1050
1059
  try {
1051
- if (typeof sg === 'string' || sg instanceof Uint8Array) {
1060
+ if (typeof sg === 'string' || ut.isBytes(sg)) {
1052
1061
  // Signature can be represented in 2 ways: compact (2*nByteLength) & DER (variable-length).
1053
1062
  // Since DER can also be 2*nByteLength bytes, we check for it first.
1054
1063
  try {
package/src/bls12-381.ts CHANGED
@@ -1,15 +1,9 @@
1
1
  /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
2
2
 
3
- // bls12-381 pairing-friendly Barreto-Lynn-Scott elliptic curve construction allows to:
4
- // - Construct zk-SNARKs at the 128-bit security
5
- // - Use threshold signatures, which allows a user to sign lots of messages with one signature and
6
- // verify them swiftly in a batch, using Boneh-Lynn-Shacham signature scheme.
7
- //
8
- // The library uses G1 for public keys and G2 for signatures. Support for G1 signatures is planned.
9
- // Compatible with Algorand, Chia, Dfinity, Ethereum, FIL, Zcash. Matches specs
10
- // [pairing-curves-11](https://tools.ietf.org/html/draft-irtf-cfrg-pairing-friendly-curves-11),
11
- // [bls-sigs-04](https:/cfrg-hash-to/tools.ietf.org/html/draft-irtf-cfrg-bls-signature-04),
12
- // [hash-to-curve-12](https://tools.ietf.org/html/draft-irtf--curve-12).
3
+ // bls12-381 is pairing-friendly Barreto-Lynn-Scott elliptic curve construction allowing to:
4
+ // - Construct zk-SNARKs at the 120-bit security
5
+ // - Efficiently verify N aggregate signatures with 1 pairing and N ec additions:
6
+ // the Boneh-Lynn-Shacham signature scheme is orders of magnitude more efficient than Schnorr
13
7
  //
14
8
  // ### Summary
15
9
  // 1. BLS Relies on Bilinear Pairing (expensive)
@@ -25,8 +19,17 @@
25
19
  // - `S = pk x H(m)` - signing
26
20
  // - `e(P, H(m)) == e(G, S)` - verification using pairings
27
21
  // - `e(G, S) = e(G, SUM(n)(Si)) = MUL(n)(e(G, Si))` - signature aggregation
28
- // Filecoin uses little endian byte arrays for private keys -
29
- // so ensure to reverse byte order if you'll use it with FIL.
22
+ //
23
+ // ### Compatibility and notes
24
+ // 1. It is compatible with Algorand, Chia, Dfinity, Ethereum, Filecoin, ZEC
25
+ // Filecoin uses little endian byte arrays for private keys - make sure to reverse byte order.
26
+ // 2. Some projects use G2 for public keys and G1 for signatures. It's called "short signature"
27
+ // 3. Curve security level is about 120 bits as per Barbulescu-Duquesne 2017
28
+ // https://hal.science/hal-01534101/file/main.pdf
29
+ // 4. Compatible with specs:
30
+ // [cfrg-pairing-friendly-curves-11](https://tools.ietf.org/html/draft-irtf-cfrg-pairing-friendly-curves-11),
31
+ // [cfrg-bls-signature-05](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-05),
32
+ // [RFC 9380](https://www.rfc-editor.org/rfc/rfc9380).
30
33
  import { sha256 } from '@noble/hashes/sha256';
31
34
  import { randomBytes } from '@noble/hashes/utils';
32
35
  import { bls, CurveFn } from './abstract/bls.js';
@@ -37,7 +40,6 @@ import {
37
40
  numberToBytesBE,
38
41
  bytesToNumberBE,
39
42
  bitLen,
40
- bitSet,
41
43
  bitGet,
42
44
  Hex,
43
45
  bitMask,
@@ -1016,11 +1018,41 @@ const htfDefaults = Object.freeze({
1016
1018
 
1017
1019
  // Encoding utils
1018
1020
  // Point on G1 curve: (x, y)
1019
- const C_BIT_POS = Fp.BITS; // C_bit, compression bit for serialization flag
1020
- const I_BIT_POS = Fp.BITS + 1; // I_bit, point-at-infinity bit for serialization flag
1021
- const S_BIT_POS = Fp.BITS + 2; // S_bit, sign bit for serialization flag
1021
+
1022
1022
  // Compressed point of infinity
1023
- const COMPRESSED_ZERO = Fp.toBytes(bitSet(bitSet(_0n, I_BIT_POS, true), S_BIT_POS, true)); // set compressed & point-at-infinity bits
1023
+ const COMPRESSED_ZERO = setMask(Fp.toBytes(_0n), { infinity: true, compressed: true }); // set compressed & point-at-infinity bits
1024
+
1025
+ function parseMask(bytes: Uint8Array) {
1026
+ // Copy, so we can remove mask data. It will be removed also later, when Fp.create will call modulo.
1027
+ bytes = bytes.slice();
1028
+ const mask = bytes[0] & 0b1110_0000;
1029
+ const compressed = !!((mask >> 7) & 1); // compression bit (0b1000_0000)
1030
+ const infinity = !!((mask >> 6) & 1); // point at infinity bit (0b0100_0000)
1031
+ const sort = !!((mask >> 5) & 1); // sort bit (0b0010_0000)
1032
+ bytes[0] &= 0b0001_1111; // clear mask (zero first 3 bits)
1033
+ return { compressed, infinity, sort, value: bytes };
1034
+ }
1035
+
1036
+ function setMask(
1037
+ bytes: Uint8Array,
1038
+ mask: { compressed?: boolean; infinity?: boolean; sort?: boolean }
1039
+ ) {
1040
+ if (bytes[0] & 0b1110_0000) throw new Error('setMask: non-empty mask');
1041
+ if (mask.compressed) bytes[0] |= 0b1000_0000;
1042
+ if (mask.infinity) bytes[0] |= 0b0100_0000;
1043
+ if (mask.sort) bytes[0] |= 0b0010_0000;
1044
+ return bytes;
1045
+ }
1046
+
1047
+ function signatureG1ToRawBytes(point: ProjPointType<Fp>) {
1048
+ point.assertValidity();
1049
+ const isZero = point.equals(bls12_381.G1.ProjectivePoint.ZERO);
1050
+ const { x, y } = point.toAffine();
1051
+ if (isZero) return COMPRESSED_ZERO.slice();
1052
+ const P = Fp.ORDER;
1053
+ const sort = Boolean((y * _2n) / P);
1054
+ return setMask(numberToBytesBE(x, Fp.BYTES), { compressed: true, sort });
1055
+ }
1024
1056
 
1025
1057
  function signatureG2ToRawBytes(point: ProjPointType<Fp2>) {
1026
1058
  // NOTE: by some reasons it was missed in bls12-381, looks like bug
@@ -1032,10 +1064,12 @@ function signatureG2ToRawBytes(point: ProjPointType<Fp2>) {
1032
1064
  const { re: x0, im: x1 } = Fp2.reim(x);
1033
1065
  const { re: y0, im: y1 } = Fp2.reim(y);
1034
1066
  const tmp = y1 > _0n ? y1 * _2n : y0 * _2n;
1035
- const aflag1 = Boolean((tmp / Fp.ORDER) & _1n);
1036
- const z1 = bitSet(bitSet(x1, 381, aflag1), S_BIT_POS, true);
1067
+ const sort = Boolean((tmp / Fp.ORDER) & _1n);
1037
1068
  const z2 = x0;
1038
- return concatB(numberToBytesBE(z1, len), numberToBytesBE(z2, len));
1069
+ return concatB(
1070
+ setMask(numberToBytesBE(x1, len), { sort, compressed: true }),
1071
+ numberToBytesBE(z2, len)
1072
+ );
1039
1073
  }
1040
1074
 
1041
1075
  // To verify curve parameters, see pairing-friendly-curves spec:
@@ -1074,7 +1108,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
1074
1108
  ),
1075
1109
  a: Fp.ZERO,
1076
1110
  b: _4n,
1077
- htfDefaults: { ...htfDefaults, m: 1 },
1111
+ htfDefaults: { ...htfDefaults, m: 1, DST: 'BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_' },
1078
1112
  wrapPrivateKey: true,
1079
1113
  allowInfinityPoint: true,
1080
1114
  // Checks is the point resides in prime-order subgroup.
@@ -1116,26 +1150,30 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
1116
1150
  return isogenyMapG1(x, y);
1117
1151
  },
1118
1152
  fromBytes: (bytes: Uint8Array): AffinePoint<Fp> => {
1119
- bytes = bytes.slice();
1120
- if (bytes.length === 48) {
1153
+ const { compressed, infinity, sort, value } = parseMask(bytes);
1154
+ if (value.length === 48 && compressed) {
1121
1155
  // TODO: Fp.bytes
1122
1156
  const P = Fp.ORDER;
1123
- const compressedValue = bytesToNumberBE(bytes);
1124
- const bflag = bitGet(compressedValue, I_BIT_POS);
1157
+ const compressedValue = bytesToNumberBE(value);
1125
1158
  // Zero
1126
- if (bflag === _1n) return { x: _0n, y: _0n };
1127
1159
  const x = Fp.create(compressedValue & Fp.MASK);
1160
+ if (infinity) {
1161
+ if (x !== _0n) throw new Error('G1: non-empty compressed point at infinity');
1162
+ return { x: _0n, y: _0n };
1163
+ }
1128
1164
  const right = Fp.add(Fp.pow(x, _3n), Fp.create(bls12_381.params.G1b)); // y² = x³ + b
1129
1165
  let y = Fp.sqrt(right);
1130
1166
  if (!y) throw new Error('Invalid compressed G1 point');
1131
- const aflag = bitGet(compressedValue, C_BIT_POS);
1132
- if ((y * _2n) / P !== aflag) y = Fp.neg(y);
1167
+ if ((y * _2n) / P !== BigInt(sort)) y = Fp.neg(y);
1133
1168
  return { x: Fp.create(x), y: Fp.create(y) };
1134
- } else if (bytes.length === 96) {
1169
+ } else if (value.length === 96 && !compressed) {
1135
1170
  // Check if the infinity flag is set
1136
- if ((bytes[0] & (1 << 6)) !== 0) return bls12_381.G1.ProjectivePoint.ZERO.toAffine();
1137
- const x = bytesToNumberBE(bytes.subarray(0, Fp.BYTES));
1138
- const y = bytesToNumberBE(bytes.subarray(Fp.BYTES));
1171
+ const x = bytesToNumberBE(value.subarray(0, Fp.BYTES));
1172
+ const y = bytesToNumberBE(value.subarray(Fp.BYTES));
1173
+ if (infinity) {
1174
+ if (x !== _0n || y !== _0n) throw new Error('G1: non-empty point at infinity');
1175
+ return bls12_381.G1.ProjectivePoint.ZERO.toAffine();
1176
+ }
1139
1177
  return { x: Fp.create(x), y: Fp.create(y) };
1140
1178
  } else {
1141
1179
  throw new Error('Invalid point G1, expected 48/96 bytes');
@@ -1147,10 +1185,8 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
1147
1185
  if (isCompressed) {
1148
1186
  if (isZero) return COMPRESSED_ZERO.slice();
1149
1187
  const P = Fp.ORDER;
1150
- let num;
1151
- num = bitSet(x, C_BIT_POS, Boolean((y * _2n) / P)); // set aflag
1152
- num = bitSet(num, S_BIT_POS, true);
1153
- return numberToBytesBE(num, Fp.BYTES);
1188
+ const sort = Boolean((y * _2n) / P);
1189
+ return setMask(numberToBytesBE(x, Fp.BYTES), { compressed: true, sort });
1154
1190
  } else {
1155
1191
  if (isZero) {
1156
1192
  // 2x PUBLIC_KEY_LENGTH
@@ -1161,6 +1197,30 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
1161
1197
  }
1162
1198
  }
1163
1199
  },
1200
+ ShortSignature: {
1201
+ fromHex(hex: Hex): ProjPointType<Fp> {
1202
+ const { infinity, sort, value } = parseMask(ensureBytes('signatureHex', hex, 48));
1203
+ const P = Fp.ORDER;
1204
+ const compressedValue = bytesToNumberBE(value);
1205
+ // Zero
1206
+ if (infinity) return bls12_381.G1.ProjectivePoint.ZERO;
1207
+ const x = Fp.create(compressedValue & Fp.MASK);
1208
+ const right = Fp.add(Fp.pow(x, _3n), Fp.create(bls12_381.params.G1b)); // y² = x³ + b
1209
+ let y = Fp.sqrt(right);
1210
+ if (!y) throw new Error('Invalid compressed G1 point');
1211
+ const aflag = BigInt(sort);
1212
+ if ((y * _2n) / P !== aflag) y = Fp.neg(y);
1213
+ const point = bls12_381.G1.ProjectivePoint.fromAffine({ x, y });
1214
+ point.assertValidity();
1215
+ return point;
1216
+ },
1217
+ toRawBytes(point: ProjPointType<Fp>) {
1218
+ return signatureG1ToRawBytes(point);
1219
+ },
1220
+ toHex(point: ProjPointType<Fp>) {
1221
+ return bytesToHex(signatureG1ToRawBytes(point));
1222
+ },
1223
+ },
1164
1224
  },
1165
1225
  // G2 is the order-q subgroup of E2(Fp²) : y² = x³+4(1+√−1),
1166
1226
  // where Fp2 is Fp[√−1]/(x2+1). #E2(Fp2 ) = h2q, where
@@ -1232,45 +1292,45 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
1232
1292
  return Q; // [x²-x-1]P + [x-1]Ψ(P) + Ψ²(2P)
1233
1293
  },
1234
1294
  fromBytes: (bytes: Uint8Array): AffinePoint<Fp2> => {
1235
- bytes = bytes.slice();
1236
- const m_byte = bytes[0] & 0xe0;
1237
- if (m_byte === 0x20 || m_byte === 0x60 || m_byte === 0xe0) {
1238
- throw new Error('Invalid encoding flag: ' + m_byte);
1295
+ const { compressed, infinity, sort, value } = parseMask(bytes);
1296
+ if (
1297
+ (!compressed && !infinity && sort) || // 00100000
1298
+ (!compressed && infinity && sort) || // 01100000
1299
+ (sort && infinity && compressed) // 11100000
1300
+ ) {
1301
+ throw new Error('Invalid encoding flag: ' + (bytes[0] & 0b1110_0000));
1239
1302
  }
1240
- const bitC = m_byte & 0x80; // compression bit
1241
- const bitI = m_byte & 0x40; // point at infinity bit
1242
- const bitS = m_byte & 0x20; // sign bit
1243
1303
  const L = Fp.BYTES;
1244
1304
  const slc = (b: Uint8Array, from: number, to?: number) => bytesToNumberBE(b.slice(from, to));
1245
- if (bytes.length === 96 && bitC) {
1305
+ if (value.length === 96 && compressed) {
1246
1306
  const b = bls12_381.params.G2b;
1247
1307
  const P = Fp.ORDER;
1248
-
1249
- bytes[0] = bytes[0] & 0x1f; // clear flags
1250
- if (bitI) {
1308
+ if (infinity) {
1251
1309
  // check that all bytes are 0
1252
- if (bytes.reduce((p, c) => (p !== 0 ? c + 1 : c), 0) > 0) {
1310
+ if (value.reduce((p, c) => (p !== 0 ? c + 1 : c), 0) > 0) {
1253
1311
  throw new Error('Invalid compressed G2 point');
1254
1312
  }
1255
1313
  return { x: Fp2.ZERO, y: Fp2.ZERO };
1256
1314
  }
1257
- const x_1 = slc(bytes, 0, L);
1258
- const x_0 = slc(bytes, L, 2 * L);
1315
+ const x_1 = slc(value, 0, L);
1316
+ const x_0 = slc(value, L, 2 * L);
1259
1317
  const x = Fp2.create({ c0: Fp.create(x_0), c1: Fp.create(x_1) });
1260
1318
  const right = Fp2.add(Fp2.pow(x, _3n), b); // y² = x³ + 4 * (u+1) = x³ + b
1261
1319
  let y = Fp2.sqrt(right);
1262
1320
  const Y_bit = y.c1 === _0n ? (y.c0 * _2n) / P : (y.c1 * _2n) / P ? _1n : _0n;
1263
- y = bitS > 0 && Y_bit > 0 ? y : Fp2.neg(y);
1321
+ y = sort && Y_bit > 0 ? y : Fp2.neg(y);
1264
1322
  return { x, y };
1265
- } else if (bytes.length === 192 && !bitC) {
1266
- // Check if the infinity flag is set
1267
- if ((bytes[0] & (1 << 6)) !== 0) {
1323
+ } else if (value.length === 192 && !compressed) {
1324
+ if (infinity) {
1325
+ if (value.reduce((p, c) => (p !== 0 ? c + 1 : c), 0) > 0) {
1326
+ throw new Error('Invalid uncompressed G2 point');
1327
+ }
1268
1328
  return { x: Fp2.ZERO, y: Fp2.ZERO };
1269
1329
  }
1270
- const x1 = slc(bytes, 0, L);
1271
- const x0 = slc(bytes, L, 2 * L);
1272
- const y1 = slc(bytes, 2 * L, 3 * L);
1273
- const y0 = slc(bytes, 3 * L, 4 * L);
1330
+ const x1 = slc(value, 0, L);
1331
+ const x0 = slc(value, L, 2 * L);
1332
+ const y1 = slc(value, 2 * L, 3 * L);
1333
+ const y0 = slc(value, 3 * L, 4 * L);
1274
1334
  return { x: Fp2.fromBigTuple([x0, x1]), y: Fp2.fromBigTuple([y0, y1]) };
1275
1335
  } else {
1276
1336
  throw new Error('Invalid point G2, expected 96/192 bytes');
@@ -1283,10 +1343,10 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
1283
1343
  if (isCompressed) {
1284
1344
  if (isZero) return concatB(COMPRESSED_ZERO, numberToBytesBE(_0n, len));
1285
1345
  const flag = Boolean(y.c1 === _0n ? (y.c0 * _2n) / P : (y.c1 * _2n) / P);
1286
- // set compressed & sign bits (looks like different offsets than for G1/Fp?)
1287
- let x_1 = bitSet(x.c1, C_BIT_POS, flag);
1288
- x_1 = bitSet(x_1, S_BIT_POS, true);
1289
- return concatB(numberToBytesBE(x_1, len), numberToBytesBE(x.c0, len));
1346
+ return concatB(
1347
+ setMask(numberToBytesBE(x.c1, len), { compressed: true, sort: flag }),
1348
+ numberToBytesBE(x.c0, len)
1349
+ );
1290
1350
  } else {
1291
1351
  if (isZero) return concatB(new Uint8Array([0x40]), new Uint8Array(4 * len - 1)); // bytes[0] |= 1 << 6;
1292
1352
  const { re: x0, im: x1 } = Fp2.reim(x);
@@ -1302,17 +1362,15 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
1302
1362
  Signature: {
1303
1363
  // TODO: Optimize, it's very slow because of sqrt.
1304
1364
  fromHex(hex: Hex): ProjPointType<Fp2> {
1305
- hex = ensureBytes('signatureHex', hex);
1365
+ const { infinity, sort, value } = parseMask(ensureBytes('signatureHex', hex));
1306
1366
  const P = Fp.ORDER;
1307
1367
  const half = hex.length / 2;
1308
1368
  if (half !== 48 && half !== 96)
1309
1369
  throw new Error('Invalid compressed signature length, must be 96 or 192');
1310
- const z1 = bytesToNumberBE(hex.slice(0, half));
1311
- const z2 = bytesToNumberBE(hex.slice(half));
1370
+ const z1 = bytesToNumberBE(value.slice(0, half));
1371
+ const z2 = bytesToNumberBE(value.slice(half));
1312
1372
  // Indicates the infinity point
1313
- const bflag1 = bitGet(z1, I_BIT_POS);
1314
- if (bflag1 === _1n) return bls12_381.G2.ProjectivePoint.ZERO;
1315
-
1373
+ if (infinity) return bls12_381.G2.ProjectivePoint.ZERO;
1316
1374
  const x1 = Fp.create(z1 & Fp.MASK);
1317
1375
  const x2 = Fp.create(z2);
1318
1376
  const x = Fp2.create({ c0: x2, c1: x1 });
@@ -1324,7 +1382,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
1324
1382
  // Choose the y whose leftmost bit of the imaginary part is equal to the a_flag1
1325
1383
  // If y1 happens to be zero, then use the bit of y0
1326
1384
  const { re: y0, im: y1 } = Fp2.reim(y);
1327
- const aflag1 = bitGet(z1, 381);
1385
+ const aflag1 = BigInt(sort);
1328
1386
  const isGreater = y1 > _0n && (y1 * _2n) / P !== aflag1;
1329
1387
  const isZero = y1 === _0n && (y0 * _2n) / P !== aflag1;
1330
1388
  if (isGreater || isZero) y = Fp2.neg(y);
package/src/bn254.ts CHANGED
@@ -6,8 +6,9 @@ import { Field } from './abstract/modular.js';
6
6
  /**
7
7
  * bn254 pairing-friendly curve.
8
8
  * Previously known as alt_bn_128, when it had 128-bit security.
9
- * Recent research shown it's weaker, the naming has been adjusted to its prime bit count.
10
- * https://github.com/zcash/zcash/issues/2502
9
+ * Barbulescu-Duquesne 2017 shown it's weaker: just about 100 bits,
10
+ * so the naming has been adjusted to its prime bit count
11
+ * https://hal.science/hal-01534101/file/main.pdf
11
12
  */
12
13
  export const bn254 = weierstrass({
13
14
  a: BigInt(0),
package/src/ed25519.ts CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  numberToBytesLE,
14
14
  } from './abstract/utils.js';
15
15
  import { createHasher, htfBasicOpts, expand_message_xmd } from './abstract/hash-to-curve.js';
16
- import { AffinePoint } from './abstract/curve.js';
16
+ import { AffinePoint, Group } from './abstract/curve.js';
17
17
 
18
18
  /**
19
19
  * ed25519 Twisted Edwards curve with following addons:
@@ -343,7 +343,7 @@ function calcElligatorRistrettoMap(r0: bigint): ExtendedPoint {
343
343
  * but it should work in its own namespace: do not combine those two.
344
344
  * https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448
345
345
  */
346
- class RistPoint {
346
+ class RistPoint implements Group<RistPoint> {
347
347
  static BASE: RistPoint;
348
348
  static ZERO: RistPoint;
349
349
  // Private property to discourage combining ExtendedPoint + RistrettoPoint
@@ -471,6 +471,14 @@ class RistPoint {
471
471
  multiplyUnsafe(scalar: bigint): RistPoint {
472
472
  return new RistPoint(this.ep.multiplyUnsafe(scalar));
473
473
  }
474
+
475
+ double(): RistPoint {
476
+ return new RistPoint(this.ep.double());
477
+ }
478
+
479
+ negate(): RistPoint {
480
+ return new RistPoint(this.ep.negate());
481
+ }
474
482
  }
475
483
  export const RistrettoPoint = /* @__PURE__ */ (() => {
476
484
  if (!RistPoint.BASE) RistPoint.BASE = new RistPoint(ed25519.ExtendedPoint.BASE);