@arkade-os/sdk 0.1.4 → 0.2.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 (114) hide show
  1. package/README.md +156 -174
  2. package/dist/cjs/arknote/index.js +61 -58
  3. package/dist/cjs/bip322/errors.js +13 -0
  4. package/dist/cjs/bip322/index.js +178 -0
  5. package/dist/cjs/forfeit.js +14 -25
  6. package/dist/cjs/identity/singleKey.js +68 -0
  7. package/dist/cjs/index.js +41 -17
  8. package/dist/cjs/providers/ark.js +253 -317
  9. package/dist/cjs/providers/indexer.js +525 -0
  10. package/dist/cjs/providers/onchain.js +193 -15
  11. package/dist/cjs/script/address.js +48 -17
  12. package/dist/cjs/script/base.js +120 -3
  13. package/dist/cjs/script/default.js +18 -4
  14. package/dist/cjs/script/tapscript.js +46 -14
  15. package/dist/cjs/script/vhtlc.js +27 -7
  16. package/dist/cjs/tree/signingSession.js +63 -106
  17. package/dist/cjs/tree/txTree.js +193 -0
  18. package/dist/cjs/tree/validation.js +79 -155
  19. package/dist/cjs/utils/anchor.js +35 -0
  20. package/dist/cjs/utils/arkTransaction.js +108 -0
  21. package/dist/cjs/utils/transactionHistory.js +84 -72
  22. package/dist/cjs/utils/txSizeEstimator.js +12 -0
  23. package/dist/cjs/utils/unknownFields.js +211 -0
  24. package/dist/cjs/wallet/index.js +12 -0
  25. package/dist/cjs/wallet/onchain.js +201 -0
  26. package/dist/cjs/wallet/ramps.js +95 -0
  27. package/dist/cjs/wallet/serviceWorker/db/vtxo/idb.js +32 -0
  28. package/dist/cjs/wallet/serviceWorker/request.js +15 -12
  29. package/dist/cjs/wallet/serviceWorker/response.js +22 -27
  30. package/dist/cjs/wallet/serviceWorker/utils.js +8 -0
  31. package/dist/cjs/wallet/serviceWorker/wallet.js +58 -34
  32. package/dist/cjs/wallet/serviceWorker/worker.js +117 -108
  33. package/dist/cjs/wallet/unroll.js +270 -0
  34. package/dist/cjs/wallet/wallet.js +701 -454
  35. package/dist/esm/arknote/index.js +61 -57
  36. package/dist/esm/bip322/errors.js +9 -0
  37. package/dist/esm/bip322/index.js +174 -0
  38. package/dist/esm/forfeit.js +15 -26
  39. package/dist/esm/identity/singleKey.js +64 -0
  40. package/dist/esm/index.js +30 -12
  41. package/dist/esm/providers/ark.js +252 -317
  42. package/dist/esm/providers/indexer.js +521 -0
  43. package/dist/esm/providers/onchain.js +193 -15
  44. package/dist/esm/script/address.js +48 -17
  45. package/dist/esm/script/base.js +120 -3
  46. package/dist/esm/script/default.js +18 -4
  47. package/dist/esm/script/tapscript.js +46 -14
  48. package/dist/esm/script/vhtlc.js +27 -7
  49. package/dist/esm/tree/signingSession.js +65 -108
  50. package/dist/esm/tree/txTree.js +189 -0
  51. package/dist/esm/tree/validation.js +75 -152
  52. package/dist/esm/utils/anchor.js +31 -0
  53. package/dist/esm/utils/arkTransaction.js +105 -0
  54. package/dist/esm/utils/transactionHistory.js +84 -72
  55. package/dist/esm/utils/txSizeEstimator.js +12 -0
  56. package/dist/esm/utils/unknownFields.js +173 -0
  57. package/dist/esm/wallet/index.js +9 -0
  58. package/dist/esm/wallet/onchain.js +196 -0
  59. package/dist/esm/wallet/ramps.js +91 -0
  60. package/dist/esm/wallet/serviceWorker/db/vtxo/idb.js +32 -0
  61. package/dist/esm/wallet/serviceWorker/request.js +15 -12
  62. package/dist/esm/wallet/serviceWorker/response.js +22 -27
  63. package/dist/esm/wallet/serviceWorker/utils.js +8 -0
  64. package/dist/esm/wallet/serviceWorker/wallet.js +59 -35
  65. package/dist/esm/wallet/serviceWorker/worker.js +117 -108
  66. package/dist/esm/wallet/unroll.js +267 -0
  67. package/dist/esm/wallet/wallet.js +674 -461
  68. package/dist/types/arknote/index.d.ts +40 -13
  69. package/dist/types/bip322/errors.d.ts +6 -0
  70. package/dist/types/bip322/index.d.ts +57 -0
  71. package/dist/types/forfeit.d.ts +2 -14
  72. package/dist/types/identity/singleKey.d.ts +27 -0
  73. package/dist/types/index.d.ts +23 -12
  74. package/dist/types/providers/ark.d.ts +114 -95
  75. package/dist/types/providers/indexer.d.ts +186 -0
  76. package/dist/types/providers/onchain.d.ts +41 -11
  77. package/dist/types/script/address.d.ts +26 -2
  78. package/dist/types/script/base.d.ts +13 -3
  79. package/dist/types/script/default.d.ts +22 -0
  80. package/dist/types/script/tapscript.d.ts +61 -5
  81. package/dist/types/script/vhtlc.d.ts +27 -0
  82. package/dist/types/tree/signingSession.d.ts +5 -5
  83. package/dist/types/tree/txTree.d.ts +28 -0
  84. package/dist/types/tree/validation.d.ts +15 -22
  85. package/dist/types/utils/anchor.d.ts +19 -0
  86. package/dist/types/utils/arkTransaction.d.ts +27 -0
  87. package/dist/types/utils/transactionHistory.d.ts +7 -1
  88. package/dist/types/utils/txSizeEstimator.d.ts +3 -0
  89. package/dist/types/utils/unknownFields.d.ts +83 -0
  90. package/dist/types/wallet/index.d.ts +51 -50
  91. package/dist/types/wallet/onchain.d.ts +49 -0
  92. package/dist/types/wallet/ramps.d.ts +32 -0
  93. package/dist/types/wallet/serviceWorker/db/vtxo/idb.d.ts +2 -0
  94. package/dist/types/wallet/serviceWorker/db/vtxo/index.d.ts +2 -0
  95. package/dist/types/wallet/serviceWorker/request.d.ts +14 -16
  96. package/dist/types/wallet/serviceWorker/response.d.ts +17 -19
  97. package/dist/types/wallet/serviceWorker/utils.d.ts +8 -0
  98. package/dist/types/wallet/serviceWorker/wallet.d.ts +36 -8
  99. package/dist/types/wallet/serviceWorker/worker.d.ts +7 -3
  100. package/dist/types/wallet/unroll.d.ts +102 -0
  101. package/dist/types/wallet/wallet.d.ts +71 -25
  102. package/package.json +14 -15
  103. package/dist/cjs/identity/inMemoryKey.js +0 -40
  104. package/dist/cjs/tree/vtxoTree.js +0 -231
  105. package/dist/cjs/utils/coinselect.js +0 -73
  106. package/dist/cjs/utils/psbt.js +0 -137
  107. package/dist/esm/identity/inMemoryKey.js +0 -36
  108. package/dist/esm/tree/vtxoTree.js +0 -191
  109. package/dist/esm/utils/coinselect.js +0 -69
  110. package/dist/esm/utils/psbt.js +0 -131
  111. package/dist/types/identity/inMemoryKey.d.ts +0 -12
  112. package/dist/types/tree/vtxoTree.d.ts +0 -33
  113. package/dist/types/utils/coinselect.d.ts +0 -21
  114. package/dist/types/utils/psbt.d.ts +0 -11
