UNPKG

@arklabs/wallet-sdk

Version:

Bitcoin wallet SDK with Taproot and Ark integration

125 lines (124 loc) 4.37 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.VTXO_TAPROOT_TREE_KEY_PREFIX = exports.CONDITION_WITNESS_KEY_PREFIX = void 0; exports.addVtxoTaprootTree = addVtxoTaprootTree; exports.addConditionWitness = addConditionWitness; exports.createVirtualTx = createVirtualTx; const btc_signer_1 = require("@scure/btc-signer"); const tapscript_1 = require("../script/tapscript"); const base_1 = require("../script/base"); const address_1 = require("../script/address"); const base_2 = require("@scure/base"); const ARK_UNKNOWN_KEY_TYPE = 255; // Constant for condition witness key prefix exports.CONDITION_WITNESS_KEY_PREFIX = new TextEncoder().encode("condition"); exports.VTXO_TAPROOT_TREE_KEY_PREFIX = new TextEncoder().encode("taptree"); function addVtxoTaprootTree(inIndex, tx, scripts) { tx.updateInput(inIndex, { unknown: [ ...(tx.getInput(inIndex)?.unknown ?? []), [ { type: ARK_UNKNOWN_KEY_TYPE, key: exports.VTXO_TAPROOT_TREE_KEY_PREFIX, }, encodeTaprootTree(scripts), ], ], }); } function addConditionWitness(inIndex, tx, witness) { const witnessBytes = btc_signer_1.RawWitness.encode(witness); tx.updateInput(inIndex, { unknown: [ ...(tx.getInput(inIndex)?.unknown ?? []), [ { type: ARK_UNKNOWN_KEY_TYPE, key: exports.CONDITION_WITNESS_KEY_PREFIX, }, witnessBytes, ], ], }); } function createVirtualTx(inputs, outputs) { let lockTime; for (const input of inputs) { const tapscript = (0, tapscript_1.decodeTapscript)((0, base_1.scriptFromTapLeafScript)(input.tapLeafScript)); if (tapscript_1.CLTVMultisigTapscript.is(tapscript)) { lockTime = Number(tapscript.params.absoluteTimelock); } } const tx = new btc_signer_1.Transaction({ allowUnknown: true, lockTime, }); for (const [i, input] of inputs.entries()) { tx.addInput({ txid: input.txid, index: input.vout, sequence: lockTime ? btc_signer_1.DEFAULT_SEQUENCE - 1 : undefined, witnessUtxo: { script: base_1.VtxoScript.decode(input.scripts).pkScript, amount: BigInt(input.value), }, tapLeafScript: [input.tapLeafScript], }); // add BIP371 encoded taproot tree to the unknown key field addVtxoTaprootTree(i, tx, input.scripts.map(base_2.hex.decode)); } for (const output of outputs) { tx.addOutput({ amount: output.amount, script: address_1.ArkAddress.decode(output.address).pkScript, }); } return tx; } function encodeTaprootTree(leaves) { const chunks = []; // Write number of leaves as compact size uint chunks.push(encodeCompactSizeUint(leaves.length)); for (const tapscript of leaves) { // Write depth (always 1 for now) chunks.push(new Uint8Array([1])); // Write leaf version (0xc0 for tapscript) chunks.push(new Uint8Array([0xc0])); // Write script length and script chunks.push(encodeCompactSizeUint(tapscript.length)); chunks.push(tapscript); } // Concatenate all chunks const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0); const result = new Uint8Array(totalLength); let offset = 0; for (const chunk of chunks) { result.set(chunk, offset); offset += chunk.length; } return result; } function encodeCompactSizeUint(value) { if (value < 0xfd) { return new Uint8Array([value]); } else if (value <= 0xffff) { const buffer = new Uint8Array(3); buffer[0] = 0xfd; new DataView(buffer.buffer).setUint16(1, value, true); return buffer; } else if (value <= 0xffffffff) { const buffer = new Uint8Array(5); buffer[0] = 0xfe; new DataView(buffer.buffer).setUint32(1, value, true); return buffer; } else { const buffer = new Uint8Array(9); buffer[0] = 0xff; new DataView(buffer.buffer).setBigUint64(1, BigInt(value), true); return buffer; } }