@scure/btc-signer 1.8.0 → 2.0.0-beta.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 (86) hide show
  1. package/README.md +15 -12
  2. package/_type_test.js +10 -12
  3. package/_type_test.js.map +1 -1
  4. package/index.js +17 -61
  5. package/index.js.map +1 -1
  6. package/musig2.d.ts +2 -2
  7. package/musig2.d.ts.map +1 -1
  8. package/musig2.js +93 -91
  9. package/musig2.js.map +1 -1
  10. package/p2p.d.ts +7 -8
  11. package/p2p.d.ts.map +1 -1
  12. package/p2p.js +32 -38
  13. package/p2p.js.map +1 -1
  14. package/package.json +33 -70
  15. package/payment.js +73 -96
  16. package/payment.js.map +1 -1
  17. package/psbt.d.ts +224 -224
  18. package/psbt.d.ts.map +1 -1
  19. package/psbt.js +58 -63
  20. package/psbt.js.map +1 -1
  21. package/script.d.ts +125 -120
  22. package/script.d.ts.map +1 -1
  23. package/script.js +46 -146
  24. package/script.js.map +1 -1
  25. package/src/_type_test.ts +2 -2
  26. package/src/musig2.ts +45 -53
  27. package/src/p2p.ts +31 -43
  28. package/src/psbt.ts +7 -7
  29. package/src/script.ts +29 -26
  30. package/src/transaction.ts +31 -24
  31. package/src/utils.ts +45 -26
  32. package/src/utxo.ts +1 -1
  33. package/transaction.d.ts +23 -28
  34. package/transaction.d.ts.map +1 -1
  35. package/transaction.js +140 -154
  36. package/transaction.js.map +1 -1
  37. package/utils.d.ts +13 -7
  38. package/utils.d.ts.map +1 -1
  39. package/utils.js +60 -62
  40. package/utils.js.map +1 -1
  41. package/utxo.d.ts +45 -45
  42. package/utxo.d.ts.map +1 -1
  43. package/utxo.js +63 -59
  44. package/utxo.js.map +1 -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 -405
  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 -171
  75. package/esm/transaction.d.ts.map +0 -1
  76. package/esm/transaction.js +0 -1182
  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/package.json +0 -3
package/src/musig2.ts CHANGED
@@ -1,16 +1,8 @@
1
- import { mod } from '@noble/curves/abstract/modular';
2
- import {
3
- aInRange,
4
- bytesToNumberBE,
5
- concatBytes,
6
- equalBytes,
7
- numberToBytesBE,
8
- } from '@noble/curves/abstract/utils';
9
- import { schnorr, secp256k1 } from '@noble/curves/secp256k1';
10
- import { abytes, anumber } from '@noble/hashes/_assert';
11
- import { randomBytes } from '@noble/hashes/utils';
1
+ import { schnorr, secp256k1 } from '@noble/curves/secp256k1.js';
2
+ import { aInRange, concatBytes, equalBytes, numberToBytesBE } from '@noble/curves/utils.js';
3
+ import { abytes, anumber, randomBytes } from '@noble/hashes/utils.js';
12
4
  import * as P from 'micro-packed';
13
- import { compareBytes } from './utils.ts';
5
+ import { compareBytes, hasEven } from './utils.ts';
14
6
 
