UNPKG

@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

559 lines 23.9 kB
"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.buildRuneMainDeployData = exports.inscribe = exports.runesMainInscribe = exports.checkEtching = exports.MAX_SPACERS = exports.uint64Max = exports.uint128Max = exports.RunesMainInscriptionTool = 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 bscript = __importStar(require("./bitcoinjs-lib/script")); const sigcost_1 = require("./sigcost"); const runestones_1 = require("./runestones"); 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 RunesMainInscriptionTool { 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 RunesMainInscriptionTool(); tool.network = network; const revealOutValue = request.revealOutValue || defaultRevealOutValue; const minChangeValue = request.minChangeValue || defaultMinChangeValue; const useDefaultOutput = request.runeData.useDefaultOutput || false; const defaultOutput = request.runeData.defaultOutput || 0; const privateKey = request.commitTxPrevOutputList[0].privateKey; if (!request.runeData.etching || !request.runeData.revealAddr) { throw new Error("etching is null"); } if (!checkEtching(request.runeData.etching)) { throw new Error("invalid etching parameter"); } let etching = request.runeData.etching; let runeCommitment = (0, runestones_1.commitment)(etching.rune); let runeOpReturnData = buildRuneMainDeployData(etching, useDefaultOutput, defaultOutput); if (request.runeData.etching.contentType && request.runeData.etching.body) { tool.inscriptionTxCtxDataList.push(createInscriptionTxCtxData(network, { contentType: request.runeData.etching.contentType, body: request.runeData.etching.body, revealAddr: request.runeData.revealAddr }, privateKey, runeCommitment)); } else { tool.inscriptionTxCtxDataList.push(createCommitmentScript(network, privateKey, request.runeData.revealAddr, runeCommitment)); } const totalRevealPrevOutputValue = tool.buildEmptyRevealTx(network, revealOutValue, request.revealFeeRate, runeOpReturnData); 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, opReturnData) { 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); if (opReturnData) { tx.addOutput(opReturnData, 0); } 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.RunesMainInscriptionTool = RunesMainInscriptionTool; exports.uint128Max = (BigInt(1) << BigInt(128)) - BigInt(1); exports.uint64Max = (BigInt(1) << BigInt(64)) - BigInt(1); const MAX_DIVISIBILITY = 38; exports.MAX_SPACERS = 134217727; function checkEtching(e) { let premine = e.premine ? BigInt(e.premine) : BigInt(0); if (premine > exports.uint128Max || premine < 0) { return false; } if (e.terms) { let amount = e.terms.amount ? BigInt(e.terms.amount) : BigInt(0); let cap = e.terms.cap ? BigInt(e.terms.cap) : BigInt(0); if (amount < 0 || amount > exports.uint128Max || cap < 0 || cap > exports.uint128Max) { return false; } if (amount * cap > exports.uint128Max) { return false; } let supply = premine + amount * cap; if (supply > exports.uint128Max) { return false; } if (e.terms.height) { if (e.terms.height.start) { if (e.terms.height.start < 0 || e.terms.height.start > exports.uint64Max) { return false; } } if (e.terms.height.end) { if (e.terms.height.end < 0 || e.terms.height.end > exports.uint64Max) { return false; } } } } if (e.divisibility) { if (e.divisibility < 0 || e.divisibility > MAX_DIVISIBILITY) { return false; } } if (typeof e.rune.value === "string") { let spacers = (0, runestones_1.getSpacersVal)(e.rune.value); if (spacers > exports.MAX_SPACERS) { return false; } let v = (0, runestones_1.base26Encode)((0, runestones_1.removeSpacers)(e.rune.value)); if (v > exports.uint128Max) { return false; } } return true; } exports.checkEtching = checkEtching; function runesMainInscribe(network, request) { const tool = RunesMainInscriptionTool.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.runesMainInscribe = runesMainInscribe; 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 createCommitmentScript(network, privateKeyWif, revealAddr, commitment) { const privateKey = coin_base_1.base.fromHex((0, txBuild_1.privateKeyFromWIF)(privateKeyWif, network)); const internalPubKey = (0, txBuild_1.wif2Public)(privateKeyWif, network).slice(1); const inscriptionBuilder = []; inscriptionBuilder.push(internalPubKey); const ops = bitcoin.script.OPS; inscriptionBuilder.push(ops.OP_CHECKSIG); inscriptionBuilder.push(ops.OP_FALSE); inscriptionBuilder.push(ops.OP_IF); inscriptionBuilder.push(commitment); inscriptionBuilder.push(ops.OP_ENDIF); let 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(revealAddr, network), }; } function createInscriptionTxCtxData(network, inscriptionData, privateKeyWif, inscriptionAddData) { 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); if (inscriptionAddData && inscriptionAddData.length > 0) { inscriptionBuilder.push(inscriptionAddData); inscriptionBuilder.push(ops.OP_DROP); } 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 = RunesMainInscriptionTool.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 buildRuneMainDeployData(etching, useDefaultOutput, defaultOutput) { let msg = buildMessage(etching, useDefaultOutput, defaultOutput); let msgBuff = toBuffer(msg); const prefix = Buffer.from('6a5d', 'hex'); let pushNum; if (msgBuff.length < 0x4c) { pushNum = Buffer.alloc(1); pushNum.writeUint8(msgBuff.length); } else if (msgBuff.length < 0x100) { pushNum = Buffer.alloc(2); pushNum.writeUint8(0x4c); pushNum.writeUint8(msgBuff.length, 1); } else if (msgBuff.length < 0x10000) { pushNum = Buffer.alloc(3); pushNum.writeUint8(0x4d); pushNum.writeUint16LE(msgBuff.length, 1); } else if (msgBuff.length < 0x100000000) { pushNum = Buffer.alloc(5); pushNum.writeUint8(0x4e); pushNum.writeUint32LE(msgBuff.length, 1); } else { throw new Error("runestone too big!"); } return bscript.compile(Buffer.concat([prefix, pushNum, msgBuff])); } exports.buildRuneMainDeployData = buildRuneMainDeployData; function toBuffer(msg) { let buffArr = []; for (const [tag, vals] of msg) { for (const val of vals) { const tagBuff = Buffer.alloc(1); tagBuff.writeUInt8(tag); buffArr.push(tagBuff); buffArr.push(Buffer.from((0, runestones_1.encodeLEB128)(val))); } } return Buffer.concat(buffArr); } function buildMessage(etching, useDefaultOutput, defaultOutput) { let fields = new Map(); let flags = 1; if (etching.terms) { let mask = 1 << runestones_1.Flag.Terms; flags |= mask; } if (etching.turbo) { let mask = 1 << runestones_1.Flag.Turbo; flags |= mask; } fields.set(runestones_1.Tag.Flags, [BigInt(flags)]); let runeValue; if (typeof etching.rune.value === 'string') { runeValue = (0, runestones_1.base26Encode)((0, runestones_1.removeSpacers)(etching.rune.value)); } else { runeValue = etching.rune.value; } fields.set(runestones_1.Tag.Rune, [BigInt(runeValue)]); if (etching.divisibility) { fields.set(runestones_1.Tag.Divisibility, [BigInt(etching.divisibility)]); } if (etching.spacers) { fields.set(runestones_1.Tag.Spacers, [BigInt(etching.spacers)]); } else { let spacers; if (typeof etching.rune.value === "bigint") { spacers = 0; } else { spacers = (0, runestones_1.getSpacersVal)(etching.rune.value); } if (spacers !== 0) { fields.set(runestones_1.Tag.Spacers, [BigInt(spacers)]); } } if (etching.symbol) { fields.set(runestones_1.Tag.Symbol, [BigInt(etching.symbol.charCodeAt(0))]); } if (etching.premine) { fields.set(runestones_1.Tag.Premine, [BigInt(etching.premine)]); } if (etching.terms) { if (etching.terms.amount) { fields.set(runestones_1.Tag.Amount, [BigInt(etching.terms.amount)]); } if (etching.terms.cap) { fields.set(runestones_1.Tag.Cap, [BigInt(etching.terms.cap)]); } if (etching.terms.height) { const heightStart = etching.terms.height.start; if (heightStart) { fields.set(runestones_1.Tag.HeightStart, [BigInt(heightStart)]); } const heightEnd = etching.terms.height.end; if (heightEnd) { fields.set(runestones_1.Tag.HeightEnd, [BigInt(heightEnd)]); } } if (etching.terms.offset) { const offsetStart = etching.terms.offset.start; if (offsetStart) { fields.set(runestones_1.Tag.OffsetStart, [BigInt(offsetStart)]); } const offsetEnd = etching.terms.offset.end; if (offsetEnd) { fields.set(runestones_1.Tag.OffsetEnd, [BigInt(offsetEnd)]); } } } if (useDefaultOutput && defaultOutput) { fields.set(runestones_1.Tag.Pointer, [BigInt(defaultOutput)]); } return fields; } //# sourceMappingURL=runesMain.js.map