@arkade-os/sdk 0.1.4 → 0.2.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 (114) hide show
  1. package/README.md +157 -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 +43 -17
  8. package/dist/cjs/providers/ark.js +261 -321
  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 +61 -20
  15. package/dist/cjs/script/vhtlc.js +85 -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 +61 -34
  32. package/dist/cjs/wallet/serviceWorker/worker.js +120 -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 +31 -12
  41. package/dist/esm/providers/ark.js +259 -320
  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 +61 -20
  48. package/dist/esm/script/vhtlc.js +85 -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 +62 -35
  65. package/dist/esm/wallet/serviceWorker/worker.js +120 -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 +24 -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 +37 -35
  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
@@ -2,6 +2,7 @@ import * as bip68 from "bip68";
2
2
  import { Script, ScriptNum } from "@scure/btc-signer/script";
3
3
  import { p2tr_ms } from "@scure/btc-signer/payment";
4
4
  import { hex } from "@scure/base";
5
+ const MinimalScriptNum = ScriptNum(undefined, true);
5
6
  export var TapscriptType;
6
7
  (function (TapscriptType) {
7
8
  TapscriptType["Multisig"] = "multisig";
@@ -10,6 +11,16 @@ export var TapscriptType;
10
11
  TapscriptType["ConditionMultisig"] = "condition-multisig";
11
12
  TapscriptType["CLTVMultisig"] = "cltv-multisig";
12
13
  })(TapscriptType || (TapscriptType = {}));
14
+ /**
15
+ * decodeTapscript is a function that decodes an ark tapsript from a raw script.
16
+ *
17
+ * @throws {Error} if the script is not a valid ark tapscript
18
+ * @example
19
+ * ```typescript
20
+ * const arkTapscript = decodeTapscript(new Uint8Array(32));
21
+ * console.log("type:", arkTapscript.type);
22
+ * ```
23
+ */
13
24
  export function decodeTapscript(script) {
14
25
  const types = [
15
26
  MultisigTapscript,
@@ -29,8 +40,14 @@ export function decodeTapscript(script) {
29
40
  throw new Error(`Failed to decode: script ${hex.encode(script)} is not a valid tapscript`);
30
41
  }
31
42
  /**
32
- * Implements a multi-signature script that requires a threshold of signatures
33
- * from the specified pubkeys.
43
+ * Implements a multi-signature tapscript.
44
+ *
45
+ * <pubkey> CHECKSIGVERIFY <pubkey> CHECKSIG
46
+ *
47
+ * @example
48
+ * ```typescript
49
+ * const multisigTapscript = MultisigTapscript.encode({ pubkeys: [new Uint8Array(32), new Uint8Array(32)] });
50
+ * ```
34
51
  */
35
52
  export var MultisigTapscript;
36
53
  (function (MultisigTapscript) {
@@ -56,7 +73,6 @@ export var MultisigTapscript;
56
73
  type: TapscriptType.Multisig,
57
74
  params,
58
75
  script: p2tr_ms(params.pubkeys.length, params.pubkeys).script,
59
- witnessSize: () => params.pubkeys.length * 64,
60
76
  };
61
77
  }
62
78
  const asm = [];
@@ -74,7 +90,6 @@ export var MultisigTapscript;
74
90
  type: TapscriptType.Multisig,
75
91
  params,
76
92
  script: Script.encode(asm),
77
- witnessSize: () => params.pubkeys.length * 64,
78
93
  };
79
94
  }
80
95
  MultisigTapscript.encode = encode;
@@ -145,7 +160,6 @@ export var MultisigTapscript;
145
160
  type: TapscriptType.Multisig,
146
161
  params: { pubkeys, type: MultisigType.CHECKSIGADD },
147
162
  script,
148
- witnessSize: () => pubkeys.length * 64,
149
163
  };
150
164
  }
151
165
  // <pubkey> CHECKSIGVERIFY <pubkey> CHECKSIG
@@ -189,7 +203,6 @@ export var MultisigTapscript;
189
203
  type: TapscriptType.Multisig,
