@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.
- package/README.md +156 -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 +41 -17
- package/dist/cjs/providers/ark.js +253 -317
- 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 +46 -14
- package/dist/cjs/script/vhtlc.js +27 -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 +58 -34
- package/dist/cjs/wallet/serviceWorker/worker.js +117 -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 +30 -12
- package/dist/esm/providers/ark.js +252 -317
- 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 +46 -14
- package/dist/esm/script/vhtlc.js +27 -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 +59 -35
- package/dist/esm/wallet/serviceWorker/worker.js +117 -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 +23 -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 +14 -15
- 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
|
@@ -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
|
|
70
|
-
*
|
|
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;
|
package/dist/cjs/script/vhtlc.js
CHANGED
|
@@ -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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
69
|
-
throw exports.
|
|
68
|
+
if (!this.graph)
|
|
69
|
+
throw exports.ErrMissingVtxoGraph;
|
|
70
70
|
if (!this.myNonces) {
|
|
71
71
|
this.myNonces = this.generateNonces();
|
|
72
72
|
}
|
|
73
|
-
const
|
|
74
|
-
for (const
|
|
75
|
-
|
|
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
|
|
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.
|
|
94
|
-
throw exports.
|
|
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 (
|
|
101
|
-
const
|
|
102
|
-
|
|
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.
|
|
120
|
-
throw exports.
|
|
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
|
|
124
|
-
const
|
|
125
|
-
|
|
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(
|
|
134
|
-
if (!this.
|
|
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
|
|
116
|
+
const myNonce = this.myNonces.get(g.txid);
|
|
141
117
|
if (!myNonce)
|
|
142
|
-
|
|
143
|
-
const aggNonce = this.aggregateNonces
|
|
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,
|
|
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 <
|
|
153
|
-
const prevout = getPrevOutput(finalKey, this.
|
|
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 =
|
|
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
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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,
|
|
193
|
-
//
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
if (
|
|
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
|
-
//
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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 =
|
|
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
|
+
}
|