@@ -47,6 +47,16 @@ var TapscriptType;
47
47
  TapscriptType["ConditionMultisig"] = "condition-multisig";
48
48
  TapscriptType["CLTVMultisig"] = "cltv-multisig";
49
49
  })(TapscriptType || (exports.TapscriptType = TapscriptType = {}));
50
+ /**
51
+ * decodeTapscript is a function that decodes an ark tapsript from a raw script.
52
+ *
53
+ * @throws {Error} if the script is not a valid ark tapscript
54
+ * @example
55
+ * ```typescript
56
+ * const arkTapscript = decodeTapscript(new Uint8Array(32));
57
+ * console.log("type:", arkTapscript.type);
58
+ * ```
59
+ */
50
60
  function decodeTapscript(script) {
51
61
  const types = [
52
62
  MultisigTapscript,
@@ -66,8 +76,14 @@ function decodeTapscript(script) {
66
76
  throw new Error(`Failed to decode: script ${base_1.hex.encode(script)} is not a valid tapscript`);
67
77
  }
68
78
  /**
69
- * Implements a multi-signature script that requires a threshold of signatures
70
- * from the specified pubkeys.
79
+ * Implements a multi-signature tapscript.
80
+ *
81
+ * <pubkey> CHECKSIGVERIFY <pubkey> CHECKSIG
82
+ *
83
+ * @example
84
+ * ```typescript
85
+ * const multisigTapscript = MultisigTapscript.encode({ pubkeys: [new Uint8Array(32), new Uint8Array(32)] });
86
+ * ```
71
87
  */
72
88
  var MultisigTapscript;
73
89
  (function (MultisigTapscript) {
@@ -93,7 +109,6 @@ var MultisigTapscript;
93
109
  type: TapscriptType.Multisig,
94
110
  params,
95
111
  script: (0, payment_1.p2tr_ms)(params.pubkeys.length, params.pubkeys).script,
96
- witnessSize: () => params.pubkeys.length * 64,
97
112
  };
98
113
  }
99
114
  const asm = [];
@@ -111,7 +126,6 @@ var MultisigTapscript;
111
126
  type: TapscriptType.Multisig,
112
127
  params,
113
128
  script: script_1.Script.encode(asm),
114
- witnessSize: () => params.pubkeys.length * 64,
115
129
  };
116
130
  }