190
204
  params: { pubkeys, type: MultisigType.CHECKSIG },
191
205
  script,
192
- witnessSize: () => pubkeys.length * 64,
193
206
  };
194
207
  }
195
208
  function is(tapscript) {
@@ -202,6 +215,13 @@ export var MultisigTapscript;
202
215
  * after the relative timelock has expired. The timelock can be specified in blocks or seconds.
203
216
  *
204
217
  * This is the standard exit closure and it is also used for the sweep closure in vtxo trees.
218
+ *
219
+ * <sequence> CHECKSEQUENCEVERIFY DROP <pubkey> CHECKSIG
220
+ *
221
+ * @example
222
+ * ```typescript
223
+ * const csvMultisigTapscript = CSVMultisigTapscript.encode({ timelock: { type: "blocks", value: 144 }, pubkeys: [new Uint8Array(32), new Uint8Array(32)] });
224
+ * ```
205
225
  */
206
226
  export var CSVMultisigTapscript;
207
227
  (function (CSVMultisigTapscript) {
@@ -211,10 +231,14 @@ export var CSVMultisigTapscript;
211
231
  throw new Error(`Invalid pubkey length: expected 32, got ${pubkey.length}`);
212
232
  }
213
233
  }
214
- const sequence = ScriptNum().encode(BigInt(bip68.encode(params.timelock.type === "blocks"
234
+ const sequence = MinimalScriptNum.encode(BigInt(bip68.encode(params.timelock.type === "blocks"
215
235
  ? { blocks: Number(params.timelock.value) }
216
236
  : { seconds: Number(params.timelock.value) })));
217
- const asm = [sequence, "CHECKSEQUENCEVERIFY", "DROP"];
237
+ const asm = [
238
+ sequence.length === 1 ? sequence[0] : sequence,
239
+ "CHECKSEQUENCEVERIFY",
240
+ "DROP",
241
+ ];
218
242
  const multisigScript = MultisigTapscript.encode(params);
219
243
  const script = new Uint8Array([
220
244
  ...Script.encode(asm),
@@ -224,7 +248,6 @@ export var CSVMultisigTapscript;
224
248
  type: TapscriptType.CSVMultisig,
225
249
  params,
226
250
  script,
227
- witnessSize: () => params.pubkeys.length * 64,
228
251
  };
229
252
  }
230
253
  CSVMultisigTapscript.encode = encode;
@@ -251,7 +274,7 @@ export var CSVMultisigTapscript;
251
274
  catch (error) {
252
275
  throw new Error(`Invalid multisig script: ${error instanceof Error ? error.message : String(error)}`);
253
276
  }
254
- const sequenceNum = Number(ScriptNum().decode(sequence));
277
+ const sequenceNum = Number(MinimalScriptNum.decode(sequence));
255
278
  const decodedTimelock = bip68.decode(sequenceNum);
256
279
  const timelock = decodedTimelock.blocks !== undefined
257
280
  ? { type: "blocks", value: BigInt(decodedTimelock.blocks) }
@@ -270,7 +293,6 @@ export var CSVMultisigTapscript;
270
293
  ...multisig.params,
271
294
  },
272
295
  script,
273
- witnessSize: () => multisig.params.pubkeys.length * 64,
274
296
  };
275
297
  }
276
298
  CSVMultisigTapscript.decode = decode;
@@ -283,6 +305,13 @@ export var CSVMultisigTapscript;
283
305
  * Combines a condition script with an exit closure. The resulting script requires
284
306
  * the condition to be met, followed by the standard exit closure requirements
285
307
  * (timelock and signatures).
308
+ *
309
+ * <conditionScript> VERIFY <sequence> CHECKSEQUENCEVERIFY DROP <pubkey> CHECKSIGVERIFY <pubkey> CHECKSIG
310
+ *
311
+ * @example
312
+ * ```typescript
313
+ * const conditionCSVMultisigTapscript = ConditionCSVMultisigTapscript.encode({ conditionScript: new Uint8Array(32), pubkeys: [new Uint8Array(32), new Uint8Array(32)] });
314
+ * ```
286
315
  */
287
316
  export var ConditionCSVMultisigTapscript;
288
317
  (function (ConditionCSVMultisigTapscript) {
@@ -296,7 +325,6 @@ export var ConditionCSVMultisigTapscript;
296
325
  type: TapscriptType.ConditionCSVMultisig,
297
326
  params,
298
327
  script,
299
- witnessSize: (conditionSize) => conditionSize + params.pubkeys.length * 64,
300
328
  };
301
329
  }
302
330
  ConditionCSVMultisigTapscript.encode = encode;
@@ -340,7 +368,6 @@ export var ConditionCSVMultisigTapscript;
340
368
  ...csvMultisig.params,
341
369
  },
