UNPKG

@arklabs/wallet-sdk

Version:

Bitcoin wallet SDK with Taproot and Ark integration

198 lines (197 loc) 7.29 kB
"use strict"; 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; }