Skip to content

Commit

Permalink
add a checkbox to disable shuffling the outputs when using op_return
Browse files Browse the repository at this point in the history
The intended use case for this is sending SLP token using a hex encoded op_return payload.

Test plan:
Check that the new checkbox is only enabled if something is typed into the op_return payload field, and disabled when this payload is deleted.

Add multiple outputs in the send tab, leave the op_return field blank. Click "Preview" multiple times and make sure the outputs are shuffled (no matter if the new checkbox is checked or not).

Type something in the op_return field, and repeat the above test. Now the shuffling behavior must depend on the checkbox. The op_return output should always be first. The other outputs should be shuffled if the checkbox is checked. If it is unchecked, then the change output must be last, and preceded by the other outputs in the specified order.

Click send and check the order in a block explorer.
  • Loading branch information
PiRK committed May 22, 2023
1 parent 3c1ed6a commit 22b5d08
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 6 deletions.
8 changes: 5 additions & 3 deletions electrumabc/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -808,16 +808,18 @@ def serialize_input(self, txin, script, estimate_size=False):
def shuffle_inputs(self):
random.shuffle(self._inputs)

def shuffle_outputs(self):
def sort_outputs(self, shuffle: bool = True):
"""Put the op_return output first, and then shuffle the other outputs unless
this behavior is explicitely disabled."""
op_returns = []
other_outputs = []
for txo in self._outputs:
if txo.is_opreturn():
op_returns.append(txo)
else:
other_outputs.append(txo)

random.shuffle(other_outputs)
if shuffle:
random.shuffle(other_outputs)
self._outputs = op_returns + other_outputs

def serialize_output(self, output):
Expand Down
6 changes: 4 additions & 2 deletions electrumabc/wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ def sweep(
inputs, outputs, locktime=locktime, sign_schnorr=sign_schnorr
)
tx.shuffle_inputs()
tx.shuffle_outputs()
tx.sort_outputs()
tx.sign(keypairs)
return tx

Expand Down Expand Up @@ -2057,6 +2057,7 @@ def make_unsigned_transaction(
fixed_fee=None,
change_addr=None,
sign_schnorr=None,
shuffle_outputs=True,
):
"""sign_schnorr flag controls whether to mark the tx as signing with
schnorr or not. Specify either a bool, or set the flag to 'None' to use
Expand Down Expand Up @@ -2169,7 +2170,8 @@ def fee_estimator(size):
raise ExcessiveFee()

tx.shuffle_inputs()
tx.shuffle_outputs()
tx.sort_outputs(shuffle=shuffle_outputs)

# Timelock tx to current height.
locktime = 0
if config.is_current_block_locktime_enabled():
Expand Down
30 changes: 29 additions & 1 deletion electrumabc_gui/qt/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -1944,11 +1944,30 @@ def create_send_tab(self):
)
)
hbox.addWidget(self.opreturn_rawhex_cb)
self.opreturn_shuffle_outputs_cb = QtWidgets.QCheckBox(_("Shuffle outputs"))
self.opreturn_shuffle_outputs_cb.setChecked(True)
self.opreturn_shuffle_outputs_cb.setEnabled(
self.message_opreturn_e.text() != ""
)
self.opreturn_shuffle_outputs_cb.setToolTip(
_(
"<p>For some OP_RETURN use cases such as SLP, the order of the outputs"
" in the transaction matters, so you might want to uncheck this. By"
" default, outputs are shuffled for privacy reasons. This setting is "
"ignored if the OP_RETURN data is empty.</p>"
)
)
hbox.addWidget(self.opreturn_shuffle_outputs_cb)
grid.addLayout(hbox, 3, 1, 1, -1)

self.message_opreturn_e.textChanged.connect(
lambda text: self.opreturn_shuffle_outputs_cb.setEnabled(bool(text))
)

self.send_tab_opreturn_widgets = [
self.message_opreturn_e,
self.opreturn_rawhex_cb,
self.opreturn_shuffle_outputs_cb,
self.opreturn_label,
]

Expand Down Expand Up @@ -2573,8 +2592,17 @@ def do_send(self, preview=False):
if not r:
return
outputs, fee, tx_desc, coins = r
shuffle_outputs = True
if (
self.message_opreturn_e.isVisible()
and self.message_opreturn_e.text()
and not self.opreturn_shuffle_outputs_cb.isChecked()
):
shuffle_outputs = False
try:
tx = self.wallet.make_unsigned_transaction(coins, outputs, self.config, fee)
tx = self.wallet.make_unsigned_transaction(
coins, outputs, self.config, fee, shuffle_outputs=shuffle_outputs
)
except NotEnoughFunds:
self.show_message(_("Insufficient funds"))
return
Expand Down

0 comments on commit 22b5d08

Please sign in to comment.