@noble/curves 0.5.2 → 0.6.1

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 (61) hide show
  1. package/README.md +115 -41
  2. package/lib/_shortw_utils.d.ts +13 -24
  3. package/lib/abstract/bls.d.ts +39 -32
  4. package/lib/abstract/bls.js +74 -73
  5. package/lib/abstract/{group.d.ts → curve.d.ts} +30 -1
  6. package/lib/abstract/{group.js → curve.js} +33 -2
  7. package/lib/abstract/edwards.d.ts +30 -72
  8. package/lib/abstract/edwards.js +206 -389
  9. package/lib/abstract/hash-to-curve.d.ts +25 -6
  10. package/lib/abstract/hash-to-curve.js +40 -12
  11. package/lib/abstract/modular.d.ts +21 -8
  12. package/lib/abstract/modular.js +72 -48
  13. package/lib/abstract/montgomery.js +23 -68
  14. package/lib/abstract/poseidon.d.ts +29 -0
  15. package/lib/abstract/poseidon.js +115 -0
  16. package/lib/abstract/utils.d.ts +9 -37
  17. package/lib/abstract/utils.js +61 -87
  18. package/lib/abstract/weierstrass.d.ts +58 -81
  19. package/lib/abstract/weierstrass.js +485 -679
  20. package/lib/bls12-381.js +63 -58
  21. package/lib/bn.js +1 -1
  22. package/lib/ed25519.d.ts +7 -5
  23. package/lib/ed25519.js +82 -79
  24. package/lib/ed448.d.ts +3 -0
  25. package/lib/ed448.js +86 -83
  26. package/lib/esm/abstract/bls.js +75 -74
  27. package/lib/esm/abstract/{group.js → curve.js} +31 -1
  28. package/lib/esm/abstract/edwards.js +204 -387
  29. package/lib/esm/abstract/hash-to-curve.js +38 -11
  30. package/lib/esm/abstract/modular.js +69 -47
  31. package/lib/esm/abstract/montgomery.js +24 -69
  32. package/lib/esm/abstract/poseidon.js +109 -0
  33. package/lib/esm/abstract/utils.js +58 -82
  34. package/lib/esm/abstract/weierstrass.js +484 -678
  35. package/lib/esm/bls12-381.js +75 -70
  36. package/lib/esm/bn.js +1 -1
  37. package/lib/esm/ed25519.js +80 -78
  38. package/lib/esm/ed448.js +84 -82
  39. package/lib/esm/jubjub.js +1 -1
  40. package/lib/esm/p224.js +1 -1
  41. package/lib/esm/p256.js +11 -9
  42. package/lib/esm/p384.js +11 -9
  43. package/lib/esm/p521.js +12 -23
  44. package/lib/esm/secp256k1.js +124 -162
  45. package/lib/esm/stark.js +105 -41
  46. package/lib/jubjub.d.ts +2 -2
  47. package/lib/jubjub.js +1 -1
  48. package/lib/p192.d.ts +26 -48
  49. package/lib/p224.d.ts +26 -48
  50. package/lib/p224.js +1 -1
  51. package/lib/p256.d.ts +29 -48
  52. package/lib/p256.js +13 -10
  53. package/lib/p384.d.ts +29 -48
  54. package/lib/p384.js +13 -10
  55. package/lib/p521.d.ts +37 -57
  56. package/lib/p521.js +14 -24
  57. package/lib/secp256k1.d.ts +37 -46
  58. package/lib/secp256k1.js +124 -162
  59. package/lib/stark.d.ts +39 -22
  60. package/lib/stark.js +108 -41
  61. package/package.json +15 -10
@@ -3,9 +3,9 @@ import { sha256 } from '@noble/hashes/sha256';
3
3
  import { Fp as Field, mod, pow2 } from './abstract/modular.js';
4
4
  import { createCurve } from './_shortw_utils.js';
5
5
  import { mapToCurveSimpleSWU } from './abstract/weierstrass.js';
6
- import { ensureBytes, concatBytes, hexToBytes, bytesToNumberBE, } from './abstract/utils.js';
6
+ import { ensureBytes, concatBytes, bytesToNumberBE as bytesToInt, numberToBytesBE, } from './abstract/utils.js';
7
7
  import { randomBytes } from '@noble/hashes/utils';
8
- import { isogenyMap } from './abstract/hash-to-curve.js';
8
+ import * as htf from './abstract/hash-to-curve.js';
9
9
  /**
10
10
  * secp256k1 belongs to Koblitz curves: it has efficiently computable endomorphism.
11
11
  * Endomorphism uses 2x less RAM, speeds up precomputation by 2x and ECDH / key recovery by 20%.
@@ -19,10 +19,7 @@ const _1n = BigInt(1);
19
19
  const _2n = BigInt(2);
20
20
  const divNearest = (a, b) => (a + b / _2n) / b;
21
21
  /**
22
- * Allows to compute square root √y 2x faster.
23
- * To calculate √y, we need to exponentiate it to a very big number:
24
- * `y² = x³ + ax + b; y = y² ^ (p+1)/4`
25
- * We are unwrapping the loop and multiplying it bit-by-bit.
22
+ * √n = n^((p+1)/4) for fields p = 3 mod 4. We unwrap the loop and multiply bit-by-bit.
26
23
  * (P+1n/4n).toString(2) would produce bits [223x 1, 0, 22x 1, 4x 0, 11, 00]
27
24
  */