342
370
  script,
343
- witnessSize: (conditionSize) => conditionSize + csvMultisig.params.pubkeys.length * 64,
344
371
  };
345
372
  }
346
373
  ConditionCSVMultisigTapscript.decode = decode;
@@ -353,6 +380,13 @@ export var ConditionCSVMultisigTapscript;
353
380
  * Combines a condition script with a forfeit closure. The resulting script requires
354
381
  * the condition to be met, followed by the standard forfeit closure requirements
355
382
  * (multi-signature).
383
+ *
384
+ * <conditionScript> VERIFY <pubkey> CHECKSIGVERIFY <pubkey> CHECKSIG
385
+ *
386
+ * @example
387
+ * ```typescript
388
+ * const conditionMultisigTapscript = ConditionMultisigTapscript.encode({ conditionScript: new Uint8Array(32), pubkeys: [new Uint8Array(32), new Uint8Array(32)] });
389
+ * ```
356
390
  */
357
391
  export var ConditionMultisigTapscript;
358
392
  (function (ConditionMultisigTapscript) {
@@ -366,7 +400,6 @@ export var ConditionMultisigTapscript;
366
400
  type: TapscriptType.ConditionMultisig,
367
401
  params,
368
402
  script,
369
- witnessSize: (conditionSize) => conditionSize + params.pubkeys.length * 64,
370
403
  };
371
404
  }
372
405
  ConditionMultisigTapscript.encode = encode;
@@ -410,7 +443,6 @@ export var ConditionMultisigTapscript;
410
443
  ...multisig.params,
411
444
  },
412
445
  script,
413
- witnessSize: (conditionSize) => conditionSize + multisig.params.pubkeys.length * 64,
414
446
  };
415
447
  }
416
448
  ConditionMultisigTapscript.decode = decode;
@@ -423,12 +455,23 @@ export var ConditionMultisigTapscript;
423
455
  * Implements an absolute timelock (CLTV) script combined with a forfeit closure.
424
456
  * The script requires waiting until a specific block height/timestamp before the
425
457
  * forfeit closure conditions can be met.
458
+ *
459
+ * <locktime> CHECKLOCKTIMEVERIFY DROP <pubkey> CHECKSIGVERIFY <pubkey> CHECKSIG
460
+ *
461
+ * @example
462
+ * ```typescript
463
+ * const cltvMultisigTapscript = CLTVMultisigTapscript.encode({ absoluteTimelock: 144, pubkeys: [new Uint8Array(32), new Uint8Array(32)] });
464
+ * ```
426
465
  */
427
466
  export var CLTVMultisigTapscript;
