Skip to content

Commit 72d30d6

Browse files
committed
Merge #16512: rpc: Shuffle inputs and outputs after joining psbts
c0b5d97 Test that joinpsbts randomly shuffles the inputs (Andrew Chow) 6f405a1 Shuffle inputs and outputs after joining psbts (Andrew Chow) Pull request description: `joinpsbts` currently just adds the inputs and outputs in the order of that the PSBTs were provided. This makes it extremely easy to identify which outputs belong to which inputs. This PR changes that so that all of the inputs and outputs are shuffled in the joined transaction. ACKs for top commit: instagibbs: utACK bitcoin/bitcoin@c0b5d97 jonatack: ACK c0b5d97 modulo suggestions for later. Tree-SHA512: 14a0b7aae07d92e6d2c76a3a3b228b481e1964cb7d34f97515bdda18e2ea05a9f97c5a22affc143b86ae8b95c3cb239849fb54219d65512bc2112264dca915c8
2 parents 408c920 + c0b5d97 commit 72d30d6

File tree

3 files changed

+38
-1
lines changed

3 files changed

+38
-1
lines changed

doc/release-notes-16512.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
RPC changes
2+
-----------
3+
The RPC `joinpsbts` will shuffle the order of the inputs and outputs of the resulting joined psbt.
4+
Previously inputs and outputs were added in the order that the PSBTs were provided which makes correlating inputs to outputs extremely easy.

src/rpc/rawtransaction.cpp

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <policy/rbf.h>
1818
#include <primitives/transaction.h>
1919
#include <psbt.h>
20+
#include <random.h>
2021
#include <rpc/rawtransaction_util.h>
2122
#include <rpc/server.h>
2223
#include <rpc/util.h>
@@ -1615,8 +1616,30 @@ UniValue joinpsbts(const JSONRPCRequest& request)
16151616
merged_psbt.unknown.insert(psbt.unknown.begin(), psbt.unknown.end());
16161617
}
16171618

1619+
// Generate list of shuffled indices for shuffling inputs and outputs of the merged PSBT
1620+
std::vector<int> input_indices(merged_psbt.inputs.size());
1621+
std::iota(input_indices.begin(), input_indices.end(), 0);
1622+
std::vector<int> output_indices(merged_psbt.outputs.size());
1623+
std::iota(output_indices.begin(), output_indices.end(), 0);
1624+
1625+
// Shuffle input and output indicies lists
1626+
Shuffle(input_indices.begin(), input_indices.end(), FastRandomContext());
1627+
Shuffle(output_indices.begin(), output_indices.end(), FastRandomContext());
1628+
1629+
PartiallySignedTransaction shuffled_psbt;
1630+
shuffled_psbt.tx = CMutableTransaction();
1631+
shuffled_psbt.tx->nVersion = merged_psbt.tx->nVersion;
1632+
shuffled_psbt.tx->nLockTime = merged_psbt.tx->nLockTime;
1633+
for (int i : input_indices) {
1634+
shuffled_psbt.AddInput(merged_psbt.tx->vin[i], merged_psbt.inputs[i]);
1635+
}
1636+
for (int i : output_indices) {
1637+
shuffled_psbt.AddOutput(merged_psbt.tx->vout[i], merged_psbt.outputs[i]);
1638+
}
1639+
shuffled_psbt.unknown.insert(merged_psbt.unknown.begin(), merged_psbt.unknown.end());
1640+
16181641
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
1619-
ssTx << merged_psbt;
1642+
ssTx << shuffled_psbt;
16201643
return EncodeBase64((unsigned char*)ssTx.data(), ssTx.size());
16211644
}
16221645

test/functional/rpc_psbt.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,16 @@ def test_psbt_input_keys(psbt_input, keys):
382382
joined_decoded = self.nodes[0].decodepsbt(joined)
383383
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]
384384

385+
# Check that joining shuffles the inputs and outputs
386+
# 10 attempts should be enough to get a shuffled join
387+
shuffled = False
388+
for i in range(0, 10):
389+
shuffled_joined = self.nodes[0].joinpsbts([psbt, psbt2])
390+
shuffled |= joined != shuffled_joined
391+
if shuffled:
392+
break
393+
assert shuffled
394+
385395
# Newly created PSBT needs UTXOs and updating
386396
addr = self.nodes[1].getnewaddress("", "p2sh-segwit")
387397
txid = self.nodes[0].sendtoaddress(addr, 7)

0 commit comments

Comments
 (0)