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

1,201 lines 56.2 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.getHashAndSighashType = exports.Psbt = void 0; const psbt_1 = require("./bip174/psbt"); const varuint = __importStar(require("./bip174/converter/varint")); const utils_1 = require("./bip174/utils"); const address_1 = require("./address"); const bufferutils_1 = require("./bufferutils"); const networks_1 = require("./networks"); const payments = __importStar(require("./payments")); const bip341_1 = require("./payments/bip341"); const bscript = __importStar(require("./script")); const transaction_1 = require("./transaction"); const bip371_1 = require("./psbt/bip371"); const psbtutils_1 = require("./psbt/psbtutils"); const crypto_lib_1 = require("@okxweb3/crypto-lib"); const script_1 = require("./script"); const taproot_1 = require("../taproot"); const schnorr = crypto_lib_1.signUtil.schnorr.secp256k1.schnorr; const DEFAULT_OPTS = { network: networks_1.bitcoin, maximumFeeRate: 5000, }; class Psbt { static fromBase64(data, opts = {}) { const buffer = Buffer.from(data, 'base64'); return this.fromBuffer(buffer, opts); } static fromHex(data, opts = {}) { const buffer = Buffer.from(data, 'hex'); return this.fromBuffer(buffer, opts); } static fromBuffer(buffer, opts = {}) { const psbtBase = psbt_1.Psbt.fromBuffer(buffer, transactionFromBuffer); const psbt = new Psbt(opts, psbtBase); checkTxForDupeIns(psbt.__CACHE.__TX, psbt.__CACHE); return psbt; } constructor(opts = {}, data = new psbt_1.Psbt(new PsbtTransaction())) { this.data = data; this.opts = Object.assign({}, DEFAULT_OPTS, opts); this.__CACHE = { __NON_WITNESS_UTXO_TX_CACHE: [], __NON_WITNESS_UTXO_BUF_CACHE: [], __TX_IN_CACHE: {}, __TX: this.data.globalMap.unsignedTx.tx, __UNSAFE_SIGN_NONSEGWIT: false, }; if (this.data.inputs.length === 0) this.setVersion(2); const dpew = (obj, attr, enumerable, writable) => Object.defineProperty(obj, attr, { enumerable, writable, }); dpew(this, '__CACHE', false, true); dpew(this, 'opts', false, true); } get inputCount() { return this.data.inputs.length; } get version() { return this.__CACHE.__TX.version; } set version(version) { this.setVersion(version); } get locktime() { return this.__CACHE.__TX.locktime; } set locktime(locktime) { this.setLocktime(locktime); } get txInputs() { return this.__CACHE.__TX.ins.map(input => ({ hash: (0, bufferutils_1.cloneBuffer)(input.hash), index: input.index, sequence: input.sequence, })); } get txOutputs() { return this.__CACHE.__TX.outs.map(output => { let address; try { address = (0, address_1.fromOutputScript)(output.script, this.opts.network); } catch (_) { } return { script: (0, bufferutils_1.cloneBuffer)(output.script), value: output.value, address, }; }); } combine(...those) { this.data.combine(...those.map(o => o.data)); return this; } clone() { const res = Psbt.fromBuffer(this.data.toBuffer()); res.opts = JSON.parse(JSON.stringify(this.opts)); return res; } setMaximumFeeRate(satoshiPerByte) { check32Bit(satoshiPerByte); this.opts.maximumFeeRate = satoshiPerByte; } setVersion(version) { check32Bit(version); checkInputsForPartialSig(this.data.inputs, 'setVersion'); const c = this.__CACHE; c.__TX.version = version; c.__EXTRACTED_TX = undefined; return this; } setLocktime(locktime) { check32Bit(locktime); checkInputsForPartialSig(this.data.inputs, 'setLocktime'); const c = this.__CACHE; c.__TX.locktime = locktime; c.__EXTRACTED_TX = undefined; return this; } setInputSequence(inputIndex, sequence) { check32Bit(sequence); checkInputsForPartialSig(this.data.inputs, 'setInputSequence'); const c = this.__CACHE; if (c.__TX.ins.length <= inputIndex) { throw new Error('Input index too high'); } c.__TX.ins[inputIndex].sequence = sequence; c.__EXTRACTED_TX = undefined; return this; } addInputs(inputDatas) { inputDatas.forEach(inputData => this.addInput(inputData)); return this; } addInput(inputData) { if (arguments.length > 1 || !inputData || inputData.hash === undefined || inputData.index === undefined) { throw new Error(`Invalid arguments for Psbt.addInput. ` + `Requires single object with at least [hash] and [index]`); } (0, bip371_1.checkTaprootInputFields)(inputData, inputData, 'addInput'); checkInputsForPartialSig(this.data.inputs, 'addInput'); if (inputData.witnessScript) checkInvalidP2WSH(inputData.witnessScript); const c = this.__CACHE; this.data.addInput(inputData); const txIn = c.__TX.ins[c.__TX.ins.length - 1]; checkTxInputCache(c, txIn); const inputIndex = this.data.inputs.length - 1; const input = this.data.inputs[inputIndex]; if (input.nonWitnessUtxo) { addNonWitnessTxCache(this.__CACHE, input, inputIndex); } c.__FEE = undefined; c.__FEE_RATE = undefined; c.__EXTRACTED_TX = undefined; return this; } addOutputs(outputDatas) { outputDatas.forEach(outputData => this.addOutput(outputData)); return this; } addOutput(outputData) { if (arguments.length > 1 || !outputData || outputData.value === undefined || (outputData.address === undefined && outputData.script === undefined)) { throw new Error(`Invalid arguments for Psbt.addOutput. ` + `Requires single object with at least [script or address] and [value]`); } checkInputsForPartialSig(this.data.inputs, 'addOutput'); const { address } = outputData; if (typeof address === 'string') { const { network } = this.opts; const script = (0, address_1.toOutputScript)(address, network); outputData = Object.assign(outputData, { script }); } (0, bip371_1.checkTaprootOutputFields)(outputData, outputData, 'addOutput'); const c = this.__CACHE; this.data.addOutput(outputData); c.__FEE = undefined; c.__FEE_RATE = undefined; c.__EXTRACTED_TX = undefined; return this; } extractTransaction(disableFeeCheck) { if (!this.data.inputs.every(isFinalized)) throw new Error('Not finalized'); const c = this.__CACHE; if (!disableFeeCheck) { checkFees(this, c, this.opts); } if (c.__EXTRACTED_TX) return c.__EXTRACTED_TX; const tx = c.__TX.clone(); inputFinalizeGetAmts(this.data.inputs, tx, c, true); return tx; } getFeeRate() { return getTxCacheValue('__FEE_RATE', 'fee rate', this.data.inputs, this.__CACHE); } getFee() { return getTxCacheValue('__FEE', 'fee', this.data.inputs, this.__CACHE); } finalizeAllInputs() { (0, utils_1.checkForInput)(this.data.inputs, 0); range(this.data.inputs.length).forEach(idx => this.finalizeInput(idx)); return this; } finalizeInput(inputIndex, finalScriptsFunc) { const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex); if ((0, bip371_1.isTaprootInput)(input)) return this._finalizeTaprootInput(inputIndex, input, undefined, finalScriptsFunc); return this._finalizeInput(inputIndex, input, finalScriptsFunc); } finalizeTaprootInput(inputIndex, tapLeafHashToFinalize, finalScriptsFunc = bip371_1.tapScriptFinalizer) { const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex); if ((0, bip371_1.isTaprootInput)(input)) return this._finalizeTaprootInput(inputIndex, input, tapLeafHashToFinalize, finalScriptsFunc); throw new Error(`Cannot finalize input #${inputIndex}. Not Taproot.`); } _finalizeInput(inputIndex, input, finalScriptsFunc = getFinalScripts) { const { script, isP2SH, isP2WSH, isSegwit } = getScriptFromInput(inputIndex, input, this.__CACHE); if (!script) throw new Error(`No script found for input #${inputIndex}`); checkPartialSigSighashes(input); const { finalScriptSig, finalScriptWitness } = finalScriptsFunc(inputIndex, input, script, isSegwit, isP2SH, isP2WSH); if (finalScriptSig) this.data.updateInput(inputIndex, { finalScriptSig }); if (finalScriptWitness) this.data.updateInput(inputIndex, { finalScriptWitness }); if (!finalScriptSig && !finalScriptWitness) throw new Error(`Unknown error finalizing input #${inputIndex}`); this.data.clearFinalizedInput(inputIndex); return this; } _finalizeTaprootInput(inputIndex, input, tapLeafHashToFinalize, finalScriptsFunc = bip371_1.tapScriptFinalizer) { if (!input.witnessUtxo) throw new Error(`Cannot finalize input #${inputIndex}. Missing withness utxo.`); if (input.tapKeySig) { const payment = payments.p2tr({ output: input.witnessUtxo.script, signature: input.tapKeySig, }); const finalScriptWitness = (0, psbtutils_1.witnessStackToScriptWitness)(payment.witness); this.data.updateInput(inputIndex, { finalScriptWitness }); } else { const { finalScriptWitness } = finalScriptsFunc(inputIndex, input, tapLeafHashToFinalize); this.data.updateInput(inputIndex, { finalScriptWitness }); } this.data.clearFinalizedInput(inputIndex); return this; } getInputType(inputIndex) { const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex); const script = getScriptFromUtxo(inputIndex, input, this.__CACHE); const result = getMeaningfulScript(script, inputIndex, 'input', input.redeemScript || redeemFromFinalScriptSig(input.finalScriptSig), input.witnessScript || redeemFromFinalWitnessScript(input.finalScriptWitness)); const type = result.type === 'raw' ? '' : result.type + '-'; const mainType = classifyScript(result.meaningfulScript); return (type + mainType); } inputHasPubkey(inputIndex, pubkey) { const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex); return pubkeyInInput(pubkey, input, inputIndex, this.__CACHE); } inputHasHDKey(inputIndex, root) { const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex); const derivationIsMine = bip32DerivationIsMine(root); return (!!input.bip32Derivation && input.bip32Derivation.some(derivationIsMine)); } outputHasPubkey(outputIndex, pubkey) { const output = (0, utils_1.checkForOutput)(this.data.outputs, outputIndex); return pubkeyInOutput(pubkey, output, outputIndex, this.__CACHE); } outputHasHDKey(outputIndex, root) { const output = (0, utils_1.checkForOutput)(this.data.outputs, outputIndex); const derivationIsMine = bip32DerivationIsMine(root); return (!!output.bip32Derivation && output.bip32Derivation.some(derivationIsMine)); } validateSignaturesOfAllInputs(validator) { (0, utils_1.checkForInput)(this.data.inputs, 0); const results = range(this.data.inputs.length).map(idx => this.validateSignaturesOfInput(idx, validator)); return results.reduce((final, res) => res === true && final, true); } validateSignaturesOfInput(inputIndex, validator, pubkey) { const input = this.data.inputs[inputIndex]; if ((0, bip371_1.isTaprootInput)(input)) return this.validateSignaturesOfTaprootInput(inputIndex, validator, pubkey); return this._validateSignaturesOfInput(inputIndex, validator, pubkey); } _validateSignaturesOfInput(inputIndex, validator, pubkey) { const input = this.data.inputs[inputIndex]; const partialSig = (input || {}).partialSig; if (!input || !partialSig || partialSig.length < 1) throw new Error('No signatures to validate'); if (typeof validator !== 'function') throw new Error('Need validator function to validate signatures'); const mySigs = pubkey ? partialSig.filter(sig => sig.pubkey.equals(pubkey)) : partialSig; if (mySigs.length < 1) throw new Error('No signatures for this pubkey'); const results = []; let hashCache; let scriptCache; let sighashCache; for (const pSig of mySigs) { const sig = bscript.signature.decode(pSig.signature); const { hash, script } = sighashCache !== sig.hashType ? getHashForSig(inputIndex, Object.assign({}, input, { sighashType: sig.hashType }), this.__CACHE, true) : { hash: hashCache, script: scriptCache }; sighashCache = sig.hashType; hashCache = hash; scriptCache = script; checkScriptForPubkey(pSig.pubkey, script, 'verify'); results.push(validator(pSig.pubkey, hash, sig.signature)); } return results.every(res => res === true); } validateSignaturesOfTaprootInput(inputIndex, validator, pubkey) { const input = this.data.inputs[inputIndex]; const tapKeySig = (input || {}).tapKeySig; const tapScriptSig = (input || {}).tapScriptSig; if (!input && !tapKeySig && !(tapScriptSig && !tapScriptSig.length)) throw new Error('No signatures to validate'); if (typeof validator !== 'function') throw new Error('Need validator function to validate signatures'); pubkey = pubkey && (0, bip371_1.toXOnly)(pubkey); const allHashses = pubkey ? getTaprootHashesForSig(inputIndex, input, this.data.inputs, pubkey, this.__CACHE) : getAllTaprootHashesForSig(inputIndex, input, this.data.inputs, this.__CACHE); if (!allHashses.length) throw new Error('No signatures for this pubkey'); const tapKeyHash = allHashses.find(h => !!h.leafHash); if (tapKeySig && tapKeyHash) { const isValidTapkeySig = validator(tapKeyHash.pubkey, tapKeyHash.hash, tapKeySig); if (!isValidTapkeySig) return false; } if (tapScriptSig) { for (const tapSig of tapScriptSig) { const tapSigHash = allHashses.find(h => tapSig.pubkey.equals(h.pubkey)); if (tapSigHash) { const isValidTapScriptSig = validator(tapSig.pubkey, tapSigHash.hash, tapSig.signature); if (!isValidTapScriptSig) return false; } } } return true; } signAllInputsHD(hdKeyPair, sighashTypes = [transaction_1.Transaction.SIGHASH_ALL]) { if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) { throw new Error('Need HDSigner to sign input'); } const results = []; for (const i of range(this.data.inputs.length)) { try { this.signInputHD(i, hdKeyPair, sighashTypes); results.push(true); } catch (err) { results.push(false); } } if (results.every(v => v === false)) { throw new Error('No inputs were signed'); } return this; } signAllInputsHDAsync(hdKeyPair, sighashTypes = [transaction_1.Transaction.SIGHASH_ALL]) { return new Promise((resolve, reject) => { if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) { return reject(new Error('Need HDSigner to sign input')); } const results = []; const promises = []; for (const i of range(this.data.inputs.length)) { promises.push(this.signInputHDAsync(i, hdKeyPair, sighashTypes).then(() => { results.push(true); }, () => { results.push(false); })); } return Promise.all(promises).then(() => { if (results.every(v => v === false)) { return reject(new Error('No inputs were signed')); } resolve(); }); }); } signInputHD(inputIndex, hdKeyPair, sighashTypes = [transaction_1.Transaction.SIGHASH_ALL]) { if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) { throw new Error('Need HDSigner to sign input'); } const signers = getSignersFromHD(inputIndex, this.data.inputs, hdKeyPair); signers.forEach(signer => this.signInput(inputIndex, signer, sighashTypes)); return this; } signInputHDAsync(inputIndex, hdKeyPair, sighashTypes = [transaction_1.Transaction.SIGHASH_ALL]) { return new Promise((resolve, reject) => { if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) { return reject(new Error('Need HDSigner to sign input')); } const signers = getSignersFromHD(inputIndex, this.data.inputs, hdKeyPair); const promises = signers.map(signer => this.signInputAsync(inputIndex, signer, sighashTypes)); return Promise.all(promises) .then(() => { resolve(); }) .catch(reject); }); } signAllInputs(keyPair, sighashTypes) { if (!keyPair || !keyPair.publicKey) throw new Error('Need Signer to sign input'); const results = []; for (const i of range(this.data.inputs.length)) { try { this.signInput(i, keyPair, sighashTypes); results.push(true); } catch (err) { results.push(false); } } if (results.every(v => v === false)) { throw new Error('No inputs were signed'); } return this; } signAllInputsAsync(keyPair, sighashTypes) { return new Promise((resolve, reject) => { if (!keyPair || !keyPair.publicKey) return reject(new Error('Need Signer to sign input')); const results = []; const promises = []; for (const [i] of this.data.inputs.entries()) { promises.push(this.signInputAsync(i, keyPair, sighashTypes).then(() => { results.push(true); }, () => { results.push(false); })); } return Promise.all(promises).then(() => { if (results.every(v => v === false)) { return reject(new Error('No inputs were signed')); } resolve(); }); }); } signInput(inputIndex, keyPair, sighashTypes, tapLeafHashToSign) { if (!keyPair || !keyPair.publicKey) throw new Error('Need Signer to sign input'); const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex); if ((0, bip371_1.isTaprootInput)(input)) { return this._signTaprootInput(inputIndex, input, keyPair, tapLeafHashToSign, sighashTypes); } return this._signInput(inputIndex, keyPair, sighashTypes); } signTaprootInput(inputIndex, keyPair, tapLeafHashToSign, sighashTypes) { if (!keyPair || !keyPair.publicKey) throw new Error('Need Signer to sign input'); const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex); if ((0, bip371_1.isTaprootInput)(input)) return this._signTaprootInput(inputIndex, input, keyPair, tapLeafHashToSign, sighashTypes); throw new Error(`Input #${inputIndex} is not of type Taproot.`); } getHashAndSighashType(inputIndex, publicKey, sighashTypes = [transaction_1.Transaction.SIGHASH_ALL]) { const { hash, sighashType } = getHashAndSighashType(this.data.inputs, inputIndex, publicKey, this.__CACHE, sighashTypes); return { hash, sighashType }; } _signInput(inputIndex, keyPair, sighashTypes = [transaction_1.Transaction.SIGHASH_ALL]) { const { hash, sighashType } = getHashAndSighashType(this.data.inputs, inputIndex, keyPair.publicKey, this.__CACHE, sighashTypes); const partialSig = [ { pubkey: keyPair.publicKey, signature: bscript.signature.encode(keyPair.sign(hash), sighashType), }, ]; this.data.updateInput(inputIndex, { partialSig }); return this; } _signTaprootInput(inputIndex, input, keyPair, tapLeafHashToSign, allowedSighashTypes = [transaction_1.Transaction.SIGHASH_DEFAULT]) { const hashesForSig = this.checkTaprootHashesForSig(inputIndex, input, keyPair, tapLeafHashToSign, allowedSighashTypes); const tapKeySig = hashesForSig .filter(h => !h.leafHash) .map(h => (0, bip371_1.serializeTaprootSignature)(keyPair.signSchnorr(h.hash), input.sighashType))[0]; const tapScriptSig = hashesForSig .filter(h => !!h.leafHash) .map(h => ({ pubkey: (0, bip371_1.toXOnly)(keyPair.publicKey), signature: (0, bip371_1.serializeTaprootSignature)(keyPair.signSchnorr(h.hash), input.sighashType), leafHash: h.leafHash, })); if (tapKeySig) { this.data.updateInput(inputIndex, { tapKeySig }); } if (tapScriptSig.length) { this.data.updateInput(inputIndex, { tapScriptSig }); } return this; } signInputAsync(inputIndex, keyPair, sighashTypes) { return Promise.resolve().then(() => { if (!keyPair || !keyPair.publicKey) throw new Error('Need Signer to sign input'); const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex); if ((0, bip371_1.isTaprootInput)(input)) return this._signTaprootInputAsync(inputIndex, input, keyPair, undefined, sighashTypes); return this._signInputAsync(inputIndex, keyPair, sighashTypes); }); } signTaprootInputAsync(inputIndex, keyPair, tapLeafHash, sighashTypes) { return Promise.resolve().then(() => { if (!keyPair || !keyPair.publicKey) throw new Error('Need Signer to sign input'); const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex); if ((0, bip371_1.isTaprootInput)(input)) return this._signTaprootInputAsync(inputIndex, input, keyPair, tapLeafHash, sighashTypes); throw new Error(`Input #${inputIndex} is not of type Taproot.`); }); } _signInputAsync(inputIndex, keyPair, sighashTypes = [transaction_1.Transaction.SIGHASH_ALL]) { const { hash, sighashType } = getHashAndSighashType(this.data.inputs, inputIndex, keyPair.publicKey, this.__CACHE, sighashTypes); return Promise.resolve(keyPair.sign(hash)).then(signature => { const partialSig = [ { pubkey: keyPair.publicKey, signature: bscript.signature.encode(signature, sighashType), }, ]; this.data.updateInput(inputIndex, { partialSig }); }); } async _signTaprootInputAsync(inputIndex, input, keyPair, tapLeafHash, sighashTypes = [transaction_1.Transaction.SIGHASH_DEFAULT]) { const hashesForSig = this.checkTaprootHashesForSig(inputIndex, input, keyPair, tapLeafHash, sighashTypes); const signaturePromises = []; const tapKeyHash = hashesForSig.filter(h => !h.leafHash)[0]; if (tapKeyHash) { const tapKeySigPromise = Promise.resolve(keyPair.signSchnorr(tapKeyHash.hash)).then(sig => { return { tapKeySig: (0, bip371_1.serializeTaprootSignature)(sig, input.sighashType) }; }); signaturePromises.push(tapKeySigPromise); } const tapScriptHashes = hashesForSig.filter(h => !!h.leafHash); if (tapScriptHashes.length) { const tapScriptSigPromises = tapScriptHashes.map(tsh => { return Promise.resolve(keyPair.signSchnorr(tsh.hash)).then(signature => { const tapScriptSig = [ { pubkey: (0, bip371_1.toXOnly)(keyPair.publicKey), signature: (0, bip371_1.serializeTaprootSignature)(signature, input.sighashType), leafHash: tsh.leafHash, }, ]; return { tapScriptSig }; }); }); signaturePromises.push(...tapScriptSigPromises); } return Promise.all(signaturePromises).then(results => { results.forEach(v => this.data.updateInput(inputIndex, v)); }); } checkTaprootHashesForSig(inputIndex, input, keyPair, tapLeafHashToSign, allowedSighashTypes) { if (typeof keyPair.signSchnorr !== 'function') throw new Error(`Need Schnorr Signer to sign taproot input #${inputIndex}.`); const hashesForSig = getTaprootHashesForSig(inputIndex, input, this.data.inputs, keyPair.publicKey, this.__CACHE, tapLeafHashToSign, allowedSighashTypes); if (!hashesForSig || !hashesForSig.length) throw new Error(`Can not sign for input #${inputIndex} with the key ${keyPair.publicKey.toString('hex')}`); return hashesForSig; } toBuffer() { checkCache(this.__CACHE); return this.data.toBuffer(); } toHex() { checkCache(this.__CACHE); return this.data.toHex(); } toBase64() { checkCache(this.__CACHE); return this.data.toBase64(); } updateGlobal(updateData) { this.data.updateGlobal(updateData); return this; } updateInput(inputIndex, updateData) { if (updateData.witnessScript) checkInvalidP2WSH(updateData.witnessScript); (0, bip371_1.checkTaprootInputFields)(this.data.inputs[inputIndex], updateData, 'updateInput'); this.data.updateInput(inputIndex, updateData); if (updateData.nonWitnessUtxo) { addNonWitnessTxCache(this.__CACHE, this.data.inputs[inputIndex], inputIndex); } return this; } updateOutput(outputIndex, updateData) { const outputData = this.data.outputs[outputIndex]; (0, bip371_1.checkTaprootOutputFields)(outputData, updateData, 'updateOutput'); this.data.updateOutput(outputIndex, updateData); return this; } addUnknownKeyValToGlobal(keyVal) { this.data.addUnknownKeyValToGlobal(keyVal); return this; } addUnknownKeyValToInput(inputIndex, keyVal) { this.data.addUnknownKeyValToInput(inputIndex, keyVal); return this; } addUnknownKeyValToOutput(outputIndex, keyVal) { this.data.addUnknownKeyValToOutput(outputIndex, keyVal); return this; } clearFinalizedInput(inputIndex) { this.data.clearFinalizedInput(inputIndex); return this; } verify(pubBuf, witness) { const allowedSighashTypes = [ transaction_1.Transaction.SIGHASH_SINGLE | transaction_1.Transaction.SIGHASH_ANYONECANPAY, transaction_1.Transaction.SIGHASH_ALL, transaction_1.Transaction.SIGHASH_DEFAULT ]; for (let i = 0; i < this.inputCount; i++) { if ((0, bip371_1.isTaprootInput)(this.data.inputs[i])) { const tweakKey = Buffer.from((0, taproot_1.taprootTweakPubkey)(pubBuf.slice(1))[0]); const bufferReader = new bufferutils_1.BufferReader(witness); const vector = bufferReader.readVector(); const signer = { publicKey: tweakKey, sign(hash) { return Buffer.alloc(64); }, signSchnorr(hash) { return Buffer.alloc(64); }, }; const hashesForSig = this.checkTaprootHashesForSig(i, this.data.inputs[i], signer, undefined, allowedSighashTypes); const messageToSign = hashesForSig.filter(h => !h.leafHash)[0].hash; if (!schnorr.verify(crypto_lib_1.base.toHex(vector[0]), crypto_lib_1.base.toHex(messageToSign), crypto_lib_1.base.toHex(tweakKey))) { return false; } } else { const { hash } = getHashAndSighashType(this.data.inputs, i, pubBuf, this.__CACHE, allowedSighashTypes); const bufferReader = new bufferutils_1.BufferReader(witness); const vector = bufferReader.readVector(); const signature = script_1.signature.decode(vector[0]).signature; const witnessPubKey = vector[1]; if (!pubBuf.equals(witnessPubKey)) { throw Error("pubKey error"); } if (!crypto_lib_1.signUtil.secp256k1.verifyWithNoRecovery(hash, signature, pubBuf)) { return false; } } } return true; } } exports.Psbt = Psbt; const transactionFromBuffer = (buffer) => new PsbtTransaction(buffer); class PsbtTransaction { constructor(buffer = Buffer.from([2, 0, 0, 0, 0, 0, 0, 0, 0, 0])) { this.tx = transaction_1.Transaction.fromBuffer(buffer); checkTxEmpty(this.tx); Object.defineProperty(this, 'tx', { enumerable: false, writable: true, }); } getInputOutputCounts() { return { inputCount: this.tx.ins.length, outputCount: this.tx.outs.length, }; } addInput(input) { if (input.hash === undefined || input.index === undefined || (!Buffer.isBuffer(input.hash) && typeof input.hash !== 'string') || typeof input.index !== 'number') { throw new Error('Error adding input.'); } const hash = typeof input.hash === 'string' ? (0, bufferutils_1.reverseBuffer)(Buffer.from(input.hash, 'hex')) : input.hash; this.tx.addInput(hash, input.index, input.sequence); } addOutput(output) { if (output.script === undefined || output.value === undefined || !Buffer.isBuffer(output.script) || typeof output.value !== 'number') { throw new Error('Error adding output.'); } this.tx.addOutput(output.script, output.value); } toBuffer() { return this.tx.toBuffer(); } } function canFinalize(input, script, scriptType) { switch (scriptType) { case 'pubkey': case 'pubkeyhash': case 'witnesspubkeyhash': return hasSigs(1, input.partialSig); case 'multisig': const p2ms = payments.p2ms({ output: script }); return hasSigs(p2ms.m, input.partialSig, p2ms.pubkeys); default: return false; } } function checkCache(cache) { if (cache.__UNSAFE_SIGN_NONSEGWIT !== false) { throw new Error('Not BIP174 compliant, can not export'); } } function hasSigs(neededSigs, partialSig, pubkeys) { if (!partialSig) return false; let sigs; if (pubkeys) { sigs = pubkeys .map(pkey => { const pubkey = compressPubkey(pkey); return partialSig.find(pSig => pSig.pubkey.equals(pubkey)); }) .filter(v => !!v); } else { sigs = partialSig; } if (sigs.length > neededSigs) throw new Error('Too many signatures'); return sigs.length === neededSigs; } function isFinalized(input) { return !!input.finalScriptSig || !!input.finalScriptWitness; } function bip32DerivationIsMine(root) { return (d) => { if (!d.masterFingerprint.equals(root.fingerprint)) return false; if (!root.derivePath(d.path).publicKey.equals(d.pubkey)) return false; return true; }; } function check32Bit(num) { if (typeof num !== 'number' || num !== Math.floor(num) || num > 0xffffffff || num < 0) { throw new Error('Invalid 32 bit integer'); } } function checkFees(psbt, cache, opts) { const feeRate = cache.__FEE_RATE || psbt.getFeeRate(); const vsize = cache.__EXTRACTED_TX.virtualSize(); const satoshis = feeRate * vsize; if (feeRate >= opts.maximumFeeRate) { throw new Error(`Warning: You are paying around ${(satoshis / 1e8).toFixed(8)} in ` + `fees, which is ${feeRate} satoshi per byte for a transaction ` + `with a VSize of ${vsize} bytes (segwit counted as 0.25 byte per ` + `byte). Use setMaximumFeeRate method to raise your threshold, or ` + `pass true to the first arg of extractTransaction.`); } } function checkInputsForPartialSig(inputs, action) { inputs.forEach(input => { const throws = (0, bip371_1.isTaprootInput)(input) ? (0, bip371_1.checkTaprootInputForSigs)(input, action) : (0, psbtutils_1.checkInputForSig)(input, action); if (throws) throw new Error('Can not modify transaction, signatures exist.'); }); } function checkPartialSigSighashes(input) { if (!input.sighashType || !input.partialSig) return; const { partialSig, sighashType } = input; partialSig.forEach(pSig => { const { hashType } = bscript.signature.decode(pSig.signature); if (sighashType !== hashType) { throw new Error('Signature sighash does not match input sighash type'); } }); } function checkScriptForPubkey(pubkey, script, action) { if (!(0, psbtutils_1.pubkeyInScript)(pubkey, script)) { throw new Error(`Can not ${action} for this input with the key ${pubkey.toString('hex')}`); } } function checkTxEmpty(tx) { const isEmpty = tx.ins.every(input => input.script && input.script.length === 0 && input.witness && input.witness.length === 0); if (!isEmpty) { throw new Error('Format Error: Transaction ScriptSigs are not empty'); } } function checkTxForDupeIns(tx, cache) { tx.ins.forEach(input => { checkTxInputCache(cache, input); }); } function checkTxInputCache(cache, input) { const key = (0, bufferutils_1.reverseBuffer)(Buffer.from(input.hash)).toString('hex') + ':' + input.index; if (cache.__TX_IN_CACHE[key]) throw new Error('Duplicate input detected.'); cache.__TX_IN_CACHE[key] = 1; } function scriptCheckerFactory(payment, paymentScriptName) { return (inputIndex, scriptPubKey, redeemScript, ioType) => { const redeemScriptOutput = payment({ redeem: { output: redeemScript }, }).output; if (!scriptPubKey.equals(redeemScriptOutput)) { throw new Error(`${paymentScriptName} for ${ioType} #${inputIndex} doesn't match the scriptPubKey in the prevout`); } }; } const checkRedeemScript = scriptCheckerFactory(payments.p2sh, 'Redeem script'); const checkWitnessScript = scriptCheckerFactory(payments.p2wsh, 'Witness script'); function getTxCacheValue(key, name, inputs, c) { if (!inputs.every(isFinalized)) throw new Error(`PSBT must be finalized to calculate ${name}`); if (key === '__FEE_RATE' && c.__FEE_RATE) return c.__FEE_RATE; if (key === '__FEE' && c.__FEE) return c.__FEE; let tx; let mustFinalize = true; if (c.__EXTRACTED_TX) { tx = c.__EXTRACTED_TX; mustFinalize = false; } else { tx = c.__TX.clone(); } inputFinalizeGetAmts(inputs, tx, c, mustFinalize); if (key === '__FEE_RATE') return c.__FEE_RATE; else if (key === '__FEE') return c.__FEE; } function getFinalScripts(inputIndex, input, script, isSegwit, isP2SH, isP2WSH) { const scriptType = classifyScript(script); if (!canFinalize(input, script, scriptType)) throw new Error(`Can not finalize input #${inputIndex}`); return prepareFinalScripts(script, scriptType, input.partialSig, isSegwit, isP2SH, isP2WSH); } function prepareFinalScripts(script, scriptType, partialSig, isSegwit, isP2SH, isP2WSH) { let finalScriptSig; let finalScriptWitness; const payment = getPayment(script, scriptType, partialSig); const p2wsh = !isP2WSH ? null : payments.p2wsh({ redeem: payment }); const p2sh = !isP2SH ? null : payments.p2sh({ redeem: p2wsh || payment }); if (isSegwit) { if (p2wsh) { finalScriptWitness = (0, psbtutils_1.witnessStackToScriptWitness)(p2wsh.witness); } else { finalScriptWitness = (0, psbtutils_1.witnessStackToScriptWitness)(payment.witness); } if (p2sh) { finalScriptSig = p2sh.input; } } else { if (p2sh) { finalScriptSig = p2sh.input; } else { finalScriptSig = payment.input; } } return { finalScriptSig, finalScriptWitness, }; } function getHashAndSighashType(inputs, inputIndex, pubkey, cache, sighashTypes) { const input = (0, utils_1.checkForInput)(inputs, inputIndex); const { hash, sighashType, script } = getHashForSig(inputIndex, input, cache, false, sighashTypes); checkScriptForPubkey(pubkey, script, 'sign'); return { hash, sighashType, }; } exports.getHashAndSighashType = getHashAndSighashType; function getHashForSig(inputIndex, input, cache, forValidate, sighashTypes) { const unsignedTx = cache.__TX; const sighashType = input.sighashType || transaction_1.Transaction.SIGHASH_ALL; checkSighashTypeAllowed(sighashType, sighashTypes); let hash; let prevout; if (input.nonWitnessUtxo) { const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache(cache, input, inputIndex); const prevoutHash = unsignedTx.ins[inputIndex].hash; const utxoHash = nonWitnessUtxoTx.getHash(); if (!prevoutHash.equals(utxoHash)) { throw new Error(`Non-witness UTXO hash for input #${inputIndex} doesn't match the hash specified in the prevout`); } const prevoutIndex = unsignedTx.ins[inputIndex].index; prevout = nonWitnessUtxoTx.outs[prevoutIndex]; } else if (input.witnessUtxo) { prevout = input.witnessUtxo; } else { throw new Error('Need a Utxo input item for signing'); } const { meaningfulScript, type } = getMeaningfulScript(prevout.script, inputIndex, 'input', input.redeemScript, input.witnessScript); if (['p2sh-p2wsh', 'p2wsh'].indexOf(type) >= 0) { hash = unsignedTx.hashForWitnessV0(inputIndex, meaningfulScript, prevout.value, sighashType); } else if ((0, psbtutils_1.isP2WPKH)(meaningfulScript)) { const signingScript = payments.p2pkh({ hash: meaningfulScript.slice(2) }) .output; hash = unsignedTx.hashForWitnessV0(inputIndex, signingScript, prevout.value, sighashType); } else { if (input.nonWitnessUtxo === undefined && cache.__UNSAFE_SIGN_NONSEGWIT === false) throw new Error(`Input #${inputIndex} has witnessUtxo but non-segwit script: ` + `${meaningfulScript.toString('hex')}`); if (!forValidate && cache.__UNSAFE_SIGN_NONSEGWIT !== false) console.warn('Warning: Signing non-segwit inputs without the full parent transaction ' + 'means there is a chance that a miner could feed you incorrect information ' + "to trick you into paying large fees. This behavior is the same as Psbt's predecesor " + '(TransactionBuilder - now removed) when signing non-segwit scripts. You are not ' + 'able to export this Psbt with toBuffer|toBase64|toHex since it is not ' + 'BIP174 compliant.\n*********************\nPROCEED WITH CAUTION!\n' + '*********************'); hash = unsignedTx.hashForSignature(inputIndex, meaningfulScript, sighashType); } return { script: meaningfulScript, sighashType, hash, }; } function getAllTaprootHashesForSig(inputIndex, input, inputs, cache) { const allPublicKeys = []; if (input.tapInternalKey) { const outputKey = (0, bip371_1.tweakInternalPubKey)(inputIndex, input); allPublicKeys.push(outputKey); } if (input.tapScriptSig) { const tapScriptPubkeys = input.tapScriptSig.map(tss => tss.pubkey); allPublicKeys.push(...tapScriptPubkeys); } const allHashes = allPublicKeys.map(pubicKey => getTaprootHashesForSig(inputIndex, input, inputs, pubicKey, cache)); return allHashes.flat(); } function getTaprootHashesForSig(inputIndex, input, inputs, pubkey, cache, tapLeafHashToSign, allowedSighashTypes) { const unsignedTx = cache.__TX; const sighashType = input.sighashType || transaction_1.Transaction.SIGHASH_DEFAULT; checkSighashTypeAllowed(sighashType, allowedSighashTypes); const prevOuts = inputs.map((i, index) => getScriptAndAmountFromUtxo(index, i, cache)); const signingScripts = prevOuts.map(o => o.script); const values = prevOuts.map(o => o.value); const hashes = []; if (input.tapInternalKey && !tapLeafHashToSign) { const outputKey = (0, bip371_1.tweakInternalPubKey)(inputIndex, input); if ((0, bip371_1.toXOnly)(pubkey).equals(outputKey)) { const tapKeyHash = unsignedTx.hashForWitnessV1(inputIndex, signingScripts, values, sighashType); hashes.push({ pubkey, hash: tapKeyHash }); } } const tapLeafHashes = (input.tapLeafScript || []) .map(tapLeaf => { const hash = (0, bip341_1.tapleafHash)({ output: tapLeaf.script, version: tapLeaf.leafVersion, }); return Object.assign({ hash }, tapLeaf); }) .filter(tapLeaf => !tapLeafHashToSign || tapLeafHashToSign.equals(tapLeaf.hash)) .map(tapLeaf => { const tapScriptHash = unsignedTx.hashForWitnessV1(inputIndex, signingScripts, values, transaction_1.Transaction.SIGHASH_DEFAULT, tapLeaf.hash); return { pubkey, hash: tapScriptHash, leafHash: tapLeaf.hash, }; }); return hashes.concat(tapLeafHashes); } function checkSighashTypeAllowed(sighashType, sighashTypes) { if (sighashTypes && sighashTypes.indexOf(sighashType) < 0) { const str = sighashTypeToString(sighashType); throw new Error(`Sighash type is not allowed. Retry the sign method passing the ` + `sighashTypes array of whitelisted types. Sighash type: ${str}`); } } function getPayment(script, scriptType, partialSig) { let payment; switch (scriptType) { case 'multisig': const sigs = getSortedSigs(script, partialSig); payment = payments.p2ms({ output: script, signatures: sigs, }); break; case 'pubkey': payment = payments.p2pk({ output: script, signature: partialSig[0].signature, }); break; case 'pubkeyhash': payment = payments.p2pkh({ output: script, pubkey: partialSig[0].pubkey, signature: partialSig[0].signature, }); break; case 'witnesspubkeyhash': payment = payments.p2wpkh({ output: script, pubkey: partialSig[0].pubkey, signature: partialSig[0].signature, }); break; } return payment; } function getScriptFromInput(inputIndex, input, cache) { const unsignedTx = cache.__TX; const res = { script: null, isSegwit: false, isP2SH: false, isP2WSH: false, }; res.isP2SH = !!input.redeemScript; res.isP2WSH = !!input.witnessScript; if (input.witnessScript) { res.script = input.witnessScript; } else if (input.redeemScript) { res.script = input.redeemScript; } else { if (input.nonWitnessUtxo) { const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache(cache, input, inputIndex); const prevoutIndex = unsignedTx.ins[inputIndex].index; res.script = nonWitnessUtxoTx.outs[prevoutIndex].script; } else if (input.witnessUtxo) { res.script = input.witnessUtxo.script; } } if (input.witnessScript || (0, psbtutils_1.isP2WPKH)(res.script)) { res.isSegwit = true; } return res; } function getSignersFromHD(inputIndex, inputs, hdKeyPair) { const input = (0, utils_1.checkForInput)(inputs, inputIndex); if (!input.bip32Derivation || input.bip32Derivation.length === 0) { throw new Error('Need bip32Derivation to sign with HD'); } const myDerivations = input.bip32Derivation .map(bipDv => { if (bipDv.masterFingerprint.equals(hdKeyPair.fingerprint)) { return bipDv; } else { return; } }) .filter(v => !!v); if (myDerivations.length === 0) { throw new Error('Need one bip32Derivation masterFingerprint to match the HDSigner fingerprint'); } const signers = myDerivations.map(bipDv => { const node = hdKeyPair.derivePath(bipDv.path); if (!bipDv.pubkey.equals(node.publicKey)) { throw new Error('pubkey did not match bip32Derivation'); } return node; }); return signers; } function getSortedSigs(script, partialSig) { const p2ms = payments.p2ms({ output: script }); return p2ms .pubkeys.map(pk => { return (partialSig.filter(ps => { return ps.pubkey.equals(pk); })[0] || {}).signature; }) .filter(v => !!v); } function scriptWitnessToWitnessStack(buffer) { let offset = 0; function readSlice(n) { offset += n; return buffer.slice(offset - n, offset); } function readVarInt() { const vi = varuint.decode(buffer, offset); offset += varuint.decode.bytes; return vi; } function readVarSlice() { return readSlice(readVarInt()); } function readVector() { const count = readVarInt(); const vector = []; for (let i = 0; i < count; i++) vector.push(readVarSlice()); return vector; } return readVector(); } function sighashTypeToString(sighashType) { let text = sighashType & transaction_1.Transaction.SIGHASH_ANYONECANPAY ? 'SIGHASH_ANYONECANPAY | ' : ''; const sigMod = sighashType & 0x1f; switch (sigMod) { case transaction_1.Transaction.SIGHASH_ALL: text += 'SIGHASH_ALL'; break; case transaction_1.Transaction.SIGHASH_SINGLE: text += 'SIGHASH_SINGLE'; break; case transaction_1.Transaction.SIGHASH_NONE: text += 'SIGHASH_NONE'; break; } return text; } function addNonWitnessTxCache(cache, input, inputIndex) { cache.__NON_WITNESS_UTXO_BUF_CACHE[inputIndex] = input.nonWitnessUtxo; const tx = transaction_1.Transaction.fromBuffer(input.nonWitnessUtxo); cache.__NON_WITNESS_UTXO_TX_CACHE[inputIndex] = tx; const self = cache; const selfIndex = inputIndex; delete input.nonWitnessUtxo; Object.defineProperty(input, 'nonWitnessUtxo', { enumerable: true, get() { const buf = self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex]; const txCache = self.__NON_WITNESS_UTXO_TX_CACHE[selfIndex]; if (buf !== undefined) { return buf; } else { const newBuf = txCache.toBuffer(); self.__NON_WITNESS_U