@arklabs/wallet-sdk
Version:
Bitcoin wallet SDK with Taproot and Ark integration
198 lines (197 loc) • 7.29 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.TxTree = exports.ErrParentNotFound = exports.ErrLeafNotFound = exports.TxTreeError = void 0;
exports.getVtxoTreeExpiry = getVtxoTreeExpiry;
exports.getCosignerKeys = getCosignerKeys;
const bip68 = __importStar(require("bip68"));
const btc_signer_1 = require("@scure/btc-signer");
const utils_1 = require("@scure/btc-signer/utils");
const base_1 = require("@scure/base");
class TxTreeError extends Error {
constructor(message) {
super(message);
this.name = "TxTreeError";
}
}
exports.TxTreeError = TxTreeError;
exports.ErrLeafNotFound = new TxTreeError("leaf not found in tx tree");
exports.ErrParentNotFound = new TxTreeError("parent not found");
// TxTree is represented as a matrix of Node objects
// the first level of the matrix is the root of the tree
class TxTree {
constructor(tree) {
this.tree = tree;
}
get levels() {
return this.tree;
}
// Returns the root node of the vtxo tree
root() {
if (this.tree.length <= 0 || this.tree[0].length <= 0) {
throw new TxTreeError("empty vtxo tree");
}
return this.tree[0][0];
}
// Returns the leaves of the vtxo tree
leaves() {
const leaves = [...this.tree[this.tree.length - 1]];
// Check other levels for leaf nodes
for (let i = 0; i < this.tree.length - 1; i++) {
for (const node of this.tree[i]) {
if (node.leaf) {
leaves.push(node);
}
}
}
return leaves;
}
// Returns all nodes that have the given node as parent
children(nodeTxid) {
const children = [];
for (const level of this.tree) {
for (const node of level) {
if (node.parentTxid === nodeTxid) {
children.push(node);
}
}
}
return children;
}
// Returns the total number of nodes in the vtxo tree
numberOfNodes() {
return this.tree.reduce((count, level) => count + level.length, 0);
}
// Returns the branch of the given vtxo txid from root to leaf
branch(vtxoTxid) {
const branch = [];
const leaves = this.leaves();
// Check if the vtxo is a leaf
const leaf = leaves.find((leaf) => leaf.txid === vtxoTxid);
if (!leaf) {
throw exports.ErrLeafNotFound;
}
branch.push(leaf);
const rootTxid = this.root().txid;
while (branch[0].txid !== rootTxid) {
const parent = this.findParent(branch[0]);
branch.unshift(parent);
}
return branch;
}
// Helper method to find parent of a node
findParent(node) {
for (const level of this.tree) {
for (const potentialParent of level) {
if (potentialParent.txid === node.parentTxid) {
return potentialParent;
}
}
}
throw exports.ErrParentNotFound;
}
// Validates that the tree is coherent by checking txids and parent relationships
validate() {
// Skip the root level, validate from level 1 onwards
for (let i = 1; i < this.tree.length; i++) {
for (const node of this.tree[i]) {
// Verify that the node's transaction matches its claimed txid
const tx = btc_signer_1.Transaction.fromPSBT(base_1.base64.decode(node.tx));
const txid = base_1.hex.encode((0, utils_1.sha256x2)(tx.toBytes(true)).reverse());
if (txid !== node.txid) {
throw new TxTreeError(`node ${node.txid} has txid ${node.txid}, but computed txid is ${txid}`);
}
// Verify that the node has a valid parent
try {
this.findParent(node);
}
catch (err) {
throw new TxTreeError(`node ${node.txid} has no parent: ${err instanceof Error ? err.message : String(err)}`);
}
}
}
}
}
exports.TxTree = TxTree;
const COSIGNER_KEY_PREFIX = new Uint8Array("cosigner".split("").map((c) => c.charCodeAt(0)));
const VTXO_TREE_EXPIRY_PSBT_KEY = new Uint8Array("expiry".split("").map((c) => c.charCodeAt(0)));
function getVtxoTreeExpiry(input) {
if (!input.unknown)
return null;
for (const u of input.unknown) {
// Check if key contains the VTXO tree expiry key
if (u.key.length < VTXO_TREE_EXPIRY_PSBT_KEY.length)
continue;
let found = true;
for (let i = 0; i < VTXO_TREE_EXPIRY_PSBT_KEY.length; i++) {
if (u.key[i] !== VTXO_TREE_EXPIRY_PSBT_KEY[i]) {
found = false;
break;
}
}
if (found) {
const value = (0, btc_signer_1.ScriptNum)(6, true).decode(u.value);
const { blocks, seconds } = bip68.decode(Number(value));
return {
type: blocks ? "blocks" : "seconds",
value: BigInt(blocks ?? seconds ?? 0),
};
}
}
return null;
}
function parsePrefixedCosignerKey(key) {
if (key.length < COSIGNER_KEY_PREFIX.length)
return false;
for (let i = 0; i < COSIGNER_KEY_PREFIX.length; i++) {
if (key[i] !== COSIGNER_KEY_PREFIX[i])
return false;
}
return true;
}
function getCosignerKeys(tx) {
const keys = [];
const input = tx.getInput(0);
if (!input.unknown)
return keys;
for (const unknown of input.unknown) {
const ok = parsePrefixedCosignerKey(new Uint8Array([unknown[0].type, ...unknown[0].key]));
if (!ok)
continue;
// Assuming the value is already a valid public key in compressed format
keys.push(unknown[1]);
}
return keys;
}