psbt: Return std::optional from PrecomputePSBTData
What changed, and why it matters
This commit changes a Bitcoin Core internal helper function so that it can now report failure when a PSBT (Partially Signed Bitcoin Transaction) is internally inconsistent. Previously, the helper would build transaction precomputation data without checking whether the underlying unsigned transaction could actually be constructed. Now it returns an optional value that callers must check, and they reject or abort the operation if the transaction cannot be valid. This is a defensive hardening change that prevents the code from continuing with malformed PSBTs.
Review as normal defensive hardening. No immediate incident response required. Verify that all callers of PrecomputePSBTData are updated and that no unchecked dereferences remain.
Security signals we found
Defensive nullability added to PSBT precomputation
New error code PSBTError::INVALID_TX for invalid PSBT transactions
Callers now abort on failure to precompute transaction data
Fuzz target updated to handle optional return
Evidence from the diff
PrecomputePSBTData is changed to return std::optional
Changed components
src/psbt.cpp / src/psbt.h - PrecomputePSBTDatasrc/node/psbt.cpp - AnalyzePSBTsrc/psbt.cpp - FinalizePSBTsrc/rpc/rawtransaction.cpp - ProcessPSBTsrc/wallet/wallet.cpp - CWallet::FillPSBTsrc/common/messages.cpp / src/common/types.h - PSBTError enum and stringsrc/wallet/test/fuzz/scriptpubkeyman.cpp - fuzz targetInspect captured patch +27 / −7
diff --git a/src/common/messages.cpp b/src/common/messages.cpp
index 12e32cae..700f4f03 100644
--- a/src/common/messages.cpp
+++ b/src/common/messages.cpp
@@ -116,6 +116,8 @@ bilingual_str PSBTErrorString(PSBTError err)
return Untranslated("Signer does not support PSBT");
case PSBTError::INCOMPLETE:
return Untranslated("Input needs additional signatures or other data");
+ case PSBTError::INVALID_TX:
+ return Untranslated("The transaction cannot be valid");
case PSBTError::OK:
return Untranslated("No errors");
} // no default case, so the compiler can warn about missing cases
diff --git a/src/common/types.h b/src/common/types.h
index c366a847..b9ebca15 100644
--- a/src/common/types.h
+++ b/src/common/types.h
@@ -23,6 +23,7 @@ enum class PSBTError {
EXTERNAL_SIGNER_FAILED,
UNSUPPORTED,
INCOMPLETE,
+ INVALID_TX,
OK,
};
/**
diff --git a/src/node/psbt.cpp b/src/node/psbt.cpp
index 38272909..5b78001c 100644
--- a/src/node/psbt.cpp
+++ b/src/node/psbt.cpp
@@ -24,7 +24,8 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx)
result.inputs.resize(psbtx.inputs.size());
- const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx);
+ // PrecomputePSBTData calls GetUnsignedTx() which we checked already works
+ const PrecomputedTransactionData txdata = *PrecomputePSBTData(psbtx);
for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) {
PSBTInput& input = psbtx.inputs[i];
diff --git a/src/psbt.cpp b/src/psbt.cpp
index 2c67af23..d7320682 100644
--- a/src/psbt.cpp
+++ b/src/psbt.cpp
@@ -429,7 +429,7 @@ void UpdatePSBTOutput(const SigningProvider& provider, PartiallySignedTransactio
psbt_out.FromSignatureData(sigdata);
}
-PrecomputedTransactionData PrecomputePSBTData(const PartiallySignedTransaction& psbt)
+std::optional<PrecomputedTransactionData> PrecomputePSBTData(const PartiallySignedTransaction& psbt)
{
const CMutableTransaction& tx = *psbt.tx;
bool have_all_spent_outputs = true;
@@ -602,7 +602,11 @@ bool FinalizePSBT(PartiallySignedTransaction& psbtx)
// PartiallySignedTransaction did not understand them), this will combine them into a final
// script.
bool complete = true;
- const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx);
+ std::optional<PrecomputedTransactionData> txdata_res = PrecomputePSBTData(psbtx);
+ if (!txdata_res) {
+ return false;
+ }
+ const PrecomputedTransactionData& txdata = *txdata_res;
for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) {
PSBTInput& input = psbtx.inputs.at(i);
complete &= (SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, &txdata, {.sighash_type = input.sighash_type, .finalize = true}, /*out_sigdata=*/nullptr) == PSBTError::OK);
diff --git a/src/psbt.h b/src/psbt.h
index 6e7e0cb9..6fe7a8b2 100644
--- a/src/psbt.h
+++ b/src/psbt.h
@@ -1333,7 +1333,7 @@ enum class PSBTRole {
std::string PSBTRoleName(PSBTRole role);
/** Compute a PrecomputedTransactionData object from a psbt. */
-PrecomputedTransactionData PrecomputePSBTData(const PartiallySignedTransaction& psbt);
+std::optional<PrecomputedTransactionData> PrecomputePSBTData(const PartiallySignedTransaction& psbt);
/** Checks whether a PSBTInput is already signed by checking for non-null finalized fields. */
bool PSBTInputSigned(const PSBTInput& input);
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index 1ed94fac..f6d323fc 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -180,7 +180,11 @@ PartiallySignedTransaction ProcessPSBT(const std::string& psbt_string, const std
}
}
- const PrecomputedTransactionData& txdata = PrecomputePSBTData(psbtx);
+ std::optional<PrecomputedTransactionData> txdata_res = PrecomputePSBTData(psbtx);
+ if (!txdata_res) {
+ throw JSONRPCPSBTError(common::PSBTError::INVALID_TX);
+ }
+ const PrecomputedTransactionData& txdata = *txdata_res;
for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) {
if (PSBTInputSigned(psbtx.inputs.at(i))) {
diff --git a/src/wallet/test/fuzz/scriptpubkeyman.cpp b/src/wallet/test/fuzz/scriptpubkeyman.cpp
index 5733bc90..620c1be6 100644
--- a/src/wallet/test/fuzz/scriptpubkeyman.cpp
+++ b/src/wallet/test/fuzz/scriptpubkeyman.cpp
@@ -184,7 +184,11 @@ FUZZ_TARGET(scriptpubkeyman, .init = initialize_spkm)
return;
}
auto psbt{*opt_psbt};
- const PrecomputedTransactionData txdata{PrecomputePSBTData(psbt)};
+ std::optional<PrecomputedTransactionData> txdata_res = PrecomputePSBTData(psbt);
+ if (!txdata_res) {
+ return;
+ }
+ const PrecomputedTransactionData& txdata = *txdata_res;
common::PSBTFillOptions options{
.sign = fuzzed_data_provider.ConsumeBool(),
.sighash_type = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 151),
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 7473f80f..e569ea6e 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -2245,7 +2245,11 @@ std::optional<PSBTError> CWallet::FillPSBT(PartiallySignedTransaction& psbtx, co
}
}
- const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx);
+ std::optional<PrecomputedTransactionData> txdata_res = PrecomputePSBTData(psbtx);
+ if (!txdata_res) {
+ return PSBTError::INVALID_TX;
+ }
+ const PrecomputedTransactionData& txdata = *txdata_res;
// Fill in information from ScriptPubKeyMans
for (ScriptPubKeyMan* spk_man : GetAllScriptPubKeyMans()) {
Why this scored 34/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.