Browse Source

whitespace: top-level modules

The MMGen Project 4 months ago
parent
commit
b5600fc563
43 changed files with 1962 additions and 1982 deletions
  1. 51 50
      mmgen/addr.py
  2. 25 25
      mmgen/addrdata.py
  3. 59 59
      mmgen/addrfile.py
  4. 9 9
      mmgen/addrgen.py
  5. 81 81
      mmgen/addrlist.py
  6. 80 80
      mmgen/autosign.py
  7. 24 26
      mmgen/base_obj.py
  8. 47 48
      mmgen/baseconv.py
  9. 27 27
      mmgen/bip39.py
  10. 129 132
      mmgen/cfg.py
  11. 55 55
      mmgen/cfgfile.py
  12. 35 35
      mmgen/color.py
  13. 77 85
      mmgen/crypto.py
  14. 101 101
      mmgen/daemon.py
  15. 6 6
      mmgen/derive.py
  16. 27 27
      mmgen/devinit.py
  17. 66 66
      mmgen/devtools.py
  18. 1 1
      mmgen/exception.py
  19. 28 28
      mmgen/filename.py
  20. 56 56
      mmgen/fileutil.py
  21. 19 19
      mmgen/flags.py
  22. 36 36
      mmgen/key.py
  23. 21 25
      mmgen/keygen.py
  24. 29 29
      mmgen/led.py
  25. 8 8
      mmgen/main.py
  26. 55 55
      mmgen/mn_entry.py
  27. 76 76
      mmgen/msg.py
  28. 86 86
      mmgen/obj.py
  29. 36 36
      mmgen/objmethods.py
  30. 5 5
      mmgen/opts.py
  31. 51 51
      mmgen/passwdlist.py
  32. 64 64
      mmgen/protocol.py
  33. 11 11
      mmgen/pyversion.py
  34. 106 106
      mmgen/rpc.py
  35. 26 26
      mmgen/seed.py
  36. 71 74
      mmgen/seedsplit.py
  37. 34 34
      mmgen/sha2.py
  38. 47 47
      mmgen/subseed.py
  39. 37 37
      mmgen/term.py
  40. 18 18
      mmgen/ui.py
  41. 101 101
      mmgen/util.py
  42. 25 25
      mmgen/util2.py
  43. 16 16
      mmgen/xmrseed.py

+ 51 - 50
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)

+ 25 - 25
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))
 			))

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

+ 9 - 9
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)

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

+ 80 - 80
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')

+ 24 - 26
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)

+ 47 - 48
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

+ 27 - 27
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
 

+ 129 - 132
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

+ 55 - 55
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)}:'

+ 35 - 35
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) ),
-
-	'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) ),
-
-	'redbg':       ( (232,210), (30,101) ),
-	'grnbg':       ( (232,121), (30,102) ),
-	'yelbg':       ( (232,229), (30,103) ),
-	'blubg':       ( (232,75),  (30,104) ),
+	'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)),
+
+	'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)),
 }
 
 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()
 

+ 77 - 85
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...')

+ 101 - 101
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')

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

+ 27 - 27
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)

+ 66 - 66
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

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

+ 28 - 28
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)

+ 56 - 56
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()

+ 19 - 19
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'

+ 36 - 36
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)

+ 21 - 25
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)

+ 29 - 29
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')

+ 8 - 8
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

+ 55 - 55
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)

+ 76 - 76
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')

+ 86 - 86
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

+ 36 - 36
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__))

+ 5 - 5
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
 

+ 51 - 51
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())

+ 64 - 64
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)

+ 11 - 11
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)
 

+ 106 - 106
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)
 

+ 26 - 26
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)

+ 71 - 74
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'))

+ 34 - 34
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

+ 47 - 47
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)

+ 37 - 37
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()
 

+ 18 - 18
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':

+ 101 - 101
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

+ 25 - 25
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:

+ 16 - 16
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)