117
131
  MultisigTapscript.encode = encode;
@@ -182,7 +196,6 @@ var MultisigTapscript;
182
196
  type: TapscriptType.Multisig,
183
197
  params: { pubkeys, type: MultisigType.CHECKSIGADD },
184
198
  script,
185
- witnessSize: () => pubkeys.length * 64,
186
199
  };
187
200
  }
188
201
  // <pubkey> CHECKSIGVERIFY <pubkey> CHECKSIG
@@ -226,7 +239,6 @@ var MultisigTapscript;
226
239
  type: TapscriptType.Multisig,
227
240
  params: { pubkeys, type: MultisigType.CHECKSIG },
228
241
  script,
229
- witnessSize: () => pubkeys.length * 64,
230
242
  };
231
243
  }
232
244
  function is(tapscript) {
@@ -239,6 +251,13 @@ var MultisigTapscript;
239
251
  * after the relative timelock has expired. The timelock can be specified in blocks or seconds.
240
252
  *
241
253
  * This is the standard exit closure and it is also used for the sweep closure in vtxo trees.
254
+ *
255
+ * <sequence> CHECKSEQUENCEVERIFY DROP <pubkey> CHECKSIG
256
+ *
257
+ * @example
258
+ * ```typescript
259
+ * const csvMultisigTapscript = CSVMultisigTapscript.encode({ timelock: { type: "blocks", value: 144 }, pubkeys: [new Uint8Array(32), new Uint8Array(32)] });
260
+ * ```
242
261
  */
243
262
  var CSVMultisigTapscript;
244
263
  (function (CSVMultisigTapscript) {
@@ -261,7 +280,6 @@ var CSVMultisigTapscript;
261
280
  type: TapscriptType.CSVMultisig,
262
281
  params,
263
282
  script,
264
- witnessSize: () => params.pubkeys.length * 64,
265
283
  };
266
284
  }
267
285
  CSVMultisigTapscript.encode = encode;
@@ -307,7 +325,6 @@ var CSVMultisigTapscript;
307
325
  ...multisig.params,
308
326
  },
309
327
  script,
310
- witnessSize: () => multisig.params.pubkeys.length * 64,
311
328
  };
312
329
  }
313
330
  CSVMultisigTapscript.decode = decode;
@@ -320,6 +337,13 @@ var CSVMultisigTapscript;
320
337
  * Combines a condition script with an exit closure. The resulting script requires
321
338
  * the condition to be met, followed by the standard exit closure requirements
322
339
  * (timelock and signatures).
340
+ *
341
+ * <conditionScript> VERIFY <sequence> CHECKSEQUENCEVERIFY DROP <pubkey> CHECKSIGVERIFY <pubkey> CHECKSIG
342
+ *
343
+ * @example
344
+ * ```typescript
345
+ * const conditionCSVMultisigTapscript = ConditionCSVMultisigTapscript.encode({ conditionScript: new Uint8Array(32), pubkeys: [new Uint8Array(32), new Uint8Array(32)] });
346
+ * ```
323
347
  */
324
348
  var ConditionCSVMultisigTapscript;
325
349
  (function (ConditionCSVMultisigTapscript) {
@@ -333,7 +357,6 @@ var ConditionCSVMultisigTapscript;
333
357
  type: TapscriptType.ConditionCSVMultisig,
334
358
  params,
335
359
  script,
336
- witnessSize: (conditionSize) => conditionSize + params.pubkeys.length * 64,
337
360
  };
338
361
  }
339
362
  ConditionCSVMultisigTapscript.encode = encode;
@@ -377,7 +400,6 @@ var ConditionCSVMultisigTapscript;
377
400
  ...csvMultisig.params,
378
401
  },
379
402
  script,
380
- witnessSize: (conditionSize) => conditionSize + csvMultisig.params.pubkeys.length * 64,
381
403
  };
382
404
  }
383
405
  ConditionCSVMultisigTapscript.decode = decode;
@@ -390,6 +412,13 @@ var ConditionCSVMultisigTapscript;
390
412
  * Combines a condition script with a forfeit closure. The resulting script requires
391
413
  * the condition to be met, followed by the standard forfeit closure requirements
392
414
  * (multi-signature).
415
+ *
416
+ * <conditionScript> VERIFY <pubkey> CHECKSIGVERIFY <pubkey> CHECKSIG
417
+ *
418
+ * @example
419
+ * ```typescript
420
+ * const conditionMultisigTapscript = ConditionMultisigTapscript.encode({ conditionScript: new Uint8Array(32), pubkeys: [new Uint8Array(32), new Uint8Array(32)] });
421
+ * ```
393
422
  */
394
423
  var ConditionMultisigTapscript;
