Merge bitcoin/bitcoin#35516: rpc: preserve global xpubs and proprietary fields in joinpsbts

436921eb46 test: check joinpsbts preserves global xpubs and proprietary fields (Thomas)
011094b282 rpc: preserve global xpubs and proprietary fields in joinpsbts (Thomas)

Pull request description:

  `joinpsbts` collects the global xpubs of all the joined PSBTs into `merged_psbt`, but returns a separately constructed `shuffled_psbt` into which only the inputs, outputs, and unknown fields are copied. The collected `PSBT_GLOBAL_XPUB` records are silently dropped, and `PSBT_GLOBAL_PROPRIETARY` records are not collected at all.

  The xpub collection was added in #17034, which was written against a `joinpsbts` that still returned `merged_psbt`, but was merged after #16512 had introduced the `shuffled_psbt` rebuild, so the collected xpubs have never reached the result.

  Shuffle the inputs and outputs of `merged_psbt` in place instead of rebuilding a new PSBT, so that all global data is preserved, and union the global proprietary records in the merge loop, matching the `combinepsbt` behavior from #34893.

ACKs for top commit:
  jpk68:
    ACK 436921eb46
  achow101:
    ACK 436921eb46
  winterrdog:
    tACK 436921eb46

Tree-SHA512: d9de34c25aecc29b6b4fb80d6584fa919cc5ff9b7ef2f4d8ce35c4043fe7638fefb8af10448f2cd14021f5d25e149f0efc8798c5b8c9bc8b5582c6152010e891
This commit is contained in:
Ava Chow
2026-08-24 14:38:14 -07:00
2 changed files with 44 additions and 20 deletions

View File

@@ -43,7 +43,6 @@
#include <validationinterface.h>
#include <cstdint>
#include <numeric>
#include <univalue.h>
@@ -1929,30 +1928,16 @@ static RPCMethod joinpsbts()
merged_psbt.AddOutput(output);
}
merged_psbt.MergeGlobalXPubs(psbt);
merged_psbt.m_proprietary.insert(psbt.m_proprietary.begin(), psbt.m_proprietary.end());
merged_psbt.unknown.insert(psbt.unknown.begin(), psbt.unknown.end());
}
// Generate list of shuffled indices for shuffling inputs and outputs of the merged PSBT
std::vector<int> input_indices(merged_psbt.inputs.size());
std::iota(input_indices.begin(), input_indices.end(), 0);
std::vector<int> output_indices(merged_psbt.outputs.size());
std::iota(output_indices.begin(), output_indices.end(), 0);
// Shuffle input and output indices lists
std::shuffle(input_indices.begin(), input_indices.end(), FastRandomContext());
std::shuffle(output_indices.begin(), output_indices.end(), FastRandomContext());
PartiallySignedTransaction shuffled_psbt(tx, merged_psbt.GetVersion());
for (int i : input_indices) {
shuffled_psbt.AddInput(merged_psbt.inputs[i]);
}
for (int i : output_indices) {
shuffled_psbt.AddOutput(merged_psbt.outputs[i]);
}
shuffled_psbt.unknown.insert(merged_psbt.unknown.begin(), merged_psbt.unknown.end());
// Shuffle the inputs and outputs for privacy
std::shuffle(merged_psbt.inputs.begin(), merged_psbt.inputs.end(), FastRandomContext());
std::shuffle(merged_psbt.outputs.begin(), merged_psbt.outputs.end(), FastRandomContext());
DataStream ssTx{};
ssTx << shuffled_psbt;
ssTx << merged_psbt;
return EncodeBase64(ssTx);
},
};