15
7
  /*
16
8
  MuSig2. This is not the full protocol: only an implementation of primitives from BIP-327.
@@ -46,20 +38,20 @@ export class InvalidContributionErr extends Error {
46
38
 
47
39
  // Utils
48
40
  const { taggedHash, pointToBytes } = schnorr.utils;
49
- const Point = secp256k1.ProjectivePoint;
41
+ const Point = secp256k1.Point;
50
42
  type Point = typeof Point.BASE;
51
- const PUBKEY_LEN = 33;
43
+ const Fn = Point.Fn;
44
+ const PUBKEY_LEN = secp256k1.lengths.publicKey!;
52
45
  const ZERO = new Uint8Array(PUBKEY_LEN); // Compressed zero point
53
- const SECP_N = secp256k1.CURVE.n;
54
46
 
55
47
  // Encoding
56
48
  // TODO: re-use in PSBT?
57
49
  const compressed = P.apply(P.bytes(33), {
58
- decode: (p: Point) => (isZero(p) ? ZERO : p.toRawBytes(true)),
59
- 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)),
60
52
  });
61
53
  const scalar = P.validate(P.U256BE, (n) => {
62
- aInRange('n', n, 1n, SECP_N);
54
+ aInRange('n', n, 1n, Fn.ORDER);
63
55
  return n;
64
56
  });
65
57
  const PubNonce = P.struct({ R1: compressed, R2: compressed });
@@ -82,10 +74,9 @@ function aXonly(lst: boolean[]) {
82
74
  });
83
75
  }
84
76
 
85
- const modN = (x: bigint) => mod(x, SECP_N);
86
77
  const taggedInt = (tag: string, ...messages: Uint8Array[]) =>
87
- modN(bytesToNumberBE(taggedHash(tag, ...messages)));
88
- 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));
89
80
 
90
81
  // Short utility for compat with reference implementation
91
82
  export function IndividualPubkey(seckey: Uint8Array): Uint8Array {
@@ -159,23 +150,22 @@ export function keyAggregate(
159
150
  for (let i = 0; i < publicKeys.length; i++) {
160
151
  let Pi;
161
152
  try {
162
- Pi = Point.fromHex(publicKeys[i]);
153
+ Pi = Point.fromBytes(publicKeys[i]);
163
154
  } catch (error) {
164
155
  throw new InvalidContributionErr(i, 'pubkey');
165
156
  }
166
157
  aggPublicKey = aggPublicKey.add(Pi.multiply(keyAggCoeffInternal(publicKeys[i], pk2, L)));
167
158
  }
168
- let gAcc = 1n;
169
- let tweakAcc = 0n;
159
+ let gAcc = Fn.ONE;
160
+ let tweakAcc = Fn.ZERO;
170
161
  // Apply tweaks
171
162
  for (let i = 0; i < tweaks.length; i++) {
172
- const g = isXonly[i] && !aggPublicKey.hasEvenY() ? modN(-1n) : 1n;
173
- const t = bytesToNumberBE(tweaks[i]);
174
- 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]);
175
165
  aggPublicKey = aggPublicKey.multiply(g).add(mulBase(t));
176
166
  if (isZero(aggPublicKey)) throw new Error('The result of tweaking cannot be infinity');
177
- gAcc = modN(g * gAcc);
178
- tweakAcc = modN(t + g * tweakAcc);
167
+ gAcc = Fn.mul(g, gAcc);
168
+ tweakAcc = Fn.add(t, Fn.mul(g, tweakAcc));
179
169
  }
180
170
  return { aggPublicKey, gAcc, tweakAcc };
181
171
  }
@@ -184,7 +174,7 @@ export function keyAggregate(
184
174
  * @param ctx The result of the keyAggregate function.
185
175
  * @returns The aggregate public key as a byte array.
186
176
  */