395
424
  (function (ConditionMultisigTapscript) {
@@ -403,7 +432,6 @@ var ConditionMultisigTapscript;
403
432
  type: TapscriptType.ConditionMultisig,
404
433
  params,
405
434
  script,
406
- witnessSize: (conditionSize) => conditionSize + params.pubkeys.length * 64,
407
435
  };
408
436
  }
409
437
  ConditionMultisigTapscript.encode = encode;
@@ -447,7 +475,6 @@ var ConditionMultisigTapscript;
447
475
  ...multisig.params,
448
476
  },
449
477
  script,
450
- witnessSize: (conditionSize) => conditionSize + multisig.params.pubkeys.length * 64,
451
478
  };
452
479
  }
453
480
  ConditionMultisigTapscript.decode = decode;
@@ -460,6 +487,13 @@ var ConditionMultisigTapscript;
460
487
  * Implements an absolute timelock (CLTV) script combined with a forfeit closure.
461
488
  * The script requires waiting until a specific block height/timestamp before the
462
489
  * forfeit closure conditions can be met.
490
+ *
491
+ * <locktime> CHECKLOCKTIMEVERIFY DROP <pubkey> CHECKSIGVERIFY <pubkey> CHECKSIG
492
+ *
493
+ * @example
494
+ * ```typescript
495
+ * const cltvMultisigTapscript = CLTVMultisigTapscript.encode({ absoluteTimelock: 144, pubkeys: [new Uint8Array(32), new Uint8Array(32)] });
496
+ * ```
463
497
  */
464
498
  var CLTVMultisigTapscript;
465
499
  (function (CLTVMultisigTapscript) {
@@ -475,7 +509,6 @@ var CLTVMultisigTapscript;
475
509
  type: TapscriptType.CLTVMultisig,
476
510
  params,
477
511
  script,
478
- witnessSize: () => params.pubkeys.length * 64,
479
512
  };
480
513
  }
481
514
  CLTVMultisigTapscript.encode = encode;
@@ -517,7 +550,6 @@ var CLTVMultisigTapscript;
517
550
  ...multisig.params,
518
551
  },
519
552
  script,
520
- witnessSize: () => multisig.params.pubkeys.length * 64,
521
553
  };
522
554
  }
523
555
  CLTVMultisigTapscript.decode = decode;
@@ -5,13 +5,33 @@ const btc_signer_1 = require("@scure/btc-signer");
5
5
  const tapscript_1 = require("./tapscript");
6
6
  const base_1 = require("@scure/base");
7
7
  const base_2 = require("./base");
8
- // VHTLC is an Hashed Timelock Contract VtxoScript implementation
9
- // - claim (preimage + receiver)
10
- // - refund (sender + receiver + server)
11
- // - refundWithoutReceiver (at refundLocktime, sender + receiver + server)
12
- // - unilateralClaim (preimage + receiver after unilateralClaimDelay)
13
- // - unilateralRefund (sender + receiver after unilateralRefundDelay)
14
- // - unilateralRefundWithoutReceiver (sender after unilateralRefundWithoutReceiverDelay)
8
+ /**
9
+ * Virtual Hash Time Lock Contract (VHTLC) implementation.
10
+ *
11
+ * VHTLC is a contract that enables atomic swaps and conditional payments
12
+ * in the Ark protocol. It provides multiple spending paths:
13
+ *
14
+ * - **claim**: Receiver can claim funds by revealing the preimage
15
+ * - **refund**: Sender and receiver can collaboratively refund
16
+ * - **refundWithoutReceiver**: Sender can refund after locktime expires
17
+ * - **unilateralClaim**: Receiver can claim unilaterally after delay
18
+ * - **unilateralRefund**: Sender and receiver can refund unilaterally after delay
19
+ * - **unilateralRefundWithoutReceiver**: Sender can refund unilaterally after delay
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * const vhtlc = new VHTLC.Script({
24
+ * sender: alicePubKey,
25
+ * receiver: bobPubKey,
26
+ * server: serverPubKey,
27
+ * preimageHash: hash160(secret),
28
+ * refundLocktime: BigInt(chainTip + 10),
29
+ * unilateralClaimDelay: { type: 'blocks', value: 100n },
30
+ * unilateralRefundDelay: { type: 'blocks', value: 102n },
31
+ * unilateralRefundWithoutReceiverDelay: { type: 'blocks', value: 103n }
32
+ * });
33
+ * ```
34
+ */
15
35
  var VHTLC;
