Merge bitcoin/bitcoin#32958: wallet/refactor: Update SignPSBTInput to return util::Expected<void, PSBTError> and remove PSBTError:Ok
What changed, and why it matters
This commit is a code cleanup (refactor) that changes how a PSBT signing function reports success or failure. It replaces an explicit 'OK' success code with a standard C++ expected-result type. There is no security bug being fixed here; the behavior of the signing logic is unchanged.
No action required. This is a non-security refactor. Treat as routine code maintenance.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit refactors SignPSBTInput to return util::Expected
Changed components
src/psbt.cppsrc/psbt.hsrc/node/psbt.cppsrc/rpc/rawtransaction.cppsrc/wallet/scriptpubkeyman.cppsrc/common/messages.cppsrc/common/types.hInspect captured patch +28 / −25
### src/common/messages.cpp
@@ -119,8 +119,6 @@ bilingual_str PSBTErrorString(PSBTError err)
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
assert(false);
}
### src/common/types.h
@@ -24,7 +24,6 @@ enum class PSBTError {
UNSUPPORTED,
INCOMPLETE,
INVALID_TX,
- OK,
};
/**
* Instructions for how a PSBT should be signed or filled with information.
### src/node/psbt.cpp
@@ -72,7 +72,8 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx)
// Figure out what is missing
SignatureData outdata;
- bool complete = SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, &txdata, /*options=*/{}, &outdata) == PSBTError::OK;
+ const auto sign_result = SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, &txdata, /*options*/{}, &outdata);
+ bool complete = sign_result.has_value();
// Things are missing
if (!complete) {
@@ -130,7 +131,8 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx)
PSBTInput& input = psbtx.inputs[i];
Coin newcoin;
- if (SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, nullptr, /*options=*/{}) != PSBTError::OK || !input.GetUTXO(newcoin.out)) {
+ const auto sign_result = SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, nullptr, /*options=*/{});
+ if (!sign_result.has_value() || !input.GetUTXO(newcoin.out)) {
success = false;
break;
} else {
### src/psbt.cpp
@@ -636,17 +636,17 @@ std::optional<PrecomputedTransactionData> PrecomputePSBTData(const PartiallySign
return txdata;
}
-PSBTError SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, const PrecomputedTransactionData* txdata, const common::PSBTFillOptions& options, SignatureData* out_sigdata)
+util::Expected<void, PSBTError> SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, const PrecomputedTransactionData* txdata, const common::PSBTFillOptions& options, SignatureData* out_sigdata)
{
PSBTInput& input = psbt.inputs.at(index);
std::optional<CMutableTransaction> unsigned_tx = psbt.GetUnsignedTx();
if (!unsigned_tx) {
- return PSBTError::INVALID_TX;
+ return util::Unexpected{PSBTError::INVALID_TX};
}
const CMutableTransaction& tx = *unsigned_tx;
if (PSBTInputSignedAndVerified(psbt, index, txdata)) {
- return PSBTError::OK;
+ return {};
}
// Fill SignatureData with input info
@@ -661,10 +661,10 @@ PSBTError SignPSBTInput(const SigningProvider& provider, PartiallySignedTransact
// If we're taking our information from a non-witness UTXO, verify that it matches the prevout.
COutPoint prevout = input.GetOutPoint();
if (prevout.n >= input.non_witness_utxo->vout.size()) {
- return PSBTError::MISSING_INPUTS;
+ return util::Unexpected{PSBTError::MISSING_INPUTS};
}
if (input.non_witness_utxo->GetHash() != prevout.hash) {
- return PSBTError::MISSING_INPUTS;
+ return util::Unexpected{PSBTError::MISSING_INPUTS};
}
utxo = input.non_witness_utxo->vout[prevout.n];
} else if (!input.witness_utxo.IsNull()) {
@@ -675,7 +675,7 @@ PSBTError SignPSBTInput(const SigningProvider& provider, PartiallySignedTransact
// a witness signature in this situation.
require_witness_sig = true;
} else {
- return PSBTError::MISSING_INPUTS;
+ return util::Unexpected{PSBTError::MISSING_INPUTS};
}
// Get the sighash type
@@ -687,7 +687,7 @@ PSBTError SignPSBTInput(const SigningProvider& provider, PartiallySignedTransact
// For user safety, the desired sighash must be provided if the PSBT wants something other than the default set in the previous line.
if (input.sighash_type && input.sighash_type != sighash) {
- return PSBTError::SIGHASH_MISMATCH;
+ return util::Unexpected{PSBTError::SIGHASH_MISMATCH};
}
// Set the PSBT sighash field when sighash is not DEFAULT or ALL
// DEFAULT is allowed for non-taproot inputs since DEFAULT may be passed for them (e.g. the psbt being signed also has taproot inputs)
@@ -700,20 +700,20 @@ PSBTError SignPSBTInput(const SigningProvider& provider, PartiallySignedTransact
// Check all existing signatures use the sighash type
if (sighash == SIGHASH_DEFAULT) {
if (!input.m_tap_key_sig.empty() && input.m_tap_key_sig.size() != 64) {
- return PSBTError::SIGHASH_MISMATCH;
+ return util::Unexpected{PSBTError::SIGHASH_MISMATCH};
}
for (const auto& [_, sig] : input.m_tap_script_sigs) {
- if (sig.size() != 64) return PSBTError::SIGHASH_MISMATCH;
+ if (sig.size() != 64) return util::Unexpected{PSBTError::SIGHASH_MISMATCH};
}
} else {
if (!input.m_tap_key_sig.empty() && (input.m_tap_key_sig.size() != 65 || input.m_tap_key_sig.back() != sighash)) {
- return PSBTError::SIGHASH_MISMATCH;
+ return util::Unexpected{PSBTError::SIGHASH_MISMATCH};
}
for (const auto& [_, sig] : input.m_tap_script_sigs) {
- if (sig.size() != 65 || sig.back() != sighash) return PSBTError::SIGHASH_MISMATCH;
+ if (sig.size() != 65 || sig.back() != sighash) return util::Unexpected{PSBTError::SIGHASH_MISMATCH};
}
for (const auto& [_, sig] : input.partial_sigs) {
- if (sig.second.back() != sighash) return PSBTError::SIGHASH_MISMATCH;
+ if (sig.second.back() != sighash) return util::Unexpected{PSBTError::SIGHASH_MISMATCH};
}
}
@@ -726,7 +726,7 @@ PSBTError SignPSBTInput(const SigningProvider& provider, PartiallySignedTransact
sig_complete = ProduceSignature(provider, creator, utxo.scriptPubKey, sigdata);
}
// Verify that a witness signature was produced in case one was required.
- if (require_witness_sig && !sigdata.witness) return PSBTError::INCOMPLETE;
+ if (require_witness_sig && !sigdata.witness) return util::Unexpected{PSBTError::INCOMPLETE};
// If we are not finalizing, set sigdata.complete to false to not set the scriptWitness
if (!options.finalize && sigdata.complete) sigdata.complete = false;
@@ -749,7 +749,8 @@ PSBTError SignPSBTInput(const SigningProvider& provider, PartiallySignedTransact
out_sigdata->missing_witness_script = sigdata.missing_witness_script;
}
- return sig_complete ? PSBTError::OK : PSBTError::INCOMPLETE;
+ if (!sig_complete) return util::Unexpected{PSBTError::INCOMPLETE};
+ return {};
}
void RemoveUnnecessaryTransactions(PartiallySignedTransaction& psbtx)
@@ -803,7 +804,8 @@ bool FinalizePSBT(PartiallySignedTransaction& psbtx)
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);
+ const auto sign_result = SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, &txdata, {.sighash_type = input.sighash_type, .finalize = true}, /*out_sigdata=*/nullptr);
+ complete &= sign_result.has_value();
}
return complete;
### src/psbt.h
@@ -18,6 +18,7 @@
#include <streams.h>
#include <uint256.h>
#include <util/result.h>
+#include <util/expected.h>
#include <optional>
#include <bitset>
@@ -1642,7 +1643,7 @@ bool PSBTInputSignedAndVerified(const PartiallySignedTransaction& psbt, unsigned
* txdata should be the output of PrecomputePSBTData (which can be shared across
* multiple SignPSBTInput calls). If it is nullptr, a dummy signature will be created.
**/
-[[nodiscard]] PSBTError SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, const PrecomputedTransactionData* txdata, const common::PSBTFillOptions& options, SignatureData* out_sigdata = nullptr);
+[[nodiscard]] util::Expected<void, PSBTError> SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, const PrecomputedTransactionData* txdata, const common::PSBTFillOptions& options, SignatureData* out_sigdata = nullptr);
/** Reduces the size of the PSBT by dropping unnecessary `non_witness_utxos` (i.e. complete previous transactions) from a psbt when all inputs are segwit v1. */
void RemoveUnnecessaryTransactions(PartiallySignedTransaction& psbtx);
### src/rpc/rawtransaction.cpp
@@ -195,7 +195,8 @@ PartiallySignedTransaction ProcessPSBT(const std::string& psbt_string, const std
// We only actually care about those if our signing provider doesn't hide private
// information, as is the case with `descriptorprocesspsbt`
// Only error for mismatching sighash types as it is critical that the sighash to sign with matches the PSBT's
- if (SignPSBTInput(provider, psbtx, /*index=*/i, &txdata, {.sighash_type = sighash_type, .finalize = finalize}, /*out_sigdata=*/nullptr) == common::PSBTError::SIGHASH_MISMATCH) {
+ const auto sign_result = SignPSBTInput(provider, psbtx, /*index=*/i, &txdata, {.sighash_type = sighash_type, .finalize = finalize}, /*out_sigdata=*/nullptr);
+ if (!sign_result.has_value() && sign_result.error() == common::PSBTError::SIGHASH_MISMATCH) {
throw JSONRPCPSBTError(common::PSBTError::SIGHASH_MISMATCH);
}
}
### src/wallet/scriptpubkeyman.cpp
@@ -1425,9 +1425,9 @@ std::optional<PSBTError> DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTran
}
}
- PSBTError res = SignPSBTInput(HidingSigningProvider(keys.get(), /*hide_secret=*/!options.sign, /*hide_origin=*/!options.bip32_derivs), psbtx, i, &txdata, options, /*out_sigdata=*/nullptr);
- if (res != PSBTError::OK && res != PSBTError::INCOMPLETE) {
- return res;
+ const auto sign_result = SignPSBTInput(HidingSigningProvider(keys.get(), /*hide_secret=*/!options.sign, /*hide_origin=*/!options.bip32_derivs), psbtx, i, &txdata, options, /*out_sigdata=*/nullptr);
+ if (!sign_result.has_value() && sign_result.error() != PSBTError::INCOMPLETE) {
+ return sign_result.error();
}
bool signed_one = PSBTInputSigned(input);Why this scored 15/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.