@noble/curves 1.5.0 → 1.6.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 (62) hide show
  1. package/README.md +26 -7
  2. package/_shortw_utils.d.ts.map +1 -1
  3. package/abstract/curve.d.ts +12 -0
  4. package/abstract/curve.d.ts.map +1 -1
  5. package/abstract/curve.js +55 -0
  6. package/abstract/curve.js.map +1 -1
  7. package/abstract/edwards.d.ts +1 -0
  8. package/abstract/edwards.d.ts.map +1 -1
  9. package/abstract/edwards.js +5 -0
  10. package/abstract/edwards.js.map +1 -1
  11. package/abstract/hash-to-curve.d.ts.map +1 -1
  12. package/abstract/hash-to-curve.js +4 -2
  13. package/abstract/hash-to-curve.js.map +1 -1
  14. package/abstract/tower.d.ts +1 -0
  15. package/abstract/tower.d.ts.map +1 -1
  16. package/abstract/tower.js +1 -0
  17. package/abstract/tower.js.map +1 -1
  18. package/abstract/weierstrass.d.ts +18 -3
  19. package/abstract/weierstrass.d.ts.map +1 -1
  20. package/abstract/weierstrass.js +101 -41
  21. package/abstract/weierstrass.js.map +1 -1
  22. package/esm/_shortw_utils.d.ts.map +1 -1
  23. package/esm/abstract/curve.d.ts +12 -0
  24. package/esm/abstract/curve.d.ts.map +1 -1
  25. package/esm/abstract/curve.js +55 -1
  26. package/esm/abstract/curve.js.map +1 -1
  27. package/esm/abstract/edwards.d.ts +1 -0
  28. package/esm/abstract/edwards.d.ts.map +1 -1
  29. package/esm/abstract/edwards.js +7 -2
  30. package/esm/abstract/edwards.js.map +1 -1
  31. package/esm/abstract/hash-to-curve.d.ts.map +1 -1
  32. package/esm/abstract/hash-to-curve.js +4 -2
  33. package/esm/abstract/hash-to-curve.js.map +1 -1
  34. package/esm/abstract/tower.d.ts +1 -0
  35. package/esm/abstract/tower.d.ts.map +1 -1
  36. package/esm/abstract/tower.js +1 -0
  37. package/esm/abstract/tower.js.map +1 -1
  38. package/esm/abstract/weierstrass.d.ts +18 -3
  39. package/esm/abstract/weierstrass.d.ts.map +1 -1
  40. package/esm/abstract/weierstrass.js +102 -42
  41. package/esm/abstract/weierstrass.js.map +1 -1
  42. package/esm/jubjub.d.ts.map +1 -1
  43. package/esm/jubjub.js +8 -2
  44. package/esm/jubjub.js.map +1 -1
  45. package/esm/p256.d.ts.map +1 -1
  46. package/esm/p384.d.ts.map +1 -1
  47. package/esm/p521.d.ts.map +1 -1
  48. package/esm/secp256k1.d.ts.map +1 -1
  49. package/jubjub.d.ts.map +1 -1
  50. package/jubjub.js +8 -2
  51. package/jubjub.js.map +1 -1
  52. package/p256.d.ts.map +1 -1
  53. package/p384.d.ts.map +1 -1
  54. package/p521.d.ts.map +1 -1
  55. package/package.json +26 -19
  56. package/secp256k1.d.ts.map +1 -1
  57. package/src/abstract/curve.ts +57 -1
  58. package/src/abstract/edwards.ts +16 -2
  59. package/src/abstract/hash-to-curve.ts +3 -1
  60. package/src/abstract/tower.ts +1 -0
  61. package/src/abstract/weierstrass.ts +95 -37
  62. package/src/jubjub.ts +7 -2