16
36
  (function (VHTLC) {
17
37
  class Script extends base_2.VtxoScript {
@@ -33,22 +33,22 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.TreeSignerSession = exports.ErrMissingAggregateKey = exports.ErrMissingVtxoTree = void 0;
36
+ exports.TreeSignerSession = exports.ErrMissingAggregateKey = exports.ErrMissingVtxoGraph = void 0;
37
37
  exports.validateTreeSigs = validateTreeSigs;
38
38
  const musig2 = __importStar(require("../musig2"));
39
- const vtxoTree_1 = require("./vtxoTree");
40
39
  const btc_signer_1 = require("@scure/btc-signer");
41
40
  const base_1 = require("@scure/base");
42
41
  const secp256k1_1 = require("@noble/curves/secp256k1");
43
42
  const utils_1 = require("@scure/btc-signer/utils");
44
- exports.ErrMissingVtxoTree = new Error("missing vtxo tree");
43
+ const unknownFields_1 = require("../utils/unknownFields");
44
+ exports.ErrMissingVtxoGraph = new Error("missing vtxo graph");
45
45
  exports.ErrMissingAggregateKey = new Error("missing aggregate key");
46
46
  class TreeSignerSession {
47
47
  constructor(secretKey) {
48
48
  this.secretKey = secretKey;
49
49
  this.myNonces = null;
50
50
  this.aggregateNonces = null;
51
- this.tree = null;
51
+ this.graph = null;
52
52
  this.scriptRoot = null;
53
53
  this.rootSharedOutputAmount = null;
54
54
  }
@@ -57,7 +57,7 @@ class TreeSignerSession {
57
57
  return new TreeSignerSession(secretKey);
58
58
  }
59
59
  init(tree, scriptRoot, rootInputAmount) {
60
- this.tree = tree;
60
+ this.graph = tree;
61
61
  this.scriptRoot = scriptRoot;
62
62
  this.rootSharedOutputAmount = rootInputAmount;
63
63
  }
@@ -65,24 +65,16 @@ class TreeSignerSession {
65
65
  return secp256k1_1.secp256k1.getPublicKey(this.secretKey);
66
66
  }
67
67
  getNonces() {
68
- if (!this.tree)
69
- throw exports.ErrMissingVtxoTree;
68
+ if (!this.graph)
69
+ throw exports.ErrMissingVtxoGraph;
70
70
  if (!this.myNonces) {
71
71
  this.myNonces = this.generateNonces();
72
72
  }
73
- const nonces = [];
74
- for (const levelNonces of this.myNonces) {
75
- const levelPubNonces = [];
76
- for (const nonce of levelNonces) {
77
- if (!nonce) {
78
- levelPubNonces.push(null);
79
- continue;
80
- }
81
- levelPubNonces.push({ pubNonce: nonce.pubNonce });
82
- }
83
- nonces.push(levelPubNonces);
73
+ const publicNonces = new Map();
74
+ for (const [txid, nonces] of this.myNonces) {
75
+ publicNonces.set(txid, { pubNonce: nonces.pubNonce });
84
76
  }
85
- return nonces;
77
+ return publicNonces;
86
78
  }
87
79
  setAggregatedNonces(nonces) {
88
80
  if (this.aggregateNonces)
@@ -90,71 +82,55 @@ class TreeSignerSession {
90
82
  this.aggregateNonces = nonces;
91
83
  }
92
84
  sign() {
93
- if (!this.tree)
94
- throw exports.ErrMissingVtxoTree;
85
+ if (!this.graph)
86
+ throw exports.ErrMissingVtxoGraph;
95
87
  if (!this.aggregateNonces)
96
88
  throw new Error("nonces not set");
97
89
  if (!this.myNonces)
98
90
  throw new Error("nonces not generated");
99
- const sigs = [];
100
- for (let levelIndex = 0; levelIndex < this.tree.levels.length; levelIndex++) {
101
- const levelSigs = [];
102
- const level = this.tree.levels[levelIndex];
103
- for (let nodeIndex = 0; nodeIndex < level.length; nodeIndex++) {
104
- const node = level[nodeIndex];
105
- const tx = btc_signer_1.Transaction.fromPSBT(base_1.base64.decode(node.tx));
106
- const sig = this.signPartial(tx, levelIndex, nodeIndex);
107
- if (sig) {
108
- levelSigs.push(sig);
109
- }
110
- else {
111
- levelSigs.push(null);
112
- }
113
- }
114
- sigs.push(levelSigs);
91
+ const sigs = new Map();
92
+ for (const g of this.graph) {
93
+ const sig = this.signPartial(g);
94
+ sigs.set(g.txid, sig);
115
95
  }
116
96
  return sigs;
117
97
  }
118
98
  generateNonces() {
119
- if (!this.tree)
120
- throw exports.ErrMissingVtxoTree;
121
- const myNonces = [];
99
+ if (!this.graph)
100
+ throw exports.ErrMissingVtxoGraph;
101
+ const myNonces = new Map();
122
102
  const publicKey = secp256k1_1.secp256k1.getPublicKey(this.secretKey);
123
- for (const level of this.tree.levels) {
124
- const levelNonces = [];
125
- for (let i = 0; i < level.length; i++) {
126
- const nonces = musig2.generateNonces(publicKey);
127
- levelNonces.push(nonces);
128
- }
129
- myNonces.push(levelNonces);
103
+ for (const g of this.graph) {
104
+ const nonces = musig2.generateNonces(publicKey);
105
+ myNonces.set(g.txid, nonces);
130
106
  }
131
107
  return myNonces;
132
108
  }
133
- signPartial(tx, levelIndex, nodeIndex) {
134
- if (!this.tree || !this.scriptRoot || !this.rootSharedOutputAmount) {
109
+ signPartial(g) {
110
+ if (!this.graph || !this.scriptRoot || !this.rootSharedOutputAmount) {
135
111
  throw TreeSignerSession.NOT_INITIALIZED;
136
112
  }
137
113
  if (!this.myNonces || !this.aggregateNonces) {
138
114
  throw new Error("session not properly initialized");
139
115
  }
140
- const myNonce = this.myNonces[levelIndex][nodeIndex];
116
+ const myNonce = this.myNonces.get(g.txid);
141
117
  if (!myNonce)
142
- return null;
143
- const aggNonce = this.aggregateNonces[levelIndex][nodeIndex];
118
+ throw new Error("missing private nonce");
119
+ const aggNonce = this.aggregateNonces.get(g.txid);
144
120
  if (!aggNonce)
145
121
  throw new Error("missing aggregate nonce");
146
122
  const prevoutAmounts = [];
147
123
  const prevoutScripts = [];
148
- const cosigners = (0, vtxoTree_1.getCosignerKeys)(tx);
124
+ const cosigners = (0, unknownFields_1.getArkPsbtFields)(g.root, 0, unknownFields_1.CosignerPublicKey).map((c) => c.key);
149
125
  const { finalKey } = musig2.aggregateKeys(cosigners, true, {
150
126
  taprootTweak: this.scriptRoot,
151
127
  });
152
- for (let inputIndex = 0; inputIndex < tx.inputsLength; inputIndex++) {
153
- const prevout = getPrevOutput(finalKey, this.tree, this.rootSharedOutputAmount, tx);
128
+ for (let inputIndex = 0; inputIndex < g.root.inputsLength; inputIndex++) {
129
+ const prevout = getPrevOutput(finalKey, this.graph, this.rootSharedOutputAmount, g.root);
154
130
  prevoutAmounts.push(prevout.amount);
155
131
  prevoutScripts.push(prevout.script);
156
132
  }
157
- const message = tx.preimageWitnessV1(0, // always first input
133
+ const message = g.root.preimageWitnessV1(0, // always first input
158
134
  prevoutScripts, btc_signer_1.SigHash.DEFAULT, prevoutAmounts);
159
135
  return musig2.sign(myNonce.secNonce, this.secretKey, aggNonce.pubNonce, cosigners, message, {
160
136
  taprootTweak: this.scriptRoot,
@@ -167,66 +143,47 @@ TreeSignerSession.NOT_INITIALIZED = new Error("session not initialized, call ini
167
143
  // Helper function to validate tree signatures
168
144
  async function validateTreeSigs(finalAggregatedKey, sharedOutputAmount, vtxoTree) {
169
145
  // Iterate through each level of the tree
170
- for (const level of vtxoTree.levels) {
171
- for (const node of level) {
172
- // Parse the transaction
173
- const tx = btc_signer_1.Transaction.fromPSBT(base_1.base64.decode(node.tx));
174
- const input = tx.getInput(0);
175
- // Check if input has signature
176
- if (!input.tapKeySig) {
177
- throw new Error("unsigned tree input");
178
- }
179
- // Get the previous output information
180
- const prevout = getPrevOutput(finalAggregatedKey, vtxoTree, sharedOutputAmount, tx);
181
- // Calculate the message that was signed
182
- const message = tx.preimageWitnessV1(0, // always first input
183
- [prevout.script], btc_signer_1.SigHash.DEFAULT, [prevout.amount]);
184
- // Verify the signature
185
- const isValid = secp256k1_1.schnorr.verify(input.tapKeySig, message, finalAggregatedKey);
186
- if (!isValid) {
187
- throw new Error("invalid signature");
188
- }
146
+ for (const g of vtxoTree) {
147
+ // Parse the transaction
148
+ const input = g.root.getInput(0);
149
+ // Check if input has signature
150
+ if (!input.tapKeySig) {
151
+ throw new Error("unsigned tree input");
152
+ }
153
+ // Get the previous output information
154
+ const prevout = getPrevOutput(finalAggregatedKey, vtxoTree, sharedOutputAmount, g.root);
155
+ // Calculate the message that was signed
156
+ const message = g.root.preimageWitnessV1(0, // always first input
157
+ [prevout.script], btc_signer_1.SigHash.DEFAULT, [prevout.amount]);
158
+ // Verify the signature
159
+ const isValid = secp256k1_1.schnorr.verify(input.tapKeySig, message, finalAggregatedKey);
160
+ if (!isValid) {
161
+ throw new Error("invalid signature");
189
162
  }
190
163
  }
191
164
  }
192
- function getPrevOutput(finalKey, vtxoTree, sharedOutputAmount, partial) {
193
- // Generate P2TR script
165
+ function getPrevOutput(finalKey, graph, sharedOutputAmount, tx) {
166
+ // generate P2TR script from musig2 final key
194
167
  const pkScript = btc_signer_1.Script.encode(["OP_1", finalKey.slice(1)]);
195
- // Get root node
196
- const rootNode = vtxoTree.levels[0][0];
197
- if (!rootNode)
198
- throw new Error("empty vtxo tree");
199
- const input = partial.getInput(0);
200
- if (!input.txid)
201
- throw new Error("missing input txid");
202
- const parentTxID = base_1.hex.encode(input.txid);
203
- // Check if parent is root
204
- if (rootNode.parentTxid === parentTxID) {
168
+ const txid = base_1.hex.encode((0, utils_1.sha256x2)(tx.toBytes(true)).reverse());
169
+ // if the input is the root input, return the shared output amount
170
+ if (txid === graph.txid) {
205
171
  return {
206
172
  amount: sharedOutputAmount,
207
173
  script: pkScript,
208
174
  };
209
175
  }
210
- // Search for parent in tree
211
- let parent = null;
212
- for (const level of vtxoTree.levels) {
213
- for (const node of level) {
214
- if (node.txid === parentTxID) {
215
- parent = node;
216
- break;
217
- }
218
- }
219
- if (parent)
220
- break;
221
- }
222
- if (!parent) {
223
- throw new Error("parent tx not found");
224
- }
225
- // Parse parent tx
226
- const parentTx = btc_signer_1.Transaction.fromPSBT(base_1.base64.decode(parent.tx));
227
- if (!input.index)
176
+ // find the parent transaction
177
+ const parentInput = tx.getInput(0);
178
+ if (!parentInput.txid)
179
+ throw new Error("missing parent input txid");
180
+ const parentTxid = base_1.hex.encode(new Uint8Array(parentInput.txid));
181
+ const parent = graph.find(parentTxid);
182
+ if (!parent)
183
+ throw new Error("parent tx not found");
184
+ if (parentInput.index === undefined)
228
185
  throw new Error("missing input index");
229
- const parentOutput = parentTx.getOutput(input.index);
186
+ const parentOutput = parent.root.getOutput(parentInput.index);
230
187
  if (!parentOutput)
231
188
  throw new Error("parent output not found");
232
189
  if (!parentOutput.amount)
@@ -0,0 +1,193 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TxTree = void 0;
4
+ const btc_signer_1 = require("@scure/btc-signer");
5
+ const base_1 = require("@scure/base");
6
+ const base_2 = require("@scure/base");
7
+ const utils_1 = require("@scure/btc-signer/utils");
8
+ /**
9
+ * TxTree is a graph of bitcoin transactions.
10
+ * It is used to represent batch tree created during settlement session
11
+ */
12
+ class TxTree {
13
+ constructor(root, children = new Map()) {
14
+ this.root = root;
15
+ this.children = children;
16
+ }
17
+ static create(chunks) {
18
+ if (chunks.length === 0) {
19
+ throw new Error("empty chunks");
20
+ }
21
+ // Create a map to store all chunks by their txid for easy lookup
22
+ const chunksByTxid = new Map();
23
+ for (const chunk of chunks) {
24
+ const decodedChunk = decodeNode(chunk);
25
+ const txid = base_2.hex.encode((0, utils_1.sha256x2)(decodedChunk.tx.toBytes(true)).reverse());
26
+ chunksByTxid.set(txid, decodedChunk);
27
+ }
28
+ // Find the root chunks (the ones that aren't referenced as a child)
29
+ const rootTxids = [];
30
+ for (const [txid] of chunksByTxid) {
31
+ let isChild = false;
32
+ for (const [otherTxid, otherChunk] of chunksByTxid) {
33
+ if (otherTxid === txid) {
34
+ // skip self
35
+ continue;
36
+ }
37
+ // check if the current chunk is a child of the other chunk
38
+ isChild = hasChild(otherChunk, txid);
39
+ if (isChild) {
40
+ break;
41
+ }
42
+ }
43
+ // if the chunk is not a child of any other chunk, it is a root
44
+ if (!isChild) {
45
+ rootTxids.push(txid);
46
+ continue;
47
+ }
48
+ }
49
+ if (rootTxids.length === 0) {
50
+ throw new Error("no root chunk found");
51
+ }
52
+ if (rootTxids.length > 1) {
53
+ throw new Error(`multiple root chunks found: ${rootTxids.join(", ")}`);
54
+ }
55
+ const graph = buildGraph(rootTxids[0], chunksByTxid);
56
+ if (!graph) {
57
+ throw new Error(`chunk not found for root txid: ${rootTxids[0]}`);
58
+ }
59
+ // verify that the number of chunks is equal to the number node in the graph
60
+ if (graph.nbOfNodes() !== chunks.length) {
61
+ throw new Error(`number of chunks (${chunks.length}) is not equal to the number of nodes in the graph (${graph.nbOfNodes()})`);
62
+ }
63
+ return graph;
64
+ }
65
+ nbOfNodes() {
66
+ let count = 1; // count this node
67
+ for (const child of this.children.values()) {
68
+ count += child.nbOfNodes();
69
+ }
70
+ return count;
71
+ }
72
+ validate() {
73
+ if (!this.root) {
74
+ throw new Error("unexpected nil root");
75
+ }
76
+ const nbOfOutputs = this.root.outputsLength;
77
+ const nbOfInputs = this.root.inputsLength;
78
+ if (nbOfInputs !== 1) {
79
+ throw new Error(`unexpected number of inputs: ${nbOfInputs}, expected 1`);
80
+ }
81
+ // the children map can't be bigger than the number of outputs (excluding the P2A)
82
+ // a graph can be "partial" and specify only some of the outputs as children,
83
+ // that's why we allow len(g.Children) to be less than nbOfOutputs-1
84
+ if (this.children.size > nbOfOutputs - 1) {
85
+ throw new Error(`unexpected number of children: ${this.children.size}, expected maximum ${nbOfOutputs - 1}`);
86
+ }
87
+ // validate each child
88
+ for (const [outputIndex, child] of this.children) {
89
+ if (outputIndex >= nbOfOutputs) {
90
+ throw new Error(`output index ${outputIndex} is out of bounds (nb of outputs: ${nbOfOutputs})`);
91
+ }
92
+ child.validate();
93
+ const childInput = child.root.getInput(0);
94
+ const parentTxid = base_2.hex.encode((0, utils_1.sha256x2)(this.root.toBytes(true)).reverse());
95
+ // verify the input of the child is the output of the parent
96
+ if (!childInput.txid ||
97
+ base_2.hex.encode(childInput.txid) !== parentTxid ||
98
+ childInput.index !== outputIndex) {
99
+ throw new Error(`input of child ${outputIndex} is not the output of the parent`);
100
+ }
101
+ // verify the sum of the child's outputs is equal to the output of the parent
102
+ let childOutputsSum = 0n;
103
+ for (let i = 0; i < child.root.outputsLength; i++) {
104
+ const output = child.root.getOutput(i);
105
+ if (output?.amount) {
106
+ childOutputsSum += output.amount;
107
+ }
108
+ }
109
+ const parentOutput = this.root.getOutput(outputIndex);
110
+ if (!parentOutput?.amount) {
111
+ throw new Error(`parent output ${outputIndex} has no amount`);
112
+ }
113
+ if (childOutputsSum !== parentOutput.amount) {
114
+ throw new Error(`sum of child's outputs is not equal to the output of the parent: ${childOutputsSum} != ${parentOutput.amount}`);
115
+ }
116
+ }
117
+ }
118
+ leaves() {
119
+ if (this.children.size === 0) {
120
+ return [this.root];
121
+ }
122
+ const leaves = [];
123
+ for (const child of this.children.values()) {
124
+ leaves.push(...child.leaves());
125
+ }
126
+ return leaves;
127
+ }
128
+ get txid() {
129
+ return base_2.hex.encode((0, utils_1.sha256x2)(this.root.toBytes(true)).reverse());
130
+ }
131
+ find(txid) {
132
+ if (txid === this.txid) {
133
+ return this;
134
+ }
135
+ for (const child of this.children.values()) {
136
+ const found = child.find(txid);
137
+ if (found) {
138
+ return found;
139
+ }
140
+ }
141
+ return null;
142
+ }
143
+ update(txid, fn) {
144
+ if (txid === this.txid) {
145
+ fn(this.root);
146
+ return;
147
+ }
148
+ for (const child of this.children.values()) {
149
+ try {
150
+ child.update(txid, fn);
151
+ return;
152
+ }
153
+ catch (error) {
154
+ // Continue searching in other children if not found
155
+ continue;
156
+ }
157
+ }
158
+ throw new Error(`tx not found: ${txid}`);
159
+ }
160
+ *[Symbol.iterator]() {
161
+ yield this;
162
+ for (const child of this.children.values()) {
163
+ yield* child;
164
+ }
165
+ }
166
+ }
167
+ exports.TxTree = TxTree;
168
+ // Helper function to check if a chunk has a specific child
169
+ function hasChild(chunk, childTxid) {
170
+ return Object.values(chunk.children).includes(childTxid);
171
+ }
172
+ // buildGraph recursively builds the TxGraph starting from the given txid
173
+ function buildGraph(rootTxid, chunksByTxid) {
174
+ const chunk = chunksByTxid.get(rootTxid);
175
+ if (!chunk) {
176
+ return null;
177
+ }
178
+ const rootTx = chunk.tx;
179
+ const children = new Map();
180
+ // Recursively build children graphs
181
+ for (const [outputIndexStr, childTxid] of Object.entries(chunk.children)) {
182
+ const outputIndex = parseInt(outputIndexStr);
183
+ const childGraph = buildGraph(childTxid, chunksByTxid);
184
+ if (childGraph) {
185
+ children.set(outputIndex, childGraph);
186
+ }
187
+ }
188
+ return new TxTree(rootTx, children);
189
+ }
190
+ function decodeNode(chunk) {
191
+ const tx = btc_signer_1.Transaction.fromPSBT(base_1.base64.decode(chunk.tx));
192
+ return { tx, children: chunk.children };
193
+ }