@scure/btc-signer 2.2.0 → 2.4.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.
package/src/p2p.ts CHANGED
@@ -42,6 +42,9 @@ const MINUS_3_SQRT = Fp.sqrt(Fp.create(BigInt(-3)));
42
42
  const _3n = BigInt(3);
43
43
  const _4n = BigInt(4);
44
44
  const _7n = BigInt(7);
45
+ // Precomputed 1/2 mod p: turns the frequent "divide by 2" steps of XSwiftEC/XSwiftECInv
46
+ // into single multiplications instead of one modular inversion per call.
47
+ const INV_2 = Fp.inv(Fp.create(_2n));
45
48
  // This is the "lift_x(x) succeeds" predicate for field-normalized x values.
46
49
  // Raw x >= p would need the full BIP340 range check before reducing modulo p.
47
50
  const isValidX = (x: bigint) => FpIsSquare(Fp, Fp.add(Fp.mul(Fp.mul(x, x), x), _7n));
@@ -91,7 +94,7 @@ export const elligatorSwift = /* @__PURE__ */ Object.freeze({
91
94
  const r = trySqrt(Fp.mul(Fp.neg(s), Fp.add(Fp.mul(_4n, t0), t1)));
92
95
  if (r === undefined) return; // [2 condition]
93
96
  if (ellCase & 1 && Fp.is0(r)) return;
94
- v = Fp.div(Fp.add(Fp.neg(u), Fp.div(r, s)), _2n); // v = (-u + r / s) / 2
97
+ v = Fp.mul(Fp.add(Fp.neg(u), Fp.div(r, s)), INV_2); // v = (-u + r / s) / 2
95
98
  }
96
99
  const w = trySqrt(s);
97
100
  if (w === undefined) return; // [3 condition]
@@ -99,7 +102,7 @@ export const elligatorSwift = /* @__PURE__ */ Object.freeze({
99
102
  const t0 = last & 1 ? Fp.add(_1n, MINUS_3_SQRT) : Fp.sub(_1n, MINUS_3_SQRT);
100
103
  const w0 = last === 0 || last === 5 ? Fp.neg(w) : w; // -w | w
101
104
  // w0 * (u * t0 / 2 + v)
102
- return Fp.mul(w0, Fp.add(Fp.div(Fp.mul(u, t0), _2n), v));
105
+ return Fp.mul(w0, Fp.add(Fp.mul(Fp.mul(u, t0), INV_2), v));
103
106
  },
104
107
  // Encode public key (point or x coordinate bigint) into 64-byte pseudorandom encoding
105
108
  // BIP324 samples encodings for x(P), so callers must pass a curve X coordinate in 0..p-1;
@@ -109,11 +112,18 @@ export const elligatorSwift = /* @__PURE__ */ Object.freeze({
109
112
  // so encode() must reject out-of-range x instead of silently reducing a different bigint modulo p.
110
113
  if (!Fp.isValid(x))
111
114
  throw new RangeError('elligatorSwift.encode: expected x coordinate in range 0..p-1');
115
+ // Off-curve x cannot round-trip: decode() only returns lift_x-able candidates, so
116
+ // the loop below would silently emit an encoding of a *different* public key.
117
+ if (!isValidX(x))
118
+ throw new RangeError('elligatorSwift.encode: expected x coordinate of a curve point');
112
119
  // 200k test cycles per keygen: avg=4 max=48
113
120
  // seems too much, but same as for reference implementation
114
121
  while (true) {
115
- // random scalar 1..Fp.ORDER
116
- const u = Fp.create(Fp.fromBytes(secp256k1.utils.randomSecretKey()));
122
+ // Random field element 1..p-1: BIP324 samples u over the whole field (the previous
123
+ // secret-key sampler silently restricted u to 1..n-1); decode() remaps u = 0, so
124
+ // zero cannot round-trip and is skipped.
125
+ const u = Fp.create(Fp.fromBytes(randomBytes(32), true));
126
+ if (Fp.is0(u)) continue;
117
127
  const ellCase = randomBytes(1)[0] & 7; // [0..8)
118
128
  const t = elligatorSwift._inv(x, u, ellCase);
119
129
  if (!t) continue;
@@ -140,9 +150,11 @@ export const elligatorSwift = /* @__PURE__ */ Object.freeze({
140
150
  // try different cases
141
151
  let res = Fp.add(u, Fp.mul(Fp.mul(y, y), _4n)); // u + 4 * Y ** 2,
142
152
  if (isValidX(res)) return Fp.toBytes(res) as TRet<Uint8Array>;
143
- res = Fp.div(Fp.sub(Fp.div(Fp.neg(x), y), u), _2n); // (-X / Y - u) / 2
153
+ // X / Y is shared by the remaining candidates; computing it once saves an inversion.
154
+ const xDivY = Fp.div(x, y);
155
+ res = Fp.mul(Fp.sub(Fp.neg(xDivY), u), INV_2); // (-X / Y - u) / 2
144
156
  if (isValidX(res)) return Fp.toBytes(res) as TRet<Uint8Array>;
145
- res = Fp.div(Fp.sub(Fp.div(x, y), u), _2n); // (X / Y - u) / 2
157
+ res = Fp.mul(Fp.sub(xDivY, u), INV_2); // (X / Y - u) / 2
146
158
  if (isValidX(res)) return Fp.toBytes(res) as TRet<Uint8Array>;
147
159
  throw new Error('elligatorSwift: cannot decode public key');
148
160
  },
@@ -173,6 +185,10 @@ export const elligatorSwift = /* @__PURE__ */ Object.freeze({
173
185
  ): TRet<Uint8Array> => {
174
186
  // BIP324 Shared secret computation hashes "the exactly 64-byte public keys'
175
187
  // encodings sent over the wire", so both ElligatorSwift inputs must be 64 bytes here.
188
+ // Initiator/responder ordering decides the hash-input order, so require a real boolean
189
+ // instead of letting arbitrary truthy values pick a side.
190
+ if (typeof initiating !== 'boolean')
191
+ throw new TypeError('"initiating" expected boolean, got type=' + typeof initiating);
176
192
  const ours = abytes(publicKeyOurs, 64, 'publicKeyOurs');
177
193
  const theirs = abytes(publicKeyTheirs, 64, 'publicKeyTheirs');
178
194
  const ecdhPoint = elligatorSwift.getSharedSecret(privateKeyOurs, theirs);