@@ -1 +1 @@
1
- {"version":3,"file":"secp256k1.d.ts","sourceRoot":"","sources":["src/secp256k1.ts"],"names":[],"mappings":"AAKA,OAAO,EAAS,GAAG,EAAQ,MAAM,uBAAuB,CAAC;AACzD,OAAO,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAGL,eAAe,EAGf,eAAe,EAChB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,aAAa,IAAI,SAAS,EAAuB,MAAM,2BAA2B,CAAC;AAsC5F;;GAEG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0CAsO0nZ,CAAC;;;;;;oDAAwmB,CAAC;mEAA2F,CAAC;+CAAuE,CAAC;;;;yCAAoH,CAAC;;;;;;;+BAA+R,CAAC,eAAe,CAAC;;EA3Ll0b,CAAC;AAOF,iBAAS,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,QAAQ,EAAE,UAAU,EAAE,GAAG,UAAU,CAQtE;AAkBD;;;GAGG;AACH,iBAAS,MAAM,CAAC,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,CAS5C;AASD;;GAEG;AACH,iBAAS,mBAAmB,CAAC,UAAU,EAAE,GAAG,GAAG,UAAU,CAExD;AAED;;;GAGG;AACH,iBAAS,WAAW,CAClB,OAAO,EAAE,GAAG,EACZ,UAAU,EAAE,OAAO,EACnB,OAAO,GAAE,GAAqB,GAC7B,UAAU,CAgBZ;AAED;;;GAGG;AACH,iBAAS,aAAa,CAAC,SAAS,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,GAAG,OAAO,CAiB5E;AAED;;GAEG;AACH,eAAO,MAAM,OAAO;;;;;;;8BAhGS,SAAS,CAAC,MAAM,CAAC;;;;;;CA6GzC,CAAC;AA0DN,eAAO,MAAM,WAAW,2IAA4C,CAAC;AACrE,eAAO,MAAM,aAAa,2IAA8C,CAAC"}
1
+ {"version":3,"file":"secp256k1.d.ts","sourceRoot":"","sources":["src/secp256k1.ts"],"names":[],"mappings":"AAKA,OAAO,EAAS,GAAG,EAAQ,MAAM,uBAAuB,CAAC;AACzD,OAAO,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAGL,eAAe,EAGf,eAAe,EAChB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,aAAa,IAAI,SAAS,EAAuB,MAAM,2BAA2B,CAAC;AAsC5F;;GAEG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0CAsOqje,CAAC;;;;;;oDAAwmB,CAAC;mEAA2F,CAAC;+CAAuE,CAAC;;;;yCAAoH,CAAC;;;;;;;+BAA+R,CAAC,eAAe,CAAC;;EA3L7vgB,CAAC;AAOF,iBAAS,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,QAAQ,EAAE,UAAU,EAAE,GAAG,UAAU,CAQtE;AAkBD;;;GAGG;AACH,iBAAS,MAAM,CAAC,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,CAS5C;AASD;;GAEG;AACH,iBAAS,mBAAmB,CAAC,UAAU,EAAE,GAAG,GAAG,UAAU,CAExD;AAED;;;GAGG;AACH,iBAAS,WAAW,CAClB,OAAO,EAAE,GAAG,EACZ,UAAU,EAAE,OAAO,EACnB,OAAO,GAAE,GAAqB,GAC7B,UAAU,CAgBZ;AAED;;;GAGG;AACH,iBAAS,aAAa,CAAC,SAAS,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,GAAG,OAAO,CAiB5E;AAED;;GAEG;AACH,eAAO,MAAM,OAAO;;;;;;;8BAhGS,SAAS,CAAC,MAAM,CAAC;;;;;;CA6GzC,CAAC;AA0DN,eAAO,MAAM,WAAW,2IAA4C,CAAC;AACrE,eAAO,MAAM,aAAa,2IAA8C,CAAC"}
@@ -1,7 +1,7 @@
1
1
  /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
2
2
  // Abelian group utilities
3
3
  import { IField, validateField, nLength } from './modular.js';
