@noble/curves 1.1.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 (101) hide show
  1. package/README.md +295 -258
  2. package/abstract/bls.d.ts +27 -10
  3. package/abstract/bls.d.ts.map +1 -1
  4. package/abstract/bls.js +60 -10
  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 +2 -2
  9. package/abstract/hash-to-curve.d.ts.map +1 -1
  10. package/abstract/hash-to-curve.js +22 -16
  11. package/abstract/hash-to-curve.js.map +1 -1
  12. package/abstract/modular.d.ts +51 -11
  13. package/abstract/modular.d.ts.map +1 -1
  14. package/abstract/modular.js +79 -21
  15. package/abstract/modular.js.map +1 -1
  16. package/abstract/montgomery.d.ts.map +1 -1
  17. package/abstract/montgomery.js +5 -7
  18. package/abstract/montgomery.js.map +1 -1
  19. package/abstract/poseidon.d.ts.map +1 -1
  20. package/abstract/poseidon.js +39 -41
  21. package/abstract/poseidon.js.map +1 -1
  22. package/abstract/utils.d.ts +3 -1
  23. package/abstract/utils.d.ts.map +1 -1
  24. package/abstract/utils.js +56 -31
  25. package/abstract/utils.js.map +1 -1
  26. package/abstract/weierstrass.d.ts +25 -28
  27. package/abstract/weierstrass.d.ts.map +1 -1
  28. package/abstract/weierstrass.js +17 -15
  29. package/abstract/weierstrass.js.map +1 -1
  30. package/bls12-381.d.ts.map +1 -1
  31. package/bls12-381.js +142 -88
  32. package/bls12-381.js.map +1 -1
  33. package/bn254.d.ts +3 -2
  34. package/bn254.d.ts.map +1 -1
  35. package/bn254.js +3 -2
  36. package/bn254.js.map +1 -1
  37. package/ed25519.d.ts +5 -2
  38. package/ed25519.d.ts.map +1 -1
  39. package/ed25519.js +17 -8
  40. package/ed25519.js.map +1 -1
  41. package/ed448.d.ts +53 -2
  42. package/ed448.d.ts.map +1 -1
  43. package/ed448.js +216 -29
  44. package/ed448.js.map +1 -1
  45. package/esm/abstract/bls.js +61 -11
  46. package/esm/abstract/bls.js.map +1 -1
  47. package/esm/abstract/curve.js.map +1 -1
  48. package/esm/abstract/edwards.js.map +1 -1
  49. package/esm/abstract/hash-to-curve.js +23 -17
  50. package/esm/abstract/hash-to-curve.js.map +1 -1
  51. package/esm/abstract/modular.js +75 -20
  52. package/esm/abstract/modular.js.map +1 -1
  53. package/esm/abstract/montgomery.js +5 -7
  54. package/esm/abstract/montgomery.js.map +1 -1
  55. package/esm/abstract/poseidon.js +39 -41
  56. package/esm/abstract/poseidon.js.map +1 -1
  57. package/esm/abstract/utils.js +54 -30
  58. package/esm/abstract/utils.js.map +1 -1
  59. package/esm/abstract/weierstrass.js +17 -15
  60. package/esm/abstract/weierstrass.js.map +1 -1
  61. package/esm/bls12-381.js +143 -89
  62. package/esm/bls12-381.js.map +1 -1
  63. package/esm/bn254.js +3 -2
  64. package/esm/bn254.js.map +1 -1
  65. package/esm/ed25519.js +17 -8
  66. package/esm/ed25519.js.map +1 -1
  67. package/esm/ed448.js +218 -32
  68. package/esm/ed448.js.map +1 -1
  69. package/esm/jubjub.js +1 -1
  70. package/esm/jubjub.js.map +1 -1
  71. package/esm/p256.js +2 -2
  72. package/esm/p256.js.map +1 -1
  73. package/esm/p384.js +2 -2
  74. package/esm/p384.js.map +1 -1
  75. package/esm/p521.js +3 -3
  76. package/esm/p521.js.map +1 -1
  77. package/esm/package.json +1 -4
  78. package/esm/secp256k1.js +6 -6
  79. package/esm/secp256k1.js.map +1 -1
  80. package/jubjub.js.map +1 -1
  81. package/p256.js +2 -2
  82. package/p256.js.map +1 -1
  83. package/p384.js +2 -2
  84. package/p384.js.map +1 -1
  85. package/p521.js +3 -3
  86. package/p521.js.map +1 -1
  87. package/package.json +7 -6
  88. package/secp256k1.js +6 -6
  89. package/secp256k1.js.map +1 -1
  90. package/src/abstract/bls.ts +120 -22
  91. package/src/abstract/hash-to-curve.ts +24 -17
  92. package/src/abstract/modular.ts +81 -22
  93. package/src/abstract/montgomery.ts +4 -6
  94. package/src/abstract/poseidon.ts +39 -40
  95. package/src/abstract/utils.ts +55 -26
  96. package/src/abstract/weierstrass.ts +29 -18
  97. package/src/bls12-381.ts +132 -75
  98. package/src/bn254.ts +3 -2
  99. package/src/ed25519.ts +19 -8
  100. package/src/ed448.ts +267 -34
  101. package/src/jubjub.ts +1 -1
