@arkade-os/sdk 0.0.16

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 (103) hide show
  1. package/README.md +312 -0
  2. package/dist/cjs/arknote/index.js +86 -0
  3. package/dist/cjs/forfeit.js +38 -0
  4. package/dist/cjs/identity/inMemoryKey.js +40 -0
  5. package/dist/cjs/identity/index.js +2 -0
  6. package/dist/cjs/index.js +48 -0
  7. package/dist/cjs/musig2/index.js +10 -0
  8. package/dist/cjs/musig2/keys.js +57 -0
  9. package/dist/cjs/musig2/nonces.js +44 -0
  10. package/dist/cjs/musig2/sign.js +102 -0
  11. package/dist/cjs/networks.js +26 -0
  12. package/dist/cjs/package.json +3 -0
  13. package/dist/cjs/providers/ark.js +530 -0
  14. package/dist/cjs/providers/onchain.js +61 -0
  15. package/dist/cjs/script/address.js +45 -0
  16. package/dist/cjs/script/base.js +51 -0
  17. package/dist/cjs/script/default.js +40 -0
  18. package/dist/cjs/script/tapscript.js +528 -0
  19. package/dist/cjs/script/vhtlc.js +84 -0
  20. package/dist/cjs/tree/signingSession.js +238 -0
  21. package/dist/cjs/tree/validation.js +184 -0
  22. package/dist/cjs/tree/vtxoTree.js +197 -0
  23. package/dist/cjs/utils/bip21.js +114 -0
  24. package/dist/cjs/utils/coinselect.js +73 -0
  25. package/dist/cjs/utils/psbt.js +124 -0
  26. package/dist/cjs/utils/transactionHistory.js +148 -0
  27. package/dist/cjs/utils/txSizeEstimator.js +95 -0
  28. package/dist/cjs/wallet/index.js +8 -0
  29. package/dist/cjs/wallet/serviceWorker/db/vtxo/idb.js +153 -0
  30. package/dist/cjs/wallet/serviceWorker/db/vtxo/index.js +2 -0
  31. package/dist/cjs/wallet/serviceWorker/request.js +75 -0
  32. package/dist/cjs/wallet/serviceWorker/response.js +187 -0
  33. package/dist/cjs/wallet/serviceWorker/wallet.js +332 -0
  34. package/dist/cjs/wallet/serviceWorker/worker.js +452 -0
  35. package/dist/cjs/wallet/wallet.js +720 -0
  36. package/dist/esm/arknote/index.js +81 -0
  37. package/dist/esm/forfeit.js +35 -0
  38. package/dist/esm/identity/inMemoryKey.js +36 -0
  39. package/dist/esm/identity/index.js +1 -0
  40. package/dist/esm/index.js +39 -0
  41. package/dist/esm/musig2/index.js +3 -0
  42. package/dist/esm/musig2/keys.js +21 -0
  43. package/dist/esm/musig2/nonces.js +8 -0
  44. package/dist/esm/musig2/sign.js +63 -0
  45. package/dist/esm/networks.js +22 -0
  46. package/dist/esm/package.json +3 -0
  47. package/dist/esm/providers/ark.js +526 -0
  48. package/dist/esm/providers/onchain.js +57 -0
  49. package/dist/esm/script/address.js +41 -0
  50. package/dist/esm/script/base.js +46 -0
  51. package/dist/esm/script/default.js +37 -0
  52. package/dist/esm/script/tapscript.js +491 -0
  53. package/dist/esm/script/vhtlc.js +81 -0
  54. package/dist/esm/tree/signingSession.js +200 -0
  55. package/dist/esm/tree/validation.js +179 -0
  56. package/dist/esm/tree/vtxoTree.js +157 -0
  57. package/dist/esm/utils/bip21.js +110 -0
  58. package/dist/esm/utils/coinselect.js +69 -0
  59. package/dist/esm/utils/psbt.js +118 -0
  60. package/dist/esm/utils/transactionHistory.js +145 -0
  61. package/dist/esm/utils/txSizeEstimator.js +91 -0
  62. package/dist/esm/wallet/index.js +5 -0
  63. package/dist/esm/wallet/serviceWorker/db/vtxo/idb.js +149 -0
  64. package/dist/esm/wallet/serviceWorker/db/vtxo/index.js +1 -0
  65. package/dist/esm/wallet/serviceWorker/request.js +72 -0
  66. package/dist/esm/wallet/serviceWorker/response.js +184 -0
  67. package/dist/esm/wallet/serviceWorker/wallet.js +328 -0
  68. package/dist/esm/wallet/serviceWorker/worker.js +448 -0
  69. package/dist/esm/wallet/wallet.js +716 -0
  70. package/dist/types/arknote/index.d.ts +17 -0
  71. package/dist/types/forfeit.d.ts +15 -0
  72. package/dist/types/identity/inMemoryKey.d.ts +12 -0
  73. package/dist/types/identity/index.d.ts +7 -0
  74. package/dist/types/index.d.ts +22 -0
  75. package/dist/types/musig2/index.d.ts +4 -0
  76. package/dist/types/musig2/keys.d.ts +9 -0
  77. package/dist/types/musig2/nonces.d.ts +13 -0
  78. package/dist/types/musig2/sign.d.ts +27 -0
  79. package/dist/types/networks.d.ts +16 -0
  80. package/dist/types/providers/ark.d.ts +126 -0
  81. package/dist/types/providers/onchain.d.ts +36 -0
  82. package/dist/types/script/address.d.ts +10 -0
  83. package/dist/types/script/base.d.ts +26 -0
  84. package/dist/types/script/default.d.ts +19 -0
  85. package/dist/types/script/tapscript.d.ts +94 -0
  86. package/dist/types/script/vhtlc.d.ts +31 -0
  87. package/dist/types/tree/signingSession.d.ts +32 -0
  88. package/dist/types/tree/validation.d.ts +22 -0
  89. package/dist/types/tree/vtxoTree.d.ts +32 -0
  90. package/dist/types/utils/bip21.d.ts +21 -0
  91. package/dist/types/utils/coinselect.d.ts +21 -0
  92. package/dist/types/utils/psbt.d.ts +11 -0
  93. package/dist/types/utils/transactionHistory.d.ts +2 -0
  94. package/dist/types/utils/txSizeEstimator.d.ts +27 -0
  95. package/dist/types/wallet/index.d.ts +122 -0
  96. package/dist/types/wallet/serviceWorker/db/vtxo/idb.d.ts +18 -0
  97. package/dist/types/wallet/serviceWorker/db/vtxo/index.d.ts +12 -0
  98. package/dist/types/wallet/serviceWorker/request.d.ts +68 -0
  99. package/dist/types/wallet/serviceWorker/response.d.ts +107 -0
  100. package/dist/types/wallet/serviceWorker/wallet.d.ts +23 -0
  101. package/dist/types/wallet/serviceWorker/worker.d.ts +26 -0
  102. package/dist/types/wallet/wallet.d.ts +42 -0
  103. package/package.json +88 -0