4
- import { validateObject } from './utils.js';
4
+ import { validateObject, bitLen } from './utils.js';
5
5
  const _0n = BigInt(0);
6
6
  const _1n = BigInt(1);
7
7
 
@@ -181,6 +181,62 @@ export function wNAF<T extends Group<T>>(c: GroupConstructor<T>, bits: number) {
181
181
  };
182
182
  }
183
183
 
184
+ /**
185
+ * Pippenger algorithm for multi-scalar multiplication (MSM).
186
+ * MSM is basically (Pa + Qb + Rc + ...).
187
+ * 30x faster vs naive addition on L=4096, 10x faster with precomputes.
188
+ * For N=254bit, L=1, it does: 1024 ADD + 254 DBL. For L=5: 1536 ADD + 254 DBL.
189
+ * Algorithmically constant-time (for same L), even when 1 point + scalar, or when scalar = 0.
190
+ * @param c Curve Point constructor
191
+ * @param field field over CURVE.N - important that it's not over CURVE.P
192
+ * @param points array of L curve points
193
+ * @param scalars array of L scalars (aka private keys / bigints)
194
+ */
195
+ export function pippenger<T extends Group<T>>(
196
+ c: GroupConstructor<T>,
197
+ field: IField<bigint>,
198
+ points: T[],
199
+ scalars: bigint[]
200
+ ): T {
201
+ // If we split scalars by some window (let's say 8 bits), every chunk will only
202
+ // take 256 buckets even if there are 4096 scalars, also re-uses double.
203
+ // TODO:
204
+ // - https://eprint.iacr.org/2024/750.pdf
205
+ // - https://tches.iacr.org/index.php/TCHES/article/view/10287
206
+ // 0 is accepted in scalars
207
+ if (!Array.isArray(points) || !Array.isArray(scalars) || scalars.length !== points.length)
208
+ throw new Error('arrays of points and scalars must have equal length');
209
+ scalars.forEach((s, i) => {
210
+ if (!field.isValid(s)) throw new Error(`wrong scalar at index ${i}`);
211
+ });
212
+ points.forEach((p, i) => {
213
+ if (!(p instanceof (c as any))) throw new Error(`wrong point at index ${i}`);
214
+ });
215
+ const wbits = bitLen(BigInt(points.length));
216
+ const windowSize = wbits > 12 ? wbits - 3 : wbits > 4 ? wbits - 2 : wbits ? 2 : 1; // in bits
217
+ const MASK = (1 << windowSize) - 1;
218
+ const buckets = new Array(MASK + 1).fill(c.ZERO); // +1 for zero array
219
+ const lastBits = Math.floor((field.BITS - 1) / windowSize) * windowSize;
220
+ let sum = c.ZERO;
221
+ for (let i = lastBits; i >= 0; i -= windowSize) {
222
+ buckets.fill(c.ZERO);
223
+ for (let j = 0; j < scalars.length; j++) {
224
+ const scalar = scalars[j];
225
+ const wbits = Number((scalar >> BigInt(i)) & BigInt(MASK));
226
+ buckets[wbits] = buckets[wbits].add(points[j]);
227
+ }
228
+ let resI = c.ZERO; // not using this will do small speed-up, but will lose ct
229
+ // Skip first bucket, because it is zero
230
+ for (let j = buckets.length - 1, sumI = c.ZERO; j > 0; j--) {
231
+ sumI = sumI.add(buckets[j]);
232
+ resI = resI.add(sumI);
233
+ }
234
+ sum = sum.add(resI);
235
+ if (i !== 0) for (let j = 0; j < windowSize; j++) sum = sum.double();
236
+ }
237
+ return sum as T;
238
+ }
239
+
184
240
  // Generic BasicCurve interface: works even for polynomial fields (BLS): P, n, h would be ok.
185
241
  // Though generator can be different (Fp2 / Fp6 for BLS).