28
25
  function sqrtMod(y) {
@@ -45,45 +42,11 @@ function sqrtMod(y) {
45
42
  const t1 = (pow2(b223, _23n, P) * b22) % P;
46
43
  const t2 = (pow2(t1, _6n, P) * b2) % P;
47
44
  const root = pow2(t2, _2n, P);
48
- if (!Fp.equals(Fp.square(root), y))
45
+ if (!Fp.eql(Fp.sqr(root), y))
49
46
  throw new Error('Cannot find square root');
50
47
  return root;
51
48
  }
52
49
  const Fp = Field(secp256k1P, undefined, undefined, { sqrt: sqrtMod });
53
- const isoMap = isogenyMap(Fp, [
54
- // xNum
55
- [
56
- '0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa8c7',
57
- '0x7d3d4c80bc321d5b9f315cea7fd44c5d595d2fc0bf63b92dfff1044f17c6581',
58
- '0x534c328d23f234e6e2a413deca25caece4506144037c40314ecbd0b53d9dd262',
59
- '0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa88c',
60
- ],
61
- // xDen
62
- [
63
- '0xd35771193d94918a9ca34ccbb7b640dd86cd409542f8487d9fe6b745781eb49b',
64
- '0xedadc6f64383dc1df7c4b2d51b54225406d36b641f5e41bbc52a56612a8c6d14',
65
- '0x0000000000000000000000000000000000000000000000000000000000000001', // LAST 1
66
- ],
67
- // yNum
68
- [
69
- '0x4bda12f684bda12f684bda12f684bda12f684bda12f684bda12f684b8e38e23c',
70
- '0xc75e0c32d5cb7c0fa9d0a54b12a0a6d5647ab046d686da6fdffc90fc201d71a3',
71
- '0x29a6194691f91a73715209ef6512e576722830a201be2018a765e85a9ecee931',
72
- '0x2f684bda12f684bda12f684bda12f684bda12f684bda12f684bda12f38e38d84',
73
- ],
74
- // yDen
75
- [
76
- '0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffff93b',
77
- '0x7a06534bb8bdb49fd5e9e6632722c2989467c1bfc8e8d978dfb425d2685c2573',
78
- '0x6484aa716545ca2cf3a70c3fa8fe337e0a3d21162f0d6299a7bf8192bfd2a76f',
79
- '0x0000000000000000000000000000000000000000000000000000000000000001', // LAST 1
80
- ],
81
- ].map((i) => i.map((j) => BigInt(j))));
82
- const mapSWU = mapToCurveSimpleSWU(Fp, {
83
- A: BigInt('0x3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533'),
84
- B: BigInt('1771'),
85
- Z: Fp.create(BigInt('-11')),
86
- });
87
50
  export const secp256k1 = createCurve({
88
51
  // Params: a, b
89
52
  // Seem to be rigid https://bitcointalk.org/index.php?topic=289795.msg3183975#msg3183975
@@ -126,51 +89,13 @@ export const secp256k1 = createCurve({
126
89
  return { k1neg, k1, k2neg, k2 };
127
90
  },
128
91
  },
129
- mapToCurve: (scalars) => {
130
- const { x, y } = mapSWU(Fp.create(scalars[0]));
131
- return isoMap(x, y);
132
- },
133
- htfDefaults: {
134
- DST: 'secp256k1_XMD:SHA-256_SSWU_RO_',
135
- p: Fp.ORDER,
136
- m: 1,
137
- k: 128,
138
- expand: 'xmd',
139
- hash: sha256,
140
- },
141
92
  }, sha256);
142
- // Schnorr
93
+ // Schnorr signatures are superior to ECDSA from above.
94
+ // Below is Schnorr-specific code as per BIP0340.
95
+ // https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki
143
96
  const _0n = BigInt(0);
144
- const numTo32b = secp256k1.utils._bigintToBytes;
145
- const numTo32bStr = secp256k1.utils._bigintToString;
146
- const normalizePrivateKey = secp256k1.utils._normalizePrivateKey;
147
- // TODO: export?
148
- function normalizePublicKey(publicKey) {
149
- if (publicKey instanceof secp256k1.Point) {
150
- publicKey.assertValidity();
151
- return publicKey;
152
- }
153
- else {
154
- const bytes = ensureBytes(publicKey);
155
- // Schnorr is 32 bytes
156
- if (bytes.length !== 32)
157
- throw new Error('Schnorr pubkeys must be 32 bytes');
158
- const x = bytesToNumberBE(bytes);
159
- if (!isValidFieldElement(x))
160
- throw new Error('Point is not on curve');
161
- const y2 = secp256k1.utils._weierstrassEquation(x); // y² = x³ + ax + b
162
- let y = sqrtMod(y2); // y = y² ^ (p+1)/4
163
- const isYOdd = (y & _1n) === _1n;
164
- // Schnorr
165
- if (isYOdd)
166
- y = secp256k1.CURVE.Fp.negate(y);
167
- const point = new secp256k1.Point(x, y);
168
- point.assertValidity();
169
- return point;
170
- }
171
- }
172
- const isWithinCurveOrder = secp256k1.utils._isWithinCurveOrder;
173
- const isValidFieldElement = secp256k1.utils._isValidFieldElement;
97
+ const fe = (x) => typeof x === 'bigint' && _0n < x && x < secp256k1P;
98
+ const ge = (x) => typeof x === 'bigint' && _0n < x && x < secp256k1N;
174
99
  const TAGS = {
175
100
  challenge: 'BIP0340/challenge',
176
101
  aux: 'BIP0340/aux',
@@ -178,7 +103,7 @@ const TAGS = {
178
103
  };
179
104
  /** An object mapping tags to their tagged hash prefix of [SHA256(tag) | SHA256(tag)] */
180
105
  const TAGGED_HASH_PREFIXES = {};
181
- export function taggedHash(tag, ...messages) {
106
+ function taggedHash(tag, ...messages) {
182
107
  let tagP = TAGGED_HASH_PREFIXES[tag];
183
108
  if (tagP === undefined) {
184
109
  const tagH = sha256(Uint8Array.from(tag, (c) => c.charCodeAt(0)));
@@ -187,71 +112,55 @@ export function taggedHash(tag, ...messages) {
187
112
  }
188
113
  return sha256(concatBytes(tagP, ...messages));
189
114
  }
190
- const toRawX = (point) => point.toRawBytes(true).slice(1);
191
- // Schnorr signatures are superior to ECDSA from above.
192
- // Below is Schnorr-specific code as per BIP0340.
193
- function schnorrChallengeFinalize(ch) {
194
- return mod(bytesToNumberBE(ch), secp256k1.CURVE.n);
115
+ const pointToBytes = (point) => point.toRawBytes(true).slice(1);
116
+ const numTo32b = (n) => numberToBytesBE(n, 32);
117
+ const modN = (x) => mod(x, secp256k1N);
118
+ const Point = secp256k1.ProjectivePoint;
119
+ const GmulAdd = (Q, a, b) => Point.BASE.multiplyAndAddUnsafe(Q, a, b);
120
+ const hex32ToInt = (key) => bytesToInt(ensureBytes(key, 32));
121
+ function schnorrGetExtPubKey(priv) {
122
+ let d = typeof priv === 'bigint' ? priv : hex32ToInt(priv);
123
+ const point = Point.fromPrivateKey(d); // P = d'⋅G; 0 < d' < n check is done inside
124
+ const scalar = point.hasEvenY() ? d : modN(-d); // d = d' if has_even_y(P), otherwise d = n-d'
125
+ return { point, scalar, bytes: pointToBytes(point) };
195
126
  }
196
- // Do we need this at all for Schnorr?
197
- class SchnorrSignature {
198
- constructor(r, s) {
199
- this.r = r;
200
- this.s = s;
201
- this.assertValidity();
202
- }
203
- static fromHex(hex) {
204
- const bytes = ensureBytes(hex);
205
- const len = 32; // group length
206
- if (bytes.length !== 2 * len)
207
- throw new TypeError(`SchnorrSignature.fromHex: expected ${2 * len} bytes, not ${bytes.length}`);
208
- const r = bytesToNumberBE(bytes.subarray(0, len));
209
- const s = bytesToNumberBE(bytes.subarray(len, 2 * len));
210
- return new SchnorrSignature(r, s);
211
- }
212
- assertValidity() {
213
- const { r, s } = this;
214
- if (!isValidFieldElement(r) || !isWithinCurveOrder(s))
215
- throw new Error('Invalid signature');
216
- }
217
- toHex() {
218
- return numTo32bStr(this.r) + numTo32bStr(this.s);
219
- }
220
- toRawBytes() {
221
- return hexToBytes(this.toHex());
222
- }
127
+ function lift_x(x) {
128
+ if (!fe(x))
129
+ throw new Error('bad x: need 0 < x < p'); // Fail if x ≥ p.
130
+ const c = mod(x * x * x + BigInt(7), secp256k1P); // Let c = x³ + 7 mod p.
131
+ let y = sqrtMod(c); // Let y = c^(p+1)/4 mod p.
132
+ if (y % 2n !== 0n)
133
+ y = mod(-y, secp256k1P); // Return the unique point P such that x(P) = x and
134
+ const p = new Point(x, y, _1n); // y(P) = y if y mod 2 = 0 or y(P) = p-y otherwise.
135
+ p.assertValidity();
136
+ return p;
223
137
  }
224
- function schnorrGetScalar(priv) {
225
- const point = secp256k1.Point.fromPrivateKey(priv);
226
- const scalar = point.hasEvenY() ? priv : secp256k1.CURVE.n - priv;
227
- return { point, scalar, x: toRawX(point) };
138
+ function challenge(...args) {
139
+ return modN(bytesToInt(taggedHash(TAGS.challenge, ...args)));
228
140
  }
229
- /**
230
- * Synchronously creates Schnorr signature. Improved security: verifies itself before
231
- * producing an output.
232
- * @param msg message (not message hash)
233
- * @param privateKey private key
234
- * @param auxRand random bytes that would be added to k. Bad RNG won't break it.
235
- */
141
+ // Schnorr's pubkey is just `x` of Point (BIP340)
142
+ function schnorrGetPublicKey(privateKey) {
143
+ return schnorrGetExtPubKey(privateKey).bytes; // d'=int(sk). Fail if d'=0 or d'≥n. Ret bytes(d'⋅G)
144
+ }
145
+ // Creates Schnorr signature as per BIP340. Verifies itself before returning anything.
146
+ // auxRand is optional and is not the sole source of k generation: bad CSPRNG won't be dangerous
236
147
  function schnorrSign(message, privateKey, auxRand = randomBytes(32)) {
237
148
  if (message == null)
238
- throw new TypeError(`sign: Expected valid message, not "${message}"`);
239
- const m = ensureBytes(message);
240
- // checks for isWithinCurveOrder
241
- const { x: px, scalar: d } = schnorrGetScalar(normalizePrivateKey(privateKey));
242
- const rand = ensureBytes(auxRand);
243
- if (rand.length !== 32)
244
- throw new TypeError('sign: Expected 32 bytes of aux randomness');
245
- const tag = taggedHash;
246
- const t0h = tag(TAGS.aux, rand);
247
- const t = numTo32b(d ^ bytesToNumberBE(t0h));
248
- const k0h = tag(TAGS.nonce, t, px, m);
249
- const k0 = mod(bytesToNumberBE(k0h), secp256k1.CURVE.n);
250
- if (k0 === _0n)
251
- throw new Error('sign: Creation of signature failed. k is zero');
252
- const { point: R, x: rx, scalar: k } = schnorrGetScalar(k0);
253
- const e = schnorrChallengeFinalize(tag(TAGS.challenge, rx, px, m));
254
- const sig = new SchnorrSignature(R.x, mod(k + e * d, secp256k1.CURVE.n)).toRawBytes();
149
+ throw new Error(`sign: Expected valid message, not "${message}"`);
150
+ const m = ensureBytes(message); // checks for isWithinCurveOrder
151
+ const { bytes: px, scalar: d } = schnorrGetExtPubKey(privateKey);
152
+ const a = ensureBytes(auxRand, 32); // Auxiliary random data a: a 32-byte array
153
+ const t = numTo32b(d ^ bytesToInt(taggedHash(TAGS.aux, a))); // Let t be the byte-wise xor of bytes(d) and hash/aux(a)
154
+ const rand = taggedHash(TAGS.nonce, t, px, m); // Let rand = hash/nonce(t || bytes(P) || m)
155
+ const k_ = modN(bytesToInt(rand)); // Let k' = int(rand) mod n
156
+ if (k_ === _0n)
157
+ throw new Error('sign failed: k is zero'); // Fail if k' = 0.
158
+ const { point: R, bytes: rx, scalar: k } = schnorrGetExtPubKey(k_); // Let R = k'⋅G.
159
+ const e = challenge(rx, px, m); // Let e = int(hash/challenge(bytes(R) || bytes(P) || m)) mod n.
160
+ const sig = new Uint8Array(64); // Let sig = bytes(R) || bytes((k + ed) mod n).
161
+ sig.set(numTo32b(R.px), 0);
162
+ sig.set(numTo32b(modN(k + e * d)), 32);
163
+ // If Verify(bytes(P), m, sig) (see below) returns failure, abort
255
164
  if (!schnorrVerify(sig, m, px))
256
165
  throw new Error('sign: Invalid signature produced');
257
166
  return sig;
@@ -261,30 +170,83 @@ function schnorrSign(message, privateKey, auxRand = randomBytes(32)) {
261
170
  */
262
171
  function schnorrVerify(signature, message, publicKey) {
263
172
  try {
264
- const raw = signature instanceof SchnorrSignature;
265
- const sig = raw ? signature : SchnorrSignature.fromHex(signature);
266
- if (raw)
267
- sig.assertValidity(); // just in case
268
- const { r, s } = sig;
269
- const m = ensureBytes(message);
270
- const P = normalizePublicKey(publicKey);
271
- const e = schnorrChallengeFinalize(taggedHash(TAGS.challenge, numTo32b(r), toRawX(P), m));
272
- // Finalize
273
- // R = s⋅G - e⋅P
274
- // -eP == (n-e)P
275
- const R = secp256k1.Point.BASE.multiplyAndAddUnsafe(P, normalizePrivateKey(s), mod(-e, secp256k1.CURVE.n));
276
- if (!R || !R.hasEvenY() || R.x !== r)
173
+ const P = lift_x(hex32ToInt(publicKey)); // P = lift_x(int(pk)); fail if that fails
174
+ const sig = ensureBytes(signature, 64);
175
+ const r = bytesToInt(sig.subarray(0, 32)); // Let r = int(sig[0:32]); fail if r ≥ p.
176
+ if (!fe(r))
177
+ return false;
178
+ const s = bytesToInt(sig.subarray(32, 64)); // Let s = int(sig[32:64]); fail if s ≥ n.
179
+ if (!ge(s))
277
180
  return false;
278
- return true;
181
+ const m = ensureBytes(message);
182
+ const e = challenge(numTo32b(r), pointToBytes(P), m); // int(challenge(bytes(r)||bytes(P)||m)) mod n
183
+ const R = GmulAdd(P, s, modN(-e)); // R = s⋅G - e⋅P
184
+ if (!R || !R.hasEvenY() || R.toAffine().x !== r)
185
+ return false; // -eP == (n-e)P
186
+ return true; // Fail if is_infinite(R) / not has_even_y(R) / x(R) ≠ r.
279
187
  }
280
188
  catch (error) {
281
189
  return false;
282
190
  }
283
191
  }
284
192
  export const schnorr = {
285
- Signature: SchnorrSignature,
286
- // Schnorr's pubkey is just `x` of Point (BIP340)
287
- getPublicKey: (privateKey) => toRawX(secp256k1.Point.fromPrivateKey(privateKey)),
193
+ getPublicKey: schnorrGetPublicKey,
288
194
  sign: schnorrSign,
289
195
  verify: schnorrVerify,
196
+ utils: {
197
+ getExtendedPublicKey: schnorrGetExtPubKey,
198
+ lift_x,
199
+ pointToBytes,
200
+ numberToBytesBE,
201
+ bytesToNumberBE: bytesToInt,
202
+ taggedHash,
203
+ mod,
204
+ },
290
205
  };
206
+ const isoMap = htf.isogenyMap(Fp, [
207
+ // xNum
208
+ [
209
+ '0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa8c7',
210
+ '0x7d3d4c80bc321d5b9f315cea7fd44c5d595d2fc0bf63b92dfff1044f17c6581',
211
+ '0x534c328d23f234e6e2a413deca25caece4506144037c40314ecbd0b53d9dd262',
212
+ '0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa88c',
213
+ ],
214
+ // xDen
215
+ [
216
+ '0xd35771193d94918a9ca34ccbb7b640dd86cd409542f8487d9fe6b745781eb49b',
217
+ '0xedadc6f64383dc1df7c4b2d51b54225406d36b641f5e41bbc52a56612a8c6d14',
218
+ '0x0000000000000000000000000000000000000000000000000000000000000001', // LAST 1
219
+ ],
220
+ // yNum
221
+ [
222
+ '0x4bda12f684bda12f684bda12f684bda12f684bda12f684bda12f684b8e38e23c',
223
+ '0xc75e0c32d5cb7c0fa9d0a54b12a0a6d5647ab046d686da6fdffc90fc201d71a3',
224
+ '0x29a6194691f91a73715209ef6512e576722830a201be2018a765e85a9ecee931',
225
+ '0x2f684bda12f684bda12f684bda12f684bda12f684bda12f684bda12f38e38d84',
226
+ ],
227
+ // yDen
228
+ [
229
+ '0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffff93b',
230
+ '0x7a06534bb8bdb49fd5e9e6632722c2989467c1bfc8e8d978dfb425d2685c2573',
231
+ '0x6484aa716545ca2cf3a70c3fa8fe337e0a3d21162f0d6299a7bf8192bfd2a76f',
232
+ '0x0000000000000000000000000000000000000000000000000000000000000001', // LAST 1
233
+ ],
234
+ ].map((i) => i.map((j) => BigInt(j))));
235
+ const mapSWU = mapToCurveSimpleSWU(Fp, {
236
+ A: BigInt('0x3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533'),
237
+ B: BigInt('1771'),
238
+ Z: Fp.create(BigInt('-11')),
239
+ });
240
+ const { hashToCurve, encodeToCurve } = htf.hashToCurve(secp256k1.ProjectivePoint, (scalars) => {
241
+ const { x, y } = mapSWU(Fp.create(scalars[0]));
242
+ return isoMap(x, y);
243
+ }, {
244
+ DST: 'secp256k1_XMD:SHA-256_SSWU_RO_',
245
+ encodeDST: 'secp256k1_XMD:SHA-256_SSWU_NU_',
246
+ p: Fp.ORDER,
247
+ m: 1,
248
+ k: 128,
249
+ expand: 'xmd',
250
+ hash: sha256,
251
+ });
252
+ export { hashToCurve, encodeToCurve };
package/lib/esm/stark.js CHANGED
@@ -3,12 +3,23 @@ import { keccak_256 } from '@noble/hashes/sha3';
3
3
  import { sha256 } from '@noble/hashes/sha256';
4
4
  import { weierstrass } from './abstract/weierstrass.js';
5
5
  import * as cutils from './abstract/utils.js';
6
- import { Fp } from './abstract/modular.js';
6
+ import { Fp, mod, validateField } from './abstract/modular.js';
7
7
  import { getHash } from './_shortw_utils.js';
8
+ import * as poseidon from './abstract/poseidon.js';
9
+ import { utf8ToBytes } from '@noble/hashes/utils';
8
10
  // Stark-friendly elliptic curve
9
11
  // https://docs.starkware.co/starkex/stark-curve.html
10
12
  const CURVE_N = BigInt('3618502788666131213697322783095070105526743751716087489154079457884512865583');
11
13
  const nBitLength = 252;
14
+ // Copy-pasted from weierstrass.ts
15
+ function bits2int(bytes) {
16
+ const delta = bytes.length * 8 - nBitLength;
17
+ const num = cutils.bytesToNumberBE(bytes);
18
+ return delta > 0 ? num >> BigInt(delta) : num;
19
+ }
20
+ function bits2int_modN(bytes) {
21
+ return mod(bits2int(bytes), CURVE_N);
22
+ }
12
23
  export const starkCurve = weierstrass({
13
24
  // Params: a, b
14
25
  a: BigInt(1),
@@ -26,33 +37,28 @@ export const starkCurve = weierstrass({
26
37
  // Default options
27
38
  lowS: false,
28
39
  ...getHash(sha256),
29
- truncateHash: (hash, truncateOnly = false) => {
30
- // TODO: cleanup, ugly code
31
- // Fix truncation
32
- if (!truncateOnly) {
33
- let hashS = bytesToNumber0x(hash).toString(16);
34
- if (hashS.length === 63) {
35
- hashS += '0';
36
- hash = hexToBytes0x(hashS);
37
- }
40
+ // Custom truncation routines for stark curve
41
+ bits2int: (bytes) => {
42
+ while (bytes[0] === 0)
43
+ bytes = bytes.subarray(1);
44
+ return bits2int(bytes);
45
+ },
46
+ bits2int_modN: (bytes) => {
47
+ let hashS = cutils.bytesToNumberBE(bytes).toString(16);
48
+ if (hashS.length === 63) {
49
+ hashS += '0';
50
+ bytes = hexToBytes0x(hashS);
38
51
  }
39
52
  // Truncate zero bytes on left (compat with elliptic)
40
- while (hash[0] === 0)
41
- hash = hash.subarray(1);
42
- const byteLength = hash.length;
43
- const delta = byteLength * 8 - nBitLength; // size of curve.n (252 bits)
44
- let h = hash.length ? bytesToNumber0x(hash) : 0n;
45
- if (delta > 0)
46
- h = h >> BigInt(delta);
47
- if (!truncateOnly && h >= CURVE_N)
48
- h -= CURVE_N;
49
- return h;
53
+ while (bytes[0] === 0)
54
+ bytes = bytes.subarray(1);
55
+ return bits2int_modN(bytes);
50
56
  },
51
57
  });
52
58
  // Custom Starknet type conversion functions that can handle 0x and unpadded hex
53
59
  function hexToBytes0x(hex) {
54
60
  if (typeof hex !== 'string') {
55
- throw new TypeError('hexToBytes: expected string, got ' + typeof hex);
61
+ throw new Error('hexToBytes: expected string, got ' + typeof hex);
56
62
  }
57
63
  hex = strip0x(hex);
58
64
  if (hex.length & 1)
@@ -72,7 +78,7 @@ function hexToBytes0x(hex) {
72
78
  }
73
79
  function hexToNumber0x(hex) {
74
80
  if (typeof hex !== 'string') {
75
- throw new TypeError('hexToNumber: expected string, got ' + typeof hex);
81
+ throw new Error('hexToNumber: expected string, got ' + typeof hex);
76
82
  }
77
83
  // Big Endian
78
84
  // TODO: strip vs no strip?
@@ -87,9 +93,9 @@ function ensureBytes0x(hex) {
87
93
  return hex instanceof Uint8Array ? Uint8Array.from(hex) : hexToBytes0x(hex);
88
94
  }
89
95
  function normalizePrivateKey(privKey) {
90
- return cutils.bytesToHex(ensureBytes0x(privKey)).padStart(32 * 2, '0');
96
+ return cutils.bytesToHex(ensureBytes0x(privKey)).padStart(64, '0');
91
97
  }
92
- function getPublicKey0x(privKey, isCompressed) {
98
+ function getPublicKey0x(privKey, isCompressed = false) {
93
99
  return starkCurve.getPublicKey(normalizePrivateKey(privKey), isCompressed);
94
100
  }
95
101
  function getSharedSecret0x(privKeyA, pubKeyB) {
@@ -104,9 +110,9 @@ function verify0x(signature, msgHash, pubKey) {
104
110
  const sig = signature instanceof Signature ? signature : ensureBytes0x(signature);
105
111
  return starkCurve.verify(sig, ensureBytes0x(msgHash), ensureBytes0x(pubKey));
106
112
  }
107
- const { CURVE, Point, ProjectivePoint, Signature } = starkCurve;
113
+ const { CURVE, ProjectivePoint, Signature } = starkCurve;
108
114
  export const utils = starkCurve.utils;
109
- export { CURVE, Point, Signature, ProjectivePoint, getPublicKey0x as getPublicKey, getSharedSecret0x as getSharedSecret, sign0x as sign, verify0x as verify, };
115
+ export { CURVE, Signature, ProjectivePoint, getPublicKey0x as getPublicKey, getSharedSecret0x as getSharedSecret, sign0x as sign, verify0x as verify, };
110
116
  const stripLeadingZeros = (s) => s.replace(/^0+/gm, '');
111
117
  export const bytesToHexEth = (uint8a) => `0x${stripLeadingZeros(cutils.bytesToHex(uint8a))}`;
112
118
  export const strip0x = (hex) => hex.replace(/^0x/i, '');
@@ -116,18 +122,17 @@ function hashKeyWithIndex(key, index) {
116
122
  let indexHex = cutils.numberToHexUnpadded(index);
117
123
  if (indexHex.length & 1)
118
124
  indexHex = '0' + indexHex;
119
- return bytesToNumber0x(sha256(cutils.concatBytes(key, hexToBytes0x(indexHex))));
125
+ return sha256Num(cutils.concatBytes(key, hexToBytes0x(indexHex)));
120
126
  }
121
127
  export function grindKey(seed) {
122
128
  const _seed = ensureBytes0x(seed);
123
129
  const sha256mask = 2n ** 256n;
124
- const Fn = Fp(CURVE.n);
125
- const limit = sha256mask - Fn.create(sha256mask);
130
+ const limit = sha256mask - mod(sha256mask, CURVE_N);
126
131
  for (let i = 0;; i++) {
127
132
  const key = hashKeyWithIndex(_seed, i);
128
133
  // key should be in [0, limit)
129
134
  if (key < limit)
130
- return Fn.create(key).toString(16);
135
+ return mod(key, CURVE_N).toString(16);
131
136
  }
132
137
  }
133
138
  export function getStarkKey(privateKey) {
@@ -142,21 +147,21 @@ export function ethSigToPrivate(signature) {
142
147
  const MASK_31 = 2n ** 31n - 1n;
143
148
  const int31 = (n) => Number(n & MASK_31);
144
149
  export function getAccountPath(layer, application, ethereumAddress, index) {
145
- const layerNum = int31(bytesToNumber0x(sha256(layer)));
146
- const applicationNum = int31(bytesToNumber0x(sha256(application)));
150
+ const layerNum = int31(sha256Num(layer));
151
+ const applicationNum = int31(sha256Num(application));
147
152
  const eth = hexToNumber0x(ethereumAddress);
148
153
  return `m/2645'/${layerNum}'/${applicationNum}'/${int31(eth)}'/${int31(eth >> 31n)}'/${index}`;
149
154
  }
150
155
  // https://docs.starkware.co/starkex/pedersen-hash-function.html
151
156
  const PEDERSEN_POINTS_AFFINE = [
152
- new Point(2089986280348253421170679821480865132823066470938446095505822317253594081284n, 1713931329540660377023406109199410414810705867260802078187082345529207694986n),
153
- new Point(996781205833008774514500082376783249102396023663454813447423147977397232763n, 1668503676786377725805489344771023921079126552019160156920634619255970485781n),
154
- new Point(2251563274489750535117886426533222435294046428347329203627021249169616184184n, 1798716007562728905295480679789526322175868328062420237419143593021674992973n),
155
- new Point(2138414695194151160943305727036575959195309218611738193261179310511854807447n, 113410276730064486255102093846540133784865286929052426931474106396135072156n),
156
- new Point(2379962749567351885752724891227938183011949129833673362440656643086021394946n, 776496453633298175483985398648758586525933812536653089401905292063708816422n),
157
+ new ProjectivePoint(2089986280348253421170679821480865132823066470938446095505822317253594081284n, 1713931329540660377023406109199410414810705867260802078187082345529207694986n, 1n),
158
+ new ProjectivePoint(996781205833008774514500082376783249102396023663454813447423147977397232763n, 1668503676786377725805489344771023921079126552019160156920634619255970485781n, 1n),
159
+ new ProjectivePoint(2251563274489750535117886426533222435294046428347329203627021249169616184184n, 1798716007562728905295480679789526322175868328062420237419143593021674992973n, 1n),
160
+ new ProjectivePoint(2138414695194151160943305727036575959195309218611738193261179310511854807447n, 113410276730064486255102093846540133784865286929052426931474106396135072156n, 1n),
161
+ new ProjectivePoint(2379962749567351885752724891227938183011949129833673362440656643086021394946n, 776496453633298175483985398648758586525933812536653089401905292063708816422n, 1n),
157
162
  ];
158
163
  // for (const p of PEDERSEN_POINTS) p._setWindowSize(8);
159
- const PEDERSEN_POINTS = PEDERSEN_POINTS_AFFINE.map(ProjectivePoint.fromAffine);
164
+ const PEDERSEN_POINTS = PEDERSEN_POINTS_AFFINE;
160
165
  function pedersenPrecompute(p1, p2) {
161
166
  const out = [];
162
167
  let p = p1;
@@ -195,7 +200,7 @@ function pedersenSingle(point, value, constants) {
195
200
  let x = pedersenArg(value);
196
201
  for (let j = 0; j < 252; j++) {
197
202
  const pt = constants[j];
198
- if (pt.x === point.x)
203
+ if (pt.px === point.px)
199
204
  throw new Error('Same point');
200
205
  if ((x & 1n) !== 0n)
201
206
  point = point.add(pt);
@@ -208,7 +213,7 @@ export function pedersen(x, y) {
208
213
  let point = PEDERSEN_POINTS[0];
209
214
  point = pedersenSingle(point, x, PEDERSEN_POINTS1);
210
215
  point = pedersenSingle(point, y, PEDERSEN_POINTS2);
211
- return bytesToHexEth(point.toAffine().toRawBytes(true).slice(1));
216
+ return bytesToHexEth(point.toRawBytes(true).slice(1));
212
217
  }
213
218
  export function hashChain(data, fn = pedersen) {
214
219
  if (!Array.isArray(data) || data.length < 1)
@@ -221,5 +226,64 @@ export function hashChain(data, fn = pedersen) {
221
226
  }
222
227
  // Same as hashChain, but computes hash even for single element and order is not revesed
223
228
  export const computeHashOnElements = (data, fn = pedersen) => [0, ...data, data.length].reduce((x, y) => fn(x, y));
224
- const MASK_250 = 2n ** 250n - 1n;
229
+ const MASK_250 = cutils.bitMask(250);
225
230
  export const keccak = (data) => bytesToNumber0x(keccak_256(data)) & MASK_250;
231
+ const sha256Num = (data) => cutils.bytesToNumberBE(sha256(data));
232
+ // Poseidon hash
233
+ export const Fp253 = Fp(BigInt('14474011154664525231415395255581126252639794253786371766033694892385558855681')); // 2^253 + 2^199 + 1
234
+ export const Fp251 = Fp(BigInt('3618502788666131213697322783095070105623107215331596699973092056135872020481')); // 2^251 + 17 * 2^192 + 1
235
+ function poseidonRoundConstant(Fp, name, idx) {
236
+ const val = Fp.fromBytes(sha256(utf8ToBytes(`${name}${idx}`)));
237
+ return Fp.create(val);
238
+ }
239
+ // NOTE: doesn't check eiginvalues and possible can create unsafe matrix. But any filtration here will break compatibility with starknet
240
+ // Please use only if you really know what you doing.
241
+ // https://eprint.iacr.org/2019/458.pdf Section 2.3 (Avoiding Insecure Matrices)
242
+ export function _poseidonMDS(Fp, name, m, attempt = 0) {
243
+ const x_values = [];
244
+ const y_values = [];
245
+ for (let i = 0; i < m; i++) {
246
+ x_values.push(poseidonRoundConstant(Fp, `${name}x`, attempt * m + i));
247
+ y_values.push(poseidonRoundConstant(Fp, `${name}y`, attempt * m + i));
248
+ }
249
+ if (new Set([...x_values, ...y_values]).size !== 2 * m)
250
+ throw new Error('X and Y values are not distinct');
251
+ return x_values.map((x) => y_values.map((y) => Fp.inv(Fp.sub(x, y))));
252
+ }
253
+ const MDS_SMALL = [
254
+ [3, 1, 1],
255
+ [1, -1, 1],
256
+ [1, 1, -2],
257
+ ].map((i) => i.map(BigInt));
258
+ export function poseidonBasic(opts, mds) {
259
+ validateField(opts.Fp);
260
+ if (!Number.isSafeInteger(opts.rate) || !Number.isSafeInteger(opts.capacity))
261
+ throw new Error(`Wrong poseidon opts: ${opts}`);
262
+ const m = opts.rate + opts.capacity;
263
+ const rounds = opts.roundsFull + opts.roundsPartial;
264
+ const roundConstants = [];
265
+ for (let i = 0; i < rounds; i++) {
266
+ const row = [];
267
+ for (let j = 0; j < m; j++)
268
+ row.push(poseidonRoundConstant(opts.Fp, 'Hades', m * i + j));
269
+ roundConstants.push(row);
270
+ }
271
+ return poseidon.poseidon({
272
+ ...opts,
273
+ t: m,
274
+ sboxPower: 3,
275
+ reversePartialPowIdx: true,
276
+ mds,
277
+ roundConstants,
278
+ });
279
+ }
280
+ export function poseidonCreate(opts, mdsAttempt = 0) {
281
+ const m = opts.rate + opts.capacity;
282
+ if (!Number.isSafeInteger(mdsAttempt))
283
+ throw new Error(`Wrong mdsAttempt=${mdsAttempt}`);
284
+ return poseidonBasic(opts, _poseidonMDS(opts.Fp, 'HadesMDS', m, mdsAttempt));
285
+ }
286
+ export const poseidonSmall = poseidonBasic({ Fp: Fp251, rate: 2, capacity: 1, roundsFull: 8, roundsPartial: 83 }, MDS_SMALL);
287
+ export function poseidonHash(x, y, fn = poseidonSmall) {
288
+ return fn([x, y, 2n])[0];
289
+ }
package/lib/jubjub.d.ts CHANGED
@@ -4,5 +4,5 @@
4
4
  * jubjub does not use EdDSA, so `hash`/sha512 params are passed because interface expects them.
5
5
  */
6
6
  export declare const jubjub: import("./abstract/edwards.js").CurveFn;
7
- export declare function groupHash(tag: Uint8Array, personalization: Uint8Array): import("./abstract/edwards.js").ExtendedPointType;
8
- export declare function findGroupHash(m: Uint8Array, personalization: Uint8Array): import("./abstract/edwards.js").ExtendedPointType;
7
+ export declare function groupHash(tag: Uint8Array, personalization: Uint8Array): import("./abstract/edwards.js").ExtPointType;
8
+ export declare function findGroupHash(m: Uint8Array, personalization: Uint8Array): import("./abstract/edwards.js").ExtPointType;
package/lib/jubjub.js CHANGED
@@ -36,7 +36,7 @@ function groupHash(tag, personalization) {
36
36
  h.update(GH_FIRST_BLOCK);
37
37
  h.update(tag);
38
38
  // NOTE: returns ExtendedPoint, in case it will be multiplied later
39
- let p = exports.jubjub.ExtendedPoint.fromAffine(exports.jubjub.Point.fromHex(h.digest()));
39
+ let p = exports.jubjub.ExtendedPoint.fromHex(h.digest());
40
40
  // NOTE: cannot replace with isSmallOrder, returns Point*8
41
41
  p = p.multiply(exports.jubjub.CURVE.h);
42
42
  if (p.equals(exports.jubjub.ExtendedPoint.ZERO))