@@ -0,0 +1,37 @@
1
+ import { VtxoScript } from './base.js';
2
+ import { CSVMultisigTapscript, MultisigTapscript, } from './tapscript.js';
3
+ import { hex } from "@scure/base";
4
+ // DefaultVtxo is the default implementation of a VtxoScript.
5
+ // it contains 1 forfeit path and 1 exit path.
6
+ // forfeit = (Alice + Server)
7
+ // exit = (Alice) after csvTimelock
8
+ export var DefaultVtxo;
9
+ (function (DefaultVtxo) {
10
+ class Script extends VtxoScript {
11
+ constructor(options) {
12
+ const { pubKey, serverPubKey, csvTimelock = Script.DEFAULT_TIMELOCK, } = options;
13
+ const forfeitScript = MultisigTapscript.encode({
14
+ pubkeys: [pubKey, serverPubKey],
15
+ }).script;
16
+ const exitScript = CSVMultisigTapscript.encode({
17
+ timelock: csvTimelock,
18
+ pubkeys: [pubKey],
19
+ }).script;
20
+ super([forfeitScript, exitScript]);
21
+ this.options = options;
22
+ this.forfeitScript = hex.encode(forfeitScript);
23
+ this.exitScript = hex.encode(exitScript);
24
+ }
25
+ forfeit() {
26
+ return this.findLeaf(this.forfeitScript);
27
+ }
28
+ exit() {
29
+ return this.findLeaf(this.exitScript);
30
+ }
31
+ }
32
+ Script.DEFAULT_TIMELOCK = {
33
+ value: 144n,
34
+ type: "blocks",
35
+ }; // 1 day in blocks
36
+ DefaultVtxo.Script = Script;
37
+ })(DefaultVtxo || (DefaultVtxo = {}));
@@ -0,0 +1,491 @@
1
+ import * as bip68 from "bip68";
2
+ import { Script, ScriptNum } from "@scure/btc-signer/script";
3
+ import { p2tr_ms } from "@scure/btc-signer/payment";
4
+ import { hex } from "@scure/base";
5
+ export var TapscriptType;
6
+ (function (TapscriptType) {
7
+ TapscriptType["Multisig"] = "multisig";
8
+ TapscriptType["CSVMultisig"] = "csv-multisig";
9
+ TapscriptType["ConditionCSVMultisig"] = "condition-csv-multisig";
10
+ TapscriptType["ConditionMultisig"] = "condition-multisig";
11
+ TapscriptType["CLTVMultisig"] = "cltv-multisig";
12
+ })(TapscriptType || (TapscriptType = {}));
13
+ export function decodeTapscript(script) {
14
+ const types = [
15
+ MultisigTapscript,
16
+ CSVMultisigTapscript,
17
+ ConditionCSVMultisigTapscript,
18
+ ConditionMultisigTapscript,
19
+ CLTVMultisigTapscript,
20
+ ];
21
+ for (const type of types) {
22
+ try {
23
+ return type.decode(script);
24
+ }
25
+ catch (error) {
26
+ continue;
27
+ }
28
+ }
29
+ throw new Error(`Failed to decode: script ${hex.encode(script)} is not a valid tapscript`);
30
+ }
31
+ /**
32
+ * Implements a multi-signature script that requires a threshold of signatures
33
+ * from the specified pubkeys.
34
+ */
35
+ export var MultisigTapscript;
36
+ (function (MultisigTapscript) {
37
+ let MultisigType;
38
+ (function (MultisigType) {
39
+ MultisigType[MultisigType["CHECKSIG"] = 0] = "CHECKSIG";
40
+ MultisigType[MultisigType["CHECKSIGADD"] = 1] = "CHECKSIGADD";
41
+ })(MultisigType = MultisigTapscript.MultisigType || (MultisigTapscript.MultisigType = {}));
42
+ function encode(params) {
43
+ if (params.pubkeys.length === 0) {
44
+ throw new Error("At least 1 pubkey is required");
45
+ }
46
+ for (const pubkey of params.pubkeys) {
47
+ if (pubkey.length !== 32) {
48
+ throw new Error(`Invalid pubkey length: expected 32, got ${pubkey.length}`);
49
+ }
50
+ }
51
+ if (!params.type) {
52
+ params.type = MultisigType.CHECKSIG;
53
+ }
54
+ if (params.type === MultisigType.CHECKSIGADD) {
55
+ return {
56
+ type: TapscriptType.Multisig,
57
+ params,
58
+ script: p2tr_ms(params.pubkeys.length, params.pubkeys).script,
59
+ witnessSize: () => params.pubkeys.length * 64,
60
+ };
61
+ }
62
+ const asm = [];
63
+ for (let i = 0; i < params.pubkeys.length; i++) {
64
+ asm.push(params.pubkeys[i]);
65
+ // CHECKSIGVERIFY except the last pubkey
66
+ if (i < params.pubkeys.length - 1) {
67
+ asm.push("CHECKSIGVERIFY");
68
+ }
69
+ else {
70
+ asm.push("CHECKSIG");
71
+ }
72
+ }
73
+ return {
74
+ type: TapscriptType.Multisig,
75
+ params,
76
+ script: Script.encode(asm),
77
+ witnessSize: () => params.pubkeys.length * 64,
78
+ };
79
+ }
80
+ MultisigTapscript.encode = encode;
81
+ function decode(script) {
82
+ if (script.length === 0) {
83
+ throw new Error("Failed to decode: script is empty");
84
+ }
85
+ try {
86
+ // Try decoding as checksigAdd first
87
+ return decodeChecksigAdd(script);
88
+ }
89
+ catch (error) {
90
+ // If checksigAdd fails, try regular checksig
91
+ try {
92
+ return decodeChecksig(script);
93
+ }
94
+ catch (error2) {
95
+ throw new Error(`Failed to decode script: ${error2 instanceof Error ? error2.message : String(error2)}`);
96
+ }
97
+ }
98
+ }
99
+ MultisigTapscript.decode = decode;
100
+ // <pubkey> CHECKSIG <pubkey> CHECKSIGADD <len_keys> NUMEQUAL
101
+ function decodeChecksigAdd(script) {
102
+ const asm = Script.decode(script);
103
+ const pubkeys = [];
104
+ let foundNumEqual = false;
105
+ // Parse through ASM operations
106
+ for (let i = 0; i < asm.length; i++) {
107
+ const op = asm[i];
108
+ // If it's a data push, it should be a 32-byte pubkey
109
+ if (typeof op !== "string" && typeof op !== "number") {
110
+ if (op.length !== 32) {
111
+ throw new Error(`Invalid pubkey length: expected 32, got ${op.length}`);
112
+ }
113
+ pubkeys.push(op);
114
+ // Check next operation is CHECKSIGADD or CHECKSIG
115
+ if (i + 1 >= asm.length ||
116
+ (asm[i + 1] !== "CHECKSIGADD" && asm[i + 1] !== "CHECKSIG")) {
117
+ throw new Error("Expected CHECKSIGADD or CHECKSIG after pubkey");
118
+ }
119
+ i++; // Skip the CHECKSIGADD op
120
+ continue;
121
+ }
122
+ // Last operation should be NUMEQUAL
123
+ if (i === asm.length - 1) {
124
+ if (op !== "NUMEQUAL") {
125
+ throw new Error("Expected NUMEQUAL at end of script");
126
+ }
127
+ foundNumEqual = true;
128
+ }
129
+ }
130
+ if (!foundNumEqual) {
131
+ throw new Error("Missing NUMEQUAL operation");
132
+ }
133
+ if (pubkeys.length === 0) {
134
+ throw new Error("Invalid script: must have at least 1 pubkey");
135
+ }
136
+ // Verify the script by re-encoding and comparing
137
+ const reconstructed = encode({
138
+ pubkeys,
139
+ type: MultisigType.CHECKSIGADD,
140
+ });
141
+ if (hex.encode(reconstructed.script) !== hex.encode(script)) {
142
+ throw new Error("Invalid script format: script reconstruction mismatch");
143
+ }
144
+ return {
145
+ type: TapscriptType.Multisig,
146
+ params: { pubkeys, type: MultisigType.CHECKSIGADD },
147
+ script,
148
+ witnessSize: () => pubkeys.length * 64,
149
+ };
150
+ }
151
+ // <pubkey> CHECKSIGVERIFY <pubkey> CHECKSIG
152
+ function decodeChecksig(script) {
153
+ const asm = Script.decode(script);
154
+ const pubkeys = [];
155
+ // Parse through ASM operations
156
+ for (let i = 0; i < asm.length; i++) {
157
+ const op = asm[i];
158
+ // If it's a data push, it should be a 32-byte pubkey
159
+ if (typeof op !== "string" && typeof op !== "number") {
160
+ if (op.length !== 32) {
161
+ throw new Error(`Invalid pubkey length: expected 32, got ${op.length}`);
162
+ }
163
+ pubkeys.push(op);
164
+ // Check next operation
165
+ if (i + 1 >= asm.length) {
166
+ throw new Error("Unexpected end of script");
167
+ }
168
+ const nextOp = asm[i + 1];
169
+ if (nextOp !== "CHECKSIGVERIFY" && nextOp !== "CHECKSIG") {
170
+ throw new Error("Expected CHECKSIGVERIFY or CHECKSIG after pubkey");
171
+ }
172
+ // Last operation must be CHECKSIG, not CHECKSIGVERIFY
173
+ if (i === asm.length - 2 && nextOp !== "CHECKSIG") {
174
+ throw new Error("Last operation must be CHECKSIG");
175
+ }
176
+ i++; // Skip the CHECKSIG/CHECKSIGVERIFY op
177
+ continue;
178
+ }
179
+ }
180
+ if (pubkeys.length === 0) {
181
+ throw new Error("Invalid script: must have at least 1 pubkey");
182
+ }
183
+ // Verify the script by re-encoding and comparing
184
+ const reconstructed = encode({ pubkeys, type: MultisigType.CHECKSIG });
185
+ if (hex.encode(reconstructed.script) !== hex.encode(script)) {
186
+ throw new Error("Invalid script format: script reconstruction mismatch");
187
+ }
188
+ return {
189
+ type: TapscriptType.Multisig,
190
+ params: { pubkeys, type: MultisigType.CHECKSIG },
191
+ script,
192
+ witnessSize: () => pubkeys.length * 64,
193
+ };
194
+ }
195
+ function is(tapscript) {
196
+ return tapscript.type === TapscriptType.Multisig;
197
+ }
198
+ MultisigTapscript.is = is;
199
+ })(MultisigTapscript || (MultisigTapscript = {}));
200
+ /**
201
+ * Implements a relative timelock script that requires all specified pubkeys to sign
202
+ * after the relative timelock has expired. The timelock can be specified in blocks or seconds.
203
+ *
204
+ * This is the standard exit closure and it is also used for the sweep closure in vtxo trees.
205
+ */
206
+ export var CSVMultisigTapscript;
207
+ (function (CSVMultisigTapscript) {
208
+ function encode(params) {
209
+ for (const pubkey of params.pubkeys) {
210
+ if (pubkey.length !== 32) {
211
+ throw new Error(`Invalid pubkey length: expected 32, got ${pubkey.length}`);
212
+ }
213
+ }
214
+ const sequence = ScriptNum().encode(BigInt(bip68.encode(params.timelock.type === "blocks"
215
+ ? { blocks: Number(params.timelock.value) }
216
+ : { seconds: Number(params.timelock.value) })));
217
+ const asm = [sequence, "CHECKSEQUENCEVERIFY", "DROP"];
218
+ const multisigScript = MultisigTapscript.encode(params);
219
+ const script = new Uint8Array([
220
+ ...Script.encode(asm),
221
+ ...multisigScript.script,
222
+ ]);
223
+ return {
224
+ type: TapscriptType.CSVMultisig,
225
+ params,
226
+ script,
227
+ witnessSize: () => params.pubkeys.length * 64,
228
+ };
229
+ }
230
+ CSVMultisigTapscript.encode = encode;
231
+ function decode(script) {
232
+ if (script.length === 0) {
233
+ throw new Error("Failed to decode: script is empty");
234
+ }
235
+ const asm = Script.decode(script);
236
+ if (asm.length < 3) {
237
+ throw new Error(`Invalid script: too short (expected at least 3)`);
238
+ }
239
+ const sequence = asm[0];
240
+ if (typeof sequence === "string" || typeof sequence === "number") {
241
+ throw new Error("Invalid script: expected sequence number");
242
+ }
243
+ if (asm[1] !== "CHECKSEQUENCEVERIFY" || asm[2] !== "DROP") {
244
+ throw new Error("Invalid script: expected CHECKSEQUENCEVERIFY DROP");
245
+ }
246
+ const multisigScript = new Uint8Array(Script.encode(asm.slice(3)));
247
+ let multisig;
248
+ try {
249
+ multisig = MultisigTapscript.decode(multisigScript);
250
+ }
251
+ catch (error) {
252
+ throw new Error(`Invalid multisig script: ${error instanceof Error ? error.message : String(error)}`);
253
+ }
254
+ const sequenceNum = Number(ScriptNum().decode(sequence));
255
+ const decodedTimelock = bip68.decode(sequenceNum);
256
+ const timelock = decodedTimelock.blocks !== undefined
257
+ ? { type: "blocks", value: BigInt(decodedTimelock.blocks) }
258
+ : { type: "seconds", value: BigInt(decodedTimelock.seconds) };
259
+ const reconstructed = encode({
260
+ timelock,
261
+ ...multisig.params,
262
+ });
263
+ if (hex.encode(reconstructed.script) !== hex.encode(script)) {
264
+ throw new Error("Invalid script format: script reconstruction mismatch");
265
+ }
266
+ return {
267
+ type: TapscriptType.CSVMultisig,
268
+ params: {
269
+ timelock,
270
+ ...multisig.params,
271
+ },
272
+ script,
273
+ witnessSize: () => multisig.params.pubkeys.length * 64,
274
+ };
275
+ }
276
+ CSVMultisigTapscript.decode = decode;
277
+ function is(tapscript) {
278
+ return tapscript.type === TapscriptType.CSVMultisig;
279
+ }
280
+ CSVMultisigTapscript.is = is;
281
+ })(CSVMultisigTapscript || (CSVMultisigTapscript = {}));
282
+ /**
283
+ * Combines a condition script with an exit closure. The resulting script requires
284
+ * the condition to be met, followed by the standard exit closure requirements
285
+ * (timelock and signatures).
286
+ */
287
+ export var ConditionCSVMultisigTapscript;
288
+ (function (ConditionCSVMultisigTapscript) {
289
+ function encode(params) {
290
+ const script = new Uint8Array([
291
+ ...params.conditionScript,
292
+ ...Script.encode(["VERIFY"]),
293
+ ...CSVMultisigTapscript.encode(params).script,
294
+ ]);
295
+ return {
296
+ type: TapscriptType.ConditionCSVMultisig,
297
+ params,
298
+ script,
299
+ witnessSize: (conditionSize) => conditionSize + params.pubkeys.length * 64,
300
+ };
301
+ }
302
+ ConditionCSVMultisigTapscript.encode = encode;
303
+ function decode(script) {
304
+ if (script.length === 0) {
305
+ throw new Error("Failed to decode: script is empty");
306
+ }
307
+ const asm = Script.decode(script);
308
+ if (asm.length < 1) {
309
+ throw new Error(`Invalid script: too short (expected at least 1)`);
310
+ }
311
+ let verifyIndex = -1;
312
+ for (let i = asm.length - 1; i >= 0; i--) {
313
+ if (asm[i] === "VERIFY") {
314
+ verifyIndex = i;
315
+ }
316
+ }
317
+ if (verifyIndex === -1) {
318
+ throw new Error("Invalid script: missing VERIFY operation");
319
+ }
320
+ const conditionScript = new Uint8Array(Script.encode(asm.slice(0, verifyIndex)));
321
+ const csvMultisigScript = new Uint8Array(Script.encode(asm.slice(verifyIndex + 1)));
322
+ let csvMultisig;
323
+ try {
324
+ csvMultisig = CSVMultisigTapscript.decode(csvMultisigScript);
325
+ }
326
+ catch (error) {
327
+ throw new Error(`Invalid CSV multisig script: ${error instanceof Error ? error.message : String(error)}`);
328
+ }
329
+ const reconstructed = encode({
330
+ conditionScript,
331
+ ...csvMultisig.params,
332
+ });
333
+ if (hex.encode(reconstructed.script) !== hex.encode(script)) {
334
+ throw new Error("Invalid script format: script reconstruction mismatch");
335
+ }
336
+ return {
337
+ type: TapscriptType.ConditionCSVMultisig,
338
+ params: {
339
+ conditionScript,
340
+ ...csvMultisig.params,
341
+ },
342
+ script,
343
+ witnessSize: (conditionSize) => conditionSize + csvMultisig.params.pubkeys.length * 64,
344
+ };
345
+ }
346
+ ConditionCSVMultisigTapscript.decode = decode;
347
+ function is(tapscript) {
348
+ return tapscript.type === TapscriptType.ConditionCSVMultisig;
349
+ }
350
+ ConditionCSVMultisigTapscript.is = is;
351
+ })(ConditionCSVMultisigTapscript || (ConditionCSVMultisigTapscript = {}));
352
+ /**
353
+ * Combines a condition script with a forfeit closure. The resulting script requires
354
+ * the condition to be met, followed by the standard forfeit closure requirements
355
+ * (multi-signature).
356
+ */
357
+ export var ConditionMultisigTapscript;
358
+ (function (ConditionMultisigTapscript) {
359
+ function encode(params) {
360
+ const script = new Uint8Array([
361
+ ...params.conditionScript,
362
+ ...Script.encode(["VERIFY"]),
363
+ ...MultisigTapscript.encode(params).script,
364
+ ]);
365
+ return {
366
+ type: TapscriptType.ConditionMultisig,
367
+ params,
368
+ script,
369
+ witnessSize: (conditionSize) => conditionSize + params.pubkeys.length * 64,
370
+ };
371
+ }
372
+ ConditionMultisigTapscript.encode = encode;
373
+ function decode(script) {
374
+ if (script.length === 0) {
375
+ throw new Error("Failed to decode: script is empty");
376
+ }
377
+ const asm = Script.decode(script);
378
+ if (asm.length < 1) {
379
+ throw new Error(`Invalid script: too short (expected at least 1)`);
380
+ }
381
+ let verifyIndex = -1;
382
+ for (let i = asm.length - 1; i >= 0; i--) {
383
+ if (asm[i] === "VERIFY") {
384
+ verifyIndex = i;
385
+ }
386
+ }
387
+ if (verifyIndex === -1) {
388
+ throw new Error("Invalid script: missing VERIFY operation");
389
+ }
390
+ const conditionScript = new Uint8Array(Script.encode(asm.slice(0, verifyIndex)));
391
+ const multisigScript = new Uint8Array(Script.encode(asm.slice(verifyIndex + 1)));
392
+ let multisig;
393
+ try {
394
+ multisig = MultisigTapscript.decode(multisigScript);
395
+ }
396
+ catch (error) {
397
+ throw new Error(`Invalid multisig script: ${error instanceof Error ? error.message : String(error)}`);
398
+ }
399
+ const reconstructed = encode({
400
+ conditionScript,
401
+ ...multisig.params,
402
+ });
403
+ if (hex.encode(reconstructed.script) !== hex.encode(script)) {
404
+ throw new Error("Invalid script format: script reconstruction mismatch");
405
+ }
406
+ return {
407
+ type: TapscriptType.ConditionMultisig,
408
+ params: {
409
+ conditionScript,
410
+ ...multisig.params,
411
+ },
412
+ script,
413
+ witnessSize: (conditionSize) => conditionSize + multisig.params.pubkeys.length * 64,
414
+ };
415
+ }
416
+ ConditionMultisigTapscript.decode = decode;
417
+ function is(tapscript) {
418
+ return tapscript.type === TapscriptType.ConditionMultisig;
419
+ }
420
+ ConditionMultisigTapscript.is = is;
421
+ })(ConditionMultisigTapscript || (ConditionMultisigTapscript = {}));
422
+ /**
423
+ * Implements an absolute timelock (CLTV) script combined with a forfeit closure.
424
+ * The script requires waiting until a specific block height/timestamp before the
425
+ * forfeit closure conditions can be met.
426
+ */
427
+ export var CLTVMultisigTapscript;
428
+ (function (CLTVMultisigTapscript) {
429
+ function encode(params) {
430
+ const locktime = ScriptNum().encode(params.absoluteTimelock);
431
+ const asm = [locktime, "CHECKLOCKTIMEVERIFY", "DROP"];
432
+ const timelockedScript = Script.encode(asm);
433
+ const script = new Uint8Array([
434
+ ...timelockedScript,
435
+ ...MultisigTapscript.encode(params).script,
436
+ ]);
437
+ return {
438
+ type: TapscriptType.CLTVMultisig,
439
+ params,
440
+ script,
441
+ witnessSize: () => params.pubkeys.length * 64,
442
+ };
443
+ }
444
+ CLTVMultisigTapscript.encode = encode;
445
+ function decode(script) {
446
+ if (script.length === 0) {
447
+ throw new Error("Failed to decode: script is empty");
448
+ }
449
+ const asm = Script.decode(script);
450
+ if (asm.length < 3) {
451
+ throw new Error(`Invalid script: too short (expected at least 3)`);
452
+ }
453
+ const locktime = asm[0];
454
+ if (typeof locktime === "string" || typeof locktime === "number") {
455
+ throw new Error("Invalid script: expected locktime number");
456
+ }
457
+ if (asm[1] !== "CHECKLOCKTIMEVERIFY" || asm[2] !== "DROP") {
458
+ throw new Error("Invalid script: expected CHECKLOCKTIMEVERIFY DROP");
459
+ }
460
+ const multisigScript = new Uint8Array(Script.encode(asm.slice(3)));
461
+ let multisig;
462
+ try {
463
+ multisig = MultisigTapscript.decode(multisigScript);
464
+ }
465
+ catch (error) {
466
+ throw new Error(`Invalid multisig script: ${error instanceof Error ? error.message : String(error)}`);
467
+ }
468
+ const absoluteTimelock = ScriptNum().decode(locktime);
469
+ const reconstructed = encode({
470
+ absoluteTimelock,
471
+ ...multisig.params,
472
+ });
473
+ if (hex.encode(reconstructed.script) !== hex.encode(script)) {
474
+ throw new Error("Invalid script format: script reconstruction mismatch");
475
+ }
476
+ return {
477
+ type: TapscriptType.CLTVMultisig,
478
+ params: {
479
+ absoluteTimelock,
480
+ ...multisig.params,
481
+ },
482
+ script,
483
+ witnessSize: () => multisig.params.pubkeys.length * 64,
484
+ };
485
+ }
486
+ CLTVMultisigTapscript.decode = decode;
487
+ function is(tapscript) {
488
+ return tapscript.type === TapscriptType.CLTVMultisig;
489
+ }
490
+ CLTVMultisigTapscript.is = is;
491
+ })(CLTVMultisigTapscript || (CLTVMultisigTapscript = {}));
@@ -0,0 +1,81 @@
1
+ import { Script } from "@scure/btc-signer";
2
+ import { CLTVMultisigTapscript, ConditionCSVMultisigTapscript, ConditionMultisigTapscript, CSVMultisigTapscript, MultisigTapscript, } from './tapscript.js';
3
+ import { hex } from "@scure/base";
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)
12
+ export var VHTLC;
13
+ (function (VHTLC) {
14
+ class Script extends VtxoScript {
15
+ constructor(options) {
16
+ const { sender, receiver, server, preimageHash, refundLocktime, unilateralClaimDelay, unilateralRefundDelay, unilateralRefundWithoutReceiverDelay, } = options;
17
+ const conditionScript = preimageConditionScript(preimageHash);
18
+ const claimScript = ConditionMultisigTapscript.encode({
19
+ conditionScript,
20
+ pubkeys: [receiver, server],
21
+ }).script;
22
+ const refundScript = MultisigTapscript.encode({
23
+ pubkeys: [sender, receiver, server],
24
+ }).script;
25
+ const refundWithoutReceiverScript = CLTVMultisigTapscript.encode({
26
+ absoluteTimelock: refundLocktime,
27
+ pubkeys: [sender, server],
28
+ }).script;
29
+ const unilateralClaimScript = ConditionCSVMultisigTapscript.encode({
30
+ conditionScript,
31
+ timelock: unilateralClaimDelay,
32
+ pubkeys: [receiver],
33
+ }).script;
34
+ const unilateralRefundScript = CSVMultisigTapscript.encode({
35
+ timelock: unilateralRefundDelay,
36
+ pubkeys: [sender, receiver],
37
+ }).script;
38
+ const unilateralRefundWithoutReceiverScript = CSVMultisigTapscript.encode({
39
+ timelock: unilateralRefundWithoutReceiverDelay,
40
+ pubkeys: [sender],
41
+ }).script;
42
+ super([
43
+ claimScript,
44
+ refundScript,
45
+ refundWithoutReceiverScript,
46
+ unilateralClaimScript,
47
+ unilateralRefundScript,
48
+ unilateralRefundWithoutReceiverScript,
49
+ ]);
50
+ this.options = options;
51
+ this.claimScript = hex.encode(claimScript);
52
+ this.refundScript = hex.encode(refundScript);
53
+ this.refundWithoutReceiverScript = hex.encode(refundWithoutReceiverScript);
54
+ this.unilateralClaimScript = hex.encode(unilateralClaimScript);
55
+ this.unilateralRefundScript = hex.encode(unilateralRefundScript);
56
+ this.unilateralRefundWithoutReceiverScript = hex.encode(unilateralRefundWithoutReceiverScript);
57
+ }
58
+ claim() {
59
+ return this.findLeaf(this.claimScript);
60
+ }
61
+ refund() {
62
+ return this.findLeaf(this.refundScript);
63
+ }
64
+ refundWithoutReceiver() {
65
+ return this.findLeaf(this.refundWithoutReceiverScript);
66
+ }
67
+ unilateralClaim() {
68
+ return this.findLeaf(this.unilateralClaimScript);
69
+ }
70
+ unilateralRefund() {
71
+ return this.findLeaf(this.unilateralRefundScript);
72
+ }
73
+ unilateralRefundWithoutReceiver() {
74
+ return this.findLeaf(this.unilateralRefundWithoutReceiverScript);
75
+ }
76
+ }
77
+ VHTLC.Script = Script;
78
+ })(VHTLC || (VHTLC = {}));
79
+ function preimageConditionScript(preimageHash) {
80
+ return Script.encode(["HASH160", preimageHash, "EQUAL"]);
81
+ }