186
242
  export type BasicCurve<T> = {
@@ -1,7 +1,15 @@
1
1
  /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
2
2
  // Twisted Edwards curve. The formula is: ax² + y² = 1 + dx²y²
3
- import { AffinePoint, BasicCurve, Group, GroupConstructor, validateBasic, wNAF } from './curve.js';
4
- import { mod } from './modular.js';
3
+ import {
4
+ AffinePoint,
5
+ BasicCurve,
6
+ Group,
7
+ GroupConstructor,
8
+ validateBasic,
9
+ wNAF,
10
+ pippenger,
11
+ } from './curve.js';
12
+ import { mod, Field } from './modular.js';
5
13
  import * as ut from './utils.js';
6
14
  import { ensureBytes, FHash, Hex, memoized, abool } from './utils.js';
7
15
 
@@ -70,6 +78,7 @@ export interface ExtPointConstructor extends GroupConstructor<ExtPointType> {
70
78
  fromAffine(p: AffinePoint<bigint>): ExtPointType;
71
79
  fromHex(hex: Hex): ExtPointType;
72
80
  fromPrivateKey(privateKey: Hex): ExtPointType;
81
+ msm(points: ExtPointType[], scalars: bigint[]): ExtPointType;
73
82
  }
74
83
 
75
84
  /**
@@ -119,6 +128,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
119
128
  } = CURVE;
120
129
  const MASK = _2n << (BigInt(nByteLength * 8) - _1n);
121
130
  const modP = Fp.create; // Function overrides
131
+ const Fn = Field(CURVE.n, CURVE.nBitLength);
122
132
 
123
133
  // sqrt(u/v)
124
134
  const uvRatio =
@@ -218,6 +228,10 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
218
228
  const toInv = Fp.invertBatch(points.map((p) => p.ez));
219
229
  return points.map((p, i) => p.toAffine(toInv[i])).map(Point.fromAffine);
220
230
  }
231
+ // Multiscalar Multiplication
232
+ static msm(points: Point[], scalars: bigint[]) {
233
+ return pippenger(Point, Fn, points, scalars);
234
+ }
221
235
 
222
236
  // "Private method", don't use it directly
223
237
  _setWindowSize(windowSize: number) {
@@ -27,6 +27,8 @@ const os2ip = bytesToNumberBE;
27
27
 
28
28
  // Integer to Octet Stream (numberToBytesBE)
29
29
  function i2osp(value: number, length: number): Uint8Array {
30
+ anum(value);
31
+ anum(length);
30
32
  if (value < 0 || value >= 1 << (8 * length)) {
31
33
  throw new Error(`bad I2OSP call: value=${value} length=${length}`);
32
34
  }
@@ -65,7 +67,7 @@ export function expand_message_xmd(
65
67
  if (DST.length > 255) DST = H(concatBytes(utf8ToBytes('H2C-OVERSIZE-DST-'), DST));
66
68
  const { outputLen: b_in_bytes, blockLen: r_in_bytes } = H;
67
69
  const ell = Math.ceil(lenInBytes / b_in_bytes);
68
- if (ell > 255) throw new Error('Invalid xmd length');
70
+ if (lenInBytes > 65535 || ell > 255) throw new Error('expand_message_xmd: invalid lenInBytes');
69
71
  const DST_prime = concatBytes(DST, i2osp(DST.length, 1));
70
72
  const Z_pad = i2osp(0, r_in_bytes);
71
73
  const l_i_b_str = i2osp(lenInBytes, 2); // len_in_bytes_str
@@ -1,3 +1,4 @@
1
+ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
1
2
  import * as mod from './modular.js';
2
3
  import { bitLen, bitMask, concatBytes, notImplemented } from './utils.js';
3
4
  import type { ProjConstructor, ProjPointType } from './weierstrass.js';
@@ -1,6 +1,14 @@
1
1
  /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
2
2
  // Short Weierstrass curve. The formula is: y² = x³ + ax + b
3
- import { AffinePoint, BasicCurve, Group, GroupConstructor, validateBasic, wNAF } from './curve.js';
3
+ import {
4
+ AffinePoint,
5
+ BasicCurve,
6
+ Group,
7
+ GroupConstructor,
8
+ validateBasic,
9
+ wNAF,
10
+ pippenger,
11
+ } from './curve.js';
4
12
  import * as mod from './modular.js';
5
13
  import * as ut from './utils.js';
6
14
  import { CHash, Hex, PrivKey, ensureBytes, memoized, abool } from './utils.js';
@@ -85,6 +93,7 @@ export interface ProjConstructor<T> extends GroupConstructor<ProjPointType<T>> {
85
93
  fromHex(hex: Hex): ProjPointType<T>;
86
94
  fromPrivateKey(privateKey: PrivKey): ProjPointType<T>;
87
95
  normalizeZ(points: ProjPointType<T>[]): ProjPointType<T>[];
96
+ msm(points: ProjPointType<T>[], scalars: bigint[]): ProjPointType<T>;
88
97
  }
89
98
 
90
99
  export type CurvePointsType<T> = BasicWCurve<T> & {
@@ -135,8 +144,15 @@ export type CurvePointsRes<T> = {
135
144
  isWithinCurveOrder: (num: bigint) => boolean;
136
145
  };
137
146
 
138
- // ASN.1 DER encoding utilities
139
147
  const { bytesToNumberBE: b2n, hexToBytes: h2b } = ut;
148
+
149
+ /**
150
+ * ASN.1 DER encoding utilities. ASN is very complex & fragile. Format:
151
+ *
152
+ * [0x30 (SEQUENCE), bytelength, 0x02 (INTEGER), intLength, R, 0x02 (INTEGER), intLength, S]
153
+ *
154
+ * Docs: https://letsencrypt.org/docs/a-warm-welcome-to-asn1-and-der/, https://luca.ntop.org/Teaching/Appunti/asn1.html
155
+ */
140
156
  export const DER = {
141
157
  // asn.1 DER encoding utils
142
158
  Err: class DERErr extends Error {
@@ -144,48 +160,84 @@ export const DER = {
144
160
  super(m);
145
161
  }
146
162
  },
147
- _parseInt(data: Uint8Array): { d: bigint; l: Uint8Array } {
148
- const { Err: E } = DER;
149
- if (data.length < 2 || data[0] !== 0x02) throw new E('Invalid signature integer tag');
150
- const len = data[1];
151
- const res = data.subarray(2, len + 2);
152
- if (!len || res.length !== len) throw new E('Invalid signature integer: wrong length');
153
- // https://crypto.stackexchange.com/a/57734 Leftmost bit of first byte is 'negative' flag,
154
- // since we always use positive integers here. It must always be empty:
155
- // - add zero byte if exists
156
- // - if next byte doesn't have a flag, leading zero is not allowed (minimal encoding)
157
- if (res[0] & 0b10000000) throw new E('Invalid signature integer: negative');
158
- if (res[0] === 0x00 && !(res[1] & 0b10000000))
159
- throw new E('Invalid signature integer: unnecessary leading zero');
160
- return { d: b2n(res), l: data.subarray(len + 2) }; // d is data, l is left
163
+ // Basic building block is TLV (Tag-Length-Value)
164
+ _tlv: {
165
+ encode: (tag: number, data: string) => {
166
+ const { Err: E } = DER;
167
+ if (tag < 0 || tag > 256) throw new E('tlv.encode: wrong tag');
168
+ if (data.length & 1) throw new E('tlv.encode: unpadded data');
169
+ const dataLen = data.length / 2;
170
+ const len = ut.numberToHexUnpadded(dataLen);
171
+ if ((len.length / 2) & 0b1000_0000) throw new E('tlv.encode: long form length too big');
172
+ // length of length with long form flag
173
+ const lenLen = dataLen > 127 ? ut.numberToHexUnpadded((len.length / 2) | 0b1000_0000) : '';
174
+ return `${ut.numberToHexUnpadded(tag)}${lenLen}${len}${data}`;
175
+ },
176
+ // v - value, l - left bytes (unparsed)
177
+ decode(tag: number, data: Uint8Array): { v: Uint8Array; l: Uint8Array } {
178
+ const { Err: E } = DER;
179
+ let pos = 0;
180
+ if (tag < 0 || tag > 256) throw new E('tlv.encode: wrong tag');
181
+ if (data.length < 2 || data[pos++] !== tag) throw new E('tlv.decode: wrong tlv');
182
+ const first = data[pos++];
183
+ const isLong = !!(first & 0b1000_0000); // First bit of first length byte is flag for short/long form
184
+ let length = 0;
185
+ if (!isLong) length = first;
186
+ else {
187
+ // Long form: [longFlag(1bit), lengthLength(7bit), length (BE)]
188
+ const lenLen = first & 0b0111_1111;
189
+ if (!lenLen) throw new E('tlv.decode(long): indefinite length not supported');
190
+ if (lenLen > 4) throw new E('tlv.decode(long): byte length is too big'); // this will overflow u32 in js
191
+ const lengthBytes = data.subarray(pos, pos + lenLen);
192
+ if (lengthBytes.length !== lenLen) throw new E('tlv.decode: length bytes not complete');
193
+ if (lengthBytes[0] === 0) throw new E('tlv.decode(long): zero leftmost byte');
194
+ for (const b of lengthBytes) length = (length << 8) | b;
195
+ pos += lenLen;
196
+ if (length < 128) throw new E('tlv.decode(long): not minimal encoding');
197
+ }
198
+ const v = data.subarray(pos, pos + length);
199
+ if (v.length !== length) throw new E('tlv.decode: wrong value length');
200
+ return { v, l: data.subarray(pos + length) };
201
+ },
202
+ },
203
+ // https://crypto.stackexchange.com/a/57734 Leftmost bit of first byte is 'negative' flag,
204
+ // since we always use positive integers here. It must always be empty:
205
+ // - add zero byte if exists
206
+ // - if next byte doesn't have a flag, leading zero is not allowed (minimal encoding)
207
+ _int: {
208
+ encode(num: bigint) {
209
+ const { Err: E } = DER;
210
+ if (num < _0n) throw new E('integer: negative integers are not allowed');
211
+ let hex = ut.numberToHexUnpadded(num);
212
+ // Pad with zero byte if negative flag is present
213
+ if (Number.parseInt(hex[0], 16) & 0b1000) hex = '00' + hex;
214
+ if (hex.length & 1) throw new E('unexpected assertion');
215
+ return hex;
216
+ },
217
+ decode(data: Uint8Array): bigint {
218
+ const { Err: E } = DER;
219
+ if (data[0] & 0b1000_0000) throw new E('Invalid signature integer: negative');
220
+ if (data[0] === 0x00 && !(data[1] & 0b1000_0000))
221
+ throw new E('Invalid signature integer: unnecessary leading zero');
222
+ return b2n(data);
223
+ },
161
224
  },
162
225
  toSig(hex: string | Uint8Array): { r: bigint; s: bigint } {
163
226
  // parse DER signature
164
- const { Err: E } = DER;
227
+ const { Err: E, _int: int, _tlv: tlv } = DER;
165
228
  const data = typeof hex === 'string' ? h2b(hex) : hex;
166
229
  ut.abytes(data);
167
- let l = data.length;
168
- if (l < 2 || data[0] != 0x30) throw new E('Invalid signature tag');
169
- if (data[1] !== l - 2) throw new E('Invalid signature: incorrect length');
170
- const { d: r, l: sBytes } = DER._parseInt(data.subarray(2));
171
- const { d: s, l: rBytesLeft } = DER._parseInt(sBytes);
172
- if (rBytesLeft.length) throw new E('Invalid signature: left bytes after parsing');
173
- return { r, s };
230
+ const { v: seqBytes, l: seqLeftBytes } = tlv.decode(0x30, data);
231
+ if (seqLeftBytes.length) throw new E('Invalid signature: left bytes after parsing');
232
+ const { v: rBytes, l: rLeftBytes } = tlv.decode(0x02, seqBytes);
233
+ const { v: sBytes, l: sLeftBytes } = tlv.decode(0x02, rLeftBytes);
234
+ if (sLeftBytes.length) throw new E('Invalid signature: left bytes after parsing');
235
+ return { r: int.decode(rBytes), s: int.decode(sBytes) };
174
236
  },
175
237
  hexFromSig(sig: { r: bigint; s: bigint }): string {
176
- // Add leading zero if first byte has negative bit enabled. More details in '_parseInt'
177
- const slice = (s: string): string => (Number.parseInt(s[0], 16) & 0b1000 ? '00' + s : s);
178
- const h = (num: number | bigint) => {
179
- const hex = num.toString(16);
180
- return hex.length & 1 ? `0${hex}` : hex;
181
- };
182
- const s = slice(h(sig.s));
183
- const r = slice(h(sig.r));
184
- const shl = s.length / 2;
185
- const rhl = r.length / 2;
186
- const sl = h(shl);
187
- const rl = h(rhl);
188
- return `30${h(rhl + shl + 4)}02${rl}${r}02${sl}${s}`;
238
+ const { _tlv: tlv, _int: int } = DER;
239
+ const seq = `${tlv.encode(0x02, int.encode(sig.r))}${tlv.encode(0x02, int.encode(sig.s))}`;
240
+ return tlv.encode(0x30, seq);
189
241
  },
190
242
  };
191
243
 
@@ -196,6 +248,7 @@ const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3), _4n =
196
248
  export function weierstrassPoints<T>(opts: CurvePointsType<T>): CurvePointsRes<T> {
197
249
  const CURVE = validatePointOpts(opts);
198
250
  const { Fp } = CURVE; // All curves has same field / group length as for now, but they can differ
251
+ const Fn = mod.Field(CURVE.n, CURVE.nBitLength);
199
252
 
200
253
  const toBytes =
201
254
  CURVE.toBytes ||
@@ -369,6 +422,11 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>): CurvePointsRes<T
369
422
  return Point.BASE.multiply(normPrivateKeyToScalar(privateKey));
370
423
  }