@@ -75,9 +75,14 @@ export function invert(number: bigint, modulo: bigint): bigint {
75
75
  return mod(x, modulo);
76
76
  }
77
77
 
78
- // Tonelli-Shanks algorithm
79
- // Paper 1: https://eprint.iacr.org/2012/685.pdf (page 12)
80
- // Paper 2: Square Roots from 1; 24, 51, 10 to Dan Shanks
78
+ /**
79
+ * Tonelli-Shanks square root search algorithm.
80
+ * 1. https://eprint.iacr.org/2012/685.pdf (page 12)
81
+ * 2. Square Roots from 1; 24, 51, 10 to Dan Shanks
82
+ * Will start an infinite loop if field order P is not prime.
83
+ * @param P field order
84
+ * @returns function that takes field Fp (created from P) and number n
85
+ */
81
86
  export function tonelliShanks(P: bigint) {
82
87
  // Legendre constant: used to calculate Legendre symbol (a | p),
83
88
  // which denotes the value of a^((p-1)/2) (mod p).
@@ -198,7 +203,7 @@ export function FpSqrt(P: bigint) {
198
203
  // Little-endian check for first LE bit (last BE bit);
199
204
  export const isNegativeLE = (num: bigint, modulo: bigint) => (mod(num, modulo) & _1n) === _1n;
200
205
 
201
- // Field is not always over prime, Fp2 for example has ORDER(q)=p^m
206
+ // Field is not always over prime: for example, Fp2 has ORDER(q)=p^m
202
207
  export interface IField<T> {
203
208
  ORDER: bigint;
204
209
  BYTES: number;
@@ -228,7 +233,8 @@ export interface IField<T> {
228
233
  sqrN(num: T): T;
229
234
 
230
235
  // Optional
231
- // Should be same as sgn0 function in https://datatracker.ietf.org/doc/draft-irtf-cfrg-hash-to-curve/
236
+ // Should be same as sgn0 function in
237
+ // [RFC9380](https://www.rfc-editor.org/rfc/rfc9380#section-4.1).
232
238
  // NOTE: sgn0 is 'negative in LE', which is same as odd. And negative in LE is kinda strange definition anyway.
233
239
  isOdd?(num: T): boolean; // Odd instead of even since we have it for Fp2
234
240
  // legendre?(num: T): T;
@@ -260,6 +266,11 @@ export function validateField<T>(field: IField<T>) {
260
266
  }
261
267
 
262
268
  // Generic field functions
269
+
270
+ /**
271
+ * Same as `pow` but for Fp: non-constant-time.
272
+ * Unsafe in some contexts: uses ladder, so can expose bigint bits.
273
+ */
263
274
  export function FpPow<T>(f: IField<T>, num: T, power: bigint): T {
264
275
  // Should have same speed as pow for bigints
265
276
  // TODO: benchmark!
@@ -276,7 +287,10 @@ export function FpPow<T>(f: IField<T>, num: T, power: bigint): T {
276
287
  return p;
277
288
  }
278
289
 
279
- // 0 is non-invertible: non-batched version will throw on 0
290
+ /**
291
+ * Efficiently invert an array of Field elements.
292
+ * `inv(0)` will return `undefined` here: make sure to throw an error.
293
+ */
280
294
  export function FpInvertBatch<T>(f: IField<T>, nums: T[]): T[] {
281
295
  const tmp = new Array(nums.length);
282
296
  // Walk from first to last, multiply them by each other MOD p
@@ -319,12 +333,12 @@ export function nLength(n: bigint, nBitLength?: number) {
319
333
 
320
334
  type FpField = IField<bigint> & Required<Pick<IField<bigint>, 'isOdd'>>;
321
335
  /**
322
- * Initializes a galois field over prime. Non-primes are not supported for now.
323
- * Do not init in loop: slow. Very fragile: always run a benchmark on change.
324
- * Major performance gains:
325
- * a) non-normalized operations like mulN instead of mul
326
- * b) `Object.freeze`
327
- * c) Same object shape: never add or remove keys
336
+ * Initializes a finite field over prime. **Non-primes are not supported.**
337
+ * Do not init in loop: slow. Very fragile: always run a benchmark on a change.
338
+ * Major performance optimizations:
339
+ * * a) denormalized operations like mulN instead of mul
340
+ * * b) same object shape: never add or remove keys
341
+ * * c) Object.freeze
328
342
  * @param ORDER prime positive bigint
329
343
  * @param bitLen how many bits the field consumes
330
344
  * @param isLE (def: false) if encoding / decoding should be in little-endian
@@ -336,7 +350,7 @@ export function Field(
336
350
  isLE = false,
337
351
  redef: Partial<IField<bigint>> = {}
338
352
  ): Readonly<FpField> {
339
- if (ORDER <= _0n) throw new Error(`Expected Fp ORDER > 0, got ${ORDER}`);
353
+ if (ORDER <= _0n) throw new Error(`Expected Field ORDER > 0, got ${ORDER}`);
340
354
  const { nBitLength: BITS, nByteLength: BYTES } = nLength(ORDER, bitLen);
341
355
  if (BYTES > 2048) throw new Error('Field lengths over 2048 bytes are not supported');
342
356
  const sqrtP = FpSqrt(ORDER);
@@ -400,15 +414,10 @@ export function FpSqrtEven<T>(Fp: IField<T>, elm: T) {
400
414
  }
401
415
 
402
416
  /**
403
- * FIPS 186 B.4.1-compliant "constant-time" private key generation utility.
404
- * Can take (n+8) or more bytes of uniform input e.g. from CSPRNG or KDF
405
- * and convert them into private scalar, with the modulo bias being negligible.
406
- * Needs at least 40 bytes of input for 32-byte private key.
407
- * https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/
408
- * @param hash hash output from SHA3 or a similar function
409
- * @param groupOrder size of subgroup - (e.g. curveFn.CURVE.n)
410
- * @param isLE interpret hash bytes as LE num
411
- * @returns valid private scalar
417
+ * "Constant-time" private key generation utility.
418
+ * Same as mapKeyToField, but accepts less bytes (40 instead of 48 for 32-byte field).
419
+ * Which makes it slightly more biased, less secure.
420
+ * @deprecated use mapKeyToField instead
412
421
  */
413
422
  export function hashToPrivateScalar(
414
423
  hash: string | Uint8Array,
@@ -423,3 +432,53 @@ export function hashToPrivateScalar(
423
432
  const num = isLE ? bytesToNumberLE(hash) : bytesToNumberBE(hash);
424
433
  return mod(num, groupOrder - _1n) + _1n;
425
434
  }
435
+
436
+ /**
437
+ * Returns total number of bytes consumed by the field element.
438
+ * For example, 32 bytes for usual 256-bit weierstrass curve.
439
+ * @param fieldOrder number of field elements, usually CURVE.n
440
+ * @returns byte length of field
441
+ */
442
+ export function getFieldBytesLength(fieldOrder: bigint): number {
443
+ if (typeof fieldOrder !== 'bigint') throw new Error('field order must be bigint');
444
+ const bitLength = fieldOrder.toString(2).length;
445
+ return Math.ceil(bitLength / 8);
446
+ }
447
+
448
+ /**
449
+ * Returns minimal amount of bytes that can be safely reduced
450
+ * by field order.
451
+ * Should be 2^-128 for 128-bit curve such as P256.
452
+ * @param fieldOrder number of field elements, usually CURVE.n
453
+ * @returns byte length of target hash
454
+ */
455
+ export function getMinHashLength(fieldOrder: bigint): number {
456
+ const length = getFieldBytesLength(fieldOrder);
457
+ return length + Math.ceil(length / 2);
458
+ }
459
+
460
+ /**
461
+ * "Constant-time" private key generation utility.
462
+ * Can take (n + n/2) or more bytes of uniform input e.g. from CSPRNG or KDF
463
+ * and convert them into private scalar, with the modulo bias being negligible.
464
+ * Needs at least 48 bytes of input for 32-byte private key.
465
+ * https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/
466
+ * FIPS 186-5, A.2 https://csrc.nist.gov/publications/detail/fips/186/5/final
467
+ * RFC 9380, https://www.rfc-editor.org/rfc/rfc9380#section-5
468
+ * @param hash hash output from SHA3 or a similar function
469
+ * @param groupOrder size of subgroup - (e.g. secp256k1.CURVE.n)
470
+ * @param isLE interpret hash bytes as LE num
471
+ * @returns valid private scalar
472
+ */
473
+ export function mapHashToField(key: Uint8Array, fieldOrder: bigint, isLE = false): Uint8Array {
474
+ const len = key.length;
475
+ const fieldLen = getFieldBytesLength(fieldOrder);
476
+ const minLen = getMinHashLength(fieldOrder);
477
+ // No small numbers: need to understand bias story. No huge numbers: easier to detect JS timings.
478
+ if (len < 16 || len < minLen || len > 1024)
479
+ throw new Error(`expected ${minLen}-1024 bytes of input, got ${len}`);
480
+ const num = isLE ? bytesToNumberBE(key) : bytesToNumberLE(key);
481
+ // `mod(x, 11)` can sometimes produce 0. `mod(x, 10) + 1` is the same, but no 0
482
+ const reduced = mod(num, fieldOrder - _1n) + _1n;
483
+ return isLE ? numberToBytesLE(reduced, fieldLen) : numberToBytesBE(reduced, fieldLen);
484
+ }
@@ -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 {
@@ -15,34 +15,36 @@ export type PoseidonOpts = {
15
15
  };
16
16
 
17
17
  export function validateOpts(opts: PoseidonOpts) {
18
- const { Fp } = opts;
18
+ const { Fp, mds, reversePartialPowIdx: rev, roundConstants: rc } = opts;
19
+ const { roundsFull, roundsPartial, sboxPower, t } = opts;
20
+
19
21
  validateField(Fp);
20
22
  for (const i of ['t', 'roundsFull', 'roundsPartial'] as const) {
21
23
  if (typeof opts[i] !== 'number' || !Number.isSafeInteger(opts[i]))
22
24
  throw new Error(`Poseidon: invalid param ${i}=${opts[i]} (${typeof opts[i]})`);
23
25
  }
24
- if (opts.reversePartialPowIdx !== undefined && typeof opts.reversePartialPowIdx !== 'boolean')
25
- throw new Error(`Poseidon: invalid param reversePartialPowIdx=${opts.reversePartialPowIdx}`);
26
- // Default is 5, but by some reasons stark uses 3
27
- let sboxPower = opts.sboxPower;
28
- if (sboxPower === undefined) sboxPower = 5;
29
- if (typeof sboxPower !== 'number' || !Number.isSafeInteger(sboxPower))
30
- throw new Error(`Poseidon wrong sboxPower=${sboxPower}`);
31
26
 
32
- const _sboxPower = BigInt(sboxPower);
33
- let sboxFn = (n: bigint) => FpPow(Fp, n, _sboxPower);
34
- // Unwrapped sbox power for common cases (195->142μs)
35
- if (sboxPower === 3) sboxFn = (n: bigint) => Fp.mul(Fp.sqrN(n), n);
36
- else if (sboxPower === 5) sboxFn = (n: bigint) => Fp.mul(Fp.sqrN(Fp.sqrN(n)), n);
27
+ // MDS is TxT matrix
28
+ if (!Array.isArray(mds) || mds.length !== t) throw new Error('Poseidon: wrong MDS matrix');
29
+ const _mds = mds.map((mdsRow) => {
30
+ if (!Array.isArray(mdsRow) || mdsRow.length !== t)
31
+ throw new Error(`Poseidon MDS matrix row: ${mdsRow}`);
32
+ return mdsRow.map((i) => {
33
+ if (typeof i !== 'bigint') throw new Error(`Poseidon MDS matrix value=${i}`);
34
+ return Fp.create(i);
35
+ });
36
+ });
37
+
38
+ if (rev !== undefined && typeof rev !== 'boolean')
39
+ throw new Error(`Poseidon: invalid param reversePartialPowIdx=${rev}`);
37
40
 
38
- if (opts.roundsFull % 2 !== 0)
39
- throw new Error(`Poseidon roundsFull is not even: ${opts.roundsFull}`);
40
- const rounds = opts.roundsFull + opts.roundsPartial;
41
+ if (roundsFull % 2 !== 0) throw new Error(`Poseidon roundsFull is not even: ${roundsFull}`);
42
+ const rounds = roundsFull + roundsPartial;
41
43
 
42
- if (!Array.isArray(opts.roundConstants) || opts.roundConstants.length !== rounds)
44
+ if (!Array.isArray(rc) || rc.length !== rounds)
43
45
  throw new Error('Poseidon: wrong round constants');
44
- const roundConstants = opts.roundConstants.map((rc) => {
45
- if (!Array.isArray(rc) || rc.length !== opts.t)
46
+ const roundConstants = rc.map((rc) => {
47
+ if (!Array.isArray(rc) || rc.length !== t)
46
48
  throw new Error(`Poseidon wrong round constants: ${rc}`);
47
49
  return rc.map((i) => {
48
50
  if (typeof i !== 'bigint' || !Fp.isValid(i))
@@ -50,18 +52,16 @@ export function validateOpts(opts: PoseidonOpts) {
50
52
  return Fp.create(i);
51
53
  });
52
54
  });
53
- // MDS is TxT matrix
54
- if (!Array.isArray(opts.mds) || opts.mds.length !== opts.t)
55
- throw new Error('Poseidon: wrong MDS matrix');
56
- const mds = opts.mds.map((mdsRow) => {
57
- if (!Array.isArray(mdsRow) || mdsRow.length !== opts.t)
58
- throw new Error(`Poseidon MDS matrix row: ${mdsRow}`);
59
- return mdsRow.map((i) => {
60
- if (typeof i !== 'bigint') throw new Error(`Poseidon MDS matrix value=${i}`);
61
- return Fp.create(i);
62
- });
63
- });
64
- return Object.freeze({ ...opts, rounds, sboxFn, roundConstants, mds });
55
+
56
+ if (!sboxPower || ![3, 5, 7].includes(sboxPower))
57
+ throw new Error(`Poseidon wrong sboxPower=${sboxPower}`);
58
+ const _sboxPower = BigInt(sboxPower);
59
+ let sboxFn = (n: bigint) => FpPow(Fp, n, _sboxPower);
60
+ // Unwrapped sbox power for common cases (195->142μs)
61
+ if (sboxPower === 3) sboxFn = (n: bigint) => Fp.mul(Fp.sqrN(n), n);
62
+ else if (sboxPower === 5) sboxFn = (n: bigint) => Fp.mul(Fp.sqrN(Fp.sqrN(n)), n);
63
+
64
+ return Object.freeze({ ...opts, rounds, sboxFn, roundConstants, mds: _mds });
65
65
  }
66
66
 
67
67
  export function splitConstants(rc: bigint[], t: number) {
@@ -80,18 +80,17 @@ export function splitConstants(rc: bigint[], t: number) {
80
80
  }
81
81
 
82
82
  export function poseidon(opts: PoseidonOpts) {
83
- const { t, Fp, rounds, sboxFn, reversePartialPowIdx } = validateOpts(opts);
84
- const halfRoundsFull = Math.floor(opts.roundsFull / 2);
85
- const partialIdx = reversePartialPowIdx ? t - 1 : 0;
83
+ const _opts = validateOpts(opts);
84
+ const { Fp, mds, roundConstants, rounds, roundsPartial, sboxFn, t } = _opts;
85
+ const halfRoundsFull = _opts.roundsFull / 2;
86
+ const partialIdx = _opts.reversePartialPowIdx ? t - 1 : 0;
86
87
  const poseidonRound = (values: bigint[], isFull: boolean, idx: number) => {
87
- values = values.map((i, j) => Fp.add(i, opts.roundConstants[idx][j]));
88
+ values = values.map((i, j) => Fp.add(i, roundConstants[idx][j]));
88
89
 
89
90
  if (isFull) values = values.map((i) => sboxFn(i));
90
91
  else values[partialIdx] = sboxFn(values[partialIdx]);
91
92
  // Matrix multiplication
92
- values = opts.mds.map((i) =>
93
- i.reduce((acc, i, j) => Fp.add(acc, Fp.mulN(i, values[j])), Fp.ZERO)
94
- );
93
+ values = mds.map((i) => i.reduce((acc, i, j) => Fp.add(acc, Fp.mulN(i, values[j])), Fp.ZERO));
95
94
  return values;
96
95
  };
97
96
  const poseidonHash = function poseidonHash(values: bigint[]) {
@@ -105,7 +104,7 @@ export function poseidon(opts: PoseidonOpts) {
105
104
  // Apply r_f/2 full rounds.
106
105
  for (let i = 0; i < halfRoundsFull; i++) values = poseidonRound(values, true, round++);
107
106
  // Apply r_p partial rounds.
108
- for (let i = 0; i < opts.roundsPartial; i++) values = poseidonRound(values, false, round++);
107
+ for (let i = 0; i < roundsPartial; i++) values = poseidonRound(values, false, round++);
109
108
  // Apply r_f/2 full rounds.
110
109
  for (let i = 0; i < halfRoundsFull; i++) values = poseidonRound(values, true, round++);
111
110
 
@@ -114,6 +113,6 @@ export function poseidon(opts: PoseidonOpts) {
114
113
  return values;
115
114
  };
116
115
  // For verification in tests
117
- poseidonHash.roundConstants = opts.roundConstants;
116
+ poseidonHash.roundConstants = roundConstants;
118
117
  return poseidonHash;
119
118
  }
@@ -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,12 +16,22 @@ export type CHash = {
17
16
  };
18
17
  export type FHash = (message: Uint8Array | string) => Uint8Array;
19
18
 
20
- const hexes = Array.from({ length: 256 }, (v, i) => i.toString(16).padStart(2, '0'));
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'
27
+ const hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) =>
28
+ i.toString(16).padStart(2, '0')
29
+ );
21
30
  /**
22
31
  * @example bytesToHex(Uint8Array.from([0xca, 0xfe, 0x01, 0x23])) // 'cafe0123'
23
32
  */
24
33
  export function bytesToHex(bytes: Uint8Array): string {
25
- if (!u8a(bytes)) throw new Error('Uint8Array expected');
34
+ if (!isBytes(bytes)) throw new Error('Uint8Array expected');
26
35
  // pre-caching improves the speed 6x
27
36
  let hex = '';
28
37
  for (let i = 0; i < bytes.length; i++) {
@@ -42,20 +51,32 @@ export function hexToNumber(hex: string): bigint {
42
51
  return BigInt(hex === '' ? '0' : `0x${hex}`);
43
52
  }
44
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
+
45
63
  /**
46
64
  * @example hexToBytes('cafe0123') // Uint8Array.from([0xca, 0xfe, 0x01, 0x23])
47
65
  */
48
66
  export function hexToBytes(hex: string): Uint8Array {
49
67
  if (typeof hex !== 'string') throw new Error('hex string expected, got ' + typeof hex);
50
- const len = hex.length;
51
- if (len % 2) throw new Error('padded hex string expected, got unpadded hex of length ' + len);
52
- const array = new Uint8Array(len / 2);
53
- for (let i = 0; i < array.length; i++) {
54
- const j = i * 2;
55
- const hexByte = hex.slice(j, j + 2);
56
- const byte = Number.parseInt(hexByte, 16);
57
- if (Number.isNaN(byte) || byte < 0) throw new Error('Invalid byte sequence');
58
- 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;
59
80
  }
60
81
  return array;
61
82
  }
@@ -65,7 +86,7 @@ export function bytesToNumberBE(bytes: Uint8Array): bigint {
65
86
  return hexToNumber(bytesToHex(bytes));
66
87
  }
67
88
  export function bytesToNumberLE(bytes: Uint8Array): bigint {
68
- if (!u8a(bytes)) throw new Error('Uint8Array expected');
89
+ if (!isBytes(bytes)) throw new Error('Uint8Array expected');
69
90
  return hexToNumber(bytesToHex(Uint8Array.from(bytes).reverse()));
70
91
  }
71
92
 
@@ -97,7 +118,7 @@ export function ensureBytes(title: string, hex: Hex, expectedLength?: number): U
97
118
  } catch (e) {
98
119
  throw new Error(`${title} must be valid hex string, got "${hex}". Cause: ${e}`);
99
120
  }
100
- } else if (u8a(hex)) {
121
+ } else if (isBytes(hex)) {
101
122
  // Uint8Array.from() instead of hash.slice() because node.js Buffer
102
123
  // is instance of Uint8Array, and its slice() creates **mutable** copy
103
124
  res = Uint8Array.from(hex);
@@ -114,21 +135,28 @@ export function ensureBytes(title: string, hex: Hex, expectedLength?: number): U
114
135
  * Copies several Uint8Arrays into one.
115
136
  */
116
137
  export function concatBytes(...arrays: Uint8Array[]): Uint8Array {
117
- const r = new Uint8Array(arrays.reduce((sum, a) => sum + a.length, 0));
118
- let pad = 0; // walk through each item, ensure they have proper type
119
- arrays.forEach((a) => {
120
- if (!u8a(a)) throw new Error('Uint8Array expected');
121
- 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);
122
149
  pad += a.length;
123
- });
124
- return r;
150
+ }
151
+ return res;
125
152
  }
126
153
 
127
- export function equalBytes(b1: Uint8Array, b2: Uint8Array) {
128
- // We don't care about timing attacks here
129
- if (b1.length !== b2.length) return false;
130
- for (let i = 0; i < b1.length; i++) if (b1[i] !== b2[i]) return false;
131
- 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;
132
160
  }
133
161
 
134
162
  // Global symbols in both browsers and Node.js since v11
@@ -246,6 +274,7 @@ const validatorFns = {
246
274
  function: (val: any) => typeof val === 'function',
247
275
  boolean: (val: any) => typeof val === 'boolean',
248
276
  string: (val: any) => typeof val === 'string',
277
+ stringOrUint8Array: (val: any) => typeof val === 'string' || isBytes(val),
249
278
  isSafeInteger: (val: any) => Number.isSafeInteger(val),
250
279
  array: (val: any) => Array.isArray(val),
251
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,13 +188,13 @@ 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
 
194
195
  const toBytes =
195
196
  CURVE.toBytes ||
196
- ((c: ProjConstructor<T>, point: ProjPointType<T>, isCompressed: boolean) => {
197
+ ((_c: ProjConstructor<T>, point: ProjPointType<T>, _isCompressed: boolean) => {
197
198
  const a = point.toAffine();
198
199
  return ut.concatBytes(Uint8Array.from([0x04]), Fp.toBytes(a.x), Fp.toBytes(a.y));
199
200
  });
@@ -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');
@@ -333,9 +338,11 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
333
338
 
334
339
  // A point on curve is valid if it conforms to equation.
335
340
  assertValidity(): void {
336
- // Zero is valid point too!
337
341
  if (this.is0()) {
338
- if (CURVE.allowInfinityPoint) return;
342
+ // (0, 1, 0) aka ZERO is invalid in most contexts.
343
+ // In BLS, ZERO can be serialized, so we allow it.
344
+ // (0, 0, 0) is wrong representation of ZERO and is always invalid.
345
+ if (CURVE.allowInfinityPoint && !Fp.is0(this.py)) return;
339
346
  throw new Error('bad point: ZERO');
340
347
  }
341
348
  // Some 3rd-party test vectors require different wording between here & `fromCompressedHex`
@@ -707,7 +714,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
707
714
  isWithinCurveOrder,
708
715
  } = weierstrassPoints({
709
716
  ...CURVE,
710
- toBytes(c, point, isCompressed: boolean): Uint8Array {
717
+ toBytes(_c, point, isCompressed: boolean): Uint8Array {
711
718
  const a = point.toAffine();
712
719
  const x = Fp.toBytes(a.x);
713
720
  const cat = ut.concatBytes;
@@ -761,7 +768,11 @@ export function weierstrass(curveDef: CurveType): CurveFn {
761
768
  * ECDSA signature with its (r, s) properties. Supports DER & compact representations.
762
769
  */
763
770
  class Signature implements SignatureType {
764
- 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
+ ) {
765
776
  this.assertValidity();
766
777
  }
767
778
 
@@ -845,13 +856,12 @@ export function weierstrass(curveDef: CurveType): CurveFn {
845
856
  normPrivateKeyToScalar: normPrivateKeyToScalar,
846
857
 
847
858
  /**
848
- * Produces cryptographically secure private key from random of size (nBitLength+64)
849
- * as per FIPS 186 B.4.1 with modulo bias being neglible.
859
+ * Produces cryptographically secure private key from random of size
860
+ * (groupLen + ceil(groupLen / 2)) with modulo bias being negligible.
850
861
  */
851
862
  randomPrivateKey: (): Uint8Array => {
852
- const rand = CURVE.randomBytes(Fp.BYTES + 8);
853
- const num = mod.hashToPrivateScalar(rand, CURVE_ORDER);
854
- return ut.numberToBytesBE(num, CURVE.nByteLength);
863
+ const length = mod.getMinHashLength(CURVE.n);
864
+ return mod.mapHashToField(CURVE.randomBytes(length), CURVE.n);
855
865
  },
856
866
 
857
867
  /**
@@ -883,7 +893,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
883
893
  * Quick and dirty check for item being public key. Does not validate hex, or being on-curve.
884
894
  */
885
895
  function isProbPub(item: PrivKey | PubKey): boolean {
886
- const arr = item instanceof Uint8Array;
896
+ const arr = ut.isBytes(item);
887
897
  const str = typeof item === 'string';
888
898
  const len = (arr || str) && (item as Hex).length;
889
899
  if (arr) return len === compressedLen || len === uncompressedLen;
@@ -964,7 +974,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
964
974
  if (ent != null) {
965
975
  // K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1) || k')
966
976
  const e = ent === true ? randomBytes(Fp.BYTES) : ent; // generate random bytes OR pass as-is
967
- seedArgs.push(ensureBytes('extraEntropy', e, Fp.BYTES)); // check for being of size BYTES
977
+ seedArgs.push(ensureBytes('extraEntropy', e)); // check for being bytes
968
978
  }
969
979
  const seed = ut.concatBytes(...seedArgs); // Step D of RFC6979 3.2
970
980
  const m = h1int; // NOTE: no need to call bits2int second time here, it is inside truncateHash!
@@ -1047,7 +1057,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
1047
1057
  let _sig: Signature | undefined = undefined;
1048
1058
  let P: ProjPointType<bigint>;
1049
1059
  try {
1050
- if (typeof sg === 'string' || sg instanceof Uint8Array) {
1060
+ if (typeof sg === 'string' || ut.isBytes(sg)) {
1051
1061
  // Signature can be represented in 2 ways: compact (2*nByteLength) & DER (variable-length).
1052
1062
  // Since DER can also be 2*nByteLength bytes, we check for it first.
1053
1063
  try {
@@ -1170,7 +1180,8 @@ export function SWUFpSqrtRatio<T>(Fp: mod.IField<T>, Z: T) {
1170
1180
  return sqrtRatio;
1171
1181
  }
1172
1182
  /**
1173
- * From draft-irtf-cfrg-hash-to-curve-16
1183
+ * Simplified Shallue-van de Woestijne-Ulas Method
1184
+ * https://www.rfc-editor.org/rfc/rfc9380#section-6.6.2
1174
1185
  */
1175
1186
  export function mapToCurveSimpleSWU<T>(
1176
1187
  Fp: mod.IField<T>,