@scure/btc-signer 1.8.1 → 2.0.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 (87) hide show
  1. package/README.md +8 -5
  2. package/index.js +17 -61
  3. package/index.js.map +1 -1
  4. package/musig2.d.ts +2 -2
  5. package/musig2.d.ts.map +1 -1
  6. package/musig2.js +93 -90
  7. package/musig2.js.map +1 -1
  8. package/p2p.d.ts +7 -8
  9. package/p2p.d.ts.map +1 -1
  10. package/p2p.js +32 -38
  11. package/p2p.js.map +1 -1
  12. package/package.json +33 -103
  13. package/payment.js +73 -96
  14. package/payment.js.map +1 -1
  15. package/psbt.d.ts +224 -224
  16. package/psbt.d.ts.map +1 -1
  17. package/psbt.js +58 -63
  18. package/psbt.js.map +1 -1
  19. package/script.d.ts +125 -120
  20. package/script.d.ts.map +1 -1
  21. package/script.js +46 -146
  22. package/script.js.map +1 -1
  23. package/src/musig2.ts +43 -50
  24. package/src/p2p.ts +28 -40
  25. package/src/psbt.ts +7 -7
  26. package/src/script.ts +29 -26
  27. package/src/transaction.ts +21 -21
  28. package/src/utils.ts +41 -22
  29. package/transaction.d.ts +22 -28
  30. package/transaction.d.ts.map +1 -1
  31. package/transaction.js +134 -151
  32. package/transaction.js.map +1 -1
  33. package/utils.d.ts +11 -5
  34. package/utils.d.ts.map +1 -1
  35. package/utils.js +60 -62
  36. package/utils.js.map +1 -1
  37. package/utxo.d.ts +45 -45
  38. package/utxo.d.ts.map +1 -1
  39. package/utxo.js +62 -58
  40. package/utxo.js.map +1 -1
  41. package/_type_test.d.ts +0 -2
  42. package/_type_test.d.ts.map +0 -1
  43. package/_type_test.js +0 -59
  44. package/_type_test.js.map +0 -1
  45. package/esm/_type_test.d.ts +0 -2
  46. package/esm/_type_test.d.ts.map +0 -1
  47. package/esm/_type_test.js +0 -57
  48. package/esm/_type_test.js.map +0 -1
  49. package/esm/index.d.ts +0 -22
  50. package/esm/index.d.ts.map +0 -1
  51. package/esm/index.js +0 -22
  52. package/esm/index.js.map +0 -1
  53. package/esm/musig2.d.ts +0 -148
  54. package/esm/musig2.d.ts.map +0 -1
  55. package/esm/musig2.js +0 -404
  56. package/esm/musig2.js.map +0 -1
  57. package/esm/p2p.d.ts +0 -38
  58. package/esm/p2p.d.ts.map +0 -1
  59. package/esm/p2p.js +0 -156
  60. package/esm/p2p.js.map +0 -1
  61. package/esm/package.json +0 -1
  62. package/esm/payment.d.ts +0 -200
  63. package/esm/payment.d.ts.map +0 -1
  64. package/esm/payment.js +0 -697
  65. package/esm/payment.js.map +0 -1
  66. package/esm/psbt.d.ts +0 -820
  67. package/esm/psbt.d.ts.map +0 -1
  68. package/esm/psbt.js +0 -409
  69. package/esm/psbt.js.map +0 -1
  70. package/esm/script.d.ts +0 -170
  71. package/esm/script.d.ts.map +0 -1
  72. package/esm/script.js +0 -356
  73. package/esm/script.js.map +0 -1
  74. package/esm/transaction.d.ts +0 -172
  75. package/esm/transaction.d.ts.map +0 -1
  76. package/esm/transaction.js +0 -1185
  77. package/esm/transaction.js.map +0 -1
  78. package/esm/utils.d.ts +0 -35
  79. package/esm/utils.d.ts.map +0 -1
  80. package/esm/utils.js +0 -118
  81. package/esm/utils.js.map +0 -1
  82. package/esm/utxo.d.ts +0 -198
  83. package/esm/utxo.d.ts.map +0 -1
  84. package/esm/utxo.js +0 -461
  85. package/esm/utxo.js.map +0 -1
  86. package/src/_type_test.ts +0 -69
  87. package/src/package.json +0 -3
package/src/musig2.ts CHANGED
@@ -1,15 +1,8 @@
1
- import { mod } from '@noble/curves/abstract/modular.js';
2
- import {
3
- aInRange,
4
- bytesToNumberBE,
5
- concatBytes,
6
- equalBytes,
7
- numberToBytesBE,
8
- } from '@noble/curves/abstract/utils.js';
9
1
  import { schnorr, secp256k1 } from '@noble/curves/secp256k1.js';
2
+ import { aInRange, concatBytes, equalBytes, numberToBytesBE } from '@noble/curves/utils.js';
10
3
  import { abytes, anumber, randomBytes } from '@noble/hashes/utils.js';
11
4
  import * as P from 'micro-packed';
12
- import { compareBytes } from './utils.ts';
5
+ import { compareBytes, hasEven } from './utils.ts';
13
6
 
