whitespace: top-level modules
This commit is contained in:
parent
96b6b38c01
commit
b5600fc563
43 changed files with 1962 additions and 1982 deletions
101
mmgen/addr.py
101
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)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
))
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
261
mmgen/cfg.py
261
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
|
||||
|
|
|
|||
110
mmgen/cfgfile.py
110
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)}:'
|
||||
|
|
|
|||
|
|
@ -21,27 +21,27 @@ color: color handling for the MMGen suite
|
|||
"""
|
||||
|
||||
_colors = {
|
||||
'black': ( 232, (30,0) ),
|
||||
'red': ( 210, (31,1) ),
|
||||
'green': ( 121, (32,1) ),
|
||||
'yellow': ( 229, (33,1) ),
|
||||
'blue': ( 75, (34,1) ),
|
||||
'magenta': ( 205, (35,1) ),
|
||||
'cyan': ( 122, (36,1) ),
|
||||
'black': (232, (30, 0)),
|
||||
'red': (210, (31, 1)),
|
||||
'green': (121, (32, 1)),
|
||||
'yellow': (229, (33, 1)),
|
||||
'blue': (75, (34, 1)),
|
||||
'magenta': (205, (35, 1)),
|
||||
'cyan': (122, (36, 1)),
|
||||
|
||||
'gray': ( 246, (30,1) ),
|
||||
'orange': ( 216, (31,1) ),
|
||||
'purple': ( 141, (35,1) ),
|
||||
'pink': ( 218, (35,1) ),
|
||||
'gray': (246, (30, 1)),
|
||||
'orange': (216, (31, 1)),
|
||||
'purple': (141, (35, 1)),
|
||||
'pink': (218, (35, 1)),
|
||||
|
||||
'melon': ( 222, (33,1) ),
|
||||
'brown': ( 173, (33,0) ),
|
||||
'grndim': ( 108, (32,0) ),
|
||||
'melon': (222, (33, 1)),
|
||||
'brown': (173, (33, 0)),
|
||||
'grndim': (108, (32, 0)),
|
||||
|
||||
'redbg': ( (232,210), (30,101) ),
|
||||
'grnbg': ( (232,121), (30,102) ),
|
||||
'yelbg': ( (232,229), (30,103) ),
|
||||
'blubg': ( (232,75), (30,104) ),
|
||||
'redbg': ((232, 210), (30, 101)),
|
||||
'grnbg': ((232, 121), (30, 102)),
|
||||
'yelbg': ((232, 229), (30, 103)),
|
||||
'blubg': ((232, 75), (30, 104)),
|
||||
}
|
||||
|
||||
def nocolor(s):
|
||||
|
|
@ -52,16 +52,16 @@ def set_vt100():
|
|||
import sys
|
||||
if sys.platform == 'win32':
|
||||
from subprocess import run
|
||||
run([],shell=True)
|
||||
run([], shell=True)
|
||||
|
||||
def get_terminfo_colors(term=None):
|
||||
from subprocess import run,PIPE
|
||||
cmd = ['infocmp','-0']
|
||||
from subprocess import run, PIPE
|
||||
cmd = ['infocmp', '-0']
|
||||
if term:
|
||||
cmd.append(term)
|
||||
|
||||
try:
|
||||
cmdout = run(cmd,stdout=PIPE,check=True).stdout.decode()
|
||||
cmdout = run(cmd, stdout=PIPE, check=True).stdout.decode()
|
||||
except:
|
||||
set_vt100()
|
||||
return None
|
||||
|
|
@ -72,12 +72,12 @@ def get_terminfo_colors(term=None):
|
|||
if s.isdecimal():
|
||||
return int(s)
|
||||
elif s.startswith('0x') and is_hex_str(s[2:]):
|
||||
return int(s[2:],16)
|
||||
return int(s[2:], 16)
|
||||
else:
|
||||
return None
|
||||
|
||||
def init_color(num_colors='auto'):
|
||||
assert num_colors in ('auto',8,16,256,0)
|
||||
assert num_colors in ('auto', 8, 16, 256, 0)
|
||||
|
||||
import sys
|
||||
self = sys.modules[__name__]
|
||||
|
|
@ -98,19 +98,19 @@ def init_color(num_colors='auto'):
|
|||
if num_colors == 0:
|
||||
ncc = (lambda s: s).__code__
|
||||
for c in _colors:
|
||||
getattr(self,c).__code__ = ncc
|
||||
getattr(self, c).__code__ = ncc
|
||||
elif num_colors == 256:
|
||||
for c,e in _colors.items():
|
||||
for c, e in _colors.items():
|
||||
start = (
|
||||
'\033[38;5;{};1m'.format(e[0]) if type(e[0]) == int else
|
||||
'\033[38;5;{};48;5;{};1m'.format(*e[0]) )
|
||||
getattr(self,c).__code__ = eval(f'(lambda s: "{start}" + s + "{reset}").__code__')
|
||||
elif num_colors in (8,16):
|
||||
for c,e in _colors.items():
|
||||
'\033[38;5;{};48;5;{};1m'.format(*e[0]))
|
||||
getattr(self, c).__code__ = eval(f'(lambda s: "{start}" + s + "{reset}").__code__')
|
||||
elif num_colors in (8, 16):
|
||||
for c, e in _colors.items():
|
||||
start = (
|
||||
'\033[{}m'.format(e[1][0]) if e[1][1] == 0 else
|
||||
'\033[{};{}m'.format(*e[1]) )
|
||||
getattr(self,c).__code__ = eval(f'(lambda s: "{start}" + s + "{reset}").__code__')
|
||||
'\033[{};{}m'.format(*e[1]))
|
||||
getattr(self, c).__code__ = eval(f'(lambda s: "{start}" + s + "{reset}").__code__')
|
||||
|
||||
set_vt100()
|
||||
|
||||
|
|
|
|||
162
mmgen/crypto.py
162
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...')
|
||||
|
|
|
|||
202
mmgen/daemon.py
202
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')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
72
mmgen/key.py
72
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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
58
mmgen/led.py
58
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')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
152
mmgen/msg.py
152
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')
|
||||
|
|
|
|||
172
mmgen/obj.py
172
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
|
||||
|
|
|
|||
|
|
@ -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__))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
212
mmgen/rpc.py
212
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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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'))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
36
mmgen/ui.py
36
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':
|
||||
|
|
|
|||
202
mmgen/util.py
202
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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue