diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -501,6 +501,7 @@ net.cpp net_processing.cpp node/coin.cpp + node/psbt.cpp node/transaction.cpp noui.cpp policy/fees.cpp diff --git a/src/Makefile.am b/src/Makefile.am --- a/src/Makefile.am +++ b/src/Makefile.am @@ -181,6 +181,7 @@ netbase.h \ netmessagemaker.h \ node/coin.h \ + node/psbt.h \ node/transaction.h \ noui.h \ optional.h \ @@ -308,6 +309,7 @@ net.cpp \ net_processing.cpp \ node/coin.cpp \ + node/psbt.cpp \ node/transaction.cpp \ noui.cpp \ policy/fees.cpp \ diff --git a/src/node/psbt.h b/src/node/psbt.h new file mode 100644 --- /dev/null +++ b/src/node/psbt.h @@ -0,0 +1,54 @@ +// Copyright (c) 2009-2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_NODE_PSBT_H +#define BITCOIN_NODE_PSBT_H + +#include + +/** + * Holds an analysis of one input from a PSBT + */ +struct PSBTInputAnalysis { + //! Whether we have UTXO information for this input + bool has_utxo; + //! Whether the input has all required information including signatures + bool is_final; + //! Which of the BIP 174 roles needs to handle this input next + PSBTRole next; + + //! Pubkeys whose BIP32 derivation path is missing + std::vector missing_pubkeys; + //! Pubkeys whose signatures are missing + std::vector missing_sigs; + //! Hash160 of redeem script, if missing + uint160 missing_redeem_script; +}; + +/** + * Holds the results of AnalyzePSBT (miscellaneous information about a PSBT) + */ +struct PSBTAnalysis { + //! Estimated weight of the transaction + Optional estimated_vsize; + //! Estimated feerate (fee / weight) of the transaction + Optional estimated_feerate; + //! Amount of fee being paid by the transaction + Optional fee; + //! More information about the individual inputs of the transaction + std::vector inputs; + //! Which of the BIP 174 roles needs to handle the transaction next + PSBTRole next; +}; + +/** + * Provides helpful miscellaneous information about where a PSBT is in the + * signing workflow. + * + * @param[in] psbtx the PSBT to analyze + * @return A PSBTAnalysis with information about the provided PSBT. + */ +PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx); + +#endif // BITCOIN_NODE_PSBT_H diff --git a/src/node/psbt.cpp b/src/node/psbt.cpp new file mode 100644 --- /dev/null +++ b/src/node/psbt.cpp @@ -0,0 +1,136 @@ +// Copyright (c) 2009-2018 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include +#include +#include +#include +#include + +#include + +PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx) { + // Go through each input and build status + PSBTAnalysis result; + + bool calc_fee = true; + bool all_final = true; + bool only_missing_sigs = true; + bool only_missing_final = false; + Amount in_amt{Amount::zero()}; + + result.inputs.resize(psbtx.tx->vin.size()); + + for (size_t i = 0; i < psbtx.tx->vin.size(); ++i) { + PSBTInput &input = psbtx.inputs[i]; + PSBTInputAnalysis &input_analysis = result.inputs[i]; + + // Check for a UTXO + CTxOut utxo; + if (psbtx.GetInputUTXO(utxo, i)) { + in_amt += utxo.nValue; + input_analysis.has_utxo = true; + } else { + input_analysis.has_utxo = false; + input_analysis.is_final = false; + input_analysis.next = PSBTRole::UPDATER; + calc_fee = false; + } + + // Check if it is final + if (!utxo.IsNull() && !PSBTInputSigned(input)) { + input_analysis.is_final = false; + all_final = false; + + // Figure out what is missing + SignatureData outdata; + bool complete = SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, + SigHashType().withForkId(), &outdata); + + // Things are missing + if (!complete) { + input_analysis.missing_pubkeys = outdata.missing_pubkeys; + input_analysis.missing_redeem_script = + outdata.missing_redeem_script; + input_analysis.missing_sigs = outdata.missing_sigs; + + // If we are only missing signatures and nothing else, then next + // is signer + if (outdata.missing_pubkeys.empty() && + outdata.missing_redeem_script.IsNull() && + !outdata.missing_sigs.empty()) { + input_analysis.next = PSBTRole::SIGNER; + } else { + only_missing_sigs = false; + input_analysis.next = PSBTRole::UPDATER; + } + } else { + only_missing_final = true; + input_analysis.next = PSBTRole::FINALIZER; + } + } else if (!utxo.IsNull()) { + input_analysis.is_final = true; + } + } + + if (all_final) { + only_missing_sigs = false; + result.next = PSBTRole::EXTRACTOR; + } + if (calc_fee) { + // Get the output amount + Amount out_amt = std::accumulate( + psbtx.tx->vout.begin(), psbtx.tx->vout.end(), Amount::zero(), + [](Amount a, const CTxOut &b) { return a += b.nValue; }); + + // Get the fee + Amount fee = in_amt - out_amt; + result.fee = fee; + + // Estimate the size + CMutableTransaction mtx(*psbtx.tx); + CCoinsView view_dummy; + CCoinsViewCache view(&view_dummy); + bool success = true; + + for (size_t i = 0; i < psbtx.tx->vin.size(); ++i) { + PSBTInput &input = psbtx.inputs[i]; + CTxOut newUtxo; + + if (!SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, + SigHashType().withForkId(), nullptr, true) || + !psbtx.GetInputUTXO(newUtxo, i)) { + success = false; + break; + } else { + mtx.vin[i].scriptSig = input.final_script_sig; + view.AddCoin(psbtx.tx->vin[i].prevout, Coin(newUtxo, 1, false), + true); + } + } + + if (success) { + CTransaction ctx = CTransaction(mtx); + size_t size = ctx.GetTotalSize(); + result.estimated_vsize = size; + // Estimate fee rate + CFeeRate feerate(fee, size); + result.estimated_feerate = feerate; + } + + if (only_missing_sigs) { + result.next = PSBTRole::SIGNER; + } else if (only_missing_final) { + result.next = PSBTRole::FINALIZER; + } else if (all_final) { + result.next = PSBTRole::EXTRACTOR; + } else { + result.next = PSBTRole::UPDATER; + } + } else { + result.next = PSBTRole::UPDATER; + } + + return result; +} diff --git a/src/psbt.h b/src/psbt.h --- a/src/psbt.h +++ b/src/psbt.h @@ -489,41 +489,6 @@ EXTRACTOR, }; -/** - * Holds an analysis of one input from a PSBT - */ -struct PSBTInputAnalysis { - //! Whether we have UTXO information for this input - bool has_utxo; - //! Whether the input has all required information including signatures - bool is_final; - //! Which of the BIP 174 roles needs to handle this input next - PSBTRole next; - - //! Pubkeys whose BIP32 derivation path is missing - std::vector missing_pubkeys; - //! Pubkeys whose signatures are missing - std::vector missing_sigs; - //! Hash160 of redeem script, if missing - uint160 missing_redeem_script; -}; - -/** - * Holds the results of AnalyzePSBT (miscellaneous information about a PSBT) - */ -struct PSBTAnalysis { - //! Estimated weight of the transaction - Optional estimated_vsize; - //! Estimated feerate (fee / weight) of the transaction - Optional estimated_feerate; - //! Amount of fee being paid by the transaction - Optional fee; - //! More information about the individual inputs of the transaction - std::vector inputs; - //! Which of the BIP 174 roles needs to handle the transaction next - PSBTRole next; -}; - std::string PSBTRoleName(PSBTRole role); /** Checks whether a PSBTInput is already signed. */ @@ -572,15 +537,6 @@ CombinePSBTs(PartiallySignedTransaction &out, const std::vector &psbtxs); -/** - * Provides helpful miscellaneous information about where a PSBT is in the - * signing workflow. - * - * @param[in] psbtx the PSBT to analyze - * @return A PSBTAnalysis with information about the provided PSBT. - */ -PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx); - //! Decode a base64ed PSBT into a PartiallySignedTransaction NODISCARD bool DecodeBase64PSBT(PartiallySignedTransaction &decoded_psbt, const std::string &base64_psbt, diff --git a/src/psbt.cpp b/src/psbt.cpp --- a/src/psbt.cpp +++ b/src/psbt.cpp @@ -291,131 +291,6 @@ } } -PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx) { - // Go through each input and build status - PSBTAnalysis result; - - bool calc_fee = true; - bool all_final = true; - bool only_missing_sigs = true; - bool only_missing_final = false; - Amount in_amt{Amount::zero()}; - - result.inputs.resize(psbtx.tx->vin.size()); - - for (size_t i = 0; i < psbtx.tx->vin.size(); ++i) { - PSBTInput &input = psbtx.inputs[i]; - PSBTInputAnalysis &input_analysis = result.inputs[i]; - - // Check for a UTXO - CTxOut utxo; - if (psbtx.GetInputUTXO(utxo, i)) { - in_amt += utxo.nValue; - input_analysis.has_utxo = true; - } else { - input_analysis.has_utxo = false; - input_analysis.is_final = false; - input_analysis.next = PSBTRole::UPDATER; - calc_fee = false; - } - - // Check if it is final - if (!utxo.IsNull() && !PSBTInputSigned(input)) { - input_analysis.is_final = false; - all_final = false; - - // Figure out what is missing - SignatureData outdata; - bool complete = SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, - SigHashType().withForkId(), &outdata); - - // Things are missing - if (!complete) { - input_analysis.missing_pubkeys = outdata.missing_pubkeys; - input_analysis.missing_redeem_script = - outdata.missing_redeem_script; - input_analysis.missing_sigs = outdata.missing_sigs; - - // If we are only missing signatures and nothing else, then next - // is signer - if (outdata.missing_pubkeys.empty() && - outdata.missing_redeem_script.IsNull() && - !outdata.missing_sigs.empty()) { - input_analysis.next = PSBTRole::SIGNER; - } else { - only_missing_sigs = false; - input_analysis.next = PSBTRole::UPDATER; - } - } else { - only_missing_final = true; - input_analysis.next = PSBTRole::FINALIZER; - } - } else if (!utxo.IsNull()) { - input_analysis.is_final = true; - } - } - - if (all_final) { - only_missing_sigs = false; - result.next = PSBTRole::EXTRACTOR; - } - if (calc_fee) { - // Get the output amount - Amount out_amt = std::accumulate( - psbtx.tx->vout.begin(), psbtx.tx->vout.end(), Amount::zero(), - [](Amount a, const CTxOut &b) { return a += b.nValue; }); - - // Get the fee - Amount fee = in_amt - out_amt; - result.fee = fee; - - // Estimate the size - CMutableTransaction mtx(*psbtx.tx); - CCoinsView view_dummy; - CCoinsViewCache view(&view_dummy); - bool success = true; - - for (size_t i = 0; i < psbtx.tx->vin.size(); ++i) { - PSBTInput &input = psbtx.inputs[i]; - CTxOut newUtxo; - - if (!SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, - SigHashType().withForkId(), nullptr, true) || - !psbtx.GetInputUTXO(newUtxo, i)) { - success = false; - break; - } else { - mtx.vin[i].scriptSig = input.final_script_sig; - view.AddCoin(psbtx.tx->vin[i].prevout, Coin(newUtxo, 1, false), - true); - } - } - - if (success) { - CTransaction ctx = CTransaction(mtx); - size_t size = ctx.GetTotalSize(); - result.estimated_vsize = size; - // Estimate fee rate - CFeeRate feerate(fee, size); - result.estimated_feerate = feerate; - } - - if (only_missing_sigs) { - result.next = PSBTRole::SIGNER; - } else if (only_missing_final) { - result.next = PSBTRole::FINALIZER; - } else if (all_final) { - result.next = PSBTRole::EXTRACTOR; - } else { - result.next = PSBTRole::UPDATER; - } - } else { - result.next = PSBTRole::UPDATER; - } - - return result; -} - bool DecodeBase64PSBT(PartiallySignedTransaction &psbt, const std::string &base64_tx, std::string &error) { bool invalid; diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include