From 710eda1a563003c15ac0d8f6edb26ebf0d394a64 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 8 Feb 2018 23:03:45 +0100 Subject: [PATCH] coinchooser: make output value rounding configurable (config var, qt) --- gui/qt/main_window.py | 15 ++++++++++++++- lib/coinchooser.py | 20 ++++++++++++++++---- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py index e4f5085a7e8b..1c860edfe22a 100644 --- a/gui/qt/main_window.py +++ b/gui/qt/main_window.py @@ -1136,7 +1136,8 @@ def setAmount(self, byte_size): def feerounding_onclick(): text = (self.feerounding_text + '\n\n' + _('To somewhat protect your privacy, Electrum tries to create change with similar precision to other outputs.') + ' ' + - _('At most 100 satoshis might be lost due to this rounding.') + '\n' + + _('At most 100 satoshis might be lost due to this rounding.') + ' ' + + _("You can disable this setting in '{}'.").format(_('Preferences')) + '\n' + _('Also, dust is not kept as change, but added to the fee.')) QMessageBox.information(self, 'Fee rounding', text) @@ -2893,6 +2894,18 @@ def on_unconf(x): unconf_cb.stateChanged.connect(on_unconf) tx_widgets.append((unconf_cb, None)) + def on_outrounding(x): + self.config.set_key('coin_chooser_output_rounding', bool(x)) + enable_outrounding = self.config.get('coin_chooser_output_rounding', False) + outrounding_cb = QCheckBox(_('Enable output value rounding')) + outrounding_cb.setToolTip( + _('Set the value of the change output so that it has similar precision to the other outputs.') + '\n' + + _('This might improve your privacy somewhat.') + '\n' + + _('If enabled, at most 100 satoshis might be lost due to this, per transaction.')) + outrounding_cb.setChecked(enable_outrounding) + outrounding_cb.stateChanged.connect(on_outrounding) + tx_widgets.append((outrounding_cb, None)) + # Fiat Currency hist_checkbox = QCheckBox() fiat_address_checkbox = QCheckBox() diff --git a/lib/coinchooser.py b/lib/coinchooser.py index 472e3aa3820f..ffc5bfd8e901 100644 --- a/lib/coinchooser.py +++ b/lib/coinchooser.py @@ -87,6 +87,8 @@ def strip_unneeded(bkts, sufficient_funds): class CoinChooserBase(PrintError): + enable_output_value_rounding = False + def keys(self, coins): raise NotImplementedError @@ -135,7 +137,13 @@ def trailing_zeroes(val): zeroes = [trailing_zeroes(i) for i in output_amounts] min_zeroes = min(zeroes) max_zeroes = max(zeroes) - zeroes = range(max(0, min_zeroes - 1), (max_zeroes + 1) + 1) + + if n > 1: + zeroes = range(max(0, min_zeroes - 1), (max_zeroes + 1) + 1) + else: + # if there is only one change output, this will ensure that we aim + # to have one that is exactly as precise as the most precise output + zeroes = [min_zeroes] # Calculate change; randomize it a bit if using more than 1 output remaining = change_amount @@ -150,8 +158,10 @@ def trailing_zeroes(val): n -= 1 # Last change output. Round down to maximum precision but lose - # no more than 100 satoshis to fees (2dp) - N = pow(10, min(2, zeroes[0])) + # no more than 10**max_dp_to_round_for_privacy + # e.g. a max of 2 decimal places means losing 100 satoshis to fees + max_dp_to_round_for_privacy = 2 if self.enable_output_value_rounding else 0 + N = pow(10, min(max_dp_to_round_for_privacy, zeroes[0])) amount = (remaining // N) * N amounts.append(amount) @@ -370,4 +380,6 @@ def get_name(config): def get_coin_chooser(config): klass = COIN_CHOOSERS[get_name(config)] - return klass() + coinchooser = klass() + coinchooser.enable_output_value_rounding = config.get('coin_chooser_output_rounding', False) + return coinchooser