Browse Source

mmgen-txcreate: warn user if change address is a used address

The MMGen Project 2 years ago
parent
commit
68caeb31

+ 7 - 0
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):

+ 20 - 1
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: '

+ 12 - 0
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

+ 14 - 6
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',(

+ 6 - 1
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)