aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/release-notes-16512.md4
-rw-r--r--src/rpc/rawtransaction.cpp25
-rwxr-xr-xtest/functional/rpc_psbt.py10
3 files changed, 38 insertions, 1 deletions
diff --git a/doc/release-notes-16512.md b/doc/release-notes-16512.md
new file mode 100644
index 0000000000..9aa9cf36f9
--- /dev/null
+++ b/doc/release-notes-16512.md
@@ -0,0 +1,4 @@
+RPC changes
+-----------
+The RPC `joinpsbts` will shuffle the order of the inputs and outputs of the resulting joined psbt.
+Previously inputs and outputs were added in the order that the PSBTs were provided which makes correlating inputs to outputs extremely easy.
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index fb8ea8c227..4003299662 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -17,6 +17,7 @@
#include <policy/rbf.h>
#include <primitives/transaction.h>
#include <psbt.h>
+#include <random.h>
#include <rpc/rawtransaction_util.h>
#include <rpc/server.h>
#include <rpc/util.h>
@@ -1615,8 +1616,30 @@ UniValue joinpsbts(const JSONRPCRequest& request)
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 indicies lists
+ Shuffle(input_indices.begin(), input_indices.end(), FastRandomContext());
+ Shuffle(output_indices.begin(), output_indices.end(), FastRandomContext());
+
+ PartiallySignedTransaction shuffled_psbt;
+ shuffled_psbt.tx = CMutableTransaction();
+ shuffled_psbt.tx->nVersion = merged_psbt.tx->nVersion;
+ shuffled_psbt.tx->nLockTime = merged_psbt.tx->nLockTime;
+ for (int i : input_indices) {
+ shuffled_psbt.AddInput(merged_psbt.tx->vin[i], merged_psbt.inputs[i]);
+ }
+ for (int i : output_indices) {
+ shuffled_psbt.AddOutput(merged_psbt.tx->vout[i], merged_psbt.outputs[i]);
+ }
+ shuffled_psbt.unknown.insert(merged_psbt.unknown.begin(), merged_psbt.unknown.end());
+
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
- ssTx << merged_psbt;
+ ssTx << shuffled_psbt;
return EncodeBase64((unsigned char*)ssTx.data(), ssTx.size());
}
diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py
index 5a04e0c8d8..cb8320a6c2 100755
--- a/test/functional/rpc_psbt.py
+++ b/test/functional/rpc_psbt.py
@@ -382,6 +382,16 @@ class PSBTTest(BitcoinTestFramework):
joined_decoded = self.nodes[0].decodepsbt(joined)
assert len(joined_decoded['inputs']) == 4 and len(joined_decoded['outputs']) == 2 and "final_scriptwitness" not in joined_decoded['inputs'][3] and "final_scriptSig" not in joined_decoded['inputs'][3]
+ # Check that joining shuffles the inputs and outputs
+ # 10 attempts should be enough to get a shuffled join
+ shuffled = False
+ for i in range(0, 10):
+ shuffled_joined = self.nodes[0].joinpsbts([psbt, psbt2])
+ shuffled |= joined != shuffled_joined
+ if shuffled:
+ break
+ assert shuffled
+
# Newly created PSBT needs UTXOs and updating
addr = self.nodes[1].getnewaddress("", "p2sh-segwit")
txid = self.nodes[0].sendtoaddress(addr, 7)