UNPKG

@okxweb3/coin-bitcoin

Version:

@ok/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.

622 lines 27.3 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.fakeSign = exports.estimateBchFee = exports.estimateBtcFee = exports.ValidSignedTransaction = exports.getMPCTransaction = exports.calculateBchTxSize = exports.calculateTxSize = exports.signBch = exports.getAddressType = exports.signBtc = exports.TxBuild = exports.private2Wif = exports.wif2Public = exports.sign = exports.private2public = exports.privateKeyFromWIF = exports.Array = void 0; const bitcoin = __importStar(require("./bitcoinjs-lib")); const bitcoinjs_lib_1 = require("./bitcoinjs-lib"); const crypto_lib_1 = require("@okxweb3/crypto-lib"); const coin_base_1 = require("@okxweb3/coin-base"); const wif = __importStar(require("./wif")); const address_1 = require("./bitcoinjs-lib/address"); const bcrypto = __importStar(require("./bitcoinjs-lib/crypto")); const taproot = __importStar(require("./taproot")); const bscript = __importStar(require("./bitcoinjs-lib/script")); const ops_1 = require("./bitcoinjs-lib/ops"); const sigcost_1 = require("./sigcost"); const schnorr = crypto_lib_1.signUtil.schnorr.secp256k1.schnorr; exports.Array = coin_base_1.typeforce.Array; function privateKeyFromWIF(wifString, network) { const decoded = wif.decode(wifString); const version = decoded.version; if ((0, exports.Array)(network)) { network = network .filter((x) => { return version === x.wif; }) .pop(); if (!network) throw new Error('Unknown network version'); } else { network = network || bitcoin.networks.bitcoin; if (version !== network.wif) throw new Error('Invalid network version'); } return coin_base_1.base.toHex(decoded.privateKey); } exports.privateKeyFromWIF = privateKeyFromWIF; function private2public(privateKey) { return crypto_lib_1.signUtil.secp256k1.publicKeyCreate(coin_base_1.base.fromHex(privateKey), true); } exports.private2public = private2public; function sign(hash, privateKey) { const { signature } = crypto_lib_1.signUtil.secp256k1.sign(hash, coin_base_1.base.fromHex(privateKey)); return Buffer.from(signature); } exports.sign = sign; function wif2Public(wif, network) { const privateKey = privateKeyFromWIF(wif, network); return private2public(privateKey); } exports.wif2Public = wif2Public; function private2Wif(privateKey, network) { network = network || bitcoin.networks.bitcoin; return wif.encode(network.wif, privateKey, true); } exports.private2Wif = private2Wif; class TxBuild { constructor(version, network, bitcoinCash, hardware, fakeKey) { this.tx = new bitcoin.Transaction(); this.network = bitcoin.networks.bitcoin; if (version) { this.tx.version = version; } else { this.tx.version = 2; } if (network) { this.network = network; } this.inputs = []; this.outputs = []; this.bitcoinCash = bitcoinCash || false; this.hardware = hardware || false; this.fakeKey = fakeKey || false; } addInput(txId, index, privateKey, address, script, value, publicKey, sequence) { this.inputs.push({ txId: txId, index: index, privateKey: privateKey, address: address, script: script, value: value, publicKey: publicKey, sequence: sequence, }); } addOutput(address, value, omniScript) { this.outputs.push({ address: address, value: value, omniScript: omniScript, }); } build(hashArray) { const eckeys = []; for (const input of this.inputs) { const hash = coin_base_1.base.reverseBuffer(Buffer.from(input.txId, 'hex')); this.tx.addInput(hash, input.index, input.sequence); if (input.privateKey) { eckeys.push(privateKeyFromWIF(input.privateKey, this.network)); } else { eckeys.push(''); } } for (const output of this.outputs) { if (output.omniScript) { this.tx.addOutput(coin_base_1.base.fromHex(output.omniScript), 0); } else { const outputScript = bitcoin.address.toOutputScript(output.address, this.network); this.tx.addOutput(outputScript, output.value); } } if (this.hardware) { return this.tx.toHex(); } for (let i = 0; i < eckeys.length; i++) { const eckey = eckeys[i]; let ecPub; if (eckey) { ecPub = private2public(eckey); } else { if (!this.hardware) { ecPub = coin_base_1.base.fromHex(this.inputs[i].publicKey); } } let hash; let hashType = bitcoin.Transaction.SIGHASH_ALL; if (this.bitcoinCash) { const script = bitcoin.payments.p2pkh({ pubkey: ecPub }) .output; hashType = bitcoin.Transaction.SIGHASH_ALL | bitcoin.Transaction.SIGHASH_BITCOINCASHBIP143; const value = this.inputs[i].value || 0; hash = this.tx.hashForCashSignature(i, script, value, hashType); let signature; if (hashArray) { hashArray.push(coin_base_1.base.toHex(hash)); signature = Buffer.alloc(64, 0); } else { signature = sign(hash, eckey); } const payment = bitcoin.payments.p2pkh({ output: script, pubkey: ecPub, signature: bitcoin.script.signature.encode(signature, hashType), }); if (payment.input) { this.tx.ins[i].script = payment.input; } } else { const addressType = getAddressType(this.inputs[i].address, this.network); if (this.fakeKey) { const { witness, script } = fakeSign(addressType); if (witness !== undefined) { this.tx.ins[i].witness = witness; } if (script !== undefined) { this.tx.ins[i].script = script; } continue; } if (addressType === 'legacy') { const script = bitcoin.payments.p2pkh({ pubkey: ecPub }) .output; hash = this.tx.hashForSignature(i, script, hashType); let signature; if (hashArray) { hashArray.push(coin_base_1.base.toHex(hash)); signature = Buffer.alloc(64, 0); } else { signature = sign(hash, eckey); } const payment = bitcoin.payments.p2pkh({ output: script, pubkey: ecPub, signature: bitcoin.script.signature.encode(signature, hashType), }); if (payment.input) { this.tx.ins[i].script = payment.input; } } else if (addressType === 'segwit_taproot') { const prevOutScripts = this.inputs.map((o) => bitcoin.address.toOutputScript(o.address, this.network)); const values = this.inputs.map((o) => o.value); hash = this.tx.hashForWitnessV1(i, prevOutScripts, values, bitcoin.Transaction.SIGHASH_DEFAULT); let signature; if (hashArray) { hashArray.push(coin_base_1.base.toHex(hash)); signature = Buffer.alloc(64, 0); } else { const tweakedPrivKey = taproot.taprootTweakPrivKey(coin_base_1.base.fromHex(eckey)); signature = Buffer.from(schnorr.sign(hash, tweakedPrivKey, coin_base_1.base.randomBytes(32))); } this.tx.ins[i].witness = [Buffer.from(signature)]; } else { const pubHash = bcrypto.hash160(ecPub); const prevOutScript = Buffer.of(0x19, 0x76, 0xa9, 0x14, ...pubHash, 0x88, 0xac); const value = this.inputs[i].value || 0; hash = this.tx.hashForWitness(i, prevOutScript, value, hashType); let signature; if (hashArray) { hashArray.push(coin_base_1.base.toHex(hash)); signature = Buffer.alloc(64, 0); } else { signature = sign(hash, eckey); } this.tx.ins[i].witness = []; this.tx.ins[i].witness.push(bitcoin.script.signature.encode(signature, hashType)); this.tx.ins[i].witness.push(ecPub); const redeemScript = Buffer.of(0x16, 0, 20, ...pubHash); if (addressType !== 'segwit_native') { this.tx.ins[i].script = redeemScript; } } } } return this.tx.toHex(); } virtualSize() { return (0, sigcost_1.countAdjustedVsize)(this.tx, this.inputs.map((a) => a.address), this.network || bitcoin.networks.bitcoin); } } exports.TxBuild = TxBuild; function signBtc(utxoTx, privateKey, network, hashArray, hardware, changeOnly) { const inputs = utxoTx.inputs; const outputs = utxoTx.outputs; const changeAddress = utxoTx.address; const feePerB = utxoTx.feePerB || 10; const dustSize = utxoTx.dustSize || 546; network = network || bitcoin.networks.bitcoin; if (utxoTx.memo) { let buf = coin_base_1.base.isHexString(utxoTx.memo) ? coin_base_1.base.fromHex(utxoTx.memo) : Buffer.from(coin_base_1.base.toUtf8(utxoTx.memo)); if (buf.length > 80) { throw new Error('data after op_return is too long'); } } let fakePrivateKey = privateKey; if (!fakePrivateKey) { fakePrivateKey = private2Wif(coin_base_1.base.fromHex('853fd8960ff34838208d662ecd3b9f8cf413e13e0f74f95e554f8089f5058db0'), network); } if (changeOnly) { let { inputAmount, outputAmount, virtualSize } = calculateTxSize(inputs, outputs, changeAddress, fakePrivateKey, network, dustSize, false, utxoTx.memo, utxoTx.memoPos); return (inputAmount - outputAmount - Math.ceil(virtualSize * feePerB)).toString(); } let { inputAmount, outputAmount, virtualSize } = calculateTxSize(inputs, outputs, changeAddress, fakePrivateKey, network, dustSize, false, utxoTx.memo, utxoTx.memoPos); let changeAmount = inputAmount - outputAmount - Math.ceil(virtualSize * feePerB); let txBuild = new TxBuild(2, network, false, hardware); for (let i = 0; i < inputs.length; i++) { let input = inputs[i]; const inputPrivKey = input.privateKey || privateKey; const inputAddress = input.address || changeAddress; txBuild.addInput(input.txId, input.vOut, inputPrivKey, inputAddress, input.reedScript, input.amount, input.publicKey, input.sequence); } if (utxoTx.memo && utxoTx.memoPos == 0) { txBuild.addOutput('', 0, coin_base_1.base.toHex(bscript.compile([ops_1.OPS.OP_RETURN].concat(coin_base_1.base.isHexString(utxoTx.memo) ? coin_base_1.base.fromHex(utxoTx.memo) : Buffer.from(coin_base_1.base.toUtf8(utxoTx.memo)))))); } for (let i = 0; i < outputs.length; i++) { let output = outputs[i]; txBuild.addOutput(output.address, output.amount, output.omniScript); if (utxoTx.memo && utxoTx.memoPos && txBuild.outputs.length == utxoTx.memoPos) { txBuild.addOutput('', 0, coin_base_1.base.toHex(bscript.compile([ops_1.OPS.OP_RETURN].concat(coin_base_1.base.isHexString(utxoTx.memo) ? coin_base_1.base.fromHex(utxoTx.memo) : Buffer.from(coin_base_1.base.toUtf8(utxoTx.memo)))))); } } if (changeAmount > dustSize) { txBuild.addOutput(changeAddress, changeAmount); if (utxoTx.memo && utxoTx.memoPos && txBuild.outputs.length == utxoTx.memoPos) { txBuild.addOutput('', 0, coin_base_1.base.toHex(bscript.compile([ops_1.OPS.OP_RETURN].concat(coin_base_1.base.isHexString(utxoTx.memo) ? coin_base_1.base.fromHex(utxoTx.memo) : Buffer.from(coin_base_1.base.toUtf8(utxoTx.memo)))))); } } if (utxoTx.memo && (utxoTx.memoPos == undefined || utxoTx.memoPos < 0 || utxoTx.memoPos > txBuild.outputs.length)) { txBuild.addOutput('', 0, coin_base_1.base.toHex(bscript.compile([ops_1.OPS.OP_RETURN].concat(coin_base_1.base.isHexString(utxoTx.memo) ? coin_base_1.base.fromHex(utxoTx.memo) : Buffer.from(coin_base_1.base.toUtf8(utxoTx.memo)))))); } return txBuild.build(hashArray); } exports.signBtc = signBtc; function getAddressType(address, network) { let decodeBase58; let decodeBech32; try { decodeBase58 = (0, address_1.fromBase58Check)(address); } catch (e) { } if (decodeBase58) { if (decodeBase58.version === network.pubKeyHash) return 'legacy'; if (decodeBase58.version === network.scriptHash) return 'segwit_nested'; } else { try { decodeBech32 = (0, address_1.fromBech32)(address); } catch (e) { } if (decodeBech32) { if (decodeBech32.prefix !== network.bech32) throw new Error(address + ' has an invalid prefix'); if (decodeBech32.version === 0) { return 'segwit_native'; } else if (decodeBech32.version === 1) { return 'segwit_taproot'; } } } return 'legacy'; } exports.getAddressType = getAddressType; function signBch(utxoTx, privateKey, network, hashArray, hardware) { const inputs = utxoTx.inputs; const outputs = utxoTx.outputs; const changeAddress = utxoTx.address; const feePerB = utxoTx.feePerB || 10; const dustSize = utxoTx.dustSize || 546; network = network || bitcoin.networks.bitcoin; let fakePrivateKey = privateKey; if (!fakePrivateKey) { fakePrivateKey = private2Wif(coin_base_1.base.fromHex('853fd8960ff34838208d662ecd3b9f8cf413e13e0f74f95e554f8089f5058db0'), network); } let { inputAmount, outputAmount, virtualSize } = calculateBchTxSize(inputs, outputs, changeAddress, fakePrivateKey, network, dustSize); let changeAmount = inputAmount - outputAmount - virtualSize * feePerB; let txBuild = new TxBuild(2, network, true, hardware); for (let i = 0; i < inputs.length; i++) { let input = inputs[i]; txBuild.addInput(input.txId, input.vOut, privateKey, changeAddress, undefined, input.amount, input.publicKey, input.sequence); } for (let i = 0; i < outputs.length; i++) { let output = outputs[i]; txBuild.addOutput(output.address, output.amount); } if (changeAmount > dustSize) { txBuild.addOutput(changeAddress, changeAmount); } return txBuild.build(hashArray); } exports.signBch = signBch; function calculateTxSize(inputs, outputs, changeAddress, privateKey, network, dustSize, hardware, memo, pos) { let preTxBuild = new TxBuild(2, network, false, hardware, true); let inputAmount = 0; for (let i = 0; i < inputs.length; i++) { let input = inputs[i]; const inputPrivKey = input.privateKey || privateKey; const inputAddress = input.address || changeAddress; preTxBuild.addInput(input.txId, input.vOut, inputPrivKey, inputAddress, input.reedScript, input.amount, input.publicKey, input.sequence); inputAmount = inputAmount + (input.amount || 0); } if (memo && pos == 0) { preTxBuild.addOutput('', 0, coin_base_1.base.toHex(bscript.compile([ops_1.OPS.OP_RETURN].concat(coin_base_1.base.isHexString(memo) ? coin_base_1.base.fromHex(memo) : Buffer.from(coin_base_1.base.toUtf8(memo)))))); } let outputAmount = 0; for (let i = 0; i < outputs.length; i++) { let output = outputs[i]; preTxBuild.addOutput(output.address, output.amount, output.omniScript); outputAmount = outputAmount + output.amount; if (memo && pos && preTxBuild.outputs.length == pos) { preTxBuild.addOutput('', 0, coin_base_1.base.toHex(bscript.compile([ops_1.OPS.OP_RETURN].concat(coin_base_1.base.isHexString(memo) ? coin_base_1.base.fromHex(memo) : Buffer.from(coin_base_1.base.toUtf8(memo)))))); } } if (inputAmount - outputAmount > dustSize) { preTxBuild.addOutput(changeAddress, inputAmount - outputAmount); if (memo && pos && preTxBuild.outputs.length == pos) { preTxBuild.addOutput('', 0, coin_base_1.base.toHex(bscript.compile([ops_1.OPS.OP_RETURN].concat(coin_base_1.base.isHexString(memo) ? coin_base_1.base.fromHex(memo) : Buffer.from(coin_base_1.base.toUtf8(memo)))))); } } if (memo && (pos == undefined || pos < 0 || pos > preTxBuild.outputs.length)) { preTxBuild.addOutput('', 0, coin_base_1.base.toHex(bscript.compile([ops_1.OPS.OP_RETURN].concat(coin_base_1.base.isHexString(memo) ? coin_base_1.base.fromHex(memo) : Buffer.from(coin_base_1.base.toUtf8(memo)))))); } let txHex = preTxBuild.build(); const virtualSize = preTxBuild.virtualSize(); return { inputAmount, outputAmount, virtualSize, txHex, }; } exports.calculateTxSize = calculateTxSize; function calculateBchTxSize(inputs, outputs, changeAddress, privateKey, network, dustSize, hardware) { let preTxBuild = new TxBuild(2, network, true, hardware); let inputAmount = 0; for (let i = 0; i < inputs.length; i++) { let input = inputs[i]; preTxBuild.addInput(input.txId, input.vOut, privateKey, changeAddress, undefined, input.amount, input.publicKey, input.sequence); inputAmount = inputAmount + (input.amount || 0); } let outputAmount = 0; for (let i = 0; i < outputs.length; i++) { let output = outputs[i]; preTxBuild.addOutput(output.address, output.amount); outputAmount = outputAmount + output.amount; } if (inputAmount - outputAmount > dustSize) { preTxBuild.addOutput(changeAddress, inputAmount - outputAmount); } let txHex = preTxBuild.build(); const virtualSize = preTxBuild.tx.virtualSize(); return { inputAmount, outputAmount, virtualSize, txHex, }; } exports.calculateBchTxSize = calculateBchTxSize; function getMPCTransaction(raw, sigs, bitcoinCash) { const transaction = bitcoinjs_lib_1.Transaction.fromBuffer(coin_base_1.base.fromHex(raw), false); for (let i = 0; i < transaction.ins.length; i++) { const input = transaction.ins[i]; const signature = coin_base_1.base.fromHex(sigs[i]); let hashType = bitcoin.Transaction.SIGHASH_ALL; if (bitcoinCash) { hashType = bitcoin.Transaction.SIGHASH_ALL | bitcoin.Transaction.SIGHASH_BITCOINCASHBIP143; const payment = bitcoin.payments.p2pkh({ input: input.script, }); const paymentNew = bitcoin.payments.p2pkh({ pubkey: payment.pubkey, signature: bitcoin.script.signature.encode(signature, hashType), }); if (paymentNew.input) { input.script = paymentNew.input; } } else { let addressType; if (input.witness.length === 2) { addressType = 'segwit_native'; } else if (input.witness.length === 1) { addressType = 'segwit_taproot'; } else if (input.witness.length === 0) { addressType = 'legacy'; } else { throw Error('unknown witness length'); } if (addressType === 'legacy') { const payment = bitcoin.payments.p2pkh({ input: input.script, }); const paymentNew = bitcoin.payments.p2pkh({ pubkey: payment.pubkey, signature: bitcoin.script.signature.encode(signature, hashType), }); if (paymentNew.input) { input.script = paymentNew.input; } } else if (addressType === 'segwit_taproot') { input.witness = [signature]; } else { input.witness[0] = bitcoin.script.signature.encode(signature, hashType); } } } return transaction.toHex(); } exports.getMPCTransaction = getMPCTransaction; function ValidSignedTransaction(signedTx, utxoInputs, network) { const transaction = bitcoinjs_lib_1.Transaction.fromBuffer(coin_base_1.base.fromHex(signedTx), false); if (!utxoInputs) { return transaction; } for (let i = 0; i < transaction.ins.length; i++) { const input = transaction.ins[i]; const utxo = utxoInputs[i]; let addressType; if (input.witness.length === 2) { addressType = 'segwit_native'; } else if (input.witness.length === 1) { addressType = 'segwit_taproot'; } else if (input.witness.length === 0) { addressType = 'legacy'; } else { throw Error('unknown witness length'); } if (addressType === 'legacy') { const chunks = bscript.decompile(input.script); const signature = chunks[0]; const pubKey = chunks[1]; const signatureData = bitcoin.script.signature.decode(signature); const prevOutScript = bitcoin.address.toOutputScript(utxo.address, network); const hash = transaction.hashForSignature(i, prevOutScript, signatureData.hashType); if (!crypto_lib_1.signUtil.secp256k1.verifyWithNoRecovery(hash, signatureData.signature, pubKey)) { throw Error('signature error'); } } else if (addressType === 'segwit_native') { const signature = input.witness[0]; const pubKey = input.witness[1]; const signatureData = bitcoin.script.signature.decode(signature); const prevOutScript = Buffer.of(0x19, 0x76, 0xa9, 0x14, ...bcrypto.hash160(pubKey), 0x88, 0xac); const hash = transaction.hashForWitness(i, prevOutScript, utxo.value, signatureData.hashType); if (!crypto_lib_1.signUtil.secp256k1.verifyWithNoRecovery(hash, signatureData.signature, pubKey)) { throw Error('signature error'); } } else { const signature = input.witness[0]; const prevOutScripts = utxoInputs.map((o) => bitcoin.address.toOutputScript(o.address, network)); const values = utxoInputs.map((o) => o.value); const hash = transaction.hashForWitnessV1(i, prevOutScripts, values, bitcoin.Transaction.SIGHASH_DEFAULT); const tweakedPubKey = taproot.taprootTweakPubkey(coin_base_1.base.fromHex(utxo.publicKey).slice(1))[0]; if (!schnorr.verify(coin_base_1.base.toHex(signature), coin_base_1.base.toHex(hash), coin_base_1.base.toHex(tweakedPubKey))) { throw Error('signature error'); } } } for (let in1 of transaction.ins) { in1.hash = coin_base_1.base.reverseBuffer(in1.hash); } return transaction; } exports.ValidSignedTransaction = ValidSignedTransaction; function estimateBtcFee(utxoTx, network) { const inputs = utxoTx.inputs; const outputs = utxoTx.outputs; const feePerB = utxoTx.feePerB || 10; const dustSize = utxoTx.dustSize || 546; network = network || bitcoin.networks.bitcoin; const fakePrivateKey = private2Wif(coin_base_1.base.fromHex('853fd8960ff34838208d662ecd3b9f8cf413e13e0f74f95e554f8089f5058db0'), network); let { virtualSize } = calculateTxSize(inputs, outputs, utxoTx.address, fakePrivateKey, network, dustSize, false, utxoTx.memo); return Math.ceil(virtualSize * feePerB); } exports.estimateBtcFee = estimateBtcFee; function estimateBchFee(utxoTx, network) { const inputs = utxoTx.inputs; const outputs = utxoTx.outputs; const feePerB = utxoTx.feePerB || 10; const dustSize = utxoTx.dustSize || 546; network = network || bitcoin.networks.bitcoin; const fakePrivateKey = private2Wif(coin_base_1.base.fromHex('853fd8960ff34838208d662ecd3b9f8cf413e13e0f74f95e554f8089f5058db0'), network); let { virtualSize } = calculateBchTxSize(inputs, outputs, utxoTx.address, fakePrivateKey, network, dustSize); return virtualSize * feePerB; } exports.estimateBchFee = estimateBchFee; function fakeSign(addressType) { let witness; let script; if (addressType === 'segwit_taproot') { witness = [Buffer.alloc(64)]; } else if (addressType === 'legacy') { script = Buffer.alloc(107); } else { witness = [Buffer.alloc(72), Buffer.alloc(33)]; if (addressType === 'segwit_nested') { script = Buffer.alloc(23); } } return { witness, script }; } exports.fakeSign = fakeSign; //# sourceMappingURL=txBuild.js.map