From b5600fc56310537fd6edc22a7f70aa3f24ea9d46 Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Fri, 18 Oct 2024 10:32:08 +0000 Subject: [PATCH] whitespace: top-level modules --- mmgen/addr.py | 101 ++++++++--------- mmgen/addrdata.py | 50 ++++----- mmgen/addrfile.py | 118 ++++++++++---------- mmgen/addrgen.py | 18 +-- mmgen/addrlist.py | 162 +++++++++++++-------------- mmgen/autosign.py | 160 +++++++++++++-------------- mmgen/base_obj.py | 50 ++++----- mmgen/baseconv.py | 95 ++++++++-------- mmgen/bip39.py | 54 ++++----- mmgen/cfg.py | 261 ++++++++++++++++++++++---------------------- mmgen/cfgfile.py | 110 +++++++++---------- mmgen/color.py | 64 +++++------ mmgen/crypto.py | 162 +++++++++++++-------------- mmgen/daemon.py | 202 +++++++++++++++++----------------- mmgen/derive.py | 12 +- mmgen/devinit.py | 54 ++++----- mmgen/devtools.py | 132 +++++++++++----------- mmgen/exception.py | 2 +- mmgen/filename.py | 56 +++++----- mmgen/fileutil.py | 112 +++++++++---------- mmgen/flags.py | 38 +++---- mmgen/key.py | 72 ++++++------ mmgen/keygen.py | 46 ++++---- mmgen/led.py | 58 +++++----- mmgen/main.py | 16 +-- mmgen/mn_entry.py | 110 +++++++++---------- mmgen/msg.py | 152 +++++++++++++------------- mmgen/obj.py | 172 ++++++++++++++--------------- mmgen/objmethods.py | 72 ++++++------ mmgen/opts.py | 10 +- mmgen/passwdlist.py | 102 ++++++++--------- mmgen/protocol.py | 128 +++++++++++----------- mmgen/pyversion.py | 22 ++-- mmgen/rpc.py | 212 +++++++++++++++++------------------ mmgen/seed.py | 52 ++++----- mmgen/seedsplit.py | 145 ++++++++++++------------ mmgen/sha2.py | 68 ++++++------ mmgen/subseed.py | 94 ++++++++-------- mmgen/term.py | 74 ++++++------- mmgen/ui.py | 36 +++--- mmgen/util.py | 202 +++++++++++++++++----------------- mmgen/util2.py | 50 ++++----- mmgen/xmrseed.py | 32 +++--- 43 files changed, 1959 insertions(+), 1979 deletions(-) diff --git a/mmgen/addr.py b/mmgen/addr.py index e7ddad5c..fa83959f 100755 --- a/mmgen/addr.py +++ b/mmgen/addr.py @@ -22,29 +22,29 @@ addr: MMGen address-related types from collections import namedtuple -from .objmethods import HiliteStr,InitErrors,MMGenObject -from .obj import ImmutableAttr,MMGenIdx,get_obj +from .objmethods import HiliteStr, InitErrors, MMGenObject +from .obj import ImmutableAttr, MMGenIdx, get_obj from .seed import SeedID from . import color as color_mod ati = namedtuple('addrtype_info', - ['name','pubkey_type','compressed','gen_method','addr_fmt','wif_label','extra_attrs','desc']) + ['name', 'pubkey_type', 'compressed', 'gen_method', 'addr_fmt', 'wif_label', 'extra_attrs', 'desc']) -class MMGenAddrType(HiliteStr,InitErrors,MMGenObject): +class MMGenAddrType(HiliteStr, InitErrors, MMGenObject): width = 1 trunc_ok = False color = 'blue' name = ImmutableAttr(str) pubkey_type = ImmutableAttr(str) - compressed = ImmutableAttr(bool,set_none_ok=True) - gen_method = ImmutableAttr(str,set_none_ok=True) - addr_fmt = ImmutableAttr(str,set_none_ok=True) - wif_label = ImmutableAttr(str,set_none_ok=True) - extra_attrs = ImmutableAttr(tuple,set_none_ok=True) + compressed = ImmutableAttr(bool, set_none_ok=True) + gen_method = ImmutableAttr(str, set_none_ok=True) + addr_fmt = ImmutableAttr(str, set_none_ok=True) + wif_label = ImmutableAttr(str, set_none_ok=True) + extra_attrs = ImmutableAttr(tuple, set_none_ok=True) desc = ImmutableAttr(str) - pkh_fmts = ('p2pkh','bech32','ethereum') + pkh_fmts = ('p2pkh', 'bech32', 'ethereum') mmtypes = { 'L': ati('legacy', 'std', False,'p2pkh', 'p2pkh', 'wif', (), 'Legacy uncompressed address'), 'C': ati('compressed','std', True, 'p2pkh', 'p2pkh', 'wif', (), 'Compressed P2PKH address'), @@ -54,34 +54,35 @@ class MMGenAddrType(HiliteStr,InitErrors,MMGenObject): 'Z': ati('zcash_z','zcash_z',False,'zcash_z', 'zcash_z', 'wif', ('viewkey',), 'Zcash z-address'), 'M': ati('monero', 'monero', False,'monero', 'monero', 'spendkey',('viewkey','wallet_passwd'),'Monero address'), } - def __new__(cls,proto,id_str,errmsg=None): - if isinstance(id_str,cls): + def __new__(cls, proto, id_str, errmsg=None): + if isinstance(id_str, cls): return id_str try: - id_str = id_str.replace('-','_') - for k,v in cls.mmtypes.items(): - if id_str in (k,v.name): + id_str = id_str.replace('-', '_') + for k, v in cls.mmtypes.items(): + if id_str in (k, v.name): if id_str == v.name: id_str = k - me = str.__new__(cls,id_str) + me = str.__new__(cls, id_str) for k in v._fields: - setattr(me,k,getattr(v,k)) + setattr(me, k, getattr(v, k)) if me not in proto.mmtypes + ('P',): raise ValueError(f'{me.name!r}: invalid address type for {proto.name} protocol') me.proto = proto return me raise ValueError(f'{id_str}: unrecognized address type for protocol {proto.name}') except Exception as e: - return cls.init_fail( e, + return cls.init_fail( + e, f"{errmsg or ''}{id_str!r}: invalid value for {cls.__name__} ({e!s})", - preformat = True ) + preformat = True) @classmethod def get_names(cls): return [v.name for v in cls.mmtypes.values()] -def is_mmgen_addrtype(proto,id_str): - return get_obj( MMGenAddrType, proto=proto, id_str=id_str, silent=True, return_bool=True ) +def is_mmgen_addrtype(proto, id_str): + return get_obj(MMGenAddrType, proto=proto, id_str=id_str, silent=True, return_bool=True) class MMGenPasswordType(MMGenAddrType): mmtypes = { @@ -92,58 +93,58 @@ class AddrIdx(MMGenIdx): max_digits = 7 def is_addr_idx(s): - return get_obj( AddrIdx, n=s, silent=True, return_bool=True ) + return get_obj(AddrIdx, n=s, silent=True, return_bool=True) -class AddrListID(HiliteStr,InitErrors,MMGenObject): +class AddrListID(HiliteStr, InitErrors, MMGenObject): width = 10 trunc_ok = False color = 'yellow' - def __new__(cls,sid=None,mmtype=None,proto=None,id_str=None): + def __new__(cls, sid=None, mmtype=None, proto=None, id_str=None): try: if id_str: - a,b = id_str.split(':') + a, b = id_str.split(':') sid = SeedID(sid=a) try: - mmtype = MMGenAddrType( proto=proto, id_str=b ) + mmtype = MMGenAddrType(proto=proto, id_str=b) except: - mmtype = MMGenPasswordType( proto=proto, id_str=b ) + mmtype = MMGenPasswordType(proto=proto, id_str=b) else: - assert isinstance(sid,SeedID), f'{sid!r} not a SeedID instance' - if not isinstance(mmtype,(MMGenAddrType,MMGenPasswordType)): + assert isinstance(sid, SeedID), f'{sid!r} not a SeedID instance' + if not isinstance(mmtype, (MMGenAddrType, MMGenPasswordType)): raise ValueError(f'{mmtype!r}: not an instance of MMGenAddrType or MMGenPasswordType') - me = str.__new__(cls,sid+':'+mmtype) + me = str.__new__(cls, sid+':'+mmtype) me.sid = sid me.mmtype = mmtype return me except Exception as e: return cls.init_fail(e, f'sid={sid}, mmtype={mmtype}') -def is_addrlist_id(proto,s): - return get_obj( AddrListID, proto=proto, id_str=s, silent=False, return_bool=True ) +def is_addrlist_id(proto, s): + return get_obj(AddrListID, proto=proto, id_str=s, silent=False, return_bool=True) -class MMGenID(HiliteStr,InitErrors,MMGenObject): +class MMGenID(HiliteStr, InitErrors, MMGenObject): color = 'orange' width = 0 trunc_ok = False - def __new__(cls,proto,id_str): + def __new__(cls, proto, id_str): try: ss = str(id_str).split(':') - assert len(ss) in (2,3),'not 2 or 3 colon-separated items' - t = proto.addr_type((ss[1],proto.dfl_mmtype)[len(ss)==2]) - me = str.__new__(cls,f'{ss[0]}:{t}:{ss[-1]}') + assert len(ss) in (2, 3), 'not 2 or 3 colon-separated items' + t = proto.addr_type((ss[1], proto.dfl_mmtype)[len(ss)==2]) + me = str.__new__(cls, f'{ss[0]}:{t}:{ss[-1]}') me.sid = SeedID(sid=ss[0]) me.idx = AddrIdx(ss[-1]) me.mmtype = t assert t in proto.mmtypes, f'{t}: invalid address type for {proto.cls_name}' - me.al_id = str.__new__(AddrListID,me.sid+':'+me.mmtype) # checks already done + me.al_id = str.__new__(AddrListID, me.sid+':'+me.mmtype) # checks already done me.sort_key = f'{me.sid}:{me.mmtype}:{me.idx:0{me.idx.max_digits}}' me.proto = proto return me except Exception as e: - return cls.init_fail(e,id_str) + return cls.init_fail(e, id_str) -def is_mmgen_id(proto,s): - return get_obj( MMGenID, proto=proto, id_str=s, silent=True, return_bool=True ) +def is_mmgen_id(proto, s): + return get_obj(MMGenID, proto=proto, id_str=s, silent=True, return_bool=True) class CoinAddr(HiliteStr, InitErrors, MMGenObject): color = 'cyan' @@ -152,7 +153,7 @@ class CoinAddr(HiliteStr, InitErrors, MMGenObject): trunc_ok = False def __new__(cls, proto, addr): - if isinstance(addr,cls): + if isinstance(addr, cls): return addr try: ap = proto.decode_addr(addr) @@ -171,18 +172,18 @@ class CoinAddr(HiliteStr, InitErrors, MMGenObject): me.proto = proto return me except Exception as e: - return cls.init_fail(e,addr,objname=f'{proto.cls_name} address') + return cls.init_fail(e, addr, objname=f'{proto.cls_name} address') @property def parsed(self): - if not hasattr(self,'_parsed'): - self._parsed = self.proto.parse_addr(self.ver_bytes,self.bytes,self.addr_fmt) + if not hasattr(self, '_parsed'): + self._parsed = self.proto.parse_addr(self.ver_bytes, self.bytes, self.addr_fmt) return self._parsed # reimplement some HiliteStr methods: @classmethod - 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 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, view_pref, width, color=False): s = self.views[view_pref] @@ -191,11 +192,11 @@ class CoinAddr(HiliteStr, InitErrors, MMGenObject): 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 ) +def is_coin_addr(proto, s): + return get_obj(CoinAddr, proto=proto, addr=s, silent=True, return_bool=True) class TokenAddr(CoinAddr): color = 'blue' -def ViewKey(proto,viewkey_str): +def ViewKey(proto, viewkey_str): return proto.viewkey(viewkey_str) diff --git a/mmgen/addrdata.py b/mmgen/addrdata.py index fa4e4cc2..0577ab67 100755 --- a/mmgen/addrdata.py +++ b/mmgen/addrdata.py @@ -21,15 +21,15 @@ addrdata: MMGen AddrData and related classes """ from .cfg import gc -from .util import fmt,die +from .util import fmt, die from .base_obj import AsyncInit -from .obj import MMGenObject,MMGenDict,get_obj -from .addr import MMGenID,AddrListID -from .addrlist import AddrListEntry,AddrListData,AddrList +from .obj import MMGenObject, MMGenDict, get_obj +from .addr import MMGenID, AddrListID +from .addrlist import AddrListEntry, AddrListData, AddrList class AddrData(MMGenObject): - def __init__(self,proto,*args,**kwargs): + def __init__(self, proto, *args, **kwargs): self.al_ids = {} self.proto = proto self.rpc = None @@ -37,63 +37,63 @@ class AddrData(MMGenObject): def seed_ids(self): return list(self.al_ids.keys()) - def addrlist(self,al_id): + def addrlist(self, al_id): # TODO: Validate al_id if al_id in self.al_ids: return self.al_ids[al_id] - def mmaddr2coinaddr(self,mmaddr): - al_id,idx = MMGenID(self.proto,mmaddr).rsplit(':',1) + def mmaddr2coinaddr(self, mmaddr): + al_id, idx = MMGenID(self.proto, mmaddr).rsplit(':', 1) coinaddr = '' if al_id in self.al_ids: coinaddr = self.addrlist(al_id).coinaddr(int(idx)) return coinaddr or None - def coinaddr2mmaddr(self,coinaddr): + def coinaddr2mmaddr(self, coinaddr): d = self.make_reverse_dict([coinaddr]) return (list(d.values())[0][0]) if d else None - def add(self,addrlist): - if isinstance(addrlist,AddrList): + def add(self, addrlist): + if isinstance(addrlist, AddrList): self.al_ids[addrlist.al_id] = addrlist return True else: raise TypeError(f'Error: object {addrlist!r} is not an instance of AddrList') - def make_reverse_dict(self,coinaddrs): + def make_reverse_dict(self, coinaddrs): d = MMGenDict() for al_id in self.al_ids: d.update(self.al_ids[al_id].make_reverse_dict_addrlist(coinaddrs)) return d -class TwAddrData(AddrData,metaclass=AsyncInit): +class TwAddrData(AddrData, metaclass=AsyncInit): - def __new__(cls,cfg,proto,*args,**kwargs): - return MMGenObject.__new__(proto.base_proto_subclass(cls,'addrdata')) + def __new__(cls, cfg, proto, *args, **kwargs): + return MMGenObject.__new__(proto.base_proto_subclass(cls, 'addrdata')) - async def __init__(self,cfg,proto,twctl=None): + async def __init__(self, cfg, proto, twctl=None): from .rpc import rpc_init from .tw.shared import TwLabel from .seed import SeedID self.cfg = cfg self.proto = proto - self.rpc = await rpc_init(cfg,proto) + self.rpc = await rpc_init(cfg, proto) self.al_ids = {} twd = await self.get_tw_data(twctl) - out,i = {},0 - for acct,addr_array in twd: - l = get_obj(TwLabel,proto=self.proto,text=acct,silent=True) + out, i = {}, 0 + for acct, addr_array in twd: + l = get_obj(TwLabel, proto=self.proto, text=acct, silent=True) if l and l.mmid.type == 'mmgen': obj = l.mmid.obj if len(addr_array) != 1: - message = self.msgs['multiple_acct_addrs'].strip().format( acct=acct, proj=gc.proj_name ) - die(3, fmt( message, indent=' ' )) + message = self.msgs['multiple_acct_addrs'].strip().format(acct=acct, proj=gc.proj_name) + die(3, fmt(message, indent=' ')) al_id = AddrListID( sid = SeedID(sid=obj.sid), - mmtype = self.proto.addr_type(obj.mmtype) ) + mmtype = self.proto.addr_type(obj.mmtype)) if al_id not in out: out[al_id] = [] - out[al_id].append(AddrListEntry(self.proto,idx=obj.idx,addr=addr_array[0],comment=l.comment)) + out[al_id].append(AddrListEntry(self.proto, idx=obj.idx, addr=addr_array[0], comment=l.comment)) i += 1 self.cfg._util.vmsg(f'{i} {gc.proj_name} addresses found, {len(twd)} accounts total') @@ -103,5 +103,5 @@ class TwAddrData(AddrData,metaclass=AsyncInit): self.cfg, self.proto, al_id = al_id, - adata = AddrListData(sorted( out[al_id], key=lambda a: a.idx )) + adata = AddrListData(sorted(out[al_id], key=lambda a: a.idx)) )) diff --git a/mmgen/addrfile.py b/mmgen/addrfile.py index 61ecefff..c5fcdda6 100755 --- a/mmgen/addrfile.py +++ b/mmgen/addrfile.py @@ -21,13 +21,13 @@ addrfile: Address and password file classes for the MMGen suite """ from .cfg import gc -from .util import msg,die,capfirst +from .util import msg, die, capfirst from .protocol import init_proto -from .obj import MMGenObject,TwComment,WalletPassword,MMGenPWIDString -from .seed import SeedID,is_seed_id +from .obj import MMGenObject, TwComment, WalletPassword, MMGenPWIDString +from .seed import SeedID, is_seed_id from .key import PrivKey -from .addr import ViewKey,AddrListID,MMGenAddrType,MMGenPasswordType,is_addr_idx -from .addrlist import KeyList,AddrListData +from .addr import ViewKey, AddrListID, MMGenAddrType, MMGenPasswordType, is_addr_idx +from .addrlist import KeyList, AddrListData class AddrFile(MMGenObject): desc = 'addresses' @@ -43,7 +43,7 @@ class AddrFile(MMGenObject): # The label may contain any printable ASCII symbol. """ - def __init__(self,parent): + def __init__(self, parent): self.parent = parent self.cfg = parent.cfg self.infile = None @@ -53,7 +53,7 @@ class AddrFile(MMGenObject): from .crypto import Crypto self.fmt_data = Crypto(self.cfg).mmgen_encrypt( data = self.fmt_data.encode(), - desc = f'new {self.parent.desc} list' ) + desc = f'new {self.parent.desc} list') self.ext += f'.{Crypto.mmenc_ext}' @property @@ -61,7 +61,7 @@ class AddrFile(MMGenObject): return '{}{}.{}'.format( self.parent.id_str, ('.' + self.parent.proto.network) if self.parent.proto.testnet else '', - self.ext ) + self.ext) def write( self, @@ -92,9 +92,9 @@ class AddrFile(MMGenObject): ) return self.parent.al_id.sid + (' ' if lbl_p2 else '') + lbl_p2 - def format(self,add_comments=False): + def format(self, add_comments=False): p = self.parent - if p.gen_passwds and p.pw_fmt in ('bip39','xmrseed'): + if p.gen_passwds and p.pw_fmt in ('bip39', 'xmrseed'): desc_pfx = f'{p.pw_fmt.upper()} ' hdr2 = '' else: @@ -102,9 +102,9 @@ class AddrFile(MMGenObject): hdr2 = self.text_label_header out = [ f'# {gc.proj_name} {desc_pfx}{p.desc} file\n#\n' - + self.header.strip().format( pnm=gc.proj_name ) + + self.header.strip().format(pnm=gc.proj_name) + '\n' - + hdr2.lstrip().format( n=TwComment.max_screen_width ) + + hdr2.lstrip().format(n=TwComment.max_screen_width) + '#\n' ] @@ -113,41 +113,41 @@ class AddrFile(MMGenObject): out.append('# Record this value to a secure location.\n') lbl = self.make_label() - self.parent.dmsg_sc('lbl',lbl[9:]) + self.parent.dmsg_sc('lbl', lbl[9:]) out.append(f'{lbl} {{') fs = ' {:<%s} {:<34}{}' % len(str(p.data[-1].idx)) for e in p.data: c = ' ' + e.comment if add_comments and e.comment else '' if type(p) is KeyList: - out.append(fs.format( e.idx, f'{p.al_id.mmtype.wif_label}: {e.sec.wif}', c )) + out.append(fs.format(e.idx, f'{p.al_id.mmtype.wif_label}: {e.sec.wif}', c)) elif type(p).__name__ == 'PasswordList': - out.append(fs.format(e.idx,e.passwd,c)) + out.append(fs.format(e.idx, e.passwd, c)) else: # First line with idx 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 )) + out.append(fs.format('', f'orig_hex: {e.sec.orig_bytes.hex()}', c)) if type(self) is not ViewKeyAddrFile: - out.append(fs.format( '', f'{p.al_id.mmtype.wif_label}: {e.sec.wif}', c )) - for k in ('viewkey','wallet_passwd'): - v = getattr(e,k) + out.append(fs.format('', f'{p.al_id.mmtype.wif_label}: {e.sec.wif}', c)) + for k in ('viewkey', 'wallet_passwd'): + v = getattr(e, k) if v: - out.append(fs.format( '', f'{k}: {v}', c )) + out.append(fs.format('', f'{k}: {v}', c)) out.append('}') self.fmt_data = '\n'.join([l.rstrip() for l in out]) + '\n' return self.fmt_data - def get_line(self,lines): - ret = lines.pop(0).split(None,2) + def get_line(self, lines): + ret = lines.pop(0).split(None, 2) self.line_ctr += 1 if ret[0] == 'orig_hex:': # hacky - ret = lines.pop(0).split(None,2) + ret = lines.pop(0).split(None, 2) self.line_ctr += 1 return ret if len(ret) == 3 else ret + [''] - def parse_file_body(self,lines): + def parse_file_body(self, lines): p = self.parent ret = AddrListData() @@ -155,39 +155,39 @@ class AddrFile(MMGenObject): iifs = "{!r}: invalid identifier [expected '{}:']" while lines: - idx,addr,comment = self.get_line(lines) + idx, addr, comment = self.get_line(lines) assert is_addr_idx(idx), f'invalid address index {idx!r}' p.check_format(addr) - a = le(**{ 'proto': p.proto, 'idx':int(idx), p.main_attr:addr, 'comment':comment }) + a = le(**{'proto': p.proto, 'idx':int(idx), p.main_attr:addr, 'comment':comment}) - if p.has_keys: # order: wif,(orig_hex),viewkey,wallet_passwd + if p.has_keys: # order: wif, (orig_hex), viewkey, wallet_passwd if type(self) is not ViewKeyAddrFile: d = self.get_line(lines) - assert d[0] == p.al_id.mmtype.wif_label+':', iifs.format(d[0],p.al_id.mmtype.wif_label) - a.sec = PrivKey(proto=p.proto,wif=d[1]) - for k,dtype,add_proto in ( - ('viewkey',ViewKey,True), - ('wallet_passwd',WalletPassword,False) ): + assert d[0] == p.al_id.mmtype.wif_label+':', iifs.format(d[0], p.al_id.mmtype.wif_label) + a.sec = PrivKey(proto=p.proto, wif=d[1]) + for k, dtype, add_proto in ( + ('viewkey', ViewKey, True), + ('wallet_passwd', WalletPassword, False)): if k in p.al_id.mmtype.extra_attrs: d = self.get_line(lines) - assert d[0] == k+':', iifs.format(d[0],k) - setattr(a,k,dtype( *((p.proto,d[1]) if add_proto else (d[1],)) ) ) + assert d[0] == k+':', iifs.format(d[0], k) + setattr(a, k, dtype(*((p.proto, d[1]) if add_proto else (d[1],)))) ret.append(a) if type(self) is not ViewKeyAddrFile and p.has_keys and p.ka_validity_chk is not False: def verify_keys(): - from .addrgen import KeyGenerator,AddrGenerator - kg = KeyGenerator( self.cfg, p.proto, p.al_id.mmtype.pubkey_type ) - ag = AddrGenerator( self.cfg, p.proto, p.al_id.mmtype ) + from .addrgen import KeyGenerator, AddrGenerator + kg = KeyGenerator(self.cfg, p.proto, p.al_id.mmtype.pubkey_type) + ag = AddrGenerator(self.cfg, p.proto, p.al_id.mmtype) llen = len(ret) qmsg_r = p.cfg._util.qmsg_r - for n,e in enumerate(ret): + for n, e in enumerate(ret): qmsg_r(f'\rVerifying keys {n+1}/{llen}') - assert e.addr == ag.to_addr(kg.gen_data(e.sec)),( + assert e.addr == ag.to_addr(kg.gen_data(e.sec)), ( f'Key doesn’t match address!\n {e.sec.wif}\n {e.addr}') p.cfg._util.qmsg(' - done') @@ -195,12 +195,12 @@ class AddrFile(MMGenObject): verify_keys() else: from .ui import keypress_confirm - if keypress_confirm( p.cfg, 'Check key-to-address validity?' ): + if keypress_confirm(p.cfg, 'Check key-to-address validity?'): verify_keys() return ret - def parse_file(self,fn,buf=[],exit_on_error=True): + def parse_file(self, fn, buf=[], exit_on_error=True): def parse_addrfile_label(lbl): """ @@ -232,7 +232,7 @@ class AddrFile(MMGenObject): network = 'mainnet' from .proto.btc.params import mainnet - if lbl in [MMGenAddrType(mainnet,key).name for key in mainnet.mmtypes]: + if lbl in [MMGenAddrType(mainnet, key).name for key in mainnet.mmtypes]: coin, mmtype_key = ('BTC', lbl) elif ':' in lbl: # first component is coin, second is mmtype_key coin, mmtype_key = lbl.split(':') @@ -249,7 +249,7 @@ class AddrFile(MMGenObject): p = self.parent from .fileutil import get_lines_from_file - lines = get_lines_from_file( p.cfg, fn, p.desc+' data', trim_comments=True ) + lines = get_lines_from_file(p.cfg, fn, p.desc+' data', trim_comments=True) try: assert len(lines) >= 3, f'Too few lines in address file ({len(lines)})' @@ -267,15 +267,15 @@ class AddrFile(MMGenObject): p.set_pw_fmt(ss[0]) p.set_pw_len(ss[1]) p.pw_id_str = MMGenPWIDString(ls.pop()) - modname,funcname = p.pw_info[p.pw_fmt].chk_func.split('.') + modname, funcname = p.pw_info[p.pw_fmt].chk_func.split('.') import importlib - p.chk_func = getattr(importlib.import_module('mmgen.'+modname),funcname) - proto = init_proto( p.cfg, 'btc' ) # FIXME: dummy protocol - mmtype = MMGenPasswordType(proto,'P') + p.chk_func = getattr(importlib.import_module('mmgen.'+modname), funcname) + proto = init_proto(p.cfg, 'btc') # FIXME: dummy protocol + mmtype = MMGenPasswordType(proto, 'P') elif len(ls) == 1: - proto,mmtype = parse_addrfile_label(ls[0]) + proto, mmtype = parse_addrfile_label(ls[0]) elif len(ls) == 0: - proto = init_proto( p.cfg, 'btc' ) + proto = init_proto(p.cfg, 'btc') mmtype = proto.addr_type('L') else: raise ValueError(f'{lines[0]}: Invalid first line for {p.gen_desc} file {fn!r}') @@ -289,22 +289,22 @@ class AddrFile(MMGenObject): raise ValueError( f'{p.desc} file is ' + f'{proto.base_coin} {proto.network} but protocol is ' - + f'{p.proto.base_coin} {p.proto.network}' ) + + f'{p.proto.base_coin} {p.proto.network}') p.base_coin = proto.base_coin p.network = proto.network - p.al_id = AddrListID( sid=SeedID(sid=sid), mmtype=mmtype ) + p.al_id = AddrListID(sid=SeedID(sid=sid), mmtype=mmtype) data = self.parse_file_body(lines[1:-1]) - assert isinstance(data,list),'Invalid file body data' + assert isinstance(data, list), 'Invalid file body data' except Exception as e: m = 'Invalid data in {} list file {!r}{} ({!s})'.format( p.desc, self.infile, (f', content line {self.line_ctr}' if self.line_ctr else ''), - e ) + e) if exit_on_error: - die(3,m) + die(3, m) else: msg(m) return False @@ -339,13 +339,13 @@ class PasswordFile(AddrFile): # password. The label may contain any printable ASCII symbol. """ - def get_line(self,lines): + def get_line(self, lines): self.line_ctr += 1 p = self.parent - if p.pw_fmt in ('bip39','xmrseed'): - ret = lines.pop(0).split(None,p.pw_len+1) + if p.pw_fmt in ('bip39', 'xmrseed'): + ret = lines.pop(0).split(None, p.pw_len+1) if len(ret) > p.pw_len+1: m1 = f'extraneous text {ret[p.pw_len+1]!r} found after password' m2 = '[bare comments not allowed in BIP39 password files]' @@ -353,10 +353,10 @@ class PasswordFile(AddrFile): elif len(ret) < p.pw_len+1: m = f'invalid password length {len(ret)-1}' else: - return (ret[0],' '.join(ret[1:p.pw_len+1]),'') + return (ret[0], ' '.join(ret[1:p.pw_len+1]), '') raise ValueError(m) else: - ret = lines.pop(0).split(None,2) + ret = lines.pop(0).split(None, 2) return ret if len(ret) == 3 else ret + [''] def make_label(self): diff --git a/mmgen/addrgen.py b/mmgen/addrgen.py index d44b129c..87014877 100755 --- a/mmgen/addrgen.py +++ b/mmgen/addrgen.py @@ -24,19 +24,19 @@ from .keygen import KeyGenerator # convenience import # decorator for to_addr() and to_viewkey() def check_data(orig_func): - def f(self,data): + def f(self, data): assert data.pubkey_type == self.pubkey_type, 'addrgen.py:check_data() pubkey_type mismatch' - assert data.compressed == self.compressed,( + assert data.compressed == self.compressed, ( f'addrgen.py:check_data() expected compressed={self.compressed} but got compressed={data.compressed}' ) - return orig_func(self,data) + return orig_func(self, data) return f class addr_generator: class base: - def __init__(self,cfg,proto,addr_type): + def __init__(self, cfg, proto, addr_type): self.proto = proto self.pubkey_type = addr_type.pubkey_type self.compressed = addr_type.compressed @@ -44,12 +44,12 @@ class addr_generator: class keccak(base): - def __init__(self,cfg,proto,addr_type): - super().__init__(cfg,proto,addr_type) + def __init__(self, cfg, proto, addr_type): + super().__init__(cfg, proto, addr_type) from .util2 import get_keccak self.keccak_256 = get_keccak(cfg) -def AddrGenerator(cfg,proto,addr_type): +def AddrGenerator(cfg, proto, addr_type): """ factory function returning an address generator for the specified address type """ @@ -67,7 +67,7 @@ def AddrGenerator(cfg,proto,addr_type): from .addr import MMGenAddrType if type(addr_type) is str: - addr_type = MMGenAddrType(proto=proto,id_str=addr_type) + addr_type = MMGenAddrType(proto=proto, id_str=addr_type) elif type(addr_type) is MMGenAddrType: assert addr_type in proto.mmtypes, f'{addr_type}: invalid address type for coin {proto.coin}' else: @@ -76,4 +76,4 @@ def AddrGenerator(cfg,proto,addr_type): import importlib return getattr( importlib.import_module(f'mmgen.proto.{package_map[addr_type.name]}.addrgen'), - addr_type.name )(cfg,proto,addr_type) + addr_type.name)(cfg, proto, addr_type) diff --git a/mmgen/addrlist.py b/mmgen/addrlist.py index 0d2225a5..0528bb54 100755 --- a/mmgen/addrlist.py +++ b/mmgen/addrlist.py @@ -20,17 +20,17 @@ addrlist: Address list classes for the MMGen suite """ -from .util import suf,make_chksum_N,Msg,die -from .objmethods import MMGenObject,HiliteStr,InitErrors -from .obj import MMGenListItem,ListItemAttr,MMGenDict,TwComment,WalletPassword +from .util import suf, make_chksum_N, Msg, die +from .objmethods import MMGenObject, HiliteStr, InitErrors +from .obj import MMGenListItem, ListItemAttr, MMGenDict, TwComment, WalletPassword from .key import PrivKey -from .addr import MMGenID,MMGenAddrType,CoinAddr,AddrIdx,AddrListID,ViewKey +from .addr import MMGenID, MMGenAddrType, CoinAddr, AddrIdx, AddrListID, ViewKey -class AddrIdxList(tuple,InitErrors,MMGenObject): +class AddrIdxList(tuple, InitErrors, MMGenObject): max_len = 1000000 - def __new__(cls,fmt_str=None,idx_list=None,sep=','): + def __new__(cls, fmt_str=None, idx_list=None, sep=','): try: if fmt_str: def gen(): @@ -45,9 +45,9 @@ class AddrIdxList(tuple,InitErrors,MMGenObject): else: raise ValueError(f'{i}: invalid range') idx_list = tuple(gen()) - return tuple.__new__(cls,sorted({AddrIdx(i) for i in (idx_list or [])})) + return tuple.__new__(cls, sorted({AddrIdx(i) for i in (idx_list or [])})) except Exception as e: - return cls.init_fail(e,idx_list or fmt_str) + return cls.init_fail(e, idx_list or fmt_str) @property def id_str(self): @@ -74,36 +74,36 @@ class AddrIdxList(tuple,InitErrors,MMGenObject): class AddrListEntryBase(MMGenListItem): invalid_attrs = {'proto'} - def __init__(self,proto,**kwargs): + def __init__(self, proto, **kwargs): self.__dict__['proto'] = proto - MMGenListItem.__init__(self,**kwargs) + MMGenListItem.__init__(self, **kwargs) class AddrListEntry(AddrListEntryBase): - addr = ListItemAttr(CoinAddr,include_proto=True) - addr_p2pkh = ListItemAttr(CoinAddr,include_proto=True) + addr = ListItemAttr(CoinAddr, include_proto=True) + addr_p2pkh = ListItemAttr(CoinAddr, include_proto=True) idx = ListItemAttr(AddrIdx) # not present in flat addrlists - comment = ListItemAttr(TwComment,reassign_ok=True) - sec = ListItemAttr(PrivKey,include_proto=True) - viewkey = ListItemAttr(ViewKey,include_proto=True) + comment = ListItemAttr(TwComment, reassign_ok=True) + sec = ListItemAttr(PrivKey, include_proto=True) + viewkey = ListItemAttr(ViewKey, include_proto=True) wallet_passwd = ListItemAttr(WalletPassword) class AddrListChksum(HiliteStr): color = 'pink' trunc_ok = False - def __new__(cls,addrlist): + def __new__(cls, addrlist): ea = addrlist.al_id.mmtype.extra_attrs or () # add viewkey and passwd to the mix, if present lines = [' '.join( addrlist.chksum_rec_f(e) + - tuple(getattr(e,a) for a in ea if getattr(e,a)) + tuple(getattr(e, a) for a in ea if getattr(e, a)) ) for e in addrlist.data] - return str.__new__(cls,make_chksum_N(' '.join(lines), nchars=16, sep=True)) + return str.__new__(cls, make_chksum_N(' '.join(lines), nchars=16, sep=True)) class AddrListIDStr(HiliteStr): color = 'green' trunc_ok = False - def __new__(cls,addrlist,fmt_str=None): + def __new__(cls, addrlist, fmt_str=None): idxs = [e.idx for e in addrlist.data] prev = idxs[0] ret = [prev] @@ -116,7 +116,7 @@ class AddrListIDStr(HiliteStr): ret.extend(['-', prev]) ret.extend([',', i]) prev = i - s = ''.join(map(str,ret)) + s = ''.join(map(str, ret)) if fmt_str: ret = fmt_str.format(s) @@ -127,14 +127,14 @@ class AddrListIDStr(HiliteStr): ret = '{}{}{}[{}]'.format( addrlist.al_id.sid, (f'-{coin}', '')[coin == 'BTC'], - (f'-{mmtype}', '')[mmtype in ('L','E')], + (f'-{mmtype}', '')[mmtype in ('L', 'E')], s) - addrlist.dmsg_sc('id_str',ret[8:].split('[')[0]) + addrlist.dmsg_sc('id_str', ret[8:].split('[')[0]) - return str.__new__(cls,ret) + return str.__new__(cls, ret) -class AddrListData(list,MMGenObject): +class AddrListData(list, MMGenObject): pass class AddrList(MMGenObject): # Address info for a single seed ID @@ -149,10 +149,10 @@ class AddrList(MMGenObject): # Address info for a single seed ID has_keys = False chksum_rec_f = lambda foo, e: (str(e.idx), e.addr.views[e.addr.view_pref]) - def dmsg_sc(self,desc,data): + def dmsg_sc(self, desc, data): Msg(f'sc_debug_{desc}: {data}') - def noop(self,desc,data): + def noop(self, desc, data): pass def __init__( @@ -171,7 +171,7 @@ class AddrList(MMGenObject): # Address info for a single seed ID key_address_validity_check = None, # None=prompt user, True=check without prompt, False=skip check skip_chksum = False, skip_chksum_msg = False, - add_p2pkh = False ): + add_p2pkh = False): self.cfg = cfg self.ka_validity_chk = key_address_validity_check @@ -183,9 +183,9 @@ class AddrList(MMGenObject): # Address info for a single seed ID self.dmsg_sc = self.noop if seed and addr_idxs: # data from seed + idxs - self.al_id = AddrListID( sid=seed.sid, mmtype=MMGenAddrType(proto, mmtype or proto.dfl_mmtype) ) + self.al_id = AddrListID(sid=seed.sid, mmtype=MMGenAddrType(proto, mmtype or proto.dfl_mmtype)) src = 'gen' - adata = self.generate(seed, addr_idxs if isinstance(addr_idxs,AddrIdxList) else AddrIdxList(addr_idxs)) + adata = self.generate(seed, addr_idxs if isinstance(addr_idxs, AddrIdxList) else AddrIdxList(addr_idxs)) do_chksum = True elif addrfile: # data from MMGen address file self.infile = addrfile @@ -196,21 +196,21 @@ class AddrList(MMGenObject): # Address info for a single seed ID elif addrlist: # data from flat address list self.al_id = None from .util import remove_dups - addrlist = remove_dups(addrlist,edesc='address',desc='address list') - adata = AddrListData([AddrListEntry(proto=proto,addr=a) for a in addrlist]) + addrlist = remove_dups(addrlist, edesc='address', desc='address list') + adata = AddrListData([AddrListEntry(proto=proto, addr=a) for a in addrlist]) elif keylist: # data from flat key list self.al_id = None from .util import remove_dups - keylist = remove_dups(keylist,edesc='key',desc='key list',hide=True) - adata = AddrListData([AddrListEntry(proto=proto,sec=PrivKey(proto=proto,wif=k)) for k in keylist]) + keylist = remove_dups(keylist, edesc='key', desc='key list', hide=True) + adata = AddrListData([AddrListEntry(proto=proto, sec=PrivKey(proto=proto, wif=k)) for k in keylist]) elif seed or addr_idxs: - die(3,'Must specify both seed and addr indexes') + die(3, 'Must specify both seed and addr indexes') elif al_id or adata: - die(3,'Must specify both al_id and adata') + die(3, 'Must specify both al_id and adata') else: - die(3,f'Incorrect arguments for {type(self).__name__}') + die(3, f'Incorrect arguments for {type(self).__name__}') - # al_id,adata now set + # al_id, adata now set self.data = adata self.num_addrs = len(adata) self.fmt_data = '' @@ -221,7 +221,7 @@ class AddrList(MMGenObject): # Address info for a single seed ID if type(self) is ViewKeyAddrList: if not 'viewkey' in self.al_id.mmtype.extra_attrs: - die(1,f'viewkeys not supported for address type {self.al_id.mmtype.desc!r}') + die(1, f'viewkeys not supported for address type {self.al_id.mmtype.desc!r}') self.id_str = AddrListIDStr(self) @@ -233,30 +233,30 @@ class AddrList(MMGenObject): # Address info for a single seed ID if not skip_chksum_msg: self.do_chksum_msg(record=src=='gen') - def do_chksum_msg(self,record): + def do_chksum_msg(self, record): chk = 'Check this value against your records' rec = f'Record this checksum: it will be used to verify the {self.desc} file in the future' self.cfg._util.qmsg( f'Checksum for {self.desc} data {self.id_str.hl()}: {self.chksum.hl()}\n' + - (chk,rec)[record] ) + (chk, rec)[record]) - def generate(self,seed,addr_idxs): + def generate(self, seed, addr_idxs): seed = self.scramble_seed(seed.data) - self.dmsg_sc('seed',seed[:8].hex()) + self.dmsg_sc('seed', seed[:8].hex()) mmtype = self.al_id.mmtype - gen_wallet_passwd = type(self) in (KeyAddrList,ViewKeyAddrList) and 'wallet_passwd' in mmtype.extra_attrs - gen_viewkey = type(self) in (KeyAddrList,ViewKeyAddrList) and 'viewkey' in mmtype.extra_attrs + gen_wallet_passwd = type(self) in (KeyAddrList, ViewKeyAddrList) and 'wallet_passwd' in mmtype.extra_attrs + gen_viewkey = type(self) in (KeyAddrList, ViewKeyAddrList) and 'viewkey' in mmtype.extra_attrs if self.gen_addrs: from .keygen import KeyGenerator from .addrgen import AddrGenerator - kg = KeyGenerator( self.cfg, self.proto, mmtype.pubkey_type ) - ag = AddrGenerator( self.cfg, self.proto, mmtype ) + kg = KeyGenerator(self.cfg, self.proto, mmtype.pubkey_type) + ag = AddrGenerator(self.cfg, self.proto, mmtype) if self.add_p2pkh: - ag2 = AddrGenerator( self.cfg, self.proto, 'compressed' ) + ag2 = AddrGenerator(self.cfg, self.proto, 'compressed') from .derive import derive_coin_privkey_bytes @@ -265,19 +265,19 @@ class AddrList(MMGenObject): # Address info for a single seed ID out = AddrListData() CR = '\n' if self.cfg.debug_addrlist else '\r' - for pk_bytes in derive_coin_privkey_bytes(seed,addr_idxs): + for pk_bytes in derive_coin_privkey_bytes(seed, addr_idxs): if not self.cfg.debug: self.cfg._util.qmsg_r( - f'{CR}Generating {self.gen_desc} #{pk_bytes.idx} ({pk_bytes.pos} of {t_addrs})' ) + f'{CR}Generating {self.gen_desc} #{pk_bytes.idx} ({pk_bytes.pos} of {t_addrs})') - e = le( proto=self.proto, idx=pk_bytes.idx ) + e = le(proto=self.proto, idx=pk_bytes.idx) e.sec = PrivKey( self.proto, pk_bytes.data, compressed = mmtype.compressed, - pubkey_type = mmtype.pubkey_type ) + pubkey_type = mmtype.pubkey_type) if self.gen_addrs: data = kg.gen_data(e.sec) @@ -288,7 +288,7 @@ class AddrList(MMGenObject): # Address info for a single seed ID e.viewkey = ag.to_viewkey(data) if gen_wallet_passwd: e.wallet_passwd = self.gen_wallet_passwd( - e.viewkey.encode() if type(self) is ViewKeyAddrList else e.sec ) + e.viewkey.encode() if type(self) is ViewKeyAddrList else e.sec) elif self.gen_passwds: e.passwd = self.gen_passwd(e.sec) # TODO - own type @@ -299,32 +299,32 @@ class AddrList(MMGenObject): # Address info for a single seed ID self.al_id.hl(), t_addrs, self.gen_desc, - suf(t_addrs,self.gen_desc_pl), - ' ' * 15 )) + suf(t_addrs, self.gen_desc_pl), + ' ' * 15)) return out - def gen_wallet_passwd(self,privbytes): + def gen_wallet_passwd(self, privbytes): from .proto.btc.common import hash256 - return WalletPassword( hash256(privbytes)[:16].hex() ) + return WalletPassword(hash256(privbytes)[:16].hex()) - def check_format(self,addr): + def check_format(self, addr): return True # format is checked when added to list entry object - def scramble_seed(self,seed): + def scramble_seed(self, seed): is_btcfork = self.proto.base_coin == 'BTC' if is_btcfork and self.al_id.mmtype == 'L' and not self.proto.testnet: - self.dmsg_sc('str','(none)') + self.dmsg_sc('str', '(none)') return seed if self.proto.base_coin == 'ETH': scramble_key = self.proto.coin.lower() else: - scramble_key = (self.proto.coin.lower()+':','')[is_btcfork] + self.al_id.mmtype.name + scramble_key = (self.proto.coin.lower()+':', '')[is_btcfork] + self.al_id.mmtype.name from .crypto import Crypto if self.proto.testnet: scramble_key += ':' + self.proto.network - self.dmsg_sc('str',scramble_key) - return Crypto(self.cfg).scramble_seed(seed,scramble_key.encode()) + self.dmsg_sc('str', scramble_key) + return Crypto(self.cfg).scramble_seed(seed, scramble_key.encode()) def idxs(self): return [e.idx for e in self.data] @@ -333,7 +333,7 @@ class AddrList(MMGenObject): # Address info for a single seed ID return [f'{self.al_id.sid}:{e.idx}' for e in self.data] def addrpairs(self): - return [(e.idx,e.addr) for e in self.data] + return [(e.idx, e.addr) for e in self.data] def coinaddrs(self): return [e.addr for e in self.data] @@ -341,57 +341,57 @@ class AddrList(MMGenObject): # Address info for a single seed ID def comments(self): return [e.comment for e in self.data] - def entry(self,idx): + def entry(self, idx): for e in self.data: if idx == e.idx: return e - def coinaddr(self,idx): + def coinaddr(self, idx): for e in self.data: if idx == e.idx: return e.addr - def comment(self,idx): + def comment(self, idx): for e in self.data: if idx == e.idx: return e.comment - def set_comment(self,idx,comment): + def set_comment(self, idx, comment): for e in self.data: if idx == e.idx: e.comment = comment - def make_reverse_dict_addrlist(self,coinaddrs): + def make_reverse_dict_addrlist(self, coinaddrs): d = MMGenDict() b = coinaddrs for e in self.data: try: - d[b[b.index(e.addr)]] = ( MMGenID(self.proto, f'{self.al_id}:{e.idx}'), e.comment ) + d[b[b.index(e.addr)]] = (MMGenID(self.proto, f'{self.al_id}:{e.idx}'), e.comment) except ValueError: pass return d - def add_wifs(self,key_list): + def add_wifs(self, key_list): """ Match WIF keys in a flat list to addresses in self by generating all possible addresses for each key. """ - def gen_addr(pk,t): + def gen_addr(pk, t): at = self.proto.addr_type(t) from .keygen import KeyGenerator from .addrgen import AddrGenerator - kg = KeyGenerator( self.cfg, self.proto, at.pubkey_type ) - ag = AddrGenerator( self.cfg, self.proto, at ) + kg = KeyGenerator(self.cfg, self.proto, at.pubkey_type) + ag = AddrGenerator(self.cfg, self.proto, at) return ag.to_addr(kg.gen_data(pk)) - compressed_types = set(self.proto.mmtypes) - {'L','E'} - uncompressed_types = set(self.proto.mmtypes) & {'L','E'} + compressed_types = set(self.proto.mmtypes) - {'L', 'E'} + uncompressed_types = set(self.proto.mmtypes) & {'L', 'E'} def gen(): for wif in key_list: - pk = PrivKey(proto=self.proto,wif=wif) + pk = PrivKey(proto=self.proto, wif=wif) for t in (compressed_types if pk.compressed else uncompressed_types): - yield ( gen_addr(pk,t), pk ) + yield (gen_addr(pk, t), pk) addrs4keys = dict(gen()) @@ -399,14 +399,14 @@ class AddrList(MMGenObject): # Address info for a single seed ID if d.addr in addrs4keys: d.sec = addrs4keys[d.addr] - def list_missing(self,attr): - return [d.addr for d in self.data if not getattr(d,attr)] + def list_missing(self, attr): + return [d.addr for d in self.data if not getattr(d, attr)] @property def file(self): - if not hasattr(self,'_file'): + if not hasattr(self, '_file'): from . import addrfile - self._file = getattr( addrfile, type(self).__name__.replace('List','File') )(self) + self._file = getattr(addrfile, type(self).__name__.replace('List', 'File'))(self) return self._file class KeyAddrList(AddrList): diff --git a/mmgen/autosign.py b/mmgen/autosign.py index 982a4f2d..f2132037 100755 --- a/mmgen/autosign.py +++ b/mmgen/autosign.py @@ -20,7 +20,7 @@ from subprocess import run, PIPE, DEVNULL from .cfg import Config from .util import msg, msg_r, ymsg, rmsg, gmsg, bmsg, die, suf, fmt, fmt_list, is_int, have_sudo, capfirst from .color import yellow, red, orange, brown, blue -from .wallet import Wallet,get_wallet_cls +from .wallet import Wallet, get_wallet_cls from .addrlist import AddrIdxList from .filename import find_file_in_dir from .ui import keypress_confirm @@ -135,10 +135,10 @@ class Signable: multiple_ok = True action_desc = 'signed' - def __init__(self,parent): + def __init__(self, parent): self.parent = parent self.cfg = parent.cfg - self.dir = getattr(parent,self.dir_name) + self.dir = getattr(parent, self.dir_name) self.name = type(self).__name__ @property @@ -152,21 +152,21 @@ class Signable: @property def unsigned(self): - return self._unprocessed( '_unsigned', self.rawext, self.sigext ) + return self._unprocessed('_unsigned', self.rawext, self.sigext) @property def unsubmitted(self): - return self._unprocessed( '_unsubmitted', self.sigext, self.subext ) + return self._unprocessed('_unsubmitted', self.sigext, self.subext) @property def unsubmitted_raw(self): - return self._unprocessed( '_unsubmitted_raw', self.rawext, self.subext ) + return self._unprocessed('_unsubmitted_raw', self.rawext, self.subext) unsent = unsubmitted unsent_raw = unsubmitted_raw - def _unprocessed(self,attrname,rawext,sigext): - if not hasattr(self,attrname): + def _unprocessed(self, attrname, rawext, sigext): + if not hasattr(self, attrname): dirlist = sorted(self.dir.iterdir()) names = {f.name for f in dirlist} setattr( @@ -174,13 +174,13 @@ class Signable: attrname, tuple(f for f in dirlist if f.name.endswith('.' + rawext) - and f.name[:-len(rawext)] + sigext not in names) ) - return getattr(self,attrname) + and f.name[:-len(rawext)] + sigext not in names)) + return getattr(self, attrname) - def print_bad_list(self,bad_files): + def print_bad_list(self, bad_files): msg('\n{a}\n{b}'.format( a = red(f'Failed {self.desc}s:'), - b = ' {}\n'.format('\n '.join(self.gen_bad_list(sorted(bad_files,key=lambda f: f.name)))) + b = ' {}\n'.format('\n '.join(self.gen_bad_list(sorted(bad_files, key=lambda f: f.name)))) )) def die_wrong_num_txs(self, tx_type, msg=None, desc=None, show_dir=False): @@ -255,7 +255,7 @@ class Signable: dir_name = 'tx_dir' fail_msg = 'failed to sign' - async def sign(self,f): + async def sign(self, f): from .tx import UnsignedTX tx1 = UnsignedTX( cfg = self.cfg, @@ -263,7 +263,7 @@ class Signable: automount = self.name=='automount_transaction') if tx1.proto.sign_mode == 'daemon': from .rpc import rpc_init - tx1.rpc = await rpc_init( self.cfg, tx1.proto, ignore_wallet=True ) + tx1.rpc = await rpc_init(self.cfg, tx1.proto, ignore_wallet=True) from .tx.sign import txsign tx2 = await txsign( cfg_parm = self.cfg, @@ -278,7 +278,7 @@ class Signable: else: return False - def print_summary(self,signables): + def print_summary(self, signables): if self.cfg.full_summary: bmsg('\nAutosign summary:\n') @@ -289,22 +289,22 @@ class Signable: for tx in signables: non_mmgen = [o for o in tx.outputs if not o.mmid] if non_mmgen: - yield (tx,non_mmgen) + yield (tx, non_mmgen) body = list(gen()) if body: bmsg('\nAutosign summary:') fs = '{} {} {}' - t_wid,a_wid = 6,44 + t_wid, a_wid = 6, 44 def gen(): - yield fs.format('TX ID ','Non-MMGen outputs'+' '*(a_wid-17),'Amount') + yield fs.format('TX ID ', 'Non-MMGen outputs'+' '*(a_wid-17), 'Amount') yield fs.format('-'*t_wid, '-'*a_wid, '-'*7) - for tx,non_mmgen in body: + for tx, non_mmgen in body: 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, + tx.txid.fmt(width=t_wid, color=True) if nm is non_mmgen[0] else ' '*t_wid, nm.addr.fmt(nm.addr.view_pref, width=a_wid, color=True), nm.amt.hl() + ' ' + yellow(tx.coin)) @@ -312,7 +312,7 @@ class Signable: else: msg('\nNo non-MMGen outputs') - def gen_bad_list(self,bad_files): + def gen_bad_list(self, bad_files): for f in bad_files: yield red(f.name) @@ -326,12 +326,12 @@ class Signable: class xmr_signable(transaction): # mixin class - def need_daemon_restart(self,m,new_idx): + def need_daemon_restart(self, m, new_idx): old_idx = self.parent.xmr_cur_wallet_idx self.parent.xmr_cur_wallet_idx = new_idx return old_idx != new_idx or m.wd.state != 'ready' - def print_summary(self,signables): + def print_summary(self, signables): bmsg('\nAutosign summary:') msg('\n'.join(s.get_info(indent=' ') for s in signables) + self.summary_footer) @@ -342,7 +342,7 @@ class Signable: multiple_ok = False summary_footer = '' - async def sign(self,f): + async def sign(self, f): from . import xmrwallet from .xmrwallet.file.tx import MoneroMMGenTX tx1 = MoneroMMGenTX.Completed(self.parent.xmrwallet_cfg, f) @@ -351,7 +351,7 @@ class Signable: self.parent.xmrwallet_cfg, infile = str(self.parent.wallet_files[0]), # MMGen wallet file wallets = str(tx1.src_wallet_idx)) - tx2 = await m.main( f, restart_daemon=self.need_daemon_restart(m,tx1.src_wallet_idx) ) + tx2 = await m.main(f, restart_daemon=self.need_daemon_restart(m, tx1.src_wallet_idx)) tx2.write(ask_write=False) return tx2 @@ -370,7 +370,7 @@ class Signable: f for f in super().unsigned if not json.loads(f.read_text())['MoneroMMGenWalletOutputsFile']['data']['imported']) - async def sign(self,f): + async def sign(self, f): from . import xmrwallet wallet_idx = xmrwallet.op_cls('wallet').get_idx_from_fn(f) m = xmrwallet.op( @@ -378,7 +378,7 @@ class Signable: self.parent.xmrwallet_cfg, infile = str(self.parent.wallet_files[0]), # MMGen wallet file wallets = str(wallet_idx)) - obj = await m.main(f, wallet_idx, restart_daemon=self.need_daemon_restart(m,wallet_idx)) + obj = await m.main(f, wallet_idx, restart_daemon=self.need_daemon_restart(m, wallet_idx)) obj.write(quiet=not obj.data.sign) self.action_desc = 'imported and signed' if obj.data.sign else 'imported' return obj @@ -390,26 +390,26 @@ class Signable: dir_name = 'msg_dir' fail_msg = 'failed to sign or signed incompletely' - async def sign(self,f): - from .msg import UnsignedMsg,SignedMsg - m = UnsignedMsg( self.cfg, infile=f ) + async def sign(self, f): + from .msg import UnsignedMsg, SignedMsg + m = UnsignedMsg(self.cfg, infile=f) await m.sign(wallet_files=self.parent.wallet_files[:], passwd_file=str(self.parent.keyfile)) - m = SignedMsg( self.cfg, data=m.__dict__ ) + m = SignedMsg(self.cfg, data=m.__dict__) m.write_to_file( outdir = self.dir.resolve(), - ask_overwrite = False ) + ask_overwrite = False) if m.data.get('failed_sids'): - die('MsgFileFailedSID',f'Failed Seed IDs: {fmt_list(m.data["failed_sids"],fmt="bare")}') + die('MsgFileFailedSID', f'Failed Seed IDs: {fmt_list(m.data["failed_sids"], fmt="bare")}') return m - def print_summary(self,signables): + def print_summary(self, signables): gmsg('\nSigned message files:') for message in signables: gmsg(' ' + message.signed_filename) - def gen_bad_list(self,bad_files): + def gen_bad_list(self, bad_files): for f in bad_files: - sigfile = f.parent / ( f.name[:-len(self.rawext)] + self.sigext ) + sigfile = f.parent / (f.name[:-len(self.rawext)] + self.sigext) yield orange(sigfile.name) if sigfile.exists() else red(f.name) class Autosign: @@ -453,13 +453,13 @@ class Autosign: def init_fixup(self): # see test/overlay/fakemods/mmgen/autosign.py pass - def __init__(self,cfg,cmd=None): + def __init__(self, cfg, cmd=None): if cfg.mnemonic_fmt: if cfg.mnemonic_fmt not in self.mn_fmts: - die(1,'{!r}: invalid mnemonic format (must be one of: {})'.format( + die(1, '{!r}: invalid mnemonic format (must be one of: {})'.format( cfg.mnemonic_fmt, - fmt_list( self.mn_fmts, fmt='no_spc' ) )) + fmt_list(self.mn_fmts, fmt='no_spc'))) if sys.platform == 'linux': self.dfl_mountpoint = f'/mnt/{self.linux_mount_subdir}' @@ -507,11 +507,11 @@ class Autosign: self.keyfile = self.mountpoint / 'autosign.key' - if any(k in cfg._uopts for k in ('help','longhelp')): + if any(k in cfg._uopts for k in ('help', 'longhelp')): return if 'coin' in cfg._uopts: - die(1,'--coin option not supported with this command. Use --coins instead') + die(1, '--coin option not supported with this command. Use --coins instead') self.coins = cfg.coins.upper().split(',') if cfg.coins else [] @@ -539,7 +539,7 @@ class Autosign: self.dirs |= self.xmr_dirs self.signables += Signable.xmr_signables - for name,path in self.dirs.items(): + for name, path in self.dirs.items(): setattr(self, name, self.mountpoint / path) self.swap = SwapMgr(self.cfg, ignore_zram=True) @@ -553,28 +553,28 @@ class Autosign: from .rpc import rpc_init from .exception import SocketError try: - await rpc_init( self.cfg, proto, ignore_wallet=True ) + await rpc_init(self.cfg, proto, ignore_wallet=True) except SocketError as e: from .daemon import CoinDaemon - d = CoinDaemon( self.cfg, proto=proto, test_suite=self.cfg.test_suite ) + d = CoinDaemon(self.cfg, proto=proto, test_suite=self.cfg.test_suite) die(2, f'\n{e}\nIs the {d.coind_name} daemon ({d.exec_fn}) running ' - + 'and listening on the correct port?' ) + + 'and listening on the correct port?') @property def wallet_files(self): - if not hasattr(self,'_wallet_files'): + if not hasattr(self, '_wallet_files'): try: dirlist = self.wallet_dir.iterdir() except: - die(1,f"Cannot open wallet directory '{self.wallet_dir}'. Did you run ‘mmgen-autosign setup’?") + die(1, f"Cannot open wallet directory '{self.wallet_dir}'. Did you run ‘mmgen-autosign setup’?") self._wallet_files = [f for f in dirlist if f.suffix == '.mmdat'] if not self._wallet_files: - die(1,'No wallet files present!') + die(1, 'No wallet files present!') return self._wallet_files @@ -595,7 +595,7 @@ class Autosign: if sys.platform == 'linux' and not self.mountpoint.is_dir(): def do_die(m): - die(1,'\n' + yellow(fmt(m.strip(),indent=' '))) + die(1, '\n' + yellow(fmt(m.strip(), indent=' '))) if Path(self.old_dfl_mountpoint).is_dir(): do_die(self.old_dfl_mountpoint_errmsg) else: @@ -607,14 +607,14 @@ class Autosign: if not silent: msg(f"Mounting '{self.mountpoint}'") else: - die(1,f'Unable to mount device {self.dev_label} at {self.mountpoint}') + die(1, f'Unable to mount device {self.dev_label} at {self.mountpoint}') for dirname in self.dirs: check_or_create(dirname) def do_umount(self, silent=False, verbose=False): if self.mountpoint.is_mount(): - run( ['sync'], check=True ) + run(['sync'], check=True) if not silent: msg(f"Unmounting '{self.mountpoint}'") redir = None if verbose else DEVNULL @@ -634,8 +634,8 @@ class Autosign: return not fails - async def sign_all(self,target_name): - target = getattr(Signable,target_name)(self) + async def sign_all(self, target_name): + target = getattr(Signable, target_name)(self) if target.unsigned: good = [] bad = [] @@ -675,7 +675,7 @@ class Autosign: self.led.set('busy') ret = [await self.sign_all(signable) for signable in self.signables] for val in ret: - if isinstance(val,str): + if isinstance(val, str): msg(val) if self.cfg.test_suite_autosign_threaded: await asyncio.sleep(0.3) @@ -701,15 +701,15 @@ class Autosign: desc = f"key file '{self.keyfile}'" msg('Creating ' + desc) try: - self.keyfile.write_text( os.urandom(32).hex() ) + self.keyfile.write_text(os.urandom(32).hex()) self.keyfile.chmod(0o400) except: - die(2,'Unable to write ' + desc) + die(2, 'Unable to write ' + desc) msg('Wrote ' + desc) - def gen_key(self,no_unmount=False): + def gen_key(self, no_unmount=False): if not self.device_inserted: - die(1,'Removable device not present!') + die(1, 'Removable device not present!') self.do_mount() self.wipe_encryption_key() self.create_key() @@ -756,7 +756,7 @@ class Autosign: try: self.wallet_dir.stat() except: - die(2,f"Unable to create wallet directory '{self.wallet_dir}'") + die(2, f"Unable to create wallet directory '{self.wallet_dir}'") self.gen_key(no_unmount=True) @@ -768,20 +768,20 @@ class Autosign: remove_wallet_dir() create_wallet_dir() - wf = find_file_in_dir( get_wallet_cls('mmgen'), self.cfg.data_dir ) + wf = find_file_in_dir(get_wallet_cls('mmgen'), self.cfg.data_dir) if wf and keypress_confirm( cfg = self.cfg, prompt = f"Default wallet '{wf}' found.\nUse default wallet for autosigning?", - default_yes = True ): - ss_in = Wallet( Config(), wf ) + default_yes = True): + ss_in = Wallet(Config(), wf) else: - ss_in = Wallet( self.cfg, in_fmt=self.mn_fmts[self.cfg.mnemonic_fmt or self.dfl_mn_fmt] ) - ss_out = Wallet( self.cfg, ss=ss_in, passwd_file=str(self.keyfile) ) - ss_out.write_to_file( desc='autosign wallet', outdir=self.wallet_dir ) + ss_in = Wallet(self.cfg, in_fmt=self.mn_fmts[self.cfg.mnemonic_fmt or self.dfl_mn_fmt]) + ss_out = Wallet(self.cfg, ss=ss_in, passwd_file=str(self.keyfile)) + ss_out.write_to_file(desc='autosign wallet', outdir=self.wallet_dir) @property def xmrwallet_cfg(self): - if not hasattr(self,'_xmrwallet_cfg'): + if not hasattr(self, '_xmrwallet_cfg'): self._xmrwallet_cfg = Config({ '_clone': self.cfg, 'coin': 'xmr', @@ -820,29 +820,29 @@ class Autosign: nonlocal count msg_r('.') from .fileutil import shred_file - shred_file( f, verbose=self.cfg.verbose ) + shred_file(f, verbose=self.cfg.verbose) count += 1 def clean_dir(s_name): - def clean_files(rawext,sigext): + def clean_files(rawext, sigext): for f in s.dir.iterdir(): if s.clean_all and (f.name.endswith(f'.{rawext}') or f.name.endswith(f'.{sigext}')): do_shred(f) elif f.name.endswith(f'.{sigext}'): - raw = f.parent / ( f.name[:-len(sigext)] + rawext ) + raw = f.parent / (f.name[:-len(sigext)] + rawext) if raw.is_file(): do_shred(raw) - s = getattr(Signable,s_name)(self) + s = getattr(Signable, s_name)(self) msg_r(f"Cleaning directory '{s.dir}'..") if s.dir.is_dir(): - clean_files( s.rawext, s.sigext ) - if hasattr(s,'subext'): - clean_files( s.rawext, s.subext ) - clean_files( s.sigext, s.subext ) + clean_files(s.rawext, s.sigext) + if hasattr(s, 'subext'): + clean_files(s.rawext, s.subext) + clean_files(s.sigext, s.subext) msg('done' if s.dir.is_dir() else 'skipped (no dir)') @@ -887,7 +887,7 @@ class Autosign: msg_r('.') n += 1 - def at_exit(self,exit_val,message=None): + def at_exit(self, exit_val, message=None): if message: msg(message) self.led.stop() @@ -895,16 +895,16 @@ class Autosign: def init_exit_handler(self): - def handler(arg1,arg2): - self.at_exit(1,'\nCleaning up...') + def handler(arg1, arg2): + self.at_exit(1, '\nCleaning up...') import signal - signal.signal( signal.SIGTERM, handler ) - signal.signal( signal.SIGINT, handler ) + signal.signal(signal.SIGTERM, handler) + signal.signal(signal.SIGINT, handler) def init_led(self): from .led import LEDControl self.led = LEDControl( enabled = self.cfg.led, - simulate = self.cfg.test_suite_autosign_led_simulate ) + simulate = self.cfg.test_suite_autosign_led_simulate) self.led.set('off') diff --git a/mmgen/base_obj.py b/mmgen/base_obj.py index 3987f475..ab9b157f 100755 --- a/mmgen/base_obj.py +++ b/mmgen/base_obj.py @@ -21,14 +21,14 @@ base_obj: base objects with no internal imports for the MMGen suite """ class AsyncInit(type): - async def __call__(cls,*args,**kwargs): - instance = cls.__new__(cls,*args,**kwargs) - await type(instance).__init__(instance,*args,**kwargs) + async def __call__(cls, *args, **kwargs): + instance = cls.__new__(cls, *args, **kwargs) + await type(instance).__init__(instance, *args, **kwargs) return instance class AttrCtrlMeta(type): - def __call__(cls,*args,**kwargs): - instance = super().__call__(*args,**kwargs) + def __call__(cls, *args, **kwargs): + instance = super().__call__(*args, **kwargs) if instance._autolock: instance._lock() return instance @@ -55,38 +55,38 @@ class AttrCtrl(metaclass=AttrCtrlMeta): def _lock(self): self._locked = True - def __getattr__(self,name): + def __getattr__(self, name): if self._locked and self._default_to_none: return None else: raise AttributeError(f'{type(self).__name__} object has no attribute {name!r}') - def __setattr__(self,name,value): + def __setattr__(self, name, value): if self._locked: assert name != '_locked', 'lock can be set only once' - def do_error(name,value,ref_val): + def do_error(name, value, ref_val): raise AttributeError( f'{value!r}: invalid value for attribute {name!r}' + ' of {} object (must be of type {}, not {})'.format( type(self).__name__, type(ref_val).__name__, - type(value).__name__ ) ) + type(value).__name__)) - if not (name in self.__dict__ or hasattr(type(self),name)): + if not (name in self.__dict__ or hasattr(type(self), name)): raise AttributeError(f'{type(self).__name__} object has no attribute {name!r}') - ref_val = getattr(type(self),name) if self._use_class_attr else getattr(self,name) + ref_val = getattr(type(self), name) if self._use_class_attr else getattr(self, name) if ( (name not in self._skip_type_check) and (ref_val is not None) - and not isinstance(value,type(ref_val)) + and not isinstance(value, type(ref_val)) ): - do_error(name,value,ref_val) + do_error(name, value, ref_val) - return object.__setattr__(self,name,value) + return object.__setattr__(self, name, value) def __delattr__(self, name): if self._locked and not name in self._delete_ok: @@ -107,25 +107,23 @@ class Lockable(AttrCtrl): _reset_ok = () def _lock(self): - for name in ('_set_ok','_reset_ok'): - for attr in getattr(self,name): - assert hasattr(self,attr), ( - f'attribute {attr!r} in {name!r} not found in {type(self).__name__} object {id(self)}' ) + for name in ('_set_ok', '_reset_ok'): + for attr in getattr(self, name): + assert hasattr(self, attr), ( + f'attribute {attr!r} in {name!r} not found in {type(self).__name__} object {id(self)}') super()._lock() - def __setattr__(self,name,value): - if self._locked and (name in self.__dict__ or hasattr(type(self),name)): - val = getattr(self,name) + def __setattr__(self, name, value): + if self._locked and (name in self.__dict__ or hasattr(type(self), name)): + val = getattr(self, name) if name not in (self._set_ok + self._reset_ok): raise AttributeError(f'attribute {name!r} of {type(self).__name__} object is read-only') elif name not in self._reset_ok: - #print(self.__dict__) if not ( (val != 0 and not val) or - (self._use_class_attr and name not in self.__dict__) ): + (self._use_class_attr and name not in self.__dict__)): raise AttributeError( f'attribute {name!r} of {type(self).__name__} object is already set,' - + ' and resetting is forbidden' ) - # else name is in (_set_ok + _reset_ok) -- allow name to be in both lists + + ' and resetting is forbidden') - return AttrCtrl.__setattr__(self,name,value) + return AttrCtrl.__setattr__(self, name, value) diff --git a/mmgen/baseconv.py b/mmgen/baseconv.py index 70cc6aae..6711a9e5 100755 --- a/mmgen/baseconv.py +++ b/mmgen/baseconv.py @@ -32,23 +32,22 @@ def is_b32_str(s): def is_mmgen_mnemonic(s): try: - baseconv('mmgen').tobytes(s.split(),pad='seed') + baseconv('mmgen').tobytes(s.split(), pad='seed') return True except: return False class baseconv: mn_base = 1626 - dt = namedtuple('desc_tuple',['short','long']) + dt = namedtuple('desc_tuple', ['short', 'long']) constants = { 'desc': { - 'b58': dt('base58', 'base58-encoded data'), - 'b32': dt('MMGen base32', 'MMGen base32-encoded data created using simple base conversion'), - 'b16': dt('hexadecimal string','base16 (hexadecimal) string data'), - 'b10': dt('base10 string', 'base10 (decimal) string data'), - 'b8': dt('base8 string', 'base8 (octal) string data'), - 'b6d': dt('base6d (die roll)', 'base6 data using the digits from one to six'), -# 'tirosh':('Tirosh mnemonic', 'base1626 mnemonic using truncated Tirosh wordlist'), # not used by wallet + 'b58': dt('base58', 'base58-encoded data'), + 'b32': dt('MMGen base32', 'MMGen base32-encoded data created using simple base conversion'), + 'b16': dt('hexadecimal string', 'base16 (hexadecimal) string data'), + 'b10': dt('base10 string', 'base10 (decimal) string data'), + 'b8': dt('base8 string', 'base8 (octal) string data'), + 'b6d': dt('base6d (die roll)', 'base6 data using the digits from one to six'), 'mmgen': dt('MMGen native mnemonic', 'MMGen native mnemonic seed phrase created using old Electrum wordlist and simple base conversion'), }, @@ -68,18 +67,18 @@ class baseconv: # 'tirosh1633': '1a5faeff' # tirosh list is 1633 words long! }, 'seedlen_map': { - 'b58': { 16:22, 24:33, 32:44 }, - 'b6d': { 16:50, 24:75, 32:100 }, - 'mmgen': { 16:12, 24:18, 32:24 }, + 'b58': {16:22, 24:33, 32:44}, + 'b6d': {16:50, 24:75, 32:100}, + 'mmgen': {16:12, 24:18, 32:24}, }, 'seedlen_map_rev': { - 'b58': { 22:16, 33:24, 44:32 }, - 'b6d': { 50:16, 75:24, 100:32 }, - 'mmgen': { 12:16, 18:24, 24:32 }, + 'b58': {22:16, 33:24, 44:32}, + 'b6d': {50:16, 75:24, 100:32}, + 'mmgen': {12:16, 18:24, 24:32}, } } - def __init__(self,wl_id): + def __init__(self, wl_id): if wl_id == 'mmgen': from .wordlist.electrum import words @@ -87,9 +86,9 @@ class baseconv: elif wl_id not in self.constants['digits']: raise ValueError(f'{wl_id}: unrecognized mnemonic ID') - for k,v in self.constants.items(): + for k, v in self.constants.items(): if wl_id in v: - setattr(self,k,v[wl_id]) + setattr(self, k, v[wl_id]) self.wl_id = wl_id @@ -98,23 +97,23 @@ class baseconv: def get_wordlist_chksum(self): from hashlib import sha256 - return sha256( ' '.join(self.digits).encode() ).hexdigest()[:8] + return sha256(' '.join(self.digits).encode()).hexdigest()[:8] - def check_wordlist(self,cfg): + def check_wordlist(self, cfg): wl = self.digits ret = f'Wordlist: {self.wl_id}\nLength: {len(wl)} words' new_chksum = self.get_wordlist_chksum() - cfg._util.compare_chksums( new_chksum, 'generated', self.wl_chksum, 'saved', die_on_fail=True ) + cfg._util.compare_chksums(new_chksum, 'generated', self.wl_chksum, 'saved', die_on_fail=True) if tuple(sorted(wl)) == wl: return ret + '\nList is sorted' else: - die(3,'ERROR: List is not sorted!') + die(3, 'ERROR: List is not sorted!') @staticmethod - def get_pad(pad,seed_pad_func): + def get_pad(pad, seed_pad_func): """ 'pad' argument to baseconv conversion methods must be either None, 'seed' or an integer. If None, output of minimum (but never zero) length will be produced. @@ -128,72 +127,72 @@ class baseconv: elif pad == 'seed': return seed_pad_func() else: - die('BaseConversionPadError',f"{pad!r}: illegal value for 'pad' (must be None,'seed' or int)") + die('BaseConversionPadError', f"{pad!r}: illegal value for 'pad' (must be None, 'seed' or int)") - def tohex(self,words_arg,pad=None): + def tohex(self, words_arg, pad=None): "convert string or list data of instance base to a hexadecimal string" return self.tobytes(words_arg, pad//2 if type(pad) is int else pad).hex() - def tobytes(self,words_arg,pad=None): + def tobytes(self, words_arg, pad=None): "convert string or list data of instance base to byte string" - words = words_arg if isinstance(words_arg,(list,tuple)) else tuple(words_arg.strip()) + words = words_arg if isinstance(words_arg, (list, tuple)) else tuple(words_arg.strip()) desc = self.desc.short if len(words) == 0: - die('BaseConversionError',f'empty {desc} data') + die('BaseConversionError', f'empty {desc} data') def get_seed_pad(): - assert hasattr(self,'seedlen_map_rev'), f'seed padding not supported for base {self.wl_id!r}' + assert hasattr(self, 'seedlen_map_rev'), f'seed padding not supported for base {self.wl_id!r}' d = self.seedlen_map_rev if not len(words) in d: - die( 'BaseConversionError', - f'{len(words)}: invalid length for seed-padded {desc} data in base conversion' ) + die('BaseConversionError', + f'{len(words)}: invalid length for seed-padded {desc} data in base conversion') return d[len(words)] - pad_val = max(self.get_pad(pad,get_seed_pad),1) + pad_val = max(self.get_pad(pad, get_seed_pad), 1) wl = self.digits base = len(wl) if not set(words) <= set(wl): - die( 'BaseConversionError', - ( 'seed data' if pad == 'seed' else f'{words_arg!r}:' ) + - f' not in {desc} format' ) + die('BaseConversionError', + ('seed data' if pad == 'seed' else f'{words_arg!r}:') + + f' not in {desc} format') ret = sum(wl.index(words[::-1][i])*(base**i) for i in range(len(words))) bl = ret.bit_length() - return ret.to_bytes(max(pad_val,bl//8+bool(bl%8)),'big') + return ret.to_bytes(max(pad_val, bl//8+bool(bl%8)), 'big') - def fromhex(self,hexstr,pad=None,tostr=False): + def fromhex(self, hexstr, pad=None, tostr=False): "convert a hexadecimal string to a list or string data of instance base" from .util import is_hex_str if not is_hex_str(hexstr): - die( 'HexadecimalStringError', - ( 'seed data' if pad == 'seed' else f'{hexstr!r}:' ) + - ' not a hexadecimal string' ) + die('HexadecimalStringError', + ('seed data' if pad == 'seed' else f'{hexstr!r}:') + + ' not a hexadecimal string') - return self.frombytes( bytes.fromhex(hexstr), pad, tostr ) + return self.frombytes(bytes.fromhex(hexstr), pad, tostr) - def frombytes(self,bytestr,pad=None,tostr=False): + def frombytes(self, bytestr, pad=None, tostr=False): "convert byte string to list or string data of instance base" if not bytestr: - die( 'BaseConversionError', 'empty data not allowed in base conversion' ) + die('BaseConversionError', 'empty data not allowed in base conversion') def get_seed_pad(): - assert hasattr(self,'seedlen_map'), f'seed padding not supported for base {self.wl_id!r}' + assert hasattr(self, 'seedlen_map'), f'seed padding not supported for base {self.wl_id!r}' d = self.seedlen_map if not len(bytestr) in d: - die( 'SeedLengthError', - f'{len(bytestr)}: invalid byte length for seed data in seed-padded base conversion' ) + die('SeedLengthError', + f'{len(bytestr)}: invalid byte length for seed data in seed-padded base conversion') return d[len(bytestr)] - pad = max(self.get_pad(pad,get_seed_pad),1) + pad = max(self.get_pad(pad, get_seed_pad), 1) wl = self.digits def gen(): - num = int.from_bytes(bytestr,'big') + num = int.from_bytes(bytestr, 'big') base = len(wl) while num: yield num % base diff --git a/mmgen/bip39.py b/mmgen/bip39.py index 89a68830..115bf1a0 100755 --- a/mmgen/bip39.py +++ b/mmgen/bip39.py @@ -23,21 +23,21 @@ bip39.py - Data and routines for BIP39 mnemonic seed phrases from hashlib import sha256 from .baseconv import baseconv -from .util import is_hex_str,die +from .util import is_hex_str, die def is_bip39_mnemonic(s): - return bool( bip39().tohex(s.split()) ) + return bool(bip39().tohex(s.split())) # implements a subset of the baseconv API class bip39(baseconv): desc = baseconv.dt('BIP39 mnemonic', 'BIP39 mnemonic seed phrase') wl_chksum = 'f18b9a84' - seedlen_map = { 16:12, 24:18, 32:24 } - seedlen_map_rev = { 12:16, 18:24, 24:32 } + seedlen_map = {16:12, 24:18, 32:24} + seedlen_map_rev = {12:16, 18:24, 24:32} from collections import namedtuple - bc = namedtuple('bip39_constants',['chk_len','mn_len']) + bc = namedtuple('bip39_constants', ['chk_len', 'mn_len']) # ENT CS MS constants = { 128: bc(4, 12), @@ -47,21 +47,21 @@ class bip39(baseconv): 256: bc(8, 24), } - def __init__(self,wl_id='bip39'): + def __init__(self, wl_id='bip39'): assert wl_id == 'bip39', "initialize with 'bip39' for compatibility with baseconv API" from .wordlist.bip39 import words self.digits = words self.wl_id = 'bip39' @classmethod - def nwords2seedlen(cls,nwords,in_bytes=False,in_hex=False): - for k,v in cls.constants.items(): + def nwords2seedlen(cls, nwords, in_bytes=False, in_hex=False): + for k, v in cls.constants.items(): if v.mn_len == nwords: return k//8 if in_bytes else k//4 if in_hex else k - die( 'MnemonicError', f'{nwords!r}: invalid word length for BIP39 mnemonic' ) + die('MnemonicError', f'{nwords!r}: invalid word length for BIP39 mnemonic') @classmethod - def seedlen2nwords(cls,seed_len,in_bytes=False,in_hex=False): + def seedlen2nwords(cls, seed_len, in_bytes=False, in_hex=False): seed_bits = seed_len * 8 if in_bytes else seed_len * 4 if in_hex else seed_len try: return cls.constants[seed_bits].mn_len @@ -71,47 +71,47 @@ class bip39(baseconv): def tohex(self, words_arg, pad=None): return self.tobytes(words_arg, pad=pad).hex() - def tobytes(self,words_arg,pad=None): - assert isinstance(words_arg,(list,tuple)),'words_arg must be list or tuple' - assert pad in (None,'seed'), f"{pad}: invalid 'pad' argument (must be None or 'seed')" + def tobytes(self, words_arg, pad=None): + assert isinstance(words_arg, (list, tuple)), 'words_arg must be list or tuple' + assert pad in (None, 'seed'), f"{pad}: invalid 'pad' argument (must be None or 'seed')" wl = self.digits - for n,w in enumerate(words_arg): + for n, w in enumerate(words_arg): if w not in wl: - die( 'MnemonicError', f'word #{n+1} is not in the BIP39 word list' ) + die('MnemonicError', f'word #{n+1} is not in the BIP39 word list') res = ''.join(f'{wl.index(w):011b}' for w in words_arg) - for k,v in self.constants.items(): + for k, v in self.constants.items(): if len(words_arg) == v.mn_len: bitlen = k break else: - die( 'MnemonicError', f'{len(words_arg)}: invalid BIP39 seed phrase length' ) + die('MnemonicError', f'{len(words_arg)}: invalid BIP39 seed phrase length') seed_bin = res[:bitlen] chk_bin = res[bitlen:] - seed_hex = f'{int(seed_bin,2):0{bitlen//4}x}' + seed_hex = f'{int(seed_bin, 2):0{bitlen//4}x}' seed_bytes = bytes.fromhex(seed_hex) chk_len = self.constants[bitlen].chk_len chk_hex_chk = sha256(seed_bytes).hexdigest() - chk_bin_chk = f'{int(chk_hex_chk,16):0256b}'[:chk_len] + chk_bin_chk = f'{int(chk_hex_chk, 16):0256b}'[:chk_len] if chk_bin != chk_bin_chk: - die( 'MnemonicError', f'invalid BIP39 seed phrase checksum ({chk_bin} != {chk_bin_chk})' ) + die('MnemonicError', f'invalid BIP39 seed phrase checksum ({chk_bin} != {chk_bin_chk})') return seed_bytes def fromhex(self, hexstr, pad=None, tostr=False): - assert is_hex_str(hexstr),'seed data not a hexadecimal string' + assert is_hex_str(hexstr), 'seed data not a hexadecimal string' return self.frombytes(bytes.fromhex(hexstr), pad=pad, tostr=tostr) def frombytes(self, seed_bytes, pad=None, tostr=False): - assert tostr is False,"'tostr' must be False for 'bip39'" - assert pad in (None,'seed'), f"{pad}: invalid 'pad' argument (must be None or 'seed')" + assert tostr is False, "'tostr' must be False for 'bip39'" + assert pad in (None, 'seed'), f"{pad}: invalid 'pad' argument (must be None or 'seed')" wl = self.digits bitlen = len(seed_bytes) * 8 @@ -121,14 +121,14 @@ class bip39(baseconv): chk_hex = sha256(seed_bytes).hexdigest() - seed_bin = f'{int(seed_bytes.hex(),16):0{bitlen}b}' - chk_bin = f'{int(chk_hex,16):0256b}' + seed_bin = f'{int(seed_bytes.hex(), 16):0{bitlen}b}' + chk_bin = f'{int(chk_hex, 16):0256b}' res = seed_bin + chk_bin - return tuple(wl[int(res[i*11:(i+1)*11],2)] for i in range(c.mn_len)) + return tuple(wl[int(res[i*11:(i+1)*11], 2)] for i in range(c.mn_len)) - def generate_seed(self,words_arg,passwd=''): + def generate_seed(self, words_arg, passwd=''): self.tohex(words_arg) # validate diff --git a/mmgen/cfg.py b/mmgen/cfg.py index e9285d73..abffb64d 100755 --- a/mmgen/cfg.py +++ b/mmgen/cfg.py @@ -20,15 +20,15 @@ cfg: Configuration classes for the MMGen suite """ -import sys,os +import sys, os from collections import namedtuple from .base_obj import Lockable -def die(*args,**kwargs): +def die(*args, **kwargs): from .util import die - die(*args,**kwargs) + die(*args, **kwargs) -def die2(exit_val,s): +def die2(exit_val, s): sys.stderr.write(s+'\n') sys.exit(exit_val) @@ -86,16 +86,16 @@ class GlobalConstants(Lockable): cmd_caps = cmd_caps_data.get(prog_id) if sys.platform not in ('linux', 'win32', 'darwin'): - die2(1,f'{sys.platform!r}: platform not supported by {proj_name}') + die2(1, f'{sys.platform!r}: platform not supported by {proj_name}') if os.getenv('HOME'): # Linux, MSYS2, or macOS home_dir = os.getenv('HOME') elif sys.platform == 'win32': # Windows without MSYS2 - not supported - die2(1,f'$HOME not set! {proj_name} for Windows must be run in MSYS2 environment') + die2(1, f'$HOME not set! {proj_name} for Windows must be run in MSYS2 environment') else: - die2(2,'$HOME is not set! Unable to determine home directory') + die2(2, '$HOME is not set! Unable to determine home directory') - def get_mmgen_data_file(self,filename,package='mmgen'): + def get_mmgen_data_file(self, filename, package='mmgen'): """ this is an expensive import, so do only when required """ @@ -111,7 +111,7 @@ class GlobalConstants(Lockable): from importlib.resources import files # Python 3.9 and above except ImportError: from importlib_resources import files - return files(package).joinpath('data',filename).read_text() + return files(package).joinpath('data', filename).read_text() @property def version(self): @@ -144,7 +144,7 @@ class Config(Lockable): 3 - config file """ _autolock = False - _set_ok = ('usr_randchars','_proto') + _set_ok = ('usr_randchars', '_proto') _reset_ok = ('accept_defaults',) _delete_ok = ('_opts',) _use_class_attr = True @@ -210,7 +210,7 @@ class Config(Lockable): pager = False columns = 0 color = bool( - ( sys.stdout.isatty() and not os.getenv('MMGEN_TEST_SUITE_PEXPECT') ) or + (sys.stdout.isatty() and not os.getenv('MMGEN_TEST_SUITE_PEXPECT')) or os.getenv('MMGEN_TEST_SUITE_ENABLE_COLOR') ) @@ -256,7 +256,7 @@ class Config(Lockable): exit_after = '' resuming = False skipping_deps = False - test_datadir = os.path.join('test','data_dir' + ('','-α')[bool(os.getenv('MMGEN_DEBUG_UTF8'))]) + test_datadir = os.path.join('test', 'data_dir' + ('', '-α')[bool(os.getenv('MMGEN_DEBUG_UTF8'))]) mnemonic_entry_modes = {} @@ -273,12 +273,12 @@ class Config(Lockable): ) _incompatible_opts = ( - ('help','longhelp'), - ('bob','alice','carol'), - ('label','keep_label'), - ('tx_id','info'), - ('tx_id','terse_info'), - ('autosign','outdir'), + ('help', 'longhelp'), + ('bob', 'alice', 'carol'), + ('label', 'keep_label'), + ('tx_id', 'info'), + ('tx_id', 'terse_info'), + ('autosign', 'outdir'), ) _cfg_file_opts = ( @@ -319,7 +319,7 @@ class Config(Lockable): 'ltc_ignore_daemon_version', 'xmr_ignore_daemon_version', 'eth_mainnet_chain_names', - 'eth_testnet_chain_names' ) + 'eth_testnet_chain_names') # Supported environmental vars # The corresponding attributes (lowercase, without 'mmgen_') must exist in the class. @@ -377,10 +377,10 @@ class Config(Lockable): 'contract_data', ) # Auto-typechecked and auto-set opts - first value in list is the default - _ov = namedtuple('autoset_opt_info',['type','choices']) + _ov = namedtuple('autoset_opt_info', ['type', 'choices']) _autoset_opts = { - 'fee_estimate_mode': _ov('nocase_pfx', ['conservative','economical']), - 'rpc_backend': _ov('nocase_pfx', ['auto','httplib','curl','aiohttp','requests']), + 'fee_estimate_mode': _ov('nocase_pfx', ['conservative', 'economical']), + 'rpc_backend': _ov('nocase_pfx', ['auto', 'httplib', 'curl', 'aiohttp', 'requests']), } _auto_typeset_opts = { @@ -414,13 +414,13 @@ class Config(Lockable): """ location of mmgen.cfg """ - if not hasattr(self,'_data_dir_root'): + if not hasattr(self, '_data_dir_root'): if self._data_dir_root_override: self._data_dir_root = os.path.normpath(os.path.abspath(self._data_dir_root_override)) elif self.test_suite: self._data_dir_root = self.test_datadir else: - self._data_dir_root = os.path.join(gc.home_dir,'.'+gc.proj_name.lower()) + self._data_dir_root = os.path.join(gc.home_dir, '.'+gc.proj_name.lower()) return self._data_dir_root @property @@ -428,12 +428,12 @@ class Config(Lockable): """ location of wallet and other data - same as data_dir_root for mainnet """ - if not hasattr(self,'_data_dir'): + if not hasattr(self, '_data_dir'): self._data_dir = os.path.normpath(os.path.join(*{ 'regtest': (self.data_dir_root, 'regtest', (self.regtest_user or 'none')), 'testnet': (self.data_dir_root, 'testnet'), 'mainnet': (self.data_dir_root,), - }[self.network] )) + }[self.network])) return self._data_dir def __init__( @@ -457,7 +457,7 @@ class Config(Lockable): if opts_data or parsed_opts or process_opts: assert cfg is None, ( 'Config(): ‘cfg’ cannot be used simultaneously with ' + - '‘opts_data’, ‘parsed_opts’ or ‘process_opts’' ) + '‘opts_data’, ‘parsed_opts’ or ‘process_opts’') from .opts import UserOpts UserOpts( cfg = self, @@ -472,26 +472,26 @@ class Config(Lockable): self._uopts = {} else: if '_clone' in cfg: - assert isinstance( cfg['_clone'], Config ) + assert isinstance(cfg['_clone'], Config) self._cloned = cfg['_clone'].__dict__ - for k,v in self._cloned.items(): + for k, v in self._cloned.items(): if not k.startswith('_'): - setattr(self,k,v) + setattr(self, k, v) del cfg['_clone'] self._uopts = cfg self._uopt_desc = 'configuration option' self._data_dir_root_override = self._cloned.pop( '_data_dir_root_override', - self._uopts.pop('data_dir',None)) + self._uopts.pop('data_dir', None)) - if parse_only and not any(k in self._uopts for k in ['help','longhelp']): + if parse_only and not any(k in self._uopts for k in ['help', 'longhelp']): return # Step 2: set cfg from user-supplied data, skipping auto opts; set type from corresponding # class attribute, if it exists: auto_opts = tuple(self._autoset_opts) + tuple(self._auto_typeset_opts) - for key,val in self._uopts.items(): + for key, val in self._uopts.items(): assert key.isascii() and key.isidentifier() and key[0] != '_', ( f'{key!r}: malformed configuration option') assert key not in self._forbidden_opts, ( @@ -524,13 +524,13 @@ class Config(Lockable): # Step 4: set cfg from cfgfile, skipping already-set opts and auto opts; save set opts and auto # opts to be set: # requires ‘data_dir_root’, ‘test_suite_cfgtest’ - self._cfgfile_opts = self._set_cfg_from_cfg_file( self._envopts, need_proto ) + self._cfgfile_opts = self._set_cfg_from_cfg_file(self._envopts, need_proto) # Step 5: set autoset opts from user-supplied data, cfgfile data, or default values, in that order: - self._set_autoset_opts( self._cfgfile_opts.autoset ) + self._set_autoset_opts(self._cfgfile_opts.autoset) # Step 6: set auto typeset opts from user-supplied data or cfgfile data, in that order: - self._set_auto_typeset_opts( self._cfgfile_opts.auto_typeset ) + self._set_auto_typeset_opts(self._cfgfile_opts.auto_typeset) if self.regtest or self.bob or self.alice or self.carol or gc.prog_name == f'{gc.proj_id}-regtest': self.network = 'regtest' @@ -567,7 +567,7 @@ class Config(Lockable): if need_proto: from .protocol import init_proto_from_cfg, warn_trustlevel # requires the default-to-none behavior, so do after the lock: - self._proto = init_proto_from_cfg(self,need_amt=need_amt) + self._proto = init_proto_from_cfg(self, need_amt=need_amt) if self._opts and not caller_post_init: self._post_init() @@ -590,41 +590,38 @@ class Config(Lockable): sys.exit(1) # called only on bad invocation def _set_cfg_from_env(self): - for name,val in ((k,v) for k,v in os.environ.items() if k.startswith('MMGEN_')): + for name, val in ((k, v) for k, v in os.environ.items() if k.startswith('MMGEN_')): if name == 'MMGEN_DEBUG_ALL': continue if name in self._env_opts: if val: # ignore empty string values; string value of '0' or 'false' sets variable to False disable = name.startswith('MMGEN_DISABLE_') - gname = name[(6,14)[disable]:].lower() + gname = name[(6, 14)[disable]:].lower() if gname in self._uopts: # don’t touch attr if already set by user continue - if hasattr(self,gname): + if hasattr(self, gname): setattr( self, gname, - conv_type( name, val, getattr(self,gname), 'environment var', invert_bool=disable )) + conv_type(name, val, getattr(self, gname), 'environment var', invert_bool=disable)) yield gname else: raise ValueError(f'Name {gname!r} not present in globals') else: raise ValueError(f'{name!r} is not a valid MMGen environment variable') - def _set_cfg_from_cfg_file( - self, - env_cfg, - need_proto ): + def _set_cfg_from_cfg_file(self, env_cfg, need_proto): - _ret = namedtuple('cfgfile_opts',['non_auto','autoset','auto_typeset']) + _ret = namedtuple('cfgfile_opts', ['non_auto', 'autoset', 'auto_typeset']) if not self._use_cfg_file: - return _ret( (), {}, {} ) + return _ret((), {}, {}) # check for changes in system template file (term must be initialized) from .cfgfile import mmgen_cfg_file - mmgen_cfg_file(self,'sample') + mmgen_cfg_file(self, 'sample') - ucfg = mmgen_cfg_file(self,'usr') + ucfg = mmgen_cfg_file(self, 'usr') self._cfgfile_fn = ucfg.fn @@ -642,9 +639,9 @@ class Config(Lockable): if ns[0] in gc.core_coins: if not need_proto: continue - nse,tn = ( - (ns[2:],ns[1]=='testnet') if len(ns) > 2 and ns[1] in ('mainnet','testnet') else - (ns[1:],False) + nse, tn = ( + (ns[2:], ns[1]=='testnet') if len(ns) > 2 and ns[1] in ('mainnet', 'testnet') else + (ns[1:], False) ) # no instance yet, so override _class_ attr: cls = init_proto(self, ns[0], tn, need_amt=True, return_cls=True) @@ -652,26 +649,26 @@ class Config(Lockable): else: cls = self attr = d.name - refval = getattr(cls,attr) - val = ucfg.parse_value(d.value,refval) + refval = getattr(cls, attr) + val = ucfg.parse_value(d.value, refval) if not val: - die( 'CfgFileParseError', f'Parse error in file {ucfg.fn!r}, line {d.lineno}' ) - val_conv = conv_type( attr, val, refval, 'configuration file option', src=ucfg.fn ) + die('CfgFileParseError', f'Parse error in file {ucfg.fn!r}, line {d.lineno}') + val_conv = conv_type(attr, val, refval, 'configuration file option', src=ucfg.fn) if not attr in already_set: - setattr(cls,attr,val_conv) + setattr(cls, attr, val_conv) non_auto_opts.append(attr) elif d.name in self._autoset_opts: autoset_opts[d.name] = d.value elif d.name in self._auto_typeset_opts: auto_typeset_opts[d.name] = d.value else: - die( 'CfgFileParseError', f'{d.name!r}: unrecognized option in {ucfg.fn!r}, line {d.lineno}' ) + die('CfgFileParseError', f'{d.name!r}: unrecognized option in {ucfg.fn!r}, line {d.lineno}') - return _ret( tuple(non_auto_opts), autoset_opts, auto_typeset_opts ) + return _ret(tuple(non_auto_opts), autoset_opts, auto_typeset_opts) - def _set_autoset_opts(self,cfgfile_autoset_opts): + def _set_autoset_opts(self, cfgfile_autoset_opts): - def get_autoset_opt(key,val,src): + def get_autoset_opt(key, val, src): def die_on_err(desc): from .util import fmt_list @@ -680,11 +677,11 @@ class Config(Lockable): '{a!r}: invalid {b} (not {c}: {d})'.format( a = val, b = { - 'cmdline': f'parameter for option --{key.replace("_","-")}', + 'cmdline': f'parameter for option --{key.replace("_", "-")}', 'cfgfile': f'value for cfg file option {key!r}' }[src], c = desc, - d = fmt_list(data.choices) )) + d = fmt_list(data.choices))) class opt_type: @@ -703,7 +700,7 @@ class Config(Lockable): data = self._autoset_opts[key] - return getattr(opt_type,data.type)() + return getattr(opt_type, data.type)() # Check autoset opts, setting if unset for key in self._autoset_opts: @@ -711,27 +708,27 @@ class Config(Lockable): if key in self._cloned: continue - assert not hasattr(self,key), f'autoset opt {key!r} is already set, but it shouldn’t be!' + assert not hasattr(self, key), f'autoset opt {key!r} is already set, but it shouldn’t be!' if key in self._uopts: - val,src = (self._uopts[key],'cmdline') + val, src = (self._uopts[key], 'cmdline') elif key in cfgfile_autoset_opts: - val,src = (cfgfile_autoset_opts[key],'cfgfile') + val, src = (cfgfile_autoset_opts[key], 'cfgfile') else: val = None if val is None: setattr(self, key, self._autoset_opts[key].choices[0]) else: - setattr(self, key, get_autoset_opt(key,val,src=src)) + setattr(self, key, get_autoset_opt(key, val, src=src)) - def _set_auto_typeset_opts(self,cfgfile_auto_typeset_opts): + def _set_auto_typeset_opts(self, cfgfile_auto_typeset_opts): - def do_set(key,val,ref_type): - assert not hasattr(self,key), f'{key!r} is in cfg!' - setattr(self,key,None if val is None else ref_type(val)) + def do_set(key, val, ref_type): + assert not hasattr(self, key), f'{key!r} is in cfg!' + setattr(self, key, None if val is None else ref_type(val)) - for key,ref_type in self._auto_typeset_opts.items(): + for key, ref_type in self._auto_typeset_opts.items(): if key in self._uopts: do_set(key, self._uopts[key], ref_type) elif key in cfgfile_auto_typeset_opts: @@ -739,18 +736,18 @@ class Config(Lockable): def _die_on_incompatible_opts(self): for group in self._incompatible_opts: - bad = [k for k in self.__dict__ if k in group and getattr(self,k) is not None] + bad = [k for k in self.__dict__ if k in group and getattr(self, k) is not None] if len(bad) > 1: - die(1,'Conflicting options: {}'.format(', '.join(map(fmt_opt,bad)))) + die(1, 'Conflicting options: {}'.format(', '.join(map(fmt_opt, bad)))) - def _set_quiet(self,val): + def _set_quiet(self, val): from .util import Util self.__dict__['quiet'] = val self.__dict__['_util'] = Util(self) # qmsg, qmsg_r def check_opts(cfg): # Raises exception if any check fails - from .util import is_int,Msg + from .util import is_int, Msg def get_desc(desc_pfx=''): return ( @@ -760,18 +757,18 @@ def check_opts(cfg): # Raises exception if any check fails if name in cfg._uopts and 'command-line' in cfg._uopt_desc else f'value for configuration option {name!r}' ) - + ( ' from environment' if name in cfg._envopts else '') + + (' from environment' if name in cfg._envopts else '') + (f' in {cfg._cfgfile_fn!r}' if name in cfg._cfgfile_opts.non_auto else '') ) - def display_opt(name,val='',beg='For selected',end=':\n'): + def display_opt(name, val='', beg='For selected', end=':\n'): from .util import msg_r msg_r('{} option {!r}{}'.format( beg, f'{fmt_opt(name)}={val}' if val else fmt_opt(name), - end )) + end)) - def opt_compares(val,op_str,target): + def opt_compares(val, op_str, target): import operator if not { '<': operator.lt, @@ -779,24 +776,24 @@ def check_opts(cfg): # Raises exception if any check fails '>': operator.gt, '>=': operator.ge, '=': operator.eq, - }[op_str](val,target): - die( 'UserOptError', f'{val}: invalid {get_desc()} (not {op_str} {target})' ) + }[op_str](val, target): + die('UserOptError', f'{val}: invalid {get_desc()} (not {op_str} {target})') - def opt_is_int(val,desc_pfx=''): + def opt_is_int(val, desc_pfx=''): if not is_int(val): - die( 'UserOptError', f'{val!r}: invalid {get_desc(desc_pfx)} (not an integer)' ) + die('UserOptError', f'{val!r}: invalid {get_desc(desc_pfx)} (not an integer)') - def opt_is_in_list(val,tlist,desc_pfx=''): + def opt_is_in_list(val, tlist, desc_pfx=''): if val not in tlist: - q,sep = (('',','),("'","','"))[isinstance(tlist[0],str)] - die( 'UserOptError', '{q}{v}{q}: invalid {w}\nValid choices: {q}{o}{q}'.format( + q, sep = (('', ','), ("'", "','"))[isinstance(tlist[0], str)] + die('UserOptError', '{q}{v}{q}: invalid {w}\nValid choices: {q}{o}{q}'.format( v = val, w = get_desc(desc_pfx), q = q, - o = sep.join(map(str,sorted(tlist))) )) + o = sep.join(map(str, sorted(tlist))))) def opt_unrecognized(): - die( 'UserOptError', f'{val!r}: unrecognized {get_desc()}' ) + die('UserOptError', f'{val!r}: unrecognized {get_desc()}') class check_funcs: @@ -808,32 +805,32 @@ def check_opts(cfg): # Raises exception if any check fails if name == 'out_fmt': p = 'hidden_incog_output_params' - if wd.type == 'incog_hidden' and not getattr(cfg,p): - die( 'UserOptError', + if wd.type == 'incog_hidden' and not getattr(cfg, p): + die('UserOptError', 'Hidden incog format output requested. ' + - f'You must supply a file and offset with the {fmt_opt(p)!r} option' ) + f'You must supply a file and offset with the {fmt_opt(p)!r} option') if wd.base_type == 'incog_base' and cfg.old_incog_fmt: - display_opt(name,val,beg='Selected',end=' ') - display_opt('old_incog_fmt',beg='conflicts with',end=':\n') - die( 'UserOptError', 'Export to old incog wallet format unsupported' ) + display_opt(name, val, beg='Selected', end=' ') + display_opt('old_incog_fmt', beg='conflicts with', end=':\n') + die('UserOptError', 'Export to old incog wallet format unsupported') elif wd.type == 'brain': - die( 'UserOptError', 'Output to brainwallet format unsupported' ) + die('UserOptError', 'Output to brainwallet format unsupported') out_fmt = in_fmt def hidden_incog_input_params(): - a = val.rsplit(',',1) # permit comma in filename + a = val.rsplit(',', 1) # permit comma in filename if len(a) != 2: - display_opt(name,val) - die( 'UserOptError', 'Option requires two comma-separated arguments' ) + display_opt(name, val) + die('UserOptError', 'Option requires two comma-separated arguments') - fn,offset = a + fn, offset = a opt_is_int(offset) - from .fileutil import check_infile,check_outdir,check_outfile + from .fileutil import check_infile, check_outdir, check_outfile if name == 'hidden_incog_input_params': - check_infile(fn,blkdev_ok=True) + check_infile(fn, blkdev_ok=True) key2 = 'in_fmt' else: try: @@ -843,52 +840,52 @@ def check_opts(cfg): # Raises exception if any check fails if b: check_outdir(b) else: - check_outfile(fn,blkdev_ok=True) + check_outfile(fn, blkdev_ok=True) key2 = 'out_fmt' - if hasattr(cfg,key2): - val2 = getattr(cfg,key2) + if hasattr(cfg, key2): + val2 = getattr(cfg, key2) from .wallet import get_wallet_data wd = get_wallet_data('incog_hidden') if val2 and val2 not in wd.fmt_codes: - die( 'UserOptError', f'Option conflict:\n {fmt_opt(name)}, with\n {fmt_opt(key2)}={val2}' ) + die('UserOptError', f'Option conflict:\n {fmt_opt(name)}, with\n {fmt_opt(key2)}={val2}') hidden_incog_output_params = hidden_incog_input_params def subseeds(): from .subseed import SubSeedIdxRange - opt_compares(val,'>=',SubSeedIdxRange.min_idx) - opt_compares(val,'<=',SubSeedIdxRange.max_idx) + opt_compares(val, '>=', SubSeedIdxRange.min_idx) + opt_compares(val, '<=', SubSeedIdxRange.max_idx) def seed_len(): from .seed import Seed - opt_is_in_list(int(val),Seed.lens) + opt_is_in_list(int(val), Seed.lens) def hash_preset(): from .crypto import Crypto - opt_is_in_list(val,list(Crypto.hash_presets.keys())) + opt_is_in_list(val, list(Crypto.hash_presets.keys())) def brain_params(): a = val.split(',') if len(a) != 2: - display_opt(name,val) - die( 'UserOptError', 'Option requires two comma-separated arguments' ) + display_opt(name, val) + die('UserOptError', 'Option requires two comma-separated arguments') - opt_is_int( a[0], desc_pfx='seed length' ) + opt_is_int(a[0], desc_pfx='seed length') from .seed import Seed - opt_is_in_list( int(a[0]), Seed.lens, desc_pfx='seed length' ) + opt_is_in_list(int(a[0]), Seed.lens, desc_pfx='seed length') from .crypto import Crypto - opt_is_in_list( a[1], list(Crypto.hash_presets.keys()), desc_pfx='hash preset' ) + opt_is_in_list(a[1], list(Crypto.hash_presets.keys()), desc_pfx='hash preset') def usr_randchars(): if val != 0: - opt_compares(val,'>=',cfg.min_urandchars) - opt_compares(val,'<=',cfg.max_urandchars) + opt_compares(val, '>=', cfg.min_urandchars) + opt_compares(val, '<=', cfg.max_urandchars) def tx_confs(): opt_is_int(val) - opt_compares(int(val),'>=',1) + opt_compares(int(val), '>=', 1) def vsize_adj(): from .util import ymsg @@ -896,19 +893,19 @@ def check_opts(cfg): # Raises exception if any check fails def daemon_id(): from .daemon import CoinDaemon - opt_is_in_list(val,CoinDaemon.all_daemon_ids()) + opt_is_in_list(val, CoinDaemon.all_daemon_ids()) def locktime(): opt_is_int(val) - opt_compares(int(val),'>',0) + opt_compares(int(val), '>', 0) def columns(): - opt_compares(val,'>',10) + opt_compares(val, '>', 10) # TODO: add checks for token, rbf, tx_fee check_funcs_names = tuple(check_funcs.__dict__) for name in tuple(cfg._uopts) + cfg._envopts + cfg._cfgfile_opts.non_auto: - val = getattr(cfg,name) + val = getattr(cfg, name) if name in cfg._infile_opts: from .fileutil import check_infile check_infile(val) # file exists and is readable - dies on error @@ -916,19 +913,19 @@ def check_opts(cfg): # Raises exception if any check fails from .fileutil import check_outdir check_outdir(val) # dies on error elif name in check_funcs_names: - getattr(check_funcs,name)() + getattr(check_funcs, name)() elif cfg.debug: Msg(f'check_opts(): No test for config opt {name!r}') def fmt_opt(o): - return '--' + o.replace('_','-') + return '--' + o.replace('_', '-') def opt_postproc_debug(cfg): - none_opts = [k for k in dir(cfg) if k[:2] != '__' and getattr(cfg,k) is None] + none_opts = [k for k in dir(cfg) if k[:2] != '__' and getattr(cfg, k) is None] from .util import Msg Msg('\n Configuration opts:') for e in [d for d in dir(cfg) if d[:2] != '__']: - Msg(f' {e:<20}: {getattr(cfg,e)}') + Msg(f' {e:<20}: {getattr(cfg, e)}') Msg(" Configuration opts set to 'None':") Msg(' {}\n'.format('\n '.join(none_opts))) Msg('\n=== end opts.py debug ===\n') @@ -939,21 +936,21 @@ def conv_type( refval, desc, invert_bool = False, - src = None ): + src = None): def do_fail(): - die(1,'{a!r}: invalid value for {b} {c!r}{d} (must be of type {e!r})'.format( + die(1, '{a!r}: invalid value for {b} {c!r}{d} (must be of type {e!r})'.format( a = val, b = desc, c = fmt_opt(name) if 'command-line' in desc else name, d = f' in {src!r}' if src else '', - e = type(refval).__name__ )) + e = type(refval).__name__)) if type(refval) is bool: v = str(val).lower() ret = ( - True if v in ('true','yes','1','on') else - False if v in ('false','no','none','0','off','') else + True if v in ('true', 'yes', '1', 'on') else + False if v in ('false', 'no', 'none', '0', 'off', '') else None ) return do_fail() if ret is None else (not ret) if invert_bool else ret diff --git a/mmgen/cfgfile.py b/mmgen/cfgfile.py index 80c629a8..f146bd55 100755 --- a/mmgen/cfgfile.py +++ b/mmgen/cfgfile.py @@ -20,13 +20,13 @@ cfgfile: API for the MMGen runtime configuration file and related files """ -import os,re +import os, re from collections import namedtuple from .cfg import gc -from .util import msg,ymsg,suf,fmt,fmt_list,oneshot_warning,strip_comment,capfirst,die +from .util import msg, ymsg, suf, fmt, fmt_list, oneshot_warning, strip_comment, capfirst, die -def mmgen_cfg_file(cfg,id_str): +def mmgen_cfg_file(cfg, id_str): return cfg_file.get_cls_by_id(id_str)(cfg) class cfg_file: @@ -35,62 +35,62 @@ class cfg_file: write_ok = False warn_missing = True write_metadata = False - line_data = namedtuple('cfgfile_line',['name','value','lineno','chunk']) + line_data = namedtuple('cfgfile_line', ['name', 'value', 'lineno', 'chunk']) fn_base = 'mmgen.cfg' class warn_missing_file(oneshot_warning): color = 'yellow' # has no effect, as color not initialized yet message = '{} not found at {!r}' - def get_data(self,fn): + def get_data(self, fn): try: with open(fn) as fp: return fp.read().splitlines() except: if self.warn_missing: - self.warn_missing_file( div=fn, fmt_args=(self.desc,fn) ) + self.warn_missing_file(div=fn, fmt_args=(self.desc, fn)) return '' - def copy_system_data(self,fn): + def copy_system_data(self, fn): assert self.write_ok, f'writing to file {fn!r} not allowed!' - src = mmgen_cfg_file(self.cfg,'sys') + src = mmgen_cfg_file(self.cfg, 'sys') if src.data: data = src.data + src.make_metadata() if self.write_metadata else src.data try: - with open(fn,'w') as fp: + with open(fn, 'w') as fp: fp.write('\n'.join(data)+'\n') - os.chmod(fn,0o600) + os.chmod(fn, 0o600) except: - die(2,f'ERROR: unable to write to {fn!r}') + die(2, f'ERROR: unable to write to {fn!r}') - def parse_value(self,value,refval): - if isinstance(refval,dict): - m = re.fullmatch(r'((\s+\w+:\S+)+)',' '+value) # expect one or more colon-separated values + def parse_value(self, value, refval): + if isinstance(refval, dict): + m = re.fullmatch(r'((\s+\w+:\S+)+)', ' '+value) # expect one or more colon-separated values if m: return dict([i.split(':') for i in m[1].split()]) - elif isinstance(refval,(list,tuple)): - m = re.fullmatch(r'((\s+\S+)+)',' '+value) # expect single value or list + elif isinstance(refval, (list, tuple)): + m = re.fullmatch(r'((\s+\S+)+)', ' '+value) # expect single value or list if m: ret = m[1].split() - return ret if isinstance(refval,list) else tuple(ret) + return ret if isinstance(refval, list) else tuple(ret) else: return value def get_lines(self): def gen_lines(): - for lineno,line in enumerate(self.data,1): + for lineno, line in enumerate(self.data, 1): line = strip_comment(line) if line == '': continue - m = re.fullmatch(r'(\w+)(\s+)(.*)',line) + m = re.fullmatch(r'(\w+)(\s+)(.*)', line) if m: - yield self.line_data(m[1],m[3],lineno,None) + yield self.line_data(m[1], m[3], lineno, None) else: - die( 'CfgFileParseError', f'Parse error in file {self.fn!r}, line {lineno}' ) + die('CfgFileParseError', f'Parse error in file {self.fn!r}, line {lineno}') return gen_lines() @classmethod - def get_cls_by_id(cls,id_str): + def get_cls_by_id(cls, id_str): d = { 'usr': CfgFileUsr, 'sys': CfgFileSampleSys, @@ -101,13 +101,13 @@ class cfg_file: class cfg_file_sample(cfg_file): @classmethod - def cls_make_metadata(cls,data): + def cls_make_metadata(cls, data): return [f'# Version {cls.cur_ver} {cls.compute_chksum(data)}'] @staticmethod def compute_chksum(data): import hashlib - return hashlib.new('ripemd160','\n'.join(data).encode()).hexdigest() + return hashlib.new('ripemd160', '\n'.join(data).encode()).hexdigest() @property def computed_chksum(self): @@ -124,19 +124,19 @@ class cfg_file_sample(cfg_file): - last line is metadata line of the form '# Version VER_NUM HASH' """ - def process_chunk(chunk,lineno): - m = re.fullmatch(r'(#\s*)(\w+)(\s+)(.*)',chunk[-1]) + def process_chunk(chunk, lineno): + m = re.fullmatch(r'(#\s*)(\w+)(\s+)(.*)', chunk[-1]) if m: - return self.line_data(m[2],m[4],lineno,chunk) + return self.line_data(m[2], m[4], lineno, chunk) else: - die( 'CfgFileParseError', f'Parse error in file {self.fn!r}, line {lineno}' ) + die('CfgFileParseError', f'Parse error in file {self.fn!r}, line {lineno}') def gen_chunks(lines): hdr = True chunk = [] in_chunk = False - for lineno,line in enumerate(lines,1): + for lineno, line in enumerate(lines, 1): if line.startswith('##'): hdr = False @@ -150,17 +150,17 @@ class cfg_file_sample(cfg_file): elif line.startswith('#'): if in_chunk is False: if chunk: - yield process_chunk(chunk,last_nonblank) + yield process_chunk(chunk, last_nonblank) chunk = [line] in_chunk = True else: chunk.append(line) last_nonblank = lineno else: - die( 'CfgFileParseError', f'Parse error in file {self.fn!r}, line {lineno}' ) + die('CfgFileParseError', f'Parse error in file {self.fn!r}, line {lineno}') if chunk: - yield process_chunk(chunk,last_nonblank) + yield process_chunk(chunk, last_nonblank) return list(gen_chunks(self.data)) @@ -169,9 +169,9 @@ class CfgFileUsr(cfg_file): warn_missing = False write_ok = True - def __init__(self,cfg): + def __init__(self, cfg): self.cfg = cfg - self.fn = os.path.join(cfg.data_dir_root,self.fn_base) + self.fn = os.path.join(cfg.data_dir_root, self.fn_base) self.data = self.get_data(self.fn) if not self.data: self.copy_system_data(self.fn) @@ -180,15 +180,15 @@ class CfgFileSampleSys(cfg_file_sample): desc = 'system sample configuration file' test_fn_subdir = 'usr.local.share' - def __init__(self,cfg): + def __init__(self, cfg): self.cfg = cfg if self.cfg.test_suite_cfgtest: - self.fn = os.path.join(cfg.data_dir_root,self.test_fn_subdir,self.fn_base) + self.fn = os.path.join(cfg.data_dir_root, self.test_fn_subdir, self.fn_base) with open(self.fn) as fp: self.data = fp.read().splitlines() else: # self.fn is used for error msgs only, so file need not exist on filesystem - self.fn = os.path.join(os.path.dirname(__file__),'data',self.fn_base) + self.fn = os.path.join(os.path.dirname(__file__), 'data', self.fn_base) self.data = gc.get_mmgen_data_file(self.fn_base).splitlines() def make_metadata(self): @@ -204,12 +204,12 @@ class CfgFileSampleUsr(cfg_file_sample): out_of_date_fs = 'File {!r} is out of date - replacing' altered_by_user_fs = 'File {!r} was altered by user - replacing' - def __init__(self,cfg): + def __init__(self, cfg): self.cfg = cfg - self.fn = os.path.join(cfg.data_dir_root,f'{self.fn_base}.sample') + self.fn = os.path.join(cfg.data_dir_root, f'{self.fn_base}.sample') self.data = self.get_data(self.fn) - src = mmgen_cfg_file(cfg,'sys') + src = mmgen_cfg_file(cfg, 'sys') if not src.data: return @@ -217,7 +217,7 @@ class CfgFileSampleUsr(cfg_file_sample): if self.data: if self.parse_metadata(): if self.chksum == self.computed_chksum: - diff = self.diff(self.get_lines(),src.get_lines()) + diff = self.diff(self.get_lines(), src.get_lines()) if not diff: return self.show_changes(diff) @@ -230,14 +230,14 @@ class CfgFileSampleUsr(cfg_file_sample): def parse_metadata(self): if self.data: - m = re.match(r'# Version (\d+) ([a-f0-9]{40})$',self.data[-1]) + m = re.match(r'# Version (\d+) ([a-f0-9]{40})$', self.data[-1]) if m: self.ver = m[1] self.chksum = m[2] self.data = self.data[:-1] # remove metadata line return True - def diff(self,a_tup,b_tup): # a=user, b=system + def diff(self, a_tup, b_tup): # a=user, b=system a = [i.name for i in a_tup]#[3:] # Debug b = [i.name for i in b_tup]#[:-2] # Debug removed = set(a) - set(b) @@ -250,34 +250,34 @@ class CfgFileSampleUsr(cfg_file_sample): else: return None - def show_changes(self,diff): + def show_changes(self, diff): ymsg('Warning: configuration file options have changed!\n') - for desc in ('added','removed'): + for desc in ('added', 'removed'): data = diff[desc] if data: - opts = fmt_list([i.name for i in data],fmt='bare') - msg(f' The following option{suf(data,verb="has")} been {desc}:\n {opts}\n') + opts = fmt_list([i.name for i in data], fmt='bare') + msg(f' The following option{suf(data, verb="has")} been {desc}:\n {opts}\n') if desc == 'removed' and data: - uc = mmgen_cfg_file(self.cfg,'usr') + uc = mmgen_cfg_file(self.cfg, 'usr') usr_names = [i.name for i in uc.get_lines()] rm_names = [i.name for i in data] bad = sorted(set(usr_names).intersection(rm_names)) if bad: m = f""" - The following removed option{suf(bad,verb='is')} set in {uc.fn!r} + The following removed option{suf(bad, verb='is')} set in {uc.fn!r} and must be deleted or commented out: - {' ' + fmt_list(bad,fmt='bare')} + {' ' + fmt_list(bad, fmt='bare')} """ - ymsg(fmt(m,indent=' ',strip_char='\t')) + ymsg(fmt(m, indent=' ', strip_char='\t')) - from .ui import keypress_confirm,do_pager + from .ui import keypress_confirm, do_pager while True: - if not keypress_confirm( self.cfg, self.details_confirm_prompt, no_nl=True ): + if not keypress_confirm(self.cfg, self.details_confirm_prompt, no_nl=True): return def get_details(): - for desc,data in diff.items(): - sep,sep2 = ('\n ','\n\n ') + for desc, data in diff.items(): + sep, sep2 = ('\n ', '\n\n ') if data: yield ( f'{capfirst(desc)} section{suf(data)}:' diff --git a/mmgen/color.py b/mmgen/color.py index 1bc4ad71..f29da5e2 100755 --- a/mmgen/color.py +++ b/mmgen/color.py @@ -21,27 +21,27 @@ color: color handling for the MMGen suite """ _colors = { - 'black': ( 232, (30,0) ), - 'red': ( 210, (31,1) ), - 'green': ( 121, (32,1) ), - 'yellow': ( 229, (33,1) ), - 'blue': ( 75, (34,1) ), - 'magenta': ( 205, (35,1) ), - 'cyan': ( 122, (36,1) ), + 'black': (232, (30, 0)), + 'red': (210, (31, 1)), + 'green': (121, (32, 1)), + 'yellow': (229, (33, 1)), + 'blue': (75, (34, 1)), + 'magenta': (205, (35, 1)), + 'cyan': (122, (36, 1)), - 'gray': ( 246, (30,1) ), - 'orange': ( 216, (31,1) ), - 'purple': ( 141, (35,1) ), - 'pink': ( 218, (35,1) ), + 'gray': (246, (30, 1)), + 'orange': (216, (31, 1)), + 'purple': (141, (35, 1)), + 'pink': (218, (35, 1)), - 'melon': ( 222, (33,1) ), - 'brown': ( 173, (33,0) ), - 'grndim': ( 108, (32,0) ), + 'melon': (222, (33, 1)), + 'brown': (173, (33, 0)), + 'grndim': (108, (32, 0)), - 'redbg': ( (232,210), (30,101) ), - 'grnbg': ( (232,121), (30,102) ), - 'yelbg': ( (232,229), (30,103) ), - 'blubg': ( (232,75), (30,104) ), + 'redbg': ((232, 210), (30, 101)), + 'grnbg': ((232, 121), (30, 102)), + 'yelbg': ((232, 229), (30, 103)), + 'blubg': ((232, 75), (30, 104)), } def nocolor(s): @@ -52,16 +52,16 @@ def set_vt100(): import sys if sys.platform == 'win32': from subprocess import run - run([],shell=True) + run([], shell=True) def get_terminfo_colors(term=None): - from subprocess import run,PIPE - cmd = ['infocmp','-0'] + from subprocess import run, PIPE + cmd = ['infocmp', '-0'] if term: cmd.append(term) try: - cmdout = run(cmd,stdout=PIPE,check=True).stdout.decode() + cmdout = run(cmd, stdout=PIPE, check=True).stdout.decode() except: set_vt100() return None @@ -72,12 +72,12 @@ def get_terminfo_colors(term=None): if s.isdecimal(): return int(s) elif s.startswith('0x') and is_hex_str(s[2:]): - return int(s[2:],16) + return int(s[2:], 16) else: return None def init_color(num_colors='auto'): - assert num_colors in ('auto',8,16,256,0) + assert num_colors in ('auto', 8, 16, 256, 0) import sys self = sys.modules[__name__] @@ -98,19 +98,19 @@ def init_color(num_colors='auto'): if num_colors == 0: ncc = (lambda s: s).__code__ for c in _colors: - getattr(self,c).__code__ = ncc + getattr(self, c).__code__ = ncc elif num_colors == 256: - for c,e in _colors.items(): + for c, e in _colors.items(): start = ( '\033[38;5;{};1m'.format(e[0]) if type(e[0]) == int else - '\033[38;5;{};48;5;{};1m'.format(*e[0]) ) - getattr(self,c).__code__ = eval(f'(lambda s: "{start}" + s + "{reset}").__code__') - elif num_colors in (8,16): - for c,e in _colors.items(): + '\033[38;5;{};48;5;{};1m'.format(*e[0])) + getattr(self, c).__code__ = eval(f'(lambda s: "{start}" + s + "{reset}").__code__') + elif num_colors in (8, 16): + for c, e in _colors.items(): start = ( '\033[{}m'.format(e[1][0]) if e[1][1] == 0 else - '\033[{};{}m'.format(*e[1]) ) - getattr(self,c).__code__ = eval(f'(lambda s: "{start}" + s + "{reset}").__code__') + '\033[{};{}m'.format(*e[1])) + getattr(self, c).__code__ = eval(f'(lambda s: "{start}" + s + "{reset}").__code__') set_vt100() diff --git a/mmgen/crypto.py b/mmgen/crypto.py index a98aa6f0..1e20fcbf 100755 --- a/mmgen/crypto.py +++ b/mmgen/crypto.py @@ -24,15 +24,7 @@ import os from collections import namedtuple from .cfg import gc -from .util import ( - msg, - msg_r, - ymsg, - fmt, - die, - make_chksum_8, - oneshot_warning, -) +from .util import msg, msg_r, ymsg, fmt, die, make_chksum_8, oneshot_warning class Crypto: @@ -41,7 +33,7 @@ class Crypto: salt_len = 16 aesctr_iv_len = 16 - aesctr_dfl_iv = int.to_bytes(1,aesctr_iv_len,'big') + aesctr_dfl_iv = int.to_bytes(1, aesctr_iv_len, 'big') hincog_chk_len = 8 mmenc_salt_len = 32 @@ -50,7 +42,7 @@ class Crypto: # Scrypt params: 'id_num': [N, r, p] (N is an exponent of two) # NB: hashlib.scrypt in Python (>=v3.6) supports max N value of 14. This means that # for hash presets > 3 the standalone scrypt library must be used! - _hp = namedtuple('scrypt_preset',['N','r','p']) + _hp = namedtuple('scrypt_preset', ['N', 'r', 'p']) hash_presets = { '1': _hp(12, 8, 1), '2': _hp(13, 8, 4), @@ -63,60 +55,60 @@ class Crypto: class pwfile_reuse_warning(oneshot_warning): message = 'Reusing passphrase from file {!r} at user request' - def __init__(self,fn): - oneshot_warning.__init__(self,div=fn,fmt_args=[fn],reverse=True) + def __init__(self, fn): + oneshot_warning.__init__(self, div=fn, fmt_args=[fn], reverse=True) - def pwfile_used(self,passwd_file): - if hasattr(self,'_pwfile_used'): + def pwfile_used(self, passwd_file): + if hasattr(self, '_pwfile_used'): self.pwfile_reuse_warning(passwd_file) return True else: self._pwfile_used = True return False - def __init__(self,cfg): + def __init__(self, cfg): self.cfg = cfg self.util = cfg._util - def get_hash_params(self,hash_preset): + def get_hash_params(self, hash_preset): if hash_preset in self.hash_presets: - return self.hash_presets[hash_preset] # N,r,p + return self.hash_presets[hash_preset] # N, r, p else: # Shouldn't be here - die(3,f"{hash_preset}: invalid 'hash_preset' value") + die(3, f"{hash_preset}: invalid 'hash_preset' value") - def sha256_rounds(self,s): + def sha256_rounds(self, s): from hashlib import sha256 for _ in range(self.scramble_hash_rounds): s = sha256(s).digest() return s - def scramble_seed(self,seed,scramble_key): + def scramble_seed(self, seed, scramble_key): import hmac - step1 = hmac.digest(seed,scramble_key,'sha256') + step1 = hmac.digest(seed, scramble_key, 'sha256') if self.cfg.debug: msg(f'Seed: {seed.hex()!r}\nScramble key: {scramble_key}\nScrambled seed: {step1.hex()}\n') return self.sha256_rounds(step1) - def encrypt_seed(self,data,key,desc='seed'): - return self.encrypt_data(data,key,desc=desc) + def encrypt_seed(self, data, key, desc='seed'): + return self.encrypt_data(data, key, desc=desc) - def decrypt_seed(self,enc_seed,key,seed_id,key_id): + def decrypt_seed(self, enc_seed, key, seed_id, key_id): self.util.vmsg_r('Checking key...') chk1 = make_chksum_8(key) if key_id: - if not self.util.compare_chksums(key_id,'key ID',chk1,'computed'): + if not self.util.compare_chksums(key_id, 'key ID', chk1, 'computed'): msg('Incorrect passphrase or hash preset') return False - dec_seed = self.decrypt_data(enc_seed,key,desc='seed') + dec_seed = self.decrypt_data(enc_seed, key, desc='seed') chk2 = make_chksum_8(dec_seed) if seed_id: - if self.util.compare_chksums(seed_id,'Seed ID',chk2,'decrypted seed'): + if self.util.compare_chksums(seed_id, 'Seed ID', chk2, 'decrypted seed'): self.util.qmsg('Passphrase is OK') else: if not self.cfg.debug: msg_r('Checking key ID...') - if self.util.compare_chksums(key_id,'key ID',chk1,'computed'): + if self.util.compare_chksums(key_id, 'key ID', chk1, 'computed'): msg('Key ID is correct but decryption of seed failed') else: msg('Incorrect passphrase or hash preset') @@ -130,26 +122,26 @@ class Crypto: self, data, key, - iv = aesctr_dfl_iv, - desc = 'data', + iv = aesctr_dfl_iv, + desc = 'data', verify = True, - silent = False ): + silent = False): - from cryptography.hazmat.primitives.ciphers import Cipher,algorithms,modes + from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.backends import default_backend if not silent: self.util.vmsg(f'Encrypting {desc}') - c = Cipher(algorithms.AES(key),modes.CTR(iv),backend=default_backend()) + c = Cipher(algorithms.AES(key), modes.CTR(iv), backend=default_backend()) encryptor = c.encryptor() enc_data = encryptor.update(data) + encryptor.finalize() if verify: self.util.vmsg_r(f'Performing a test decryption of the {desc}...') - c = Cipher(algorithms.AES(key),modes.CTR(iv),backend=default_backend()) + c = Cipher(algorithms.AES(key), modes.CTR(iv), backend=default_backend()) encryptor = c.encryptor() dec_data = encryptor.update(enc_data) + encryptor.finalize() if dec_data != data: - die(2,f'ERROR.\nDecrypted {desc} doesn’t match original {desc}') + die(2, f'ERROR.\nDecrypted {desc} doesn’t match original {desc}') if not silent: self.util.vmsg('done') @@ -159,13 +151,13 @@ class Crypto: self, enc_data, key, - iv = aesctr_dfl_iv, - desc = 'data' ): + iv = aesctr_dfl_iv, + desc = 'data'): - from cryptography.hazmat.primitives.ciphers import Cipher,algorithms,modes + from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.backends import default_backend self.util.vmsg_r(f'Decrypting {desc} with key...') - c = Cipher(algorithms.AES(key),modes.CTR(iv),backend=default_backend()) + c = Cipher(algorithms.AES(key), modes.CTR(iv), backend=default_backend()) encryptor = c.encryptor() return encryptor.update(enc_data) + encryptor.finalize() @@ -174,13 +166,13 @@ class Crypto: passwd, salt, hash_preset, - buflen = 32 ): + buflen = 32): # Buflen arg is for brainwallets only, which use this function to generate # the seed directly. ps = self.get_hash_params(hash_preset) - if isinstance(passwd,str): + if isinstance(passwd, str): passwd = passwd.encode() def do_hashlib_scrypt(): @@ -192,7 +184,7 @@ class Crypto: r = ps.r, p = ps.p, maxmem = 0, - dklen = buflen ) + dklen = buflen) def do_standalone_scrypt(): import scrypt @@ -202,7 +194,7 @@ class Crypto: N = 2**ps.N, r = ps.r, p = ps.p, - buflen = buflen ) + buflen = buflen) if int(hash_preset) > 3: msg_r('Hashing passphrase, please wait...') @@ -210,7 +202,7 @@ class Crypto: # hashlib.scrypt doesn't support N > 14 (hash preset > 3) ret = ( do_standalone_scrypt() if ps.N > 14 or self.cfg.force_standalone_scrypt_module else - do_hashlib_scrypt() ) + do_hashlib_scrypt()) if int(hash_preset) > 3: msg_r('\b'*34 + ' '*34 + '\b'*34) @@ -222,19 +214,19 @@ class Crypto: passwd, salt, hash_preset, - desc = 'encryption key', + desc = 'encryption key', from_what = 'passphrase', - verbose = False ): + verbose = False): if self.cfg.verbose or verbose: msg_r(f"Generating {desc}{' from ' + from_what if from_what else ''}...") - key = self.scrypt_hash_passphrase(passwd,salt,hash_preset) + key = self.scrypt_hash_passphrase(passwd, salt, hash_preset) if self.cfg.verbose or verbose: msg('done') self.util.dmsg(f'Key: {key.hex()}') return key - def _get_random_data_from_user(self,uchars=None,desc='data'): + def _get_random_data_from_user(self, uchars=None, desc='data'): if uchars is None: uchars = self.cfg.usr_randchars @@ -260,8 +252,8 @@ class Crypto: """ msg(f'Enter {uchars} random symbols' if self.cfg.quiet else - '\n' + fmt(info1,indent=' ') + - '\n' + fmt(info2) ) + '\n' + fmt(info1, indent=' ') + + '\n' + fmt(info2)) import time from .term import get_char_raw @@ -272,7 +264,7 @@ class Crypto: key_data += get_char_raw(f'\rYou may begin typing. {uchars-i} symbols left: ') time_data.append(time.time()) - msg_r( '\r' if self.cfg.quiet else f'\rThank you. That’s enough.{" "*18}\n\n' ) + msg_r('\r' if self.cfg.quiet else f'\rThank you. That’s enough.{" "*18}\n\n') time_data = [f'{t:.22f}'.rstrip('0') for t in time_data] @@ -287,24 +279,24 @@ class Crypto: msg(f'USER ENTROPY (user input + keystroke timings):\n{ret}') from .ui import line_input - line_input( self.cfg, 'User random data successfully acquired. Press ENTER to continue: ' ) + line_input(self.cfg, 'User random data successfully acquired. Press ENTER to continue: ') return ret.encode() - def get_random(self,length): + def get_random(self, length): os_rand = os.urandom(length) assert len(os_rand) == length, f'OS random number generator returned {len(os_rand)} (!= {length}) bytes!' return self.add_user_random( rand_bytes = os_rand, - desc = 'from your operating system' ) + desc = 'from your operating system') def add_user_random( self, rand_bytes, desc, - urand = {'data':b'', 'counter':0} ): + urand = {'data':b'', 'counter':0}): assert type(rand_bytes) is bytes, 'add_user_random_chk1' @@ -323,12 +315,12 @@ class Crypto: import hmac key = hmac.digest( urand['data'], - os_rand + int.to_bytes(urand['counter'],8,'big'), - 'sha256' ) + os_rand + int.to_bytes(urand['counter'], 8, 'big'), + 'sha256') msg(f'Encrypting random data {desc} with ephemeral key #{urand["counter"]}') - return self.encrypt_data( data=rand_bytes, key=key, desc=desc, verify=False, silent=True ) + return self.encrypt_data(data=rand_bytes, key=key, desc=desc, verify=False, silent=True) else: return rand_bytes @@ -336,15 +328,15 @@ class Crypto: self, old_preset = gc.dfl_hash_preset, data_desc = 'data', - prompt = None ): + prompt = None): prompt = prompt or ( - f'Enter hash preset for {data_desc},\n' + - f'or hit ENTER to accept the default value ({old_preset!r}): ' ) + f'Enter hash preset for {data_desc}, \n' + + f'or hit ENTER to accept the default value ({old_preset!r}): ') from .ui import line_input while True: - ret = line_input( self.cfg, prompt ) + ret = line_input(self.cfg, prompt) if ret: if ret in self.hash_presets: return ret @@ -353,7 +345,7 @@ class Crypto: else: return old_preset - def get_new_passphrase(self,data_desc,hash_preset,passwd_file,pw_desc='passphrase'): + def get_new_passphrase(self, data_desc, hash_preset, passwd_file, pw_desc='passphrase'): message = f""" You must choose a passphrase to encrypt your {data_desc} with. A key will be generated from your passphrase using a hash preset of '{hash_preset}'. @@ -366,72 +358,72 @@ class Crypto: cfg = self.cfg, infile = passwd_file, desc = f'{pw_desc} for {data_desc}', - quiet = self.pwfile_used(passwd_file) )) + quiet = self.pwfile_used(passwd_file))) else: - self.util.qmsg('\n'+fmt(message,indent=' ')) + self.util.qmsg('\n'+fmt(message, indent=' ')) from .ui import get_words_from_user if self.cfg.echo_passphrase: - pw = ' '.join(get_words_from_user( self.cfg, f'Enter {pw_desc} for {data_desc}: ' )) + pw = ' '.join(get_words_from_user(self.cfg, f'Enter {pw_desc} for {data_desc}: ')) else: for _ in range(gc.passwd_max_tries): - pw = ' '.join(get_words_from_user( self.cfg, f'Enter {pw_desc} for {data_desc}: ' )) - pw_chk = ' '.join(get_words_from_user( self.cfg, f'Repeat {pw_desc}: ' )) + pw = ' '.join(get_words_from_user(self.cfg, f'Enter {pw_desc} for {data_desc}: ')) + pw_chk = ' '.join(get_words_from_user(self.cfg, f'Repeat {pw_desc}: ')) self.util.dmsg(f'Passphrases: [{pw}] [{pw_chk}]') if pw == pw_chk: self.util.vmsg('Passphrases match') break msg('Passphrases do not match. Try again.') else: - die(2,f'User failed to duplicate passphrase in {gc.passwd_max_tries} attempts') + die(2, f'User failed to duplicate passphrase in {gc.passwd_max_tries} attempts') if pw == '': self.util.qmsg('WARNING: Empty passphrase') return pw - def get_passphrase(self,data_desc,passwd_file,pw_desc='passphrase'): + def get_passphrase(self, data_desc, passwd_file, pw_desc='passphrase'): if passwd_file: from .fileutil import get_words_from_file return ' '.join(get_words_from_file( cfg = self.cfg, infile = passwd_file, desc = f'{pw_desc} for {data_desc}', - quiet = self.pwfile_used(passwd_file) )) + quiet = self.pwfile_used(passwd_file))) else: from .ui import get_words_from_user - return ' '.join(get_words_from_user( self.cfg, f'Enter {pw_desc} for {data_desc}: ' )) + return ' '.join(get_words_from_user(self.cfg, f'Enter {pw_desc} for {data_desc}: ')) - def mmgen_encrypt(self,data,desc='data',hash_preset=None): + def mmgen_encrypt(self, data, desc='data', hash_preset=None): salt = self.get_random(self.mmenc_salt_len) iv = self.get_random(self.aesctr_iv_len) nonce = self.get_random(self.mmenc_nonce_len) hp = hash_preset or self.cfg.hash_preset or self.get_hash_preset_from_user(data_desc=desc) - m = ('user-requested','default')[hp=='3'] + m = ('user-requested', 'default')[hp=='3'] self.util.vmsg(f'Encrypting {desc}') self.util.qmsg(f'Using {m} hash preset of {hp!r}') passwd = self.get_new_passphrase( data_desc = desc, hash_preset = hp, - passwd_file = self.cfg.passwd_file ) - key = self.make_key(passwd,salt,hp) + passwd_file = self.cfg.passwd_file) + key = self.make_key(passwd, salt, hp) from hashlib import sha256 - enc_d = self.encrypt_data( sha256(nonce+data).digest() + nonce + data, key, iv, desc=desc ) + enc_d = self.encrypt_data(sha256(nonce+data).digest() + nonce + data, key, iv, desc=desc) return salt+iv+enc_d - def mmgen_decrypt(self,data,desc='data',hash_preset=None): + def mmgen_decrypt(self, data, desc='data', hash_preset=None): self.util.vmsg(f'Preparing to decrypt {desc}') dstart = self.mmenc_salt_len + self.aesctr_iv_len salt = data[:self.mmenc_salt_len] iv = data[self.mmenc_salt_len:dstart] enc_d = data[dstart:] hp = hash_preset or self.cfg.hash_preset or self.get_hash_preset_from_user(data_desc=desc) - m = ('user-requested','default')[hp=='3'] + m = ('user-requested', 'default')[hp=='3'] self.util.qmsg(f'Using {m} hash preset of {hp!r}') passwd = self.get_passphrase( data_desc = desc, - passwd_file = self.cfg.passwd_file ) - key = self.make_key(passwd,salt,hp) - dec_d = self.decrypt_data( enc_d, key, iv, desc ) + passwd_file = self.cfg.passwd_file) + key = self.make_key(passwd, salt, hp) + dec_d = self.decrypt_data(enc_d, key, iv, desc) sha256_len = 32 from hashlib import sha256 if dec_d[:sha256_len] == sha256(dec_d[sha256_len:]).digest(): @@ -441,9 +433,9 @@ class Crypto: msg('Incorrect passphrase or hash preset') return False - def mmgen_decrypt_retry(self,d,desc='data'): + def mmgen_decrypt_retry(self, d, desc='data'): while True: - d_dec = self.mmgen_decrypt(d,desc) + d_dec = self.mmgen_decrypt(d, desc) if d_dec: return d_dec msg('Trying again...') diff --git a/mmgen/daemon.py b/mmgen/daemon.py index 38ba55f2..897bb1bc 100755 --- a/mmgen/daemon.py +++ b/mmgen/daemon.py @@ -20,17 +20,17 @@ daemon: Daemon control interface for the MMGen suite """ -import sys,os,time,importlib -from subprocess import run,PIPE,CompletedProcess +import sys, os, time, importlib +from subprocess import run, PIPE, CompletedProcess from collections import namedtuple from .base_obj import Lockable from .color import set_vt100 -from .util import msg,Msg_r,die,remove_dups,oneshot_warning,fmt_list -from .flags import ClassFlags,ClassOpts +from .util import msg, Msg_r, die, remove_dups, oneshot_warning, fmt_list +from .flags import ClassFlags, ClassOpts -_dd = namedtuple('daemon_data',['coind_name','coind_version','coind_version_str']) # latest tested version -_nw = namedtuple('coin_networks',['mainnet','testnet','regtest']) +_dd = namedtuple('daemon_data', ['coind_name', 'coind_version', 'coind_version_str']) # latest tested version +_nw = namedtuple('coin_networks', ['mainnet', 'testnet', 'regtest']) class Daemon(Lockable): @@ -47,10 +47,10 @@ class Daemon(Lockable): private_port = None avail_opts = () avail_flags = () # like opts, but can be set or unset after instantiation - _reset_ok = ('debug','wait','pids') + _reset_ok = ('debug', 'wait', 'pids') version_info_arg = '--version' - def __init__(self,cfg,opts=None,flags=None): + def __init__(self, cfg, opts=None, flags=None): self.cfg = cfg self.platform = sys.platform @@ -58,40 +58,40 @@ class Daemon(Lockable): self.use_pidfile = False self.use_threads = True - self.opt = ClassOpts(self,opts) - self.flag = ClassFlags(self,flags) + self.opt = ClassOpts(self, opts) + self.flag = ClassFlags(self, flags) self.debug = self.debug or cfg.debug_daemon - def exec_cmd_thread(self,cmd): + def exec_cmd_thread(self, cmd): import threading - tname = ('exec_cmd','exec_cmd_win_console')[self.platform == 'win32' and self.new_console_mswin] - t = threading.Thread(target=getattr(self,tname),args=(cmd,)) + tname = ('exec_cmd', 'exec_cmd_win_console')[self.platform == 'win32' and self.new_console_mswin] + t = threading.Thread(target=getattr(self, tname), args=(cmd,)) t.daemon = True t.start() if self.platform == 'win32': Msg_r(' \b') # blocks w/o this...crazy return True - def exec_cmd_win_console(self,cmd): - from subprocess import Popen,CREATE_NEW_CONSOLE,STARTUPINFO,STARTF_USESHOWWINDOW,SW_HIDE - si = STARTUPINFO(dwFlags=STARTF_USESHOWWINDOW,wShowWindow=SW_HIDE) - p = Popen(cmd,creationflags=CREATE_NEW_CONSOLE,startupinfo=si) + def exec_cmd_win_console(self, cmd): + from subprocess import Popen, CREATE_NEW_CONSOLE, STARTUPINFO, STARTF_USESHOWWINDOW, SW_HIDE + si = STARTUPINFO(dwFlags=STARTF_USESHOWWINDOW, wShowWindow=SW_HIDE) + p = Popen(cmd, creationflags=CREATE_NEW_CONSOLE, startupinfo=si) p.wait() - def exec_cmd(self,cmd,is_daemon=False,check_retcode=False): - out = (PIPE,None)[is_daemon and self.opt.no_daemonize] + def exec_cmd(self, cmd, is_daemon=False, check_retcode=False): + out = (PIPE, None)[is_daemon and self.opt.no_daemonize] try: - cp = run(cmd,check=False,stdout=out,stderr=out) + cp = run(cmd, check=False, stdout=out, stderr=out) except OSError as e: - die( 'MMGenCalledProcessError', f'Error starting executable: {type(e).__name__} [Errno {e.errno}]' ) + die('MMGenCalledProcessError', f'Error starting executable: {type(e).__name__} [Errno {e.errno}]') set_vt100() if check_retcode and cp.returncode: - die(1,str(cp)) + die(1, str(cp)) if self.debug: print(cp) return cp - def run_cmd(self,cmd,silent=False,is_daemon=False,check_retcode=False): + def run_cmd(self, cmd, silent=False, is_daemon=False, check_retcode=False): if self.debug: msg('\n\n') @@ -100,14 +100,14 @@ class Daemon(Lockable): msg(f'Starting {self.desc} on port {self.bind_port}') if self.debug: - msg(f'\nExecuting:\n{fmt_list(cmd,fmt="col",indent=" ")}\n') + msg(f'\nExecuting:\n{fmt_list(cmd, fmt="col", indent=" ")}\n') if self.use_threads and is_daemon and not self.opt.no_daemonize: ret = self.exec_cmd_thread(cmd) else: - ret = self.exec_cmd(cmd,is_daemon,check_retcode) + ret = self.exec_cmd(cmd, is_daemon, check_retcode) - if isinstance(ret,CompletedProcess): + if isinstance(ret, CompletedProcess): if ret.stdout and (self.debug or not silent): msg(ret.stdout.decode().rstrip()) if ret.stderr and (self.debug or (ret.returncode and not silent)): @@ -124,7 +124,7 @@ class Daemon(Lockable): # Assumes only one running instance of given daemon. If multiple daemons are running, # the first PID in the list is returned and self.pids is set to the PID list. ss = f'{self.exec_fn}.exe' - cp = self.run_cmd(['ps','-Wl'],silent=True) + cp = self.run_cmd(['ps', '-Wl'], silent=True) self.pids = () # use Windows, not Cygwin, PID pids = tuple(line.split()[3] for line in cp.stdout.decode().splitlines() if ss in line) @@ -134,10 +134,10 @@ class Daemon(Lockable): return pids[0] elif self.platform in ('linux', 'darwin'): ss = ' '.join(self.start_cmd) - cp = self.run_cmd(['pgrep','-f',ss],silent=True) + cp = self.run_cmd(['pgrep', '-f', ss], silent=True) if cp.stdout: return cp.stdout.strip().decode() - die(2,f'{ss!r} not found in process list, cannot determine PID') + die(2, f'{ss!r} not found in process list, cannot determine PID') @property def bind_port(self): @@ -147,7 +147,7 @@ class Daemon(Lockable): def state(self): if self.debug: msg(f'Testing port {self.bind_port}') - return 'ready' if self.test_socket('localhost',self.bind_port) else 'stopped' + return 'ready' if self.test_socket('localhost', self.bind_port) else 'stopped' @property def start_cmds(self): @@ -156,17 +156,17 @@ class Daemon(Lockable): @property def stop_cmd(self): return ( - ['kill','-Wf',self.pid] if self.platform == 'win32' else - ['kill','-9',self.pid] if self.force_kill else - ['kill',self.pid] ) + ['kill', '-Wf', self.pid] if self.platform == 'win32' else + ['kill', '-9', self.pid] if self.force_kill else + ['kill', self.pid]) - def cmd(self,action,*args,**kwargs): - return getattr(self,action)(*args,**kwargs) + def cmd(self, action, *args, **kwargs): + return getattr(self, action)(*args, **kwargs) - def cli(self,*cmds,silent=False): - return self.run_cmd(self.cli_cmd(*cmds),silent=silent) + def cli(self, *cmds, silent=False): + return self.run_cmd(self.cli_cmd(*cmds), silent=silent) - def state_msg(self,extra_text=None): + def state_msg(self, extra_text=None): try: pid = self.pid except: @@ -176,12 +176,12 @@ class Daemon(Lockable): f'{self.desc} {extra_text}running', 'pid N/A' if pid is None or self.pids or self.state == 'stopped' else f'pid {pid}', f'port {self.bind_port}', - w = 60 ) + w = 60) def pre_start(self): pass - def start(self,quiet=False,silent=False): + def start(self, quiet=False, silent=False): if self.state == 'ready': if not (quiet or silent): msg(self.state_msg(extra_text='already')) @@ -193,24 +193,24 @@ class Daemon(Lockable): if not silent: msg(f'Starting {self.desc} on port {self.bind_port}') - ret = self.run_cmd(self.start_cmd,silent=True,is_daemon=True,check_retcode=True) + ret = self.run_cmd(self.start_cmd, silent=True, is_daemon=True, check_retcode=True) if self.wait: self.wait_for_state('ready') return ret - def stop(self,quiet=False,silent=False): + def stop(self, quiet=False, silent=False): if self.state == 'ready': if not silent: msg(f'Stopping {self.desc} on port {self.bind_port}') if self.force_kill: run(['sync']) - ret = self.run_cmd(self.stop_cmd,silent=True) + ret = self.run_cmd(self.stop_cmd, silent=True) if self.pids: msg('Warning: multiple PIDs [{}] -- we may be stopping the wrong instance'.format( - fmt_list(self.pids,fmt='bare') + fmt_list(self.pids, fmt='bare') )) if self.wait: self.wait_for_state('stopped') @@ -221,49 +221,49 @@ class Daemon(Lockable): msg(f'{self.desc} on port {self.bind_port} not running') return True - def restart(self,silent=False): + def restart(self, silent=False): self.stop(silent=silent) return self.start(silent=silent) - def test_socket(self,host,port,timeout=10): + def test_socket(self, host, port, timeout=10): import socket try: - socket.create_connection((host,port),timeout=timeout).close() + socket.create_connection((host, port), timeout=timeout).close() except: return False else: return True - def wait_for_state(self,req_state): + def wait_for_state(self, req_state): for _ in range(300): if self.state == req_state: return True time.sleep(0.2) - die(2,f'Wait for state {req_state!r} timeout exceeded for {self.desc} (port {self.bind_port})') + die(2, f'Wait for state {req_state!r} timeout exceeded for {self.desc} (port {self.bind_port})') @classmethod def get_exec_version_str(cls): try: - cp = run([cls.exec_fn,cls.version_info_arg],stdout=PIPE,stderr=PIPE,check=True) + cp = run([cls.exec_fn, cls.version_info_arg], stdout=PIPE, stderr=PIPE, check=True) except Exception as e: - die(2,f'{e}\nUnable to execute {cls.exec_fn}') + die(2, f'{e}\nUnable to execute {cls.exec_fn}') if cp.returncode: - die(2,f'Unable to execute {cls.exec_fn}') + die(2, f'Unable to execute {cls.exec_fn}') else: res = cp.stdout.decode().splitlines() - return ( res[0] if len(res) == 1 else [s for s in res if 'ersion' in s][0] ).strip() + return (res[0] if len(res) == 1 else [s for s in res if 'ersion' in s][0]).strip() class RPCDaemon(Daemon): avail_opts = ('no_daemonize',) - def __init__(self,cfg,opts=None,flags=None): - super().__init__(cfg,opts=opts,flags=flags) + def __init__(self, cfg, opts=None, flags=None): + super().__init__(cfg, opts=opts, flags=flags) self.desc = '{} {} {}RPC daemon'.format( self.rpc_type, - getattr(self.proto.network_names,self.proto.network), - 'test suite ' if self.test_suite else '' ) + getattr(self.proto.network_names, self.proto.network), + 'test suite ' if self.test_suite else '') self._set_ok += ('usr_daemon_args',) self.usr_daemon_args = [] @@ -272,22 +272,22 @@ class RPCDaemon(Daemon): return [self.exec_fn] + self.daemon_args + self.usr_daemon_args class CoinDaemon(Daemon): - networks = ('mainnet','testnet','regtest') + networks = ('mainnet', 'testnet', 'regtest') cfg_file_hdr = '' avail_flags = ('keep_cfg_file',) - avail_opts = ('no_daemonize','online') + avail_opts = ('no_daemonize', 'online') testnet_dir = None test_suite_port_shift = 1237 rpc_user = None rpc_password = None - _cd = namedtuple('coins_data',['daemon_ids']) + _cd = namedtuple('coins_data', ['daemon_ids']) coins = { 'BTC': _cd(['bitcoin_core']), 'BCH': _cd(['bitcoin_cash_node']), 'LTC': _cd(['litecoin_core']), 'XMR': _cd(['monero']), - 'ETH': _cd(['geth','erigon','openethereum']), + 'ETH': _cd(['geth', 'erigon', 'openethereum']), 'ETC': _cd(['parity']), } @@ -300,7 +300,7 @@ class CoinDaemon(Daemon): message = 'blacklisted daemon: {!r}' @classmethod - def get_daemon_ids(cls,cfg,coin): + def get_daemon_ids(cls, cfg, coin): ret = cls.coins[coin].daemon_ids if 'erigon' in ret and not cfg.enable_erigon: @@ -310,14 +310,14 @@ class CoinDaemon(Daemon): def gen(): for daemon_id in ret: if daemon_id in blacklist: - cls.warn_blacklisted(div=daemon_id,fmt_args=[daemon_id]) + cls.warn_blacklisted(div=daemon_id, fmt_args=[daemon_id]) else: yield daemon_id ret = list(gen()) return ret @classmethod - def get_daemon(cls,cfg,coin,daemon_id,proto=None): + def get_daemon(cls, cfg, coin, daemon_id, proto=None): if proto: proto_cls = type(proto) else: @@ -325,17 +325,17 @@ class CoinDaemon(Daemon): proto_cls = init_proto(cfg, coin, return_cls=True) return getattr( importlib.import_module(f'mmgen.proto.{proto_cls.base_proto_coin.lower()}.daemon'), - daemon_id+'_daemon' ) + daemon_id+'_daemon') @classmethod - def get_network_ids(cls,cfg): + def get_network_ids(cls, cfg): from .protocol import CoinProtocol def gen(): for coin in cls.coins: - for daemon_id in cls.get_daemon_ids(cfg,coin): - for network in cls.get_daemon( cfg, coin, daemon_id ).networks: - yield CoinProtocol.Base.create_network_id(coin,network) - return remove_dups(list(gen()),quiet=True) + for daemon_id in cls.get_daemon_ids(cfg, coin): + for network in cls.get_daemon(cfg, coin, daemon_id).networks: + yield CoinProtocol.Base.create_network_id(coin, network) + return remove_dups(list(gen()), quiet=True) def __new__(cls, cfg, @@ -347,7 +347,7 @@ class CoinDaemon(Daemon): port_shift = None, p2p_port = None, datadir = None, - daemon_id = None ): + daemon_id = None): assert network_id or proto, 'CoinDaemon_chk1' assert not (network_id and proto), 'CoinDaemon_chk2' @@ -358,20 +358,20 @@ class CoinDaemon(Daemon): coin = proto.coin else: network_id = network_id.lower() - from .protocol import CoinProtocol,init_proto - proto = init_proto( cfg, network_id=network_id ) - coin,network = CoinProtocol.Base.parse_network_id(network_id) + from .protocol import CoinProtocol, init_proto + proto = init_proto(cfg, network_id=network_id) + coin, network = CoinProtocol.Base.parse_network_id(network_id) coin = coin.upper() - daemon_ids = cls.get_daemon_ids(cfg,coin) + daemon_ids = cls.get_daemon_ids(cfg, coin) if not daemon_ids: - die(1,f'No configured daemons for coin {coin}!') + die(1, f'No configured daemons for coin {coin}!') daemon_id = daemon_id or cfg.daemon_id or daemon_ids[0] if daemon_id not in daemon_ids: - die(1,f'{daemon_id!r}: invalid daemon_id - valid choices: {fmt_list(daemon_ids)}') + die(1, f'{daemon_id!r}: invalid daemon_id - valid choices: {fmt_list(daemon_ids)}') - me = Daemon.__new__(cls.get_daemon( cfg, None, daemon_id, proto=proto )) + me = Daemon.__new__(cls.get_daemon(cfg, None, daemon_id, proto=proto)) assert network in me.networks, f'{network!r}: unsupported network for daemon {daemon_id}' me.network_id = network_id @@ -392,30 +392,30 @@ class CoinDaemon(Daemon): port_shift = None, p2p_port = None, datadir = None, - daemon_id = None ): + daemon_id = None): self.test_suite = test_suite - super().__init__(cfg=cfg,opts=opts,flags=flags) + super().__init__(cfg=cfg, opts=opts, flags=flags) - self._set_ok += ('shared_args','usr_coind_args') + self._set_ok += ('shared_args', 'usr_coind_args') self.shared_args = [] self.usr_coind_args = [] - for k,v in self.daemon_data._asdict().items(): - setattr(self,k,v) + for k, v in self.daemon_data._asdict().items(): + setattr(self, k, v) self.desc = '{} {} {}daemon'.format( self.coind_name, - getattr(self.proto.network_names,self.network), - 'test suite ' if test_suite else '' ) + getattr(self.proto.network_names, self.network), + 'test suite ' if test_suite else '') # user-set values take precedence self.datadir = os.path.abspath(datadir or cfg.daemon_data_dir or self.init_datadir()) self.non_dfl_datadir = bool(datadir or cfg.daemon_data_dir or test_suite or self.network == 'regtest') # init_datadir() may have already initialized logdir - self.logdir = os.path.abspath(getattr(self,'logdir',self.datadir)) + self.logdir = os.path.abspath(getattr(self, 'logdir', self.datadir)) ps_adj = (port_shift or 0) + (self.test_suite_port_shift if test_suite else 0) @@ -424,10 +424,10 @@ class CoinDaemon(Daemon): self.p2p_port = ( p2p_port or ( self.get_p2p_port() + ps_adj if self.get_p2p_port() and (test_suite or ps_adj) else None - ) if self.network != 'regtest' else None ) + ) if self.network != 'regtest' else None) - if hasattr(self,'private_ports'): - self.private_port = getattr(self.private_ports,self.network) + if hasattr(self, 'private_ports'): + self.private_port = getattr(self.private_ports, self.network) # bind_port == self.private_port or self.rpc_port self.pidfile = f'{self.logdir}/{self.id}-{self.network}-daemon-{self.bind_port}.pid' @@ -437,7 +437,7 @@ class CoinDaemon(Daemon): def init_datadir(self): if self.test_suite: - return os.path.join('test','daemons',self.network_id) + return os.path.join('test', 'daemons', self.network_id) else: return os.path.join(*self.datadirs[self.platform]) @@ -446,7 +446,7 @@ class CoinDaemon(Daemon): return self.datadir def get_rpc_port(self): - return getattr(self.rpc_ports,self.network) + return getattr(self.rpc_ports, self.network) def get_p2p_port(self): return None @@ -456,27 +456,27 @@ class CoinDaemon(Daemon): return ([self.exec_fn] + self.coind_args + self.shared_args - + self.usr_coind_args ) + + self.usr_coind_args) - def cli_cmd(self,*cmds): + def cli_cmd(self, *cmds): return ([self.cli_fn] + self.shared_args - + list(cmds) ) + + list(cmds)) - def start(self,*args,**kwargs): + def start(self, *args, **kwargs): assert self.test_suite or self.network == 'regtest', 'start() restricted to test suite and regtest' - return super().start(*args,**kwargs) + return super().start(*args, **kwargs) - def stop(self,*args,**kwargs): + def stop(self, *args, **kwargs): assert self.test_suite or self.network == 'regtest', 'stop() restricted to test suite and regtest' - return super().stop(*args,**kwargs) + return super().stop(*args, **kwargs) def pre_start(self): - os.makedirs(self.datadir,exist_ok=True) + os.makedirs(self.datadir, exist_ok=True) if self.test_suite or self.network == 'regtest': if self.cfg_file and not self.flag.keep_cfg_file: - with open(f'{self.datadir}/{self.cfg_file}','w') as fp: + with open(f'{self.datadir}/{self.cfg_file}', 'w') as fp: fp.write(self.cfg_file_hdr) if self.use_pidfile and os.path.exists(self.pidfile): @@ -491,7 +491,7 @@ class CoinDaemon(Daemon): run([ ('rm' if self.platform == 'win32' else '/bin/rm'), '-rf', - self.datadir ]) + self.datadir]) set_vt100() else: msg(f'Cannot remove {self.network_datadir!r} - daemon is not stopped') diff --git a/mmgen/derive.py b/mmgen/derive.py index f713b4fd..b5668b5b 100755 --- a/mmgen/derive.py +++ b/mmgen/derive.py @@ -21,19 +21,19 @@ derive: coin private key secret derivation for the MMGen suite """ from collections import namedtuple -from hashlib import sha512,sha256 +from hashlib import sha512, sha256 from .addrlist import AddrIdxList -pk_bytes = namedtuple('coin_privkey_bytes',['idx','pos','data']) +pk_bytes = namedtuple('coin_privkey_bytes', ['idx', 'pos', 'data']) -def derive_coin_privkey_bytes(seed,idxs): +def derive_coin_privkey_bytes(seed, idxs): - assert isinstance(idxs,AddrIdxList), f'{type(idxs)}: idx list not of type AddrIdxList' + assert isinstance(idxs, AddrIdxList), f'{type(idxs)}: idx list not of type AddrIdxList' t_keys = len(idxs) pos = 0 - for idx in range( 1, AddrIdxList.max_len+1 ): # key/addr indexes begin from one + for idx in range(1, AddrIdxList.max_len + 1): # key/addr indexes begin from one seed = sha512(seed).digest() @@ -42,7 +42,7 @@ def derive_coin_privkey_bytes(seed,idxs): pos += 1 # secret is double sha256 of seed hash round /idx/ - yield pk_bytes( idx, pos, sha256(sha256(seed).digest()).digest() ) + yield pk_bytes(idx, pos, sha256(sha256(seed).digest()).digest()) if pos == t_keys: break diff --git a/mmgen/devinit.py b/mmgen/devinit.py index 17a1d8cd..f32a6a90 100755 --- a/mmgen/devinit.py +++ b/mmgen/devinit.py @@ -13,35 +13,35 @@ devinit: Developer tools initialization for the MMGen suite """ devtools_funcs = { - 'pfmt': lambda *args,**kwargs: devtools_call('pfmt',*args,**kwargs), - 'pmsg': lambda *args,**kwargs: devtools_call('pmsg',*args,**kwargs), - 'pmsg_r': lambda *args,**kwargs: devtools_call('pmsg_r',*args,**kwargs), - 'pdie': lambda *args,**kwargs: devtools_call('pdie',*args,**kwargs), - 'pexit': lambda *args,**kwargs: devtools_call('pexit',*args,**kwargs), - 'Pmsg': lambda *args,**kwargs: devtools_call('Pmsg',*args,**kwargs), - 'Pdie': lambda *args,**kwargs: devtools_call('Pdie',*args,**kwargs), - 'Pexit': lambda *args,**kwargs: devtools_call('Pexit',*args,**kwargs), - 'print_stack_trace': lambda *args,**kwargs: devtools_call('print_stack_trace',*args,**kwargs), - 'get_diff': lambda *args,**kwargs: devtools_call('get_diff',*args,**kwargs), - 'print_diff': lambda *args,**kwargs: devtools_call('print_diff',*args,**kwargs), - 'get_ndiff': lambda *args,**kwargs: devtools_call('get_ndiff',*args,**kwargs), - 'print_ndiff': lambda *args,**kwargs: devtools_call('print_ndiff',*args,**kwargs), + 'pfmt': lambda *args, **kwargs: devtools_call('pfmt', *args, **kwargs), + 'pmsg': lambda *args, **kwargs: devtools_call('pmsg', *args, **kwargs), + 'pmsg_r': lambda *args, **kwargs: devtools_call('pmsg_r', *args, **kwargs), + 'pdie': lambda *args, **kwargs: devtools_call('pdie', *args, **kwargs), + 'pexit': lambda *args, **kwargs: devtools_call('pexit', *args, **kwargs), + 'Pmsg': lambda *args, **kwargs: devtools_call('Pmsg', *args, **kwargs), + 'Pdie': lambda *args, **kwargs: devtools_call('Pdie', *args, **kwargs), + 'Pexit': lambda *args, **kwargs: devtools_call('Pexit', *args, **kwargs), + 'print_stack_trace': lambda *args, **kwargs: devtools_call('print_stack_trace', *args, **kwargs), + 'get_diff': lambda *args, **kwargs: devtools_call('get_diff', *args, **kwargs), + 'print_diff': lambda *args, **kwargs: devtools_call('print_diff', *args, **kwargs), + 'get_ndiff': lambda *args, **kwargs: devtools_call('get_ndiff', *args, **kwargs), + 'print_ndiff': lambda *args, **kwargs: devtools_call('print_ndiff', *args, **kwargs), } -def devtools_call(funcname,*args,**kwargs): +def devtools_call(funcname, *args, **kwargs): from . import devtools - return getattr(devtools,funcname)(*args,**kwargs) + return getattr(devtools, funcname)(*args, **kwargs) -def MMGenObject_call(methodname,*args,**kwargs): +def MMGenObject_call(methodname, *args, **kwargs): from .devtools import MMGenObjectMethods - return getattr(MMGenObjectMethods,methodname)(*args,**kwargs) + return getattr(MMGenObjectMethods, methodname)(*args, **kwargs) class MMGenObjectDevTools: - pmsg = lambda *args,**kwargs: MMGenObject_call('pmsg',*args,**kwargs) - pdie = lambda *args,**kwargs: MMGenObject_call('pdie',*args,**kwargs) - pexit = lambda *args,**kwargs: MMGenObject_call('pexit',*args,**kwargs) - pfmt = lambda *args,**kwargs: MMGenObject_call('pfmt',*args,**kwargs) + pmsg = lambda *args, **kwargs: MMGenObject_call('pmsg', *args, **kwargs) + pdie = lambda *args, **kwargs: MMGenObject_call('pdie', *args, **kwargs) + pexit = lambda *args, **kwargs: MMGenObject_call('pexit', *args, **kwargs) + pfmt = lambda *args, **kwargs: MMGenObject_call('pfmt', *args, **kwargs) # Check that all immutables have been initialized. Expensive, so do only when testing. def immutable_attr_init_check(self): @@ -50,22 +50,22 @@ class MMGenObjectDevTools: for attrname in self.valid_attrs: - for o in (cls,cls.__bases__[0]): # assume there's only one base class + for o in (cls, cls.__bases__[0]): # assume there's only one base class if attrname in o.__dict__: attr = o.__dict__[attrname] break else: from .util import die - die(4,f'unable to find descriptor {cls.__name__}.{attrname}') + die(4, f'unable to find descriptor {cls.__name__}.{attrname}') if type(attr).__name__ == 'ImmutableAttr' and attrname not in self.__dict__: from .util import die - die(4,f'attribute {attrname!r} of {cls.__name__} has not been initialized in constructor!') + die(4, f'attribute {attrname!r} of {cls.__name__} has not been initialized in constructor!') def init_dev(): import builtins # MMGenObject is added to the namespace by objmethods.py, so we must name the builtin differently # to avoid inadvertently adding MMGenObject to the global namespace here: - setattr(builtins,'MMGenObjectDevTools',MMGenObjectDevTools) - for funcname,func in devtools_funcs.items(): - setattr(builtins,funcname,func) + setattr(builtins, 'MMGenObjectDevTools', MMGenObjectDevTools) + for funcname, func in devtools_funcs.items(): + setattr(builtins, funcname, func) diff --git a/mmgen/devtools.py b/mmgen/devtools.py index e4c228f0..0b6824c3 100755 --- a/mmgen/devtools.py +++ b/mmgen/devtools.py @@ -16,77 +16,77 @@ import sys color_funcs = { name: lambda s, n=n: f'\033[{n};1m{s}\033[0m' - for name,n in ( - ('red', 31), - ('green', 32), - ('yellow',33), - ('blue', 34), - ('purple',35)) + for name, n in ( + ('red', 31), + ('green', 32), + ('yellow', 33), + ('blue', 34), + ('purple', 35)) } -def pfmt(*args,color=None): +def pfmt(*args, color=None): import pprint - ret = pprint.PrettyPrinter(indent=4,width=116).pformat( - args if len(args) > 1 else '' if not args else args[0] ) + ret = pprint.PrettyPrinter(indent=4, width=116).pformat( + args if len(args) > 1 else '' if not args else args[0]) return color_funcs[color](ret) if color else ret -def pmsg(*args,color=None): - sys.stderr.write(pfmt(*args,color=color) + '\n') +def pmsg(*args, color=None): + sys.stderr.write(pfmt(*args, color=color) + '\n') -def pmsg_r(*args,color=None): - sys.stderr.write(pfmt(*args,color=color)) +def pmsg_r(*args, color=None): + sys.stderr.write(pfmt(*args, color=color)) -def pdie(*args,exit_val=1): - pmsg(*args,color='red' if exit_val else None) +def pdie(*args, exit_val=1): + pmsg(*args, color='red' if exit_val else None) sys.exit(exit_val) def pexit(*args): - pdie(*args,exit_val=0) + pdie(*args, exit_val=0) -def Pmsg(*args,color=None): - sys.stdout.write(pfmt(*args,color=color) + '\n') +def Pmsg(*args, color=None): + sys.stdout.write(pfmt(*args, color=color) + '\n') -def Pdie(*args,exit_val=1): - Pmsg(*args,color=('yellow' if exit_val == 1 else 'red' if exit_val else None)) +def Pdie(*args, exit_val=1): + Pmsg(*args, color=('yellow' if exit_val == 1 else 'red' if exit_val else None)) sys.exit(exit_val) def Pexit(*args): - Pdie(*args,exit_val=0) + Pdie(*args, exit_val=0) -def print_stack_trace(message=None,fh_list=[],nl='\n',sep='\n ',trim=4): +def print_stack_trace(message=None, fh_list=[], nl='\n', sep='\n ', trim=4): if not fh_list: import os - fh_list.append(open(f'devtools.trace.{os.getpid()}','w')) + fh_list.append(open(f'devtools.trace.{os.getpid()}', 'w')) nl = '' - res = get_stack_trace(message,nl,sep,trim) + res = get_stack_trace(message, nl, sep, trim) sys.stderr.write(res) fh_list[0].write(res) -def get_stack_trace(message=None,nl='\n',sep='\n ',trim=3): +def get_stack_trace(message=None, nl='\n', sep='\n ', trim=3): - import os,re,traceback + import os, re, traceback tb = [t for t in traceback.extract_stack() if t.filename[:1] != '<'] fs = '{}:{}: in {}:\n {}' out = [ fs.format( - re.sub(r'^\./','',os.path.relpath(t.filename)), + re.sub(r'^\./', '', os.path.relpath(t.filename)), t.lineno, (t.name+'()' if t.name[-1] != '>' else t.name), t.line or '(none)') - for t in (tb[:-trim] if trim else tb) ] + for t in (tb[:-trim] if trim else tb)] return f'{nl}STACK TRACE {message or "[unnamed]"}:{sep}{sep.join(out)}\n' -def print_diff(*args,**kwargs): - sys.stderr.write(get_diff(*args,**kwargs)) +def print_diff(*args, **kwargs): + sys.stderr.write(get_diff(*args, **kwargs)) -def get_diff(a,b,a_fn='',b_fn='',from_json=True): +def get_diff(a, b, a_fn='', b_fn='', from_json=True): if from_json: import json - a = json.dumps(json.loads(a),indent=4) - b = json.dumps(json.loads(b),indent=4) + a = json.dumps(json.loads(a), indent=4) + b = json.dumps(json.loads(b), indent=4) from difflib import unified_diff # chunk headers have trailing newlines, hence the rstrip() @@ -95,70 +95,70 @@ def get_diff(a,b,a_fn='',b_fn='',from_json=True): a.split('\n'), b.split('\n'), a_fn, - b_fn ))) + b_fn))) -def print_ndiff(*args,**kwargs): - sys.stderr.write(get_ndiff(*args,**kwargs)) +def print_ndiff(*args, **kwargs): + sys.stderr.write(get_ndiff(*args, **kwargs)) -def get_ndiff(a,b): +def get_ndiff(a, b): from difflib import ndiff return list(ndiff( a.split('\n'), - b.split('\n') )) + b.split('\n'))) class MMGenObjectMethods: # mixin class for MMGenObject # Pretty-print an MMGenObject instance, recursing into sub-objects - WIP - def pmsg(self,color=None): + def pmsg(self, color=None): sys.stdout.write('\n'+self.pfmt(color=color)) - def pdie(self,exit_val=1): + def pdie(self, exit_val=1): self.pmsg(color='red' if exit_val else None) sys.exit(exit_val) def pexit(self): self.pdie(exit_val=0) - def pfmt(self,lvl=0,id_list=[],color=None): + def pfmt(self, lvl=0, id_list=[], color=None): from decimal import Decimal - scalars = (str,int,float,Decimal) + scalars = (str, int, float, Decimal) def isDict(obj): - return isinstance(obj,dict) + return isinstance(obj, dict) def isList(obj): - return isinstance(obj,list) + return isinstance(obj, list) def isScalar(obj): - return isinstance(obj,scalars) + return isinstance(obj, scalars) - def do_list(out,e,lvl=0,is_dict=False): + def do_list(out, e, lvl=0, is_dict=False): out.append('\n') for i in e: el = i if not is_dict else e[i] if is_dict: -# out.append('{s}{:<{l}}'.format(i,s=' '*(4*lvl+8),l=10,l2=8*(lvl+1)+8)) +# out.append('{s}{:<{l}}'.format(i, s=' '*(4*lvl+8), l=10, l2=8*(lvl+1)+8)) out.append('{s1}{i}{s2}'.format( i = i, s1 = ' ' * (4*lvl+8), - s2 = ' ' * 10 )) - if hasattr(el,'pfmt'): + s2 = ' ' * 10)) + if hasattr(el, 'pfmt'): out.append('{:>{l}}{}'.format( '', - el.pfmt( lvl=lvl+1, id_list=id_list+[id(self)] ), - l = (lvl+1)*8 )) - elif isinstance(el,scalars): + el.pfmt(lvl=lvl+1, id_list=id_list+[id(self)]), + l = (lvl+1)*8)) + elif isinstance(el, scalars): if isList(e): - out.append( '{:>{l}}{!r:16}\n'.format( '', el, l=lvl*8 )) + out.append('{:>{l}}{!r:16}\n'.format('', el, l=lvl*8)) else: out.append(f' {el!r}') elif isList(el) or isDict(el): indent = 1 if is_dict else lvl*8+4 - out.append('{:>{l}}{:16}'.format( '', f'<{type(el).__name__}>', l=indent )) - if isList(el) and isinstance(el[0],scalars): + out.append('{:>{l}}{:16}'.format('', f'<{type(el).__name__}>', l=indent)) + if isList(el) and isinstance(el[0], scalars): out.append('\n') - do_list(out,el,lvl=lvl+1,is_dict=isDict(el)) + do_list(out, el, lvl=lvl+1, is_dict=isDict(el)) else: - out.append('{:>{l}}{:16} {!r}\n'.format( '', f'<{type(el).__name__}>', el, l=(lvl*8)+8 )) + out.append('{:>{l}}{:16} {!r}\n'.format('', f'<{type(el).__name__}>', el, l=(lvl*8)+8)) out.append('\n') if not e: @@ -169,27 +169,27 @@ class MMGenObjectMethods: # mixin class for MMGenObject if id(self) in id_list: return out[-1].rstrip() + ' [RECURSION]\n' if isList(self) or isDict(self): - do_list(out,self,lvl=lvl,is_dict=isDict(self)) + do_list(out, self, lvl=lvl, is_dict=isDict(self)) for k in self.__dict__: - e = getattr(self,k) + e = getattr(self, k) if isList(e) or isDict(e): - out.append('{:>{l}}{:<10} {:16}'.format( '', k, f'<{type(e).__name__}>', l=(lvl*8)+4 )) - do_list(out,e,lvl=lvl,is_dict=isDict(e)) - elif hasattr(e,'pfmt') and callable(e.pfmt) and not isinstance(e,type): + out.append('{:>{l}}{:<10} {:16}'.format('', k, f'<{type(e).__name__}>', l=(lvl*8)+4)) + do_list(out, e, lvl=lvl, is_dict=isDict(e)) + elif hasattr(e, 'pfmt') and callable(e.pfmt) and not isinstance(e, type): out.append('{:>{l}}{:10} {}'.format( '', k, - e.pfmt( lvl=lvl+1, id_list=id_list+[id(self)] ), - l = (lvl*8)+4 )) + e.pfmt(lvl=lvl+1, id_list=id_list+[id(self)]), + l = (lvl*8)+4)) else: out.append('{:>{l}}{:<10} {:16} {}\n'.format( '', k, f'<{type(e).__name__}>', repr(e), - l=(lvl*8)+4 )) + l=(lvl*8)+4)) import re - ret = re.sub('\n+','\n',''.join(out)) + ret = re.sub('\n+', '\n', ''.join(out)) return color_funcs[color](ret) if color else ret diff --git a/mmgen/exception.py b/mmgen/exception.py index 51ac63ea..67343a5b 100755 --- a/mmgen/exception.py +++ b/mmgen/exception.py @@ -22,7 +22,7 @@ exception: Exception classes for the MMGen suite class MMGenError(Exception): - def __init__(self,errno,strerror,stdout): + def __init__(self, errno, strerror, stdout): self.mmcode = errno self.stdout = stdout super().__init__(strerror) diff --git a/mmgen/filename.py b/mmgen/filename.py index baa7df98..bfed5ca2 100755 --- a/mmgen/filename.py +++ b/mmgen/filename.py @@ -20,12 +20,12 @@ filename: File and MMGenFile classes and methods for the MMGen suite """ -import sys,os -from .util import die,get_extension +import sys, os +from .util import die, get_extension class File: - def __init__(self,fn,write=False): + def __init__(self, fn, write=False): self.name = fn self.dirname = os.path.dirname(fn) @@ -38,18 +38,18 @@ class File: try: st = os.stat(fn) except: - die( 'FileNotFound', f'{fn!r}: file not found' ) + die('FileNotFound', f'{fn!r}: file not found') import stat if stat.S_ISBLK(st.st_mode): if sys.platform in ('win32',): die(2, 'Access to raw block devices not supported on platform {sys.platform!r}') - mode = (os.O_RDONLY,os.O_RDWR)[bool(write)] + mode = (os.O_RDONLY, os.O_RDWR)[bool(write)] try: fd = os.open(fn, mode) except OSError as e: if e.errno == 13: - die(2,f'{fn!r}: permission denied') + die(2, f'{fn!r}: permission denied') # if e.errno != 17: raise else: if sys.platform == 'linux': @@ -66,21 +66,21 @@ class File: class FileList(list): - def __init__(self,fns,write=False): + def __init__(self, fns, write=False): list.__init__( self, - [File(fn,write) for fn in fns] ) + [File(fn, write) for fn in fns]) def names(self): return [f.name for f in self] - def sort_by_age(self,key='mtime',reverse=False): - assert key in ('atime','ctime','mtime'), f'{key!r}: invalid sort key' - self.sort( key=lambda a: getattr(a,key), reverse=reverse ) + def sort_by_age(self, key='mtime', reverse=False): + assert key in ('atime', 'ctime', 'mtime'), f'{key!r}: invalid sort key' + self.sort(key=lambda a: getattr(a, key), reverse=reverse) class MMGenFile(File): - def __init__(self,fn,base_class=None,subclass=None,proto=None,write=False): + def __init__(self, fn, base_class=None, subclass=None, proto=None, write=False): """ 'base_class' - a base class with an 'ext_to_cls' method 'subclass' - a subclass with an 'ext' attribute @@ -91,45 +91,45 @@ class MMGenFile(File): attribute to True. """ - super().__init__(fn,write) + super().__init__(fn, write) assert (subclass or base_class) and not (subclass and base_class), 'MMGenFile chk1' - if not getattr(subclass or base_class,'filename_api',False): - die(3,f'Class {(subclass or base_class).__name__!r} does not support the MMGenFile API') + if not getattr(subclass or base_class, 'filename_api', False): + die(3, f'Class {(subclass or base_class).__name__!r} does not support the MMGenFile API') if base_class: - subclass = base_class.ext_to_cls( self.ext, proto ) + subclass = base_class.ext_to_cls(self.ext, proto) if not subclass: - die( 'BadFileExtension', f'{self.ext!r}: not a recognized file extension for {base_class}' ) + die('BadFileExtension', f'{self.ext!r}: not a recognized file extension for {base_class}') self.subclass = subclass class MMGenFileList(FileList): - def __init__(self,fns,base_class,proto=None,write=False): + def __init__(self, fns, base_class, proto=None, write=False): list.__init__( self, - [MMGenFile( fn, base_class=base_class, proto=proto, write=write ) for fn in fns] ) + [MMGenFile(fn, base_class=base_class, proto=proto, write=write) for fn in fns]) -def find_files_in_dir(subclass,fdir,no_dups=False): +def find_files_in_dir(subclass, fdir, no_dups=False): - assert isinstance(subclass,type), f'{subclass}: not a class' + assert isinstance(subclass, type), f'{subclass}: not a class' - if not getattr(subclass,'filename_api',False): - die(3,f'Class {subclass.__name__!r} does not support the MMGenFile API') + if not getattr(subclass, 'filename_api', False): + die(3, f'Class {subclass.__name__!r} does not support the MMGenFile API') matches = [l for l in os.listdir(fdir) if l.endswith('.'+subclass.ext)] if no_dups: if len(matches) == 1: - return os.path.join(fdir,matches[0]) + return os.path.join(fdir, matches[0]) elif matches: - die(1,f'ERROR: more than one {subclass.__name__} file in directory {fdir!r}') + die(1, f'ERROR: more than one {subclass.__name__} file in directory {fdir!r}') else: return None else: - return [os.path.join(fdir,m) for m in matches] + return [os.path.join(fdir, m) for m in matches] -def find_file_in_dir(subclass,fdir): - return find_files_in_dir(subclass,fdir,no_dups=True) +def find_file_in_dir(subclass, fdir): + return find_files_in_dir(subclass, fdir, no_dups=True) diff --git a/mmgen/fileutil.py b/mmgen/fileutil.py index 56e594fa..a5c43b88 100755 --- a/mmgen/fileutil.py +++ b/mmgen/fileutil.py @@ -22,7 +22,7 @@ fileutil: Routines that read, write, execute or stat files # 09 Mar 2024: make all utils accept Path instances as arguments -import sys,os +import sys, os from .color import set_vt100 from .util import ( @@ -48,42 +48,42 @@ def check_or_create_dir(path): str(path)]) set_vt100() try: - os.makedirs(path,0o700) + os.makedirs(path, 0o700) except: die(2, f'ERROR: unable to read or create path ‘{path}’') def check_binary(args): - from subprocess import run,DEVNULL + from subprocess import run, DEVNULL try: - run(args,stdout=DEVNULL,stderr=DEVNULL,check=True) + run(args, stdout=DEVNULL, stderr=DEVNULL, check=True) except: - die(2,f'{args[0]!r} binary missing, not in path, or not executable') + die(2, f'{args[0]!r} binary missing, not in path, or not executable') set_vt100() -def shred_file(fn,verbose=False): - check_binary(['shred','--version']) +def shred_file(fn, verbose=False): + check_binary(['shred', '--version']) from subprocess import run run( - ['shred','--force','--iterations=30','--zero','--remove=wipesync'] + ['shred', '--force', '--iterations=30', '--zero', '--remove=wipesync'] + (['--verbose'] if verbose else []) + [str(fn)], - check=True ) + check=True) set_vt100() -def _check_file_type_and_access(fname,ftype,blkdev_ok=False): +def _check_file_type_and_access(fname, ftype, blkdev_ok=False): import stat - access,op_desc = ( - (os.W_OK,'writ') if ftype in ('output file','output directory') else - (os.R_OK,'read') ) + access, op_desc = ( + (os.W_OK, 'writ') if ftype in ('output file', 'output directory') else + (os.R_OK, 'read')) if ftype == 'output directory': ok_types = [(stat.S_ISDIR, 'output directory')] else: ok_types = [ - (stat.S_ISREG,'regular file'), - (stat.S_ISLNK,'symbolic link') + (stat.S_ISREG, 'regular file'), + (stat.S_ISLNK, 'symbolic link') ] if blkdev_ok: if not sys.platform in ('win32',): @@ -98,31 +98,31 @@ def _check_file_type_and_access(fname,ftype,blkdev_ok=False): if t[0](mode): break else: - ok_list = ' or '.join( t[1] for t in ok_types ) + ok_list = ' or '.join(t[1] for t in ok_types) die(1, f'Requested {ftype} ‘{fname}’ is not a {ok_list}') - if not os.access(fname,access): + if not os.access(fname, access): die(1, f'Requested {ftype} ‘{fname}’ is not {op_desc}able by you') return True -def check_infile(f,blkdev_ok=False): - return _check_file_type_and_access(f,'input file',blkdev_ok=blkdev_ok) +def check_infile(f, blkdev_ok=False): + return _check_file_type_and_access(f, 'input file', blkdev_ok=blkdev_ok) -def check_outfile(f,blkdev_ok=False): - return _check_file_type_and_access(f,'output file',blkdev_ok=blkdev_ok) +def check_outfile(f, blkdev_ok=False): + return _check_file_type_and_access(f, 'output file', blkdev_ok=blkdev_ok) def check_outdir(f): - return _check_file_type_and_access(f,'output directory') + return _check_file_type_and_access(f, 'output directory') -def get_seed_file(cfg,nargs,wallets=None,invoked_as=None): +def get_seed_file(cfg, nargs, wallets=None, invoked_as=None): wallets = wallets or cfg._args from .filename import find_file_in_dir from .wallet.mmgen import wallet - wf = find_file_in_dir(wallet,cfg.data_dir) + wf = find_file_in_dir(wallet, cfg.data_dir) wd_from_opt = bool(cfg.hidden_incog_input_params or cfg.in_fmt) # have wallet data from opt? @@ -138,16 +138,16 @@ def get_seed_file(cfg,nargs,wallets=None,invoked_as=None): if wallets or wf: check_infile(wallets[0] if wallets else wf) - return str(wallets[0]) if wallets else (wf,None)[wd_from_opt] # could be a Path instance + return str(wallets[0]) if wallets else (wf, None)[wd_from_opt] # could be a Path instance -def _open_or_die(filename,mode,silent=False): +def _open_or_die(filename, mode, silent=False): try: - return open(filename,mode) + return open(filename, mode) except: if silent: - die(2,'') + die(2, '') else: - fn = {0:'STDIN',1:'STDOUT',2:'STDERR'}[filename] if isinstance(filename,int) else f'‘{filename}’' + fn = {0:'STDIN', 1:'STDOUT', 2:'STDERR'}[filename] if isinstance(filename, int) else f'‘{filename}’' desc = 'reading' if 'r' in mode else 'writing' die(2, f'Unable to open file {fn} for {desc}') @@ -184,13 +184,13 @@ def write_data_to_file( cfg._util.qmsg('Output to STDOUT requested') if cfg.stdin_tty: if no_tty: - die(2,f'Printing {desc} to screen is not allowed') + die(2, f'Printing {desc} to screen is not allowed') if (ask_tty and not cfg.quiet) or binary: from .ui import confirm_or_raise confirm_or_raise( cfg, message = '', - action = f'output {desc} to screen' ) + action = f'output {desc} to screen') else: try: of = os.readlink(f'/proc/{os.getpid()}/fd/1') # Linux @@ -200,15 +200,15 @@ def write_data_to_file( if of: if of[:5] == 'pipe:': if no_tty: - die(2,f'Writing {desc} to pipe is not allowed') + die(2, f'Writing {desc} to pipe is not allowed') if ask_tty and not cfg.quiet: from .ui import confirm_or_raise confirm_or_raise( cfg, message = '', - action = f'output {desc} to pipe' ) + action = f'output {desc} to pipe') msg('') - of2,pd = os.path.relpath(of),os.path.pardir + of2, pd = os.path.relpath(of), os.path.pardir msg('Redirecting output to file {!r}'.format(of if of2[:len(pd)] == pd else of2)) else: msg('Redirecting output to file') @@ -216,15 +216,15 @@ def write_data_to_file( if binary: if sys.platform == 'win32': # condition on separate line for pylint import msvcrt - msvcrt.setmode(sys.stdout.fileno(),os.O_BINARY) + msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) # MSWin workaround. See msg_r() try: - sys.stdout.write(data.decode() if isinstance(data,bytes) else data) + sys.stdout.write(data.decode() if isinstance(data, bytes) else data) except: - os.write(1,data if isinstance(data,bytes) else data.encode()) + os.write(1, data if isinstance(data, bytes) else data.encode()) - def do_file(outfile,ask_write_prompt): + def do_file(outfile, ask_write_prompt): if (outdir or (cfg.outdir and not ignore_opt_outdir)) and not os.path.isabs(outfile): outfile = make_full_path(str(outdir or cfg.outdir), outfile) # outdir could be Path instance @@ -235,8 +235,8 @@ def write_data_to_file( if not keypress_confirm( cfg, ask_write_prompt, - default_yes = ask_write_default_yes ): - die(1,f'{capfirst(desc)} not saved') + default_yes = ask_write_default_yes): + die(1, f'{capfirst(desc)} not saved') hush = False if os.path.lexists(outfile) and ask_overwrite: @@ -244,7 +244,7 @@ def write_data_to_file( confirm_or_raise( cfg, message = '', - action = f'File {outfile!r} already exists\nOverwrite?' ) + action = f'File {outfile!r} already exists\nOverwrite?') msg(f'Overwriting file {outfile!r}') hush = True @@ -253,34 +253,34 @@ def write_data_to_file( if check_data: d = '' try: - with open(outfile,('r','rb')[bool(binary)]) as fp: + with open(outfile, ('r', 'rb')[bool(binary)]) as fp: d = fp.read() except: pass if d != cmp_data: - die(3,f'{desc} in file {outfile!r} has been altered by some other program! Aborting file write') + die(3, f'{desc} in file {outfile!r} has been altered by some other program! Aborting file write') # To maintain portability, always open files in binary mode # If 'binary' option not set, encode/decode data before writing and after reading try: - with _open_or_die(outfile,'wb') as fp: + with _open_or_die(outfile, 'wb') as fp: fp.write(data if binary else data.encode()) except: - die(2,f'Failed to write {desc} to file {outfile!r}') + die(2, f'Failed to write {desc} to file {outfile!r}') if not (hush or quiet): msg(f'{capfirst(desc)} written to file {outfile!r}') return True - if cfg.stdout or outfile in ('','-'): + if cfg.stdout or outfile in ('', '-'): do_stdout() elif sys.stdin.isatty() and not sys.stdout.isatty(): do_stdout() else: - do_file(outfile,ask_write_prompt) + do_file(outfile, ask_write_prompt) -def get_words_from_file(cfg,infile,desc,quiet=False): +def get_words_from_file(cfg, infile, desc, quiet=False): if not quiet: cfg._util.qmsg(f'Getting {desc} from file ‘{infile}’') @@ -291,7 +291,7 @@ def get_words_from_file(cfg,infile,desc,quiet=False): try: words = data.decode().split() except: - die(1,f'{capfirst(desc)} data must be UTF-8 encoded.') + die(1, f'{capfirst(desc)} data must be UTF-8 encoded.') cfg._util.dmsg('Sanitized input: [{}]'.format(' '.join(words))) @@ -304,7 +304,7 @@ def get_data_from_file( dash = False, silent = False, binary = False, - quiet = False ): + quiet = False): if not (cfg.quiet or silent or quiet): cfg._util.qmsg(f'Getting {desc} from file ‘{infile}’') @@ -319,8 +319,8 @@ def get_data_from_file( data = data.decode() if len(data) == cfg.max_input_size + 1: - die( 'MaxInputSizeExceeded', - f'Too much input data! Max input data size: {cfg.max_input_size} bytes' ) + die('MaxInputSizeExceeded', + f'Too much input data! Max input data size: {cfg.max_input_size} bytes') return data @@ -330,16 +330,16 @@ def get_lines_from_file( desc = 'data', trim_comments = False, quiet = False, - silent = False ): + silent = False): def decrypt_file_maybe(): - data = get_data_from_file( cfg, fn, desc=desc, binary=True, quiet=quiet, silent=silent ) + data = get_data_from_file(cfg, fn, desc=desc, binary=True, quiet=quiet, silent=silent) from .crypto import Crypto have_enc_ext = get_extension(fn) == Crypto.mmenc_ext if have_enc_ext or not is_utf8(data): - m = ('Attempting to decrypt','Decrypting')[have_enc_ext] + m = ('Attempting to decrypt', 'Decrypting')[have_enc_ext] cfg._util.qmsg(f'{m} {desc} ‘{fn}’') - data = Crypto(cfg).mmgen_decrypt_retry(data,desc) + data = Crypto(cfg).mmgen_decrypt_retry(data, desc) return data lines = decrypt_file_maybe().decode().splitlines() diff --git a/mmgen/flags.py b/mmgen/flags.py index 4159dd20..3531157b 100755 --- a/mmgen/flags.py +++ b/mmgen/flags.py @@ -20,27 +20,27 @@ flags: Class flags and opts for the MMGen suite """ -from .base_obj import AttrCtrl,Lockable -from .util import fmt_list,die +from .base_obj import AttrCtrl, Lockable +from .util import fmt_list, die class ClassFlags(AttrCtrl): _name = 'flags' _desc = 'flag' reserved_attrs = () - def __init__(self,parent,arg): + def __init__(self, parent, arg): self._parent = parent - self._available = getattr(self._parent,'avail_'+self._name) + self._available = getattr(self._parent, 'avail_'+self._name) for a in self._available: if a.startswith('_'): - die( 'ClassFlagsError', f'{a!r}: {self._desc} cannot begin with an underscore' ) + die('ClassFlagsError', f'{a!r}: {self._desc} cannot begin with an underscore') for b in self.reserved_attrs: if a == b: - die( 'ClassFlagsError', f'{a!r}: {b} is a reserved name for {self._desc}' ) + die('ClassFlagsError', f'{a!r}: {b} is a reserved name for {self._desc}') if arg: - assert type(arg) in (list,tuple), f"{arg!r}: {self._name!r} must be list or tuple" + assert type(arg) in (list, tuple), f"{arg!r}: {self._name!r} must be list or tuple" else: arg = [] @@ -49,15 +49,15 @@ class ClassFlags(AttrCtrl): self.not_available_error(e) for e in self._available: - setattr(self,e,e in arg) + setattr(self, e, e in arg) def __dir__(self): return [k for k in self.__dict__ if not k.startswith('_') and not k in self.reserved_attrs] def __str__(self): - return ' '.join(f'{k}={getattr(self,k)}' for k in dir(self)) + return ' '.join(f'{k}={getattr(self, k)}' for k in dir(self)) - def __setattr__(self,name,val): + def __setattr__(self, name, val): if self._locked: @@ -65,23 +65,23 @@ class ClassFlags(AttrCtrl): self.not_available_error(name) if self._name == 'flags': - assert isinstance(val,bool), f'{val!r} not boolean' - old_val = getattr(self,name) + assert isinstance(val, bool), f'{val!r} not boolean' + old_val = getattr(self, name) if val and old_val: - die( 'ClassFlagsError', f'{self._desc} {name!r} already set' ) + die('ClassFlagsError', f'{self._desc} {name!r} already set') if not val and not old_val: - die( 'ClassFlagsError', f'{self._desc} {name!r} not set, so cannot be unset' ) + die('ClassFlagsError', f'{self._desc} {name!r} not set, so cannot be unset') - super().__setattr__(name,val) + super().__setattr__(name, val) - def not_available_error(self,name): - die( 'ClassFlagsError', '{!r}: unrecognized {} for {}: (available {}: {})'.format( + def not_available_error(self, name): + die('ClassFlagsError', '{!r}: unrecognized {} for {}: (available {}: {})'.format( name, self._desc, type(self._parent).__name__, self._name, - fmt_list(self._available,fmt='bare') )) + fmt_list(self._available, fmt='bare'))) -class ClassOpts(ClassFlags,Lockable): +class ClassOpts(ClassFlags, Lockable): _name = 'opts' _desc = 'opt' diff --git a/mmgen/key.py b/mmgen/key.py index c87e33ce..673e46d0 100755 --- a/mmgen/key.py +++ b/mmgen/key.py @@ -20,41 +20,41 @@ key: MMGen public and private key objects """ -from .objmethods import HiliteStr,InitErrors,MMGenObject -from .obj import ImmutableAttr,get_obj +from .objmethods import HiliteStr, InitErrors, MMGenObject +from .obj import ImmutableAttr, get_obj -class WifKey(HiliteStr,InitErrors): +class WifKey(HiliteStr, InitErrors): """ Initialize a WIF key, checking its well-formedness. The numeric validity of the private key it encodes is not checked. """ width = 53 color = 'blue' - def __new__(cls,proto,wif): - if isinstance(wif,cls): + def __new__(cls, proto, wif): + if isinstance(wif, cls): return wif try: assert wif.isascii() and wif.isalnum(), 'not an ASCII alphanumeric string' proto.decode_wif(wif) # raises exception on error - return str.__new__(cls,wif) + return str.__new__(cls, wif) except Exception as e: - return cls.init_fail(e,wif) + return cls.init_fail(e, wif) -def is_wif(proto,s): - return get_obj( WifKey, proto=proto, wif=s, silent=True, return_bool=True ) +def is_wif(proto, s): + return get_obj(WifKey, proto=proto, wif=s, silent=True, return_bool=True) -class PubKey(bytes,InitErrors,MMGenObject): # TODO: add some real checks +class PubKey(bytes, InitErrors, MMGenObject): # TODO: add some real checks - def __new__(cls,s,compressed): + def __new__(cls, s, compressed): try: - assert isinstance(s,bytes) - me = bytes.__new__(cls,s) + assert isinstance(s, bytes) + me = bytes.__new__(cls, s) me.compressed = compressed return me except Exception as e: - return cls.init_fail(e,s) + return cls.init_fail(e, s) -class PrivKey(bytes,InitErrors,MMGenObject): +class PrivKey(bytes, InitErrors, MMGenObject): """ Input: a) raw, non-preprocessed bytes; or b) WIF key. Output: preprocessed key bytes, plus WIF key in 'wif' attribute @@ -65,49 +65,49 @@ class PrivKey(bytes,InitErrors,MMGenObject): width = 32 trunc_ok = False - compressed = ImmutableAttr(bool,typeconv=False) - wif = ImmutableAttr(WifKey,typeconv=False) + compressed = ImmutableAttr(bool, typeconv=False) + wif = ImmutableAttr(WifKey, typeconv=False) - # initialize with (priv_bin,compressed), WIF or self - def __new__(cls,proto,s=None,compressed=None,wif=None,pubkey_type=None): - if isinstance(s,cls): + # initialize with (priv_bin, compressed), WIF or self + def __new__(cls, proto, s=None, compressed=None, wif=None, pubkey_type=None): + if isinstance(s, cls): return s if wif: try: - assert s is None,"'wif' and key hex args are mutually exclusive" + assert s is None, "'wif' and key hex args are mutually exclusive" assert wif.isascii() and wif.isalnum(), 'not an ASCII alphanumeric string' k = proto.decode_wif(wif) # raises exception on error - me = bytes.__new__(cls,k.sec) + me = bytes.__new__(cls, k.sec) me.compressed = k.compressed me.pubkey_type = k.pubkey_type - me.wif = str.__new__(WifKey,wif) # check has been done + me.wif = str.__new__(WifKey, wif) # check has been done me.orig_bytes = None - if k.sec != proto.preprocess_key(k.sec,k.pubkey_type): + if k.sec != proto.preprocess_key(k.sec, k.pubkey_type): from .util import die - die( 'PrivateKeyError', - f'{proto.cls_name} WIF key {me.wif!r} encodes private key with invalid value {me}' ) + die('PrivateKeyError', + f'{proto.cls_name} WIF key {me.wif!r} encodes private key with invalid value {me}') me.proto = proto return me except Exception as e: - return cls.init_fail(e,s,objname=f'{proto.coin} WIF key') + return cls.init_fail(e, s, objname=f'{proto.coin} WIF key') else: try: - assert s,'private key bytes data missing' - assert isinstance(s,bytes),'input is not bytes' - assert pubkey_type is not None,"'pubkey_type' arg missing" + assert s, 'private key bytes data missing' + assert isinstance(s, bytes), 'input is not bytes' + assert pubkey_type is not None, "'pubkey_type' arg missing" assert len(s) == cls.width, f'key length must be {cls.width} bytes' if pubkey_type == 'password': # skip WIF creation and pre-processing for passwds - me = bytes.__new__(cls,s) + me = bytes.__new__(cls, s) else: assert compressed is not None, "'compressed' arg missing" - assert type(compressed) is bool,( - f"'compressed' must be of type bool, not {type(compressed).__name__}" ) - me = bytes.__new__( cls, proto.preprocess_key(s,pubkey_type) ) - me.wif = WifKey( proto, proto.encode_wif(me,pubkey_type,compressed) ) + assert type(compressed) is bool, ( + f"'compressed' must be of type bool, not {type(compressed).__name__}") + me = bytes.__new__(cls, proto.preprocess_key(s, pubkey_type)) + me.wif = WifKey(proto, proto.encode_wif(me, pubkey_type, compressed)) me.compressed = compressed me.pubkey_type = pubkey_type me.orig_bytes = s # save the non-preprocessed key me.proto = proto return me except Exception as e: - return cls.init_fail(e,s) + return cls.init_fail(e, s) diff --git a/mmgen/keygen.py b/mmgen/keygen.py index eb34ad93..dddb244c 100755 --- a/mmgen/keygen.py +++ b/mmgen/keygen.py @@ -28,39 +28,39 @@ keygen_public_data = namedtuple( 'pubkey', 'viewkey_bytes', 'pubkey_type', - 'compressed' ]) + 'compressed']) class keygen_base: - def __init__(self,cfg): + def __init__(self, cfg): if not (self.production_safe or cfg.test_suite): from .util import die die(2, f'Public key generator {type(self).__name__!r} is not safe from timing attacks ' 'and may only be used in a testing environment') - def gen_data(self,privkey): - assert isinstance(privkey,PrivKey) + def gen_data(self, privkey): + assert isinstance(privkey, PrivKey) return keygen_public_data( self.to_pubkey(privkey), self.to_viewkey(privkey), privkey.pubkey_type, - privkey.compressed ) + privkey.compressed) - def to_viewkey(self,privkey): + def to_viewkey(self, privkey): return None @classmethod - def get_clsname(cls,cfg,silent=False): + def get_clsname(cls, cfg, silent=False): return cls.__name__ backend_data = { 'std': { - 'backends': ('libsecp256k1','python-ecdsa'), + 'backends': ('libsecp256k1', 'python-ecdsa'), 'package': 'secp256k1', }, 'monero': { - 'backends': ('nacl','ed25519ll-djbec','ed25519'), + 'backends': ('nacl', 'ed25519ll-djbec', 'ed25519'), 'package': 'xmr', }, 'zcash_z': { @@ -76,11 +76,11 @@ def get_pubkey_type_cls(pubkey_type): import importlib return getattr( importlib.import_module(f'mmgen.proto.{backend_data[pubkey_type]["package"]}.keygen'), - 'backend' ) + 'backend') -def _check_backend(cfg,backend,pubkey_type,desc='keygen backend'): +def _check_backend(cfg, backend, pubkey_type, desc='keygen backend'): - from .util import is_int,die + from .util import is_int, die assert is_int(backend), f'illegal value for {desc} (must be an integer)' @@ -90,25 +90,21 @@ def _check_backend(cfg,backend,pubkey_type,desc='keygen backend'): die(1, f'{backend}: {desc} out of range\n' + 'Configured backends: ' + - ' '.join( f'{n}:{k}' for n,k in enumerate(backends,1) ) + ' '.join(f'{n}:{k}' for n, k in enumerate(backends, 1)) ) cfg._util.qmsg(f'Using backend {backends[int(backend)-1]!r} for public key generation') return True -def check_backend(cfg,proto,backend,addr_type): +def check_backend(cfg, proto, backend, addr_type): from .addr import MMGenAddrType - pubkey_type = MMGenAddrType(proto,addr_type or proto.dfl_mmtype).pubkey_type + pubkey_type = MMGenAddrType(proto, addr_type or proto.dfl_mmtype).pubkey_type - return _check_backend( - cfg, - backend, - pubkey_type, - desc = '--keygen-backend parameter' ) + return _check_backend(cfg, backend, pubkey_type, desc='--keygen-backend parameter') -def KeyGenerator(cfg,proto,pubkey_type,backend=None,silent=False): +def KeyGenerator(cfg, proto, pubkey_type, backend=None, silent=False): """ factory function returning a key generator backend for the specified pubkey type """ @@ -119,13 +115,13 @@ def KeyGenerator(cfg,proto,pubkey_type,backend=None,silent=False): backend = backend or cfg.keygen_backend if backend: - _check_backend( cfg, backend, pubkey_type ) + _check_backend(cfg, backend, pubkey_type) backend_id = backend_data[pubkey_type]['backends'][int(backend) - 1 if backend else 0] backend_clsname = getattr( pubkey_type_cls, - backend_id.replace('-','_') - ).get_clsname(cfg,silent=silent) + backend_id.replace('-', '_') + ).get_clsname(cfg, silent=silent) - return getattr(pubkey_type_cls,backend_clsname)(cfg) + return getattr(pubkey_type_cls, backend_clsname)(cfg) diff --git a/mmgen/led.py b/mmgen/led.py index ad2efd6b..dcaa46a9 100755 --- a/mmgen/led.py +++ b/mmgen/led.py @@ -29,31 +29,31 @@ from .color import blue, orange class LEDControl: - binfo = namedtuple('board_info',['name','status','trigger','trigger_states']) + binfo = namedtuple('board_info', ['name', 'status', 'trigger', 'trigger_states']) boards = { 'raspi_pi': binfo( name = 'Raspberry Pi', status = '/sys/class/leds/led0/brightness', trigger = '/sys/class/leds/led0/trigger', - trigger_states = ('none','mmc0') ), + trigger_states = ('none', 'mmc0')), 'orange_pi': binfo( name = 'Orange Pi (Armbian)', status = '/sys/class/leds/orangepi:red:status/brightness', trigger = None, - trigger_states = None ), + trigger_states = None), 'rock_pi': binfo( name = 'Rock Pi (Armbian)', status = '/sys/class/leds/status/brightness', trigger = '/sys/class/leds/status/trigger', - trigger_states = ('none','heartbeat') ), + trigger_states = ('none', 'heartbeat')), 'dummy': binfo( name = 'Fake', status = '/tmp/led_status', trigger = '/tmp/led_trigger', - trigger_states = ('none','original_value') ), + trigger_states = ('none', 'original_value')), } - def __init__(self,enabled,simulate=False,debug=False): + def __init__(self, enabled, simulate=False, debug=False): self.enabled = enabled self.debug = debug or simulate @@ -65,7 +65,7 @@ class LEDControl: self.ev = threading.Event() self.led_thread = None - for board_id,board in self.boards.items(): + for board_id, board in self.boards.items(): if board_id == 'dummy' and not simulate: continue try: @@ -75,20 +75,20 @@ class LEDControl: else: break else: - die( 'NoLEDSupport', 'Control files not found! LED control not supported on this system' ) + die('NoLEDSupport', 'Control files not found! LED control not supported on this system') msg(f'{board.name} board detected') if self.debug: msg(f'\n Status file: {board.status}\n Trigger file: {board.trigger}') - def check_access(fn,desc,init_val=None): + def check_access(fn, desc, init_val=None): def write_init_val(init_val): if not init_val: with open(fn) as fp: init_val = fp.read().strip() - with open(fn,'w') as fp: + with open(fn, 'w') as fp: fp.write(f'{init_val}\n') try: @@ -107,58 +107,58 @@ class LEDControl: )) sys.exit(1) - check_access(board.status,desc='status LED control') + check_access(board.status, desc='status LED control') if board.trigger: - check_access(board.trigger,desc='LED trigger',init_val=board.trigger_states[0]) + check_access(board.trigger, desc='LED trigger', init_val=board.trigger_states[0]) self.board = board @classmethod def create_dummy_control_files(cls): db = cls.boards['dummy'] - with open(db.status,'w') as fp: + with open(db.status, 'w') as fp: fp.write('0\n') - with open(db.trigger,'w') as fp: + with open(db.trigger, 'w') as fp: fp.write(db.trigger_states[1]+'\n') - def noop(self,*args,**kwargs): + def noop(self, *args, **kwargs): pass - def ev_sleep(self,secs): + def ev_sleep(self, secs): self.ev.wait(secs) return self.ev.is_set() - def led_loop(self,on_secs,off_secs): + def led_loop(self, on_secs, off_secs): if self.debug: - msg(f'led_loop({on_secs},{off_secs})') + msg(f'led_loop({on_secs}, {off_secs})') if not on_secs: - with open(self.board.status,'w') as fp: + with open(self.board.status, 'w') as fp: fp.write('0\n') while True: if self.ev_sleep(3600): return while True: - for s_time,val in ((on_secs,255),(off_secs,0)): + for s_time, val in ((on_secs, 255), (off_secs, 0)): if self.debug: - msg_r(('^','+')[bool(val)]) - with open(self.board.status,'w') as fp: + msg_r(('^', '+')[bool(val)]) + with open(self.board.status, 'w') as fp: fp.write(f'{val}\n') if self.ev_sleep(s_time): if self.debug: msg('\n') return - def set(self,state): - lt = namedtuple('led_timings',['on_secs','off_secs']) + def set(self, state): + lt = namedtuple('led_timings', ['on_secs', 'off_secs']) timings = { - 'off': lt( 0, 0 ), - 'standby': lt( 2.2, 0.2 ), - 'busy': lt( 0.06, 0.06 ), - 'error': lt( 0.5, 0.5 ) } + 'off': lt(0, 0), + 'standby': lt(2.2, 0.2), + 'busy': lt(0.06, 0.06), + 'error': lt(0.5, 0.5)} if self.led_thread: self.ev.set() @@ -186,5 +186,5 @@ class LEDControl: msg('Stopping LED') if self.board.trigger: - with open(self.board.trigger,'w') as fp: + with open(self.board.trigger, 'w') as fp: fp.write(self.board.trigger_states[1]+'\n') diff --git a/mmgen/main.py b/mmgen/main.py index bb14cf96..942245ef 100755 --- a/mmgen/main.py +++ b/mmgen/main.py @@ -20,15 +20,15 @@ main: Script launcher for the MMGen Project """ -import sys,os +import sys, os def launch(*, mod=None, func=None, fqmod=None, package='mmgen'): if sys.platform in ('linux', 'darwin') and sys.stdin.isatty(): - import termios,atexit + import termios, atexit fd = sys.stdin.fileno() old = termios.tcgetattr(fd) - atexit.register(lambda: termios.tcsetattr(fd,termios.TCSADRAIN,old)) + atexit.register(lambda: termios.tcsetattr(fd, termios.TCSADRAIN, old)) try: __import__(f'{package}.main_{mod}') if mod else func() if func else __import__(fqmod) @@ -47,9 +47,9 @@ def launch(*, mod=None, func=None, fqmod=None, package='mmgen'): errmsg = repr(e.args[0]) if e.args else repr(e) from collections import namedtuple - from .color import nocolor,yellow,red + from .color import nocolor, yellow, red - _o = namedtuple('exit_data',['color','exit_val','fs']) + _o = namedtuple('exit_data', ['color', 'exit_val', 'fs']) d = { 0: _o(nocolor, 0, '{message}'), 1: _o(nocolor, 1, '{message}'), @@ -57,14 +57,14 @@ def launch(*, mod=None, func=None, fqmod=None, package='mmgen'): 3: _o(yellow, 3, '\nMMGen Error ({name}):\n{message}'), 4: _o(red, 4, '\nMMGen Fatal Error ({name}):\n{message}'), 'x': _o(yellow, 5, '\nMMGen Unhandled Exception ({name}): {e}'), - }[getattr(e,'mmcode','x')] + }[getattr(e, 'mmcode', 'x')] - (sys.stdout if getattr(e,'stdout',None) else sys.stderr).write( + (sys.stdout if getattr(e, 'stdout', None) else sys.stderr).write( d.color(d.fs.format( name = type(e).__name__, message = errmsg.strip() or e, e = e)) - + '\n' ) + + '\n') if os.getenv('MMGEN_EXEC_WRAPPER') or os.getenv('MMGEN_TRACEBACK'): raise diff --git a/mmgen/mn_entry.py b/mmgen/mn_entry.py index 2b5d5cd7..b0688226 100755 --- a/mmgen/mn_entry.py +++ b/mmgen/mn_entry.py @@ -20,10 +20,10 @@ mn_entry.py - Mnemonic user entry methods for the MMGen suite """ -import sys,time +import sys, time -from .util import msg,msg_r,fmt,fmt_list,capfirst,die,ascii_lowercase -from .term import get_char,get_char_raw +from .util import msg, msg_r, fmt, fmt_list, capfirst, die, ascii_lowercase +from .term import get_char, get_char_raw from .color import cyan _return_chars = '\n\r ' @@ -43,19 +43,19 @@ class MnEntryMode: pad characters per word are permitted. """ - def __init__(self,mne): + def __init__(self, mne): self.pad_max_info = ' ' + self.pad_max_info.lstrip() if self.pad_max else '\n' self.mne = mne - def get_char(self,s): + def get_char(self, s): did_erase = False while True: - ch = get_char_raw('',num_bytes=1) + ch = get_char_raw('', num_bytes=1) if s and ch in _erase_chars: s = s[:-1] did_erase = True else: - return (ch,s,did_erase) + return (ch, s, did_erase) class MnEntryModeFull(MnEntryMode): name = 'Full' @@ -73,10 +73,10 @@ class MnEntryModeFull(MnEntryMode): def ss_len(self): return self.mne.longest_word - def get_word(self,mne): - s,pad = ('', 0) + def get_word(self, mne): + s, pad = ('', 0) while True: - ch,s,_ = self.get_char(s) + ch, s, _ = self.get_char(s) if ch in _return_chars: if s: break @@ -87,7 +87,7 @@ class MnEntryModeFull(MnEntryMode): if pad + len(s) > self.ss_len: break - return mne.idx(s,'full') + return mne.idx(s, 'full') class MnEntryModeShort(MnEntryMode): name = 'Short' @@ -104,7 +104,7 @@ class MnEntryModeShort(MnEntryMode): """ pad_max = 16 - def __init__(self,mne): + def __init__(self, mne): if mne.wl_id == 'bip39': self.prompt_info += ' ' + self.prompt_info_bip39_add.strip() super().__init__(mne) @@ -113,10 +113,10 @@ class MnEntryModeShort(MnEntryMode): def ss_len(self): return self.mne.uniq_ss_len - def get_word(self,mne): - s,pad = ('', 0) + def get_word(self, mne): + s, pad = ('', 0) while True: - ch,s,_ = self.get_char(s) + ch, s, _ = self.get_char(s) if ch in _return_chars: if s: break @@ -129,7 +129,7 @@ class MnEntryModeShort(MnEntryMode): if pad > self.pad_max: break - return mne.idx(s,'short') + return mne.idx(s, 'short') class MnEntryModeFixed(MnEntryMode): name = 'Fixed' @@ -140,14 +140,14 @@ class MnEntryModeFixed(MnEntryMode): prompt_info = """ Each word is entered automatically once exactly {ssl} characters are typed. """ - prompt_info_add = ( """ + prompt_info_add = (""" Words shorter than {ssl} letters must be padded to fit. """, """ {sw}-letter words must be padded with one pad character. - """ ) + """) pad_max = None - def __init__(self,mne): + def __init__(self, mne): self.len_diff = mne.uniq_ss_len - mne.shortest_word self.prompt_info += self.prompt_info_add[self.len_diff==1].lstrip() super().__init__(mne) @@ -156,23 +156,23 @@ class MnEntryModeFixed(MnEntryMode): def ss_len(self): return self.mne.uniq_ss_len - def get_word(self,mne): - s,pad = ('', 0) + def get_word(self, mne): + s, pad = ('', 0) while True: - ch,s,_ = self.get_char(s) + ch, s, _ = self.get_char(s) if ch in _return_chars: if s: break elif ch in ascii_lowercase: s += ch if len(s) + pad == self.ss_len: - return mne.idx(s,'short') + return mne.idx(s, 'short') else: pad += 1 if pad > self.len_diff: return None if len(s) + pad == self.ss_len: - return mne.idx(s,'short') + return mne.idx(s, 'short') class MnEntryModeMinimal(MnEntryMode): name = 'Minimal' @@ -191,26 +191,26 @@ class MnEntryModeMinimal(MnEntryMode): pad_max = 16 ss_len = None - def get_word(self,mne): - s,pad = ('', 0) - lo,hi = (0, len(mne.wl) - 1) + def get_word(self, mne): + s, pad = ('', 0) + lo, hi = (0, len(mne.wl) - 1) while True: - ch,s,did_erase = self.get_char(s) + ch, s, did_erase = self.get_char(s) if did_erase: - lo,hi = (0, len(mne.wl) - 1) + lo, hi = (0, len(mne.wl) - 1) if ch in _return_chars: if s: - return mne.idx(s,'full',lo_idx=lo,hi_idx=hi) + return mne.idx(s, 'full', lo_idx=lo, hi_idx=hi) elif ch in ascii_lowercase: s += ch - ret = mne.idx(s,'minimal',lo_idx=lo,hi_idx=hi) - if not isinstance(ret,tuple): + ret = mne.idx(s, 'minimal', lo_idx=lo, hi_idx=hi) + if not isinstance(ret, tuple): return ret - lo,hi = ret + lo, hi = ret else: pad += 1 if pad > self.pad_max: - return mne.idx(s,'full',lo_idx=lo,hi_idx=hi) + return mne.idx(s, 'full', lo_idx=lo, hi_idx=hi) class MnemonicEntry: @@ -225,13 +225,13 @@ class MnemonicEntry: acters may be typed before, after, or in the middle of words. """, } - word_prompt = ('Enter word #{}: ','Incorrect entry. Repeat word #{}: ') + word_prompt = ('Enter word #{}: ', 'Incorrect entry. Repeat word #{}: ') usr_dfl_entry_mode = None _lw = None _sw = None _usl = None - def __init__(self,cfg): + def __init__(self, cfg): self.cfg = cfg self.set_dfl_entry_mode() @@ -252,7 +252,7 @@ class MnemonicEntry: if not self._usl: usl = 0 for i in range(len(self.wl)-1): - w1,w2 = self.wl[i],self.wl[i+1] + w1, w2 = self.wl[i], self.wl[i+1] while True: if w1[:usl] == w2[:usl]: usl += 1 @@ -261,7 +261,7 @@ class MnemonicEntry: self._usl = usl return self._usl - def idx(self,w,entry_mode,lo_idx=None,hi_idx=None): + def idx(self, w, entry_mode, lo_idx=None, hi_idx=None): """ Return values: - all modes: @@ -285,9 +285,9 @@ class MnemonicEntry: if cur_w == w: if entry_mode == 'minimal': if idx > 0 and self.wl[idx-1][:len(w)] == w: - return (lo,hi) + return (lo, hi) elif idx < last_idx and self.wl[idx+1][:len(w)] == w: - return (lo,hi) + return (lo, hi) return idx elif hi <= lo: return None @@ -296,17 +296,17 @@ class MnemonicEntry: else: lo = idx + 1 - def get_cls_by_entry_mode(self,entry_mode): + def get_cls_by_entry_mode(self, entry_mode): return getattr(sys.modules[__name__], 'MnEntryMode' + capfirst(entry_mode)) def choose_entry_mode(self): msg('Choose an entry mode:\n') em_objs = [self.get_cls_by_entry_mode(entry_mode)(self) for entry_mode in self.entry_modes] - for n,mode in enumerate(em_objs,1): + for n, mode in enumerate(em_objs, 1): msg(' {}) {:8} {}'.format( n, mode.name + ':', - fmt(mode.choose_info,' '*14).lstrip().format(usl=self.uniq_ss_len), + fmt(mode.choose_info, ' '*14).lstrip().format(usl=self.uniq_ss_len), )) prompt = f'Type a number, or hit ENTER for the default ({capfirst(self.dfl_entry_mode)}): ' erase = '\r' + ' ' * (len(prompt)+19) + '\r' @@ -315,7 +315,7 @@ class MnemonicEntry: if uret == '': msg_r(erase) return self.get_cls_by_entry_mode(self.dfl_entry_mode)(self) - elif uret in [str(i) for i in range(1,len(em_objs)+1)]: + elif uret in [str(i) for i in range(1, len(em_objs)+1)]: msg_r(erase) return em_objs[int(uret)-1] else: @@ -323,7 +323,7 @@ class MnemonicEntry: time.sleep(self.cfg.err_disp_timeout) msg_r(erase) - def get_mnemonic_from_user(self,mn_len,validate=True): + def get_mnemonic_from_user(self, mn_len, validate=True): mll = list(self.bconv.seedlen_map_rev) assert mn_len in mll, f'{mn_len}: invalid mnemonic length (must be one of {mll})' @@ -349,8 +349,8 @@ class MnemonicEntry: sw = self.shortest_word, )) - clear_line = '\n' if self.cfg.test_suite else '{r}{s}{r}'.format(r='\r',s=' '*40) - idx,idxs = 1,[] # initialize idx to a non-None value + clear_line = '\n' if self.cfg.test_suite else '{r}{s}{r}'.format(r='\r', s=' '*40) + idx, idxs = 1, [] # initialize idx to a non-None value while len(idxs) < mn_len: msg_r(self.word_prompt[idx is None].format(len(idxs)+1)) @@ -369,7 +369,7 @@ class MnemonicEntry: return ' '.join(words) @classmethod - def get_cls_by_wordlist(cls,wl): + def get_cls_by_wordlist(cls, wl): d = { 'mmgen': MnemonicEntryMMGen, 'bip39': MnemonicEntryBIP39, @@ -385,7 +385,7 @@ class MnemonicEntry: In addition to setting the default entry mode for the current wordlist, checks validity of all user-configured entry modes """ - for k,v in self.cfg.mnemonic_entry_modes.items(): + for k, v in self.cfg.mnemonic_entry_modes.items(): cls = self.get_cls_by_wordlist(k) if v not in cls.entry_modes: errmsg = f""" @@ -393,37 +393,37 @@ class MnemonicEntry: Entry mode {v!r} not recognized for wordlist {k!r}: Valid choices: {fmt_list(cls.entry_modes)} """ - die(2, '\n' + fmt(errmsg,indent=' ')) + die(2, '\n' + fmt(errmsg, indent=' ')) if cls == type(self): self.usr_dfl_entry_mode = v class MnemonicEntryMMGen(MnemonicEntry): wl_id = 'mmgen' modname = 'baseconv' - entry_modes = ('full','minimal','fixed') + entry_modes = ('full', 'minimal', 'fixed') dfl_entry_mode = 'minimal' has_chksum = False class MnemonicEntryBIP39(MnemonicEntry): wl_id = 'bip39' modname = 'bip39' - entry_modes = ('full','short','fixed') + entry_modes = ('full', 'short', 'fixed') dfl_entry_mode = 'fixed' has_chksum = True class MnemonicEntryMonero(MnemonicEntry): wl_id = 'xmrseed' modname = 'xmrseed' - entry_modes = ('full','short') + entry_modes = ('full', 'short') dfl_entry_mode = 'short' has_chksum = True -def mn_entry(cfg,wl_id,entry_mode=None): +def mn_entry(cfg, wl_id, entry_mode=None): if wl_id == 'words': wl_id = 'mmgen' me = MnemonicEntry.get_cls_by_wordlist(wl_id)(cfg) import importlib - me.bconv = getattr(importlib.import_module(f'mmgen.{me.modname}'),me.modname)(wl_id) + me.bconv = getattr(importlib.import_module(f'mmgen.{me.modname}'), me.modname)(wl_id) me.wl = me.bconv.digits if entry_mode: me.em = getattr(sys.modules[__name__], 'MnEntryMode' + capfirst(entry_mode))(me) diff --git a/mmgen/msg.py b/mmgen/msg.py index b01ac994..92926b68 100755 --- a/mmgen/msg.py +++ b/mmgen/msg.py @@ -12,40 +12,40 @@ msg: base message signing classes """ -import os,importlib,json +import os, importlib, json from .cfg import gc -from .objmethods import MMGenObject,HiliteStr,InitErrors -from .util import msg,die,make_chksum_6,fmt_list,remove_dups -from .color import red,orange,grnbg +from .objmethods import MMGenObject, HiliteStr, InitErrors +from .util import msg, die, make_chksum_6, fmt_list, remove_dups +from .color import red, orange, grnbg from .protocol import init_proto -from .fileutil import get_data_from_file,write_data_to_file -from .addr import MMGenID,CoinAddr +from .fileutil import get_data_from_file, write_data_to_file +from .addr import MMGenID, CoinAddr -class MMGenIDRange(HiliteStr,InitErrors,MMGenObject): +class MMGenIDRange(HiliteStr, InitErrors, MMGenObject): """ closely based on MMGenID """ color = 'orange' width = 0 trunc_ok = False - def __new__(cls,proto,id_str): + def __new__(cls, proto, id_str): from .addrlist import AddrIdxList from .addr import AddrListID from .seed import SeedID try: ss = str(id_str).split(':') - assert len(ss) in (2,3),'not 2 or 3 colon-separated items' - t = proto.addr_type((ss[1],proto.dfl_mmtype)[len(ss)==2]) - me = str.__new__(cls,'{}:{}:{}'.format(ss[0],t,ss[-1])) + assert len(ss) in (2, 3), 'not 2 or 3 colon-separated items' + t = proto.addr_type((ss[1], proto.dfl_mmtype)[len(ss)==2]) + me = str.__new__(cls, '{}:{}:{}'.format(ss[0], t, ss[-1])) me.sid = SeedID(sid=ss[0]) me.idxlist = AddrIdxList(ss[-1]) me.mmtype = t assert t in proto.mmtypes, f'{t}: invalid address type for {proto.cls_name}' - me.al_id = str.__new__(AddrListID,me.sid+':'+me.mmtype) # checks already done + me.al_id = str.__new__(AddrListID, me.sid+':'+me.mmtype) # checks already done me.proto = proto return me except Exception as e: - return cls.init_fail(e,id_str) + return cls.init_fail(e, id_str) class coin_msg: @@ -53,7 +53,7 @@ class coin_msg: ext = 'rawmsg.json' signed = False - chksum_keys = ('addrlists','message','msghash_type','network') + chksum_keys = ('addrlists', 'message', 'msghash_type', 'network') @property def desc(self): @@ -70,11 +70,11 @@ class coin_msg: @property def filename_stem(self): - coin,network = self.data['network'].split('_') + coin, network = self.data['network'].split('_') return '{}[{}]{}'.format( self.chksum.upper(), coin.upper(), - ('' if network == 'mainnet' else '.'+network) ) + ('' if network == 'mainnet' else '.'+network)) @property def filename(self): @@ -85,13 +85,13 @@ class coin_msg: return f'{self.filename_stem}.{coin_msg.signed.ext}' @staticmethod - def get_proto_from_file(cfg,filename): + def get_proto_from_file(cfg, filename): data = json.loads(get_data_from_file(cfg, filename)) network_id = data['metadata']['network'] if 'metadata' in data else data['network'].lower() - coin,network = network_id.split('_') + coin, network = network_id.split('_') return init_proto(cfg=cfg, coin=coin, network=network) - def write_to_file(self,outdir=None,ask_overwrite=False): + def write_to_file(self, outdir=None, ask_overwrite=False): data = { 'id': f'{gc.proj_name} {self.desc}', 'metadata': self.data, @@ -100,23 +100,23 @@ class coin_msg: write_data_to_file( cfg = self.cfg, - outfile = os.path.join(outdir or '',self.filename), - data = json.dumps(data,sort_keys=True,indent=4), + outfile = os.path.join(outdir or '', self.filename), + data = json.dumps(data, sort_keys=True, indent=4), desc = self.desc, - ask_overwrite = ask_overwrite ) + ask_overwrite = ask_overwrite) class new(base): - def __init__(self,message,addrlists,msghash_type,*args,**kwargs): + def __init__(self, message, addrlists, msghash_type, *args, **kwargs): msghash_type = msghash_type or self.msg_cls.msghash_types[0] if msghash_type not in self.msg_cls.msghash_types: - die(2,f'msghash_type {msghash_type!r} not supported for {self.proto.base_proto} protocol') + die(2, f'msghash_type {msghash_type!r} not supported for {self.proto.base_proto} protocol') self.data = { - 'network': '{}_{}'.format( self.proto.coin.lower(), self.proto.network ), - 'addrlists': [MMGenIDRange(self.proto,i) for i in addrlists.split()], + 'network': '{}_{}'.format(self.proto.coin.lower(), self.proto.network), + 'addrlists': [MMGenIDRange(self.proto, i) for i in addrlists.split()], 'message': message, 'msghash_type': msghash_type, } @@ -124,7 +124,7 @@ class coin_msg: class completed(base): - def __init__(self,data,infile,*args,**kwargs): + def __init__(self, data, infile, *args, **kwargs): if data: self.__dict__ = data @@ -133,14 +133,14 @@ class coin_msg: self.data = get_data_from_file( cfg = self.cfg, infile = infile, - desc = self.desc ) + desc = self.desc) d = json.loads(self.data) self.data = d['metadata'] self.sigs = d['signatures'] - self.addrlists = [MMGenIDRange(self.proto,i) for i in self.data['addrlists']] + self.addrlists = [MMGenIDRange(self.proto, i) for i in self.data['addrlists']] - def format(self,req_addr=None): + def format(self, req_addr=None): labels = { 'addr': 'address:', @@ -152,37 +152,37 @@ class coin_msg: def gen_entry(e): for k in labels: if e.get(k): - yield fs_sig.format( labels[k], e[k] ) + yield fs_sig.format(labels[k], e[k]) def gen_all(): - for k,v in hdr_data.items(): - yield fs_hdr.format( v[0], v[1](self.data[k]) ) + for k, v in hdr_data.items(): + yield fs_hdr.format(v[0], v[1](self.data[k])) if self.sigs: yield '' yield 'Signatures:' - for n,(k,v) in enumerate(self.sigs.items()): + for n, (k, v) in enumerate(self.sigs.items()): yield '' yield f'{n+1:>3}) {k}' yield from gen_entry(v) def gen_single(): - for k,v in hdr_data.items(): - yield fs_hdr.format( v[0], v[1](self.data[k]) ) + for k, v in hdr_data.items(): + yield fs_hdr.format(v[0], v[1](self.data[k])) if self.sigs: yield 'Signature data:' k = ( - CoinAddr(self.proto,req_addr) if type(self).__name__ == 'exported_sigs' else - MMGenID(self.proto,req_addr) ) + CoinAddr(self.proto, req_addr) if type(self).__name__ == 'exported_sigs' else + MMGenID(self.proto, req_addr)) if k not in self.sigs: - die(1,f'{k}: address not found in signature data') + die(1, f'{k}: address not found in signature data') yield from gen_entry(self.sigs[k]) hdr_data = { - 'message': ('Message:', grnbg ), - 'network': ('Network:', lambda v: v.replace('_',' ').upper() ), - 'msghash_type': ('Message Hash Type:', lambda v: v ), - 'addrlists': ('Address Ranges:', lambda v: fmt_list(v,fmt='bare') ), - 'failed_sids': ('Failed Seed IDs:', lambda v: red(fmt_list(v,fmt='bare')) ), + 'message': ('Message:', grnbg), + 'network': ('Network:', lambda v: v.replace('_', ' ').upper()), + 'msghash_type': ('Message Hash Type:', lambda v: v), + 'addrlists': ('Address Ranges:', lambda v: fmt_list(v, fmt='bare')), + 'failed_sids': ('Failed Seed IDs:', lambda v: red(fmt_list(v, fmt='bare'))), } if len(self.msg_cls.msghash_types) == 1: @@ -212,7 +212,7 @@ class coin_msg: from .addrlist import KeyAddrList - async def sign_list(al_in,seed): + async def sign_list(al_in, seed): al = KeyAddrList( cfg = self.cfg, proto = self.proto, @@ -220,13 +220,13 @@ class coin_msg: addr_idxs = al_in.idxlist, mmtype = al_in.mmtype, skip_chksum = True, - add_p2pkh = al_in.mmtype in ('S','B') ) + add_p2pkh = al_in.mmtype in ('S', 'B')) for e in al.data: sig = await self.do_sign( wif = e.sec.wif, message = self.data['message'], - msghash_type = self.data['msghash_type'] ) + msghash_type = self.data['msghash_type']) mmid = f'{al_in.sid}:{al_in.mmtype}:{e.idx}' data = { @@ -234,7 +234,7 @@ class coin_msg: 'sig': sig, } if self.msg_cls.include_pubhash: - data.update({ 'pubhash': self.proto.decode_addr(e.addr_p2pkh or e.addr).bytes.hex() }) + data.update({'pubhash': self.proto.decode_addr(e.addr_p2pkh or e.addr).bytes.hex()}) if e.addr_p2pkh: data.update({'addr_p2pkh': e.addr_p2pkh}) @@ -243,7 +243,7 @@ class coin_msg: if self.proto.sign_mode == 'daemon': from .rpc import rpc_init - self.rpc = await rpc_init( self.cfg, self.proto, ignore_wallet=True ) + self.rpc = await rpc_init(self.cfg, self.proto, ignore_wallet=True) from .wallet import Wallet wallet_seeds = [Wallet(cfg=self.cfg, fn=fn, passwd_file=passwd_file).seed for fn in wallet_files] @@ -261,7 +261,7 @@ class coin_msg: # Then subseeds: for sid in need_sids: for seed in wallet_seeds: - subseed = seed.subseeds.get_subseed_by_seed_id(sid,print_msg=True) + subseed = seed.subseeds.get_subseed_by_seed_id(sid, print_msg=True) if subseed: saved_seeds.append(subseed) need_sids.remove(sid) @@ -270,11 +270,11 @@ class coin_msg: for al in self.addrlists: for seed in saved_seeds: if al.sid == seed.sid: - await sign_list(al,seed) + await sign_list(al, seed) break if need_sids: - msg('Failed Seed IDs: {}'.format(orange(fmt_list(need_sids,fmt='bare')))) + msg('Failed Seed IDs: {}'.format(orange(fmt_list(need_sids, fmt='bare')))) self.data['failed_sids'] = need_sids @@ -285,45 +285,45 @@ class coin_msg: class signed_online(signed): - def get_sigs(self,addr): + def get_sigs(self, addr): if addr: req_addr = ( - CoinAddr(self.proto,addr) if type(self).__name__ == 'exported_sigs' else - MMGenID(self.proto,addr) ) - sigs = {k:v for k,v in self.sigs.items() if k == req_addr} + CoinAddr(self.proto, addr) if type(self).__name__ == 'exported_sigs' else + MMGenID(self.proto, addr)) + sigs = {k:v for k, v in self.sigs.items() if k == req_addr} else: sigs = self.sigs if not sigs: - die(1,'No signatures') + die(1, 'No signatures') return sigs - async def verify(self,addr=None): + async def verify(self, addr=None): sigs = self.get_sigs(addr) if self.proto.sign_mode == 'daemon': from .rpc import rpc_init - self.rpc = await rpc_init( self.cfg, self.proto, ignore_wallet=True ) + self.rpc = await rpc_init(self.cfg, self.proto, ignore_wallet=True) - for k,v in sigs.items(): + for k, v in sigs.items(): ret = await self.do_verify( addr = v.get('addr_p2pkh') or v['addr'], sig = v['sig'], message = self.data['message'], - msghash_type = self.data['msghash_type'] ) + msghash_type = self.data['msghash_type']) if not ret: - die(3,f'Invalid signature for address {k} ({v["addr"]})') + die(3, f'Invalid signature for address {k} ({v["addr"]})') return len(sigs) - def get_json_for_export(self,addr=None): - sigs = list( self.get_sigs(addr).values() ) + def get_json_for_export(self, addr=None): + sigs = list(self.get_sigs(addr).values()) pfx = self.msg_cls.sigdata_pfx if pfx: - sigs = [{k:pfx+v for k,v in e.items()} for e in sigs] + sigs = [{k:pfx+v for k, v in e.items()} for e in sigs] return json.dumps( { 'message': self.data['message'], @@ -337,23 +337,23 @@ class coin_msg: class exported_sigs(signed_online): - def __init__(self,infile,*args,**kwargs): + def __init__(self, infile, *args, **kwargs): self.data = json.loads( get_data_from_file( cfg = self.cfg, infile = infile, - desc = self.desc ) + desc = self.desc) ) pfx = self.msg_cls.sigdata_pfx self.sigs = {sig_data['addr']:sig_data for sig_data in ( - [{k:v[len(pfx):] for k,v in e.items()} for e in self.data['signatures']] + [{k:v[len(pfx):] for k, v in e.items()} for e in self.data['signatures']] if pfx else self.data['signatures'] )} -def _get_obj(clsname,cfg,*args,coin=None,network='mainnet',infile=None,data=None,**kwargs): +def _get_obj(clsname, cfg, *args, coin=None, network='mainnet', infile=None, data=None, **kwargs): assert not args, 'msg:_get_obj(): only keyword args allowed' @@ -364,27 +364,27 @@ def _get_obj(clsname,cfg,*args,coin=None,network='mainnet',infile=None,data=None proto = ( data['proto'] if data else - init_proto( cfg=cfg, coin=coin, network=network ) if coin else - coin_msg.base.get_proto_from_file(cfg,infile) ) + init_proto(cfg=cfg, coin=coin, network=network) if coin else + coin_msg.base.get_proto_from_file(cfg, infile)) try: msg_cls = getattr( importlib.import_module(f'mmgen.proto.{proto.base_proto_coin.lower()}.msg'), - 'coin_msg' ) + 'coin_msg') except: - die(1,f'Message signing operations not supported for {proto.base_proto} protocol') + die(1, f'Message signing operations not supported for {proto.base_proto} protocol') - me = MMGenObject.__new__(getattr( msg_cls, clsname, getattr(coin_msg,clsname) )) + me = MMGenObject.__new__(getattr(msg_cls, clsname, getattr(coin_msg, clsname))) me.msg_cls = msg_cls me.cfg = cfg me.proto = proto - me.__init__(infile=infile,data=data,*args,**kwargs) + me.__init__(infile=infile, data=data, *args, **kwargs) return me def _get(clsname): - return lambda *args,**kwargs: _get_obj(clsname,*args,**kwargs) + return lambda *args, **kwargs: _get_obj(clsname, *args, **kwargs) NewMsg = _get('new') CompletedMsg = _get('completed') diff --git a/mmgen/obj.py b/mmgen/obj.py index ea3e0404..63cff9d1 100755 --- a/mmgen/obj.py +++ b/mmgen/obj.py @@ -22,9 +22,9 @@ obj: MMGen native classes import unicodedata -from .objmethods import MMGenObject,Hilite,HiliteStr,InitErrors +from .objmethods import MMGenObject, Hilite, HiliteStr, InitErrors -def get_obj(objname,*args,**kwargs): +def get_obj(objname, *args, **kwargs): """ Wrapper for data objects - If the object throws an exception on instantiation, return False, otherwise return the object. @@ -34,7 +34,7 @@ def get_obj(objname,*args,**kwargs): """ assert not args, 'get_obj_chk1' - silent,return_bool = (False,False) + silent, return_bool = (False, False) if 'silent' in kwargs: silent = kwargs['silent'] del kwargs['silent'] @@ -55,44 +55,44 @@ def get_obj(objname,*args,**kwargs): # dict that keeps a list of keys for efficient lookup by index class IndexedDict(dict): - def __init__(self,*args,**kwargs): + def __init__(self, *args, **kwargs): if args or kwargs: self.die('initializing values via constructor') self.__keylist = [] - dict.__init__(self,*args,**kwargs) + dict.__init__(self, *args, **kwargs) - def __setitem__(self,key,value): + def __setitem__(self, key, value): if key in self: self.die('reassignment to existing key') self.__keylist.append(key) - return dict.__setitem__(self,key,value) + return dict.__setitem__(self, key, value) @property def keys(self): return self.__keylist - def key(self,idx): + def key(self, idx): return self.__keylist[idx] - def __delitem__(self,*args): + def __delitem__(self, *args): self.die('item deletion') - def move_to_end(self,*args): + def move_to_end(self, *args): self.die('item moving') - def clear(self,*args): + def clear(self, *args): self.die('clearing') - def update(self,*args): + def update(self, *args): self.die('updating') - def die(self,desc): + def die(self, desc): raise NotImplementedError(f'{desc} not implemented for type {type(self).__name__}') -class MMGenList(list,MMGenObject): +class MMGenList(list, MMGenObject): pass -class MMGenDict(dict,MMGenObject): +class MMGenDict(dict, MMGenObject): pass class ImmutableAttr: # Descriptor @@ -100,53 +100,53 @@ class ImmutableAttr: # Descriptor For attributes that are always present in the data instance Reassignment and deletion forbidden """ - ok_dtypes = (type,type(None),type(lambda:0)) + ok_dtypes = (type, type(None), type(lambda:0)) - def __init__(self,dtype,typeconv=True,set_none_ok=False,include_proto=False): + def __init__(self, dtype, typeconv=True, set_none_ok=False, include_proto=False): self.set_none_ok = set_none_ok self.typeconv = typeconv - assert isinstance(dtype,self.ok_dtypes), 'ImmutableAttr_check1' + assert isinstance(dtype, self.ok_dtypes), 'ImmutableAttr_check1' if include_proto: assert typeconv, 'ImmutableAttr_check2' if set_none_ok: - assert typeconv and not isinstance(dtype,str), 'ImmutableAttr_check3' + assert typeconv and not isinstance(dtype, str), 'ImmutableAttr_check3' if typeconv: # convert this attribute's type if set_none_ok: - self.conv = lambda instance,value: None if value is None else dtype(value) + self.conv = lambda instance, value: None if value is None else dtype(value) elif include_proto: - self.conv = lambda instance,value: dtype(instance.proto,value) + self.conv = lambda instance, value: dtype(instance.proto, value) else: - self.conv = lambda instance,value: dtype(value) + self.conv = lambda instance, value: dtype(value) else: # check this attribute's type - def assign_with_check(instance,value): + def assign_with_check(instance, value): if type(value) is dtype: return value raise TypeError('Attribute {!r} of {} instance must of type {}'.format( self.name, type(instance).__name__, - dtype )) + dtype)) self.conv = assign_with_check - def __set_name__(self,owner,name): + def __set_name__(self, owner, name): self.name = name - def __get__(self,instance,owner): + def __get__(self, instance, owner): return instance.__dict__[self.name] - def setattr_condition(self,instance): + def setattr_condition(self, instance): 'forbid all reassignment' return not self.name in instance.__dict__ - def __set__(self,instance,value): + def __set__(self, instance, value): if not self.setattr_condition(instance): raise AttributeError(f'Attribute {self.name!r} of {type(instance)} instance cannot be reassigned') - instance.__dict__[self.name] = self.conv(instance,value) + instance.__dict__[self.name] = self.conv(instance, value) - def __delete__(self,instance): + def __delete__(self, instance): raise AttributeError( f'Attribute {self.name!r} of {type(instance).__name__} instance cannot be deleted') @@ -155,27 +155,27 @@ class ListItemAttr(ImmutableAttr): For attributes that might not be present in the data instance Reassignment or deletion allowed if specified """ - def __init__(self,dtype,typeconv=True,include_proto=False,reassign_ok=False,delete_ok=False): + def __init__(self, dtype, typeconv=True, include_proto=False, reassign_ok=False, delete_ok=False): self.reassign_ok = reassign_ok self.delete_ok = delete_ok - ImmutableAttr.__init__(self,dtype,typeconv=typeconv,include_proto=include_proto) + ImmutableAttr.__init__(self, dtype, typeconv=typeconv, include_proto=include_proto) - def __get__(self,instance,owner): + def __get__(self, instance, owner): "return None if attribute doesn't exist" try: return instance.__dict__[self.name] except: return None - def setattr_condition(self,instance): - return getattr(instance,self.name) is None or self.reassign_ok + def setattr_condition(self, instance): + return getattr(instance, self.name) is None or self.reassign_ok - def __delete__(self,instance): + def __delete__(self, instance): if self.delete_ok: if self.name in instance.__dict__: del instance.__dict__[self.name] else: - ImmutableAttr.__delete__(self,instance) + ImmutableAttr.__delete__(self, instance) class MMGenListItem(MMGenObject): valid_attrs = set() @@ -189,7 +189,7 @@ class MMGenListItem(MMGenObject): 'immutable_attr_init_check', } - def __init__(self,*args,**kwargs): + def __init__(self, *args, **kwargs): # generate valid_attrs, or use the class valid_attrs if set self.__dict__['valid_attrs'] = self.valid_attrs or ( {e for e in dir(self) if e[0] != '_'} @@ -200,49 +200,49 @@ class MMGenListItem(MMGenObject): if args: raise ValueError(f'Non-keyword args not allowed in {type(self).__name__!r} constructor') - for k,v in kwargs.items(): + for k, v in kwargs.items(): if v is not None: - setattr(self,k,v) + setattr(self, k, v) # Require all immutables to be initialized. Check performed only when testing. self.immutable_attr_init_check() # allow only valid attributes to be set - def __setattr__(self,name,value): + def __setattr__(self, name, value): if name not in self.valid_attrs: raise AttributeError(f'{name!r}: no such attribute in class {type(self)}') - return object.__setattr__(self,name,value) + return object.__setattr__(self, name, value) def _asdict(self): - return dict((k,v) for k,v in self.__dict__.items() if k in self.valid_attrs) + return dict((k, v) for k, v in self.__dict__.items() if k in self.valid_attrs) -class MMGenRange(tuple,InitErrors,MMGenObject): +class MMGenRange(tuple, InitErrors, MMGenObject): min_idx = None max_idx = None - def __new__(cls,*args): + def __new__(cls, *args): try: if len(args) == 1: s = args[0] - if isinstance(s,cls): + if isinstance(s, cls): return s - assert isinstance(s,str),'not a string or string subclass' - ss = s.split('-',1) + assert isinstance(s, str), 'not a string or string subclass' + ss = s.split('-', 1) first = int(ss[0]) last = int(ss.pop()) else: s = repr(args) # needed if exception occurs - assert len(args) == 2,'one format string arg or two start,stop args required' - first,last = args + assert len(args) == 2, 'one format string arg or two start, stop args required' + first, last = args assert first <= last, 'start of range greater than end of range' if cls.min_idx is not None: assert first >= cls.min_idx, f'start of range < {cls.min_idx:,}' if cls.max_idx is not None: assert last <= cls.max_idx, f'end of range > {cls.max_idx:,}' - return tuple.__new__(cls,(first,last)) + return tuple.__new__(cls, (first, last)) except Exception as e: - return cls.init_fail(e,s) + return cls.init_fail(e, s) @property def first(self): @@ -253,23 +253,23 @@ class MMGenRange(tuple,InitErrors,MMGenObject): return self[1] def iterate(self): - return range(self[0],self[1]+1) + return range(self[0], self[1]+1) @property def items(self): return list(self.iterate()) -class Int(int,Hilite,InitErrors): +class Int(int, Hilite, InitErrors): min_val = None max_val = None max_digits = None color = 'red' - def __new__(cls,n,base=10): - if isinstance(n,cls): + def __new__(cls, n, base=10): + if isinstance(n, cls): return n try: - me = int.__new__(cls,str(n),base) + me = int.__new__(cls, str(n), base) if cls.min_val is not None: assert me >= cls.min_val, f'is less than cls.min_val ({cls.min_val})' if cls.max_val is not None: @@ -278,16 +278,16 @@ class Int(int,Hilite,InitErrors): assert len(str(me)) <= cls.max_digits, f'has more than {cls.max_digits} digits' return me except Exception as e: - return cls.init_fail(e,n) + return cls.init_fail(e, n) @classmethod - def fmtc(cls,s,width,color=False): + def fmtc(cls, s, width, color=False): return super().fmtc(str(s), width=width, color=color) - def fmt(self,width,color=False): + def fmt(self, width, color=False): return super().fmtc(str(self), width=width, color=color) - def hl(self,**kwargs): + def hl(self, **kwargs): return super().colorize(str(self), **kwargs) class NonNegativeInt(Int): @@ -302,44 +302,44 @@ class ETHNonce(Int): class Str(HiliteStr): pass -class HexStr(HiliteStr,InitErrors): +class HexStr(HiliteStr, InitErrors): color = 'red' width = None hexcase = 'lower' trunc_ok = False - def __new__(cls,s,case=None): - if isinstance(s,cls): + def __new__(cls, s, case=None): + if isinstance(s, cls): return s if case is None: case = cls.hexcase - from .util import hexdigits_lc,hexdigits_uc + from .util import hexdigits_lc, hexdigits_uc try: - assert isinstance(s,str),'not a string or string subclass' - assert case in ('upper','lower'), f'{case!r} incorrect case specifier' + assert isinstance(s, str), 'not a string or string subclass' + assert case in ('upper', 'lower'), f'{case!r} incorrect case specifier' assert set(s) <= set(hexdigits_lc if case == 'lower' else hexdigits_uc), ( - f'not {case}case hexadecimal symbols' ) - assert not len(s) % 2,'odd-length string' + f'not {case}case hexadecimal symbols') + assert not len(s) % 2, 'odd-length string' if cls.width: assert len(s) == cls.width, f'Value is not {cls.width} characters wide' - return str.__new__(cls,s) + return str.__new__(cls, s) except Exception as e: - return cls.init_fail(e,s) + return cls.init_fail(e, s) - def truncate(self,width,color=True): + def truncate(self, width, color=True): return self.colorize( self if width >= self.width else self[:width-2] + '..', - color = color ) + color = color) class CoinTxID(HexStr): - color,width,hexcase = ('purple',64,'lower') + color, width, hexcase = ('purple', 64, 'lower') class WalletPassword(HexStr): - color,width,hexcase = ('blue',32,'lower') + color, width, hexcase = ('blue', 32, 'lower') class MMGenTxID(HexStr): - color,width,hexcase = ('red',6,'upper') + color, width, hexcase = ('red', 6, 'upper') -class MMGenLabel(HiliteStr,InitErrors): +class MMGenLabel(HiliteStr, InitErrors): color = 'pink' allowed = [] forbidden = [] @@ -348,26 +348,26 @@ class MMGenLabel(HiliteStr,InitErrors): min_len = 0 max_screen_width = 0 # if != 0, overrides max_len desc = 'label' - def __new__(cls,s): - if isinstance(s,cls): + def __new__(cls, s): + if isinstance(s, cls): return s try: s = s.strip() if not cls.allowed: for ch in s: - # Allow: (L)etter,(N)umber,(P)unctuation,(S)ymbol,(Z)space - # Disallow: (C)ontrol,(M)combining + # Allow: (L)etter, (N)umber, (P)unctuation, (S)ymbol, (Z)space + # Disallow: (C)ontrol, (M)combining # Combining characters create width formatting issues, so disallow them for now - if unicodedata.category(ch)[0] in ('C','M'): + if unicodedata.category(ch)[0] in ('C', 'M'): raise ValueError( '{!a}: {} characters not allowed'.format( ch, - { 'C':'control', 'M':'combining' }[unicodedata.category(ch)[0]] )) + {'C':'control', 'M':'combining'}[unicodedata.category(ch)[0]])) - me = str.__new__(cls,s) + me = str.__new__(cls, s) if cls.max_screen_width: - me.screen_width = len(s) + len([1 for ch in s if unicodedata.east_asian_width(ch) in ('F','W')]) + me.screen_width = len(s) + len([1 for ch in s if unicodedata.east_asian_width(ch) in ('F', 'W')]) assert me.screen_width <= cls.max_screen_width, f'too wide (>{cls.max_screen_width} screen width)' else: assert len(s) <= cls.max_len, f'too long (>{cls.max_len} symbols)' @@ -385,7 +385,7 @@ class MMGenLabel(HiliteStr,InitErrors): return me except Exception as e: - return cls.init_fail(e,s) + return cls.init_fail(e, s) class MMGenWalletLabel(MMGenLabel): max_len = 48 diff --git a/mmgen/objmethods.py b/mmgen/objmethods.py index 2deb5b5e..ad267b92 100755 --- a/mmgen/objmethods.py +++ b/mmgen/objmethods.py @@ -31,13 +31,13 @@ else: def immutable_attr_init_check(self): pass -def truncate_str(s,width): # width = screen width +def truncate_str(s, width): # width = screen width wide_count = 0 - for n,ch in enumerate(s,1): - wide_count += unicodedata.east_asian_width(ch) in ('F','W') + for n, ch in enumerate(s, 1): + wide_count += unicodedata.east_asian_width(ch) in ('F', 'W') if n + wide_count > width: - return s[:n-1] + ('',' ')[ - unicodedata.east_asian_width(ch) in ('F','W') + return s[:n-1] + ('', ' ')[ + unicodedata.east_asian_width(ch) in ('F', 'W') and n + wide_count == width + 1] raise ValueError('string requires no truncating') @@ -49,34 +49,34 @@ class Hilite: # class method equivalent of fmt() @classmethod - def fmtc( cls, s, width, color=False ): + def fmtc(cls, s, width, color=False): if len(s) > width: assert cls.trunc_ok, "If 'trunc_ok' is false, 'width' must be >= width of string" - return cls.colorize( s[:width].ljust(width), color=color ) + return cls.colorize(s[:width].ljust(width), color=color) else: - return cls.colorize( s.ljust(width), color=color ) + return cls.colorize(s.ljust(width), color=color) @classmethod - def hlc(cls,s,color=True): - return getattr( color_mod, cls.color )(s) if color else s + def hlc(cls, s, color=True): + return getattr(color_mod, cls.color)(s) if color else s @classmethod - def colorize(cls,s,color=True): - return getattr( color_mod, cls.color )(s) if color else s + def colorize(cls, s, color=True): + return getattr(color_mod, cls.color)(s) if color else s @classmethod - def colorize2(cls,s,color=True,color_override=''): - return getattr( color_mod, color_override or cls.color )(s) if color else s + def colorize2(cls, s, color=True, color_override=''): + return getattr(color_mod, color_override or cls.color)(s) if color else s -class HiliteStr(str,Hilite): +class HiliteStr(str, Hilite): # supports single-width characters only - def fmt( self, width, color=False ): + def fmt(self, width, color=False): if len(self) > width: assert self.trunc_ok, "If 'trunc_ok' is false, 'width' must be >= width of string" - return self.colorize( self[:width].ljust(width), color=color ) + return self.colorize(self[:width].ljust(width), color=color) else: - return self.colorize( self.ljust(width), color=color ) + return self.colorize(self.ljust(width), color=color) # an alternative to fmt(), with double-width char support and other features def fmt2( @@ -87,46 +87,46 @@ class HiliteStr(str,Hilite): nullrepl = '', append_chars = '', # single-width chars only append_color = False, - color_override = '' ): + color_override = ''): if self == '': - return getattr( color_mod, self.color )(nullrepl.ljust(width)) if color else nullrepl.ljust(width) + return getattr(color_mod, self.color)(nullrepl.ljust(width)) if color else nullrepl.ljust(width) - s_wide_count = len(['' for ch in self if unicodedata.east_asian_width(ch) in ('F','W')]) + s_wide_count = len(['' for ch in self if unicodedata.east_asian_width(ch) in ('F', 'W')]) - a,b = encl or ('','') + a, b = encl or ('', '') add_len = len(append_chars) + len(encl) if len(self) + s_wide_count + add_len > width: assert self.trunc_ok, "If 'trunc_ok' is false, 'width' must be >= screen width of string" - s = a + (truncate_str(self,width-add_len) if s_wide_count else self[:width-add_len]) + b + s = a + (truncate_str(self, width-add_len) if s_wide_count else self[:width-add_len]) + b else: s = a + self + b if append_chars: return ( - self.colorize(s,color=color) + self.colorize(s, color=color) + self.colorize2( append_chars.ljust(width-len(s)-s_wide_count), - color_override = append_color )) + color_override = append_color)) else: - return self.colorize2( s.ljust(width-s_wide_count), color=color, color_override=color_override ) + return self.colorize2(s.ljust(width-s_wide_count), color=color, color_override=color_override) - def hl(self,color=True): - return getattr( color_mod, self.color )(self) if color else self + def hl(self, color=True): + return getattr(color_mod, self.color)(self) if color else self # an alternative to hl(), with enclosure and color override # can be called as an unbound method with class as first argument - def hl2(self,s=None,color=True,encl='',color_override=''): + def hl2(self, s=None, color=True, encl='', color_override=''): if encl: - return self.colorize2( encl[0]+(s or self)+encl[1], color=color, color_override=color_override ) + return self.colorize2(encl[0]+(s or self)+encl[1], color=color, color_override=color_override) else: - return self.colorize2( (s or self), color=color, color_override=color_override ) + return self.colorize2((s or self), color=color, color_override=color_override) class InitErrors: @classmethod - def init_fail(cls,e,m,e2=None,m2=None,objname=None,preformat=False): + def init_fail(cls, e, m, e2=None, m2=None, objname=None, preformat=False): def get_errmsg(): ret = m if preformat else ( @@ -134,18 +134,18 @@ class InitErrors: m, (objname or cls.__name__), (f'({e2!s}) ' if e2 else ''), - e )) + e)) return f'{m2!r}\n{ret}' if m2 else ret - if hasattr(cls,'passthru_excs') and type(e).__name__ in cls.passthru_excs: + if hasattr(cls, 'passthru_excs') and type(e).__name__ in cls.passthru_excs: raise e from .util import die - die(getattr(cls,'exc','ObjectInitError'), get_errmsg()) + die(getattr(cls, 'exc', 'ObjectInitError'), get_errmsg()) @classmethod def method_not_implemented(cls): import traceback raise NotImplementedError( 'method {}() not implemented for class {!r}'.format( - traceback.extract_stack()[-2].name, cls.__name__) ) + traceback.extract_stack()[-2].name, cls.__name__)) diff --git a/mmgen/opts.py b/mmgen/opts.py index ca87ee3e..51d8db00 100755 --- a/mmgen/opts.py +++ b/mmgen/opts.py @@ -134,7 +134,7 @@ def parse_opts(opts_data, opt_filter, global_opts_data, global_opts_filter): return namedtuple('parsed_cmd_opts', ['user_opts', 'cmd_args', 'opts'])( uopts, # dict uargs, # list, callers can pop - tuple(v.name for k,v in opts if len(k) > 1) + tuple(v.name for k, v in opts if len(k) > 1) ) def opt_preproc_debug(po): @@ -145,10 +145,10 @@ def opt_preproc_debug(po): ('Cmd args', po.cmd_args, False), ('Opts', po.opts, True), ) - from .util import Msg,fmt_list + from .util import Msg, fmt_list Msg('\n=== opts.py debug ===') - for label,data,pretty in d: - Msg(' {:<20}: {}'.format(label,'\n' + fmt_list(data,fmt='col',indent=' '*8) if pretty else data)) + for label, data, pretty in d: + Msg(' {:<20}: {}'.format(label, '\n' + fmt_list(data, fmt='col', indent=' '*8) if pretty else data)) opts_data_dfl = { 'text': { @@ -202,7 +202,7 @@ class Opts: cfg._uopts = uopts = po.user_opts if init_opts: # initialize user opts to given value - for uopt,val in init_opts.items(): + for uopt, val in init_opts.items(): if uopt not in uopts: uopts[uopt] = val diff --git a/mmgen/passwdlist.py b/mmgen/passwdlist.py index d4b61ee9..0c7a2d49 100755 --- a/mmgen/passwdlist.py +++ b/mmgen/passwdlist.py @@ -22,10 +22,10 @@ passwdlist: Password list class for the MMGen suite from collections import namedtuple -from .util import ymsg,is_int,die -from .obj import ImmutableAttr,ListItemAttr,MMGenPWIDString,TwComment +from .util import ymsg, is_int, die +from .obj import ImmutableAttr, ListItemAttr, MMGenPWIDString, TwComment from .key import PrivKey -from .addr import MMGenPasswordType,AddrIdx,AddrListID +from .addr import MMGenPasswordType, AddrIdx, AddrListID from .addrlist import ( AddrListChksum, AddrListIDStr, @@ -34,10 +34,10 @@ from .addrlist import ( ) class PasswordListEntry(AddrListEntryBase): - passwd = ListItemAttr(str,typeconv=False) # TODO: create Password type + passwd = ListItemAttr(str, typeconv=False) # TODO: create Password type idx = ImmutableAttr(AddrIdx) - comment = ListItemAttr(TwComment,reassign_ok=True) - sec = ListItemAttr(PrivKey,include_proto=True) + comment = ListItemAttr(TwComment, reassign_ok=True) + sec = ListItemAttr(PrivKey, include_proto=True) class PasswordList(AddrList): entry_type = PasswordListEntry @@ -50,17 +50,17 @@ class PasswordList(AddrList): gen_passwds = True pw_len = None dfl_pw_fmt = 'b58' - pwinfo = namedtuple('passwd_info',['min_len','max_len','dfl_len','valid_lens','desc','chk_func']) + pwinfo = namedtuple('passwd_info', ['min_len', 'max_len', 'dfl_len', 'valid_lens', 'desc', 'chk_func']) pw_info = { # 32**25 < 2**128 < 32**26 - 'b32': pwinfo(10, 42 ,24, None, 'base32 password', 'baseconv.is_b32_str'), + 'b32': pwinfo(10, 42 , 24, None, 'base32 password', 'baseconv.is_b32_str'), # 58**21 < 2**128 < 58**22 - 'b58': pwinfo(8, 36 ,20, None, 'base58 password', 'baseconv.is_b58_str'), - 'bip39': pwinfo(12, 24 ,24, [12,18,24],'BIP39 mnemonic', 'bip39.is_bip39_mnemonic'), - 'xmrseed': pwinfo(25, 25, 25, [25], 'Monero new-style mnemonic','xmrseed.is_xmrseed'), - 'hex': pwinfo(32, 64 ,64, [32,48,64],'hexadecimal password', 'util.is_hex_str'), + 'b58': pwinfo(8, 36 , 20, None, 'base58 password', 'baseconv.is_b58_str'), + 'bip39': pwinfo(12, 24 , 24, [12, 18, 24], 'BIP39 mnemonic', 'bip39.is_bip39_mnemonic'), + 'xmrseed': pwinfo(25, 25, 25, [25], 'Monero new-style mnemonic', 'xmrseed.is_xmrseed'), + 'hex': pwinfo(32, 64 , 64, [32, 48, 64], 'hexadecimal password', 'util.is_hex_str'), } - chksum_rec_f = lambda foo,e: (str(e.idx), e.passwd) + chksum_rec_f = lambda foo, e: (str(e.idx), e.passwd) feature_warn_fs = 'WARNING: {!r} is a potentially dangerous feature. Use at your own risk!' hex2bip39 = False @@ -76,7 +76,7 @@ class PasswordList(AddrList): pw_len = None, pw_fmt = None, chk_params_only = False, - skip_chksum_msg = False ): + skip_chksum_msg = False): self.cfg = cfg self.proto = proto # proto is ignored @@ -90,7 +90,7 @@ class PasswordList(AddrList): self.data = self.file.parse_file(infile) else: if not chk_params_only: - for k in (seed,pw_idxs): + for k in (seed, pw_idxs): assert k self.pw_id_str = MMGenPWIDString(pw_id_str) self.set_pw_fmt(pw_fmt) @@ -100,20 +100,20 @@ class PasswordList(AddrList): if self.hex2bip39: ymsg(self.feature_warn_fs.format(pw_fmt)) self.set_pw_len_vs_seed_len(seed) # sets self.bip39, self.xmrseed, self.xmrproto self.baseconv - self.al_id = AddrListID( sid=seed.sid, mmtype=MMGenPasswordType(self.proto,'P') ) - self.data = self.generate(seed,pw_idxs) + self.al_id = AddrListID(sid=seed.sid, mmtype=MMGenPasswordType(self.proto, 'P')) + self.data = self.generate(seed, pw_idxs) self.num_addrs = len(self.data) self.fmt_data = '' self.chksum = AddrListChksum(self) fs = f'{self.al_id.sid}-{self.pw_id_str}-{self.pw_fmt_disp}-{self.pw_len}[{{}}]' - self.id_str = AddrListIDStr(self,fs) + self.id_str = AddrListIDStr(self, fs) if not skip_chksum_msg: self.do_chksum_msg(record=not infile) - def set_pw_fmt(self,pw_fmt): + def set_pw_fmt(self, pw_fmt): if pw_fmt == 'hex2bip39': self.hex2bip39 = True self.pw_fmt = 'bip39' @@ -122,12 +122,12 @@ class PasswordList(AddrList): self.pw_fmt = pw_fmt self.pw_fmt_disp = pw_fmt if self.pw_fmt not in self.pw_info: - die( 'InvalidPasswdFormat', - f'{self.pw_fmt!r}: invalid password format. Valid formats: {", ".join(self.pw_info)}' ) + die('InvalidPasswdFormat', + f'{self.pw_fmt!r}: invalid password format. Valid formats: {", ".join(self.pw_info)}') - def chk_pw_len(self,passwd=None): + def chk_pw_len(self, passwd=None): if passwd is None: - assert self.pw_len,'either passwd or pw_len must be set' + assert self.pw_len, 'either passwd or pw_len must be set' pw_len = self.pw_len fs = '{l}: invalid user-requested length for {b} ({c}{m})' else: @@ -136,13 +136,13 @@ class PasswordList(AddrList): d = self.pw_info[self.pw_fmt] if d.valid_lens: if pw_len not in d.valid_lens: - die(2, fs.format( l=pw_len, b=d.desc, c='not one of ', m=d.valid_lens, pw=passwd )) + die(2, fs.format(l=pw_len, b=d.desc, c='not one of ', m=d.valid_lens, pw=passwd)) elif pw_len > d.max_len: - die(2, fs.format( l=pw_len, b=d.desc, c='>', m=d.max_len, pw=passwd )) + die(2, fs.format(l=pw_len, b=d.desc, c='>', m=d.max_len, pw=passwd)) elif pw_len < d.min_len: - die(2, fs.format( l=pw_len, b=d.desc, c='<', m=d.min_len, pw=passwd )) + die(2, fs.format(l=pw_len, b=d.desc, c='<', m=d.min_len, pw=passwd)) - def set_pw_len(self,pw_len): + def set_pw_len(self, pw_len): d = self.pw_info[self.pw_fmt] if pw_len is None: @@ -150,11 +150,11 @@ class PasswordList(AddrList): return if not is_int(pw_len): - die(2,f'{pw_len!r}: invalid user-requested password length (not an integer)') + die(2, f'{pw_len!r}: invalid user-requested password length (not an integer)') self.pw_len = int(pw_len) self.chk_pw_len() - def set_pw_len_vs_seed_len(self,seed): + def set_pw_len_vs_seed_len(self, seed): pf = self.pw_fmt if pf == 'hex': pw_bytes = self.pw_len // 2 @@ -162,83 +162,83 @@ class PasswordList(AddrList): elif pf == 'bip39': from .bip39 import bip39 self.bip39 = bip39() - pw_bytes = bip39.nwords2seedlen(self.pw_len,in_bytes=True) - good_pw_len = bip39.seedlen2nwords(seed.byte_len,in_bytes=True) + pw_bytes = bip39.nwords2seedlen(self.pw_len, in_bytes=True) + good_pw_len = bip39.seedlen2nwords(seed.byte_len, in_bytes=True) elif pf == 'xmrseed': from .xmrseed import xmrseed from .protocol import init_proto self.xmrseed = xmrseed() - self.xmrproto = init_proto( self.cfg, 'xmr' ) + self.xmrproto = init_proto(self.cfg, 'xmr') pw_bytes = xmrseed().seedlen_map_rev[self.pw_len] try: good_pw_len = xmrseed().seedlen_map[seed.byte_len] except: - die(1,f'{seed.byte_len*8}: unsupported seed length for Monero new-style mnemonic') - elif pf in ('b32','b58'): + die(1, f'{seed.byte_len*8}: unsupported seed length for Monero new-style mnemonic') + elif pf in ('b32', 'b58'): pw_int = (32 if pf == 'b32' else 58) ** self.pw_len pw_bytes = pw_int.bit_length() // 8 from .baseconv import baseconv self.baseconv = baseconv(self.pw_fmt) - good_pw_len = len( baseconv(pf).frombytes(b'\xff'*seed.byte_len) ) + good_pw_len = len(baseconv(pf).frombytes(b'\xff'*seed.byte_len)) else: raise NotImplementedError(f'{pf!r}: unknown password format') if pw_bytes > seed.byte_len: die(1, f'Cannot generate passwords with more entropy than underlying seed! ({len(seed.data)*8} bits)\n' + - (f'Re-run the command with --passwd-len={good_pw_len}' if pf in ('bip39','hex') else + (f'Re-run the command with --passwd-len={good_pw_len}' if pf in ('bip39', 'hex') else 'Re-run the command, specifying a password length of {} or less') ) - if pf in ('bip39','hex') and pw_bytes < seed.byte_len: + if pf in ('bip39', 'hex') and pw_bytes < seed.byte_len: from .ui import keypress_confirm if not keypress_confirm( self.cfg, f'WARNING: requested {self.pw_info[pf].desc} length has less entropy ' + 'than underlying seed!\nIs this what you want?', - default_yes = True ): - die(1,'Exiting at user request') + default_yes = True): + die(1, 'Exiting at user request') - def gen_passwd(self,secbytes): + def gen_passwd(self, secbytes): assert self.pw_fmt in self.pw_info if self.pw_fmt == 'hex': # take most significant part return secbytes.hex()[:self.pw_len] elif self.pw_fmt == 'bip39': - pw_len_bytes = self.bip39.nwords2seedlen( self.pw_len, in_bytes=True ) + pw_len_bytes = self.bip39.nwords2seedlen(self.pw_len, in_bytes=True) # take most significant part - return ' '.join( self.bip39.fromhex(secbytes[:pw_len_bytes].hex()) ) + return ' '.join(self.bip39.fromhex(secbytes[:pw_len_bytes].hex())) elif self.pw_fmt == 'xmrseed': pw_len_bytes = self.xmrseed.seedlen_map_rev[self.pw_len] bytes_preproc = self.xmrproto.preprocess_key( secbytes[:pw_len_bytes], # take most significant part - None ) - return ' '.join( self.xmrseed.frombytes(bytes_preproc) ) + None) + return ' '.join(self.xmrseed.frombytes(bytes_preproc)) else: # take least significant part return self.baseconv.frombytes( secbytes, pad = self.pw_len, - tostr = True )[-self.pw_len:] + tostr = True)[-self.pw_len:] - def check_format(self,pw): + def check_format(self, pw): if not self.chk_func(pw): raise ValueError(f'Password is not valid {self.pw_info[self.pw_fmt].desc} data') - pwlen = len(pw.split()) if self.pw_fmt in ('bip39','xmrseed') else len(pw) + pwlen = len(pw.split()) if self.pw_fmt in ('bip39', 'xmrseed') else len(pw) if pwlen != self.pw_len: raise ValueError(f'Password has incorrect length ({pwlen} != {self.pw_len})') return True - def scramble_seed(self,seed): + def scramble_seed(self, seed): # Changing either pw_fmt or pw_len will cause a different, unrelated # set of passwords to be generated: this is what we want. # NB: In original implementation, pw_id_str was 'baseN', not 'bN' scramble_key = f'{self.pw_fmt}:{self.pw_len}:{self.pw_id_str}' if self.hex2bip39: - pwlen = self.bip39.nwords2seedlen(self.pw_len,in_hex=True) + pwlen = self.bip39.nwords2seedlen(self.pw_len, in_hex=True) scramble_key = f'hex:{pwlen}:{self.pw_id_str}' - self.dmsg_sc('str',scramble_key) + self.dmsg_sc('str', scramble_key) from .crypto import Crypto - return Crypto(self.cfg).scramble_seed(seed,scramble_key.encode()) + return Crypto(self.cfg).scramble_seed(seed, scramble_key.encode()) diff --git a/mmgen/protocol.py b/mmgen/protocol.py index b4395a9e..e9bfee5e 100755 --- a/mmgen/protocol.py +++ b/mmgen/protocol.py @@ -25,17 +25,17 @@ from collections import namedtuple from .cfg import gc from .objmethods import MMGenObject -decoded_wif = namedtuple('decoded_wif',['sec','pubkey_type','compressed']) +decoded_wif = namedtuple('decoded_wif', ['sec', 'pubkey_type', 'compressed']) decoded_addr = namedtuple('decoded_addr', ['bytes', 'ver_bytes', 'fmt']) decoded_addr_multiview = namedtuple('mv_decoded_addr', ['bytes', 'ver_bytes', 'fmt', 'addr', 'views', 'view_pref']) -parsed_addr = namedtuple('parsed_addr',['ver_bytes','data']) +parsed_addr = namedtuple('parsed_addr', ['ver_bytes', 'data']) -_finfo = namedtuple('fork_info',['height','hash','name','replayable']) -_nw = namedtuple('coin_networks',['mainnet','testnet','regtest']) +_finfo = namedtuple('fork_info', ['height', 'hash', 'name', 'replayable']) +_nw = namedtuple('coin_networks', ['mainnet', 'testnet', 'regtest']) class CoinProtocol(MMGenObject): - proto_info = namedtuple('proto_info',['name','trust_level']) # trust levels: see altcoin/params.py + proto_info = namedtuple('proto_info', ['name', 'trust_level']) # trust levels: see altcoin/params.py # keys are mirrored in gc.core_coins: coins = { @@ -54,10 +54,10 @@ class CoinProtocol(MMGenObject): base_coin = None is_fork_of = None chain_names = None - networks = ('mainnet','testnet','regtest') + networks = ('mainnet', 'testnet', 'regtest') decimal_prec = 28 - def __init__(self,cfg,coin,name,network,tokensym=None,need_amt=False): + def __init__(self, cfg, coin, name, network, tokensym=None, need_amt=False): self.cfg = cfg self.coin = coin.upper() self.coin_id = self.coin @@ -65,24 +65,24 @@ class CoinProtocol(MMGenObject): self.network = network self.tokensym = tokensym self.cls_name = type(self).__name__ - self.testnet = network in ('testnet','regtest') + self.testnet = network in ('testnet', 'regtest') self.regtest = network == 'regtest' - self.networks = tuple(k for k,v in self.network_names._asdict().items() if v) + self.networks = tuple(k for k, v in self.network_names._asdict().items() if v) self.network_id = coin.lower() + { 'mainnet': '', 'testnet': '_tn', 'regtest': '_rt', }[network] - if hasattr(self,'wif_ver_num'): - self.wif_ver_bytes = {k:bytes.fromhex(v) for k,v in self.wif_ver_num.items()} - self.wif_ver_bytes_to_pubkey_type = {v:k for k,v in self.wif_ver_bytes.items()} + if hasattr(self, 'wif_ver_num'): + self.wif_ver_bytes = {k:bytes.fromhex(v) for k, v in self.wif_ver_num.items()} + self.wif_ver_bytes_to_pubkey_type = {v:k for k, v in self.wif_ver_bytes.items()} vbs = list(self.wif_ver_bytes.values()) self.wif_ver_bytes_len = len(vbs[0]) if len(set(len(b) for b in vbs)) == 1 else None - if hasattr(self,'addr_ver_info'): - self.addr_ver_bytes = {bytes.fromhex(k):v for k,v in self.addr_ver_info.items()} - self.addr_fmt_to_ver_bytes = {v:k for k,v in self.addr_ver_bytes.items()} + if hasattr(self, 'addr_ver_info'): + self.addr_ver_bytes = {bytes.fromhex(k):v for k, v in self.addr_ver_info.items()} + self.addr_fmt_to_ver_bytes = {v:k for k, v in self.addr_ver_bytes.items()} self.addr_ver_bytes_len = len(list(self.addr_ver_bytes)[0]) if gc.cmd_caps: @@ -100,15 +100,15 @@ class CoinProtocol(MMGenObject): if self.tokensym: assert self.name.startswith('Ethereum'), 'CoinProtocol.Base_chk1' - if self.base_coin in ('ETH','XMR'): + if self.base_coin in ('ETH', 'XMR'): from .util2 import get_keccak self.keccak_256 = get_keccak(cfg) if need_amt: from . import amt from decimal import getcontext - self.coin_amt = getattr(amt,self.coin_amt) - self.max_tx_fee = self.coin_amt(self.max_tx_fee) if hasattr(self,'max_tx_fee') else None + self.coin_amt = getattr(amt, self.coin_amt) + self.max_tx_fee = self.coin_amt(self.max_tx_fee) if hasattr(self, 'max_tx_fee') else None getcontext().prec = self.decimal_prec else: self.coin_amt = None @@ -119,7 +119,7 @@ class CoinProtocol(MMGenObject): return self.coin @classmethod - def chain_name_to_network(cls,cfg,coin,chain_name): + def chain_name_to_network(cls, cfg, coin, chain_name): """ The generic networks 'mainnet', 'testnet' and 'regtest' are required for all coins that support transaction operations. @@ -128,8 +128,8 @@ class CoinProtocol(MMGenObject): the attribute 'chain_name' is used, while 'network' retains the generic name. For Bitcoin and Bitcoin forks, 'network' and 'chain_name' are equivalent. """ - for network in ('mainnet','testnet','regtest'): - proto = init_proto( cfg, coin, network=network ) + for network in ('mainnet', 'testnet', 'regtest'): + proto = init_proto(cfg, coin, network=network) for proto_chain_name in proto.chain_names: if chain_name == proto_chain_name: return network @@ -137,22 +137,22 @@ class CoinProtocol(MMGenObject): @staticmethod def parse_network_id(network_id): - nid = namedtuple('parsed_network_id',['coin','network']) + nid = namedtuple('parsed_network_id', ['coin', 'network']) if network_id.endswith('_tn'): - return nid(network_id[:-3],'testnet') + return nid(network_id[:-3], 'testnet') elif network_id.endswith('_rt'): - return nid(network_id[:-3],'regtest') + return nid(network_id[:-3], 'regtest') else: - return nid(network_id,'mainnet') + return nid(network_id, 'mainnet') @staticmethod - def create_network_id(coin,network): - return coin.lower() + { 'mainnet':'', 'testnet':'_tn', 'regtest':'_rt' }[network] + def create_network_id(coin, network): + return coin.lower() + {'mainnet':'', 'testnet':'_tn', 'regtest':'_rt'}[network] - def cap(self,s): + def cap(self, s): return s in self.caps - def get_addr_len(self,addr_fmt): + def get_addr_len(self, addr_fmt): return self.addr_len def decode_addr_bytes(self, addr_bytes): @@ -162,18 +162,18 @@ class CoinProtocol(MMGenObject): addr_bytes[:vlen], self.addr_ver_bytes[addr_bytes[:vlen]]) - def coin_addr(self,addr): + def coin_addr(self, addr): from .addr import CoinAddr - return CoinAddr( proto=self, addr=addr ) + return CoinAddr(proto=self, addr=addr) - def addr_type(self,id_str): + def addr_type(self, id_str): from .addr import MMGenAddrType - return MMGenAddrType( proto=self, id_str=id_str ) + return MMGenAddrType(proto=self, id_str=id_str) - def viewkey(self,viewkey_str): + def viewkey(self, viewkey_str): raise NotImplementedError(f'{self.name} protocol does not support view keys') - def base_proto_subclass(self,cls,modname,sub_clsname=None): + def base_proto_subclass(self, cls, modname, sub_clsname=None): """ magic module loading and class selection """ @@ -182,13 +182,13 @@ class CoinProtocol(MMGenObject): clsname = ( self.mod_clsname + ('Token' if self.tokensym else '') - + cls.__name__ ) + + cls.__name__) import importlib if sub_clsname: - return getattr(getattr(importlib.import_module(modpath),clsname),sub_clsname) + return getattr(getattr(importlib.import_module(modpath), clsname), sub_clsname) else: - return getattr(importlib.import_module(modpath),clsname) + return getattr(importlib.import_module(modpath), clsname) class Secp256k1(Base): @@ -199,42 +199,42 @@ class CoinProtocol(MMGenObject): privkey_len = 32 pubkey_types = ('std',) - def parse_addr(self,ver_bytes,addr_bytes,fmt): + def parse_addr(self, ver_bytes, addr_bytes, fmt): return parsed_addr( ver_bytes = ver_bytes, data = addr_bytes, ) - def preprocess_key(self,sec,pubkey_type): + def preprocess_key(self, sec, pubkey_type): # Key must be non-zero and less than group order of secp256k1 curve - if 0 < int.from_bytes(sec,'big') < self.secp256k1_group_order: + if 0 < int.from_bytes(sec, 'big') < self.secp256k1_group_order: return sec else: # chance of this is less than 1 in 2^127 - from .util import die,ymsg - pk = int.from_bytes(sec,'big') + from .util import die, ymsg + pk = int.from_bytes(sec, 'big') if pk == 0: # chance of this is 1 in 2^256 - die(4,'Private key is zero!') + die(4, 'Private key is zero!') elif pk == self.secp256k1_group_order: # ditto - die(4,'Private key == secp256k1_group_order!') + die(4, 'Private key == secp256k1_group_order!') else: # return key mod group order as the key if not self.cfg.test_suite: ymsg(f'Warning: private key is greater than secp256k1 group order!:\n {sec.hex()}') - return (pk % self.secp256k1_group_order).to_bytes(self.privkey_len,'big') + return (pk % self.secp256k1_group_order).to_bytes(self.privkey_len, 'big') class DummyWIF: """ Ethereum and Monero protocols inherit from this class """ - def encode_wif(self,privbytes,pubkey_type,compressed): + def encode_wif(self, privbytes, pubkey_type, compressed): assert pubkey_type == self.pubkey_type, f'{pubkey_type}: invalid pubkey_type for {self.name} protocol!' assert compressed is False, f'{self.name} protocol does not support compressed pubkeys!' return privbytes.hex() - def decode_wif(self,wif): + def decode_wif(self, wif): return decoded_wif( sec = bytes.fromhex(wif), pubkey_type = self.pubkey_type, - compressed = False ) + compressed = False) def init_proto( cfg, @@ -253,7 +253,7 @@ def init_proto( assert not (coin and network_id), 'init_proto_chk4' if network_id: - coin,network = CoinProtocol.Base.parse_network_id(network_id) + coin, network = CoinProtocol.Base.parse_network_id(network_id) elif network: assert network in CoinProtocol.Base.networks, f'init_proto_chk5 - {network!r}: invalid network' assert testnet is False, 'init_proto_chk6' @@ -265,37 +265,37 @@ def init_proto( if coin not in CoinProtocol.coins: from .altcoin.params import init_genonly_altcoins - init_genonly_altcoins( coin, testnet=testnet ) # raises exception on failure + init_genonly_altcoins(coin, testnet=testnet) # raises exception on failure name = CoinProtocol.coins[coin].name proto_name = name + ('' if network == 'mainnet' else network.capitalize()) - if not hasattr(CoinProtocol,proto_name): + if not hasattr(CoinProtocol, proto_name): import importlib setattr( CoinProtocol, proto_name, - getattr(importlib.import_module(f'mmgen.proto.{coin}.params'),network) + getattr(importlib.import_module(f'mmgen.proto.{coin}.params'), network) ) if return_cls: - return getattr(CoinProtocol,proto_name) + return getattr(CoinProtocol, proto_name) - return getattr(CoinProtocol,proto_name)( + return getattr(CoinProtocol, proto_name)( cfg = cfg, coin = coin, name = name, network = network, tokensym = tokensym, - need_amt = need_amt ) + need_amt = need_amt) -def init_proto_from_cfg(cfg,need_amt): +def init_proto_from_cfg(cfg, need_amt): return init_proto( cfg = cfg, coin = cfg.coin, network = cfg.network, tokensym = cfg.token, - need_amt = need_amt ) + need_amt = need_amt) def warn_trustlevel(cfg): @@ -305,11 +305,11 @@ def warn_trustlevel(cfg): trust_level = CoinProtocol.coins[coinsym.lower()].trust_level else: from .altcoin.params import CoinInfo - e = CoinInfo.get_entry(coinsym,'mainnet') + e = CoinInfo.get_entry(coinsym, 'mainnet') trust_level = e.trust_level if e else None - if trust_level in (None,-1): + if trust_level in (None, -1): from .util import die - die(1,f'Coin {coinsym} is not supported by {gc.proj_name}') + die(1, f'Coin {coinsym} is not supported by {gc.proj_name}') if trust_level > 3: return @@ -322,7 +322,7 @@ def warn_trustlevel(cfg): """ from .util import fmt - from .color import red,yellow,green + from .color import red, yellow, green warning = fmt(m).strip().format( c = coinsym.upper(), @@ -332,13 +332,13 @@ def warn_trustlevel(cfg): 2: yellow('MEDIUM'), 3: green('OK'), }[trust_level], - p = gc.proj_name ) + p = gc.proj_name) if cfg.test_suite: cfg._util.qmsg(warning) return from .ui import keypress_confirm - if not keypress_confirm( cfg, warning, default_yes=True ): + if not keypress_confirm(cfg, warning, default_yes=True): import sys sys.exit(0) diff --git a/mmgen/pyversion.py b/mmgen/pyversion.py index def558b6..297a0c86 100755 --- a/mmgen/pyversion.py +++ b/mmgen/pyversion.py @@ -17,40 +17,40 @@ class PythonVersion(str): major = 0 minor = 0 - def __new__(cls,arg=None): - if isinstance(arg,PythonVersion): + def __new__(cls, arg=None): + if isinstance(arg, PythonVersion): return arg if arg: - major,minor = arg.split('.') + major, minor = arg.split('.') else: import platform - major,minor = platform.python_version_tuple()[:2] - me = str.__new__( cls, f'{major}.{minor}' ) + major, minor = platform.python_version_tuple()[:2] + me = str.__new__(cls, f'{major}.{minor}') me.major = int(major) me.minor = int(minor) return me - def __lt__(self,other): + def __lt__(self, other): other = type(self)(other) return self.major < other.major or (self.major == other.major and self.minor < other.minor) - def __le__(self,other): + def __le__(self, other): other = type(self)(other) return self.major < other.major or (self.major == other.major and self.minor <= other.minor) - def __eq__(self,other): + def __eq__(self, other): other = type(self)(other) return self.major == other.major and self.minor == other.minor - def __ne__(self,other): + def __ne__(self, other): other = type(self)(other) return not (self.major == other.major and self.minor == other.minor) - def __gt__(self,other): + def __gt__(self, other): other = type(self)(other) return self.major > other.major or (self.major == other.major and self.minor > other.minor) - def __ge__(self,other): + def __ge__(self, other): other = type(self)(other) return self.major > other.major or (self.major == other.major and self.minor >= other.minor) diff --git a/mmgen/rpc.py b/mmgen/rpc.py index 0ba15bcc..842d0648 100755 --- a/mmgen/rpc.py +++ b/mmgen/rpc.py @@ -20,43 +20,43 @@ rpc: Cryptocoin RPC library for the MMGen suite """ -import sys,re,base64,json,asyncio,importlib +import sys, re, base64, json, asyncio, importlib from decimal import Decimal from collections import namedtuple -from .util import msg,ymsg,die,fmt,fmt_list,pp_fmt,oneshot_warning +from .util import msg, ymsg, die, fmt, fmt_list, pp_fmt, oneshot_warning from .base_obj import AsyncInit from .obj import NonNegativeInt -from .objmethods import HiliteStr,InitErrors,MMGenObject +from .objmethods import HiliteStr, InitErrors, MMGenObject -auth_data = namedtuple('rpc_auth_data',['user','passwd']) +auth_data = namedtuple('rpc_auth_data', ['user', 'passwd']) -def dmsg_rpc(fs,data=None,is_json=False): +def dmsg_rpc(fs, data=None, is_json=False): msg( fs if data is None else fs.format(pp_fmt(json.loads(data) if is_json else data)) ) -def dmsg_rpc_backend(host_url,host_path,payload): +def dmsg_rpc_backend(host_url, host_path, payload): msg( f'\n RPC URL: {host_url}{host_path}' + '\n RPC PAYLOAD data (httplib) ==>' + - f'\n{pp_fmt(payload)}\n' ) + f'\n{pp_fmt(payload)}\n') -def noop(*args,**kwargs): +def noop(*args, **kwargs): pass -class IPPort(HiliteStr,InitErrors): +class IPPort(HiliteStr, InitErrors): color = 'yellow' width = 0 trunc_ok = False min_len = 9 # 0.0.0.0:0 max_len = 21 # 255.255.255.255:65535 - def __new__(cls,s): - if isinstance(s,cls): + def __new__(cls, s): + if isinstance(s, cls): return s try: - m = re.fullmatch(r'{q}\.{q}\.{q}\.{q}:(\d{{1,10}})'.format(q=r'([0-9]{1,3})'),s) + m = re.fullmatch(r'{q}\.{q}\.{q}\.{q}:(\d{{1,10}})'.format(q=r'([0-9]{1,3})'), s) assert m is not None, f'{s!r}: invalid IP:HOST specifier' for e in m.groups(): if len(e) != 1 and e[0] == '0': @@ -65,13 +65,13 @@ class IPPort(HiliteStr,InitErrors): for e in res[:4]: assert e <= 255, f'{e}: dotted decimal element > 255' assert res[4] <= 65535, f'{res[4]}: port number > 65535' - me = str.__new__(cls,s) + me = str.__new__(cls, s) me.ip = '{}.{}.{}.{}'.format(*res) - me.ip_num = sum( res[i] * ( 2 ** (-(i-3)*8) ) for i in range(4) ) + me.ip_num = sum(res[i] * (2 ** (-(i-3)*8)) for i in range(4)) me.port = res[4] return me except Exception as e: - return cls.init_fail(e,s) + return cls.init_fail(e, s) class json_encoder(json.JSONEncoder): def default(self, o): @@ -84,7 +84,7 @@ class RPCBackends: class base: - def __init__(self,caller): + def __init__(self, caller): self.cfg = caller.cfg self.host = caller.host self.port = caller.port @@ -95,7 +95,7 @@ class RPCBackends: self.name = type(self).__name__ self.caller = caller - class aiohttp(base,metaclass=AsyncInit): + class aiohttp(base, metaclass=AsyncInit): """ Contrary to the requests library, aiohttp won’t read environment variables by default. But you can do so by passing trust_env=True into aiohttp.ClientSession @@ -108,61 +108,61 @@ class RPCBackends: self.session.detach() del self.session - async def __init__(self,caller): + async def __init__(self, caller): super().__init__(caller) import aiohttp self.connector = aiohttp.TCPConnector(limit_per_host=self.cfg.aiohttp_rpc_queue_len) self.session = aiohttp.ClientSession( - headers = { 'Content-Type': 'application/json' }, + headers = {'Content-Type': 'application/json'}, connector = self.connector, ) if caller.auth_type == 'basic': - self.auth = aiohttp.BasicAuth(*caller.auth,encoding='UTF-8') + self.auth = aiohttp.BasicAuth(*caller.auth, encoding='UTF-8') else: self.auth = None - async def run(self,payload,timeout,host_path): - dmsg_rpc_backend(self.host_url,host_path,payload) + async def run(self, payload, timeout, host_path): + dmsg_rpc_backend(self.host_url, host_path, payload) async with self.session.post( url = self.host_url + host_path, auth = self.auth, - data = json.dumps(payload,cls=json_encoder), + data = json.dumps(payload, cls=json_encoder), timeout = timeout or self.timeout, ) as res: - return (await res.text(),res.status) + return (await res.text(), res.status) class requests(base): def __del__(self): self.session.close() - def __init__(self,caller): + def __init__(self, caller): super().__init__(caller) - import requests,urllib3 + import requests, urllib3 urllib3.disable_warnings() self.session = requests.Session() self.session.trust_env = False # ignore *_PROXY environment vars self.session.headers = caller.http_hdrs if caller.auth_type: auth = 'HTTP' + caller.auth_type.capitalize() + 'Auth' - self.session.auth = getattr(requests.auth,auth)(*caller.auth) + self.session.auth = getattr(requests.auth, auth)(*caller.auth) if self.proxy: # used only by XMR for now: requires pysocks package self.session.proxies.update({ 'http': f'socks5h://{self.proxy}', 'https': f'socks5h://{self.proxy}' }) - async def run(self,*args,**kwargs): - return self.run_noasync(*args,**kwargs) + async def run(self, *args, **kwargs): + return self.run_noasync(*args, **kwargs) - def run_noasync(self,payload,timeout,host_path): - dmsg_rpc_backend(self.host_url,host_path,payload) + def run_noasync(self, payload, timeout, host_path): + dmsg_rpc_backend(self.host_url, host_path, payload) res = self.session.post( url = self.host_url + host_path, - data = json.dumps(payload,cls=json_encoder), + data = json.dumps(payload, cls=json_encoder), timeout = timeout or self.timeout, - verify = False ) - return (res.content,res.status_code) + verify = False) + return (res.content, res.status_code) class httplib(base): """ @@ -171,22 +171,22 @@ class RPCBackends: def __del__(self): self.session.close() - def __init__(self,caller): + def __init__(self, caller): super().__init__(caller) import http.client - self.session = http.client.HTTPConnection(caller.host,caller.port,caller.timeout) + self.session = http.client.HTTPConnection(caller.host, caller.port, caller.timeout) if caller.auth_type == 'basic': auth_str = f'{caller.auth.user}:{caller.auth.passwd}' auth_str_b64 = 'Basic ' + base64.b64encode(auth_str.encode()).decode() - self.http_hdrs.update({ 'Host': self.host, 'Authorization': auth_str_b64 }) + self.http_hdrs.update({'Host': self.host, 'Authorization': auth_str_b64}) dmsg_rpc(f' RPC AUTHORIZATION data ==> raw: [{auth_str}]\n{"":>31}enc: [{auth_str_b64}]\n') - async def run(self,payload,timeout,host_path): - dmsg_rpc_backend(self.host_url,host_path,payload) + async def run(self, payload, timeout, host_path): + dmsg_rpc_backend(self.host_url, host_path, payload) if timeout: import http.client - s = http.client.HTTPConnection(self.host,self.port,timeout) + s = http.client.HTTPConnection(self.host, self.port, timeout) else: s = self.session @@ -194,25 +194,25 @@ class RPCBackends: s.request( method = 'POST', url = host_path, - body = json.dumps(payload,cls=json_encoder), - headers = self.http_hdrs ) + body = json.dumps(payload, cls=json_encoder), + headers = self.http_hdrs) r = s.getresponse() # => http.client.HTTPResponse instance except Exception as e: - die( 'RPCFailure', str(e) ) + die('RPCFailure', str(e)) if timeout: - ret = ( r.read(), r.status ) + ret = (r.read(), r.status) s.close() return ret else: - return ( r.read(), r.status ) + return (r.read(), r.status) class curl(base): - def __init__(self,caller): + def __init__(self, caller): def gen_opts(): - for k,v in caller.http_hdrs.items(): + for k, v in caller.http_hdrs.items(): yield from ('--header', f'{k}: {v}') if caller.auth_type: # Authentication with curl is insecure, as it exposes the user's credentials @@ -227,12 +227,12 @@ class RPCBackends: self.exec_opts = list(gen_opts()) + ['--silent'] self.arg_max = 8192 # set way below system ARG_MAX, just to be safe - async def run(self,payload,timeout,host_path): - data = json.dumps(payload,cls=json_encoder) + async def run(self, payload, timeout, host_path): + data = json.dumps(payload, cls=json_encoder) if len(data) > self.arg_max: ymsg('Warning: Curl data payload length exceeded - falling back on httplib') - return RPCBackends.httplib(self.caller).run(payload,timeout,host_path) - dmsg_rpc_backend(self.host_url,host_path,payload) + return RPCBackends.httplib(self.caller).run(payload, timeout, host_path) + dmsg_rpc_backend(self.host_url, host_path, payload) exec_cmd = [ 'curl', '--proxy', f'socks5h://{self.proxy}' if self.proxy else '', @@ -241,13 +241,13 @@ class RPCBackends: '--data-binary', data ] + self.exec_opts + [self.host_url + host_path] - dmsg_rpc(' RPC curl exec data ==>\n{}\n',exec_cmd) + dmsg_rpc(' RPC curl exec data ==>\n{}\n', exec_cmd) - from subprocess import run,PIPE + from subprocess import run, PIPE from .color import set_vt100 - res = run(exec_cmd,stdout=PIPE,check=True,text=True).stdout + res = run(exec_cmd, stdout=PIPE, check=True, text=True).stdout set_vt100() - return (res[:-3],int(res[-3:])) + return (res[:-3], int(res[-3:])) class RPCClient(MMGenObject): @@ -256,7 +256,7 @@ class RPCClient(MMGenObject): network_proto = 'http' proxy = None - def __init__(self,cfg,host,port,test_connection=True): + def __init__(self, cfg, host, port, test_connection=True): self.cfg = cfg self.name = type(self).__name__ @@ -265,7 +265,7 @@ class RPCClient(MMGenObject): if sys.platform == 'win32' and host == 'localhost': host = '127.0.0.1' - global dmsg_rpc,dmsg_rpc_backend + global dmsg_rpc, dmsg_rpc_backend if not self.cfg.debug_rpc: dmsg_rpc = dmsg_rpc_backend = noop @@ -275,18 +275,18 @@ class RPCClient(MMGenObject): if test_connection: import socket try: - socket.create_connection((host,port),timeout=1).close() + socket.create_connection((host, port), timeout=1).close() except: - die( 'SocketError', f'Unable to connect to {host}:{port}' ) + die('SocketError', f'Unable to connect to {host}:{port}') - self.http_hdrs = { 'Content-Type': 'application/json' } + self.http_hdrs = {'Content-Type': 'application/json'} self.host_url = f'{self.network_proto}://{host}:{port}' self.host = host self.port = port self.timeout = self.cfg.http_timeout self.auth = None - def _get_backend(self,backend): + def _get_backend(self, backend): backend_id = backend or self.cfg.rpc_backend if backend_id == 'auto': return { @@ -295,12 +295,12 @@ class RPCClient(MMGenObject): 'win32': RPCBackends.requests }[sys.platform](self) else: - return getattr(RPCBackends,backend_id)(self) + return getattr(RPCBackends, backend_id)(self) - def set_backend(self,backend=None): + def set_backend(self, backend=None): self.backend = self._get_backend(backend) - async def set_backend_async(self,backend=None): + async def set_backend_async(self, backend=None): ret = self._get_backend(backend) self.backend = (await ret) if type(ret).__name__ == 'coroutine' else ret @@ -308,17 +308,17 @@ class RPCClient(MMGenObject): # - positional params are passed to the daemon, 'timeout' and 'wallet' kwargs to the backend # - 'wallet' kwarg is used only by regtest - async def call(self,method,*params,timeout=None,wallet=None): + async def call(self, method, *params, timeout=None, wallet=None): """ default call: call with param list unrolled, exactly as with cli """ return self.process_http_resp(await self.backend.run( - payload = {'id': 1, 'jsonrpc': '2.0', 'method': method, 'params': params }, + payload = {'id': 1, 'jsonrpc': '2.0', 'method': method, 'params': params}, timeout = timeout, host_path = self.make_host_path(wallet) )) - async def batch_call(self,method,param_list,timeout=None,wallet=None): + async def batch_call(self, method, param_list, timeout=None, wallet=None): """ Make a single call with a list of tuples as first argument For RPC calls that return a list of results @@ -328,17 +328,17 @@ class RPCClient(MMGenObject): 'id': n, 'jsonrpc': '2.0', 'method': method, - 'params': params } for n,params in enumerate(param_list,1) ], + 'params': params} for n, params in enumerate(param_list, 1)], timeout = timeout, host_path = self.make_host_path(wallet) - ),batch=True) + ), batch=True) - async def gathered_call(self,method,args_list,timeout=None,wallet=None): + async def gathered_call(self, method, args_list, timeout=None, wallet=None): """ Perform multiple RPC calls, returning results in a list Can be called two ways: 1) method = methodname, args_list = [args_tuple1, args_tuple2,...] - 2) method = None, args_list = [(methodname1,args_tuple1), (methodname2,args_tuple2), ...] + 2) method = None, args_list = [(methodname1, args_tuple1), (methodname2, args_tuple2), ...] """ cmd_list = args_list if method is None else tuple(zip([method] * len(args_list), args_list)) @@ -348,10 +348,10 @@ class RPCClient(MMGenObject): while cur_pos < len(cmd_list): tasks = [self.backend.run( - payload = {'id': n, 'jsonrpc': '2.0', 'method': method, 'params': params }, + payload = {'id': n, 'jsonrpc': '2.0', 'method': method, 'params': params}, timeout = timeout, host_path = self.make_host_path(wallet) - ) for n,(method,params) in enumerate(cmd_list[cur_pos:chunk_size+cur_pos],1)] + ) for n, (method, params) in enumerate(cmd_list[cur_pos:chunk_size+cur_pos], 1)] ret.extend(await asyncio.gather(*tasks)) cur_pos += chunk_size @@ -362,22 +362,22 @@ class RPCClient(MMGenObject): # - remaining kwargs are passed to CallSigs method # - CallSigs method returns method and positional params for Call method - def icall(self,method,**kwargs): - timeout = kwargs.pop('timeout',None) - wallet = kwargs.pop('wallet',None) + def icall(self, method, **kwargs): + timeout = kwargs.pop('timeout', None) + wallet = kwargs.pop('wallet', None) return self.call( - *getattr(self.call_sigs,method)(**kwargs), + *getattr(self.call_sigs, method)(**kwargs), timeout = timeout, - wallet = wallet ) + wallet = wallet) - def gathered_icall(self,method,args_list,timeout=None,wallet=None): + def gathered_icall(self, method, args_list, timeout=None, wallet=None): return self.gathered_call( method, - [getattr(self.call_sigs,method)(*a)[1:] for a in args_list], + [getattr(self.call_sigs, method)(*a)[1:] for a in args_list], timeout = timeout, - wallet = wallet ) + wallet = wallet) - def process_http_resp(self,run_ret,batch=False,json_rpc=True): + def process_http_resp(self, run_ret, batch=False, json_rpc=True): def float_parser(n): return n @@ -385,22 +385,22 @@ class RPCClient(MMGenObject): text, status = run_ret if status == 200: - dmsg_rpc(' RPC RESPONSE data ==>\n{}\n',text,is_json=True) + dmsg_rpc(' RPC RESPONSE data ==>\n{}\n', text, is_json=True) m = None if batch: - return [r['result'] for r in json.loads(text,parse_float=float_parser)] + return [r['result'] for r in json.loads(text, parse_float=float_parser)] else: try: if json_rpc: - ret = json.loads(text,parse_float=float_parser)['result'] - if isinstance(ret,list) and ret and type(ret[0]) == dict and 'success' in ret[0]: + ret = json.loads(text, parse_float=float_parser)['result'] + if isinstance(ret, list) and ret and type(ret[0]) == dict and 'success' in ret[0]: for res in ret: if not res['success']: m = str(res['error']) assert False return ret else: - return json.loads(text,parse_float=float_parser) + return json.loads(text, parse_float=float_parser) except: if not m: t = json.loads(text) @@ -414,7 +414,7 @@ class RPCClient(MMGenObject): die('RPCFailure', m) else: import http - m,s = ( '', http.HTTPStatus(status) ) + m, s = ('', http.HTTPStatus(status)) if text: try: m = json.loads(text)['error']['message'] @@ -423,9 +423,9 @@ class RPCClient(MMGenObject): m = text.decode() except: m = text - die( 'RPCFailure', f'{s.value} {s.name}: {m}' ) + die('RPCFailure', f'{s.value} {s.name}: {m}') - async def stop_daemon(self,quiet=False,silent=False): + async def stop_daemon(self, quiet=False, silent=False): if self.daemon.state == 'ready': if not (quiet or silent): msg(f'Stopping {self.daemon.desc} on port {self.daemon.bind_port}') @@ -438,24 +438,24 @@ class RPCClient(MMGenObject): msg(f'{self.daemon.desc} on port {self.daemon.bind_port} not running') return True - def start_daemon(self,silent=False): + def start_daemon(self, silent=False): return self.daemon.start(silent=silent) - async def restart_daemon(self,quiet=False,silent=False): - await self.stop_daemon(quiet=quiet,silent=silent) + async def restart_daemon(self, quiet=False, silent=False): + await self.stop_daemon(quiet=quiet, silent=silent) return self.daemon.start(silent=silent) - def handle_unsupported_daemon_version(self,name,warn_only): + def handle_unsupported_daemon_version(self, name, warn_only): class daemon_version_warning(oneshot_warning): color = 'yellow' message = 'ignoring unsupported {} daemon version at user request' if warn_only: - daemon_version_warning(div=name,fmt_args=[self.daemon.coind_name]) + daemon_version_warning(div=name, fmt_args=[self.daemon.coind_name]) else: name = self.daemon.coind_name - die(2,'\n'+fmt(f""" + die(2, '\n'+fmt(f""" The running {name} daemon has version {self.daemon_version_str}. This version of MMGen is tested only on {name} v{self.daemon.coind_version_str} and below. @@ -463,7 +463,7 @@ class RPCClient(MMGenObject): Alternatively, you may invoke the command with the --ignore-daemon-version option, in which case you proceed at your own risk. - """,indent=' ')) + """, indent=' ')) async def rpc_init( cfg, @@ -471,36 +471,36 @@ async def rpc_init( backend = None, daemon = None, ignore_daemon_version = False, - ignore_wallet = False ): + ignore_wallet = False): proto = proto or cfg._proto if not 'rpc_init' in proto.mmcaps: - die(1,f'rpc_init() not supported for {proto.name} protocol!') + die(1, f'rpc_init() not supported for {proto.name} protocol!') cls = getattr( importlib.import_module(f'mmgen.proto.{proto.base_proto_coin.lower()}.rpc'), - proto.base_proto + 'RPCClient' ) + proto.base_proto + 'RPCClient') from .daemon import CoinDaemon rpc = await cls( cfg = cfg, proto = proto, - daemon = daemon or CoinDaemon(cfg,proto=proto,test_suite=cfg.test_suite), + daemon = daemon or CoinDaemon(cfg, proto=proto, test_suite=cfg.test_suite), backend = backend or cfg.rpc_backend, - ignore_wallet = ignore_wallet ) + ignore_wallet = ignore_wallet) if rpc.daemon_version > rpc.daemon.coind_version: rpc.handle_unsupported_daemon_version( proto.name, - ignore_daemon_version or proto.ignore_daemon_version or cfg.ignore_daemon_version ) + ignore_daemon_version or proto.ignore_daemon_version or cfg.ignore_daemon_version) if rpc.chain not in proto.chain_names: - die( 'RPCChainMismatch', '\n' + fmt(f""" + die('RPCChainMismatch', '\n' + fmt(f""" Protocol: {proto.cls_name} - Valid chain names: {fmt_list(proto.chain_names,fmt='bare')} + Valid chain names: {fmt_list(proto.chain_names, fmt='bare')} RPC client chain: {rpc.chain} - """,indent=' ').rstrip() ) + """, indent=' ').rstrip()) rpc.blockcount = NonNegativeInt(rpc.blockcount) diff --git a/mmgen/seed.py b/mmgen/seed.py index 167396bf..4e8fa1d8 100755 --- a/mmgen/seed.py +++ b/mmgen/seed.py @@ -20,41 +20,41 @@ seed: Seed-related classes and methods for the MMGen suite """ -from .util import make_chksum_8,hexdigits_uc,die -from .objmethods import HiliteStr,InitErrors,MMGenObject -from .obj import ImmutableAttr,get_obj +from .util import make_chksum_8, hexdigits_uc, die +from .objmethods import HiliteStr, InitErrors, MMGenObject +from .obj import ImmutableAttr, get_obj -class SeedID(HiliteStr,InitErrors): +class SeedID(HiliteStr, InitErrors): color = 'blue' width = 8 trunc_ok = False - def __new__(cls,seed=None,sid=None): - if isinstance(sid,cls): + def __new__(cls, seed=None, sid=None): + if isinstance(sid, cls): return sid try: if seed: - assert isinstance(seed,SeedBase),'not a subclass of SeedBase' - return str.__new__(cls,make_chksum_8(seed.data)) + assert isinstance(seed, SeedBase), 'not a subclass of SeedBase' + return str.__new__(cls, make_chksum_8(seed.data)) elif sid: assert set(sid) <= set(hexdigits_uc), 'not uppercase hex digits' assert len(sid) == cls.width, f'not {cls.width} characters wide' - return str.__new__(cls,sid) + return str.__new__(cls, sid) raise ValueError('no arguments provided') except Exception as e: - return cls.init_fail(e,seed or sid) + return cls.init_fail(e, seed or sid) def is_seed_id(s): - return get_obj( SeedID, sid=s, silent=True, return_bool=True ) + return get_obj(SeedID, sid=s, silent=True, return_bool=True) class SeedBase(MMGenObject): - lens = ( 128, 192, 256 ) + lens = (128, 192, 256) dfl_len = 256 - data = ImmutableAttr(bytes,typeconv=False) - sid = ImmutableAttr(SeedID,typeconv=False) + data = ImmutableAttr(bytes, typeconv=False) + sid = ImmutableAttr(SeedID, typeconv=False) - def __init__(self,cfg,seed_bin=None,nSubseeds=None): + def __init__(self, cfg, seed_bin=None, nSubseeds=None): if not seed_bin: from .crypto import Crypto @@ -62,7 +62,7 @@ class SeedBase(MMGenObject): # Truncate random data for smaller seed lengths seed_bin = sha256(Crypto(cfg).get_random(1033)).digest()[:(cfg.seed_len or self.dfl_len)//8] elif len(seed_bin)*8 not in self.lens: - die(3,f'{len(seed_bin)*8}: invalid seed bit length') + die(3, f'{len(seed_bin)*8}: invalid seed bit length') self.cfg = cfg self.data = seed_bin @@ -85,24 +85,24 @@ class Seed(SeedBase): @property def subseeds(self): - if not hasattr(self,'_subseeds'): + if not hasattr(self, '_subseeds'): from .subseed import SubSeedList self._subseeds = SubSeedList( self, - length = self.nSubseeds or self.cfg.subseeds ) + length = self.nSubseeds or self.cfg.subseeds) return self._subseeds - def subseed(self,*args,**kwargs): - return self.subseeds.get_subseed_by_ss_idx(*args,**kwargs) + def subseed(self, *args, **kwargs): + return self.subseeds.get_subseed_by_ss_idx(*args, **kwargs) - def subseed_by_seed_id(self,*args,**kwargs): - return self.subseeds.get_subseed_by_seed_id(*args,**kwargs) + def subseed_by_seed_id(self, *args, **kwargs): + return self.subseeds.get_subseed_by_seed_id(*args, **kwargs) - def split(self,*args,**kwargs): + def split(self, *args, **kwargs): from .seedsplit import SeedShareList - return SeedShareList(self,*args,**kwargs) + return SeedShareList(self, *args, **kwargs) @staticmethod - def join_shares(*args,**kwargs): + def join_shares(*args, **kwargs): from .seedsplit import join_shares - return join_shares(*args,**kwargs) + return join_shares(*args, **kwargs) diff --git a/mmgen/seedsplit.py b/mmgen/seedsplit.py index 0f3ae1ae..4a83e0c3 100755 --- a/mmgen/seedsplit.py +++ b/mmgen/seedsplit.py @@ -21,11 +21,11 @@ seedsplit: Seed split classes and methods for the MMGen suite """ from .color import yellow -from .util import msg,die -from .objmethods import MMGenObject,HiliteStr,InitErrors -from .obj import ImmutableAttr,MMGenPWIDString,MMGenIdx,get_obj,IndexedDict -from .seed import Seed,SeedBase -from .subseed import SubSeedList,SubSeedIdx,SubSeed +from .util import msg, die +from .objmethods import MMGenObject, HiliteStr, InitErrors +from .obj import ImmutableAttr, MMGenPWIDString, MMGenIdx, get_obj, IndexedDict +from .seed import Seed, SeedBase +from .subseed import SubSeedList, SubSeedIdx, SubSeed from .crypto import Crypto class SeedShareIdx(MMGenIdx): @@ -37,26 +37,26 @@ class SeedShareCount(SeedShareIdx): class MasterShareIdx(MMGenIdx): max_val = 1024 -class SeedSplitSpecifier(HiliteStr,InitErrors,MMGenObject): +class SeedSplitSpecifier(HiliteStr, InitErrors, MMGenObject): color = 'red' - def __new__(cls,s): - if isinstance(s,cls): + def __new__(cls, s): + if isinstance(s, cls): return s try: arr = s.split(':') - assert len(arr) in (2,3), 'cannot be parsed' - a,b,c = arr if len(arr) == 3 else ['default'] + arr - me = str.__new__(cls,s) + assert len(arr) in (2, 3), 'cannot be parsed' + a, b, c = arr if len(arr) == 3 else ['default'] + arr + me = str.__new__(cls, s) me.id = SeedSplitIDString(a) me.idx = SeedShareIdx(b) me.count = SeedShareCount(c) assert me.idx <= me.count, 'share index greater than share count' return me except Exception as e: - return cls.init_fail(e,s) + return cls.init_fail(e, s) def is_seed_split_specifier(s): - return get_obj( SeedSplitSpecifier, s=s, silent=True, return_bool=True ) + return get_obj(SeedSplitSpecifier, s=s, silent=True, return_bool=True) class SeedSplitIDString(MMGenPWIDString): desc = 'seed split ID string' @@ -68,7 +68,7 @@ class SeedShareList(SubSeedList): count = ImmutableAttr(SeedShareCount) id_str = ImmutableAttr(SeedSplitIDString) - def __init__(self,parent_seed,count,id_str=None,master_idx=None,debug_last_share=False): + def __init__(self, parent_seed, count, id_str=None, master_idx=None, debug_last_share=False): self.member_type = SeedShare self.parent_seed = parent_seed self.id_str = id_str or 'default' @@ -77,13 +77,13 @@ class SeedShareList(SubSeedList): def make_master_share(): for nonce in range(SeedShare.max_nonce+1): - ms = SeedShareMaster(self,master_idx,nonce) + ms = SeedShareMaster(self, master_idx, nonce) if ms.sid == parent_seed.sid: if parent_seed.cfg.debug_subseed: msg(f'master_share seed ID collision with parent seed, incrementing nonce to {nonce+1}') else: return ms - die( 'SubSeedNonceRangeExceeded', 'nonce range exceeded' ) + die('SubSeedNonceRangeExceeded', 'nonce range exceeded') def last_share_debug(last_share): if not debug_last_share: @@ -98,29 +98,29 @@ class SeedShareList(SubSeedList): for nonce in range(SeedShare.max_nonce+1): self.nonce_start = nonce - self.data = { 'long': IndexedDict(), 'short': IndexedDict() } # 'short' is required as a placeholder + self.data = {'long': IndexedDict(), 'short': IndexedDict()} # 'short' is required as a placeholder if self.master_share: - self.data['long'][self.master_share.sid] = (1,self.master_share.nonce) + self.data['long'][self.master_share.sid] = (1, self.master_share.nonce) self._generate(count-1) self.last_share = ls = SeedShareLast(self) if last_share_debug(ls) or ls.sid in self.data['long'] or ls.sid == parent_seed.sid: # collision: throw out entire split list and redo with new start nonce if parent_seed.cfg.debug_subseed: - self._collision_debug_msg(ls.sid,count,nonce,'nonce_start',debug_last_share) + self._collision_debug_msg(ls.sid, count, nonce, 'nonce_start', debug_last_share) else: - self.data['long'][ls.sid] = (count,nonce) + self.data['long'][ls.sid] = (count, nonce) break else: - die( 'SubSeedNonceRangeExceeded', 'nonce range exceeded' ) + die('SubSeedNonceRangeExceeded', 'nonce range exceeded') if parent_seed.cfg.debug_subseed: A = parent_seed.data B = self.join().data assert A == B, f'Data mismatch!\noriginal seed: {A!r}\nrejoined seed: {B!r}' - def get_share_by_idx(self,idx,base_seed=False): + def get_share_by_idx(self, idx, base_seed=False): if idx < 1 or idx > self.count: - die( 'RangeError', f'{idx}: share index out of range' ) + die('RangeError', f'{idx}: share index out of range') elif idx == self.count: return self.last_share elif self.master_share and idx == 1: @@ -129,7 +129,7 @@ class SeedShareList(SubSeedList): ss_idx = SubSeedIdx(str(idx) + 'L') return self.get_subseed_by_ss_idx(ss_idx) - def get_share_by_seed_id(self,sid,base_seed=False): + def get_share_by_seed_id(self, sid, base_seed=False): if sid == self.data['long'].key(self.count-1): return self.last_share elif self.master_share and sid == self.data['long'].key(0): @@ -140,26 +140,26 @@ class SeedShareList(SubSeedList): def join(self): return Seed.join_shares( self.parent_seed.cfg, - [self.get_share_by_idx(i+1) for i in range(len(self))] ) + [self.get_share_by_idx(i+1) for i in range(len(self))]) def format(self): assert self.split_type == 'N-of-N' fs1 = ' {}\n' fs2 = '{i:>5}: {}\n' - mfs1,mfs2,midx,msid = ('','','','') + mfs1, mfs2, midx, msid = ('', '', '', '') if self.master_share: - mfs1,mfs2 = (' with master share #{} ({})',' (master share #{})') - midx,msid = (self.master_share.idx,self.master_share.sid) + mfs1, mfs2 = (' with master share #{} ({})', ' (master share #{})') + midx, msid = (self.master_share.idx, self.master_share.sid) - hdr = ' {} {} ({} bits)\n'.format('Seed:',self.parent_seed.sid.hl(),self.parent_seed.bitlen) - hdr += ' {} {c}-of-{c} (XOR){m}\n'.format('Split Type:',c=self.count,m=mfs1.format(midx,msid)) - hdr += ' {} {}\n\n'.format('ID String:',self.id_str.hl()) + hdr = ' {} {} ({} bits)\n'.format('Seed:', self.parent_seed.sid.hl(), self.parent_seed.bitlen) + hdr += ' {} {c}-of-{c} (XOR){m}\n'.format('Split Type:', c=self.count, m=mfs1.format(midx, msid)) + hdr += ' {} {}\n\n'.format('ID String:', self.id_str.hl()) hdr += fs1.format('Shares') hdr += fs1.format('------') sl = self.data['long'].keys - body1 = fs2.format(sl[0]+mfs2.format(midx),i=1) - body = (fs2.format(sl[n],i=n+1) for n in range(1,len(self))) + body1 = fs2.format(sl[0]+mfs2.format(midx), i=1) + body = (fs2.format(sl[n], i=n+1) for n in range(1, len(self))) return hdr + body1 + ''.join(body) @@ -181,103 +181,100 @@ class SeedShareBase(MMGenObject): def desc(self): return self.get_desc() - def get_desc(self,ui=False): + def get_desc(self, ui=False): pl = self.parent_list mss = f', with master share #{pl.master_share.idx}' if pl.master_share else '' if ui: - m = ( yellow("(share {} of {} of ") + m = (yellow("(share {} of {} of ") + pl.parent_seed.sid.hl() + yellow(', split id ') + pl.id_str.hl2(encl='‘’') - + yellow('{})') ) + + yellow('{})')) else: m = "share {} of {} of " + pl.parent_seed.sid + ", split id '" + pl.id_str + "'{}" - return m.format(self.idx,pl.count,mss) + return m.format(self.idx, pl.count, mss) -class SeedShare(SeedShareBase,SubSeed): +class SeedShare(SeedShareBase, SubSeed): @staticmethod - def make_subseed_bin(parent_list,idx:int,nonce:int,length:str): + def make_subseed_bin(parent_list, idx:int, nonce:int, length:str): seed = parent_list.parent_seed assert parent_list.have_short is False assert length == 'long' # field maximums: id_str: none (256 chars), count: 65535 (1024), idx: 65535 (1024), nonce: 65535 (1000) scramble_key = ( f'{parent_list.split_type}:{parent_list.id_str}:'.encode() + - parent_list.count.to_bytes(2,'big') + - idx.to_bytes(2,'big') + - nonce.to_bytes(2,'big') + parent_list.count.to_bytes(2, 'big') + + idx.to_bytes(2, 'big') + + nonce.to_bytes(2, 'big') ) if parent_list.master_share: scramble_key += ( b':master:' + - parent_list.master_share.idx.to_bytes(2,'big') + parent_list.master_share.idx.to_bytes(2, 'big') ) - return Crypto(parent_list.parent_seed.cfg).scramble_seed(seed.data,scramble_key)[:seed.byte_len] + return Crypto(parent_list.parent_seed.cfg).scramble_seed(seed.data, scramble_key)[:seed.byte_len] -class SeedShareLast(SeedShareBase,SeedBase): +class SeedShareLast(SeedShareBase, SeedBase): idx = ImmutableAttr(SeedShareIdx) nonce = 0 - def __init__(self,parent_list): + def __init__(self, parent_list): self.idx = parent_list.count self.parent_list = parent_list SeedBase.__init__( self, parent_list.parent_seed.cfg, - seed_bin=self.make_subseed_bin(parent_list) ) + seed_bin=self.make_subseed_bin(parent_list)) @staticmethod def make_subseed_bin(parent_list): seed_list = (parent_list.get_share_by_idx(i+1) for i in range(len(parent_list))) seed = parent_list.parent_seed - ret = int(seed.data.hex(),16) + ret = int(seed.data.hex(), 16) for ss in seed_list: - ret ^= int(ss.data.hex(),16) + ret ^= int(ss.data.hex(), 16) - return ret.to_bytes(seed.byte_len,'big') + return ret.to_bytes(seed.byte_len, 'big') -class SeedShareMaster(SeedBase,SeedShareBase): +class SeedShareMaster(SeedBase, SeedShareBase): idx = ImmutableAttr(MasterShareIdx) - nonce = ImmutableAttr(int,typeconv=False) + nonce = ImmutableAttr(int, typeconv=False) - def __init__(self,parent_list,idx,nonce): + def __init__(self, parent_list, idx, nonce): self.idx = idx self.nonce = nonce self.parent_list = parent_list self.cfg = parent_list.parent_seed.cfg - SeedBase.__init__( self, self.cfg, self.make_base_seed_bin() ) + SeedBase.__init__(self, self.cfg, self.make_base_seed_bin()) self.derived_seed = SeedBase( self.cfg, - self.make_derived_seed_bin( parent_list.id_str, parent_list.count ) + self.make_derived_seed_bin(parent_list.id_str, parent_list.count) ) @property def fn_stem(self): - return '{}-MASTER{}[{}]'.format( - self.parent_list.parent_seed.sid, - self.idx, - self.sid ) + return '{}-MASTER{}[{}]'.format(self.parent_list.parent_seed.sid, self.idx, self.sid) def make_base_seed_bin(self): seed = self.parent_list.parent_seed # field maximums: idx: 65535 (1024) - scramble_key = b'master_share:' + self.idx.to_bytes(2,'big') + self.nonce.to_bytes(2,'big') - return Crypto(self.cfg).scramble_seed(seed.data,scramble_key)[:seed.byte_len] + scramble_key = b'master_share:' + self.idx.to_bytes(2, 'big') + self.nonce.to_bytes(2, 'big') + return Crypto(self.cfg).scramble_seed(seed.data, scramble_key)[:seed.byte_len] # Don't bother with avoiding seed ID collision here, as sid of derived seed is not used # by user as an identifier - def make_derived_seed_bin(self,id_str,count): + def make_derived_seed_bin(self, id_str, count): # field maximums: id_str: none (256 chars), count: 65535 (1024) - scramble_key = id_str.encode() + b':' + count.to_bytes(2,'big') - return Crypto(self.cfg).scramble_seed(self.data,scramble_key)[:self.byte_len] + scramble_key = id_str.encode() + b':' + count.to_bytes(2, 'big') + return Crypto(self.cfg).scramble_seed(self.data, scramble_key)[:self.byte_len] - def get_desc(self,ui=False): + def get_desc(self, ui=False): psid = self.parent_list.parent_seed.sid mss = f'master share #{self.idx} of ' return yellow('(' + mss) + psid.hl() + yellow(')') if ui else mss + psid @@ -287,33 +284,33 @@ class SeedShareMasterJoining(SeedShareMaster): id_str = ImmutableAttr(SeedSplitIDString) count = ImmutableAttr(SeedShareCount) - def __init__(self,cfg,idx,base_seed,id_str,count): + def __init__(self, cfg, idx, base_seed, id_str, count): - SeedBase.__init__( self, cfg, seed_bin=base_seed.data ) + SeedBase.__init__(self, cfg, seed_bin=base_seed.data) self.cfg = cfg self.id_str = id_str or 'default' self.count = count - self.derived_seed = SeedBase( cfg, self.make_derived_seed_bin(self.id_str,self.count) ) + self.derived_seed = SeedBase(cfg, self.make_derived_seed_bin(self.id_str, self.count)) def join_shares( cfg, seed_list, master_idx = None, - id_str = None ): + id_str = None): - if not hasattr(seed_list,'__next__'): # seed_list can be iterator or iterable + if not hasattr(seed_list, '__next__'): # seed_list can be iterator or iterable seed_list = iter(seed_list) class d: - byte_len,ret,count = None,0,0 + byte_len, ret, count = None, 0, 0 def add_share(ss): if d.byte_len: assert ss.byte_len == d.byte_len, f'Seed length mismatch! {ss.byte_len} != {d.byte_len}' else: d.byte_len = ss.byte_len - d.ret ^= int(ss.data.hex(),16) + d.ret ^= int(ss.data.hex(), 16) d.count += 1 if master_idx: @@ -323,8 +320,8 @@ def join_shares( add_share(ss) if master_idx: - add_share(SeedShareMasterJoining( cfg, master_idx, master_share, id_str, d.count+1 ).derived_seed) + add_share(SeedShareMasterJoining(cfg, master_idx, master_share, id_str, d.count+1).derived_seed) SeedShareCount(d.count) # check that d.count is in valid range - return Seed( cfg, seed_bin=d.ret.to_bytes(d.byte_len,'big') ) + return Seed(cfg, seed_bin=d.ret.to_bytes(d.byte_len, 'big')) diff --git a/mmgen/sha2.py b/mmgen/sha2.py index 49079138..4b36945c 100755 --- a/mmgen/sha2.py +++ b/mmgen/sha2.py @@ -27,7 +27,7 @@ sha2: A non-optimized but very compact implementation of the SHA2 hash # implementation must not be used for anything but testing and study. Test with # the test/hashfunc.py script in the MMGen repository. -from struct import pack,unpack +from struct import pack, unpack class Sha2: 'Implementation based on the pseudocode at https://en.wikipedia.org/wiki/SHA-2' @@ -40,7 +40,7 @@ class Sha2: def nextPrime(n=2): while True: - for factor in range(2,int(sqrt(n))+1): + for factor in range(2, int(sqrt(n))+1): if n % factor == 0: break else: @@ -56,9 +56,9 @@ class Sha2: return int((n - int(n)) * fb_mul) if cls.use_gmp: - from gmpy2 import context,set_context,sqrt,cbrt + from gmpy2 import context, set_context, sqrt, cbrt # context() parameters are platform-dependent! - set_context(context(precision=75,round=1)) # OK for gmp 6.1.2 / gmpy 2.1.0 + set_context(context(precision=75, round=1)) # OK for gmp 6.1.2 / gmpy 2.1.0 else: cbrt = lambda n: pow(n, 1 / 3) @@ -68,9 +68,9 @@ class Sha2: # First wordBits bits of the fractional parts of the cube roots of the first nRounds primes cls.K = tuple(getFractionalBits(cbrt(n)) for n in primes) - def __init__(self,message,preprocess=True): + def __init__(self, message, preprocess=True): 'Use preprocess=False for Sha256Compress' - assert isinstance(message,(bytes,bytearray,list)),'message must be of type bytes, bytearray or list' + assert isinstance(message, (bytes, bytearray, list)), 'message must be of type bytes, bytearray or list' if not self.K: type(self).initConstants() self.H = list(self.H_init) @@ -100,25 +100,25 @@ class Sha2: def bytesToWords(self): ws = self.wordSize assert len(self.M) % ws == 0 - self.M = tuple(unpack(self.word_fmt,self.M[i*ws:ws+(i*ws)])[0] for i in range(len(self.M) // ws)) + self.M = tuple(unpack(self.word_fmt, self.M[i*ws:ws+(i*ws)])[0] for i in range(len(self.M) // ws)) def digest(self): - return b''.join((pack(self.word_fmt,w) for w in self.H)) + return b''.join((pack(self.word_fmt, w) for w in self.H)) def hexdigest(self): return self.digest().hex() def compute(self): - for i in range(0,len(self.M),16): + for i in range(0, len(self.M), 16): self.processBlock(i) - def processBlock(self,offset): + def processBlock(self, offset): 'Process a blkSize-byte chunk of the message' - def rrotate(a,b): + def rrotate(a, b): return ((a << self.wordBits-b) & self.wordMask) | (a >> b) - def addm(a,b): + def addm(a, b): return (a + b) & self.wordMask # Copy chunk into first 16 words of message schedule array @@ -126,46 +126,46 @@ class Sha2: self.W[i] = self.M[offset + i] # Extend the first 16 words into the remaining nRounds words of message schedule array - for i in range(16,self.nRounds): + for i in range(16, self.nRounds): g0 = self.W[i-15] - gamma0 = rrotate(g0,self.g0r1) ^ rrotate(g0,self.g0r2) ^ (g0 >> self.g0r3) + gamma0 = rrotate(g0, self.g0r1) ^ rrotate(g0, self.g0r2) ^ (g0 >> self.g0r3) g1 = self.W[i-2] - gamma1 = rrotate(g1,self.g1r1) ^ rrotate(g1,self.g1r2) ^ (g1 >> self.g1r3) - self.W[i] = addm(addm(addm(gamma0,self.W[i-7]),gamma1),self.W[i-16]) + gamma1 = rrotate(g1, self.g1r1) ^ rrotate(g1, self.g1r2) ^ (g1 >> self.g1r3) + self.W[i] = addm(addm(addm(gamma0, self.W[i-7]), gamma1), self.W[i-16]) # Initialize working variables from current hash state - a,b,c,d,e,f,g,h = self.H + a, b, c, d, e, f, g, h = self.H # Compression function main loop for i in range(self.nRounds): ch = (e & f) ^ (~e & g) maj = (a & b) ^ (a & c) ^ (b & c) - sigma0 = rrotate(a,self.s0r1) ^ rrotate(a,self.s0r2) ^ rrotate(a,self.s0r3) - sigma1 = rrotate(e,self.s1r1) ^ rrotate(e,self.s1r2) ^ rrotate(e,self.s1r3) + sigma0 = rrotate(a, self.s0r1) ^ rrotate(a, self.s0r2) ^ rrotate(a, self.s0r3) + sigma1 = rrotate(e, self.s1r1) ^ rrotate(e, self.s1r2) ^ rrotate(e, self.s1r3) - t1 = addm(addm(addm(addm(h,sigma1),ch),self.K[i]),self.W[i]) - t2 = addm(sigma0,maj) + t1 = addm(addm(addm(addm(h, sigma1), ch), self.K[i]), self.W[i]) + t2 = addm(sigma0, maj) h = g g = f f = e - e = addm(d,t1) + e = addm(d, t1) d = c c = b b = a - a = addm(t1,t2) + a = addm(t1, t2) # Save hash state - for n,v in enumerate((a,b,c,d,e,f,g,h)): - self.H[n] = addm(self.H[n],v) + for n, v in enumerate((a, b, c, d, e, f, g, h)): + self.H[n] = addm(self.H[n], v) class Sha256(Sha2): use_gmp = False - g0r1,g0r2,g0r3 = (7,18,3) - g1r1,g1r2,g1r3 = (17,19,10) - s0r1,s0r2,s0r3 = (2,13,22) - s1r1,s1r2,s1r3 = (6,11,25) + g0r1, g0r2, g0r3 = (7, 18, 3) + g1r1, g1r2, g1r3 = (17, 19, 10) + s0r1, s0r2, s0r3 = (2, 13, 22) + s1r1, s1r2, s1r3 = (6, 11, 25) blkSize = 64 nRounds = 64 wordSize = 4 @@ -174,7 +174,7 @@ class Sha256(Sha2): word_fmt = '>I' def pack_msglen(self): - return pack('>Q',len(self.M)*8) + return pack('>Q', len(self.M)*8) class Sha512(Sha2): """ @@ -190,10 +190,10 @@ class Sha512(Sha2): - the shift and rotate amounts used are different. """ use_gmp = True - g0r1,g0r2,g0r3 = (1,8,7) - g1r1,g1r2,g1r3 = (19,61,6) - s0r1,s0r2,s0r3 = (28,34,39) - s1r1,s1r2,s1r3 = (14,18,41) + g0r1, g0r2, g0r3 = (1, 8, 7) + g1r1, g1r2, g1r3 = (19, 61, 6) + s0r1, s0r2, s0r3 = (28, 34, 39) + s1r1, s1r2, s1r3 = (14, 18, 41) blkSize = 128 nRounds = 80 wordSize = 8 diff --git a/mmgen/subseed.py b/mmgen/subseed.py index 6ea1cccf..8a665306 100755 --- a/mmgen/subseed.py +++ b/mmgen/subseed.py @@ -21,64 +21,64 @@ subseed: Subseed classes and methods for the MMGen suite """ from .color import green -from .util import msg_r,msg,die,make_chksum_8 -from .objmethods import MMGenObject,HiliteStr,InitErrors -from .obj import MMGenRange,IndexedDict,ImmutableAttr -from .seed import SeedBase,SeedID +from .util import msg_r, msg, die, make_chksum_8 +from .objmethods import MMGenObject, HiliteStr, InitErrors +from .obj import MMGenRange, IndexedDict, ImmutableAttr +from .seed import SeedBase, SeedID class SubSeedIdxRange(MMGenRange): min_idx = 1 max_idx = 1000000 -class SubSeedIdx(HiliteStr,InitErrors): +class SubSeedIdx(HiliteStr, InitErrors): color = 'red' trunc_ok = False - def __new__(cls,s): - if isinstance(s,cls): + def __new__(cls, s): + if isinstance(s, cls): return s try: - assert isinstance(s,str),'not a string or string subclass' + assert isinstance(s, str), 'not a string or string subclass' idx = s[:-1] if s[-1] in 'SsLl' else s from .util import is_int - assert is_int(idx),"valid format: an integer, plus optional letter 'S','s','L' or 'l'" + assert is_int(idx), "valid format: an integer, plus optional letter 'S', 's', 'L' or 'l'" idx = int(idx) assert idx >= SubSeedIdxRange.min_idx, f'subseed index < {SubSeedIdxRange.min_idx:,}' assert idx <= SubSeedIdxRange.max_idx, f'subseed index > {SubSeedIdxRange.max_idx:,}' - sstype,ltr = ('short','S') if s[-1] in 'Ss' else ('long','L') - me = str.__new__(cls,str(idx)+ltr) + sstype, ltr = ('short', 'S') if s[-1] in 'Ss' else ('long', 'L') + me = str.__new__(cls, str(idx)+ltr) me.idx = idx me.type = sstype return me except Exception as e: - return cls.init_fail(e,s) + return cls.init_fail(e, s) class SubSeed(SeedBase): - idx = ImmutableAttr(int,typeconv=False) - nonce = ImmutableAttr(int,typeconv=False) + idx = ImmutableAttr(int, typeconv=False) + nonce = ImmutableAttr(int, typeconv=False) ss_idx = ImmutableAttr(SubSeedIdx) max_nonce = 1000 - def __init__(self,parent_list,idx,nonce,length): + def __init__(self, parent_list, idx, nonce, length): self.idx = idx self.nonce = nonce - self.ss_idx = str(idx) + { 'long': 'L', 'short': 'S' }[length] + self.ss_idx = str(idx) + {'long': 'L', 'short': 'S'}[length] self.parent_list = parent_list SeedBase.__init__( self, parent_list.parent_seed.cfg, - seed_bin=self.make_subseed_bin( parent_list, idx, nonce, length )) + seed_bin=self.make_subseed_bin(parent_list, idx, nonce, length)) @staticmethod - def make_subseed_bin(parent_list,idx:int,nonce:int,length:str): + def make_subseed_bin(parent_list, idx:int, nonce:int, length:str): seed = parent_list.parent_seed - short = { 'short': True, 'long': False }[length] + short = {'short': True, 'long': False}[length] # field maximums: idx: 4294967295 (1000000), nonce: 65535 (1000), short: 255 (1) - scramble_key = idx.to_bytes(4,'big') + nonce.to_bytes(2,'big') + short.to_bytes(1,'big') + scramble_key = idx.to_bytes(4, 'big') + nonce.to_bytes(2, 'big') + short.to_bytes(1, 'big') from .crypto import Crypto return Crypto(parent_list.parent_seed.cfg).scramble_seed( - seed.data,scramble_key)[:16 if short else seed.byte_len] + seed.data, scramble_key)[:16 if short else seed.byte_len] class SubSeedList(MMGenObject): have_short = True @@ -86,16 +86,16 @@ class SubSeedList(MMGenObject): debug_last_share_sid_len = 3 dfl_len = 100 - def __init__(self,parent_seed,length=None): + def __init__(self, parent_seed, length=None): self.member_type = SubSeed self.parent_seed = parent_seed - self.data = { 'long': IndexedDict(), 'short': IndexedDict() } + self.data = {'long': IndexedDict(), 'short': IndexedDict()} self.len = length or self.dfl_len def __len__(self): return len(self.data['long']) - def get_subseed_by_ss_idx(self,ss_idx_in,print_msg=False): + def get_subseed_by_ss_idx(self, ss_idx_in, print_msg=False): ss_idx = SubSeedIdx(ss_idx_in) if print_msg: msg_r('{} {} of {}...'.format( @@ -108,27 +108,27 @@ class SubSeedList(MMGenObject): self._generate(ss_idx.idx) sid = self.data[ss_idx.type].key(ss_idx.idx-1) - idx,nonce = self.data[ss_idx.type][sid] + idx, nonce = self.data[ss_idx.type][sid] if idx != ss_idx.idx: die(3, "{} != {}: self.data[{t!r}].key(i) does not match self.data[{t!r}][i]!".format( idx, ss_idx.idx, - t = ss_idx.type )) + t = ss_idx.type)) if print_msg: msg(f'\b\b\b => {SeedID.hlc(sid)}') - seed = self.member_type(self,idx,nonce,length=ss_idx.type) + seed = self.member_type(self, idx, nonce, length=ss_idx.type) assert seed.sid == sid, f'{seed.sid} != {sid}: Seed ID mismatch!' return seed - def get_subseed_by_seed_id(self,sid,last_idx=None,print_msg=False): + def get_subseed_by_seed_id(self, sid, last_idx=None, print_msg=False): def get_existing_subseed_by_seed_id(sid): - for k in ('long','short') if self.have_short else ('long',): + for k in ('long', 'short') if self.have_short else ('long',): if sid in self.data[k]: - idx,nonce = self.data[k][sid] - return self.member_type(self,idx,nonce,length=k) + idx, nonce = self.data[k][sid] + return self.member_type(self, idx, nonce, length=k) def do_msg(subseed): if print_msg: @@ -150,14 +150,14 @@ class SubSeedList(MMGenObject): if len(self) >= last_idx: return None - self._generate(last_idx,last_sid=sid) + self._generate(last_idx, last_sid=sid) subseed = get_existing_subseed_by_seed_id(sid) if subseed: do_msg(subseed) return subseed - def _collision_debug_msg(self,sid,idx,nonce,nonce_desc='nonce',debug_last_share=False): + def _collision_debug_msg(self, sid, idx, nonce, nonce_desc='nonce', debug_last_share=False): slen = 'short' if sid in self.data['short'] else 'long' m1 = f'add_subseed(idx={idx},{slen}):' if sid == self.parent_seed.sid: @@ -172,7 +172,7 @@ class SubSeedList(MMGenObject): m2 = f'collision with ID {sid} (idx={colliding_idx},{slen}),' msg(f'{m1:30} {m2:46} incrementing {nonce_desc} to {nonce+1}') - def _generate(self,last_idx=None,last_sid=None): + def _generate(self, last_idx=None, last_sid=None): if last_idx is None: last_idx = self.len @@ -185,27 +185,27 @@ class SubSeedList(MMGenObject): if last_sid is not None: last_sid = SeedID(sid=last_sid) - def add_subseed(idx,length): - for nonce in range(self.nonce_start,self.member_type.max_nonce+1): # handle SeedID collisions - sid = make_chksum_8(self.member_type.make_subseed_bin(self,idx,nonce,length)) + def add_subseed(idx, length): + for nonce in range(self.nonce_start, self.member_type.max_nonce+1): # handle SeedID collisions + sid = make_chksum_8(self.member_type.make_subseed_bin(self, idx, nonce, length)) if sid in self.data['long'] or sid in self.data['short'] or sid == self.parent_seed.sid: if self.parent_seed.cfg.debug_subseed: # should get ≈450 collisions for first 1,000,000 subseeds - self._collision_debug_msg(sid,idx,nonce) + self._collision_debug_msg(sid, idx, nonce) else: - self.data[length][sid] = (idx,nonce) + self.data[length][sid] = (idx, nonce) return last_sid == sid # must exit here, as this could leave self.data in inconsistent state die('SubSeedNonceRangeExceeded', 'add_subseed(): nonce range exceeded') - for idx in SubSeedIdxRange(first_idx,last_idx).iterate(): - match1 = add_subseed(idx,'long') - match2 = add_subseed(idx,'short') if self.have_short else False + for idx in SubSeedIdxRange(first_idx, last_idx).iterate(): + match1 = add_subseed(idx, 'long') + match2 = add_subseed(idx, 'short') if self.have_short else False if match1 or match2: break - def format(self,first_idx,last_idx): + def format(self, first_idx, last_idx): - r = SubSeedIdxRange(first_idx,last_idx) + r = SubSeedIdxRange(first_idx, last_idx) if len(self) < last_idx: self._generate(last_idx) @@ -214,11 +214,11 @@ class SubSeedList(MMGenObject): fs2 = '{i:>7}L: {:8} {i:>7}S: {:8}\n' hdr = f' Parent Seed: {self.parent_seed.sid.hl()} ({self.parent_seed.bitlen} bits)\n\n' - hdr += fs1.format('Long Subseeds','Short Subseeds') - hdr += fs1.format('-------------','--------------') + hdr += fs1.format('Long Subseeds', 'Short Subseeds') + hdr += fs1.format('-------------', '--------------') sl = self.data['long'].keys ss = self.data['short'].keys - body = (fs2.format( sl[n-1], ss[n-1], i=n ) for n in r.iterate()) + body = (fs2.format(sl[n-1], ss[n-1], i=n) for n in r.iterate()) return hdr + ''.join(body) diff --git a/mmgen/term.py b/mmgen/term.py index 9f71fd8e..dcd733f9 100755 --- a/mmgen/term.py +++ b/mmgen/term.py @@ -22,26 +22,26 @@ term: Terminal classes for the MMGen suite # TODO: reimplement as instance instead of class -import sys,os,time +import sys, os, time from collections import namedtuple -from .util import msg,msg_r,die +from .util import msg, msg_r, die if sys.platform in ('linux', 'darwin'): - import tty,termios + import tty, termios from select import select hold_protect_timeout = 2 if sys.platform == 'darwin' else 0.3 elif sys.platform == 'win32': try: import msvcrt except: - die(2,'Unable to set terminal mode') + die(2, 'Unable to set terminal mode') if not sys.stdin.isatty(): - msvcrt.setmode(sys.stdin.fileno(),os.O_BINARY) + msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY) else: - die(2,f'{sys.platform!r}: unsupported platform') + die(2, f'{sys.platform!r}: unsupported platform') -_term_dimensions = namedtuple('terminal_dimensions',['width','height']) +_term_dimensions = namedtuple('terminal_dimensions', ['width', 'height']) class MMGenTerm: @@ -50,11 +50,11 @@ class MMGenTerm: pass @classmethod - def init(cls,noecho=False): + def init(cls, noecho=False): pass @classmethod - def set(cls,*args,**kwargs): + def set(cls, *args, **kwargs): pass @classmethod @@ -69,34 +69,34 @@ class MMGenTermLinux(MMGenTerm): @classmethod def register_cleanup(cls): - if not hasattr(cls,'cleanup_registered'): + if not hasattr(cls, 'cleanup_registered'): import atexit atexit.register( lambda: termios.tcsetattr( cls.stdin_fd, termios.TCSADRAIN, - cls.orig_term) ) + cls.orig_term)) cls.cleanup_registered = True @classmethod def reset(cls): - termios.tcsetattr( cls.stdin_fd, termios.TCSANOW, cls.orig_term ) + termios.tcsetattr(cls.stdin_fd, termios.TCSANOW, cls.orig_term) cls.cur_term = cls.orig_term @classmethod - def set(cls,setting): + def set(cls, setting): d = { 'echo': lambda t: t[:3] + [t[3] | (termios.ECHO | termios.ECHONL)] + t[4:], # echo input chars 'noecho': lambda t: t[:3] + [t[3] & ~(termios.ECHO | termios.ECHONL)] + t[4:], # don’t echo input chars } - termios.tcsetattr( cls.stdin_fd, termios.TCSANOW, d[setting](cls.cur_term) ) + termios.tcsetattr(cls.stdin_fd, termios.TCSANOW, d[setting](cls.cur_term)) cls.cur_term = termios.tcgetattr(cls.stdin_fd) @classmethod - def init(cls,noecho=False): + def init(cls, noecho=False): cls.stdin_fd = sys.stdin.fileno() cls.cur_term = termios.tcgetattr(cls.stdin_fd) - if not hasattr(cls,'orig_term'): + if not hasattr(cls, 'orig_term'): cls.orig_term = cls.cur_term if noecho: cls.set('noecho') @@ -109,9 +109,9 @@ class MMGenTermLinux(MMGenTerm): try: ret = ( int(os.environ['COLUMNS']), - int(os.environ['LINES']) ) + int(os.environ['LINES'])) except: - ret = (80,25) + ret = (80, 25) return _term_dimensions(*ret) @classmethod @@ -128,7 +128,7 @@ class MMGenTermLinux(MMGenTerm): break @classmethod - def get_char(cls,prompt='',immed_chars='',prehold_protect=True,num_bytes=5): + def get_char(cls, prompt='', immed_chars='', prehold_protect=True, num_bytes=5): """ Use os.read(), not file.read(), to get a variable number of bytes without blocking. Request 5 bytes to cover escape sequences generated by F1, F2, .. Fn keys (5 bytes) @@ -142,7 +142,7 @@ class MMGenTermLinux(MMGenTerm): while True: # Protect against held-down key before read() key = select([sys.stdin], [], [], timeout)[0] - s = os.read(cls.stdin_fd,num_bytes).decode() + s = os.read(cls.stdin_fd, num_bytes).decode() if prehold_protect and key: continue if s in immed_chars: @@ -155,10 +155,10 @@ class MMGenTermLinux(MMGenTerm): return s @classmethod - def get_char_raw(cls,prompt='',num_bytes=5,**kwargs): + def get_char_raw(cls, prompt='', num_bytes=5, **kwargs): tty.setcbreak(cls.stdin_fd) msg_r(prompt) - s = os.read(cls.stdin_fd,num_bytes).decode() + s = os.read(cls.stdin_fd, num_bytes).decode() termios.tcsetattr(cls.stdin_fd, termios.TCSADRAIN, cls.cur_term) return s @@ -169,11 +169,11 @@ class MMGenTermLinuxStub(MMGenTermLinux): pass @classmethod - def init(cls,noecho=False): + def init(cls, noecho=False): cls.stdin_fd = sys.stdin.fileno() @classmethod - def set(cls,*args,**kwargs): + def set(cls, *args, **kwargs): pass @classmethod @@ -181,9 +181,9 @@ class MMGenTermLinuxStub(MMGenTermLinux): pass @classmethod - def get_char(cls,prompt='',immed_chars='',prehold_protect=None,num_bytes=5): + def get_char(cls, prompt='', immed_chars='', prehold_protect=None, num_bytes=5): msg_r(prompt) - return os.read(0,num_bytes).decode() + return os.read(0, num_bytes).decode() get_char_raw = get_char @@ -196,26 +196,26 @@ class MMGenTermMSWin(MMGenTerm): @classmethod def get_terminal_size(cls): import struct - x,y = 0,0 + x, y = 0, 0 try: - from ctypes import windll,create_string_buffer + from ctypes import windll, create_string_buffer # handles - stdin: -10, stdout: -11, stderr: -12 csbi = create_string_buffer(22) h = windll.kernel32.GetStdHandle(-12) - res = windll.kernel32.GetConsoleScreenBufferInfo(h,csbi) + res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi) assert res, 'failed to get console screen buffer info' - left,top,right,bottom = struct.unpack('hhhhHhhhhhh', csbi.raw)[5:9] + left, top, right, bottom = struct.unpack('hhhhHhhhhhh', csbi.raw)[5:9] x = right - left + 1 y = bottom - top + 1 except: pass if x and y: - return _term_dimensions(x,y) + return _term_dimensions(x, y) else: from .color import yellow msg(yellow('Warning: could not get terminal size. Using fallback dimensions.')) - return _term_dimensions(80,25) + return _term_dimensions(80, 25) @classmethod def kb_hold_protect(cls): @@ -230,7 +230,7 @@ class MMGenTermMSWin(MMGenTerm): return @classmethod - def get_char(cls,prompt='',immed_chars='',prehold_protect=True,num_bytes=None): + def get_char(cls, prompt='', immed_chars='', prehold_protect=True, num_bytes=None): """ always return a single character, ignore num_bytes first character of 2-character sequence returned by F1-F12 keys is discarded @@ -253,13 +253,13 @@ class MMGenTermMSWin(MMGenTerm): return ch @classmethod - def get_char_raw(cls,prompt='',num_bytes=None,**kwargs): + def get_char_raw(cls, prompt='', num_bytes=None, **kwargs): """ return single ASCII char or 2-char escape sequence, ignoring num_bytes """ msg_r(prompt) ret = msvcrt.getch() - if ret in (b'\x00',b'\xe0'): # first byte of 2-byte escape sequence + if ret in (b'\x00', b'\xe0'): # first byte of 2-byte escape sequence return chr(ret[0]) + chr(msvcrt.getch()[0]) if ret == b'\x03': raise KeyboardInterrupt @@ -268,7 +268,7 @@ class MMGenTermMSWin(MMGenTerm): class MMGenTermMSWinStub(MMGenTermMSWin): @classmethod - def get_char(cls,prompt='',immed_chars='',prehold_protect=None,num_bytes=None): + def get_char(cls, prompt='', immed_chars='', prehold_protect=None, num_bytes=None): """ Use stdin to allow UTF-8 and emulate the one-character behavior of MMGenTermMSWin """ @@ -289,7 +289,7 @@ def get_term(): 'win32': (MMGenTermMSWin if sys.stdin.isatty() else MMGenTermMSWinStub), }[sys.platform] -def init_term(cfg,noecho=False): +def init_term(cfg, noecho=False): term = get_term() diff --git a/mmgen/ui.py b/mmgen/ui.py index a04bff44..0ba05760 100755 --- a/mmgen/ui.py +++ b/mmgen/ui.py @@ -12,11 +12,11 @@ ui: Interactive user interface functions for the MMGen suite """ -import sys,os +import sys, os -from .util import msg,msg_r,Msg,die +from .util import msg, msg_r, Msg, die -def confirm_or_raise(cfg,message,action,expect='YES',exit_msg='Exiting at user request'): +def confirm_or_raise(cfg, message, action, expect='YES', exit_msg='Exiting at user request'): if message: msg(message) if line_input( @@ -24,27 +24,27 @@ def confirm_or_raise(cfg,message,action,expect='YES',exit_msg='Exiting at user r (f'{action} ' if action[0].isupper() else f'Are you sure you want to {action}?\n') + f'Type uppercase {expect!r} to confirm: ' ).strip() != expect: - die( 'UserNonConfirmation', exit_msg ) + die('UserNonConfirmation', exit_msg) -def get_words_from_user(cfg,prompt): - words = line_input( cfg, prompt, echo=cfg.echo_passphrase ).split() +def get_words_from_user(cfg, prompt): + words = line_input(cfg, prompt, echo=cfg.echo_passphrase).split() if cfg.debug: msg('Sanitized input: [{}]'.format(' '.join(words))) return words -def get_data_from_user(cfg,desc='data'): # user input MUST be UTF-8 - data = line_input( cfg, f'Enter {desc}: ', echo=cfg.echo_passphrase ) +def get_data_from_user(cfg, desc='data'): # user input MUST be UTF-8 + data = line_input(cfg, f'Enter {desc}: ', echo=cfg.echo_passphrase) if cfg.debug: msg(f'User input: [{data}]') return data -def line_input(cfg,prompt,echo=True,insert_txt='',hold_protect=True): +def line_input(cfg, prompt, echo=True, insert_txt='', hold_protect=True): """ multi-line prompts OK one-line prompts must begin at beginning of line empty prompts forbidden due to interactions with readline """ - assert prompt,'calling line_input() with an empty prompt forbidden' + assert prompt, 'calling line_input() with an empty prompt forbidden' def get_readline(): try: @@ -60,7 +60,7 @@ def line_input(cfg,prompt,echo=True,insert_txt='',hold_protect=True): if cfg.test_suite_popen_spawn: msg(prompt) sys.stderr.flush() # required by older Pythons (e.g. v3.7) - reply = os.read(0,4096).decode().rstrip('\n') # strip NL to mimic behavior of input() + reply = os.read(0, 4096).decode().rstrip('\n') # strip NL to mimic behavior of input() elif not sys.stdin.isatty(): msg_r(prompt) reply = input('') @@ -86,10 +86,10 @@ def keypress_confirm( default_yes = False, verbose = False, no_nl = False, - complete_prompt = False ): + complete_prompt = False): if not complete_prompt: - prompt = '{} {}: '.format( prompt, '(Y/n)' if default_yes else '(y/N)' ) + prompt = '{} {}: '.format(prompt, '(Y/n)' if default_yes else '(y/N)') nl = f'\r{" "*len(prompt)}\r' if no_nl else '\n' @@ -99,7 +99,7 @@ def keypress_confirm( from .term import get_char while True: - reply = get_char(prompt,immed_chars='yYnN').strip('\n\r') + reply = get_char(prompt, immed_chars='yYnN').strip('\n\r') if not reply: msg_r(nl) return default_yes @@ -111,7 +111,7 @@ def keypress_confirm( def do_pager(text): - pagers = ['less','more'] + pagers = ['less', 'more'] end_msg = '\n(end of text)\n\n' os.environ['LESS'] = '--jump-target=2 --shift=4 --tabs=4 --RAW-CONTROL-CHARS --chop-long-lines' @@ -123,7 +123,7 @@ def do_pager(text): for pager in pagers: try: m = text + ('' if pager == 'less' else end_msg) - run([pager],input=m.encode(),check=True) + run([pager], input=m.encode(), check=True) msg_r('\r') except: pass @@ -133,7 +133,7 @@ def do_pager(text): Msg(text+end_msg) set_vt100() -def do_license_msg(cfg,immed=False): +def do_license_msg(cfg, immed=False): if cfg.quiet or cfg.no_license or cfg.yes or not cfg.stdin_tty: return @@ -145,7 +145,7 @@ def do_license_msg(cfg,immed=False): from .term import get_char prompt = "Press 'w' for conditions and warranty info, or 'c' to continue: " while True: - reply = get_char(prompt, immed_chars=('','wc')[bool(immed)]) + reply = get_char(prompt, immed_chars=('', 'wc')[bool(immed)]) if reply == 'w': do_pager(gpl.conditions) elif reply == 'c': diff --git a/mmgen/util.py b/mmgen/util.py index 3b4783ff..ad88a0d1 100755 --- a/mmgen/util.py +++ b/mmgen/util.py @@ -20,9 +20,9 @@ util: Frequently-used variables, classes and utility functions for the MMGen suite """ -import sys,os,time,re +import sys, os, time, re -from .color import red,yellow,green,blue,purple +from .color import red, yellow, green, blue, purple from .cfg import gv ascii_lowercase = 'abcdefghijklmnopqrstuvwxyz' @@ -32,12 +32,12 @@ hexdigits = '0123456789abcdefABCDEF' hexdigits_uc = '0123456789ABCDEF' hexdigits_lc = '0123456789abcdef' -def noop(*args,**kwargs): +def noop(*args, **kwargs): pass class Util: - def __init__(self,cfg): + def __init__(self, cfg): self.cfg = cfg @@ -75,9 +75,9 @@ class Util: if not chk1 == chk2: fs = "{} ERROR: {} checksum ({}) doesn't match {} checksum ({})" - m = fs.format((hdr+':\n ' if hdr else 'CHECKSUM'),desc2,chk2,desc1,chk1) + m = fs.format((hdr+':\n ' if hdr else 'CHECKSUM'), desc2, chk2, desc1, chk1) if die_on_fail: - die(3,m) + die(3, m) else: if verbose or self.cfg.verbose: msg(m) @@ -90,7 +90,7 @@ class Util: def compare_or_die(self, val1, desc1, val2, desc2, e='Error'): if val1 != val2: - die(3,f"{e}: {desc2} ({val2}) doesn't match {desc1} ({val1})") + die(3, f"{e}: {desc2} ({val2}) doesn't match {desc1} ({val1})") if self.cfg.debug: msg(f'{capfirst(desc2)} OK ({val2})') return True @@ -101,7 +101,7 @@ if sys.platform == 'win32': gv.stderr.write(s) gv.stderr.flush() except: - os.write(2,s.encode()) + os.write(2, s.encode()) def msg(s): msg_r(s + '\n') @@ -111,7 +111,7 @@ if sys.platform == 'win32': gv.stdout.write(s) gv.stdout.flush() except: - os.write(1,s.encode()) + os.write(1, s.encode()) def Msg(s): Msg_r(s + '\n') @@ -156,73 +156,73 @@ def mdie(*args): mmsg(*args) sys.exit(0) -def die(ev,s='',stdout=False): - if isinstance(ev,int): - from .exception import MMGenSystemExit,MMGenError +def die(ev, s='', stdout=False): + if isinstance(ev, int): + from .exception import MMGenSystemExit, MMGenError if ev <= 2: - raise MMGenSystemExit(ev,s,stdout) + raise MMGenSystemExit(ev, s, stdout) else: - raise MMGenError(ev,s,stdout) - elif isinstance(ev,str): + raise MMGenError(ev, s, stdout) + elif isinstance(ev, str): from . import exception - raise getattr(exception,ev)(s) + raise getattr(exception, ev)(s) else: raise ValueError(f'{ev}: exit value must be string or int instance') -def Die(ev=0,s=''): - die(ev=ev,s=s,stdout=True) +def Die(ev=0, s=''): + die(ev=ev, s=s, stdout=True) def pp_fmt(d): import pprint - return pprint.PrettyPrinter(indent=4,compact=False).pformat(d) + return pprint.PrettyPrinter(indent=4, compact=False).pformat(d) def pp_msg(d): msg(pp_fmt(d)) -def indent(s,indent=' ',append='\n'): +def indent(s, indent=' ', append='\n'): "indent multiple lines of text with specified string" return indent + ('\n'+indent).join(s.strip().splitlines()) + append -def fmt(s,indent='',strip_char=None,append='\n'): +def fmt(s, indent='', strip_char=None, append='\n'): "de-indent multiple lines of text, or indent with specified string" return indent + ('\n'+indent).join([l.lstrip(strip_char) for l in s.strip().splitlines()]) + append -def fmt_list(iterable,fmt='dfl',indent='',conv=None): +def fmt_list(iterable, fmt='dfl', indent='', conv=None): "pretty-format a list" - _conv,sep,lq,rq = { - 'dfl': ( str, ", ", "'", "'"), - 'utf8': ( str, ", ", "“", "”"), - 'bare': ( repr, " ", "", ""), - 'barest': ( str, " ", "", ""), - 'fancy': ( str, " ", "‘", "’"), - 'no_quotes': ( str, ", ", "", ""), - 'compact': ( str, ",", "", ""), - 'no_spc': ( str, ",", "'", "'"), - 'min': ( str, ",", "", ""), - 'repr': ( repr, ", ", "", ""), - 'csv': ( repr, ",", "", ""), - 'col': ( str, "\n", "", ""), + _conv, sep, lq, rq = { + 'dfl': (str, ", ", "'", "'"), + 'utf8': (str, ", ", "“", "”"), + 'bare': (repr, " ", "", ""), + 'barest': (str, " ", "", ""), + 'fancy': (str, " ", "‘", "’"), + 'no_quotes': (str, ", ", "", ""), + 'compact': (str, ",", "", ""), + 'no_spc': (str, ",", "'", "'"), + 'min': (str, ",", "", ""), + 'repr': (repr, ", ", "", ""), + 'csv': (repr, ",", "", ""), + 'col': (str, "\n", "", ""), }[fmt] conv = conv or _conv return indent + (sep+indent).join(lq+conv(e)+rq for e in iterable) -def fmt_dict(mapping,fmt='dfl',kconv=None,vconv=None): +def fmt_dict(mapping, fmt='dfl', kconv=None, vconv=None): "pretty-format a dict" - kc,vc,sep,fs = { - 'dfl': ( str, str, ", ", "'{}' ({})" ), - 'dfl_compact': ( str, str, " ", "{} ({})" ), - 'square': ( str, str, ", ", "'{}' [{}]" ), - 'square_compact':( str, str, " ", "{} [{}]" ), - 'equal': ( str, str, ", ", "'{}'={}" ), - 'equal_spaced': ( str, str, ", ", "'{}' = {}" ), - 'equal_compact': ( str, str, " ", "{}={}" ), - 'kwargs': ( str, repr, ", ", "{}={}" ), - 'colon': ( str, repr, ", ", "{}:{}" ), - 'colon_compact': ( str, str, " ", "{}:{}" ), + kc, vc, sep, fs = { + 'dfl': (str, str, ", ", "'{}' ({})"), + 'dfl_compact': (str, str, " ", "{} ({})"), + 'square': (str, str, ", ", "'{}' [{}]"), + 'square_compact':(str, str, " ", "{} [{}]"), + 'equal': (str, str, ", ", "'{}'={}"), + 'equal_spaced': (str, str, ", ", "'{}' = {}"), + 'equal_compact': (str, str, " ", "{}={}"), + 'kwargs': (str, repr, ", ", "{}={}"), + 'colon': (str, repr, ", ", "{}:{}"), + 'colon_compact': (str, str, " ", "{}:{}"), }[fmt] kconv = kconv or kc vconv = vconv or vc - return sep.join(fs.format(kconv(k),vconv(v)) for k,v in mapping.items()) + return sep.join(fs.format(kconv(k), vconv(v)) for k, v in mapping.items()) def list_gen(*data): """ @@ -231,10 +231,10 @@ def list_gen(*data): elements of the sublist are included in the result. Otherwise the sublist is skipped. - If a sublist contains only one element, the condition defaults to true. """ - assert type(data) in (list,tuple), f'{type(data).__name__} not in (list,tuple)' + assert type(data) in (list, tuple), f'{type(data).__name__} not in (list, tuple)' def gen(): for d in data: - assert isinstance(d,list), f'{type(d).__name__} != list' + assert isinstance(d, list), f'{type(d).__name__} != list' if len(d) == 1: yield d[0] elif d[-1]: @@ -242,7 +242,7 @@ def list_gen(*data): yield d[idx] return list(gen()) -def remove_dups(iterable,edesc='element',desc='list',quiet=False,hide=False): +def remove_dups(iterable, edesc='element', desc='list', quiet=False, hide=False): """ Remove duplicate occurrences of iterable elements, preserving first occurrence If iterable is a generator, return a list, else type(iterable) @@ -256,44 +256,44 @@ def remove_dups(iterable,edesc='element',desc='list',quiet=False,hide=False): ret.append(e) return ret if type(iterable).__name__ == 'generator' else type(iterable)(ret) -def contains_any(target_list,source_list): - return any(map(target_list.count,source_list)) +def contains_any(target_list, source_list): + return any(map(target_list.count, source_list)) -def suf(arg,suf_type='s',verb='none'): +def suf(arg, suf_type='s', verb='none'): suf_types = { 'none': { - 's': ('s', ''), - 'es': ('es', ''), - 'ies': ('ies','y'), + 's': ('s', ''), + 'es': ('es', ''), + 'ies': ('ies', 'y'), }, 'is': { - 's': ('s are', ' is'), - 'es': ('es are', ' is'), - 'ies': ('ies are','y is'), + 's': ('s are', ' is'), + 'es': ('es are', ' is'), + 'ies': ('ies are', 'y is'), }, 'has': { - 's': ('s have', ' has'), - 'es': ('es have', ' has'), - 'ies': ('ies have','y has'), + 's': ('s have', ' has'), + 'es': ('es have', ' has'), + 'ies': ('ies have', 'y has'), }, } - if isinstance(arg,int): + if isinstance(arg, int): n = arg - elif isinstance(arg,(list,tuple,set,dict)): + elif isinstance(arg, (list, tuple, set, dict)): n = len(arg) else: - die(2,f'{arg}: invalid parameter for suf()') + die(2, f'{arg}: invalid parameter for suf()') return suf_types[verb][suf_type][n == 1] def get_extension(fn): return os.path.splitext(fn)[1][1:] -def remove_extension(fn,ext): - a,b = os.path.splitext(fn) +def remove_extension(fn, ext): + a, b = os.path.splitext(fn) return a if b[1:] == ext else fn -def make_chksum_N(s,nchars,sep=False,rounds=2,upper=True): - if isinstance(s,str): +def make_chksum_N(s, nchars, sep=False, rounds=2, upper=True): + if isinstance(s, str): s = s.encode() from hashlib import sha256 for i in range(rounds): @@ -301,28 +301,28 @@ def make_chksum_N(s,nchars,sep=False,rounds=2,upper=True): ret = s.hex()[:nchars] if sep: assert 4 <= nchars <= 64 and (not nchars % 4), 'illegal ‘nchars’ value' - ret = ' '.join( ret[i:i+4] for i in range(0,nchars,4) ) + ret = ' '.join(ret[i:i+4] for i in range(0, nchars, 4)) else: assert 4 <= nchars <= 64, 'illegal ‘nchars’ value' return ret.upper() if upper else ret -def make_chksum_8(s,sep=False): +def make_chksum_8(s, sep=False): from .obj import HexStr from hashlib import sha256 - s = HexStr(sha256(sha256(s).digest()).hexdigest()[:8].upper(),case='upper') - return '{} {}'.format(s[:4],s[4:]) if sep else s + s = HexStr(sha256(sha256(s).digest()).hexdigest()[:8].upper(), case='upper') + return '{} {}'.format(s[:4], s[4:]) if sep else s def make_chksum_6(s): from .obj import HexStr from hashlib import sha256 - if isinstance(s,str): + if isinstance(s, str): s = s.encode() return HexStr(sha256(s).hexdigest()[:6]) def is_chksum_6(s): return len(s) == 6 and set(s) <= set(hexdigits_lc) -def split_into_cols(col_wid,s): +def split_into_cols(col_wid, s): return ' '.join([s[col_wid*i:col_wid*(i+1)] for i in range(len(s)//col_wid+1)]).rstrip() def capfirst(s): # different from str.capitalize() - doesn't downcase any uc in string @@ -332,20 +332,20 @@ def decode_timestamp(s): # tz_save = open('/etc/timezone').read().rstrip() os.environ['TZ'] = 'UTC' # os.environ['TZ'] = tz_save - return int(time.mktime( time.strptime(s,'%Y%m%d_%H%M%S') )) + return int(time.mktime(time.strptime(s, '%Y%m%d_%H%M%S'))) def make_timestamp(secs=None): return '{:04d}{:02d}{:02d}_{:02d}{:02d}{:02d}'.format(*time.gmtime( - int(secs) if secs is not None else time.time() )[:6]) + int(secs) if secs is not None else time.time())[:6]) def make_timestr(secs=None): return '{}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}'.format(*time.gmtime( - int(secs) if secs is not None else time.time() )[:6]) + int(secs) if secs is not None else time.time())[:6]) def secs_to_dhms(secs): hrs = secs // 3600 return '{}{:02d}:{:02d}:{:02d} h/m/s'.format( - ('{} day{}, '.format(hrs//24,suf(hrs//24)) if hrs > 24 else ''), + ('{} day{}, '.format(hrs//24, suf(hrs//24)) if hrs > 24 else ''), hrs % 24, (secs // 60) % 60, secs % 60 @@ -360,9 +360,9 @@ def secs_to_ms(secs): def is_int(s): # actually is_nonnegative_int() return set(str(s)) <= set(digits) -def check_int_between(val,imin,imax,desc): +def check_int_between(val, imin, imax, desc): if not imin <= int(val) <= imax: - die(1,f'{val}: invalid value for {desc} (must be between {imin} and {imax})') + die(1, f'{val}: invalid value for {desc} (must be between {imin} and {imax})') return int(val) def is_hex_str(s): @@ -379,36 +379,36 @@ def is_utf8(s): else: return True -def remove_whitespace(s,ws='\t\r\n '): - return s.translate(dict((ord(e),None) for e in ws)) +def remove_whitespace(s, ws='\t\r\n '): + return s.translate(dict((ord(e), None) for e in ws)) def strip_comment(line): - return re.sub('#.*','',line).rstrip() + return re.sub('#.*', '', line).rstrip() def strip_comments(lines): pat = re.compile('#.*') - return [m for m in [pat.sub('',l).rstrip() for l in lines] if m != ''] + return [m for m in [pat.sub('', l).rstrip() for l in lines] if m != ''] -def make_full_path(outdir,outfile): +def make_full_path(outdir, outfile): return os.path.normpath(os.path.join(outdir, os.path.basename(outfile))) class oneshot_warning: color = 'nocolor' - def __init__(self,div=None,fmt_args=[],reverse=False): - self.do(type(self),div,fmt_args,reverse) + def __init__(self, div=None, fmt_args=[], reverse=False): + self.do(type(self), div, fmt_args, reverse) - def do(self,wcls,div,fmt_args,reverse): + def do(self, wcls, div, fmt_args, reverse): def do_warning(): from . import color - msg(getattr(color, getattr(wcls,'color'))('WARNING: ' + getattr(wcls,'message').format(*fmt_args))) + msg(getattr(color, getattr(wcls, 'color'))('WARNING: ' + getattr(wcls, 'message').format(*fmt_args))) - if not hasattr(wcls,'data'): - setattr(wcls,'data',[]) + if not hasattr(wcls, 'data'): + setattr(wcls, 'data', []) - data = getattr(wcls,'data') + data = getattr(wcls, 'data') condition = (div in data) if reverse else (not div in data) if not div in data: @@ -422,10 +422,10 @@ class oneshot_warning: class oneshot_warning_group(oneshot_warning): - def __init__(self,wcls,div=None,fmt_args=[],reverse=False): - self.do(getattr(self,wcls),div,fmt_args,reverse) + def __init__(self, wcls, div=None, fmt_args=[], reverse=False): + self.do(getattr(self, wcls), div, fmt_args, reverse) -def get_subclasses(cls,names=False): +def get_subclasses(cls, names=False): def gen(cls): for i in cls.__subclasses__(): yield i @@ -442,11 +442,11 @@ def wrap_ripemd160(called=[]): import hashlib hashlib.new('ripemd160') except ValueError: - def hashlib_new_wrapper(name,*args,**kwargs): + def hashlib_new_wrapper(name, *args, **kwargs): if name == 'ripemd160': - return ripemd160(*args,**kwargs) + return ripemd160(*args, **kwargs) else: - return hashlib_new(name,*args,**kwargs) + return hashlib_new(name, *args, **kwargs) from .contrib.ripemd160 import ripemd160 hashlib_new = hashlib.new hashlib.new = hashlib_new_wrapper @@ -454,7 +454,7 @@ def wrap_ripemd160(called=[]): def exit_if_mswin(feature): if sys.platform == 'win32': - die(2, capfirst(feature) + ' not supported on the MSWin / MSYS2 platform' ) + die(2, capfirst(feature) + ' not supported on the MSWin / MSYS2 platform') def have_sudo(silent=False): from subprocess import run, DEVNULL diff --git a/mmgen/util2.py b/mmgen/util2.py index c0f26edd..7e3da4e5 100755 --- a/mmgen/util2.py +++ b/mmgen/util2.py @@ -12,19 +12,19 @@ util2: Less frequently-used variables, classes and utility functions for the MMGen suite """ -import sys,re,time -from .util import msg,suf,hexdigits,die +import sys, re, time +from .util import msg, suf, hexdigits, die -def die_wait(delay,ev=0,s=''): - assert isinstance(delay,int) - assert isinstance(ev,int) +def die_wait(delay, ev=0, s=''): + assert isinstance(delay, int) + assert isinstance(ev, int) if s: msg(s) time.sleep(delay) sys.exit(ev) -def die_pause(ev=0,s=''): - assert isinstance(ev,int) +def die_pause(ev=0, s=''): + assert isinstance(ev, int) if s: msg(s) input('Press ENTER to exit') @@ -39,12 +39,12 @@ def load_cryptodomex(): try: import Crypto # cryptodome except ImportError: - die(2,'Unable to import either the ‘pycryptodomex’ or ‘pycryptodome’ package') + die(2, 'Unable to import either the ‘pycryptodomex’ or ‘pycryptodome’ package') else: sys.modules['Cryptodome'] = Crypto # called with no arguments by pyethereum.utils: -def get_keccak(cfg=None,cached_ret=[]): +def get_keccak(cfg=None, cached_ret=[]): if not cached_ret: if cfg and cfg.use_internal_keccak_module: @@ -61,7 +61,7 @@ def get_keccak(cfg=None,cached_ret=[]): die('MMGenImportError', 'Please install the ‘pycryptodome’ or ‘pycryptodomex’ package on your system') def keccak_256(data): - return keccak.new(data=data,digest_bytes=32) + return keccak.new(data=data, digest_bytes=32) cached_ret.append(keccak_256) return cached_ret[0] @@ -87,13 +87,13 @@ bytespec_map = ( ('E', 1152921504606846976), ) -def int2bytespec(n,spec,fmt,print_sym=True,strip=False,add_space=False): +def int2bytespec(n, spec, fmt, print_sym=True, strip=False, add_space=False): def spec2int(spec): - for k,v in bytespec_map: + for k, v in bytespec_map: if k == spec: return v - die(1,f'{spec!r}: unrecognized bytespec') + die(1, f'{spec!r}: unrecognized bytespec') ret = f'{n/spec2int(spec):{fmt}f}' if strip: @@ -101,17 +101,17 @@ def int2bytespec(n,spec,fmt,print_sym=True,strip=False,add_space=False): return ( ret + ('0' if ret.endswith('.') else '') - + ((' ' if add_space else '') + spec if print_sym else '') ) + + ((' ' if add_space else '') + spec if print_sym else '')) else: return ( ret - + ((' ' if add_space else '') + spec if print_sym else '') ) + + ((' ' if add_space else '') + spec if print_sym else '')) def parse_bytespec(nbytes): - m = re.match(r'([0123456789.]+)(.*)',nbytes) + m = re.match(r'([0123456789.]+)(.*)', nbytes) if m: if m.group(2): - for k,v in bytespec_map: + for k, v in bytespec_map: if k == m.group(2): from decimal import Decimal return int(Decimal(m.group(1)) * v) @@ -121,9 +121,9 @@ def parse_bytespec(nbytes): else: return int(nbytes) - die(1,f'{nbytes!r}: invalid byte specifier') + die(1, f'{nbytes!r}: invalid byte specifier') -def format_elapsed_days_hr(t,now=None,cached={}): +def format_elapsed_days_hr(t, now=None, cached={}): e = int((now or time.time()) - t) if not e in cached: days = abs(e) // 86400 @@ -155,7 +155,7 @@ def format_elapsed_hr(t, now=None, cached={}, rel_now=True, show_secs=False): cached[key] = ' '.join(f'{n} {desc}{suf(n)}' for desc, n in data if n) + add_suffix() return cached[key] -def pretty_format(s,width=80,pfx=''): +def pretty_format(s, width=80, pfx=''): out = [] while s: if len(s) <= width: @@ -166,8 +166,8 @@ def pretty_format(s,width=80,pfx=''): s = s[i+1:] return pfx + ('\n'+pfx).join(out) -def block_format(data,gw=2,cols=8,line_nums=None,data_is_hex=False): - assert line_nums in (None,'hex','dec'),"'line_nums' must be one of None, 'hex' or 'dec'" +def block_format(data, gw=2, cols=8, line_nums=None, data_is_hex=False): + assert line_nums in (None, 'hex', 'dec'), "'line_nums' must be one of None, 'hex' or 'dec'" ln_fs = '{:06x}: ' if line_nums == 'hex' else '{:06}: ' bytes_per_chunk = gw if data_is_hex: @@ -180,12 +180,12 @@ def block_format(data,gw=2,cols=8,line_nums=None,data_is_hex=False): for i in range(nchunks) ).rstrip() + '\n' -def pretty_hexdump(data,gw=2,cols=8,line_nums=None): - return block_format(data.hex(),gw,cols,line_nums,data_is_hex=True) +def pretty_hexdump(data, gw=2, cols=8, line_nums=None): + return block_format(data.hex(), gw, cols, line_nums, data_is_hex=True) def decode_pretty_hexdump(data): pat = re.compile(fr'^[{hexdigits}]+:\s+') - lines = [pat.sub('',line) for line in data.splitlines()] + lines = [pat.sub('', line) for line in data.splitlines()] try: return bytes.fromhex(''.join((''.join(lines).split()))) except: diff --git a/mmgen/xmrseed.py b/mmgen/xmrseed.py index 425ec6c9..7a120cfb 100755 --- a/mmgen/xmrseed.py +++ b/mmgen/xmrseed.py @@ -24,17 +24,17 @@ from .baseconv import baseconv from .util import die def is_xmrseed(s): - return bool( xmrseed().tobytes(s.split()) ) + return bool(xmrseed().tobytes(s.split())) # implements a subset of the baseconv API class xmrseed(baseconv): desc = baseconv.dt('Monero mnemonic', 'Monero new-style mnemonic seed phrase') wl_chksum = '3c381ebb' - seedlen_map = { 32:25 } - seedlen_map_rev = { 25:32 } + seedlen_map = {32: 25} + seedlen_map_rev = {25: 32} - def __init__(self,wl_id='xmrseed'): + def __init__(self, wl_id='xmrseed'): assert wl_id == 'xmrseed', "initialize with 'xmrseed' for compatibility with baseconv API" from .wordlist.monero import words self.digits = words @@ -46,9 +46,9 @@ class xmrseed(baseconv): wstr = ''.join(word[:3] for word in words) return words[crc32(wstr.encode()) % len(words)] - def tobytes(self,words_arg,pad=None): + def tobytes(self, words_arg, pad=None): - assert isinstance(words_arg,(list,tuple)),'words must be list or tuple' + assert isinstance(words_arg, (list, tuple)), 'words must be list or tuple' assert pad is None, f"{pad}: invalid 'pad' argument (must be None)" words = words_arg @@ -58,26 +58,26 @@ class xmrseed(baseconv): base = len(wl) if not set(words) <= set(wl): - die( 'MnemonicError', f'{words!r}: not in {desc} format' ) + die('MnemonicError', f'{words!r}: not in {desc} format') if len(words) not in self.seedlen_map_rev: - die( 'MnemonicError', f'{len(words)}: invalid seed phrase length for {desc}' ) + die('MnemonicError', f'{len(words)}: invalid seed phrase length for {desc}') z = self.monero_mn_checksum(words[:-1]) if z != words[-1]: - die( 'MnemonicError', f'invalid {desc} checksum' ) + die('MnemonicError', f'invalid {desc} checksum') words = tuple(words[:-1]) def gen(): for i in range(len(words)//3): - w1,w2,w3 = [wl.index(w) for w in words[3*i:3*i+3]] + w1, w2, w3 = [wl.index(w) for w in words[3*i:3*i+3]] x = w1 + base*((w2-w1)%base) + base*base*((w3-w2)%base) - yield x.to_bytes(4,'big')[::-1] + yield x.to_bytes(4, 'big')[::-1] return b''.join(gen()) - def frombytes(self,bytestr,pad=None,tostr=False): + def frombytes(self, bytestr, pad=None, tostr=False): assert pad is None, f"{pad}: invalid 'pad' argument (must be None)" desc = self.desc.short @@ -85,19 +85,19 @@ class xmrseed(baseconv): base = len(wl) if len(bytestr) not in self.seedlen_map: - die( 'SeedLengthError', f'{len(bytestr)}: invalid seed byte length for {desc}' ) + die('SeedLengthError', f'{len(bytestr)}: invalid seed byte length for {desc}') def num2base_monero(num): w1 = num % base w2 = (num//base + w1) % base w3 = (num//base//base + w2) % base - return ( wl[w1], wl[w2], wl[w3] ) + return (wl[w1], wl[w2], wl[w3]) def gen(): for i in range(len(bytestr)//4): - yield from num2base_monero(int.from_bytes( bytestr[i*4:i*4+4][::-1], 'big' )) + yield from num2base_monero(int.from_bytes(bytestr[i*4:i*4+4][::-1], 'big')) o = list(gen()) - o.append( self.monero_mn_checksum(o) ) + o.append(self.monero_mn_checksum(o)) return ' '.join(o) if tostr else tuple(o)