371
424
 
425
+ // Multiscalar Multiplication
426
+ static msm(points: Point[], scalars: bigint[]) {
427
+ return pippenger(Point, Fn, points, scalars);
428
+ }
429
+
372
430
  // "Private method", don't use it directly
373
431
  _setWindowSize(windowSize: number) {
374
432
  wnaf.setWindowSize(this, windowSize);
package/src/jubjub.ts CHANGED
@@ -46,13 +46,18 @@ export function groupHash(tag: Uint8Array, personalization: Uint8Array) {
46
46
  return p;
47
47
  }
48
48
 
49
+ // No secret data is leaked here at all.
50
+ // It operates over public data:
51
+ // const G_SPEND = jubjub.findGroupHash(new Uint8Array(), utf8ToBytes('Item_G_'));
49
52
  export function findGroupHash(m: Uint8Array, personalization: Uint8Array) {
50
53
  const tag = concatBytes(m, new Uint8Array([0]));
54
+ const hashes = [];
51
55
  for (let i = 0; i < 256; i++) {
52
56
  tag[tag.length - 1] = i;
53
57
  try {
54
- return groupHash(tag, personalization);
58
+ hashes.push(groupHash(tag, personalization));
55
59
  } catch (e) {}
56
60
  }
57
- throw new Error('findGroupHash tag overflow');
61
+ if (!hashes.length) throw new Error('findGroupHash tag overflow');
62
+ return hashes[0];
58
63
  }