UNPKG

@arkade-os/sdk

Version:

Bitcoin wallet SDK with Taproot and Ark integration

93 lines (92 loc) 3.53 kB
import { Script, Address, p2tr, taprootListToTree, TAPROOT_UNSPENDABLE_KEY, } from "@scure/btc-signer"; import { TAP_LEAF_VERSION } from "@scure/btc-signer/payment.js"; import { PSBTOutput } from "@scure/btc-signer/psbt.js"; import { hex } from "@scure/base"; import { ArkAddress } from './address.js'; import { ConditionCSVMultisigTapscript, CSVMultisigTapscript, } from './tapscript.js'; export const TapTreeCoder = PSBTOutput.tapTree[2]; export function scriptFromTapLeafScript(leaf) { return leaf[1].subarray(0, leaf[1].length - 1); // remove the version byte } /** * VtxoScript is a script that contains a list of tapleaf scripts. * It is used to create vtxo scripts. * * @example * ```typescript * const vtxoScript = new VtxoScript([new Uint8Array(32), new Uint8Array(32)]); */ export class VtxoScript { static decode(tapTree) { const leaves = TapTreeCoder.decode(tapTree); const scripts = leaves.map((leaf) => leaf.script); return new VtxoScript(scripts); } constructor(scripts) { this.scripts = scripts; // reverse the scripts if the number of scripts is odd // this is to be compatible with arkd algorithm computing taproot tree from list of tapscripts // the scripts must be reversed only HERE while we compute the tweaked public key // but the original order should be preserved while encoding as taptree // note: .slice().reverse() is used instead of .reverse() to avoid mutating the original array const list = scripts.length % 2 !== 0 ? scripts.slice().reverse() : scripts; const tapTree = taprootListToTree(list.map((script) => ({ script, leafVersion: TAP_LEAF_VERSION, }))); const payment = p2tr(TAPROOT_UNSPENDABLE_KEY, tapTree, undefined, true); if (!payment.tapLeafScript || payment.tapLeafScript.length !== scripts.length) { throw new Error("invalid scripts"); } this.leaves = payment.tapLeafScript; this.tweakedPublicKey = payment.tweakedPubkey; } encode() { const tapTree = TapTreeCoder.encode(this.scripts.map((script) => ({ depth: 1, version: TAP_LEAF_VERSION, script, }))); return tapTree; } address(prefix, serverPubKey) { return new ArkAddress(serverPubKey, this.tweakedPublicKey, prefix); } get pkScript() { return Script.encode(["OP_1", this.tweakedPublicKey]); } onchainAddress(network) { return Address(network).encode({ type: "tr", pubkey: this.tweakedPublicKey, }); } findLeaf(scriptHex) { const leaf = this.leaves.find((leaf) => hex.encode(scriptFromTapLeafScript(leaf)) === scriptHex); if (!leaf) { throw new Error(`leaf '${scriptHex}' not found`); } return leaf; } exitPaths() { const paths = []; for (const leaf of this.leaves) { try { const tapscript = CSVMultisigTapscript.decode(scriptFromTapLeafScript(leaf)); paths.push(tapscript); continue; } catch (e) { try { const tapscript = ConditionCSVMultisigTapscript.decode(scriptFromTapLeafScript(leaf)); paths.push(tapscript); } catch (e) { continue; } } } return paths; } }