@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.
- package/README.md +157 -174
- package/dist/cjs/arknote/index.js +61 -58
- package/dist/cjs/bip322/errors.js +13 -0
- package/dist/cjs/bip322/index.js +178 -0
- package/dist/cjs/forfeit.js +14 -25
- package/dist/cjs/identity/singleKey.js +68 -0
- package/dist/cjs/index.js +43 -17
- package/dist/cjs/providers/ark.js +261 -321
- package/dist/cjs/providers/indexer.js +525 -0
- package/dist/cjs/providers/onchain.js +193 -15
- package/dist/cjs/script/address.js +48 -17
- package/dist/cjs/script/base.js +120 -3
- package/dist/cjs/script/default.js +18 -4
- package/dist/cjs/script/tapscript.js +61 -20
- package/dist/cjs/script/vhtlc.js +85 -7
- package/dist/cjs/tree/signingSession.js +63 -106
- package/dist/cjs/tree/txTree.js +193 -0
- package/dist/cjs/tree/validation.js +79 -155
- package/dist/cjs/utils/anchor.js +35 -0
- package/dist/cjs/utils/arkTransaction.js +108 -0
- package/dist/cjs/utils/transactionHistory.js +84 -72
- package/dist/cjs/utils/txSizeEstimator.js +12 -0
- package/dist/cjs/utils/unknownFields.js +211 -0
- package/dist/cjs/wallet/index.js +12 -0
- package/dist/cjs/wallet/onchain.js +201 -0
- package/dist/cjs/wallet/ramps.js +95 -0
- package/dist/cjs/wallet/serviceWorker/db/vtxo/idb.js +32 -0
- package/dist/cjs/wallet/serviceWorker/request.js +15 -12
- package/dist/cjs/wallet/serviceWorker/response.js +22 -27
- package/dist/cjs/wallet/serviceWorker/utils.js +8 -0
- package/dist/cjs/wallet/serviceWorker/wallet.js +61 -34
- package/dist/cjs/wallet/serviceWorker/worker.js +120 -108
- package/dist/cjs/wallet/unroll.js +270 -0
- package/dist/cjs/wallet/wallet.js +701 -454
- package/dist/esm/arknote/index.js +61 -57
- package/dist/esm/bip322/errors.js +9 -0
- package/dist/esm/bip322/index.js +174 -0
- package/dist/esm/forfeit.js +15 -26
- package/dist/esm/identity/singleKey.js +64 -0
- package/dist/esm/index.js +31 -12
- package/dist/esm/providers/ark.js +259 -320
- package/dist/esm/providers/indexer.js +521 -0
- package/dist/esm/providers/onchain.js +193 -15
- package/dist/esm/script/address.js +48 -17
- package/dist/esm/script/base.js +120 -3
- package/dist/esm/script/default.js +18 -4
- package/dist/esm/script/tapscript.js +61 -20
- package/dist/esm/script/vhtlc.js +85 -7
- package/dist/esm/tree/signingSession.js +65 -108
- package/dist/esm/tree/txTree.js +189 -0
- package/dist/esm/tree/validation.js +75 -152
- package/dist/esm/utils/anchor.js +31 -0
- package/dist/esm/utils/arkTransaction.js +105 -0
- package/dist/esm/utils/transactionHistory.js +84 -72
- package/dist/esm/utils/txSizeEstimator.js +12 -0
- package/dist/esm/utils/unknownFields.js +173 -0
- package/dist/esm/wallet/index.js +9 -0
- package/dist/esm/wallet/onchain.js +196 -0
- package/dist/esm/wallet/ramps.js +91 -0
- package/dist/esm/wallet/serviceWorker/db/vtxo/idb.js +32 -0
- package/dist/esm/wallet/serviceWorker/request.js +15 -12
- package/dist/esm/wallet/serviceWorker/response.js +22 -27
- package/dist/esm/wallet/serviceWorker/utils.js +8 -0
- package/dist/esm/wallet/serviceWorker/wallet.js +62 -35
- package/dist/esm/wallet/serviceWorker/worker.js +120 -108
- package/dist/esm/wallet/unroll.js +267 -0
- package/dist/esm/wallet/wallet.js +674 -461
- package/dist/types/arknote/index.d.ts +40 -13
- package/dist/types/bip322/errors.d.ts +6 -0
- package/dist/types/bip322/index.d.ts +57 -0
- package/dist/types/forfeit.d.ts +2 -14
- package/dist/types/identity/singleKey.d.ts +27 -0
- package/dist/types/index.d.ts +24 -12
- package/dist/types/providers/ark.d.ts +114 -95
- package/dist/types/providers/indexer.d.ts +186 -0
- package/dist/types/providers/onchain.d.ts +41 -11
- package/dist/types/script/address.d.ts +26 -2
- package/dist/types/script/base.d.ts +13 -3
- package/dist/types/script/default.d.ts +22 -0
- package/dist/types/script/tapscript.d.ts +61 -5
- package/dist/types/script/vhtlc.d.ts +27 -0
- package/dist/types/tree/signingSession.d.ts +5 -5
- package/dist/types/tree/txTree.d.ts +28 -0
- package/dist/types/tree/validation.d.ts +15 -22
- package/dist/types/utils/anchor.d.ts +19 -0
- package/dist/types/utils/arkTransaction.d.ts +27 -0
- package/dist/types/utils/transactionHistory.d.ts +7 -1
- package/dist/types/utils/txSizeEstimator.d.ts +3 -0
- package/dist/types/utils/unknownFields.d.ts +83 -0
- package/dist/types/wallet/index.d.ts +51 -50
- package/dist/types/wallet/onchain.d.ts +49 -0
- package/dist/types/wallet/ramps.d.ts +32 -0
- package/dist/types/wallet/serviceWorker/db/vtxo/idb.d.ts +2 -0
- package/dist/types/wallet/serviceWorker/db/vtxo/index.d.ts +2 -0
- package/dist/types/wallet/serviceWorker/request.d.ts +14 -16
- package/dist/types/wallet/serviceWorker/response.d.ts +17 -19
- package/dist/types/wallet/serviceWorker/utils.d.ts +8 -0
- package/dist/types/wallet/serviceWorker/wallet.d.ts +36 -8
- package/dist/types/wallet/serviceWorker/worker.d.ts +7 -3
- package/dist/types/wallet/unroll.d.ts +102 -0
- package/dist/types/wallet/wallet.d.ts +71 -25
- package/package.json +37 -35
- package/dist/cjs/identity/inMemoryKey.js +0 -40
- package/dist/cjs/tree/vtxoTree.js +0 -231
- package/dist/cjs/utils/coinselect.js +0 -73
- package/dist/cjs/utils/psbt.js +0 -137
- package/dist/esm/identity/inMemoryKey.js +0 -36
- package/dist/esm/tree/vtxoTree.js +0 -191
- package/dist/esm/utils/coinselect.js +0 -69
- package/dist/esm/utils/psbt.js +0 -131
- package/dist/types/identity/inMemoryKey.d.ts +0 -12
- package/dist/types/tree/vtxoTree.d.ts +0 -33
- package/dist/types/utils/coinselect.d.ts +0 -21
- 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
|
|
33
|
-
*
|
|
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 =
|
|
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 = [
|
|
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(
|
|
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 =
|
|
431
|
-
const asm = [
|
|
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 =
|
|
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;
|
package/dist/esm/script/vhtlc.js
CHANGED
|
@@ -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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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 {
|
|
3
|
-
import {
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
32
|
-
throw
|
|
31
|
+
if (!this.graph)
|
|
32
|
+
throw ErrMissingVtxoGraph;
|
|
33
33
|
if (!this.myNonces) {
|
|
34
34
|
this.myNonces = this.generateNonces();
|
|
35
35
|
}
|
|
36
|
-
const
|
|
37
|
-
for (const
|
|
38
|
-
|
|
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
|
|
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.
|
|
57
|
-
throw
|
|
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 (
|
|
64
|
-
const
|
|
65
|
-
|
|
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.
|
|
83
|
-
throw
|
|
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
|
|
87
|
-
const
|
|
88
|
-
|
|
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(
|
|
97
|
-
if (!this.
|
|
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
|
|
79
|
+
const myNonce = this.myNonces.get(g.txid);
|
|
104
80
|
if (!myNonce)
|
|
105
|
-
|
|
106
|
-
const aggNonce = this.aggregateNonces
|
|
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 =
|
|
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 <
|
|
116
|
-
const prevout = getPrevOutput(finalKey, this.
|
|
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 =
|
|
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
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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,
|
|
155
|
-
//
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
if (
|
|
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
|
-
//
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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 =
|
|
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)
|