@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.
- package/README.md +312 -0
- package/dist/cjs/arknote/index.js +86 -0
- package/dist/cjs/forfeit.js +38 -0
- package/dist/cjs/identity/inMemoryKey.js +40 -0
- package/dist/cjs/identity/index.js +2 -0
- package/dist/cjs/index.js +48 -0
- package/dist/cjs/musig2/index.js +10 -0
- package/dist/cjs/musig2/keys.js +57 -0
- package/dist/cjs/musig2/nonces.js +44 -0
- package/dist/cjs/musig2/sign.js +102 -0
- package/dist/cjs/networks.js +26 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/providers/ark.js +530 -0
- package/dist/cjs/providers/onchain.js +61 -0
- package/dist/cjs/script/address.js +45 -0
- package/dist/cjs/script/base.js +51 -0
- package/dist/cjs/script/default.js +40 -0
- package/dist/cjs/script/tapscript.js +528 -0
- package/dist/cjs/script/vhtlc.js +84 -0
- package/dist/cjs/tree/signingSession.js +238 -0
- package/dist/cjs/tree/validation.js +184 -0
- package/dist/cjs/tree/vtxoTree.js +197 -0
- package/dist/cjs/utils/bip21.js +114 -0
- package/dist/cjs/utils/coinselect.js +73 -0
- package/dist/cjs/utils/psbt.js +124 -0
- package/dist/cjs/utils/transactionHistory.js +148 -0
- package/dist/cjs/utils/txSizeEstimator.js +95 -0
- package/dist/cjs/wallet/index.js +8 -0
- package/dist/cjs/wallet/serviceWorker/db/vtxo/idb.js +153 -0
- package/dist/cjs/wallet/serviceWorker/db/vtxo/index.js +2 -0
- package/dist/cjs/wallet/serviceWorker/request.js +75 -0
- package/dist/cjs/wallet/serviceWorker/response.js +187 -0
- package/dist/cjs/wallet/serviceWorker/wallet.js +332 -0
- package/dist/cjs/wallet/serviceWorker/worker.js +452 -0
- package/dist/cjs/wallet/wallet.js +720 -0
- package/dist/esm/arknote/index.js +81 -0
- package/dist/esm/forfeit.js +35 -0
- package/dist/esm/identity/inMemoryKey.js +36 -0
- package/dist/esm/identity/index.js +1 -0
- package/dist/esm/index.js +39 -0
- package/dist/esm/musig2/index.js +3 -0
- package/dist/esm/musig2/keys.js +21 -0
- package/dist/esm/musig2/nonces.js +8 -0
- package/dist/esm/musig2/sign.js +63 -0
- package/dist/esm/networks.js +22 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/providers/ark.js +526 -0
- package/dist/esm/providers/onchain.js +57 -0
- package/dist/esm/script/address.js +41 -0
- package/dist/esm/script/base.js +46 -0
- package/dist/esm/script/default.js +37 -0
- package/dist/esm/script/tapscript.js +491 -0
- package/dist/esm/script/vhtlc.js +81 -0
- package/dist/esm/tree/signingSession.js +200 -0
- package/dist/esm/tree/validation.js +179 -0
- package/dist/esm/tree/vtxoTree.js +157 -0
- package/dist/esm/utils/bip21.js +110 -0
- package/dist/esm/utils/coinselect.js +69 -0
- package/dist/esm/utils/psbt.js +118 -0
- package/dist/esm/utils/transactionHistory.js +145 -0
- package/dist/esm/utils/txSizeEstimator.js +91 -0
- package/dist/esm/wallet/index.js +5 -0
- package/dist/esm/wallet/serviceWorker/db/vtxo/idb.js +149 -0
- package/dist/esm/wallet/serviceWorker/db/vtxo/index.js +1 -0
- package/dist/esm/wallet/serviceWorker/request.js +72 -0
- package/dist/esm/wallet/serviceWorker/response.js +184 -0
- package/dist/esm/wallet/serviceWorker/wallet.js +328 -0
- package/dist/esm/wallet/serviceWorker/worker.js +448 -0
- package/dist/esm/wallet/wallet.js +716 -0
- package/dist/types/arknote/index.d.ts +17 -0
- package/dist/types/forfeit.d.ts +15 -0
- package/dist/types/identity/inMemoryKey.d.ts +12 -0
- package/dist/types/identity/index.d.ts +7 -0
- package/dist/types/index.d.ts +22 -0
- package/dist/types/musig2/index.d.ts +4 -0
- package/dist/types/musig2/keys.d.ts +9 -0
- package/dist/types/musig2/nonces.d.ts +13 -0
- package/dist/types/musig2/sign.d.ts +27 -0
- package/dist/types/networks.d.ts +16 -0
- package/dist/types/providers/ark.d.ts +126 -0
- package/dist/types/providers/onchain.d.ts +36 -0
- package/dist/types/script/address.d.ts +10 -0
- package/dist/types/script/base.d.ts +26 -0
- package/dist/types/script/default.d.ts +19 -0
- package/dist/types/script/tapscript.d.ts +94 -0
- package/dist/types/script/vhtlc.d.ts +31 -0
- package/dist/types/tree/signingSession.d.ts +32 -0
- package/dist/types/tree/validation.d.ts +22 -0
- package/dist/types/tree/vtxoTree.d.ts +32 -0
- package/dist/types/utils/bip21.d.ts +21 -0
- package/dist/types/utils/coinselect.d.ts +21 -0
- package/dist/types/utils/psbt.d.ts +11 -0
- package/dist/types/utils/transactionHistory.d.ts +2 -0
- package/dist/types/utils/txSizeEstimator.d.ts +27 -0
- package/dist/types/wallet/index.d.ts +122 -0
- package/dist/types/wallet/serviceWorker/db/vtxo/idb.d.ts +18 -0
- package/dist/types/wallet/serviceWorker/db/vtxo/index.d.ts +12 -0
- package/dist/types/wallet/serviceWorker/request.d.ts +68 -0
- package/dist/types/wallet/serviceWorker/response.d.ts +107 -0
- package/dist/types/wallet/serviceWorker/wallet.d.ts +23 -0
- package/dist/types/wallet/serviceWorker/worker.d.ts +26 -0
- package/dist/types/wallet/wallet.d.ts +42 -0
- package/package.json +88 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.TreeSignerSession = exports.ErrMissingAggregateKey = exports.ErrMissingVtxoTree = void 0;
|
|
37
|
+
exports.validateTreeSigs = validateTreeSigs;
|
|
38
|
+
const musig2 = __importStar(require("../musig2"));
|
|
39
|
+
const vtxoTree_1 = require("./vtxoTree");
|
|
40
|
+
const btc_signer_1 = require("@scure/btc-signer");
|
|
41
|
+
const base_1 = require("@scure/base");
|
|
42
|
+
const secp256k1_1 = require("@noble/curves/secp256k1");
|
|
43
|
+
const utils_1 = require("@scure/btc-signer/utils");
|
|
44
|
+
exports.ErrMissingVtxoTree = new Error("missing vtxo tree");
|
|
45
|
+
exports.ErrMissingAggregateKey = new Error("missing aggregate key");
|
|
46
|
+
class TreeSignerSession {
|
|
47
|
+
constructor(secretKey) {
|
|
48
|
+
this.secretKey = secretKey;
|
|
49
|
+
this.myNonces = null;
|
|
50
|
+
this.aggregateNonces = null;
|
|
51
|
+
this.tree = null;
|
|
52
|
+
this.scriptRoot = null;
|
|
53
|
+
this.rootSharedOutputAmount = null;
|
|
54
|
+
}
|
|
55
|
+
static random() {
|
|
56
|
+
const secretKey = (0, utils_1.randomPrivateKeyBytes)();
|
|
57
|
+
return new TreeSignerSession(secretKey);
|
|
58
|
+
}
|
|
59
|
+
init(tree, scriptRoot, rootInputAmount) {
|
|
60
|
+
this.tree = tree;
|
|
61
|
+
this.scriptRoot = scriptRoot;
|
|
62
|
+
this.rootSharedOutputAmount = rootInputAmount;
|
|
63
|
+
}
|
|
64
|
+
getPublicKey() {
|
|
65
|
+
return secp256k1_1.secp256k1.getPublicKey(this.secretKey);
|
|
66
|
+
}
|
|
67
|
+
getNonces() {
|
|
68
|
+
if (!this.tree)
|
|
69
|
+
throw exports.ErrMissingVtxoTree;
|
|
70
|
+
if (!this.myNonces) {
|
|
71
|
+
this.myNonces = this.generateNonces();
|
|
72
|
+
}
|
|
73
|
+
const nonces = [];
|
|
74
|
+
for (const levelNonces of this.myNonces) {
|
|
75
|
+
const levelPubNonces = [];
|
|
76
|
+
for (const nonce of levelNonces) {
|
|
77
|
+
if (!nonce) {
|
|
78
|
+
levelPubNonces.push(null);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
levelPubNonces.push({ pubNonce: nonce.pubNonce });
|
|
82
|
+
}
|
|
83
|
+
nonces.push(levelPubNonces);
|
|
84
|
+
}
|
|
85
|
+
return nonces;
|
|
86
|
+
}
|
|
87
|
+
setAggregatedNonces(nonces) {
|
|
88
|
+
if (this.aggregateNonces)
|
|
89
|
+
throw new Error("nonces already set");
|
|
90
|
+
this.aggregateNonces = nonces;
|
|
91
|
+
}
|
|
92
|
+
sign() {
|
|
93
|
+
if (!this.tree)
|
|
94
|
+
throw exports.ErrMissingVtxoTree;
|
|
95
|
+
if (!this.aggregateNonces)
|
|
96
|
+
throw new Error("nonces not set");
|
|
97
|
+
if (!this.myNonces)
|
|
98
|
+
throw new Error("nonces not generated");
|
|
99
|
+
const sigs = [];
|
|
100
|
+
for (let levelIndex = 0; levelIndex < this.tree.levels.length; levelIndex++) {
|
|
101
|
+
const levelSigs = [];
|
|
102
|
+
const level = this.tree.levels[levelIndex];
|
|
103
|
+
for (let nodeIndex = 0; nodeIndex < level.length; nodeIndex++) {
|
|
104
|
+
const node = level[nodeIndex];
|
|
105
|
+
const tx = btc_signer_1.Transaction.fromPSBT(base_1.base64.decode(node.tx));
|
|
106
|
+
const sig = this.signPartial(tx, levelIndex, nodeIndex);
|
|
107
|
+
if (sig) {
|
|
108
|
+
levelSigs.push(sig);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
levelSigs.push(null);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
sigs.push(levelSigs);
|
|
115
|
+
}
|
|
116
|
+
return sigs;
|
|
117
|
+
}
|
|
118
|
+
generateNonces() {
|
|
119
|
+
if (!this.tree)
|
|
120
|
+
throw exports.ErrMissingVtxoTree;
|
|
121
|
+
const myNonces = [];
|
|
122
|
+
const publicKey = secp256k1_1.secp256k1.getPublicKey(this.secretKey);
|
|
123
|
+
for (const level of this.tree.levels) {
|
|
124
|
+
const levelNonces = [];
|
|
125
|
+
for (let i = 0; i < level.length; i++) {
|
|
126
|
+
const nonces = musig2.generateNonces(publicKey);
|
|
127
|
+
levelNonces.push(nonces);
|
|
128
|
+
}
|
|
129
|
+
myNonces.push(levelNonces);
|
|
130
|
+
}
|
|
131
|
+
return myNonces;
|
|
132
|
+
}
|
|
133
|
+
signPartial(tx, levelIndex, nodeIndex) {
|
|
134
|
+
if (!this.tree || !this.scriptRoot || !this.rootSharedOutputAmount) {
|
|
135
|
+
throw TreeSignerSession.NOT_INITIALIZED;
|
|
136
|
+
}
|
|
137
|
+
if (!this.myNonces || !this.aggregateNonces) {
|
|
138
|
+
throw new Error("session not properly initialized");
|
|
139
|
+
}
|
|
140
|
+
const myNonce = this.myNonces[levelIndex][nodeIndex];
|
|
141
|
+
if (!myNonce)
|
|
142
|
+
return null;
|
|
143
|
+
const aggNonce = this.aggregateNonces[levelIndex][nodeIndex];
|
|
144
|
+
if (!aggNonce)
|
|
145
|
+
throw new Error("missing aggregate nonce");
|
|
146
|
+
const prevoutAmounts = [];
|
|
147
|
+
const prevoutScripts = [];
|
|
148
|
+
const cosigners = (0, vtxoTree_1.getCosignerKeys)(tx);
|
|
149
|
+
const { finalKey } = musig2.aggregateKeys(cosigners, true, {
|
|
150
|
+
taprootTweak: this.scriptRoot,
|
|
151
|
+
});
|
|
152
|
+
for (let inputIndex = 0; inputIndex < tx.inputsLength; inputIndex++) {
|
|
153
|
+
const prevout = getPrevOutput(finalKey, this.tree, this.rootSharedOutputAmount, tx);
|
|
154
|
+
prevoutAmounts.push(prevout.amount);
|
|
155
|
+
prevoutScripts.push(prevout.script);
|
|
156
|
+
}
|
|
157
|
+
const message = tx.preimageWitnessV1(0, // always first input
|
|
158
|
+
prevoutScripts, btc_signer_1.SigHash.DEFAULT, prevoutAmounts);
|
|
159
|
+
return musig2.sign(myNonce.secNonce, this.secretKey, aggNonce.pubNonce, cosigners, message, {
|
|
160
|
+
taprootTweak: this.scriptRoot,
|
|
161
|
+
sortKeys: true,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
exports.TreeSignerSession = TreeSignerSession;
|
|
166
|
+
TreeSignerSession.NOT_INITIALIZED = new Error("session not initialized, call init method");
|
|
167
|
+
// Helper function to validate tree signatures
|
|
168
|
+
async function validateTreeSigs(finalAggregatedKey, sharedOutputAmount, vtxoTree) {
|
|
169
|
+
// Iterate through each level of the tree
|
|
170
|
+
for (const level of vtxoTree.levels) {
|
|
171
|
+
for (const node of level) {
|
|
172
|
+
// Parse the transaction
|
|
173
|
+
const tx = btc_signer_1.Transaction.fromPSBT(base_1.base64.decode(node.tx));
|
|
174
|
+
const input = tx.getInput(0);
|
|
175
|
+
// Check if input has signature
|
|
176
|
+
if (!input.tapKeySig) {
|
|
177
|
+
throw new Error("unsigned tree input");
|
|
178
|
+
}
|
|
179
|
+
// Get the previous output information
|
|
180
|
+
const prevout = getPrevOutput(finalAggregatedKey, vtxoTree, sharedOutputAmount, tx);
|
|
181
|
+
// Calculate the message that was signed
|
|
182
|
+
const message = tx.preimageWitnessV1(0, // always first input
|
|
183
|
+
[prevout.script], btc_signer_1.SigHash.DEFAULT, [prevout.amount]);
|
|
184
|
+
// Verify the signature
|
|
185
|
+
const isValid = secp256k1_1.schnorr.verify(input.tapKeySig, message, finalAggregatedKey);
|
|
186
|
+
if (!isValid) {
|
|
187
|
+
throw new Error("invalid signature");
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function getPrevOutput(finalKey, vtxoTree, sharedOutputAmount, partial) {
|
|
193
|
+
// Generate P2TR script
|
|
194
|
+
const pkScript = btc_signer_1.Script.encode(["OP_1", finalKey.slice(1)]);
|
|
195
|
+
// Get root node
|
|
196
|
+
const rootNode = vtxoTree.levels[0][0];
|
|
197
|
+
if (!rootNode)
|
|
198
|
+
throw new Error("empty vtxo tree");
|
|
199
|
+
const input = partial.getInput(0);
|
|
200
|
+
if (!input.txid)
|
|
201
|
+
throw new Error("missing input txid");
|
|
202
|
+
const parentTxID = base_1.hex.encode(input.txid);
|
|
203
|
+
// Check if parent is root
|
|
204
|
+
if (rootNode.parentTxid === parentTxID) {
|
|
205
|
+
return {
|
|
206
|
+
amount: sharedOutputAmount,
|
|
207
|
+
script: pkScript,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
// Search for parent in tree
|
|
211
|
+
let parent = null;
|
|
212
|
+
for (const level of vtxoTree.levels) {
|
|
213
|
+
for (const node of level) {
|
|
214
|
+
if (node.txid === parentTxID) {
|
|
215
|
+
parent = node;
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (parent)
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
if (!parent) {
|
|
223
|
+
throw new Error("parent tx not found");
|
|
224
|
+
}
|
|
225
|
+
// Parse parent tx
|
|
226
|
+
const parentTx = btc_signer_1.Transaction.fromPSBT(base_1.base64.decode(parent.tx));
|
|
227
|
+
if (!input.index)
|
|
228
|
+
throw new Error("missing input index");
|
|
229
|
+
const parentOutput = parentTx.getOutput(input.index);
|
|
230
|
+
if (!parentOutput)
|
|
231
|
+
throw new Error("parent output not found");
|
|
232
|
+
if (!parentOutput.amount)
|
|
233
|
+
throw new Error("parent output amount not found");
|
|
234
|
+
return {
|
|
235
|
+
amount: parentOutput.amount,
|
|
236
|
+
script: pkScript,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ErrInvalidNodeTransaction = exports.ErrInvalidRootTransaction = exports.ErrInvalidControlBlock = exports.ErrInternalKey = exports.ErrInvalidTaprootScript = exports.ErrLeafChildren = exports.ErrParentTxidInput = exports.ErrNodeTxidDifferent = exports.ErrNodeParentTxidEmpty = exports.ErrNodeTxidEmpty = exports.ErrNodeTxEmpty = exports.ErrNoLeaves = exports.ErrInvalidAmount = exports.ErrWrongSettlementTxid = exports.ErrNumberOfInputs = exports.ErrInvalidRootLevel = exports.ErrEmptyTree = exports.ErrInvalidSettlementTxOutputs = exports.ErrInvalidSettlementTx = void 0;
|
|
4
|
+
exports.validateConnectorsTree = validateConnectorsTree;
|
|
5
|
+
exports.validateVtxoTree = validateVtxoTree;
|
|
6
|
+
const base_1 = require("@scure/base");
|
|
7
|
+
const btc_signer_1 = require("@scure/btc-signer");
|
|
8
|
+
const base_2 = require("@scure/base");
|
|
9
|
+
const utils_1 = require("@scure/btc-signer/utils");
|
|
10
|
+
const musig2_1 = require("../musig2");
|
|
11
|
+
const vtxoTree_1 = require("./vtxoTree");
|
|
12
|
+
exports.ErrInvalidSettlementTx = new vtxoTree_1.TxTreeError("invalid settlement transaction");
|
|
13
|
+
exports.ErrInvalidSettlementTxOutputs = new vtxoTree_1.TxTreeError("invalid settlement transaction outputs");
|
|
14
|
+
exports.ErrEmptyTree = new vtxoTree_1.TxTreeError("empty tree");
|
|
15
|
+
exports.ErrInvalidRootLevel = new vtxoTree_1.TxTreeError("invalid root level");
|
|
16
|
+
exports.ErrNumberOfInputs = new vtxoTree_1.TxTreeError("invalid number of inputs");
|
|
17
|
+
exports.ErrWrongSettlementTxid = new vtxoTree_1.TxTreeError("wrong settlement txid");
|
|
18
|
+
exports.ErrInvalidAmount = new vtxoTree_1.TxTreeError("invalid amount");
|
|
19
|
+
exports.ErrNoLeaves = new vtxoTree_1.TxTreeError("no leaves");
|
|
20
|
+
exports.ErrNodeTxEmpty = new vtxoTree_1.TxTreeError("node transaction empty");
|
|
21
|
+
exports.ErrNodeTxidEmpty = new vtxoTree_1.TxTreeError("node txid empty");
|
|
22
|
+
exports.ErrNodeParentTxidEmpty = new vtxoTree_1.TxTreeError("node parent txid empty");
|
|
23
|
+
exports.ErrNodeTxidDifferent = new vtxoTree_1.TxTreeError("node txid different");
|
|
24
|
+
exports.ErrParentTxidInput = new vtxoTree_1.TxTreeError("parent txid input mismatch");
|
|
25
|
+
exports.ErrLeafChildren = new vtxoTree_1.TxTreeError("leaf node has children");
|
|
26
|
+
exports.ErrInvalidTaprootScript = new vtxoTree_1.TxTreeError("invalid taproot script");
|
|
27
|
+
exports.ErrInternalKey = new vtxoTree_1.TxTreeError("invalid internal key");
|
|
28
|
+
exports.ErrInvalidControlBlock = new vtxoTree_1.TxTreeError("invalid control block");
|
|
29
|
+
exports.ErrInvalidRootTransaction = new vtxoTree_1.TxTreeError("invalid root transaction");
|
|
30
|
+
exports.ErrInvalidNodeTransaction = new vtxoTree_1.TxTreeError("invalid node transaction");
|
|
31
|
+
const SHARED_OUTPUT_INDEX = 0;
|
|
32
|
+
const CONNECTORS_OUTPUT_INDEX = 1;
|
|
33
|
+
function validateConnectorsTree(settlementTxB64, connectorsTree) {
|
|
34
|
+
connectorsTree.validate();
|
|
35
|
+
const rootNode = connectorsTree.root();
|
|
36
|
+
if (!rootNode)
|
|
37
|
+
throw exports.ErrEmptyTree;
|
|
38
|
+
const rootTx = btc_signer_1.Transaction.fromPSBT(base_2.base64.decode(rootNode.tx));
|
|
39
|
+
if (rootTx.inputsLength !== 1)
|
|
40
|
+
throw exports.ErrNumberOfInputs;
|
|
41
|
+
const rootInput = rootTx.getInput(0);
|
|
42
|
+
const settlementTx = btc_signer_1.Transaction.fromPSBT(base_2.base64.decode(settlementTxB64));
|
|
43
|
+
if (settlementTx.outputsLength <= CONNECTORS_OUTPUT_INDEX)
|
|
44
|
+
throw exports.ErrInvalidSettlementTxOutputs;
|
|
45
|
+
const expectedRootTxid = base_1.hex.encode((0, utils_1.sha256x2)(settlementTx.toBytes(true)).reverse());
|
|
46
|
+
if (!rootInput.txid)
|
|
47
|
+
throw exports.ErrWrongSettlementTxid;
|
|
48
|
+
if (base_1.hex.encode(rootInput.txid) !== expectedRootTxid)
|
|
49
|
+
throw exports.ErrWrongSettlementTxid;
|
|
50
|
+
if (rootInput.index !== CONNECTORS_OUTPUT_INDEX)
|
|
51
|
+
throw exports.ErrWrongSettlementTxid;
|
|
52
|
+
}
|
|
53
|
+
function validateVtxoTree(settlementTx, vtxoTree, sweepTapTreeRoot) {
|
|
54
|
+
vtxoTree.validate();
|
|
55
|
+
// Parse settlement transaction
|
|
56
|
+
let settlementTransaction;
|
|
57
|
+
try {
|
|
58
|
+
settlementTransaction = btc_signer_1.Transaction.fromPSBT(base_2.base64.decode(settlementTx));
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
throw exports.ErrInvalidSettlementTx;
|
|
62
|
+
}
|
|
63
|
+
if (settlementTransaction.outputsLength <= SHARED_OUTPUT_INDEX) {
|
|
64
|
+
throw exports.ErrInvalidSettlementTxOutputs;
|
|
65
|
+
}
|
|
66
|
+
const sharedOutput = settlementTransaction.getOutput(SHARED_OUTPUT_INDEX);
|
|
67
|
+
if (!sharedOutput?.amount)
|
|
68
|
+
throw exports.ErrInvalidSettlementTxOutputs;
|
|
69
|
+
const sharedOutputAmount = sharedOutput.amount;
|
|
70
|
+
const nbNodes = vtxoTree.numberOfNodes();
|
|
71
|
+
if (nbNodes === 0) {
|
|
72
|
+
throw exports.ErrEmptyTree;
|
|
73
|
+
}
|
|
74
|
+
if (vtxoTree.levels[0].length !== 1) {
|
|
75
|
+
throw exports.ErrInvalidRootLevel;
|
|
76
|
+
}
|
|
77
|
+
// Check root input is connected to settlement tx
|
|
78
|
+
const rootNode = vtxoTree.levels[0][0];
|
|
79
|
+
let rootTx;
|
|
80
|
+
try {
|
|
81
|
+
rootTx = btc_signer_1.Transaction.fromPSBT(base_2.base64.decode(rootNode.tx));
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
throw exports.ErrInvalidRootTransaction;
|
|
85
|
+
}
|
|
86
|
+
if (rootTx.inputsLength !== 1) {
|
|
87
|
+
throw exports.ErrNumberOfInputs;
|
|
88
|
+
}
|
|
89
|
+
const rootInput = rootTx.getInput(0);
|
|
90
|
+
if (!rootInput.txid || rootInput.index === undefined)
|
|
91
|
+
throw exports.ErrWrongSettlementTxid;
|
|
92
|
+
const settlementTxid = base_1.hex.encode((0, utils_1.sha256x2)(settlementTransaction.toBytes(true)).reverse());
|
|
93
|
+
if (base_1.hex.encode(rootInput.txid) !== settlementTxid ||
|
|
94
|
+
rootInput.index !== SHARED_OUTPUT_INDEX) {
|
|
95
|
+
throw exports.ErrWrongSettlementTxid;
|
|
96
|
+
}
|
|
97
|
+
// Check root output amounts
|
|
98
|
+
let sumRootValue = 0n;
|
|
99
|
+
for (let i = 0; i < rootTx.outputsLength; i++) {
|
|
100
|
+
const output = rootTx.getOutput(i);
|
|
101
|
+
if (!output?.amount)
|
|
102
|
+
continue;
|
|
103
|
+
sumRootValue += output.amount;
|
|
104
|
+
}
|
|
105
|
+
if (sumRootValue >= sharedOutputAmount) {
|
|
106
|
+
throw exports.ErrInvalidAmount;
|
|
107
|
+
}
|
|
108
|
+
if (vtxoTree.leaves().length === 0) {
|
|
109
|
+
throw exports.ErrNoLeaves;
|
|
110
|
+
}
|
|
111
|
+
// Validate each node in the tree
|
|
112
|
+
for (const level of vtxoTree.levels) {
|
|
113
|
+
for (const node of level) {
|
|
114
|
+
validateNode(vtxoTree, node, sweepTapTreeRoot);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function validateNode(vtxoTree, node, tapTreeRoot) {
|
|
119
|
+
if (!node.tx)
|
|
120
|
+
throw exports.ErrNodeTxEmpty;
|
|
121
|
+
if (!node.txid)
|
|
122
|
+
throw exports.ErrNodeTxidEmpty;
|
|
123
|
+
if (!node.parentTxid)
|
|
124
|
+
throw exports.ErrNodeParentTxidEmpty;
|
|
125
|
+
// Parse node transaction
|
|
126
|
+
let tx;
|
|
127
|
+
try {
|
|
128
|
+
tx = btc_signer_1.Transaction.fromPSBT(base_2.base64.decode(node.tx));
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
throw exports.ErrInvalidNodeTransaction;
|
|
132
|
+
}
|
|
133
|
+
const txid = base_1.hex.encode((0, utils_1.sha256x2)(tx.toBytes(true)).reverse());
|
|
134
|
+
if (txid !== node.txid) {
|
|
135
|
+
throw exports.ErrNodeTxidDifferent;
|
|
136
|
+
}
|
|
137
|
+
if (tx.inputsLength !== 1) {
|
|
138
|
+
throw exports.ErrNumberOfInputs;
|
|
139
|
+
}
|
|
140
|
+
const input = tx.getInput(0);
|
|
141
|
+
if (!input.txid)
|
|
142
|
+
throw exports.ErrParentTxidInput;
|
|
143
|
+
if (base_1.hex.encode(input.txid) !== node.parentTxid) {
|
|
144
|
+
throw exports.ErrParentTxidInput;
|
|
145
|
+
}
|
|
146
|
+
const children = vtxoTree.children(node.txid);
|
|
147
|
+
if (node.leaf && children.length >= 1) {
|
|
148
|
+
throw exports.ErrLeafChildren;
|
|
149
|
+
}
|
|
150
|
+
// Validate each child
|
|
151
|
+
for (let childIndex = 0; childIndex < children.length; childIndex++) {
|
|
152
|
+
const child = children[childIndex];
|
|
153
|
+
const childTx = btc_signer_1.Transaction.fromPSBT(base_2.base64.decode(child.tx));
|
|
154
|
+
const parentOutput = tx.getOutput(childIndex);
|
|
155
|
+
if (!parentOutput?.script)
|
|
156
|
+
throw exports.ErrInvalidTaprootScript;
|
|
157
|
+
const previousScriptKey = parentOutput.script.slice(2);
|
|
158
|
+
if (previousScriptKey.length !== 32) {
|
|
159
|
+
throw exports.ErrInvalidTaprootScript;
|
|
160
|
+
}
|
|
161
|
+
// Get cosigner keys from input
|
|
162
|
+
const cosignerKeys = (0, vtxoTree_1.getCosignerKeys)(childTx);
|
|
163
|
+
// Aggregate keys
|
|
164
|
+
const { finalKey } = (0, musig2_1.aggregateKeys)(cosignerKeys, true, {
|
|
165
|
+
taprootTweak: tapTreeRoot,
|
|
166
|
+
});
|
|
167
|
+
if (base_1.hex.encode(finalKey) !== base_1.hex.encode(previousScriptKey.slice(2))) {
|
|
168
|
+
throw exports.ErrInternalKey;
|
|
169
|
+
}
|
|
170
|
+
// Check amounts
|
|
171
|
+
let sumChildAmount = 0n;
|
|
172
|
+
for (let i = 0; i < childTx.outputsLength; i++) {
|
|
173
|
+
const output = childTx.getOutput(i);
|
|
174
|
+
if (!output?.amount)
|
|
175
|
+
continue;
|
|
176
|
+
sumChildAmount += output.amount;
|
|
177
|
+
}
|
|
178
|
+
if (!parentOutput.amount)
|
|
179
|
+
throw exports.ErrInvalidAmount;
|
|
180
|
+
if (sumChildAmount >= parentOutput.amount) {
|
|
181
|
+
throw exports.ErrInvalidAmount;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.TxTree = exports.ErrParentNotFound = exports.ErrLeafNotFound = exports.TxTreeError = void 0;
|
|
37
|
+
exports.getVtxoTreeExpiry = getVtxoTreeExpiry;
|
|
38
|
+
exports.getCosignerKeys = getCosignerKeys;
|
|
39
|
+
const bip68 = __importStar(require("bip68"));
|
|
40
|
+
const btc_signer_1 = require("@scure/btc-signer");
|
|
41
|
+
const utils_1 = require("@scure/btc-signer/utils");
|
|
42
|
+
const base_1 = require("@scure/base");
|
|
43
|
+
class TxTreeError extends Error {
|
|
44
|
+
constructor(message) {
|
|
45
|
+
super(message);
|
|
46
|
+
this.name = "TxTreeError";
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
exports.TxTreeError = TxTreeError;
|
|
50
|
+
exports.ErrLeafNotFound = new TxTreeError("leaf not found in tx tree");
|
|
51
|
+
exports.ErrParentNotFound = new TxTreeError("parent not found");
|
|
52
|
+
// TxTree is represented as a matrix of Node objects
|
|
53
|
+
// the first level of the matrix is the root of the tree
|
|
54
|
+
class TxTree {
|
|
55
|
+
constructor(tree) {
|
|
56
|
+
this.tree = tree;
|
|
57
|
+
}
|
|
58
|
+
get levels() {
|
|
59
|
+
return this.tree;
|
|
60
|
+
}
|
|
61
|
+
// Returns the root node of the vtxo tree
|
|
62
|
+
root() {
|
|
63
|
+
if (this.tree.length <= 0 || this.tree[0].length <= 0) {
|
|
64
|
+
throw new TxTreeError("empty vtxo tree");
|
|
65
|
+
}
|
|
66
|
+
return this.tree[0][0];
|
|
67
|
+
}
|
|
68
|
+
// Returns the leaves of the vtxo tree
|
|
69
|
+
leaves() {
|
|
70
|
+
const leaves = [...this.tree[this.tree.length - 1]];
|
|
71
|
+
// Check other levels for leaf nodes
|
|
72
|
+
for (let i = 0; i < this.tree.length - 1; i++) {
|
|
73
|
+
for (const node of this.tree[i]) {
|
|
74
|
+
if (node.leaf) {
|
|
75
|
+
leaves.push(node);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return leaves;
|
|
80
|
+
}
|
|
81
|
+
// Returns all nodes that have the given node as parent
|
|
82
|
+
children(nodeTxid) {
|
|
83
|
+
const children = [];
|
|
84
|
+
for (const level of this.tree) {
|
|
85
|
+
for (const node of level) {
|
|
86
|
+
if (node.parentTxid === nodeTxid) {
|
|
87
|
+
children.push(node);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return children;
|
|
92
|
+
}
|
|
93
|
+
// Returns the total number of nodes in the vtxo tree
|
|
94
|
+
numberOfNodes() {
|
|
95
|
+
return this.tree.reduce((count, level) => count + level.length, 0);
|
|
96
|
+
}
|
|
97
|
+
// Returns the branch of the given vtxo txid from root to leaf
|
|
98
|
+
branch(vtxoTxid) {
|
|
99
|
+
const branch = [];
|
|
100
|
+
const leaves = this.leaves();
|
|
101
|
+
// Check if the vtxo is a leaf
|
|
102
|
+
const leaf = leaves.find((leaf) => leaf.txid === vtxoTxid);
|
|
103
|
+
if (!leaf) {
|
|
104
|
+
throw exports.ErrLeafNotFound;
|
|
105
|
+
}
|
|
106
|
+
branch.push(leaf);
|
|
107
|
+
const rootTxid = this.root().txid;
|
|
108
|
+
while (branch[0].txid !== rootTxid) {
|
|
109
|
+
const parent = this.findParent(branch[0]);
|
|
110
|
+
branch.unshift(parent);
|
|
111
|
+
}
|
|
112
|
+
return branch;
|
|
113
|
+
}
|
|
114
|
+
// Helper method to find parent of a node
|
|
115
|
+
findParent(node) {
|
|
116
|
+
for (const level of this.tree) {
|
|
117
|
+
for (const potentialParent of level) {
|
|
118
|
+
if (potentialParent.txid === node.parentTxid) {
|
|
119
|
+
return potentialParent;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
throw exports.ErrParentNotFound;
|
|
124
|
+
}
|
|
125
|
+
// Validates that the tree is coherent by checking txids and parent relationships
|
|
126
|
+
validate() {
|
|
127
|
+
// Skip the root level, validate from level 1 onwards
|
|
128
|
+
for (let i = 1; i < this.tree.length; i++) {
|
|
129
|
+
for (const node of this.tree[i]) {
|
|
130
|
+
// Verify that the node's transaction matches its claimed txid
|
|
131
|
+
const tx = btc_signer_1.Transaction.fromPSBT(base_1.base64.decode(node.tx));
|
|
132
|
+
const txid = base_1.hex.encode((0, utils_1.sha256x2)(tx.toBytes(true)).reverse());
|
|
133
|
+
if (txid !== node.txid) {
|
|
134
|
+
throw new TxTreeError(`node ${node.txid} has txid ${node.txid}, but computed txid is ${txid}`);
|
|
135
|
+
}
|
|
136
|
+
// Verify that the node has a valid parent
|
|
137
|
+
try {
|
|
138
|
+
this.findParent(node);
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
throw new TxTreeError(`node ${node.txid} has no parent: ${err instanceof Error ? err.message : String(err)}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
exports.TxTree = TxTree;
|
|
148
|
+
const COSIGNER_KEY_PREFIX = new Uint8Array("cosigner".split("").map((c) => c.charCodeAt(0)));
|
|
149
|
+
const VTXO_TREE_EXPIRY_PSBT_KEY = new Uint8Array("expiry".split("").map((c) => c.charCodeAt(0)));
|
|
150
|
+
function getVtxoTreeExpiry(input) {
|
|
151
|
+
if (!input.unknown)
|
|
152
|
+
return null;
|
|
153
|
+
for (const u of input.unknown) {
|
|
154
|
+
// Check if key contains the VTXO tree expiry key
|
|
155
|
+
if (u.key.length < VTXO_TREE_EXPIRY_PSBT_KEY.length)
|
|
156
|
+
continue;
|
|
157
|
+
let found = true;
|
|
158
|
+
for (let i = 0; i < VTXO_TREE_EXPIRY_PSBT_KEY.length; i++) {
|
|
159
|
+
if (u.key[i] !== VTXO_TREE_EXPIRY_PSBT_KEY[i]) {
|
|
160
|
+
found = false;
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (found) {
|
|
165
|
+
const value = (0, btc_signer_1.ScriptNum)(6, true).decode(u.value);
|
|
166
|
+
const { blocks, seconds } = bip68.decode(Number(value));
|
|
167
|
+
return {
|
|
168
|
+
type: blocks ? "blocks" : "seconds",
|
|
169
|
+
value: BigInt(blocks ?? seconds ?? 0),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
function parsePrefixedCosignerKey(key) {
|
|
176
|
+
if (key.length < COSIGNER_KEY_PREFIX.length)
|
|
177
|
+
return false;
|
|
178
|
+
for (let i = 0; i < COSIGNER_KEY_PREFIX.length; i++) {
|
|
179
|
+
if (key[i] !== COSIGNER_KEY_PREFIX[i])
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
function getCosignerKeys(tx) {
|
|
185
|
+
const keys = [];
|
|
186
|
+
const input = tx.getInput(0);
|
|
187
|
+
if (!input.unknown)
|
|
188
|
+
return keys;
|
|
189
|
+
for (const unknown of input.unknown) {
|
|
190
|
+
const ok = parsePrefixedCosignerKey(new Uint8Array([unknown[0].type, ...unknown[0].key]));
|
|
191
|
+
if (!ok)
|
|
192
|
+
continue;
|
|
193
|
+
// Assuming the value is already a valid public key in compressed format
|
|
194
|
+
keys.push(unknown[1]);
|
|
195
|
+
}
|
|
196
|
+
return keys;
|
|
197
|
+
}
|