14
7
  /*
15
8
  MuSig2. This is not the full protocol: only an implementation of primitives from BIP-327.
@@ -45,20 +38,20 @@ export class InvalidContributionErr extends Error {
45
38
 
46
39
  // Utils
47
40
  const { taggedHash, pointToBytes } = schnorr.utils;
48
- const Point = secp256k1.ProjectivePoint;
41
+ const Point = secp256k1.Point;
49
42
  type Point = typeof Point.BASE;
50
- const PUBKEY_LEN = 33;
43
+ const Fn = Point.Fn;
44
+ const PUBKEY_LEN = secp256k1.lengths.publicKey!;
51
45
  const ZERO = new Uint8Array(PUBKEY_LEN); // Compressed zero point
52
- const SECP_N = secp256k1.CURVE.n;
53
46
 
54
47
  // Encoding
55
48
  // TODO: re-use in PSBT?
56
49
  const compressed = P.apply(P.bytes(33), {
57
- decode: (p: Point) => (isZero(p) ? ZERO : p.toRawBytes(true)),
58
- encode: (b: Uint8Array) => (equalBytes(b, ZERO) ? Point.ZERO : Point.fromHex(b)),
50
+ decode: (p: Point) => (isZero(p) ? ZERO : p.toBytes(true)),
51
+ encode: (b: Uint8Array) => (equalBytes(b, ZERO) ? Point.ZERO : Point.fromBytes(b)),
59
52
  });
60
53
  const scalar = P.validate(P.U256BE, (n) => {
61
- aInRange('n', n, 1n, SECP_N);
54
+ aInRange('n', n, 1n, Fn.ORDER);
62
55
  return n;
63
56
  });
64
57
  const PubNonce = P.struct({ R1: compressed, R2: compressed });
@@ -81,10 +74,9 @@ function aXonly(lst: boolean[]) {
81
74
  });
82
75
  }
83
76
 
84
- const modN = (x: bigint) => mod(x, SECP_N);
85
77
  const taggedInt = (tag: string, ...messages: Uint8Array[]) =>
86
- modN(bytesToNumberBE(taggedHash(tag, ...messages)));
87
- const evenScalar = (p: Point, n: bigint) => (p.hasEvenY() ? n : modN(-n));
78
+ Fn.create(Fn.fromBytes(taggedHash(tag, ...messages), true));
79
+ const evenScalar = (p: Point, n: bigint) => (hasEven(p.y) ? n : Fn.neg(n));
88
80
 
89
81
  // Short utility for compat with reference implementation
90
82
  export function IndividualPubkey(seckey: Uint8Array): Uint8Array {
@@ -158,23 +150,22 @@ export function keyAggregate(
158
150
  for (let i = 0; i < publicKeys.length; i++) {
159
151
  let Pi;
160
152
  try {
161
- Pi = Point.fromHex(publicKeys[i]);
153
+ Pi = Point.fromBytes(publicKeys[i]);
162
154
  } catch (error) {
163
155
  throw new InvalidContributionErr(i, 'pubkey');
164
156
  }
165
157
  aggPublicKey = aggPublicKey.add(Pi.multiply(keyAggCoeffInternal(publicKeys[i], pk2, L)));
166
158
  }
167
- let gAcc = 1n;
168
- let tweakAcc = 0n;
159
+ let gAcc = Fn.ONE;
160
+ let tweakAcc = Fn.ZERO;
169
161
  // Apply tweaks
170
162
  for (let i = 0; i < tweaks.length; i++) {
171
- const g = isXonly[i] && !aggPublicKey.hasEvenY() ? modN(-1n) : 1n;
172
- const t = bytesToNumberBE(tweaks[i]);
173
- aInRange('tweak', t, 0n, SECP_N);
163
+ const g = isXonly[i] && !hasEven(aggPublicKey.y) ? Fn.neg(Fn.ONE) : Fn.ONE;
164
+ const t = Fn.fromBytes(tweaks[i]);
174
165
  aggPublicKey = aggPublicKey.multiply(g).add(mulBase(t));
175
166
  if (isZero(aggPublicKey)) throw new Error('The result of tweaking cannot be infinity');
176
- gAcc = modN(g * gAcc);
177
- tweakAcc = modN(t + g * tweakAcc);
167
+ gAcc = Fn.mul(g, gAcc);
168
+ tweakAcc = Fn.add(t, Fn.mul(g, tweakAcc));
178
169
  }
179
170
  return { aggPublicKey, gAcc, tweakAcc };
180
171
  }
@@ -183,7 +174,7 @@ export function keyAggregate(
183
174
  * @param ctx The result of the keyAggregate function.
184
175
  * @returns The aggregate public key as a byte array.
185
176
  */
