What changed, and why it matters
This commit is a code-quality refactor for Bitcoin Core's Partially Signed Bitcoin Transaction (PSBT) handling. It removes the ability to create an empty/default PSBT object and changes helper functions so they return a valid PSBT or an explicit failure instead of filling in an empty object. This makes accidental use of an uninitialized PSBT impossible, but it does not fix a known active bug or vulnerability by itself.
Treat as a normal maintainability/refactor commit. No urgent security action is required. Reviewers may want to verify that all former default-constructed PSBT sites have been converted correctly and that no uninitialized PSBT can still be created accidentally.
Security signals we found
Defensive removal of default constructor to prevent uninitialized PSBT objects
Conversion of PSBT decode helpers to return optional/result types
No change to parsing, validation, or consensus rules
No mention of vulnerability, CVE, bug, or security fix in commit message
Evidence from the diff
The patch deletes PartiallySignedTransaction() = default and converts CombinePSBTs, DecodeBase64PSBT, and DecodeRawPSBT from output-parameter style to return std::optional<PartiallySignedTransaction> or util::Result<PartiallySignedTransaction>. Callers are updated to check the result before use. This is a defensive type-safety improvement: it prevents default-constructed PSBTs from being passed around and forces deserialization failures to be handled explicitly. No deserialization logic, consensus code, or network behavior is changed.
Changed components
src/psbt.hsrc/psbt.cppsrc/external_signer.cppsrc/qt/walletframe.cppsrc/rpc/rawtransaction.cppsrc/wallet/rpc/spend.cpptest/fuzz targets for PSBT and deserializationInspect captured patch +108 / −94
diff --git a/src/external_signer.cpp b/src/external_signer.cpp
index 3790f4d3..2da95026 100644
--- a/src/external_signer.cpp
+++ b/src/external_signer.cpp
@@ -112,14 +112,13 @@ bool ExternalSigner::SignTransaction(PartiallySignedTransaction& psbtx, std::str
return false;
}
- PartiallySignedTransaction signer_psbtx;
- std::string signer_psbt_error;
- if (!DecodeBase64PSBT(signer_psbtx, signer_result.find_value("psbt").get_str(), signer_psbt_error)) {
- error = strprintf("TX decode failed %s", signer_psbt_error);
+ util::Result<PartiallySignedTransaction> signer_psbtx = DecodeBase64PSBT(signer_result.find_value("psbt").get_str());
+ if (!signer_psbtx) {
+ error = strprintf("TX decode failed %s", util::ErrorString(signer_psbtx).original);
return false;
}
- psbtx = signer_psbtx;
+ psbtx = *signer_psbtx;
return true;
}
diff --git a/src/psbt.cpp b/src/psbt.cpp
index 29f16e73..1a49d01a 100644
--- a/src/psbt.cpp
+++ b/src/psbt.cpp
@@ -9,6 +9,7 @@
#include <policy/policy.h>
#include <script/signingprovider.h>
#include <util/check.h>
+#include <util/result.h>
#include <util/strencodings.h>
using common::PSBTError;
@@ -599,17 +600,17 @@ bool FinalizeAndExtractPSBT(PartiallySignedTransaction& psbtx, CMutableTransacti
return true;
}
-bool CombinePSBTs(PartiallySignedTransaction& out, const std::vector<PartiallySignedTransaction>& psbtxs)
+std::optional<PartiallySignedTransaction> CombinePSBTs(const std::vector<PartiallySignedTransaction>& psbtxs)
{
- out = psbtxs[0]; // Copy the first one
+ PartiallySignedTransaction out = psbtxs[0]; // Copy the first one
// Merge
for (auto it = std::next(psbtxs.begin()); it != psbtxs.end(); ++it) {
if (!out.Merge(*it)) {
- return false;
+ return std::nullopt;
}
}
- return true;
+ return out;
}
std::string PSBTRoleName(PSBTRole role) {
@@ -623,30 +624,27 @@ std::string PSBTRoleName(PSBTRole role) {
assert(false);
}
-bool DecodeBase64PSBT(PartiallySignedTransaction& psbt, const std::string& base64_tx, std::string& error)
+util::Result<PartiallySignedTransaction> DecodeBase64PSBT(const std::string& base64_tx)
{
auto tx_data = DecodeBase64(base64_tx);
if (!tx_data) {
- error = "invalid base64";
- return false;
+ return util::Error{Untranslated("invalid base64")};
}
- return DecodeRawPSBT(psbt, MakeByteSpan(*tx_data), error);
+ return DecodeRawPSBT(MakeByteSpan(*tx_data));
}
-bool DecodeRawPSBT(PartiallySignedTransaction& psbt, std::span<const std::byte> tx_data, std::string& error)
+util::Result<PartiallySignedTransaction> DecodeRawPSBT(std::span<const std::byte> tx_data)
{
SpanReader ss_data{tx_data};
try {
- ss_data >> psbt;
+ PartiallySignedTransaction psbt(deserialize, ss_data);
if (!ss_data.empty()) {
- error = "extra data after PSBT";
- return false;
+ return util::Error{Untranslated("extra data after PSBT")};
}
+ return psbt;
} catch (const std::exception& e) {
- error = e.what();
- return false;
+ return util::Error{Untranslated(e.what())};
}
- return true;
}
uint32_t PartiallySignedTransaction::GetVersion() const
diff --git a/src/psbt.h b/src/psbt.h
index dcf0d6bd..3d893194 100644
--- a/src/psbt.h
+++ b/src/psbt.h
@@ -15,6 +15,7 @@
#include <script/signingprovider.h>
#include <span.h>
#include <streams.h>
+#include <util/result.h>
#include <optional>
@@ -1080,7 +1081,6 @@ public:
[[nodiscard]] bool Merge(const PartiallySignedTransaction& psbt);
bool AddInput(const CTxIn& txin, PSBTInput& psbtin);
bool AddOutput(const CTxOut& txout, const PSBTOutput& psbtout);
- PartiallySignedTransaction() = default;
explicit PartiallySignedTransaction(const CMutableTransaction& tx);
/**
* Finds the UTXO for a given input index
@@ -1374,15 +1374,14 @@ bool FinalizeAndExtractPSBT(PartiallySignedTransaction& psbtx, CMutableTransacti
/**
* Combines PSBTs with the same underlying transaction, resulting in a single PSBT with all partial signatures from each input.
*
- * @param[out] out the combined PSBT, if successful
* @param[in] psbtxs the PSBTs to combine
- * @return True if we successfully combined the transactions, false if they were not compatible
+ * @return The combined PSBT or std::nullopt if the PSBTs cannot be combined
*/
-[[nodiscard]] bool CombinePSBTs(PartiallySignedTransaction& out, const std::vector<PartiallySignedTransaction>& psbtxs);
+[[nodiscard]] std::optional<PartiallySignedTransaction> CombinePSBTs(const std::vector<PartiallySignedTransaction>& psbtxs);
//! Decode a base64ed PSBT into a PartiallySignedTransaction
-[[nodiscard]] bool DecodeBase64PSBT(PartiallySignedTransaction& decoded_psbt, const std::string& base64_psbt, std::string& error);
+[[nodiscard]] util::Result<PartiallySignedTransaction> DecodeBase64PSBT(const std::string& base64_tx);
//! Decode a raw (binary blob) PSBT into a PartiallySignedTransaction
-[[nodiscard]] bool DecodeRawPSBT(PartiallySignedTransaction& decoded_psbt, std::span<const std::byte> raw_psbt, std::string& error);
+[[nodiscard]] util::Result<PartiallySignedTransaction> DecodeRawPSBT(std::span<const std::byte> tx_data);
#endif // BITCOIN_PSBT_H
diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp
index bb9e0618..dec3d2b4 100644
--- a/src/qt/test/wallettests.cpp
+++ b/src/qt/test/wallettests.cpp
@@ -431,9 +431,8 @@ void TestGUIWatchOnly(interfaces::Node& node, TestChain100Setup& test)
// Decode psbt
std::optional<std::vector<unsigned char>> decoded_psbt = DecodeBase64(psbt_string);
QVERIFY(decoded_psbt);
- PartiallySignedTransaction psbt;
- std::string err;
- QVERIFY(DecodeRawPSBT(psbt, MakeByteSpan(*decoded_psbt), err));
+ util::Result<PartiallySignedTransaction> psbt = DecodeRawPSBT(MakeByteSpan(*decoded_psbt));
+ QVERIFY(psbt);
}
void TestGUI(interfaces::Node& node)
diff --git a/src/qt/walletframe.cpp b/src/qt/walletframe.cpp
index 579c033d..ec5b5123 100644
--- a/src/qt/walletframe.cpp
+++ b/src/qt/walletframe.cpp
@@ -223,15 +223,14 @@ void WalletFrame::gotoLoadPSBT(bool from_clipboard)
}
}
- std::string error;
- PartiallySignedTransaction psbtx;
- if (!DecodeRawPSBT(psbtx, MakeByteSpan(data), error)) {
- Q_EMIT message(tr("Error"), tr("Unable to decode PSBT") + "\n" + QString::fromStdString(error), CClientUIInterface::MSG_ERROR);
+ util::Result<PartiallySignedTransaction> psbt_res = DecodeRawPSBT(MakeByteSpan(data));
+ if (!psbt_res) {
+ Q_EMIT message(tr("Error"), tr("Unable to decode PSBT") + "\n" + QString::fromStdString(util::ErrorString(psbt_res).original), CClientUIInterface::MSG_ERROR);
return;
}
auto dlg = new PSBTOperationsDialog(this, currentWalletModel(), clientModel);
- dlg->openWithPSBT(psbtx);
+ dlg->openWithPSBT(*psbt_res);
GUIUtil::ShowModalDialogAsynchronously(dlg);
}
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index 517a02bf..f3fb3eb7 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -128,11 +128,11 @@ static std::vector<RPCArg> CreateTxDoc()
PartiallySignedTransaction ProcessPSBT(const std::string& psbt_string, const std::any& context, const HidingSigningProvider& provider, std::optional<int> sighash_type, bool finalize)
{
// Unserialize the transactions
- PartiallySignedTransaction psbtx;
- std::string error;
- if (!DecodeBase64PSBT(psbtx, psbt_string, error)) {
- throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
+ util::Result<PartiallySignedTransaction> psbt_res = DecodeBase64PSBT(psbt_string);
+ if (!psbt_res) {
+ throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", util::ErrorString(psbt_res).original));
}
+ PartiallySignedTransaction psbtx = *psbt_res;
if (g_txindex) g_txindex->BlockUntilSyncedToCurrentChain();
const NodeContext& node = EnsureAnyNodeContext(context);
@@ -1066,11 +1066,11 @@ static RPCMethod decodepsbt()
[](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
{
// Unserialize the transactions
- PartiallySignedTransaction psbtx;
- std::string error;
- if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) {
- throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
+ util::Result<PartiallySignedTransaction> psbt_res = DecodeBase64PSBT(request.params[0].get_str());
+ if (!psbt_res) {
+ throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", util::ErrorString(psbt_res).original));
}
+ PartiallySignedTransaction psbtx = *psbt_res;
UniValue result(UniValue::VOBJ);
@@ -1547,21 +1547,20 @@ static RPCMethod combinepsbt()
throw JSONRPCError(RPC_INVALID_PARAMETER, "Parameter 'txs' cannot be empty");
}
for (unsigned int i = 0; i < txs.size(); ++i) {
- PartiallySignedTransaction psbtx;
- std::string error;
- if (!DecodeBase64PSBT(psbtx, txs[i].get_str(), error)) {
- throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
+ util::Result<PartiallySignedTransaction> psbt_res = DecodeBase64PSBT(txs[i].get_str());
+ if (!psbt_res) {
+ throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", util::ErrorString(psbt_res).original));
}
- psbtxs.push_back(psbtx);
+ psbtxs.push_back(*psbt_res);
}
- PartiallySignedTransaction merged_psbt;
- if (!CombinePSBTs(merged_psbt, psbtxs)) {
+ std::optional<PartiallySignedTransaction> merged_psbt = CombinePSBTs(psbtxs);
+ if (!merged_psbt) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "PSBTs not compatible (different transactions)");
}
DataStream ssTx{};
- ssTx << merged_psbt;
+ ssTx << *merged_psbt;
return EncodeBase64(ssTx);
},
};
@@ -1593,11 +1592,11 @@ static RPCMethod finalizepsbt()
[](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
{
// Unserialize the transactions
- PartiallySignedTransaction psbtx;
- std::string error;
- if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) {
- throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
+ util::Result<PartiallySignedTransaction> psbt_res = DecodeBase64PSBT(request.params[0].get_str());
+ if (!psbt_res) {
+ throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", util::ErrorString(psbt_res).original));
}
+ PartiallySignedTransaction psbtx = *psbt_res;
bool extract = request.params[1].isNull() || (!request.params[1].isNull() && request.params[1].get_bool());
@@ -1799,11 +1798,11 @@ static RPCMethod joinpsbts()
uint32_t best_version = 1;
uint32_t best_locktime = 0xffffffff;
for (unsigned int i = 0; i < txs.size(); ++i) {
- PartiallySignedTransaction psbtx;
- std::string error;
- if (!DecodeBase64PSBT(psbtx, txs[i].get_str(), error)) {
- throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
+ util::Result<PartiallySignedTransaction> psbt_res = DecodeBase64PSBT(txs[i].get_str());
+ if (!psbt_res) {
+ throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", util::ErrorString(psbt_res).original));
}
+ const PartiallySignedTransaction& psbtx = *psbt_res;
psbtxs.push_back(psbtx);
// Choose the highest version number
if (psbtx.tx->version > best_version) {
@@ -1816,10 +1815,10 @@ static RPCMethod joinpsbts()
}
// Create a blank psbt where everything will be added
- PartiallySignedTransaction merged_psbt;
- merged_psbt.tx = CMutableTransaction();
- merged_psbt.tx->version = best_version;
- merged_psbt.tx->nLockTime = best_locktime;
+ CMutableTransaction tx;
+ tx.version = best_version;
+ tx.nLockTime = best_locktime;
+ PartiallySignedTransaction merged_psbt(tx);
// Merge
for (auto& psbt : psbtxs) {
@@ -1851,10 +1850,7 @@ static RPCMethod joinpsbts()
std::shuffle(input_indices.begin(), input_indices.end(), FastRandomContext());
std::shuffle(output_indices.begin(), output_indices.end(), FastRandomContext());
- PartiallySignedTransaction shuffled_psbt;
- shuffled_psbt.tx = CMutableTransaction();
- shuffled_psbt.tx->version = merged_psbt.tx->version;
- shuffled_psbt.tx->nLockTime = merged_psbt.tx->nLockTime;
+ PartiallySignedTransaction shuffled_psbt(tx);
for (int i : input_indices) {
shuffled_psbt.AddInput(merged_psbt.tx->vin[i], merged_psbt.inputs[i]);
}
@@ -1916,11 +1912,11 @@ static RPCMethod analyzepsbt()
[](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
{
// Unserialize the transaction
- PartiallySignedTransaction psbtx;
- std::string error;
- if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) {
- throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
+ util::Result<PartiallySignedTransaction> psbt_res = DecodeBase64PSBT(request.params[0].get_str());
+ if (!psbt_res) {
+ throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", util::ErrorString(psbt_res).original));
}
+ const PartiallySignedTransaction& psbtx = *psbt_res;
PSBTAnalysis psbta = AnalyzePSBT(psbtx);
diff --git a/src/test/fuzz/base_encode_decode.cpp b/src/test/fuzz/base_encode_decode.cpp
index 1d2f4d7e..3f9c2abb 100644
--- a/src/test/fuzz/base_encode_decode.cpp
+++ b/src/test/fuzz/base_encode_decode.cpp
@@ -90,8 +90,5 @@ FUZZ_TARGET(psbt_base64_decode)
{
const std::string random_string{buffer.begin(), buffer.end()};
- PartiallySignedTransaction psbt;
- std::string error;
- const bool ok{DecodeBase64PSBT(psbt, random_string, error)};
- assert(ok == error.empty());
+ util::Result<PartiallySignedTransaction> psbt = DecodeBase64PSBT(random_string);
}
diff --git a/src/test/fuzz/deserialize.cpp b/src/test/fuzz/deserialize.cpp
index 15150c4a..8e8ab53e 100644
--- a/src/test/fuzz/deserialize.cpp
+++ b/src/test/fuzz/deserialize.cpp
@@ -111,6 +111,19 @@ void DeserializeFromFuzzingInput(FuzzBufferType buffer, T&& obj)
assert(buffer.empty() || !Serialize(obj).empty());
}
+template <typename T>
+T DeserializeConstructFromFuzzingInput(FuzzBufferType buffer)
+{
+ try {
+ SpanReader reader{buffer};
+ T obj(deserialize, reader);
+ assert(buffer.empty() || !Serialize(obj).empty());
+ return obj;
+ } catch (const std::ios_base::failure&) {
+ throw invalid_fuzzing_input_exception();
+ }
+}
+
template <typename T, typename P>
void AssertEqualAfterSerializeDeserialize(const T& obj, const P& params)
{
@@ -184,8 +197,7 @@ FUZZ_TARGET_DESERIALIZE(key_origin_info_deserialize, {
AssertEqualAfterSerializeDeserialize(key_origin_info);
})
FUZZ_TARGET_DESERIALIZE(partially_signed_transaction_deserialize, {
- PartiallySignedTransaction partially_signed_transaction;
- DeserializeFromFuzzingInput(buffer, partially_signed_transaction);
+ PartiallySignedTransaction partially_signed_transaction = DeserializeConstructFromFuzzingInput<PartiallySignedTransaction>(buffer);
})
FUZZ_TARGET_DESERIALIZE(prefilled_transaction_deserialize, {
PrefilledTransaction prefilled_transaction;
diff --git a/src/test/fuzz/psbt.cpp b/src/test/fuzz/psbt.cpp
index e0f2177a..dd7c61eb 100644
--- a/src/test/fuzz/psbt.cpp
+++ b/src/test/fuzz/psbt.cpp
@@ -25,19 +25,19 @@ FUZZ_TARGET(psbt)
{
SeedRandomStateForTest(SeedRand::ZEROS);
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
- PartiallySignedTransaction psbt_mut;
- std::string error;
auto str = fuzzed_data_provider.ConsumeRandomLengthString();
- if (!DecodeRawPSBT(psbt_mut, MakeByteSpan(str), error)) {
+ util::Result<PartiallySignedTransaction> psbt_res = DecodeRawPSBT(MakeByteSpan(str));
+ if (!psbt_res) {
return;
}
+ PartiallySignedTransaction psbt_mut = *psbt_res;
const PartiallySignedTransaction psbt = psbt_mut;
// A PSBT must roundtrip.
- PartiallySignedTransaction psbt_roundtrip;
std::vector<uint8_t> psbt_ser;
VectorWriter{psbt_ser, 0, psbt};
- SpanReader{psbt_ser} >> psbt_roundtrip;
+ SpanReader reader{psbt_ser};
+ PartiallySignedTransaction psbt_roundtrip(deserialize, reader);
// And be stable across roundtrips.
std::vector<uint8_t> roundtrip_ser;
@@ -85,16 +85,19 @@ FUZZ_TARGET(psbt)
const PartiallySignedTransaction psbt_from_tx{result};
}
- PartiallySignedTransaction psbt_merge;
+ PartiallySignedTransaction psbt_merge = psbt;
str = fuzzed_data_provider.ConsumeRandomLengthString();
- if (!DecodeRawPSBT(psbt_merge, MakeByteSpan(str), error)) {
- psbt_merge = psbt;
+ util::Result<PartiallySignedTransaction> psbt_merge_res = DecodeRawPSBT(MakeByteSpan(str));
+ if (psbt_merge_res) {
+ psbt_merge = *psbt_merge_res;
}
psbt_mut = psbt;
(void)psbt_mut.Merge(psbt_merge);
psbt_mut = psbt;
- (void)CombinePSBTs(psbt_mut, {psbt_mut, psbt_merge});
- psbt_mut = psbt;
+ std::optional<PartiallySignedTransaction> comb_res = CombinePSBTs({psbt_mut, psbt_merge});
+ if (comb_res) {
+ psbt_mut = *comb_res;
+ }
for (unsigned int i = 0; i < psbt_merge.tx->vin.size(); ++i) {
(void)psbt_mut.AddInput(psbt_merge.tx->vin[i], psbt_merge.inputs[i]);
}
diff --git a/src/test/fuzz/rpc.cpp b/src/test/fuzz/rpc.cpp
index f0362db2..a6e486ce 100644
--- a/src/test/fuzz/rpc.cpp
+++ b/src/test/fuzz/rpc.cpp
@@ -292,7 +292,7 @@ std::string ConsumeScalarRPCArgument(FuzzedDataProvider& fuzzed_data_provider, b
},
[&] {
// base64 encoded psbt
- std::optional<PartiallySignedTransaction> opt_psbt = ConsumeDeserializable<PartiallySignedTransaction>(fuzzed_data_provider);
+ std::optional<PartiallySignedTransaction> opt_psbt = ConsumeDeserializableConstructor<PartiallySignedTransaction>(fuzzed_data_provider);
if (!opt_psbt) {
good_data = false;
return;
diff --git a/src/test/fuzz/util.h b/src/test/fuzz/util.h
index fd53e39f..7b99e6e6 100644
--- a/src/test/fuzz/util.h
+++ b/src/test/fuzz/util.h
@@ -124,6 +124,19 @@ template <typename T>
return obj;
}
+template <typename T>
+[[nodiscard]] inline std::optional<T> ConsumeDeserializableConstructor(FuzzedDataProvider& fuzzed_data_provider, const std::optional<size_t>& max_length = std::nullopt) noexcept
+{
+ const std::vector<uint8_t> buffer = ConsumeRandomLengthByteVector(fuzzed_data_provider, max_length);
+ SpanReader ds{buffer};
+ try {
+ T obj(deserialize, ds);
+ return obj;
+ } catch (const std::ios_base::failure&) {
+ return std::nullopt;
+ }
+}
+
template <typename WeakEnumType, size_t size>
[[nodiscard]] WeakEnumType ConsumeWeakEnum(FuzzedDataProvider& fuzzed_data_provider, const WeakEnumType (&all_types)[size]) noexcept
{
diff --git a/src/wallet/rpc/spend.cpp b/src/wallet/rpc/spend.cpp
index af121c12..ef59c1f8 100644
--- a/src/wallet/rpc/spend.cpp
+++ b/src/wallet/rpc/spend.cpp
@@ -1618,11 +1618,11 @@ RPCMethod walletprocesspsbt()
wallet.BlockUntilSyncedToCurrentChain();
// Unserialize the transaction
- PartiallySignedTransaction psbtx;
- std::string error;
- if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) {
- throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
+ util::Result<PartiallySignedTransaction> psbt_res = DecodeBase64PSBT(request.params[0].get_str());
+ if (!psbt_res) {
+ throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", util::ErrorString(psbt_res).original));
}
+ PartiallySignedTransaction psbtx = *psbt_res;
// Get the sighash type
std::optional<int> nHashType = ParseSighashString(request.params[2]);
diff --git a/src/wallet/test/fuzz/scriptpubkeyman.cpp b/src/wallet/test/fuzz/scriptpubkeyman.cpp
index 100db1a6..5733bc90 100644
--- a/src/wallet/test/fuzz/scriptpubkeyman.cpp
+++ b/src/wallet/test/fuzz/scriptpubkeyman.cpp
@@ -178,7 +178,7 @@ FUZZ_TARGET(scriptpubkeyman, .init = initialize_spkm)
(void)spk_manager->SignTransaction(tx_to, coins, sighash, input_errors);
},
[&] {
- std::optional<PartiallySignedTransaction> opt_psbt{ConsumeDeserializable<PartiallySignedTransaction>(fuzzed_data_provider)};
+ std::optional<PartiallySignedTransaction> opt_psbt{ConsumeDeserializableConstructor<PartiallySignedTransaction>(fuzzed_data_provider)};
if (!opt_psbt) {
good_data = false;
return;
diff --git a/src/wallet/test/psbt_wallet_tests.cpp b/src/wallet/test/psbt_wallet_tests.cpp
index 7e2ee6ce..639cb233 100644
--- a/src/wallet/test/psbt_wallet_tests.cpp
+++ b/src/wallet/test/psbt_wallet_tests.cpp
@@ -56,11 +56,10 @@ BOOST_AUTO_TEST_CASE(psbt_updater_test)
import_descriptor(m_wallet, "wpkh(xprv9s21ZrQH143K2LE7W4Xf3jATf9jECxSb7wj91ZnmY4qEJrS66Qru9RFqq8xbkgT32ya6HqYJweFdJUEDf5Q6JFV7jMiUws7kQfe6Tv4RbfN/0h/0h/*h)");
// Call FillPSBT
- PartiallySignedTransaction psbtx;
DataStream ssData{
"70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f000000000000000000"_hex,
};
- ssData >> psbtx;
+ PartiallySignedTransaction psbtx(deserialize, ssData);
// Fill transaction with our data
bool complete = true;
Why this scored 19/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.