From 6f405a1d3b38395e35571b68aae55cae50e0762a Mon Sep 17 00:00:00 2001 From: Andrew Chow Date: Wed, 31 Jul 2019 18:02:24 -0400 Subject: [PATCH 1/2] Shuffle inputs and outputs after joining psbts --- doc/release-notes-16512.md | 4 ++++ src/rpc/rawtransaction.cpp | 25 ++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 doc/release-notes-16512.md diff --git a/doc/release-notes-16512.md b/doc/release-notes-16512.md new file mode 100644 index 000000000..9aa9cf36f --- /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 0ab504de0..a26304312 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -1604,8 +1605,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 input_indices(merged_psbt.inputs.size()); + std::iota(input_indices.begin(), input_indices.end(), 0); + std::vector 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()); } From c0b5d9710322a614a50ab5da081558cf6a38ad2a Mon Sep 17 00:00:00 2001 From: Andrew Chow Date: Wed, 14 Aug 2019 14:29:55 -0400 Subject: [PATCH 2/2] Test that joinpsbts randomly shuffles the inputs --- test/functional/rpc_psbt.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py index b3d869620..493e025e4 100755 --- a/test/functional/rpc_psbt.py +++ b/test/functional/rpc_psbt.py @@ -370,6 +370,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)