@okxweb3/coin-bitcoin
Version:
@okxweb3/coin-bitcoin is a Bitcoin SDK for building Web3 wallets and applications. It supports BTC, BSV, DOGE, LTC, and TBTC, enabling private key management, transaction signing, address generation, and inscriptions like BRC-20, Runes, CAT, and Atomicals
467 lines • 22.5 kB
JavaScript
"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 (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.inscribeForMPCSigned = exports.inscribeForMPCUnsigned = exports.inscribe = exports.InscriptionTool = void 0;
const bitcoin = __importStar(require("./bitcoinjs-lib"));
const crypto_lib_1 = require("@okxweb3/crypto-lib");
const coin_base_1 = require("@okxweb3/coin-base");
const taproot = __importStar(require("./taproot"));
const bcrypto = __importStar(require("./bitcoinjs-lib/crypto"));
const transaction_1 = require("./bitcoinjs-lib/transaction");
const txBuild_1 = require("./txBuild");
const psbtutils_1 = require("./bitcoinjs-lib/psbt/psbtutils");
const sigcost_1 = require("./sigcost");
const schnorr = crypto_lib_1.signUtil.schnorr.secp256k1.schnorr;
const defaultTxVersion = 2;
const defaultSequenceNum = 0xfffffffd;
const defaultRevealOutValue = 546;
const defaultMinChangeValue = 546;
const maxStandardTxWeight = 4000000 / 10;
class InscriptionTool {
constructor() {
this.network = bitcoin.networks.bitcoin;
this.inscriptionTxCtxDataList = [];
this.revealTxs = [];
this.commitTx = new bitcoin.Transaction();
this.commitTxPrevOutputFetcher = [];
this.revealTxPrevOutputFetcher = [];
this.mustCommitTxFee = 0;
this.mustRevealTxFees = [];
this.commitAddrs = [];
}
static newInscriptionTool(network, request) {
const tool = new InscriptionTool();
tool.network = network;
const revealOutValue = request.revealOutValue || defaultRevealOutValue;
const minChangeValue = request.minChangeValue || defaultMinChangeValue;
const privateKey = request.commitTxPrevOutputList[0].privateKey;
request.inscriptionDataList.forEach(inscriptionData => {
tool.inscriptionTxCtxDataList.push(createInscriptionTxCtxData(network, inscriptionData, privateKey));
});
const totalRevealPrevOutputValue = tool.buildEmptyRevealTx(network, revealOutValue, request.revealFeeRate);
const insufficient = tool.buildCommitTx(network, request.commitTxPrevOutputList, request.changeAddress, totalRevealPrevOutputValue, request.commitFeeRate, minChangeValue);
if (insufficient) {
return tool;
}
tool.signCommitTx(request.commitTxPrevOutputList);
tool.completeRevealTx();
return tool;
}
buildEmptyRevealTx(network, revealOutValue, revealFeeRate) {
let totalPrevOutputValue = 0;
const revealTxs = [];
const mustRevealTxFees = [];
const commitAddrs = [];
this.inscriptionTxCtxDataList.forEach((inscriptionTxCtxData, i) => {
const tx = new bitcoin.Transaction();
tx.version = defaultTxVersion;
tx.addInput(Buffer.alloc(32), i, defaultSequenceNum);
tx.addOutput(inscriptionTxCtxData.revealPkScript, revealOutValue);
const emptySignature = Buffer.alloc(64);
const emptyControlBlockWitness = Buffer.alloc(33);
const txWitness = [];
txWitness.push(emptySignature);
txWitness.push(inscriptionTxCtxData.inscriptionScript);
txWitness.push(emptyControlBlockWitness);
const fee = Math.floor((tx.byteLength() + Math.floor(((0, transaction_1.vectorSize)(txWitness) + 2 + 3) / 4)) * revealFeeRate);
const prevOutputValue = revealOutValue + fee;
inscriptionTxCtxData.revealTxPrevOutput = {
pkScript: inscriptionTxCtxData.commitTxAddressPkScript,
value: prevOutputValue,
};
totalPrevOutputValue += prevOutputValue;
revealTxs.push(tx);
mustRevealTxFees.push(fee);
commitAddrs.push(inscriptionTxCtxData.commitTxAddress);
});
this.revealTxs = revealTxs;
this.mustRevealTxFees = mustRevealTxFees;
this.commitAddrs = commitAddrs;
return totalPrevOutputValue;
}
buildCommitTx(network, commitTxPrevOutputList, changeAddress, totalRevealPrevOutputValue, commitFeeRate, minChangeValue) {
let totalSenderAmount = 0;
const tx = new bitcoin.Transaction();
tx.version = defaultTxVersion;
commitTxPrevOutputList.forEach(commitTxPrevOutput => {
const hash = coin_base_1.base.reverseBuffer(coin_base_1.base.fromHex(commitTxPrevOutput.txId));
tx.addInput(hash, commitTxPrevOutput.vOut, defaultSequenceNum);
this.commitTxPrevOutputFetcher.push(commitTxPrevOutput.amount);
totalSenderAmount += commitTxPrevOutput.amount;
});
this.inscriptionTxCtxDataList.forEach(inscriptionTxCtxData => {
tx.addOutput(inscriptionTxCtxData.revealTxPrevOutput.pkScript, inscriptionTxCtxData.revealTxPrevOutput.value);
});
const changePkScript = bitcoin.address.toOutputScript(changeAddress, network);
tx.addOutput(changePkScript, 0);
const txForEstimate = tx.clone();
signTx(txForEstimate, commitTxPrevOutputList.map(i => ({ ...i, privateKey: '' })), this.network);
const vsize = (0, sigcost_1.countAdjustedVsize)(txForEstimate, commitTxPrevOutputList.map(a => a.address), network);
const fee = Math.floor(vsize * commitFeeRate);
const changeAmount = totalSenderAmount - totalRevealPrevOutputValue - fee;
if (changeAmount >= minChangeValue) {
tx.outs[tx.outs.length - 1].value = changeAmount;
}
else {
tx.outs = tx.outs.slice(0, tx.outs.length - 1);
txForEstimate.outs = txForEstimate.outs.slice(0, txForEstimate.outs.length - 1);
const vsizeWithoutChange = (0, sigcost_1.countAdjustedVsize)(txForEstimate, commitTxPrevOutputList.map(a => a.address), network);
const feeWithoutChange = Math.floor(vsizeWithoutChange * commitFeeRate);
if (totalSenderAmount - totalRevealPrevOutputValue - feeWithoutChange < 0) {
this.mustCommitTxFee = fee;
return true;
}
}
this.commitTx = tx;
return false;
}
signCommitTx(commitTxPrevOutputList) {
signTx(this.commitTx, commitTxPrevOutputList, this.network);
}
completeRevealTx() {
this.revealTxs.forEach((revealTx, i) => {
revealTx.ins[0].hash = this.commitTx.getHash();
const prevOutScripts = [this.inscriptionTxCtxDataList[i].revealTxPrevOutput.pkScript];
const values = [this.inscriptionTxCtxDataList[i].revealTxPrevOutput.value];
this.revealTxPrevOutputFetcher.push(this.inscriptionTxCtxDataList[i].revealTxPrevOutput.value);
const hash = revealTx.hashForWitnessV1(0, prevOutScripts, values, bitcoin.Transaction.SIGHASH_DEFAULT, this.inscriptionTxCtxDataList[i].hash);
const signature = Buffer.from(schnorr.sign(hash, this.inscriptionTxCtxDataList[i].privateKey, coin_base_1.base.randomBytes(32)));
revealTx.ins[0].witness = [Buffer.from(signature), ...this.inscriptionTxCtxDataList[i].witness];
const revealWeight = revealTx.weight();
if (revealWeight > maxStandardTxWeight) {
throw new Error(`reveal(index ${i}) transaction weight greater than ${maxStandardTxWeight} (MAX_STANDARD_TX_WEIGHT): ${revealWeight}`);
}
});
}
calculateFee() {
let commitTxFee = 0;
this.commitTx.ins.forEach((_, i) => {
commitTxFee += this.commitTxPrevOutputFetcher[i];
});
this.commitTx.outs.forEach(out => {
commitTxFee -= out.value;
});
let revealTxFees = [];
this.revealTxs.forEach((revealTx, i) => {
let revealTxFee = 0;
revealTxFee += this.revealTxPrevOutputFetcher[i];
revealTxFee -= revealTx.outs[0].value;
revealTxFees.push(revealTxFee);
});
return {
commitTxFee,
revealTxFees,
};
}
}
exports.InscriptionTool = InscriptionTool;
function signTx(tx, commitTxPrevOutputList, network) {
tx.ins.forEach((input, i) => {
const addressType = (0, txBuild_1.getAddressType)(commitTxPrevOutputList[i].address, network);
if (commitTxPrevOutputList[i].privateKey == '') {
const { witness, script } = (0, txBuild_1.fakeSign)(addressType);
if (witness !== undefined) {
input.witness = witness;
}
if (script !== undefined) {
input.script = script;
}
return;
}
const privateKey = coin_base_1.base.fromHex((0, txBuild_1.privateKeyFromWIF)(commitTxPrevOutputList[i].privateKey, network));
const privateKeyHex = coin_base_1.base.toHex(privateKey);
const publicKey = (0, txBuild_1.private2public)(privateKeyHex);
if (addressType === 'segwit_taproot') {
const prevOutScripts = commitTxPrevOutputList.map(o => bitcoin.address.toOutputScript(o.address, network));
const values = commitTxPrevOutputList.map(o => o.amount);
const hash = tx.hashForWitnessV1(i, prevOutScripts, values, bitcoin.Transaction.SIGHASH_DEFAULT);
const tweakedPrivKey = taproot.taprootTweakPrivKey(privateKey);
const signature = Buffer.from(schnorr.sign(hash, tweakedPrivKey, coin_base_1.base.randomBytes(32)));
input.witness = [Buffer.from(signature)];
}
else if (addressType === 'legacy') {
const prevScript = bitcoin.address.toOutputScript(commitTxPrevOutputList[i].address, network);
const hash = tx.hashForSignature(i, prevScript, bitcoin.Transaction.SIGHASH_ALL);
const signature = (0, txBuild_1.sign)(hash, privateKeyHex);
const payment = bitcoin.payments.p2pkh({
signature: bitcoin.script.signature.encode(signature, bitcoin.Transaction.SIGHASH_ALL),
pubkey: publicKey,
});
input.script = payment.input;
}
else {
const pubKeyHash = bcrypto.hash160(publicKey);
const prevOutScript = Buffer.of(0x19, 0x76, 0xa9, 0x14, ...pubKeyHash, 0x88, 0xac);
const value = commitTxPrevOutputList[i].amount;
const hash = tx.hashForWitness(i, prevOutScript, value, bitcoin.Transaction.SIGHASH_ALL);
const signature = (0, txBuild_1.sign)(hash, privateKeyHex);
input.witness = [
bitcoin.script.signature.encode(signature, bitcoin.Transaction.SIGHASH_ALL),
publicKey,
];
const redeemScript = Buffer.of(0x16, 0, 20, ...pubKeyHash);
if (addressType === "segwit_nested") {
input.script = redeemScript;
}
}
});
}
function createInscriptionTxCtxData(network, inscriptionData, privateKeyWif) {
const privateKey = coin_base_1.base.fromHex((0, txBuild_1.privateKeyFromWIF)(privateKeyWif, network));
const internalPubKey = (0, txBuild_1.wif2Public)(privateKeyWif, network).slice(1);
const ops = bitcoin.script.OPS;
const inscriptionBuilder = [];
inscriptionBuilder.push(internalPubKey);
inscriptionBuilder.push(ops.OP_CHECKSIG);
inscriptionBuilder.push(ops.OP_FALSE);
inscriptionBuilder.push(ops.OP_IF);
inscriptionBuilder.push(Buffer.from("ord"));
inscriptionBuilder.push(ops.OP_DATA_1);
inscriptionBuilder.push(ops.OP_DATA_1);
inscriptionBuilder.push(Buffer.from(inscriptionData.contentType));
inscriptionBuilder.push(ops.OP_0);
const maxChunkSize = 520;
let body = Buffer.from(inscriptionData.body);
let bodySize = body.length;
for (let i = 0; i < bodySize; i += maxChunkSize) {
let end = i + maxChunkSize;
if (end > bodySize) {
end = bodySize;
}
inscriptionBuilder.push(body.slice(i, end));
}
inscriptionBuilder.push(ops.OP_ENDIF);
const inscriptionScript = bitcoin.script.compile(inscriptionBuilder);
const scriptTree = {
output: inscriptionScript,
};
const redeem = {
output: inscriptionScript,
redeemVersion: 0xc0,
};
const { output, witness, hash, address } = bitcoin.payments.p2tr({
internalPubkey: internalPubKey,
scriptTree,
redeem,
network,
});
return {
privateKey,
inscriptionScript,
commitTxAddress: address,
commitTxAddressPkScript: output,
witness: witness,
hash: hash,
revealTxPrevOutput: {
pkScript: Buffer.alloc(0),
value: 0,
},
revealPkScript: bitcoin.address.toOutputScript(inscriptionData.revealAddr, network),
};
}
function inscribe(network, request) {
const tool = InscriptionTool.newInscriptionTool(network, request);
if (tool.mustCommitTxFee > 0) {
return {
commitTx: "",
revealTxs: [],
commitTxFee: tool.mustCommitTxFee,
revealTxFees: tool.mustRevealTxFees,
commitAddrs: tool.commitAddrs,
};
}
return {
commitTx: tool.commitTx.toHex(),
revealTxs: tool.revealTxs.map(revealTx => revealTx.toHex()),
...tool.calculateFee(),
commitAddrs: tool.commitAddrs,
};
}
exports.inscribe = inscribe;
function inscribeForMPCUnsigned(request, network, unsignedCommitTxHash, signedCommitTxHash) {
const privateKey = request.commitTxPrevOutputList[0].privateKey;
const scriptCtxList = [];
request.inscriptionDataList.forEach(inscriptionData => {
scriptCtxList.push(createInscriptionTxCtxData(network, inscriptionData, privateKey));
});
let totalRevealInValue = 0;
const revealOutValue = request.revealOutValue || defaultRevealOutValue;
const revealTxList = [];
scriptCtxList.forEach((ctx, i) => {
const tx = new bitcoin.Transaction();
tx.version = defaultTxVersion;
tx.addInput(Buffer.alloc(32), i, defaultSequenceNum);
tx.addOutput(ctx.revealPkScript, revealOutValue);
revealTxList.push(tx);
const emptySignature = Buffer.alloc(64);
const emptyControlBlockWitness = Buffer.alloc(33);
const txWitness = [];
txWitness.push(emptySignature);
txWitness.push(ctx.inscriptionScript);
txWitness.push(emptyControlBlockWitness);
const revealFee = Math.floor((tx.byteLength() + Math.floor(((0, transaction_1.vectorSize)(txWitness) + 2 + 3) / 4)) * request.revealFeeRate);
const revealInValue = revealOutValue + revealFee;
ctx.revealTxPrevOutput = {
pkScript: ctx.commitTxAddressPkScript,
value: revealInValue,
};
totalRevealInValue += revealInValue;
});
let totalCommitInValue = 0;
const commitTx = new bitcoin.Transaction();
commitTx.version = defaultTxVersion;
request.commitTxPrevOutputList.forEach(uxto => {
commitTx.addInput(coin_base_1.base.reverseBuffer(coin_base_1.base.fromHex(uxto.txId)), uxto.vOut, defaultSequenceNum);
totalCommitInValue += uxto.amount;
});
const commitAddrs = [];
scriptCtxList.forEach(ctx => {
commitTx.addOutput(ctx.revealTxPrevOutput.pkScript, ctx.revealTxPrevOutput.value);
commitAddrs.push(ctx.commitTxAddress);
});
const changePkScript = bitcoin.address.toOutputScript(request.changeAddress, network);
commitTx.addOutput(changePkScript, 0);
const estimateTx = commitTx.clone();
signTx(estimateTx, request.commitTxPrevOutputList, network);
const vsize = (0, sigcost_1.countAdjustedVsize)(estimateTx, request.commitTxPrevOutputList.map(a => a.address), network);
const fee = Math.floor(vsize * request.commitFeeRate);
const changeValue = totalCommitInValue - totalRevealInValue - fee;
if (changeValue >= (request.minChangeValue || defaultMinChangeValue)) {
commitTx.outs[commitTx.outs.length - 1].value = changeValue;
}
else {
commitTx.outs = commitTx.outs.slice(0, commitTx.outs.length - 1);
estimateTx.outs = estimateTx.outs.slice(0, estimateTx.outs.length - 1);
const vsizeWithoutChange = (0, sigcost_1.countAdjustedVsize)(estimateTx, request.commitTxPrevOutputList.map(a => a.address), network);
const feeWithoutChange = Math.floor(vsizeWithoutChange * request.commitFeeRate);
if (totalCommitInValue - totalRevealInValue - feeWithoutChange < 0) {
throw new Error("insufficient balance");
}
}
const sigHashList = calculateSigHash(commitTx, request.commitTxPrevOutputList, network);
let commitTxHash = commitTx.getHash();
if (signedCommitTxHash) {
commitTxHash = signedCommitTxHash;
}
revealTxList.forEach((revealTx, i) => {
revealTx.ins[0].hash = commitTxHash;
const prevOutScripts = [scriptCtxList[i].revealTxPrevOutput.pkScript];
const values = [scriptCtxList[i].revealTxPrevOutput.value];
const sigHash = revealTx.hashForWitnessV1(0, prevOutScripts, values, bitcoin.Transaction.SIGHASH_DEFAULT, scriptCtxList[i].hash);
const signature = Buffer.from(schnorr.sign(sigHash, scriptCtxList[i].privateKey, coin_base_1.base.randomBytes(32)));
revealTx.ins[0].witness = [signature, ...scriptCtxList[i].witness];
});
let commitTxFee = 0;
commitTx.ins.forEach((_, i) => {
commitTxFee += request.commitTxPrevOutputList[i].amount;
});
commitTx.outs.forEach(out => {
commitTxFee -= out.value;
});
let revealTxFees = [];
revealTxList.forEach((revealTx, i) => {
let revealTxFee = 0;
revealTxFee += scriptCtxList[i].revealTxPrevOutput.value;
revealTxFee -= revealTx.outs[0].value;
revealTxFees.push(revealTxFee);
});
return {
signHashList: sigHashList,
commitTx: commitTx.toHex(),
revealTxs: revealTxList.map(e => e.toHex()),
commitTxFee: commitTxFee,
revealTxFees: revealTxFees,
commitAddrs: commitAddrs,
};
}
exports.inscribeForMPCUnsigned = inscribeForMPCUnsigned;
function inscribeForMPCSigned(request, network) {
const unsignedCommitTxHex = request.commitTx;
const signatures = request.signatureList;
const tx = bitcoin.Transaction.fromHex(unsignedCommitTxHex);
const unsignedCommitTxHash = tx.getHash();
tx.ins.forEach((input, i) => {
const signature = coin_base_1.base.fromHex(signatures[i]);
if (!input.witness || input.witness.length == 0) {
input.script = bitcoin.payments.p2pkh({
pubkey: bitcoin.payments.p2pkh({ input: input.script }).pubkey,
signature: bitcoin.script.signature.encode(signature, bitcoin.Transaction.SIGHASH_ALL),
}).input;
}
else {
input.witness[0] = bitcoin.script.signature.encode(signature, bitcoin.Transaction.SIGHASH_ALL);
}
});
const signedCommitTxHash = tx.getHash();
const res = inscribeForMPCUnsigned(request, network, unsignedCommitTxHash, signedCommitTxHash);
return {
signHashList: null,
commitTx: tx.toHex(),
revealTxs: res.revealTxs,
commitTxFee: res.commitTxFee,
revealTxFees: res.revealTxFees,
commitAddrs: res.commitAddrs,
};
}
exports.inscribeForMPCSigned = inscribeForMPCSigned;
function calculateSigHash(tx, prevOutFetcher, network) {
const sigHashList = [];
tx.ins.forEach((input, i) => {
const publicKey = coin_base_1.base.fromHex(prevOutFetcher[i].publicKey);
const pkScript = bitcoin.address.toOutputScript(prevOutFetcher[i].address, network);
const placeholderSignature = Buffer.alloc(64, 0);
let sigHash;
if ((0, psbtutils_1.isP2TR)(pkScript)) {
const prevOutScripts = prevOutFetcher.map(o => bitcoin.address.toOutputScript(o.address, network));
const values = prevOutFetcher.map(o => o.amount);
sigHash = tx.hashForWitnessV1(i, prevOutScripts, values, bitcoin.Transaction.SIGHASH_DEFAULT);
input.witness = [placeholderSignature];
}
else if ((0, psbtutils_1.isP2PKH)(pkScript)) {
const prevScript = bitcoin.address.toOutputScript(prevOutFetcher[i].address, network);
sigHash = tx.hashForSignature(i, prevScript, bitcoin.Transaction.SIGHASH_ALL);
input.script = bitcoin.payments.p2pkh({
pubkey: publicKey,
signature: bitcoin.script.signature.encode(placeholderSignature, bitcoin.Transaction.SIGHASH_ALL),
}).input;
}
else {
const pubKeyHash = bcrypto.hash160(publicKey);
const prevOutScript = Buffer.of(0x19, 0x76, 0xa9, 0x14, ...pubKeyHash, 0x88, 0xac);
sigHash = tx.hashForWitness(i, prevOutScript, prevOutFetcher[i].amount, bitcoin.Transaction.SIGHASH_ALL);
input.witness = bitcoin.payments.p2wpkh({
pubkey: publicKey,
signature: bitcoin.script.signature.encode(placeholderSignature, bitcoin.Transaction.SIGHASH_ALL),
}).witness;
const redeemScript = Buffer.of(0x16, 0, 20, ...pubKeyHash);
if ((0, psbtutils_1.isP2SHScript)(pkScript)) {
input.script = redeemScript;
}
}
sigHashList.push(coin_base_1.base.toHex(sigHash));
});
return sigHashList;
}
//# sourceMappingURL=inscribe.js.map