From 68caeb3130f2888ccbad1d05e8728279ea4e90a7 Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Wed, 16 Nov 2022 17:56:04 +0000 Subject: [PATCH] mmgen-txcreate: warn user if change address is a used address --- mmgen/tw/addresses.py | 7 +++++++ mmgen/tx/new.py | 21 ++++++++++++++++++++- test/overlay/fakemods/mmgen/tx/new.py | 12 ++++++++++++ test/test_py_d/ts_regtest.py | 20 ++++++++++++++------ test/test_py_d/ts_shared.py | 7 ++++++- 5 files changed, 59 insertions(+), 8 deletions(-) create mode 100644 test/overlay/fakemods/mmgen/tx/new.py diff --git a/mmgen/tw/addresses.py b/mmgen/tw/addresses.py index 99afbfed..6006d27a 100755 --- a/mmgen/tw/addresses.py +++ b/mmgen/tw/addresses.py @@ -242,6 +242,13 @@ class TwAddresses(TwView): def dump_fn_pfx(self): return 'listaddresses' + (f'-minconf-{self.minconf}' if self.minconf else '') + def is_used(self,coinaddr): + for e in self.data: + if e.addr == coinaddr: + return bool(e.recvd) + else: # addr not in tracking wallet + return None + class action(TwView.action): def s_amt(self,parent): diff --git a/mmgen/tx/new.py b/mmgen/tx/new.py index 69139cf9..4cd7733b 100755 --- a/mmgen/tx/new.py +++ b/mmgen/tx/new.py @@ -15,7 +15,7 @@ tx.new: new transaction class from ..globalvars import * from ..opts import opt from .base import Base -from ..color import pink +from ..color import pink,yellow from ..obj import get_obj,MMGenList from ..util import msg,qmsg,fmt,die,suf,remove_dups,get_extension from ..addr import is_mmgen_id,CoinAddr,is_coin_addr @@ -235,6 +235,25 @@ class New(Base): self.add_mmaddrs_to_outputs(ad_w,ad_f) self.check_dup_addrs('outputs') + chg_idx = self.get_chg_output_idx() + if chg_idx is not None: + await self.warn_chg_addr_used(self.outputs[chg_idx]) + + async def warn_chg_addr_used(self,chg): + from ..tw.addresses import TwAddresses + if (await TwAddresses(self.proto,get_data=True)).is_used(chg.addr): + from ..ui import keypress_confirm + if not keypress_confirm( + '{a} {b} {c}\n{d}'.format( + a = yellow(f'Requested change address'), + b = (chg.mmid or chg.addr).hl(), + c = yellow('is already used!'), + d = yellow('Address reuse harms your privacy and security. Continue anyway? (y/N): ') + ), + complete_prompt = True, + default_yes = False ): + die(1,'Exiting at user request') + # inputs methods def select_unspent(self,unspent): prompt = 'Enter a range or space-separated list of outputs to spend: ' diff --git a/test/overlay/fakemods/mmgen/tx/new.py b/test/overlay/fakemods/mmgen/tx/new.py new file mode 100644 index 00000000..0d3a877c --- /dev/null +++ b/test/overlay/fakemods/mmgen/tx/new.py @@ -0,0 +1,12 @@ +import os as overlay_fake_os +from .new_orig import * + +if overlay_fake_os.getenv('MMGEN_BOGUS_UNSPENT_DATA'): + + class overlay_fake_data: + + async def warn_chg_addr_used(foo,us): + from ..util import ymsg + ymsg('Bogus unspent data: skipping change address is used check') + + New.warn_chg_addr_used = overlay_fake_data.warn_chg_addr_used diff --git a/test/test_py_d/ts_regtest.py b/test/test_py_d/ts_regtest.py index f6b85c01..6964354d 100755 --- a/test/test_py_d/ts_regtest.py +++ b/test/test_py_d/ts_regtest.py @@ -638,9 +638,13 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared): sid1 = self._get_user_subsid('bob','29L') sid2 = self._get_user_subsid('bob','127S') chg_addr = self._user_sid('bob') + (':B:1',':L:1')[self.proto.coin=='BCH'] - outputs_cl = [sid1+':C:2,0.29',sid2+':C:3,0.127',chg_addr] - inputs = ('3','1')[self.proto.coin=='BCH'] - return self.user_txdo('bob',rtFee[1],outputs_cl,inputs,extra_args=['--subseeds=127']) + return self.user_txdo( + user = 'bob', + fee = rtFee[1], + outputs_cl = [sid1+':C:2,0.29',sid2+':C:3,0.127',chg_addr], + outputs_list = ('3','1')[self.proto.coin=='BCH'], + extra_args = ['--subseeds=127'], + used_chg_addr_resp = (None,'y')[self.proto.coin=='BCH'] ) def bob_twview2(self): sid1 = self._get_user_subsid('bob','29L') @@ -779,7 +783,8 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared): bad_locktime = False, full_tx_view = False, menu = ['M'], - skip_passphrase = False ): + skip_passphrase = False, + used_chg_addr_resp = None ): t = self.spawn('mmgen-txdo', ['-d',self.tmpdir,'-B','--'+user] + @@ -793,7 +798,9 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared): file_desc = 'Signed transaction', interactive_fee = (tx_fee,'')[bool(fee)], add_comment = tx_comment_jp, - view = 't',save=True) + view = 't', + save = True, + used_chg_addr_resp = used_chg_addr_resp ) if not skip_passphrase: t.passphrase(dfl_wcls.desc,rt_pw) @@ -848,7 +855,8 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared): outputs_cl = self._create_tx_outputs('alice',(('L',1,',60'),('C',1,',40'))) # alice_sid:L:1, alice_sid:C:1 outputs_cl += [self._user_sid('bob')+':'+rtBobOp3] return self.user_txdo('bob',rtFee[1],outputs_cl,'3', - extra_args=([],['--rbf'])[self.proto.cap('rbf')]) + extra_args = ([],['--rbf'])[self.proto.cap('rbf')], + used_chg_addr_resp = 'y' ) def bob_send_non_mmgen(self): outputs_cl = self._create_tx_outputs('alice',( diff --git a/test/test_py_d/ts_shared.py b/test/test_py_d/ts_shared.py index 8c486e10..6dc6b5b7 100755 --- a/test/test_py_d/ts_shared.py +++ b/test/test_py_d/ts_shared.py @@ -46,13 +46,18 @@ class TestSuiteShared(object): add_comment = '', view = 't', save = True, - tweaks = [] ): + tweaks = [], + used_chg_addr_resp = None ): txdo = (caller or self.test_name)[:4] == 'txdo' expect_pat = r'\[q\]uit view, .*?:.' delete_pat = r'Enter account number .*:.' confirm_pat = r'Is this what you want.*:.' + + if used_chg_addr_resp is not None: + t.expect('reuse harms your privacy.*:.*',used_chg_addr_resp,regex=True) + pat = expect_pat for choice in menu + ['q']: t.expect(pat,choice,regex=True)