@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
430 lines • 19.7 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.dogInscribe = exports.DogInscriptionTool = exports.DogScript = exports.bufferToChunk = exports.bufferToBuffer = exports.CHANGE_OUTPUT_MAX_SIZE = void 0;
const bitcoin = __importStar(require("./bitcoinjs-lib"));
const crypto_lib_1 = require("@okxweb3/crypto-lib");
const bcrypto = __importStar(require("./bitcoinjs-lib/crypto"));
const txBuild_1 = require("./txBuild");
const bitcoinjs_lib_1 = require("./bitcoinjs-lib");
const ops_1 = require("./bitcoinjs-lib/ops");
const payments = __importStar(require("./bitcoinjs-lib/payments"));
const bufferutils_1 = require("./bitcoinjs-lib/bufferutils");
exports.CHANGE_OUTPUT_MAX_SIZE = 20 + 4 + 34 + 4;
const defaultTxVersion = 2;
const defaultSequenceNum = 0xfffffffd;
const defaultRevealOutValue = 100000;
const defaultMinChangeValue = 100000;
const MAX_CHUNK_LEN = 240;
const MAX_PAYLOAD_LEN = 1500;
function numberToChunk(n) {
return {
buf: n <= 16 ? undefined : n < 128 ? Buffer.from([n]) : Buffer.from([n % 256, n / 256]),
len: n <= 16 ? 0 : n < 128 ? 1 : 2,
opcodenum: n == 0 ? 0 : n <= 16 ? 80 + n : n < 128 ? 1 : 2
};
}
function bufferToBuffer(b) {
let c = bufferToChunk(b);
let size = bufferutils_1.varuint.encodingLength(c.opcodenum);
var opcodenum = c.opcodenum;
if (c.buf) {
if (opcodenum === ops_1.OPS.OP_PUSHDATA1) {
size += bufferutils_1.varuint.encodingLength(c.len);
}
else if (opcodenum === ops_1.OPS.OP_PUSHDATA2) {
size += bufferutils_1.varuint.encodingLength(c.len);
}
else if (opcodenum === ops_1.OPS.OP_PUSHDATA4) {
size += bufferutils_1.varuint.encodingLength(c.len);
}
size += c.buf.length;
}
let bw = bitcoinjs_lib_1.BufferWriter.withCapacity(size);
bw.writeUInt8(c.opcodenum);
if (c.buf) {
if (opcodenum < ops_1.OPS.OP_PUSHDATA1) {
bw.writeSlice(c.buf);
}
else if (opcodenum === ops_1.OPS.OP_PUSHDATA1) {
bw.writeUInt8(c.len);
bw.writeSlice(c.buf);
}
else if (opcodenum === ops_1.OPS.OP_PUSHDATA2) {
bw.writeUInt64(c.len);
bw.writeSlice(c.buf);
}
else if (opcodenum === ops_1.OPS.OP_PUSHDATA4) {
bw.writeUInt32(c.len);
bw.writeSlice(c.buf);
}
}
return bw.end();
}
exports.bufferToBuffer = bufferToBuffer;
function bufferToChunk(b) {
return {
buf: b.length ? b : undefined,
len: b.length,
opcodenum: b.length <= 75 ? b.length : b.length <= 255 ? 76 : 77
};
}
exports.bufferToChunk = bufferToChunk;
function opcodeToChunk(op) {
return { opcodenum: op };
}
class DogScript {
constructor() {
this.chunks = [];
}
total() {
if (this.chunks.length == 0) {
return 0;
}
const size = this.chunks
.map(chunk => {
let size = bufferutils_1.varuint.encodingLength(chunk.opcodenum);
var opcodenum = chunk.opcodenum;
if (chunk.buf) {
if (opcodenum < ops_1.OPS.OP_PUSHDATA1) {
}
else if (opcodenum === ops_1.OPS.OP_PUSHDATA1) {
size += bufferutils_1.varuint.encodingLength(chunk.len);
}
else if (opcodenum === ops_1.OPS.OP_PUSHDATA2) {
size += bufferutils_1.varuint.encodingLength(chunk.len);
}
else if (opcodenum === ops_1.OPS.OP_PUSHDATA4) {
size += bufferutils_1.varuint.encodingLength(chunk.len);
}
size += chunk.buf.length;
}
return size;
})
.reduce((a, b) => a + b);
return size;
}
toBuffer() {
let total = this.total();
let bw = bitcoinjs_lib_1.BufferWriter.withCapacity(total);
for (var i = 0; i < this.chunks.length; i++) {
var chunk = this.chunks[i];
var opcodenum = chunk.opcodenum;
bw.writeUInt8(chunk.opcodenum);
if (chunk.buf) {
if (opcodenum < ops_1.OPS.OP_PUSHDATA1) {
bw.writeSlice(chunk.buf);
}
else if (opcodenum === ops_1.OPS.OP_PUSHDATA1) {
bw.writeUInt8(chunk.len);
bw.writeSlice(chunk.buf);
}
else if (opcodenum === ops_1.OPS.OP_PUSHDATA2) {
bw.writeUInt64(chunk.len);
bw.writeSlice(chunk.buf);
}
else if (opcodenum === ops_1.OPS.OP_PUSHDATA4) {
bw.writeUInt32(chunk.len);
bw.writeSlice(chunk.buf);
}
}
}
return bw.end();
}
}
exports.DogScript = DogScript;
class DogInscriptionTool {
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 = [];
this.fromAddr = '';
this.revealAddr = '';
}
static newDogInscriptionTool(network, request) {
const tool = new DogInscriptionTool();
tool.network = network;
const revealOutValue = request.revealOutValue || defaultRevealOutValue;
const minChangeValue = request.minChangeValue || defaultMinChangeValue;
const privateKey = request.commitTxPrevOutputList[0].privateKey;
tool.inscriptionTxCtxDataList = createInscriptionTxCtxData(network, request.inscriptionData, privateKey);
tool.revealAddr = request.inscriptionData.revealAddr;
const privateKeyHex = crypto_lib_1.base.toHex(crypto_lib_1.base.fromHex((0, txBuild_1.privateKeyFromWIF)(privateKey, network)));
const publicKey = (0, txBuild_1.private2public)(privateKeyHex);
tool.fromAddr = bitcoin.payments.p2pkh({ pubkey: publicKey, network: network }).address;
const totalRevealPrevOutputValue = tool.buildEmptyRevealTxs(network, revealOutValue, request.revealFeeRate);
const insufficient = tool.buildCommitTx(network, request.commitTxPrevOutputList, request.changeAddress, totalRevealPrevOutputValue, revealOutValue, request.commitFeeRate, minChangeValue);
if (insufficient) {
return tool;
}
tool.signCommitTx(request.commitTxPrevOutputList);
tool.completeRevealTx();
return tool;
}
buildEmptyRevealTxs(network, revealOutValue, revealFeeRate) {
let totalPrevOutputValue = 0;
const revealTxs = [];
const mustRevealTxFees = [];
const commitAddrs = [];
let left = 0;
for (let i = this.inscriptionTxCtxDataList.length - 1; i > -1; i--) {
let inscriptionTxCtxData = this.inscriptionTxCtxDataList[i];
const tx = new bitcoin.Transaction();
tx.version = defaultTxVersion;
tx.addInput(Buffer.alloc(32), 0, defaultSequenceNum);
tx.addInput(Buffer.alloc(32), 1, defaultSequenceNum);
tx.addOutput(i != this.inscriptionTxCtxDataList.length - 1 ? inscriptionTxCtxData.commitTxAddressPkScript : inscriptionTxCtxData.revealPkScript, revealOutValue);
const emptySignature = Buffer.alloc(72);
let unlock = Buffer.concat([inscriptionTxCtxData.inscriptionScript, bufferToBuffer(emptySignature), bufferToBuffer(inscriptionTxCtxData.redeemScript)]);
tx.ins[0].script = unlock;
if (i != this.inscriptionTxCtxDataList.length - 1) {
tx.addOutput(bitcoin.address.toOutputScript(this.fromAddr, network), left);
}
tx.ins[1].script = Buffer.alloc(106);
const fee = Math.floor((tx.dogeByteLength() + exports.CHANGE_OUTPUT_MAX_SIZE) * revealFeeRate);
left += fee;
const prevOutputValue = fee;
inscriptionTxCtxData.revealTxPrevOutput = {
pkScript: inscriptionTxCtxData.commitTxAddressPkScript,
value: prevOutputValue,
};
totalPrevOutputValue += prevOutputValue;
revealTxs.push(tx);
mustRevealTxFees.push(fee);
commitAddrs.push(inscriptionTxCtxData.commitTxAddress);
}
for (let i = 0, j = revealTxs.length - 1; i < j; i++, j--) {
[revealTxs[i], revealTxs[j]] = [revealTxs[j], revealTxs[i]];
[mustRevealTxFees[i], mustRevealTxFees[j]] = [mustRevealTxFees[j], mustRevealTxFees[i]];
[commitAddrs[i], commitAddrs[j]] = [commitAddrs[j], commitAddrs[i]];
}
this.revealTxs = revealTxs;
this.mustRevealTxFees = mustRevealTxFees;
this.commitAddrs = commitAddrs;
totalPrevOutputValue += revealOutValue;
return totalPrevOutputValue;
}
buildCommitTx(network, commitTxPrevOutputList, changeAddress, totalRevealPrevOutputValue, revealOutValue, commitFeeRate, minChangeValue) {
let totalSenderAmount = 0;
const tx = new bitcoin.Transaction();
tx.version = defaultTxVersion;
commitTxPrevOutputList.forEach(commitTxPrevOutput => {
const hash = crypto_lib_1.base.reverseBuffer(crypto_lib_1.base.fromHex(commitTxPrevOutput.txId));
tx.addInput(hash, commitTxPrevOutput.vOut, defaultSequenceNum);
this.commitTxPrevOutputFetcher.push(commitTxPrevOutput.amount);
totalSenderAmount += commitTxPrevOutput.amount;
});
tx.addOutput(this.inscriptionTxCtxDataList[0].revealTxPrevOutput.pkScript, revealOutValue);
tx.addOutput(bitcoin.address.toOutputScript(this.fromAddr, network), totalRevealPrevOutputValue);
const changePkScript = bitcoin.address.toOutputScript(changeAddress, network);
tx.addOutput(changePkScript, 0);
const txForEstimate = tx.clone();
signTx(txForEstimate, commitTxPrevOutputList, this.network);
const fee = Math.floor((txForEstimate.dogeByteLength() + exports.CHANGE_OUTPUT_MAX_SIZE) * 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 feeWithoutChange = Math.floor(txForEstimate.dogeByteLength() * 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() {
for (let i = 0; i < this.revealTxs.length; i++) {
let revealTx = this.revealTxs[i];
revealTx.ins[0].hash = i == 0 ? this.commitTx.getHash() : this.revealTxs[i - 1].getHash();
revealTx.ins[1].hash = i == 0 ? this.commitTx.getHash() : this.revealTxs[i - 1].getHash();
this.revealTxPrevOutputFetcher.push(this.inscriptionTxCtxDataList[i].revealTxPrevOutput.value);
const prevOutScripts = this.inscriptionTxCtxDataList[i].redeemScript;
const hash = revealTx.hashForSignature(0, prevOutScripts, bitcoin.Transaction.SIGHASH_ALL);
const privateKeyHex = crypto_lib_1.base.toHex(this.inscriptionTxCtxDataList[i].privateKey);
const signature = (0, txBuild_1.sign)(hash, privateKeyHex);
let txsignature = bitcoin.script.signature.encode(signature, bitcoin.Transaction.SIGHASH_ALL);
revealTx.ins[0].script = Buffer.concat([this.inscriptionTxCtxDataList[i].inscriptionScript, bufferToBuffer(txsignature), bufferToBuffer(this.inscriptionTxCtxDataList[i].redeemScript)]);
const prevScript = bitcoin.address.toOutputScript(this.fromAddr, this.network);
const hash2 = revealTx.hashForSignature(1, prevScript, bitcoin.Transaction.SIGHASH_ALL);
const signature2 = (0, txBuild_1.sign)(hash2, privateKeyHex);
const payment = bitcoin.payments.p2pkh({
signature: bitcoin.script.signature.encode(signature2, bitcoin.Transaction.SIGHASH_ALL),
pubkey: (0, txBuild_1.private2public)(privateKeyHex),
});
revealTx.ins[1].script = payment.input;
}
}
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];
revealTxFees.push(revealTxFee);
});
return {
commitTxFee,
revealTxFees,
};
}
}
exports.DogInscriptionTool = DogInscriptionTool;
function signTx(tx, commitTxPrevOutputList, network) {
tx.ins.forEach((input, i) => {
const addressType = (0, txBuild_1.getAddressType)(commitTxPrevOutputList[i].address, network);
const privateKey = crypto_lib_1.base.fromHex((0, txBuild_1.privateKeyFromWIF)(commitTxPrevOutputList[i].privateKey, network));
const privateKeyHex = crypto_lib_1.base.toHex(privateKey);
const publicKey = (0, txBuild_1.private2public)(privateKeyHex);
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 = crypto_lib_1.base.fromHex((0, txBuild_1.privateKeyFromWIF)(privateKeyWif, network));
const pubKey = (0, txBuild_1.wif2Public)(privateKeyWif, network);
const ops = bitcoin.script.OPS;
let data;
if (typeof inscriptionData.body == 'string') {
data = Buffer.from(inscriptionData.body);
}
else {
data = inscriptionData.body;
}
let parts = [];
while (data.length) {
let part = data.slice(0, Math.min(MAX_CHUNK_LEN, data.length));
data = data.slice(part.length);
parts.push(part);
}
let inscription = new DogScript();
inscription.chunks.push(bufferToChunk(Buffer.from('ord')));
inscription.chunks.push(numberToChunk(parts.length));
inscription.chunks.push(bufferToChunk(Buffer.from(inscriptionData.contentType)));
parts.forEach((part, n) => {
inscription.chunks.push(numberToChunk(parts.length - n - 1));
inscription.chunks.push(bufferToChunk(part));
});
let ctxDatas = [];
while (inscription.chunks.length) {
let partial = new DogScript();
if (ctxDatas.length == 0) {
partial.chunks.push(inscription.chunks.shift());
}
while (partial.total() <= MAX_PAYLOAD_LEN && inscription.chunks.length) {
partial.chunks.push(inscription.chunks.shift());
partial.chunks.push(inscription.chunks.shift());
}
if (partial.total() > MAX_PAYLOAD_LEN) {
inscription.chunks.unshift(partial.chunks.pop());
inscription.chunks.unshift(partial.chunks.pop());
}
let lock = new DogScript();
lock.chunks.push(bufferToChunk(pubKey));
lock.chunks.push(opcodeToChunk(ops.OP_CHECKSIGVERIFY));
partial.chunks.forEach(() => {
lock.chunks.push(opcodeToChunk(ops.OP_DROP));
});
lock.chunks.push(opcodeToChunk(ops.OP_TRUE));
let lockhash = crypto_lib_1.base.ripemd160(crypto_lib_1.base.sha256(lock.toBuffer()));
let { output, address } = payments.p2sh({ hash: Buffer.from(lockhash), network: network });
let ctx = {
privateKey: privateKey,
inscriptionScript: partial.toBuffer(),
redeemScript: lock.toBuffer(),
commitTxAddress: address,
commitTxAddressPkScript: output,
revealTxPrevOutput: {
pkScript: Buffer.alloc(0),
value: 100000,
},
revealPkScript: bitcoin.address.toOutputScript(inscriptionData.revealAddr, network),
};
ctxDatas.push(ctx);
}
return ctxDatas;
}
function dogInscribe(network, request) {
const tool = DogInscriptionTool.newDogInscriptionTool(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.dogInscribe = dogInscribe;
//# sourceMappingURL=doginals.js.map