From 28b12e26479604f46311a0403fe7cd3f38eaf659 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Tue, 12 Dec 2023 23:40:10 +0700 Subject: [PATCH 01/22] fix: follow-up bitcoin#15638 to move AnalyzePSBT related code --- src/Makefile.am | 2 + src/node/psbt.cpp | 145 +++++++++++++++++++++++++++++++++++++ src/node/psbt.h | 53 ++++++++++++++ src/psbt.cpp | 132 --------------------------------- src/psbt.h | 43 ----------- src/qt/walletview.cpp | 1 + src/rpc/rawtransaction.cpp | 1 + src/test/fuzz/psbt.cpp | 1 + 8 files changed, 203 insertions(+), 175 deletions(-) create mode 100644 src/node/psbt.cpp create mode 100644 src/node/psbt.h diff --git a/src/Makefile.am b/src/Makefile.am index f181677e7211..965c96202bc2 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -259,6 +259,7 @@ BITCOIN_CORE_H = \ node/coin.h \ node/coinstats.h \ node/context.h \ + node/psbt.h \ node/transaction.h \ node/utxo_snapshot.h \ noui.h \ @@ -468,6 +469,7 @@ libbitcoin_server_a_SOURCES = \ node/coinstats.cpp \ node/context.cpp \ node/interfaces.cpp \ + node/psbt.cpp \ node/transaction.cpp \ noui.cpp \ policy/fees.cpp \ diff --git a/src/node/psbt.cpp b/src/node/psbt.cpp new file mode 100644 index 000000000000..872f98369e93 --- /dev/null +++ b/src/node/psbt.cpp @@ -0,0 +1,145 @@ +// Copyright (c) 2009-2020 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 + +#include + +PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx) +{ + // Go through each input and build status + PSBTAnalysis result; + + bool calc_fee = true; + + CAmount in_amt = 0; + + result.inputs.resize(psbtx.tx->vin.size()); + + for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { + PSBTInput& input = psbtx.inputs[i]; + PSBTInputAnalysis& input_analysis = result.inputs[i]; + + // We set next role here and ratchet backwards as required + input_analysis.next = PSBTRole::EXTRACTOR; + + // Check for a UTXO + CTxOut utxo; + if (psbtx.GetInputUTXO(utxo, i)) { + if (!MoneyRange(utxo.nValue) || !MoneyRange(in_amt + utxo.nValue)) { + result.SetInvalid(strprintf("PSBT is not valid. Input %u has invalid value", i)); + return result; + } + in_amt += utxo.nValue; + input_analysis.has_utxo = true; + } else { + if (input.non_witness_utxo && psbtx.tx->vin[i].prevout.n >= input.non_witness_utxo->vout.size()) { + result.SetInvalid(strprintf("PSBT is not valid. Input %u specifies invalid prevout", i)); + return result; + } + input_analysis.has_utxo = false; + input_analysis.is_final = false; + input_analysis.next = PSBTRole::UPDATER; + calc_fee = false; + } + + if (!utxo.IsNull() && utxo.scriptPubKey.IsUnspendable()) { + result.SetInvalid(strprintf("PSBT is not valid. Input %u spends unspendable output", i)); + return result; + } + + // Check if it is final + if (!utxo.IsNull() && !PSBTInputSigned(input)) { + input_analysis.is_final = false; + + // Figure out what is missing + SignatureData outdata; + bool complete = SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, 1, &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 { + input_analysis.next = PSBTRole::UPDATER; + } + } else { + input_analysis.next = PSBTRole::FINALIZER; + } + } else if (!utxo.IsNull()){ + input_analysis.is_final = true; + } + } + + // Calculate next role for PSBT by grabbing "minumum" PSBTInput next role + result.next = PSBTRole::EXTRACTOR; + for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { + PSBTInputAnalysis& input_analysis = result.inputs[i]; + result.next = std::min(result.next, input_analysis.next); + } + assert(result.next > PSBTRole::CREATOR); + + if (calc_fee) { + // Get the output amount + CAmount out_amt = std::accumulate(psbtx.tx->vout.begin(), psbtx.tx->vout.end(), CAmount(0), + [](CAmount a, const CTxOut& b) { + if (!MoneyRange(a) || !MoneyRange(b.nValue) || !MoneyRange(a + b.nValue)) { + return CAmount(-1); + } + return a += b.nValue; + } + ); + if (!MoneyRange(out_amt)) { + result.SetInvalid(strprintf("PSBT is not valid. Output amount invalid")); + return result; + } + + // Get the fee + CAmount 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 (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { + PSBTInput& input = psbtx.inputs[i]; + Coin newcoin; + + if (!SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, 1, nullptr, true) || !psbtx.GetInputUTXO(newcoin.out, i)) { + success = false; + break; + } else { + mtx.vin[i].scriptSig = input.final_script_sig; + newcoin.nHeight = 1; + view.AddCoin(psbtx.tx->vin[i].prevout, std::move(newcoin), true); + } + } + + if (success) { + CTransaction ctx = CTransaction(mtx); + size_t size = GetVirtualTransactionSize(ctx, GetTransactionSigOpCount(ctx, view, STANDARD_SCRIPT_VERIFY_FLAGS)); + result.estimated_vsize = size; + // Estimate fee rate + CFeeRate feerate(fee, size); + result.estimated_feerate = feerate; + } + + } + + return result; +} + diff --git a/src/node/psbt.h b/src/node/psbt.h new file mode 100644 index 000000000000..fd2f063351ce --- /dev/null +++ b/src/node/psbt.h @@ -0,0 +1,53 @@ +// Copyright (c) 2009-2020 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 { + bool has_utxo; //!< Whether we have UTXO information for this input + bool is_final; //!< Whether the input has all required information including signatures + PSBTRole next; //!< Which of the BIP 174 roles needs to handle this input next + + std::vector missing_pubkeys; //!< Pubkeys whose BIP32 derivation path is missing + std::vector missing_sigs; //!< Pubkeys whose signatures are missing + uint160 missing_redeem_script; //!< Hash160 of redeem script, if missing +}; + +/** + * Holds the results of AnalyzePSBT (miscellaneous information about a PSBT) + */ +struct PSBTAnalysis { + std::optional estimated_vsize; //!< Estimated weight of the transaction + std::optional estimated_feerate; //!< Estimated feerate (fee / weight) of the transaction + std::optional fee; //!< Amount of fee being paid by the transaction + std::vector inputs; //!< More information about the individual inputs of the transaction + PSBTRole next; //!< Which of the BIP 174 roles needs to handle the transaction next + std::string error; //!< Error message + + void SetInvalid(std::string err_msg) + { + estimated_vsize = std::nullopt; + estimated_feerate = std::nullopt; + fee = std::nullopt; + inputs.clear(); + next = PSBTRole::CREATOR; + error = err_msg; + } +}; + +/** + * 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_PSBT_H diff --git a/src/psbt.cpp b/src/psbt.cpp index c5eafb675d51..c6dc7bb0d963 100644 --- a/src/psbt.cpp +++ b/src/psbt.cpp @@ -316,138 +316,6 @@ std::string PSBTRoleName(PSBTRole role) { assert(false); } -PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx) -{ - // Go through each input and build status - PSBTAnalysis result; - - bool calc_fee = true; - - CAmount in_amt = 0; - - result.inputs.resize(psbtx.tx->vin.size()); - - for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { - PSBTInput& input = psbtx.inputs[i]; - PSBTInputAnalysis& input_analysis = result.inputs[i]; - - // We set next role here and ratchet backwards as required - input_analysis.next = PSBTRole::EXTRACTOR; - - // Check for a UTXO - CTxOut utxo; - if (psbtx.GetInputUTXO(utxo, i)) { - if (!MoneyRange(utxo.nValue) || !MoneyRange(in_amt + utxo.nValue)) { - result.SetInvalid(strprintf("PSBT is not valid. Input %u has invalid value", i)); - return result; - } - in_amt += utxo.nValue; - input_analysis.has_utxo = true; - } else { - if (input.non_witness_utxo && psbtx.tx->vin[i].prevout.n >= input.non_witness_utxo->vout.size()) { - result.SetInvalid(strprintf("PSBT is not valid. Input %u specifies invalid prevout", i)); - return result; - } - input_analysis.has_utxo = false; - input_analysis.is_final = false; - input_analysis.next = PSBTRole::UPDATER; - calc_fee = false; - } - - if (!utxo.IsNull() && utxo.scriptPubKey.IsUnspendable()) { - result.SetInvalid(strprintf("PSBT is not valid. Input %u spends unspendable output", i)); - return result; - } - - // Check if it is final - if (!utxo.IsNull() && !PSBTInputSigned(input)) { - input_analysis.is_final = false; - - // Figure out what is missing - SignatureData outdata; - bool complete = SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, 1, &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 { - input_analysis.next = PSBTRole::UPDATER; - } - } else { - input_analysis.next = PSBTRole::FINALIZER; - } - } else if (!utxo.IsNull()){ - input_analysis.is_final = true; - } - } - - // Calculate next role for PSBT by grabbing "minumum" PSBTInput next role - result.next = PSBTRole::EXTRACTOR; - for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { - PSBTInputAnalysis& input_analysis = result.inputs[i]; - result.next = std::min(result.next, input_analysis.next); - } - assert(result.next > PSBTRole::CREATOR); - - if (calc_fee) { - // Get the output amount - CAmount out_amt = std::accumulate(psbtx.tx->vout.begin(), psbtx.tx->vout.end(), CAmount(0), - [](CAmount a, const CTxOut& b) { - if (!MoneyRange(a) || !MoneyRange(b.nValue) || !MoneyRange(a + b.nValue)) { - return CAmount(-1); - } - return a += b.nValue; - } - ); - if (!MoneyRange(out_amt)) { - result.SetInvalid(strprintf("PSBT is not valid. Output amount invalid")); - return result; - } - - // Get the fee - CAmount 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 (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { - PSBTInput& input = psbtx.inputs[i]; - Coin newcoin; - - if (!SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, 1, nullptr, true) || !psbtx.GetInputUTXO(newcoin.out, i)) { - success = false; - break; - } else { - mtx.vin[i].scriptSig = input.final_script_sig; - newcoin.nHeight = 1; - view.AddCoin(psbtx.tx->vin[i].prevout, std::move(newcoin), true); - } - } - - if (success) { - CTransaction ctx = CTransaction(mtx); - size_t size = GetVirtualTransactionSize(ctx, GetTransactionSigOpCount(ctx, view, STANDARD_SCRIPT_VERIFY_FLAGS)); - result.estimated_vsize = size; - // Estimate fee rate - CFeeRate feerate(fee, size); - result.estimated_feerate = feerate; - } - - } - - return result; -} - bool DecodeBase64PSBT(PartiallySignedTransaction& psbt, const std::string& base64_tx, std::string& error) { bool invalid; diff --git a/src/psbt.h b/src/psbt.h index 05189b9693d6..515226807b74 100644 --- a/src/psbt.h +++ b/src/psbt.h @@ -498,41 +498,6 @@ enum class PSBTRole { EXTRACTOR }; -/** - * Holds an analysis of one input from a PSBT - */ -struct PSBTInputAnalysis { - bool has_utxo; //!< Whether we have UTXO information for this input - bool is_final; //!< Whether the input has all required information including signatures - PSBTRole next; //!< Which of the BIP 174 roles needs to handle this input next - - std::vector missing_pubkeys; //!< Pubkeys whose BIP32 derivation path is missing - std::vector missing_sigs; //!< Pubkeys whose signatures are missing - uint160 missing_redeem_script; //!< Hash160 of redeem script, if missing -}; - -/** - * Holds the results of AnalyzePSBT (miscellaneous information about a PSBT) - */ -struct PSBTAnalysis { - std::optional estimated_vsize; //!< Estimated weight of the transaction - std::optional estimated_feerate; //!< Estimated feerate (fee / weight) of the transaction - std::optional fee; //!< Amount of fee being paid by the transaction - std::vector inputs; //!< More information about the individual inputs of the transaction - PSBTRole next; //!< Which of the BIP 174 roles needs to handle the transaction next - std::string error; //!< Error message - - void SetInvalid(std::string err_msg) - { - estimated_vsize = std::nullopt; - estimated_feerate = std::nullopt; - fee = std::nullopt; - inputs.clear(); - next = PSBTRole::CREATOR; - error = err_msg; - } -}; - std::string PSBTRoleName(PSBTRole role); /** Checks whether a PSBTInput is already signed. */ @@ -573,14 +538,6 @@ bool FinalizeAndExtractPSBT(PartiallySignedTransaction& psbtx, CMutableTransacti */ [[nodiscard]] TransactionError 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, std::string& error); //! Decode a raw (binary blob) PSBT into a PartiallySignedTransaction diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp index 389e8e5d8f0b..31b00eed552d 100644 --- a/src/qt/walletview.cpp +++ b/src/qt/walletview.cpp @@ -4,6 +4,7 @@ #include +#include #include #include #include diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 8f06aac2b69b..cf1f755e99b5 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include diff --git a/src/test/fuzz/psbt.cpp b/src/test/fuzz/psbt.cpp index 8c01eee2c70d..ce0c27e26964 100644 --- a/src/test/fuzz/psbt.cpp +++ b/src/test/fuzz/psbt.cpp @@ -4,6 +4,7 @@ #include +#include #include #include #include