UNPKG

bgoldjs-lib-bit

Version:

Client-side Bitcoin Gold JavaScript library

1,446 lines (1,445 loc) 46.1 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); 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 crypto_1 = require('./crypto'); const ecpair_1 = require('./ecpair'); const networks_1 = require('./networks'); const payments = require('./payments'); const bscript = require('./script'); const transaction_1 = require('./transaction'); /** * 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, }; /** * 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 { 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, // Old TransactionBuilder 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); } 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; } 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: bufferutils_1.cloneBuffer(input.hash), index: input.index, sequence: input.sequence, })); } get txOutputs() { return this.__CACHE.__TX.outs.map(output => { let address; try { address = address_1.fromOutputScript(output.script, this.opts.network); } catch (_) {} return { script: 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]`, ); } 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 = address_1.toOutputScript(address, network); outputData = Object.assign(outputData, { script }); } 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() { 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 = getFinalScripts) { const input = utils_1.checkForInput(this.data.inputs, inputIndex); 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; } getInputType(inputIndex) { const input = 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 = utils_1.checkForInput(this.data.inputs, inputIndex); return pubkeyInInput(pubkey, input, inputIndex, this.__CACHE); } inputHasHDKey(inputIndex, root) { const input = utils_1.checkForInput(this.data.inputs, inputIndex); const derivationIsMine = bip32DerivationIsMine(root); return ( !!input.bip32Derivation && input.bip32Derivation.some(derivationIsMine) ); } outputHasPubkey(outputIndex, pubkey) { const output = utils_1.checkForOutput(this.data.outputs, outputIndex); return pubkeyInOutput(pubkey, output, outputIndex, this.__CACHE); } outputHasHDKey(outputIndex, root) { const output = utils_1.checkForOutput(this.data.outputs, outputIndex); const derivationIsMine = bip32DerivationIsMine(root); return ( !!output.bip32Derivation && output.bip32Derivation.some(derivationIsMine) ); } validateSignaturesOfAllInputs() { 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), ); return results.reduce((final, res) => res === true && final, true); } validateSignaturesOfInput(inputIndex, pubkey) { const input = this.data.inputs[inputIndex]; const partialSig = (input || {}).partialSig; if (!input || !partialSig || partialSig.length < 1) throw new Error('No signatures to validate'); 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, undefined, this.opts.network.forkId, ) : { hash: hashCache, script: scriptCache }; sighashCache = sig.hashType; hashCache = hash; scriptCache = script; checkScriptForPubkey(pSig.pubkey, script, 'verify'); const keypair = ecpair_1.fromPublicKey(pSig.pubkey); results.push(keypair.verify(hash, sig.signature)); } return results.every(res => res === 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 = [transaction_1.Transaction.SIGHASH_ALL], ) { 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 = [transaction_1.Transaction.SIGHASH_ALL], ) { 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 = [transaction_1.Transaction.SIGHASH_ALL], ) { if (!keyPair || !keyPair.publicKey) throw new Error('Need Signer to sign input'); const { hash, sighashType } = getHashAndSighashType( this.data.inputs, inputIndex, keyPair.publicKey, this.__CACHE, sighashTypes, this.opts.network.forkId, ); const partialSig = [ { pubkey: keyPair.publicKey, signature: bscript.signature.encode(keyPair.sign(hash), sighashType), }, ]; this.data.updateInput(inputIndex, { partialSig }); return this; } signInputAsync( inputIndex, keyPair, sighashTypes = [transaction_1.Transaction.SIGHASH_ALL], ) { return Promise.resolve().then(() => { if (!keyPair || !keyPair.publicKey) throw new Error('Need Signer to sign input'); const { hash, sighashType } = getHashAndSighashType( this.data.inputs, inputIndex, keyPair.publicKey, this.__CACHE, sighashTypes, this.opts.network.forkId, ); return Promise.resolve(keyPair.sign(hash)).then(signature => { const partialSig = [ { pubkey: keyPair.publicKey, signature: bscript.signature.encode(signature, sighashType), }, ]; this.data.updateInput(inputIndex, { partialSig }); }); }); } 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); this.data.updateInput(inputIndex, updateData); if (updateData.nonWitnessUtxo) { addNonWitnessTxCache( this.__CACHE, this.data.inputs[inputIndex], inputIndex, ); } return this; } updateOutput(outputIndex, updateData) { 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' ? 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); case 'cltvmultisig': const p2cltvms = payments.p2cltvms({ output: script }); return hasSigs(p2cltvms.m, input.partialSig, p2cltvms.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 = ecpair_1.fromPublicKey(pkey, { compressed: true }) .publicKey; 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 isPaymentFactory(payment) { return script => { try { payment({ output: script }); return true; } catch (err) { return false; } }; } const isP2CLTVMS = isPaymentFactory(payments.p2cltvms); const isP2MS = isPaymentFactory(payments.p2ms); const isP2PK = isPaymentFactory(payments.p2pk); const isP2PKH = isPaymentFactory(payments.p2pkh); const isP2WPKH = isPaymentFactory(payments.p2wpkh); const isP2WSHScript = isPaymentFactory(payments.p2wsh); const isP2SHScript = isPaymentFactory(payments.p2sh); 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 => { let throws = false; let pSigs = []; if ((input.partialSig || []).length === 0) { if (!input.finalScriptSig && !input.finalScriptWitness) return; pSigs = getPsigsFromInputFinalScripts(input); } else { pSigs = input.partialSig; } pSigs.forEach(pSig => { const { hashType } = bscript.signature.decode(pSig.signature); const whitelist = []; const isAnyoneCanPay = hashType & transaction_1.Transaction.SIGHASH_ANYONECANPAY; if (isAnyoneCanPay) whitelist.push('addInput'); const hashMod = hashType & 0x1f; switch (hashMod) { case transaction_1.Transaction.SIGHASH_ALL: break; case transaction_1.Transaction.SIGHASH_SINGLE: case transaction_1.Transaction.SIGHASH_NONE: whitelist.push('addOutput'); whitelist.push('setInputSequence'); break; } if (whitelist.indexOf(action) === -1) { throws = true; } }); 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 (!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 = 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 = witnessStackToScriptWitness(p2wsh.witness); } else { finalScriptWitness = 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, forkId, ) { const input = utils_1.checkForInput(inputs, inputIndex); const { hash, sighashType, script } = getHashForSig( inputIndex, input, cache, false, sighashTypes, forkId, ); checkScriptForPubkey(pubkey, script, 'sign'); return { hash, sighashType, }; } function getHashForSig( inputIndex, input, cache, forValidate, sighashTypes, forkId, ) { const unsignedTx = cache.__TX; const sighashType = input.sighashType || transaction_1.Transaction.SIGHASH_ALL; 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}`, ); } 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, ); const useForkId = !!(sighashType & transaction_1.Transaction.SIGHASH_FORKID); const sighashTypeWithForkId = useForkId ? sighashType | ((forkId || 0) << 8) : sighashType; if (['p2sh-p2wsh', 'p2wsh'].indexOf(type) >= 0) { hash = unsignedTx.hashForWitnessV0( inputIndex, meaningfulScript, prevout.value, sighashTypeWithForkId, ); } else if (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, sighashTypeWithForkId, ); } 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 the old ' + 'TransactionBuilder class 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' + '*********************', ); if (useForkId) { hash = unsignedTx.hashForWitnessV0( inputIndex, meaningfulScript, prevout.value, sighashTypeWithForkId, ); } else { hash = unsignedTx.hashForSignature( inputIndex, meaningfulScript, sighashType, ); } } return { script: meaningfulScript, sighashType, hash, }; } function getPayment(script, scriptType, partialSig) { let payment; switch (scriptType) { case 'multisig': const sigs = getSortedSigs(script, partialSig, scriptType); payment = payments.p2ms({ output: script, signatures: sigs, }); break; case 'cltvmultisig': const cltvSigs = getSortedSigs(script, partialSig, scriptType); payment = payments.p2cltvms({ output: script, signatures: cltvSigs, }); 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 getPsigsFromInputFinalScripts(input) { const scriptItems = !input.finalScriptSig ? [] : bscript.decompile(input.finalScriptSig) || []; const witnessItems = !input.finalScriptWitness ? [] : bscript.decompile(input.finalScriptWitness) || []; return scriptItems .concat(witnessItems) .filter(item => { return Buffer.isBuffer(item) && bscript.isCanonicalScriptSignature(item); }) .map(sig => ({ signature: sig })); } 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 || isP2WPKH(res.script)) { res.isSegwit = true; } return res; } function getSignersFromHD(inputIndex, inputs, hdKeyPair) { const input = 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 getSortedSigsForMsPayment(p2ms, partialSig) { // 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 getSortedSigs(script, partialSig, type) { const payment = type === 'multisig' ? payments.p2ms({ output: script }) : payments.p2cltvms({ output: script }); return getSortedSigsForMsPayment(payment, partialSig); } 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) { const components = []; if (sighashType & transaction_1.Transaction.SIGHASH_ANYONECANPAY) { components.push('SIGHASH_ANYONECANPAY'); } const sigMod = sighashType & 0x1f; switch (sigMod) { case transaction_1.Transaction.SIGHASH_ALL: components.push('SIGHASH_ALL'); break; case transaction_1.Transaction.SIGHASH_SINGLE: components.push('SIGHASH_SINGLE'); break; case transaction_1.Transaction.SIGHASH_NONE: components.push('SIGHASH_NONE'); break; } if (sighashType & transaction_1.Transaction.SIGHASH_FORKID) { components.push('SIGHASH_FORKID'); } return components.join(' | '); } function witnessStackToScriptWitness(witness) { let buffer = Buffer.allocUnsafe(0); function writeSlice(slice) { buffer = Buffer.concat([buffer, Buffer.from(slice)]); } function writeVarInt(i) { const currentLen = buffer.length; const varintLen = varuint.encodingLength(i); buffer = Buffer.concat([buffer, Buffer.allocUnsafe(varintLen)]); varuint.encode(i, buffer, currentLen); } function writeVarSlice(slice) { writeVarInt(slice.length); writeSlice(slice); } function writeVector(vector) { writeVarInt(vector.length); vector.forEach(writeVarSlice); } writeVector(witness); return buffer; } 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) { inputAmount += input.witnessUtxo.value; } else if (input.nonWitnessUtxo) { const nwTx = nonWitnessUtxoTxFromCache(cache, input, idx); const vout = tx.ins[idx].index; const out = nwTx.outs[vout]; inputAmount += out.value; } }); const outputAmount = tx.outs.reduce((total, o) => total + o.value, 0); const fee = inputAmount - outputAmount; if (fee < 0) { throw new Error('Outputs are spending more than Inputs'); } const bytes = tx.virtualSize(); cache.__FEE = fee; cache.__EXTRACTED_TX = tx; cache.__FEE_RATE = Math.floor(fee / bytes); } function nonWitnessUtxoTxFromCache(cache, input, inputIndex) { const c = cache.__NON_WITNESS_UTXO_TX_CACHE; if (!c[inputIndex]) { addNonWitnessTxCache(cache, input, inputIndex); } return c[inputIndex]; } function getScriptFromUtxo(inputIndex, input, cache) { if (input.witnessUtxo !== undefined) { return input.witnessUtxo.script; } else if (input.nonWitnessUtxo !== undefined) { const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache( cache, input, inputIndex, ); return nonWitnessUtxoTx.outs[cache.__TX.ins[inputIndex].index].script; } else { throw new Error("Can't find pubkey in input without Utxo data"); } } function pubkeyInInput(pubkey, input, inputIndex, cache) { const script = getScriptFromUtxo(inputIndex, input, cache); const { meaningfulScript } = getMeaningfulScript( script, inputIndex, 'input', input.redeemScript, input.witnessScript, ); return pubkeyInScript(pubkey, meaningfulScript); } function pubkeyInOutput(pubkey, output, outputIndex, cache) { const script = cache.__TX.outs[outputIndex].script; const { meaningfulScript } = getMeaningfulScript( script, outputIndex, 'output', output.redeemScript, output.witnessScript, ); return pubkeyInScript(pubkey, meaningfulScript); } function redeemFromFinalScriptSig(finalScript) { if (!finalScript) return; const decomp = bscript.decompile(finalScript); if (!decomp) return; const lastItem = decomp[decomp.length - 1]; if ( !Buffer.isBuffer(lastItem) || isPubkeyLike(lastItem) || isSigLike(lastItem) ) return; const sDecomp = bscript.decompile(lastItem); if (!sDecomp) return; return lastItem; } function redeemFromFinalWitnessScript(finalScript) { if (!finalScript) return; const decomp = scriptWitnessToWitnessStack(finalScript); const lastItem = decomp[decomp.length - 1]; if (isPubkeyLike(lastItem)) return; const sDecomp = bscript.decompile(lastItem); if (!sDecomp) return; return lastItem; } function isPubkeyLike(buf) { return buf.length === 33 && bscript.isCanonicalPubKey(buf); } function isSigLike(buf) { return bscript.isCanonicalScriptSignature(buf); } function getMeaningfulScript( script, index, ioType, redeemScript, witnessScript, ) { const isP2SH = isP2SHScript(script); const isP2SHP2WSH = isP2SH && redeemScript && isP2WSHScript(redeemScript); const isP2WSH = isP2WSHScript(script); if (isP2SH && redeemScript === undefined) throw new Error('scriptPubkey is P2SH but redeemScript missing'); if ((isP2WSH || isP2SHP2WSH) && witnessScript === undefined) throw new Error( 'scriptPubkey or redeemScript is P2WSH but witnessScript missing', ); let meaningfulScript; if (isP2SHP2WSH) { meaningfulScript = witnessScript; checkRedeemScript(index, script, redeemScript, ioType); checkWitnessScript(index, redeemScript, witnessScript, ioType); checkInvalidP2WSH(meaningfulScript); } else if (isP2WSH) { meaningfulScript = witnessScript; checkWitnessScript(index, script, witnessScript, ioType); checkInvalidP2WSH(meaningfulScript); } else if (isP2SH) { meaningfulScript = redeemScript; checkRedeemScript(index, script, redeemScript, ioType); } else { meaningfulScript = script; } return { meaningfulScript, type: isP2SHP2WSH ? 'p2sh-p2wsh' : isP2SH ? 'p2sh' : isP2WSH ? 'p2wsh' : 'raw', }; } function checkInvalidP2WSH(script) { if (isP2WPKH(script) || isP2SHScript(script)) { throw new Error('P2WPKH or P2SH can not be contained within P2WSH'); } } function pubkeyInScript(pubkey, script) { const pubkeyHash = crypto_1.hash160(pubkey); const decompiled = bscript.decompile(script); if (decompiled === null) throw new Error('Unknown script error'); return decompiled.some(element => { if (typeof element === 'number') return false; return element.equals(pubkey) || element.equals(pubkeyHash); }); } function classifyScript(script) { if (isP2WPKH(script)) return 'witnesspubkeyhash'; if (isP2PKH(script)) return 'pubkeyhash'; if (isP2MS(script)) return 'multisig'; if (isP2PK(script)) return 'pubkey'; if (isP2CLTVMS(script)) return 'cltvmultisig'; return 'nonstandard'; } function range(n) { return [...Array(n).keys()]; }