186
- export function keyAggExport(ctx: ReturnType<typeof keyAggregate>) {
177
+ export function keyAggExport(ctx: ReturnType<typeof keyAggregate>): Uint8Array {
187
178
  return pointToBytes(ctx.aggPublicKey);
188
179
  }
189
180
 
@@ -237,7 +228,8 @@ export function nonceGen(
237
228
  ): Nonces {
238
229
  abytes(publicKey, PUBKEY_LEN);
239
230
  abytesOptional(secretKey, 32);
240
- abytes(aggPublicKey, 0, 32);
231
+ abytes(aggPublicKey);
232
+ if (![0, 32].includes(aggPublicKey.length)) throw new Error('wrong aggPublicKey');
241
233
  abytesOptional(msg);
242
234
  abytes(extraIn);
243
235
  abytes(rand, 32);
@@ -245,8 +237,8 @@ export function nonceGen(
245
237
  if (secretKey !== undefined) rand = aux(secretKey, rand);
246
238
  const msgPrefixed =
247
239
  msg !== undefined
248
- ? concatBytes(new Uint8Array([1]), numberToBytesBE(msg.length, 8), msg)
249
- : new Uint8Array([0]);
240
+ ? concatBytes(Uint8Array.of(1), numberToBytesBE(msg.length, 8), msg)
241
+ : Uint8Array.of(0);
250
242
  const k1 = nonceHash(rand, publicKey, aggPublicKey, 0, msgPrefixed, extraIn);
251
243
  const k2 = nonceHash(rand, publicKey, aggPublicKey, 1, msgPrefixed, extraIn);
252
244
  return {
@@ -342,7 +334,7 @@ export class Session {
342
334
  */
343
335
  private getSessionKeyAggCoeff(P: Point): bigint {
344
336
  const { publicKeys } = this;
345
- const pk = P.toRawBytes(true);
337
+ const pk = P.toBytes(true);
346
338
  const found = publicKeys.some((p) => equalBytes(p, pk));
347
339
  if (!found) throw new Error("The signer's pubkey must be included in the list of pubkeys");
348
340
  return keyAggCoeffInternal(pk, this.secondKey, this.L);
@@ -353,16 +345,16 @@ export class Session {
353
345
  publicKey: Uint8Array
354
346
  ): boolean {
355
347
  const { Q, gAcc, b, R, e } = this;
356
- const s = bytesToNumberBE(partialSig);
357
- if (s >= SECP_N) return false;
348
+ const s = Fn.fromBytes(partialSig, true);
349
+ if (!Fn.isValid(s)) return false;
358
350
  const { R1, R2 } = PubNonce.decode(publicNonce);
359
351
  const Re_s_ = R1.add(R2.multiply(b));
360
- const Re_s = R.hasEvenY() ? Re_s_ : Re_s_.negate();
361
- const P = Point.fromHex(publicKey);
352
+ const Re_s = hasEven(R.y) ? Re_s_ : Re_s_.negate();
353
+ const P = Point.fromBytes(publicKey);
362
354
  const a = this.getSessionKeyAggCoeff(P);
363
- const g = modN(evenScalar(Q, 1n) * gAcc);
355
+ const g = Fn.mul(evenScalar(Q, 1n), gAcc);
364
356
  const left = mulBase(s);
365
- const right = Re_s.add(P.multiply(modN(e * a * g)));
357
+ const right = Re_s.add(P.multiply(Fn.mul(e, Fn.mul(a, g))));
366
358
  return left.equals(right);
367
359
  }
368
360
 
@@ -383,20 +375,21 @@ export class Session {
383
375
  // zero-out the first 64 bytes of secretNonce so it cannot be reused
384
376
  // TODO: this was in reference implementation, but feels very broken. Modifying input arguments is pretty bad.
385
377
  secretNonce.fill(0, 0, 64);
386
- aInRange('k1', k1_, 0n, SECP_N);
387
- aInRange('k2', k2_, 0n, SECP_N);
378
+ if (!Fn.isValid(k1_)) throw new Error('wrong k1');
379
+ if (!Fn.isValid(k2_)) throw new Error('wrong k1');
388
380
  const k1 = evenScalar(R, k1_);
389
381
  const k2 = evenScalar(R, k2_);
390
- const d_ = bytesToNumberBE(secret);
391
- aInRange('d_', d_, 1n, SECP_N);
382
+ const d_ = Fn.fromBytes(secret);
383
+ if (Fn.is0(d_)) throw new Error('wrong d_');
392
384
  const P = mulBase(d_);
393
- const pk = P.toRawBytes(true);
385
+ const pk = P.toBytes(true);
394
386
  if (!equalBytes(pk, originalPk)) throw new Error('Public key does not match nonceGen argument');
395
387
  const a = this.getSessionKeyAggCoeff(P);
396
388
  const g = evenScalar(Q, 1n);
397
- const d = modN(g * gAcc * d_);
398
- const s = modN(k1 + b * k2 + e * a * d);
399
- const partialSig = numberToBytesBE(s, 32);
389
+ const d = Fn.mul(g, Fn.mul(gAcc, d_));
390
+ /// k1 + (b*k2) + (e*a*d)
391
+ const s = Fn.add(k1, Fn.add(Fn.mul(b, k2), Fn.mul(e, Fn.mul(a, d))));
392
+ const partialSig = Fn.toBytes(s);
400
393
  // Skip validation in fast-sign mode
401
394
  if (!fastSign) {
402
395
  const publicNonce = PubNonce.encode({
@@ -447,13 +440,13 @@ export class Session {
447
440
  const { Q, tweakAcc, R, e } = this;
448
441
  let s = 0n;
449
442
  for (let i = 0; i < partialSigs.length; i++) {
450
- const si = bytesToNumberBE(partialSigs[i]);
451
- if (si >= SECP_N) throw new InvalidContributionErr(i, 'psig');
452
- s = modN(s + si);
443
+ const si = Fn.fromBytes(partialSigs[i], true);
444
+ if (!Fn.isValid(si)) throw new InvalidContributionErr(i, 'psig');
445
+ s = Fn.add(s, si);
453
446
  }
454
447
  const g = evenScalar(Q, 1n);
455
- s = modN(s + e * g * tweakAcc);
456
- return concatBytes(pointToBytes(R), numberToBytesBE(s, 32));
448
+ s = Fn.add(s, Fn.mul(e, Fn.mul(g, tweakAcc))); // s + e * g * tweakAcc
449
+ return concatBytes(pointToBytes(R), Fn.toBytes(s));
457
450
  }
458
451
  }
459
452
 
package/src/p2p.ts CHANGED
@@ -19,21 +19,14 @@
19
19
  */
20
20
 
21
21
  import { FpIsSquare } from '@noble/curves/abstract/modular.js';
22
- import type { Hex } from '@noble/curves/abstract/utils.js';
23
- import {
24
- bytesToNumberBE,
25
- concatBytes,
26
- ensureBytes,
27
- numberToBytesBE,
28
- } from '@noble/curves/abstract/utils.js';
29
- import { type ProjPointType as PointType } from '@noble/curves/abstract/weierstrass.js';
22
+ import { concatBytes, abytes } from '@noble/curves/utils.js';
30
23
  import { schnorr, secp256k1 } from '@noble/curves/secp256k1.js';
31
24
  import { randomBytes } from '@noble/hashes/utils.js';
32
- import { tagSchnorr } from './utils.ts';
33
-
34
- const Fp = secp256k1.CURVE.Fp;
35
- const Point = secp256k1.ProjectivePoint;
25
+ import { tagSchnorr, type Bytes } from './utils.ts';
36
26
 
27
+ const Point = secp256k1.Point;
28
+ const Fp = Point.Fp;
29
+ const Fn = Point.Fn;
37
30
  const _1n = BigInt(1);
38
31
  const _2n = BigInt(2);
39
32
 
@@ -86,29 +79,23 @@ export const elligatorSwift = {
86
79
  return Fp.mul(w0, Fp.add(Fp.div(Fp.mul(u, t0), _2n), v));
87
80
  },
88
81
  // Encode public key (point or x coordinate bigint) into 64-byte pseudorandom encoding
89
- encode: (x: bigint | PointType<bigint>): Uint8Array => {
90
- if (x instanceof secp256k1.ProjectivePoint) x = x.x;
91
- if (typeof x !== 'bigint') {
92
- throw new Error(
93
- 'elligatorSwift.encode: wrong public key. Should be Projective point or x coordinate (bigint)'
94
- );
95
- }
82
+ encode: (x: bigint): Uint8Array => {
96
83
  // 200k test cycles per keygen: avg=4 max=48
97
84
  // seems too much, but same as for reference implementation
98
85
  while (true) {
99
86
  // random scalar 1..Fp.ORDER
100
- const u = Fp.create(Fp.fromBytes(secp256k1.utils.randomPrivateKey()));
87
+ const u = Fp.create(Fp.fromBytes(secp256k1.utils.randomSecretKey()));
101
88
  const ellCase = randomBytes(1)[0] & 7; // [0..8)
102
89
  const t = elligatorSwift._inv(x, u, ellCase);
103
90
  if (!t) continue;
104
- return concatBytes(numberToBytesBE(u, 32), numberToBytesBE(t, 32));
91
+ return concatBytes(Fp.toBytes(u), Fp.toBytes(t));
105
92
  }
106
93
  },
107
94
  // Decode elligatorSwift point to xonly
108
- decode: (data: Hex): Uint8Array => {
109
- const _data = ensureBytes('data', data, 64);
110
- let u = Fp.create(Fp.fromBytes(_data.subarray(0, 32)));
111
- let t = Fp.create(Fp.fromBytes(_data.subarray(32, 64)));
95
+ decode: (data: Uint8Array): Uint8Array => {
96
+ const _data = abytes(data, 64, 'data');
97
+ let u = Fp.create(Fp.fromBytes(_data.subarray(0, 32), true));
98
+ let t = Fp.create(Fp.fromBytes(_data.subarray(32, 64), true));
112
99
  if (Fp.is0(u)) u = Fp.create(_1n);
113
100
  if (Fp.is0(t)) t = Fp.create(_1n);
114
101
  const u3 = Fp.mul(Fp.mul(u, u), u); // u**3
@@ -121,36 +108,37 @@ export const elligatorSwift = {
121
108
  const y = Fp.div(Fp.add(x, t), Fp.mul(MINUS_3_SQRT, u));
122
109
  // try different cases
123
110
  let res = Fp.add(u, Fp.mul(Fp.mul(y, y), _4n)); // u + 4 * Y ** 2,
124
- if (isValidX(res)) return numberToBytesBE(res, 32);
111
+ if (isValidX(res)) return Fp.toBytes(res);
125
112
  res = Fp.div(Fp.sub(Fp.div(Fp.neg(x), y), u), _2n); // (-X / Y - u) / 2
126
- if (isValidX(res)) return numberToBytesBE(res, 32);
113
+ if (isValidX(res)) return Fp.toBytes(res);
127
114
  res = Fp.div(Fp.sub(Fp.div(x, y), u), _2n); // (X / Y - u) / 2
128
- if (isValidX(res)) return numberToBytesBE(res, 32);
115
+ if (isValidX(res)) return Fp.toBytes(res);
129
116
  throw new Error('elligatorSwift: cannot decode public key');
130
117
  },
131
118
  // Generate pair (public key, secret key)
132
119
  keygen: () => {
133
- const privateKey = secp256k1.utils.randomPrivateKey();
134
- const publicKey = elligatorSwift.encode(Point.fromPrivateKey(privateKey));
120
+ const privateKey: Bytes = secp256k1.utils.randomSecretKey();
121
+ const p = Point.BASE.multiply(Point.Fn.fromBytes(privateKey));
122
+ const publicKey: Bytes = elligatorSwift.encode(p.x);
135
123
  return { privateKey, publicKey };
136
124
  },
137
125
  // Generates shared secret between a pub key and a priv key
138
- getSharedSecret: (privateKeyA: Hex, publicKeyB: Hex) => {
126
+ getSharedSecret: (privateKeyA: Uint8Array, publicKeyB: Uint8Array): Bytes => {
139
127
  const pub = elligatorSwift.decode(publicKeyB);
140
- const priv = ensureBytes('privKey', privateKeyA, 32);
128
+ const priv = abytes(privateKeyA, 32, 'privKey');
141
129
  const point = schnorr.utils.lift_x(Fp.fromBytes(pub));
142
- const d = bytesToNumberBE(priv);
143
- return numberToBytesBE(point.multiply(d).x, 32);
130
+ const d = Fn.fromBytes(priv);
131
+ return Fp.toBytes(point.multiply(d).x);
144
132
  },
145
133
  // BIP324 shared secret
146
134
  getSharedSecretBip324: (
147
- privateKeyOurs: Hex,
148
- publicKeyTheirs: Hex,
149
- publicKeyOurs: Hex,
135
+ privateKeyOurs: Uint8Array,
136
+ publicKeyTheirs: Uint8Array,
137
+ publicKeyOurs: Uint8Array,
150
138
  initiating: boolean
151
- ) => {
152
- const ours = ensureBytes('publicKeyOurs', publicKeyOurs);
153
- const theirs = ensureBytes('publicKeyTheirs', publicKeyTheirs);
139
+ ): Uint8Array => {
140
+ const ours = abytes(publicKeyOurs, undefined, 'publicKeyOurs');
141
+ const theirs = abytes(publicKeyTheirs, undefined, 'publicKeyTheirs');
154
142
  const ecdhPoint = elligatorSwift.getSharedSecret(privateKeyOurs, theirs);
155
143
  const pubs = initiating ? [ours, theirs] : [theirs, ours];
156
144
  return tagSchnorr('bip324_ellswift_xonly_ecdh', ...pubs, ecdhPoint);
package/src/psbt.ts CHANGED
@@ -14,13 +14,13 @@ import { type Bytes, compareBytes, equalBytes, PubT, validatePubkey } from './ut
14
14
  // PSBT BIP174, BIP370, BIP371
15
15
 
16
16
  // Can be 33 or 64 bytes
17
- const PubKeyECDSA: P.CoderType<Uint8Array> = P.validate(P.bytes(null), (pub) =>
17
+ const PubKeyECDSA: P.CoderType<Bytes> = P.validate(P.bytes(null), (pub) =>
18
18
  validatePubkey(pub, PubT.ecdsa)
19
19
  );
20
- const PubKeySchnorr: P.CoderType<Uint8Array> = P.validate(P.bytes(32), (pub) =>
20
+ const PubKeySchnorr: P.CoderType<Bytes> = P.validate(P.bytes(32), (pub) =>
21
21
  validatePubkey(pub, PubT.schnorr)
22
22
  );
23
- const SignatureSchnorr: P.CoderType<Uint8Array> = P.validate(P.bytes(null), (sig) => {
23
+ const SignatureSchnorr: P.CoderType<Bytes> = P.validate(P.bytes(null), (sig) => {
24
24
  if (sig.length !== 64 && sig.length !== 65)
25
25
  throw new Error('Schnorr signature should be 64 or 65 bytes long');
26
26
  return sig;
@@ -61,9 +61,9 @@ const tapTree = P.array(
61
61
  })
62
62
  );
63
63
 
64
- const BytesInf: P.CoderType<P.Bytes> = P.bytes(null); // Bytes will conflict with Bytes type
65
- const Bytes20: P.CoderType<P.Bytes> = P.bytes(20);
66
- const Bytes32: P.CoderType<P.Bytes> = P.bytes(32);
64
+ const BytesInf: P.CoderType<Bytes> = P.bytes(null); // Bytes will conflict with Bytes type
65
+ const Bytes20: P.CoderType<Bytes> = P.bytes(20);
66
+ const Bytes32: P.CoderType<Bytes> = P.bytes(32);
67
67
  // versionsRequiringExclusing = !versionsAllowsInclusion (as set)
68
68
  // {name: [tag, keyCoder, valueCoder, versionsRequiringInclusion, versionsRequiringExclusing, versionsAllowsInclusion, silentIgnore]}
69
69
  // SilentIgnore: we use some v2 fields for v1 representation too, so we just clean them before serialize
@@ -180,7 +180,7 @@ type PSBTKeyMap = Record<string, PSBTKeyMapInfo>;
180
180
  const PSBTUnknownKey: P.CoderType<
181
181
  P.StructInput<{
182
182
  type: number;
183
- key: Uint8Array;
183
+ key: Bytes;
184
184
  }>
185
185
  > = P.struct({ type: CompactSizeLen, key: P.bytes(null) });
186
186
  type PSBTUnknownFields = { unknown?: [P.UnwrapCoder<typeof PSBTUnknownKey>, Bytes][] };
package/src/script.ts CHANGED
@@ -1,39 +1,42 @@
1
1
  import * as P from 'micro-packed';
2
- import { isBytes } from './utils.ts';
2
+ import { isBytes, reverseObject, type ValueOf, type Bytes } from './utils.ts';
3
3
 
4
4
  export const MAX_SCRIPT_BYTE_LENGTH = 520;
5
5
 
6
6
  // prettier-ignore
7
- export enum OP {
8
- OP_0 = 0x00, PUSHDATA1 = 0x4c, PUSHDATA2, PUSHDATA4, '1NEGATE',
9
- RESERVED = 0x50,
10
- OP_1, OP_2, OP_3, OP_4, OP_5, OP_6, OP_7, OP_8,
11
- OP_9, OP_10, OP_11, OP_12, OP_13, OP_14, OP_15, OP_16,
7
+ export const OP = {
8
+ OP_0: 0, PUSHDATA1: 76, PUSHDATA2: 77, PUSHDATA4: 78, '1NEGATE': 79,
9
+ RESERVED: 80,
10
+ OP_1: 81, OP_2: 82, OP_3: 83, OP_4: 84, OP_5: 85, OP_6: 86, OP_7: 87, OP_8: 88, OP_9: 89,
11
+ OP_10: 90, OP_11: 91, OP_12: 92, OP_13: 93, OP_14: 94, OP_15: 95, OP_16: 96,
12
12
  // Control
13
- NOP, VER, IF, NOTIF, VERIF, VERNOTIF, ELSE, ENDIF, VERIFY, RETURN,
13
+ NOP: 97, VER: 98, IF: 99, NOTIF: 100, VERIF: 101, VERNOTIF: 102, ELSE: 103, ENDIF: 104, VERIFY: 105, RETURN: 106,
14
14
  // Stack
15
- TOALTSTACK, FROMALTSTACK, '2DROP', '2DUP', '3DUP', '2OVER', '2ROT', '2SWAP',
16
- IFDUP, DEPTH, DROP, DUP, NIP, OVER, PICK, ROLL, ROT, SWAP, TUCK,
15
+ TOALTSTACK: 107, FROMALTSTACK: 108, '2DROP': 109, '2DUP': 110, '3DUP': 111, '2OVER': 112, '2ROT': 113, '2SWAP': 114,
16
+ IFDUP: 115, DEPTH: 116, DROP: 117, DUP: 118, NIP: 119, OVER: 120, PICK: 121, ROLL: 122, ROT: 123, SWAP: 124, TUCK: 125,
17
17
  // Splice
18
- CAT, SUBSTR, LEFT, RIGHT, SIZE,
18
+ CAT: 126, SUBSTR: 127, LEFT: 128, RIGHT: 129, SIZE: 130,
19
19
  // Boolean logic
20
- INVERT, AND, OR, XOR, EQUAL, EQUALVERIFY, RESERVED1, RESERVED2,
21
- // Numbers
22
- '1ADD', '1SUB', '2MUL', '2DIV',
23
- NEGATE, ABS, NOT, '0NOTEQUAL',
24
- ADD, SUB, MUL, DIV, MOD, LSHIFT, RSHIFT, BOOLAND, BOOLOR,
25
- NUMEQUAL, NUMEQUALVERIFY, NUMNOTEQUAL, LESSTHAN, GREATERTHAN,
26
- LESSTHANOREQUAL, GREATERTHANOREQUAL, MIN, MAX, WITHIN,
20
+ INVERT: 131, AND: 132, OR: 133, XOR: 134, EQUAL: 135, EQUALVERIFY: 136, RESERVED1: 137, RESERVED2: 138,
21
+ // Numbers
22
+ '1ADD': 139, '1SUB': 140, '2MUL': 141, '2DIV': 142,
23
+ NEGATE: 143, ABS: 144, NOT: 145, '0NOTEQUAL': 146,
24
+ ADD: 147, SUB: 148, MUL: 149, DIV: 150, MOD: 151, LSHIFT: 152, RSHIFT: 153, BOOLAND: 154, BOOLOR: 155,
25
+ NUMEQUAL: 156, NUMEQUALVERIFY: 157, NUMNOTEQUAL: 158, LESSTHAN: 159, GREATERTHAN: 160,
26
+ LESSTHANOREQUAL: 161, GREATERTHANOREQUAL: 162, MIN: 163, MAX: 164, WITHIN: 165,
27
27
  // Crypto
28
- RIPEMD160, SHA1, SHA256, HASH160, HASH256, CODESEPARATOR,
29
- CHECKSIG, CHECKSIGVERIFY, CHECKMULTISIG, CHECKMULTISIGVERIFY,
28
+ RIPEMD160: 166, SHA1: 167, SHA256: 168, HASH160: 169, HASH256: 170, CODESEPARATOR: 171,
29
+ CHECKSIG: 172, CHECKSIGVERIFY: 173, CHECKMULTISIG: 174, CHECKMULTISIGVERIFY: 175,
30
30
  // Expansion
31
- NOP1, CHECKLOCKTIMEVERIFY, CHECKSEQUENCEVERIFY, NOP4, NOP5, NOP6, NOP7, NOP8, NOP9, NOP10,
31
+ NOP1: 176, CHECKLOCKTIMEVERIFY: 177, CHECKSEQUENCEVERIFY: 178, NOP4: 179, NOP5: 180, NOP6: 181, NOP7: 182, NOP8: 183, NOP9: 184, NOP10: 185,
32
32
  // BIP 342
33
- CHECKSIGADD,
33
+ CHECKSIGADD: 186,
34
34
  // Invalid
35
- INVALID = 0xff,
36
- }
35
+ INVALID: 255,
36
+ };
37
+
38
+ export const OPNames = reverseObject(OP);
39
+ export type OP = ValueOf<typeof OP>;
37
40
 
38
41
  export type ScriptOP = keyof typeof OP | Uint8Array | number;
39
42
  export type ScriptType = ScriptOP[];
@@ -157,7 +160,7 @@ export const Script: P.CoderType<ScriptType> = P.wrap({
157
160
  } else if (OP.OP_1 <= cur && cur <= OP.OP_16) {
158
161
  out.push(cur - (OP.OP_1 - 1));
159
162
  } else {
160
- const op = OP[cur] as keyof typeof OP;
163
+ const op = OPNames[cur] as keyof typeof OP;
161
164
  if (op === undefined) throw new Error(`Unknown opcode=${cur.toString(16)}`);
162
165
  out.push(op);
163
166
  }
@@ -200,10 +203,10 @@ export const CompactSize: P.CoderType<bigint> = P.wrap({
200
203
  export const CompactSizeLen: P.CoderType<number> = P.apply(CompactSize, P.coders.numberBigint);
201
204
 
202
205
  // ui8a of size <CompactSize>
203
- export const VarBytes: P.CoderType<Uint8Array> = P.bytes(CompactSize);
206
+ export const VarBytes: P.CoderType<Bytes> = P.bytes(CompactSize);
204
207
 
205
208
  // SegWit v0 stack of witness buffers
206
- export const RawWitness: P.CoderType<Uint8Array[]> = P.array(CompactSizeLen, VarBytes);
209
+ export const RawWitness: P.CoderType<Bytes[]> = P.array(CompactSizeLen, VarBytes);
207
210
 
208
211
  // Array of size <CompactSize>
209
212
  export const BTCArray = <T>(t: P.CoderType<T>): P.CoderType<T[]> => P.array(CompactSize, t);
@@ -14,7 +14,7 @@ import {
14
14
  import * as u from './utils.ts';
15
15
  import { type Bytes, NETWORK, concatBytes, equalBytes, isBytes } from './utils.ts';
16
16
 
17
- const EMPTY32 = new Uint8Array(32);
17
+ const EMPTY32: Uint8Array = new Uint8Array(32);
18
18
  const EMPTY_OUTPUT: P.UnwrapCoder<typeof RawOutput> = {
19
19
  amount: 0xffffffffffffffffn,
20
20
  script: P.EMPTY,
@@ -94,24 +94,26 @@ export interface TxOpts {
94
94
  * Internal, exported only for backwards-compat. Use `SigHash` instead.
95
95
  * @deprecated
96
96
  */
97
- export enum SignatureHash {
98
- DEFAULT,
99
- ALL,
100
- NONE,
101
- SINGLE,
102
- ANYONECANPAY = 0x80,
103
- }
97
+ export const SignatureHash = {
98
+ DEFAULT: 0,
99
+ ALL: 1,
100
+ NONE: 2,
101
+ SINGLE: 3,
102
+ ANYONECANPAY: 0x80,
103
+ };
104
104
 
105
- export enum SigHash {
106
- DEFAULT = SignatureHash.DEFAULT,
107
- ALL = SignatureHash.ALL,
108
- NONE = SignatureHash.NONE,
109
- SINGLE = SignatureHash.SINGLE,
110
- DEFAULT_ANYONECANPAY = SignatureHash.DEFAULT | SignatureHash.ANYONECANPAY,
111
- ALL_ANYONECANPAY = SignatureHash.ALL | SignatureHash.ANYONECANPAY,
112
- NONE_ANYONECANPAY = SignatureHash.NONE | SignatureHash.ANYONECANPAY,
113
- SINGLE_ANYONECANPAY = SignatureHash.SINGLE | SignatureHash.ANYONECANPAY,
114
- }
105
+ export const SigHash = {
106
+ DEFAULT: SignatureHash.DEFAULT,
107
+ ALL: SignatureHash.ALL,
108
+ NONE: SignatureHash.NONE,
109
+ SINGLE: SignatureHash.SINGLE,
110
+ DEFAULT_ANYONECANPAY: SignatureHash.DEFAULT | SignatureHash.ANYONECANPAY,
111
+ ALL_ANYONECANPAY: SignatureHash.ALL | SignatureHash.ANYONECANPAY,
112
+ NONE_ANYONECANPAY: SignatureHash.NONE | SignatureHash.ANYONECANPAY,
113
+ SINGLE_ANYONECANPAY: SignatureHash.SINGLE | SignatureHash.ANYONECANPAY,
114
+ } as const;
115
+ export const SigHashNames = u.reverseObject(SigHash);
116
+ export type SigHash = u.ValueOf<typeof SigHash>;
115
117
 
116
118
  function getTaprootKeys(
117
119
  privKey: Bytes,
@@ -163,7 +165,7 @@ function cleanFinalInput(i: psbt.TransactionInput) {
163
165
  const TxHashIdx = P.struct({ txid: P.bytes(32, true), index: P.U32LE });
164
166
 
165
167
  function validateSigHash(s: SigHash) {
166
- if (typeof s !== 'number' || typeof SigHash[s] !== 'string')
168
+ if (typeof s !== 'number' || typeof SigHashNames[s] !== 'string')
167
169
  throw new Error(`Invalid SigHash=${s}`);
168
170
  return s;
169
171
  }
@@ -635,11 +637,9 @@ export class Transaction {
635
637
  }
636
638
 
637
639
  get hash(): string {
638
- if (!this.isFinal) throw new Error('Transaction is not finalized');
639
640
  return hex.encode(u.sha256x2(this.toBytes(true)));
640
641
  }
641
642
  get id(): string {
642
- if (!this.isFinal) throw new Error('Transaction is not finalized');
643
643
  return hex.encode(u.sha256x2(this.toBytes(true)).reverse());
644
644
  }
645
645
  // Input stuff
package/src/utils.ts CHANGED
@@ -1,12 +1,15 @@
1
1
  import { schnorr, secp256k1 as secp } from '@noble/curves/secp256k1.js';
2
+ import { bytesToNumberBE, numberToBytesBE } from '@noble/curves/utils.js';
2
3
  import { ripemd160 } from '@noble/hashes/legacy.js';
3
4
  import { sha256 } from '@noble/hashes/sha2.js';
4
5
  import { utils as packedUtils, U32LE } from 'micro-packed';
5
6
 
6
7
  export type Hex = string | Uint8Array;
7
8
  export type Bytes = Uint8Array;
8
- const Point = secp.ProjectivePoint;
9
- const CURVE_ORDER = secp.CURVE.n;
9
+ const Point = secp.Point;
10
+ const Fn = Point.Fn;
11
+ const CURVE_ORDER = Point.Fn.ORDER;
12
+ export const hasEven = (y: bigint) => y % 2n === 0n;
10
13
 
11
14
  const isBytes: (a: unknown) => a is Uint8Array = packedUtils.isBytes;
12
15
  const concatBytes: (...arrays: Uint8Array[]) => Uint8Array = packedUtils.concatBytes;
@@ -15,9 +18,9 @@ export { concatBytes, equalBytes, isBytes, sha256 };
15
18
 
16
19
  export const hash160 = (msg: Uint8Array): Uint8Array => ripemd160(sha256(msg));
17
20
  export const sha256x2 = (...msgs: Uint8Array[]): Uint8Array => sha256(sha256(concatBytes(...msgs)));
18
- export const randomPrivateKeyBytes: () => Uint8Array = schnorr.utils.randomPrivateKey;
21
+ export const randomPrivateKeyBytes: () => Uint8Array = schnorr.utils.randomSecretKey;
19
22
  export const pubSchnorr = schnorr.getPublicKey as (priv: string | Uint8Array) => Uint8Array;
20
- export const pubECDSA: (privateKey: string | Uint8Array, isCompressed?: boolean) => Uint8Array =
23
+ export const pubECDSA: (privateKey: Uint8Array, isCompressed?: boolean) => Uint8Array =
21
24
  secp.getPublicKey;
22
25
 
23
26
  // low-r signature grinding. Used to reduce tx size by 1 byte.
@@ -26,35 +29,37 @@ export const pubECDSA: (privateKey: string | Uint8Array, isCompressed?: boolean)
26
29
  // Not best way, but closest to bitcoin implementation (easier to check)
27
30
  const hasLowR = (sig: { r: bigint; s: bigint }) => sig.r < CURVE_ORDER / 2n;
28
31
  export function signECDSA(hash: Bytes, privateKey: Bytes, lowR = false): Bytes {
29
- let sig = secp.sign(hash, privateKey);
32
+ let sig = secp.Signature.fromBytes(secp.sign(hash, privateKey, { prehash: false }));
30
33
  if (lowR && !hasLowR(sig)) {
31
34
  const extraEntropy = new Uint8Array(32);
32
35
  let counter = 0;
33
36
  while (!hasLowR(sig)) {
34
37
  extraEntropy.set(U32LE.encode(counter++));
35
- sig = secp.sign(hash, privateKey, { extraEntropy });
38
+ sig = secp.Signature.fromBytes(secp.sign(hash, privateKey, { prehash: false, extraEntropy }));
36
39
  if (counter > 4294967295) throw new Error('lowR counter overflow: report the error');
37
40
  }
38
41
  }
39
- return sig.toDERRawBytes();
42
+ return sig.toBytes('der');
40
43
  }
41
44
 
42
45
  export const signSchnorr: typeof schnorr.sign = schnorr.sign;
43
46
  export const tagSchnorr: typeof schnorr.utils.taggedHash = schnorr.utils.taggedHash;
44
47
 
45
- export enum PubT {
46
- ecdsa,
47
- schnorr,
48
- }
48
+ export const PubT = {
49
+ ecdsa: 0,
50
+ schnorr: 1,
51
+ };
52
+ export type PubT = ValueOf<typeof PubT>;
53
+
49
54
  export function validatePubkey(pub: Bytes, type: PubT): Bytes {
50
55
  const len = pub.length;
51
56
  if (type === PubT.ecdsa) {
52
57
  if (len === 32) throw new Error('Expected non-Schnorr key');
53
- Point.fromHex(pub); // does assertValidity
58
+ Point.fromBytes(pub); // does assertValidity
54
59
  return pub;
55
60
  } else if (type === PubT.schnorr) {
56
61
  if (len !== 32) throw new Error('Expected 32-byte Schnorr key');
57
- schnorr.utils.lift_x(schnorr.utils.bytesToNumberBE(pub));
62
+ schnorr.utils.lift_x(bytesToNumberBE(pub));
58
63
  return pub;
59
64
  } else {
60
65
  throw new Error('Unknown key type');
@@ -64,30 +69,30 @@ export function validatePubkey(pub: Bytes, type: PubT): Bytes {
64
69
  export function tapTweak(a: Bytes, b: Bytes): bigint {
65
70
  const u = schnorr.utils;
66
71
  const t = u.taggedHash('TapTweak', a, b);
67
- const tn = u.bytesToNumberBE(t);
72
+ const tn = bytesToNumberBE(t);
68
73
  if (tn >= CURVE_ORDER) throw new Error('tweak higher than curve order');
69
74
  return tn;
70
75
  }
71
76
 
72
77
  export function taprootTweakPrivKey(privKey: Bytes, merkleRoot: Bytes = Uint8Array.of()): Bytes {
73
78
  const u = schnorr.utils;
74
- const seckey0 = u.bytesToNumberBE(privKey); // seckey0 = int_from_bytes(seckey0)
75
- const P = Point.fromPrivateKey(seckey0); // P = point_mul(G, seckey0)
79
+ const seckey0 = bytesToNumberBE(privKey); // seckey0 = int_from_bytes(seckey0)
80
+ const P = Point.BASE.multiply(seckey0); // P = point_mul(G, seckey0)
76
81
  // seckey = seckey0 if has_even_y(P) else SECP256K1_ORDER - seckey0
77
- const seckey = P.hasEvenY() ? seckey0 : u.mod(-seckey0, CURVE_ORDER);
82
+ const seckey = hasEven(P.y) ? seckey0 : Fn.neg(seckey0);
78
83
  const xP = u.pointToBytes(P);
79
84
  // t = int_from_bytes(tagged_hash("TapTweak", bytes_from_int(x(P)) + h)); >= SECP256K1_ORDER check
80
85
  const t = tapTweak(xP, merkleRoot);
81
86
  // bytes_from_int((seckey + t) % SECP256K1_ORDER)
82
- return u.numberToBytesBE(u.mod(seckey + t, CURVE_ORDER), 32);
87
+ return numberToBytesBE(Fn.add(seckey, t), 32);
83
88
  }
84
89
 
85
90
  export function taprootTweakPubkey(pubKey: Bytes, h: Bytes): [Bytes, number] {
86
91
  const u = schnorr.utils;
87
92
  const t = tapTweak(pubKey, h); // t = int_from_bytes(tagged_hash("TapTweak", pubkey + h))
88
- const P = u.lift_x(u.bytesToNumberBE(pubKey)); // P = lift_x(int_from_bytes(pubkey))
89
- const Q = P.add(Point.fromPrivateKey(t)); // Q = point_add(P, point_mul(G, t))
90
- const parity = Q.hasEvenY() ? 0 : 1; // 0 if has_even_y(Q) else 1
93
+ const P = u.lift_x(bytesToNumberBE(pubKey)); // P = lift_x(int_from_bytes(pubkey))
94
+ const Q = P.add(Point.BASE.multiply(t)); // Q = point_add(P, point_mul(G, t))
95
+ const parity = hasEven(Q.y) ? 0 : 1; // 0 if has_even_y(Q) else 1
91
96
  return [u.pointToBytes(Q), parity]; // bytes_from_int(x(Q))
92
97
  }
93
98
 
@@ -97,7 +102,7 @@ export function taprootTweakPubkey(pubKey: Bytes, h: Bytes): [Bytes, number] {
97
102
  // It is possible to switch SECP256K1_GENERATOR_POINT with some random point;
98
103
  // but it's too complex to prove.
99
104
  // Also used by bitcoin-core and bitcoinjs-lib
100
- export const TAPROOT_UNSPENDABLE_KEY: Bytes = sha256(Point.BASE.toRawBytes(false));
105
+ export const TAPROOT_UNSPENDABLE_KEY: Bytes = sha256(Point.BASE.toBytes(false));
101
106
 
102
107
  export type BTC_NETWORK = {
103
108
  bech32: string;
@@ -127,3 +132,17 @@ export function compareBytes(a: Bytes, b: Bytes): number {
127
132
  for (let i = 0; i < len; i++) if (a[i] != b[i]) return Math.sign(a[i] - b[i]);
128
133
  return Math.sign(a.length - b.length);
129
134
  }
135
+
136
+ // Reverses key<->values
137
+ export function reverseObject<T extends Record<string, string | number>>(
138
+ obj: T
139
+ ): { [K in T[keyof T]]: Extract<keyof T, string> } {
140
+ const res = {} as any;
141
+ for (const k in obj) {
142
+ if (res[obj[k]] !== undefined) throw new Error('duplicate key');
143
+ res[obj[k]] = k;
144
+ }
145
+ return res;
146
+ }
147
+
148
+ export type ValueOf<T> = T[keyof T];