187
- export function keyAggExport(ctx: ReturnType<typeof keyAggregate>) {
177
+ export function keyAggExport(ctx: ReturnType<typeof keyAggregate>): Uint8Array {
188
178
  return pointToBytes(ctx.aggPublicKey);
189
179
  }
190
180
 
@@ -238,7 +228,8 @@ export function nonceGen(
238
228
  ): Nonces {
239
229
  abytes(publicKey, PUBKEY_LEN);
240
230
  abytesOptional(secretKey, 32);
241
- abytes(aggPublicKey, 0, 32);
231
+ abytes(aggPublicKey);
232
+ if (![0, 32].includes(aggPublicKey.length)) throw new Error('wrong aggPublicKey');
242
233
  abytesOptional(msg);
243
234
  abytes(extraIn);
244
235
  abytes(rand, 32);
@@ -246,8 +237,8 @@ export function nonceGen(
246
237
  if (secretKey !== undefined) rand = aux(secretKey, rand);
247
238
  const msgPrefixed =
248
239
  msg !== undefined
249
- ? concatBytes(new Uint8Array([1]), numberToBytesBE(msg.length, 8), msg)
250
- : new Uint8Array([0]);
240
+ ? concatBytes(Uint8Array.of(1), numberToBytesBE(msg.length, 8), msg)
241
+ : Uint8Array.of(0);
251
242
  const k1 = nonceHash(rand, publicKey, aggPublicKey, 0, msgPrefixed, extraIn);
252
243
  const k2 = nonceHash(rand, publicKey, aggPublicKey, 1, msgPrefixed, extraIn);
253
244
  return {
@@ -343,7 +334,7 @@ export class Session {
343
334
  */
344
335
  private getSessionKeyAggCoeff(P: Point): bigint {
345
336
  const { publicKeys } = this;
346
- const pk = P.toRawBytes(true);
337
+ const pk = P.toBytes(true);
347
338
  const found = publicKeys.some((p) => equalBytes(p, pk));
348
339
  if (!found) throw new Error("The signer's pubkey must be included in the list of pubkeys");
349
340
  return keyAggCoeffInternal(pk, this.secondKey, this.L);
@@ -354,16 +345,16 @@ export class Session {
354
345
  publicKey: Uint8Array
355
346
  ): boolean {
356
347
  const { Q, gAcc, b, R, e } = this;
357
- const s = bytesToNumberBE(partialSig);
358
- if (s >= SECP_N) return false;
348
+ const s = Fn.fromBytes(partialSig, true);
349
+ if (!Fn.isValid(s)) return false;
359
350
  const { R1, R2 } = PubNonce.decode(publicNonce);
360
351
  const Re_s_ = R1.add(R2.multiply(b));
361
- const Re_s = R.hasEvenY() ? Re_s_ : Re_s_.negate();
362
- const P = Point.fromHex(publicKey);
352
+ const Re_s = hasEven(R.y) ? Re_s_ : Re_s_.negate();
353
+ const P = Point.fromBytes(publicKey);
363
354
  const a = this.getSessionKeyAggCoeff(P);
364
- const g = modN(evenScalar(Q, 1n) * gAcc);
355
+ const g = Fn.mul(evenScalar(Q, 1n), gAcc);
365
356
  const left = mulBase(s);
366
- 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))));
367
358
  return left.equals(right);
368
359
  }
369
360
 
@@ -384,20 +375,21 @@ export class Session {
384
375
  // zero-out the first 64 bytes of secretNonce so it cannot be reused
385
376
  // TODO: this was in reference implementation, but feels very broken. Modifying input arguments is pretty bad.
386
377
  secretNonce.fill(0, 0, 64);
387
- aInRange('k1', k1_, 0n, SECP_N);
388
- 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');
389
380
  const k1 = evenScalar(R, k1_);
390
381
  const k2 = evenScalar(R, k2_);
391
- const d_ = bytesToNumberBE(secret);
392
- aInRange('d_', d_, 1n, SECP_N);
382
+ const d_ = Fn.fromBytes(secret);
383
+ if (Fn.is0(d_)) throw new Error('wrong d_');
393
384
  const P = mulBase(d_);
394
- const pk = P.toRawBytes(true);
385
+ const pk = P.toBytes(true);
395
386
  if (!equalBytes(pk, originalPk)) throw new Error('Public key does not match nonceGen argument');
396
387
  const a = this.getSessionKeyAggCoeff(P);
397
388
  const g = evenScalar(Q, 1n);
398
- const d = modN(g * gAcc * d_);
399
- const s = modN(k1 + b * k2 + e * a * d);
400
- 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);
401
393
  // Skip validation in fast-sign mode
402
394
  if (!fastSign) {
403
395
  const publicNonce = PubNonce.encode({
@@ -448,13 +440,13 @@ export class Session {
448
440
  const { Q, tweakAcc, R, e } = this;
449
441
  let s = 0n;
450
442
  for (let i = 0; i < partialSigs.length; i++) {
451
- const si = bytesToNumberBE(partialSigs[i]);
452
- if (si >= SECP_N) throw new InvalidContributionErr(i, 'psig');
453
- 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);
454
446
  }
455
447
  const g = evenScalar(Q, 1n);
456
- s = modN(s + e * g * tweakAcc);
457
- 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));
458
450
  }
459
451
  }
460
452
 
package/src/p2p.ts CHANGED
@@ -18,22 +18,15 @@
18
18
  * @module
19
19
  */
20
20
 
21
- import { FpIsSquare } from '@noble/curves/abstract/modular';
22
- import type { Hex } from '@noble/curves/abstract/utils';
23
- import {
24
- bytesToNumberBE,
25
- concatBytes,
26
- ensureBytes,
27
- numberToBytesBE,
28
- } from '@noble/curves/abstract/utils';
29
- import { type ProjPointType as PointType } from '@noble/curves/abstract/weierstrass';
30
- import { schnorr, secp256k1 } from '@noble/curves/secp256k1';
31
- import { randomBytes } from '@noble/hashes/utils';
32
- import { tagSchnorr } from './utils.ts';
33
-
34
- const Fp = secp256k1.CURVE.Fp;
35
- const Point = secp256k1.ProjectivePoint;
21
+ import { FpIsSquare } from '@noble/curves/abstract/modular.js';
22
+ import { concatBytes, abytes } from '@noble/curves/utils.js';
23
+ import { schnorr, secp256k1 } from '@noble/curves/secp256k1.js';
24
+ import { randomBytes } from '@noble/hashes/utils.js';
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,
@@ -66,6 +66,8 @@ export interface TxOpts {
66
66
  lockTime?: number;
67
67
  PSBTVersion?: number;
68
68
  // Flags
69
+ // Allow non-standard transaction version
70
+ allowUnknownVersion?: boolean;
69
71
  // Allow output scripts to be unknown scripts (probably unspendable)
70
72
  /** @deprecated Use `allowUnknownOutputs` */
71
73
  allowUnknowOutput?: boolean;
@@ -92,24 +94,26 @@ export interface TxOpts {
92
94
  * Internal, exported only for backwards-compat. Use `SigHash` instead.
93
95
  * @deprecated
94
96
  */
95
- export enum SignatureHash {
96
- DEFAULT,
97
- ALL,
98
- NONE,
99
- SINGLE,
100
- ANYONECANPAY = 0x80,
101
- }
97
+ export const SignatureHash = {
98
+ DEFAULT: 0,
99
+ ALL: 1,
100
+ NONE: 2,
101
+ SINGLE: 3,
102
+ ANYONECANPAY: 0x80,
103
+ };
102
104
 
103
- export enum SigHash {
104
- DEFAULT = SignatureHash.DEFAULT,
105
- ALL = SignatureHash.ALL,
106
- NONE = SignatureHash.NONE,
107
- SINGLE = SignatureHash.SINGLE,
108
- DEFAULT_ANYONECANPAY = SignatureHash.DEFAULT | SignatureHash.ANYONECANPAY,
109
- ALL_ANYONECANPAY = SignatureHash.ALL | SignatureHash.ANYONECANPAY,
110
- NONE_ANYONECANPAY = SignatureHash.NONE | SignatureHash.ANYONECANPAY,
111
- SINGLE_ANYONECANPAY = SignatureHash.SINGLE | SignatureHash.ANYONECANPAY,
112
- }
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>;
113
117
 
114
118
  function getTaprootKeys(
115
119
  privKey: Bytes,
@@ -161,7 +165,7 @@ function cleanFinalInput(i: psbt.TransactionInput) {
161
165
  const TxHashIdx = P.struct({ txid: P.bytes(32, true), index: P.U32LE });
162
166
 
163
167
  function validateSigHash(s: SigHash) {
164
- if (typeof s !== 'number' || typeof SigHash[s] !== 'string')
168
+ if (typeof s !== 'number' || typeof SigHashNames[s] !== 'string')
165
169
  throw new Error(`Invalid SigHash=${s}`);
166
170
  return s;
167
171
  }
@@ -190,9 +194,6 @@ function validateOpts(opts: TxOpts): Readonly<TxOpts> {
190
194
  opts.allowUnknownInputs = _opts.allowUnknowInput;
191
195
  if (typeof _opts.allowUnknowOutput !== 'undefined')
192
196
  opts.allowUnknownOutputs = _opts.allowUnknowOutput;
193
- // 0 and -1 happens in tests
194
- if (![-1, 0, 1, 2, 3].includes(_opts.version))
195
- throw new Error(`Unknown version: ${_opts.version}`);
196
197
  if (typeof _opts.lockTime !== 'number') throw new Error('Transaction lock time should be number');
197
198
  P.U32LE.encode(_opts.lockTime); // Additional range checks that lockTime
198
199
  // There is no PSBT v1, and any new version will probably have fields which we don't know how to parse, which
@@ -201,6 +202,7 @@ function validateOpts(opts: TxOpts): Readonly<TxOpts> {
201
202
  throw new Error(`Unknown PSBT version ${_opts.PSBTVersion}`);
202
203
  // Flags
203
204
  for (const k of [
205
+ 'allowUnknownVersion',
204
206
  'allowUnknownOutputs',
205
207
  'allowUnknownInputs',
206
208
  'disableScriptCheck',
@@ -213,6 +215,13 @@ function validateOpts(opts: TxOpts): Readonly<TxOpts> {
213
215
  if (typeof v !== 'boolean')
214
216
  throw new Error(`Transation options wrong type: ${k}=${v} (${typeof v})`);
215
217
  }
218
+ // 0 and -1 happens in tests
219
+ if (
220
+ _opts.allowUnknownVersion
221
+ ? typeof _opts.version === 'number'
222
+ : ![-1, 0, 1, 2, 3].includes(_opts.version)
223
+ )
224
+ throw new Error(`Unknown version: ${_opts.version}`);
216
225
  if (_opts.customScripts !== undefined) {
217
226
  const cs = _opts.customScripts;
218
227
  if (!Array.isArray(cs)) {
@@ -628,11 +637,9 @@ export class Transaction {
628
637
  }
629
638
 
630
639
  get hash(): string {
631
- if (!this.isFinal) throw new Error('Transaction is not finalized');
632
640
  return hex.encode(u.sha256x2(this.toBytes(true)));
633
641
  }
634
642
  get id(): string {
635
- if (!this.isFinal) throw new Error('Transaction is not finalized');
636
643
  return hex.encode(u.sha256x2(this.toBytes(true)).reverse());
637
644
  }
638
645
  // Input stuff