@arklabs/wallet-sdk
Version:
Bitcoin wallet SDK with Taproot and Ark integration
125 lines (124 loc) • 4.37 kB
JavaScript
;
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;
}
}