UNPKG

bitcoinjs-lib

Version:

Client-side Bitcoin JavaScript library

1,567 lines (1,566 loc) 55.2 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); exports.Psbt = void 0; const bip174_1 = require('bip174'); const varuint = require('bip174/src/lib/converter/varint'); const utils_1 = require('bip174/src/lib/utils'); const address_1 = require('./address'); const bufferutils_1 = require('./bufferutils'); const networks_1 = require('./networks'); const payments = require('./payments'); const bip341_1 = require('./payments/bip341'); const bscript = require('./script'); const transaction_1 = require('./transaction'); const bip371_1 = require('./psbt/bip371'); const psbtutils_1 = require('./psbt/psbtutils'); /** * These are the default arguments for a Psbt instance. */ const DEFAULT_OPTS = { /** * A bitcoinjs Network object. This is only used if you pass an `address` * parameter to addOutput. Otherwise it is not needed and can be left default. */ network: networks_1.bitcoin, /** * When extractTransaction is called, the fee rate is checked. * THIS IS NOT TO BE RELIED ON. * It is only here as a last ditch effort to prevent sending a 500 BTC fee etc. */ maximumFeeRate: 5000, // satoshi per byte }; /** * Psbt class can parse and generate a PSBT binary based off of the BIP174. * There are 6 roles that this class fulfills. (Explained in BIP174) * * Creator: This can be done with `new Psbt()` * * Updater: This can be done with `psbt.addInput(input)`, `psbt.addInputs(inputs)`, * `psbt.addOutput(output)`, `psbt.addOutputs(outputs)` when you are looking to * add new inputs and outputs to the PSBT, and `psbt.updateGlobal(itemObject)`, * `psbt.updateInput(itemObject)`, `psbt.updateOutput(itemObject)` * addInput requires hash: Buffer | string; and index: number; as attributes * and can also include any attributes that are used in updateInput method. * addOutput requires script: Buffer; and value: number; and likewise can include * data for updateOutput. * For a list of what attributes should be what types. Check the bip174 library. * Also, check the integration tests for some examples of usage. * * Signer: There are a few methods. signAllInputs and signAllInputsAsync, which will search all input * information for your pubkey or pubkeyhash, and only sign inputs where it finds * your info. Or you can explicitly sign a specific input with signInput and * signInputAsync. For the async methods you can create a SignerAsync object * and use something like a hardware wallet to sign with. (You must implement this) * * Combiner: psbts can be combined easily with `psbt.combine(psbt2, psbt3, psbt4 ...)` * the psbt calling combine will always have precedence when a conflict occurs. * Combine checks if the internal bitcoin transaction is the same, so be sure that * all sequences, version, locktime, etc. are the same before combining. * * Input Finalizer: This role is fairly important. Not only does it need to construct * the input scriptSigs and witnesses, but it SHOULD verify the signatures etc. * Before running `psbt.finalizeAllInputs()` please run `psbt.validateSignaturesOfAllInputs()` * Running any finalize method will delete any data in the input(s) that are no longer * needed due to the finalized scripts containing the information. * * Transaction Extractor: This role will perform some checks before returning a * Transaction object. Such as fee rate not being larger than maximumFeeRate etc. */ 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 = bip174_1.Psbt.fromBuffer(buffer, transactionFromBuffer); const psbt = new Psbt(opts, psbtBase); checkTxForDupeIns(psbt.__CACHE.__TX, psbt.__CACHE); return psbt; } constructor(opts = {}, data = new bip174_1.Psbt(new PsbtTransaction())) { this.data = data; // set defaults 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, // Psbt's predecessor (TransactionBuilder - now removed) behavior // was to not confirm input values before signing. // Even though we highly encourage people to get // the full parent transaction to verify values, the ability to // sign non-segwit inputs without the full transaction was often // requested. So the only way to activate is to use @ts-ignore. // We will disable exporting the Psbt when unsafe sign is active. // because it is not BIP174 compliant. __UNSAFE_SIGN_NONSEGWIT: false, }; if (this.data.inputs.length === 0) this.setVersion(2); // Make data hidden when enumerating 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() { // TODO: more efficient cloning const res = Psbt.fromBuffer(this.data.toBuffer()); res.opts = JSON.parse(JSON.stringify(this.opts)); return res; } setMaximumFeeRate(satoshiPerByte) { check32Bit(satoshiPerByte); // 42.9 BTC per byte IS excessive... so throw 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); // making sure we have at least one 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.`, ); // Check key spend first. Increased privacy and reduced block space. 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); // making sure we have at least one 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); let validationResultCount = 0; if (tapKeySig && tapKeyHash) { const isValidTapkeySig = validator( tapKeyHash.pubkey, tapKeyHash.hash, trimTaprootSig(tapKeySig), ); if (!isValidTapkeySig) return false; validationResultCount++; } 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, trimTaprootSig(tapSig.signature), ); if (!isValidTapScriptSig) return false; validationResultCount++; } } } return validationResultCount > 0; } 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'); // TODO: Add a pubkey/pubkeyhash cache to each input // as input information is added, then eventually // optimize this method. 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')); // TODO: Add a pubkey/pubkeyhash cache to each input // as input information is added, then eventually // optimize this method. 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) { 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, undefined, 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.`); } _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; } } exports.Psbt = Psbt; /** * This function is needed to pass to the bip174 base class's fromBuffer. * It takes the "transaction buffer" portion of the psbt buffer and returns a * Transaction (From the bip174 library) interface. */ const transactionFromBuffer = buffer => new PsbtTransaction(buffer); /** * This class implements the Transaction interface from bip174 library. * It contains a bitcoinjs-lib Transaction object. */ 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; // Wow, the payments API is very handy 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, }; } 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 a non-witness UTXO is provided, its hash must match the hash specified in the prevout 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)) { // P2WPKH uses the P2PKH template for prevoutScript when signing const signingScript = payments.p2pkh({ hash: meaningfulScript.slice(2), }).output; hash = unsignedTx.hashForWitnessV0( inputIndex, signingScript, prevout.value, sighashType, ); } else { // non-segwit 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 predecessor " + '(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 key = getPrevoutTaprootKey(inputIndex, input, cache); if (key) { allPublicKeys.push(key); } } 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 getPrevoutTaprootKey(inputIndex, input, cache) { const { script } = getScriptAndAmountFromUtxo(inputIndex, input, cache); return (0, psbtutils_1.isP2TR)(script) ? script.subarray(2, 34) : null; } function trimTaprootSig(signature) { return signature.length === 64 ? signature : signature.subarray(0, 64); } 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 = getPrevoutTaprootKey(inputIndex, input, cache) || Buffer.from([]); 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 || []) .filter(tapLeaf => (0, psbtutils_1.pubkeyInScript)(pubkey, tapLeaf.script)) .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, sighashType, 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 }); // for each pubkey in order of p2ms script return p2ms.pubkeys .map(pk => { // filter partialSig array by pubkey being equal return ( partialSig.filter(ps => { return ps.pubkey.equals(pk); })[0] || {} ).signature; // Any pubkey without a match will return undefined // this last filter removes all the undefined items in the array. }) .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_UTXO_BUF_CACHE[selfIndex] = newBuf; return newBuf; } }, set(data) { self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex] = data; }, }); } function inputFinalizeGetAmts(inputs, tx, cache, mustFinalize) { let inputAmount = 0; inputs.forEach((input, idx) => { if (mustFinalize && input.finalScriptSig) tx.ins[idx].script = input.finalScriptSig; if (mustFinalize && input.finalScriptWitness) { tx.ins[idx].witness = scriptWitnessToWitnessStack( input.finalScriptWitness, ); } if (input.witnessUtxo) {