From eeb98869d391fbae19f219285c80305b6eaaebc7 Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Sun, 29 Sep 2024 11:59:56 +0000 Subject: [PATCH] CoinAddr: add `views`, `view_pref` attrs --- mmgen/addr.py | 9 +++++++-- mmgen/addrfile.py | 2 +- mmgen/addrlist.py | 4 ++-- mmgen/autosign.py | 2 +- mmgen/main_addrimport.py | 2 +- mmgen/proto/btc/tw/txhistory.py | 8 ++++---- mmgen/proto/btc/tx/info.py | 9 +++++---- mmgen/proto/eth/tx/info.py | 6 +++--- mmgen/proto/eth/tx/online.py | 2 +- mmgen/tw/addresses.py | 4 ++-- mmgen/tw/ctl.py | 3 ++- mmgen/tw/txhistory.py | 14 ++++++++------ mmgen/tw/unspent.py | 4 ++-- mmgen/tw/view.py | 4 ++++ mmgen/tx/new.py | 2 +- mmgen/xmrwallet.py | 12 ++++++------ test/gentest.py | 5 +++-- 17 files changed, 53 insertions(+), 39 deletions(-) diff --git a/mmgen/addr.py b/mmgen/addr.py index 9efbdd33..ddcfced6 100755 --- a/mmgen/addr.py +++ b/mmgen/addr.py @@ -158,6 +158,8 @@ class CoinAddr(HiliteStr, InitErrors, MMGenObject): ap = proto.decode_addr(addr) assert ap, f'coin address {addr!r} could not be parsed' me = str.__new__(cls, addr) + me.views = [addr] + me.view_pref = 0 me.addr_fmt = ap.fmt me.bytes = ap.bytes me.ver_bytes = ap.ver_bytes @@ -177,10 +179,13 @@ class CoinAddr(HiliteStr, InitErrors, MMGenObject): def fmtc(cls,s,width,color=False): return super().fmtc( s=s[:width-2]+'..' if len(s) > width else s, width=width, color=color ) - def fmt(self, width, color=False): - s = self + def fmt(self, view_pref, width, color=False): + s = self.views[view_pref] return super().fmtc(f'{s[:width-2]}..' if len(s) > width else s, width=width, color=color) + def hl(self, view_pref, color=True): + return getattr(color_mod, self.color)(self.views[view_pref]) if color else self.views[view_pref] + def is_coin_addr(proto,s): return get_obj( CoinAddr, proto=proto, addr=s, silent=True, return_bool=True ) diff --git a/mmgen/addrfile.py b/mmgen/addrfile.py index 8bb5d00b..4f6d1a1e 100755 --- a/mmgen/addrfile.py +++ b/mmgen/addrfile.py @@ -124,7 +124,7 @@ class AddrFile(MMGenObject): elif type(p).__name__ == 'PasswordList': out.append(fs.format(e.idx,e.passwd,c)) else: # First line with idx - out.append(fs.format(e.idx,e.addr,c)) + out.append(fs.format(e.idx, e.addr.views[e.addr.view_pref], c)) if p.has_keys: if self.cfg.b16: out.append(fs.format( '', f'orig_hex: {e.sec.orig_bytes.hex()}', c )) diff --git a/mmgen/addrlist.py b/mmgen/addrlist.py index 8d928293..d11e9431 100755 --- a/mmgen/addrlist.py +++ b/mmgen/addrlist.py @@ -147,7 +147,7 @@ class AddrList(MMGenObject): # Address info for a single seed ID gen_passwds = False gen_keys = False has_keys = False - chksum_rec_f = lambda foo, e: (str(e.idx), e.addr) + chksum_rec_f = lambda foo, e: (str(e.idx), e.addr.views[e.addr.view_pref]) def dmsg_sc(self,desc,data): Msg(f'sc_debug_{desc}: {data}') @@ -415,7 +415,7 @@ class KeyAddrList(AddrList): gen_desc_pl = 's' gen_keys = True has_keys = True - chksum_rec_f = lambda foo, e: (str(e.idx), e.addr, e.sec.wif) + chksum_rec_f = lambda foo, e: (str(e.idx), e.addr.views[e.addr.view_pref], e.sec.wif) class ViewKeyAddrList(KeyAddrList): desc = 'viewkey-address' diff --git a/mmgen/autosign.py b/mmgen/autosign.py index 12732ea2..e62b7b73 100755 --- a/mmgen/autosign.py +++ b/mmgen/autosign.py @@ -305,7 +305,7 @@ class Signable: for nm in non_mmgen: yield fs.format( tx.txid.fmt( width=t_wid, color=True ) if nm is non_mmgen[0] else ' '*t_wid, - nm.addr.fmt( width=a_wid, color=True ), + nm.addr.fmt(nm.addr.view_pref, width=a_wid, color=True), nm.amt.hl() + ' ' + yellow(tx.coin)) msg('\n' + '\n'.join(gen())) diff --git a/mmgen/main_addrimport.py b/mmgen/main_addrimport.py index ddb2a8d5..e664db8c 100755 --- a/mmgen/main_addrimport.py +++ b/mmgen/main_addrimport.py @@ -147,7 +147,7 @@ async def main(): mode = 'i' ) if cfg.token or cfg.token_addr: - msg(f'Importing for token {twctl.token.hl()} ({twctl.token.hlc(proto.tokensym)})') + msg(f'Importing for token {twctl.token.hl(0)} ({twctl.token.hlc(proto.tokensym)})') for k,v in addrimport_msgs.items(): addrimport_msgs[k] = fmt(v,indent=' ',strip_char='\t').rstrip() diff --git a/mmgen/proto/btc/tw/txhistory.py b/mmgen/proto/btc/tw/txhistory.py index 83281e51..10b2dd2e 100755 --- a/mmgen/proto/btc/tw/txhistory.py +++ b/mmgen/proto/btc/tw/txhistory.py @@ -145,7 +145,7 @@ class BitcoinTwTransaction: def txid_disp(self,color,width=None): return self.txid.hl(color=color) if width is None else self.txid.truncate(width=width,color=color) - def vouts_list_disp(self,src,color,indent=''): + def vouts_list_disp(self, src, color, indent, addr_view_pref): fs1,fs2 = { 'inputs': ('{i},{n} {a} {A}', '{i},{n} {a} {A} {l}'), @@ -160,7 +160,7 @@ class BitcoinTwTransaction: i = CoinTxID(e.txid).hl(color=color), n = (nocolor,red)[color](str(e.data['n']).ljust(3)), a = CoinAddr(self.proto, e.coin_addr).fmt( - width=self.max_addrlen[src], color=color), + addr_view_pref, width=self.max_addrlen[src], color=color), A = self.proto.coin_amt( e.data['value'] ).fmt(color=color) ).rstrip() else: @@ -180,7 +180,7 @@ class BitcoinTwTransaction: return f'\n{indent}'.join( gen_output() ).strip() - def vouts_disp(self,src,width,color): + def vouts_disp(self, src, width, color, addr_view_pref): def gen_output(): @@ -192,7 +192,7 @@ class BitcoinTwTransaction: if not mmid: if width and space_left < addr_w: break - yield CoinAddr(self.proto, e.coin_addr).fmt(width=addr_w, color=color) + yield CoinAddr(self.proto, e.coin_addr).fmt(addr_view_pref, width=addr_w, color=color) space_left -= addr_w elif mmid.type == 'mmgen': mmid_disp = mmid + bal_star diff --git a/mmgen/proto/btc/tx/info.py b/mmgen/proto/btc/tx/info.py index be781af5..7fa264be 100755 --- a/mmgen/proto/btc/tx/info.py +++ b/mmgen/proto/btc/tx/info.py @@ -81,11 +81,11 @@ class TxInfo(TxInfo): if terse: iwidth = max(len(str(int(e.amt))) for e in io) - addr_w = max(len(e.addr) for f in (tx.inputs,tx.outputs) for e in f) + addr_w = max(len(e.addr.views[vp1]) for f in (tx.inputs,tx.outputs) for e in f) for n,e in enumerate(io_sorted()): yield '{:3} {} {} {} {}\n'.format( n+1, - e.addr.fmt(width=addr_w, color=True), + e.addr.fmt(vp1, width=addr_w, color=True), get_mmid_fmt(e, is_input), e.amt.fmt(iwidth=iwidth,color=True), tx.dcoin ) @@ -99,9 +99,9 @@ class TxInfo(TxInfo): def gen(): if is_input: yield (n+1, 'tx,vout:', f'{e.txid.hl()},{red(str(e.vout))}') - yield ('', 'address:', f'{e.addr.hl()} {mmid_fmt}') + yield ('', 'address:', f'{e.addr.hl(vp1)} {mmid_fmt}') else: - yield (n+1, 'address:', f'{e.addr.hl()} {mmid_fmt}') + yield (n+1, 'address:', f'{e.addr.hl(vp1)} {mmid_fmt}') if e.comment: yield ('', 'comment:', e.comment.hl()) yield ('', 'amount:', f'{e.amt.hl()} {tx.dcoin}') @@ -112,6 +112,7 @@ class TxInfo(TxInfo): yield '\n'.join('{:>{w}} {:<8} {}'.format(*d,w=col1_w) for d in gen()) + '\n\n' tx = self.tx + vp1 = 0 return ( 'Displaying inputs and outputs in {} sort order'.format({'raw':'raw','addr':'address'}[sort]) diff --git a/mmgen/proto/eth/tx/info.py b/mmgen/proto/eth/tx/info.py index 43b4f209..6323f9f6 100755 --- a/mmgen/proto/eth/tx/info.py +++ b/mmgen/proto/eth/tx/info.py @@ -48,8 +48,8 @@ class TxInfo(TxInfo): td = t['data'] to_addr = t[self.to_addr_key] return fs.format( - f = t['from'].hl(), - t = to_addr.hl() if to_addr else blue('None'), + f = t['from'].hl(0), + t = to_addr.hl(0) if to_addr else blue('None'), a = t['amt'].hl(), n = t['nonce'].hl(), d = '{}... ({} bytes)'.format(td[:40],len(td)//2) if len(td) else blue('None'), @@ -82,6 +82,6 @@ class TokenTxInfo(TxInfo): def format_body(self,*args,**kwargs): return 'Token: {d} {c}\n{r}'.format( - d = self.tx.txobj['token_addr'].hl(), + d = self.tx.txobj['token_addr'].hl(0), c = blue('(' + self.tx.proto.dcoin + ')'), r = super().format_body(*args,**kwargs )) diff --git a/mmgen/proto/eth/tx/online.py b/mmgen/proto/eth/tx/online.py index 2c1dc85c..0c12a13e 100755 --- a/mmgen/proto/eth/tx/online.py +++ b/mmgen/proto/eth/tx/online.py @@ -56,7 +56,7 @@ class OnlineSigned(Signed,TxBase.OnlineSigned): def print_contract_addr(self): if 'token_addr' in self.txobj: - msg('Contract address: {}'.format(self.txobj['token_addr'].hl())) + msg('Contract address: {}'.format(self.txobj['token_addr'].hl(0))) class TokenOnlineSigned(TokenSigned,OnlineSigned): diff --git a/mmgen/tw/addresses.py b/mmgen/tw/addresses.py index 413946d5..e5337259 100755 --- a/mmgen/tw/addresses.py +++ b/mmgen/tw/addresses.py @@ -183,7 +183,7 @@ class TwAddresses(TwView): n = str(n) + ')', m = d.twmmid.fmt( width=cw.mmid, color=color ), u = yes if d.recvd else no, - a = d.addr.fmt( color=color, width=cw.addr ), + a = d.addr.fmt(self.addr_view_pref, width=cw.addr, color=color), c = d.comment.fmt2( width=cw.comment, color=color, nullrepl='-' ), A = d.amt.fmt( color=color, iwidth=cw.iwidth, prec=self.disp_prec ), d = self.age_disp( d, self.age_fmt ) @@ -194,7 +194,7 @@ class TwAddresses(TwView): n = str(n) + ')', m = d.twmmid.fmt( width=cw.mmid, color=color ), u = yes if d.recvd else no, - a = d.addr.fmt( color=color, width=cw.addr ), + a = d.addr.fmt(self.addr_view_pref, width=cw.addr, color=color), c = d.comment.fmt2( width=cw.comment, color=color, nullrepl='-' ), A = d.amt.fmt( color=color, iwidth=cw.iwidth, prec=self.disp_prec ), b = self.age_disp( d, 'block' ), diff --git a/mmgen/tw/ctl.py b/mmgen/tw/ctl.py index 54306877..abe19f5f 100755 --- a/mmgen/tw/ctl.py +++ b/mmgen/tw/ctl.py @@ -280,7 +280,8 @@ class TwCtl(MMGenObject,metaclass=AsyncInit): if not silent: desc = '{t} address {a} in tracking wallet'.format( t = res.twmmid.type.replace('mmgen','MMGen'), - a = res.twmmid.addr.hl()) + a = res.twmmid.addr.hl() if res.twmmid.type == 'mmgen' else + res.twmmid.addr.hl(res.twmmid.addr.view_pref)) msg( 'Added label {} to {}'.format(comment.hl2(encl='‘’'),desc) if comment else 'Removed label from {}'.format(desc) ) diff --git a/mmgen/tw/txhistory.py b/mmgen/tw/txhistory.py index d7d70a29..c0b6bf2f 100755 --- a/mmgen/tw/txhistory.py +++ b/mmgen/tw/txhistory.py @@ -62,8 +62,10 @@ class TwTxHistory(TwView): # var cols: inputs outputs comment [txid] if not hasattr(self,'varcol_maxwidths'): self.varcol_maxwidths = { - 'inputs': max(len(d.vouts_disp('inputs',width=None,color=False)) for d in data), - 'outputs': max(len(d.vouts_disp('outputs',width=None,color=False)) for d in data), + 'inputs': max(len(d.vouts_disp( + 'inputs', width=None, color=False, addr_view_pref=self.addr_view_pref)) for d in data), + 'outputs': max(len(d.vouts_disp( + 'outputs', width=None, color=False, addr_view_pref=self.addr_view_pref)) for d in data), 'comment': max(len(d.comment) for d in data), } @@ -123,9 +125,9 @@ class TwTxHistory(TwView): n = str(n) + ')', t = d.txid_disp( width=cw.txid, color=color ) if hasattr(cw,'txid') else None, d = d.age_disp( self.age_fmt, width=self.age_w, color=color ), - i = d.vouts_disp( 'inputs', width=cw.inputs, color=color ), + i = d.vouts_disp('inputs', width=cw.inputs, color=color, addr_view_pref=self.addr_view_pref), A = d.amt_disp(self.show_total_amt).fmt( iwidth=cw.iwidth, prec=self.disp_prec, color=color ), - o = d.vouts_disp( 'outputs', width=cw.outputs, color=color ), + o = d.vouts_disp('outputs', width=cw.outputs, color=color, addr_view_pref=self.addr_view_pref), c = d.comment.fmt2( width=cw.comment, color=color, nullrepl='-' ) ) def gen_detail_display(self,data,cw,fs,color,fmt_method): @@ -153,9 +155,9 @@ class TwTxHistory(TwView): A = d.amt_disp(show_total_amt=True).hl( color=color ), B = d.amt_disp(show_total_amt=False).hl( color=color ), f = d.fee_disp( color=color ), - i = d.vouts_list_disp( 'inputs', color=color, indent=' '*8 ), + i = d.vouts_list_disp('inputs', color=color, indent=' '*8, addr_view_pref=self.addr_view_pref), N = d.nOutputs, - o = d.vouts_list_disp( 'outputs', color=color, indent=' '*8 ), + o = d.vouts_list_disp('outputs', color=color, indent=' '*8, addr_view_pref=self.addr_view_pref), ) sort_disp = { diff --git a/mmgen/tw/unspent.py b/mmgen/tw/unspent.py index 59c51b6c..7e19c147 100755 --- a/mmgen/tw/unspent.py +++ b/mmgen/tw/unspent.py @@ -187,7 +187,7 @@ class TwUnspentOutputs(TwView): else d.txid.truncate( width=cw.txid, color=color )) if cw.txid else None, v = ' ' + d.vout.fmt( width=cw.vout-1, color=color ) if cw.vout else None, a = d.addr.fmtc( '|' + '.'*(cw.addr-1), width=cw.addr, color=color ) if d.skip == 'addr' - else d.addr.fmt( width=cw.addr, color=color ), + else d.addr.fmt(self.addr_view_pref, width=cw.addr, color=color), m = (d.twmmid.fmtc( '.'*cw.mmid, width=cw.mmid, color=color ) if d.skip == 'addr' else d.twmmid.fmt( width=cw.mmid, color=color )) if cw.mmid else None, c = d.comment.fmt2( width=cw.comment, color=color, nullrepl='-' ) if cw.comment else None, @@ -203,7 +203,7 @@ class TwUnspentOutputs(TwView): n = str(n+1) + ')', t = d.txid.fmt( width=cw.txid, color=color ) if cw.txid else None, v = ' ' + d.vout.fmt( width=cw.vout-1, color=color ) if cw.vout else None, - a = d.addr.fmt( width=cw.addr, color=color ), + a = d.addr.fmt(self.addr_view_pref, width=cw.addr, color=color), m = d.twmmid.fmt( width=cw.mmid, color=color ), A = d.amt.fmt( color=color, iwidth=cw.iwidth, prec=self.disp_prec ), B = d.amt2.fmt( color=color, iwidth=cw.iwidth2, prec=self.disp_prec ) if cw.amt2 else None, diff --git a/mmgen/tw/view.py b/mmgen/tw/view.py index 2b64653e..0e5ae946 100755 --- a/mmgen/tw/view.py +++ b/mmgen/tw/view.py @@ -96,6 +96,7 @@ class TwView(MMGenObject,metaclass=AsyncInit): term_width = 0 scrollable_height = 0 min_scrollable_height = 5 + addr_view_pref = 0 pos = 0 filters = () @@ -827,3 +828,6 @@ class TwView(MMGenObject,metaclass=AsyncInit): def d_redraw(self,parent): msg_r(CUR_HOME + ERASE_ALL) + + def d_addr_view_pref(self,parent): + parent.addr_view_pref = (parent.addr_view_pref + 1) % len(parent.bch_addr_fmts) diff --git a/mmgen/tx/new.py b/mmgen/tx/new.py index 3e2f462e..fd3415b1 100755 --- a/mmgen/tx/new.py +++ b/mmgen/tx/new.py @@ -297,7 +297,7 @@ class New(Base): self.cfg, '{a} {b} {c}\n{d}'.format( a = yellow('Requested change address'), - b = (chg.mmid or chg.addr).hl(), + b = chg.mmid.hl() if chg.mmid else chg.addr.hl(chg.addr.view_pref), c = yellow('is already used!'), d = yellow('Address reuse harms your privacy and security. Continue anyway? (y/N): ') ), diff --git a/mmgen/xmrwallet.py b/mmgen/xmrwallet.py index fd034381..5f61fc33 100755 --- a/mmgen/xmrwallet.py +++ b/mmgen/xmrwallet.py @@ -116,7 +116,7 @@ def gen_acct_addr_info(self, wallet_data, account, indent=''): continue yield fs.format( I = addr['address_index'], - A = ca.hl() if self.cfg.full_address else ca.fmt(color=True, width=addr_width), + A = ca.hl(0) if self.cfg.full_address else ca.fmt(0, color=True, width=addr_width), U = (red('True ') if addr['used'] else green('False')), B = fmt_amt(bal), L = pink(addr['label'])) @@ -277,7 +277,7 @@ class MoneroMMGenTX: f = red('{}:{}'.format(d.source.wallet,d.source.account).ljust(6)), g = red('{}:{}'.format(d.dest.wallet,d.dest.account).ljust(6)) if d.dest else cyan('ext '), h = d.amount.fmt( color=True, iwidth=4, prec=12 ), - j = d.dest_address.fmt(width=addr_w, color=True) if addr_w else d.dest_address.hl(), + j = d.dest_address.fmt(0, width=addr_w, color=True) if addr_w else d.dest_address.hl(0), x = '->' ) @@ -317,8 +317,8 @@ class MoneroMMGenTX: m = d.amount.hl(), F = (Int(d.priority).hl() + f' [{tx_priorities[d.priority]}]') if d.priority else None, n = d.fee.hl(), - o = d.dest_address.hl() if self.cfg.full_address - else d.dest_address.fmt(width=addr_width, color=True), + o = d.dest_address.hl(0) if self.cfg.full_address + else d.dest_address.fmt(0, width=addr_width, color=True), P = pink(pmt_id.hex()) if pmt_id else None, s = make_timestr(d.submit_time) if d.submit_time else None, S = pink(f" [cold signed{', submitted' if d.complete else ''}]") if d.signed_txset else '', @@ -1079,7 +1079,7 @@ class MoneroWalletOps: ca = CoinAddr(self.proto, e['base_address']) yield fs.format( I = str(e['account_index']), - A = ca.hl() if self.cfg.full_address else ca.fmt(color=True, width=addr_width), + A = ca.hl(0) if self.cfg.full_address else ca.fmt(0, color=True, width=addr_width), N = red(str(len(addrs_data[i]['addresses'])).ljust(6)), B = fmt_amt(e['unlocked_balance']), L = pink(e['label'])) @@ -1831,7 +1831,7 @@ class MoneroWalletOps: ca = CoinAddr(self.proto, addr['address']) msg('\n {a} {b}\n {c} {d}\n {e} {f}'.format( a = 'Address: ', - b = ca.hl() if self.cfg.full_address else ca.fmt(color=True, width=addr_width), + b = ca.hl(0) if self.cfg.full_address else ca.fmt(0, color=True, width=addr_width), c = 'Existing label:', d = pink(addr['label']) if addr['label'] else gray('[none]'), e = 'New label: ', diff --git a/test/gentest.py b/test/gentest.py index 36f98897..e3cd7e6f 100755 --- a/test/gentest.py +++ b/test/gentest.py @@ -294,6 +294,7 @@ def do_ab_test(proto,scfg,addr_type,gen1,kg2,ag,tool,cache_data): sec = PrivKey(proto,in_bytes,compressed=addr_type.compressed,pubkey_type=addr_type.pubkey_type) data = kg1.gen_data(sec) addr1 = ag.to_addr(data) + view_pref = 0 tinfo = ( in_bytes, sec, sec.wif, type(kg1).__name__, type(kg2).__name__ if kg2 else tool.desc ) def do_msg(): @@ -304,14 +305,14 @@ def do_ab_test(proto,scfg,addr_type,gen1,kg2,ag,tool,cache_data): def run_tool(): o = tool.run_tool(sec,cache_data) test_equal( 'WIF keys', sec.wif, o.wif, *tinfo ) - test_equal( 'addresses', addr1, o.addr, *tinfo ) + test_equal('addresses', addr1.views[view_pref], o.addr, *tinfo) if o.viewkey: test_equal( 'view keys', ag.to_viewkey(data), o.viewkey, *tinfo ) return o.viewkey vk2 = run_tool() do_msg() else: - test_equal( 'addresses', addr1, ag.to_addr(kg2.gen_data(sec)), *tinfo ) + test_equal('addresses', addr1.views[view_pref], ag.to_addr(kg2.gen_data(sec)), *tinfo) vk2 = None do_msg()