mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-02-03 20:21:10 +00:00
deaa6dd144f5650b385658a0c4f9a014aff8dde2 psbt: check output index is within bounds before accessing (Andrew Chow) f1ef7f0aa46338f4cd8de79696027a1bf868f359 Don't calculate tx fees for PSBTs with invalid money values (Andrew Chow) Pull request description: Fixes #17149 Two classes of issues were found by the psbt fuzzer: values out of range and causing overflows, and prevout indexes being out of range. This PR fixes both. When accessing a specific output using the index given in the tx, check that it is actually a possible output before trying to access the output. When summing and checking amounts for `decodepsbt` and `analyzepsbt`, make sure that the values are actually valid money values.. Otherwise, stop summing and don't show the fee. For `analyzepsbt`, return that the next role is the Creator since the Creator needs to remake the transaction to be valid. ACKs for top commit: practicalswift: ACK deaa6dd144f5650b385658a0c4f9a014aff8dde2 -- only change since last ACK was the addition of tests gwillen: tested ACK deaa6dd, would also like to see this merged! Tree-SHA512: 06c36720bbb5a7ab1c29f7d15878bf9f0d3e5760c06bff479d412e1bf07bb3e0e9ab6cca820a4bfedaab71bfd7af813807e87cbcdf0af25cc3f66a53a06dbcfd
157 lines
5.6 KiB
C++
157 lines
5.6 KiB
C++
// 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.
|
|
|
|
#include <amount.h>
|
|
#include <coins.h>
|
|
#include <consensus/tx_verify.h>
|
|
#include <node/psbt.h>
|
|
#include <policy/policy.h>
|
|
#include <policy/settings.h>
|
|
#include <tinyformat.h>
|
|
|
|
#include <numeric>
|
|
|
|
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;
|
|
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];
|
|
|
|
// 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;
|
|
all_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_witness_script = outdata.missing_witness_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_witness_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
|
|
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;
|
|
mtx.vin[i].scriptWitness = input.final_script_witness;
|
|
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, GetTransactionSigOpCost(ctx, view, STANDARD_SCRIPT_VERIFY_FLAGS));
|
|
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;
|
|
}
|