What changed, and why it matters
This change updates the Bitcoin Core 'joinpsbts' RPC command so it only accepts PSBT version 0 files. Previously, it could accept newer PSBT versions, which might have led to incorrect or unexpected behavior when combining partially signed transactions. The change adds an explicit version check and rejects non-v0 PSBTs with a clear error message.
Treat as a hardening change. If running a node that exposes joinpsbts, ensure this or a later version is deployed so users cannot accidentally combine unsupported PSBT versions. Monitor for related PSBTv2 handling fixes.
Security signals we found
RPC input validation hardening
PSBT version restriction
Potential correctness issue when joining PSBTv2 transactions
Evidence from the diff
The commit modifies src/rpc/rawtransaction.cpp in the joinpsbts RPC implementation. It changes the help text to state ‘version 0 PSBTs’, reorders the push_back and reference binding, and adds a runtime check: if any input PSBT’s version is not 0, the RPC throws RPC_INVALID_PARAMETER. The rest of the join logic remains unchanged. This is a hardening/restriction change rather than a full fix of an underlying bug.
Changed components
src/rpc/rawtransaction.cppjoinpsbts RPC commandPSBT processingInspect captured patch +6 / −3
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index 202a8660..7eb1c68c 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -1769,7 +1769,7 @@ static RPCMethod joinpsbts()
{
return RPCMethod{
"joinpsbts",
- "Joins multiple distinct PSBTs with different inputs and outputs into one PSBT with inputs and outputs from all of the PSBTs\n"
+ "Joins multiple distinct version 0 PSBTs with different inputs and outputs into one version 0 PSBT with inputs and outputs from all of the PSBTs\n"
"No input in any of the PSBTs can be in more than one of the PSBTs.\n",
{
{"txs", RPCArg::Type::ARR, RPCArg::Optional::NO, "The base64 strings of partially signed transactions",
@@ -1800,8 +1800,11 @@ static RPCMethod joinpsbts()
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);
+ psbtxs.push_back(*psbt_res);
+ const PartiallySignedTransaction& psbtx = psbtxs.back();
+ if (psbtx.GetVersion() != 0) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "joinpsbts only operates on version 0 PSBTs");
+ }
// Choose the highest version number
if (psbtx.tx_version > best_version) {
best_version = psbtx.tx_version;
Why this scored 32/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.