428
467
  (function (CLTVMultisigTapscript) {
429
468
  function encode(params) {
430
- const locktime = ScriptNum().encode(params.absoluteTimelock);
431
- const asm = [locktime, "CHECKLOCKTIMEVERIFY", "DROP"];
469
+ const locktime = MinimalScriptNum.encode(params.absoluteTimelock);
470
+ const asm = [
471
+ locktime.length === 1 ? locktime[0] : locktime,
472
+ "CHECKLOCKTIMEVERIFY",
473
+ "DROP",
474
+ ];
432
475
  const timelockedScript = Script.encode(asm);
433
476
  const script = new Uint8Array([
434
477
  ...timelockedScript,
@@ -438,7 +481,6 @@ export var CLTVMultisigTapscript;
438
481
  type: TapscriptType.CLTVMultisig,
439
482
  params,
440
483
  script,
441
- witnessSize: () => params.pubkeys.length * 64,
442
484
  };
443
485
  }
444
486
  CLTVMultisigTapscript.encode = encode;
@@ -465,7 +507,7 @@ export var CLTVMultisigTapscript;
465
507
  catch (error) {
466
508
  throw new Error(`Invalid multisig script: ${error instanceof Error ? error.message : String(error)}`);
467
509
  }
468
- const absoluteTimelock = ScriptNum().decode(locktime);
510
+ const absoluteTimelock = MinimalScriptNum.decode(locktime);
469
511
  const reconstructed = encode({
470
512
  absoluteTimelock,
471
513
  ...multisig.params,
@@ -480,7 +522,6 @@ export var CLTVMultisigTapscript;
480
522
  ...multisig.params,
481
523
  },
482
524
  script,
483
- witnessSize: () => multisig.params.pubkeys.length * 64,
484
525
  };
485
526
  }
486
527
  CLTVMultisigTapscript.decode = decode;
@@ -2,17 +2,38 @@ import { Script } from "@scure/btc-signer";
2
2
  import { CLTVMultisigTapscript, ConditionCSVMultisigTapscript, ConditionMultisigTapscript, CSVMultisigTapscript, MultisigTapscript, } from './tapscript.js';
3
3
  import { hex } from "@scure/base";
4
4
  import { VtxoScript } from './base.js';
5
- // VHTLC is an Hashed Timelock Contract VtxoScript implementation
6
- // - claim (preimage + receiver)
7
- // - refund (sender + receiver + server)
8
- // - refundWithoutReceiver (at refundLocktime, sender + receiver + server)
9
- // - unilateralClaim (preimage + receiver after unilateralClaimDelay)
10
- // - unilateralRefund (sender + receiver after unilateralRefundDelay)
11
- // - unilateralRefundWithoutReceiver (sender after unilateralRefundWithoutReceiverDelay)
5
+ /**
6
+ * Virtual Hash Time Lock Contract (VHTLC) implementation.
7
+ *
8
+ * VHTLC is a contract that enables atomic swaps and conditional payments
9
+ * in the Ark protocol. It provides multiple spending paths:
10
+ *
11
+ * - **claim**: Receiver can claim funds by revealing the preimage
12
+ * - **refund**: Sender and receiver can collaboratively refund
13
+ * - **refundWithoutReceiver**: Sender can refund after locktime expires
14
+ * - **unilateralClaim**: Receiver can claim unilaterally after delay
15
+ * - **unilateralRefund**: Sender and receiver can refund unilaterally after delay
16
+ * - **unilateralRefundWithoutReceiver**: Sender can refund unilaterally after delay
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * const vhtlc = new VHTLC.Script({
21
+ * sender: alicePubKey,
22
+ * receiver: bobPubKey,
23
+ * server: serverPubKey,
24
+ * preimageHash: hash160(secret),
25
+ * refundLocktime: BigInt(chainTip + 10),
26
+ * unilateralClaimDelay: { type: 'blocks', value: 100n },
27
+ * unilateralRefundDelay: { type: 'blocks', value: 102n },
28
+ * unilateralRefundWithoutReceiverDelay: { type: 'blocks', value: 103n }
29
+ * });
30
+ * ```
31
+ */
12
32
  export var VHTLC;
13
33
  (function (VHTLC) {
14
34
  class Script extends VtxoScript {
15
35
  constructor(options) {
36
+ validateOptions(options);
16
37
  const { sender, receiver, server, preimageHash, refundLocktime, unilateralClaimDelay, unilateralRefundDelay, unilateralRefundWithoutReceiverDelay, } = options;
17
38
  const conditionScript = preimageConditionScript(preimageHash);
18
39
  const claimScript = ConditionMultisigTapscript.encode({
@@ -75,6 +96,63 @@ export var VHTLC;
75
96
  }
76
97
  }
77
98
  VHTLC.Script = Script;
99
+ function validateOptions(options) {
100
+ const { sender, receiver, server, preimageHash, refundLocktime, unilateralClaimDelay, unilateralRefundDelay, unilateralRefundWithoutReceiverDelay, } = options;
101
+ if (!preimageHash || preimageHash.length !== 20) {
102
+ throw new Error("preimage hash must be 20 bytes");
103
+ }
104
+ if (!receiver || receiver.length !== 32) {
105
+ throw new Error("Invalid public key length (receiver)");
106
+ }
107
+ if (!sender || sender.length !== 32) {
108
+ throw new Error("Invalid public key length (sender)");
109
+ }
110
+ if (!server || server.length !== 32) {
111
+ throw new Error("Invalid public key length (server)");
112
+ }
113
+ if (typeof refundLocktime !== "bigint" || refundLocktime <= 0n) {
114
+ throw new Error("refund locktime must be greater than 0");
115
+ }
116
+ if (!unilateralClaimDelay ||
117
+ typeof unilateralClaimDelay.value !== "bigint" ||
118
+ unilateralClaimDelay.value <= 0n) {
119
+ throw new Error("unilateral claim delay must greater than 0");
120
+ }
121
+ if (unilateralClaimDelay.type === "seconds" &&
122
+ unilateralClaimDelay.value % 512n !== 0n) {
123
+ throw new Error("seconds timelock must be multiple of 512");
124
+ }
125
+ if (unilateralClaimDelay.type === "seconds" &&
126
+ unilateralClaimDelay.value < 512n) {
127
+ throw new Error("seconds timelock must be greater or equal to 512");
128
+ }
129
+ if (!unilateralRefundDelay ||
130
+ typeof unilateralRefundDelay.value !== "bigint" ||
131
+ unilateralRefundDelay.value <= 0n) {
132
+ throw new Error("unilateral refund delay must greater than 0");
133
+ }
134
+ if (unilateralRefundDelay.type === "seconds" &&
135
+ unilateralRefundDelay.value % 512n !== 0n) {
136
+ throw new Error("seconds timelock must be multiple of 512");
137
+ }
138
+ if (unilateralRefundDelay.type === "seconds" &&
139
+ unilateralRefundDelay.value < 512n) {
140
+ throw new Error("seconds timelock must be greater or equal to 512");
141
+ }
142
+ if (!unilateralRefundWithoutReceiverDelay ||
143
+ typeof unilateralRefundWithoutReceiverDelay.value !== "bigint" ||
144
+ unilateralRefundWithoutReceiverDelay.value <= 0n) {
145
+ throw new Error("unilateral refund without receiver delay must greater than 0");
146
+ }
147
+ if (unilateralRefundWithoutReceiverDelay.type === "seconds" &&
148
+ unilateralRefundWithoutReceiverDelay.value % 512n !== 0n) {
149
+ throw new Error("seconds timelock must be multiple of 512");
150
+ }
151
+ if (unilateralRefundWithoutReceiverDelay.type === "seconds" &&
152
+ unilateralRefundWithoutReceiverDelay.value < 512n) {
153
+ throw new Error("seconds timelock must be greater or equal to 512");
154
+ }
155
+ }
78
156
  })(VHTLC || (VHTLC = {}));
79
157
  function preimageConditionScript(preimageHash) {
80
158
  return Script.encode(["HASH160", preimageHash, "EQUAL"]);
@@ -1,17 +1,17 @@
1
1
  import * as musig2 from '../musig2/index.js';
2
- import { getCosignerKeys } from './vtxoTree.js';
3
- import { Script, SigHash, Transaction } from "@scure/btc-signer";
4
- import { base64, hex } from "@scure/base";
2
+ import { Script, SigHash } from "@scure/btc-signer";
3
+ import { hex } from "@scure/base";
5
4
  import { schnorr, secp256k1 } from "@noble/curves/secp256k1";
6
- import { randomPrivateKeyBytes } from "@scure/btc-signer/utils";
7
- export const ErrMissingVtxoTree = new Error("missing vtxo tree");
5
+ import { randomPrivateKeyBytes, sha256x2 } from "@scure/btc-signer/utils";
6
+ import { CosignerPublicKey, getArkPsbtFields } from '../utils/unknownFields.js';
7
+ export const ErrMissingVtxoGraph = new Error("missing vtxo graph");
8
8
  export const ErrMissingAggregateKey = new Error("missing aggregate key");
9
9
  export class TreeSignerSession {
10
10
  constructor(secretKey) {
11
11
  this.secretKey = secretKey;
12
12
  this.myNonces = null;
13
13
  this.aggregateNonces = null;
14
- this.tree = null;
14
+ this.graph = null;
15
15
  this.scriptRoot = null;
16
16
  this.rootSharedOutputAmount = null;
17
17
  }
@@ -20,7 +20,7 @@ export class TreeSignerSession {
20
20
  return new TreeSignerSession(secretKey);
21
21
  }
22
22
  init(tree, scriptRoot, rootInputAmount) {
23
- this.tree = tree;
23
+ this.graph = tree;
24
24
  this.scriptRoot = scriptRoot;
25
25
  this.rootSharedOutputAmount = rootInputAmount;
26
26
  }
@@ -28,24 +28,16 @@ export class TreeSignerSession {
28
28
  return secp256k1.getPublicKey(this.secretKey);
29
29
  }
30
30
  getNonces() {
31
- if (!this.tree)
32
- throw ErrMissingVtxoTree;
31
+ if (!this.graph)
32
+ throw ErrMissingVtxoGraph;
33
33
  if (!this.myNonces) {
34
34
  this.myNonces = this.generateNonces();
35
35
  }
36
- const nonces = [];
37
- for (const levelNonces of this.myNonces) {
38
- const levelPubNonces = [];
39
- for (const nonce of levelNonces) {
40
- if (!nonce) {
41
- levelPubNonces.push(null);
42
- continue;
43
- }
44
- levelPubNonces.push({ pubNonce: nonce.pubNonce });
45
- }
46
- nonces.push(levelPubNonces);
36
+ const publicNonces = new Map();
37
+ for (const [txid, nonces] of this.myNonces) {
38
+ publicNonces.set(txid, { pubNonce: nonces.pubNonce });
47
39
  }
48
- return nonces;
40
+ return publicNonces;
49
41
  }
50
42
  setAggregatedNonces(nonces) {
51
43
  if (this.aggregateNonces)
@@ -53,71 +45,55 @@ export class TreeSignerSession {
53
45
  this.aggregateNonces = nonces;
54
46
  }
55
47
  sign() {
56
- if (!this.tree)
57
- throw ErrMissingVtxoTree;
48
+ if (!this.graph)
49
+ throw ErrMissingVtxoGraph;
58
50
  if (!this.aggregateNonces)
59
51
  throw new Error("nonces not set");
60
52
  if (!this.myNonces)
61
53
  throw new Error("nonces not generated");
62
- const sigs = [];
63
- for (let levelIndex = 0; levelIndex < this.tree.levels.length; levelIndex++) {
64
- const levelSigs = [];
65
- const level = this.tree.levels[levelIndex];
66
- for (let nodeIndex = 0; nodeIndex < level.length; nodeIndex++) {
67
- const node = level[nodeIndex];
68
- const tx = Transaction.fromPSBT(base64.decode(node.tx));
69
- const sig = this.signPartial(tx, levelIndex, nodeIndex);
70
- if (sig) {
71
- levelSigs.push(sig);
72
- }
73
- else {
74
- levelSigs.push(null);
75
- }
76
- }
77
- sigs.push(levelSigs);
54
+ const sigs = new Map();
55
+ for (const g of this.graph) {
56
+ const sig = this.signPartial(g);
57
+ sigs.set(g.txid, sig);
78
58
  }
79
59
  return sigs;
80
60
  }
81
61
  generateNonces() {
82
- if (!this.tree)
83
- throw ErrMissingVtxoTree;
84
- const myNonces = [];
62
+ if (!this.graph)
63
+ throw ErrMissingVtxoGraph;
64
+ const myNonces = new Map();
85
65
  const publicKey = secp256k1.getPublicKey(this.secretKey);
86
- for (const level of this.tree.levels) {
87
- const levelNonces = [];
88
- for (let i = 0; i < level.length; i++) {
89
- const nonces = musig2.generateNonces(publicKey);
90
- levelNonces.push(nonces);
91
- }
92
- myNonces.push(levelNonces);
66
+ for (const g of this.graph) {
67
+ const nonces = musig2.generateNonces(publicKey);
68
+ myNonces.set(g.txid, nonces);
93
69
  }
94
70
  return myNonces;
95
71
  }
96
- signPartial(tx, levelIndex, nodeIndex) {
97
- if (!this.tree || !this.scriptRoot || !this.rootSharedOutputAmount) {
72
+ signPartial(g) {
73
+ if (!this.graph || !this.scriptRoot || !this.rootSharedOutputAmount) {
98
74
  throw TreeSignerSession.NOT_INITIALIZED;
99
75
  }
100
76
  if (!this.myNonces || !this.aggregateNonces) {
101
77
  throw new Error("session not properly initialized");
102
78
  }
103
- const myNonce = this.myNonces[levelIndex][nodeIndex];
79
+ const myNonce = this.myNonces.get(g.txid);
104
80
  if (!myNonce)
105
- return null;
106
- const aggNonce = this.aggregateNonces[levelIndex][nodeIndex];
81
+ throw new Error("missing private nonce");
82
+ const aggNonce = this.aggregateNonces.get(g.txid);
107
83
  if (!aggNonce)
108
84
  throw new Error("missing aggregate nonce");
109
85
  const prevoutAmounts = [];
110
86
  const prevoutScripts = [];
111
- const cosigners = getCosignerKeys(tx);
87
+ const cosigners = getArkPsbtFields(g.root, 0, CosignerPublicKey).map((c) => c.key);
112
88
  const { finalKey } = musig2.aggregateKeys(cosigners, true, {
113
89
  taprootTweak: this.scriptRoot,
114
90
  });
115
- for (let inputIndex = 0; inputIndex < tx.inputsLength; inputIndex++) {
116
- const prevout = getPrevOutput(finalKey, this.tree, this.rootSharedOutputAmount, tx);
91
+ for (let inputIndex = 0; inputIndex < g.root.inputsLength; inputIndex++) {
92
+ const prevout = getPrevOutput(finalKey, this.graph, this.rootSharedOutputAmount, g.root);
117
93
  prevoutAmounts.push(prevout.amount);
118
94
  prevoutScripts.push(prevout.script);
119
95
  }
120
- const message = tx.preimageWitnessV1(0, // always first input
96
+ const message = g.root.preimageWitnessV1(0, // always first input
121
97
  prevoutScripts, SigHash.DEFAULT, prevoutAmounts);
122
98
  return musig2.sign(myNonce.secNonce, this.secretKey, aggNonce.pubNonce, cosigners, message, {
123
99
  taprootTweak: this.scriptRoot,
@@ -129,66 +105,47 @@ TreeSignerSession.NOT_INITIALIZED = new Error("session not initialized, call ini
129
105
  // Helper function to validate tree signatures
130
106
  export async function validateTreeSigs(finalAggregatedKey, sharedOutputAmount, vtxoTree) {
131
107
  // Iterate through each level of the tree
132
- for (const level of vtxoTree.levels) {
133
- for (const node of level) {
134
- // Parse the transaction
135
- const tx = Transaction.fromPSBT(base64.decode(node.tx));
136
- const input = tx.getInput(0);
137
- // Check if input has signature
138
- if (!input.tapKeySig) {
139
- throw new Error("unsigned tree input");
140
- }
141
- // Get the previous output information
142
- const prevout = getPrevOutput(finalAggregatedKey, vtxoTree, sharedOutputAmount, tx);
143
- // Calculate the message that was signed
144
- const message = tx.preimageWitnessV1(0, // always first input
145
- [prevout.script], SigHash.DEFAULT, [prevout.amount]);
146
- // Verify the signature
147
- const isValid = schnorr.verify(input.tapKeySig, message, finalAggregatedKey);
148
- if (!isValid) {
149
- throw new Error("invalid signature");
150
- }
108
+ for (const g of vtxoTree) {
109
+ // Parse the transaction
110
+ const input = g.root.getInput(0);
111
+ // Check if input has signature
112
+ if (!input.tapKeySig) {
113
+ throw new Error("unsigned tree input");
114
+ }
115
+ // Get the previous output information
116
+ const prevout = getPrevOutput(finalAggregatedKey, vtxoTree, sharedOutputAmount, g.root);
117
+ // Calculate the message that was signed
118
+ const message = g.root.preimageWitnessV1(0, // always first input
119
+ [prevout.script], SigHash.DEFAULT, [prevout.amount]);
120
+ // Verify the signature
121
+ const isValid = schnorr.verify(input.tapKeySig, message, finalAggregatedKey);
122
+ if (!isValid) {
123
+ throw new Error("invalid signature");
151
124
  }
152
125
  }
153
126
  }
154
- function getPrevOutput(finalKey, vtxoTree, sharedOutputAmount, partial) {
155
- // Generate P2TR script
127
+ function getPrevOutput(finalKey, graph, sharedOutputAmount, tx) {
128
+ // generate P2TR script from musig2 final key
156
129
  const pkScript = Script.encode(["OP_1", finalKey.slice(1)]);
157
- // Get root node
158
- const rootNode = vtxoTree.levels[0][0];
159
- if (!rootNode)
160
- throw new Error("empty vtxo tree");
161
- const input = partial.getInput(0);
162
- if (!input.txid)
163
- throw new Error("missing input txid");
164
- const parentTxID = hex.encode(input.txid);
165
- // Check if parent is root
166
- if (rootNode.parentTxid === parentTxID) {
130
+ const txid = hex.encode(sha256x2(tx.toBytes(true)).reverse());
131
+ // if the input is the root input, return the shared output amount
132
+ if (txid === graph.txid) {
167
133
  return {
168
134
  amount: sharedOutputAmount,
169
135
  script: pkScript,
170
136
  };
171
137
  }
172
- // Search for parent in tree
173
- let parent = null;
174
- for (const level of vtxoTree.levels) {
175
- for (const node of level) {
176
- if (node.txid === parentTxID) {
177
- parent = node;
178
- break;
179
- }
180
- }
181
- if (parent)
182
- break;
183
- }
184
- if (!parent) {
185
- throw new Error("parent tx not found");
186
- }
187
- // Parse parent tx
188
- const parentTx = Transaction.fromPSBT(base64.decode(parent.tx));
189
- if (!input.index)
138
+ // find the parent transaction
139
+ const parentInput = tx.getInput(0);
140
+ if (!parentInput.txid)
141
+ throw new Error("missing parent input txid");
142
+ const parentTxid = hex.encode(new Uint8Array(parentInput.txid));
143
+ const parent = graph.find(parentTxid);
144
+ if (!parent)
145
+ throw new Error("parent tx not found");
146
+ if (parentInput.index === undefined)
190
147
  throw new Error("missing input index");
191
- const parentOutput = parentTx.getOutput(input.index);
148
+ const parentOutput = parent.root.getOutput(parentInput.index);
192
149
  if (!parentOutput)
193
150
  throw new Error("parent output not found");
194
151
  if (!parentOutput.amount)