|
@@ -22,18 +22,10 @@ addr.py: Address generation/display routines for the MMGen suite
|
|
|
|
|
|
from hashlib import sha256,sha512
|
|
|
from .common import *
|
|
|
-from .base_obj import AsyncInit
|
|
|
-from .objmethods import Hilite,InitErrors,MMGenObject
|
|
|
+from .objmethods import MMGenObject
|
|
|
from .obj import *
|
|
|
from .baseconv import *
|
|
|
-from .protocol import init_proto,hash160
|
|
|
-from .seed import SeedID,is_seed_id
|
|
|
-
|
|
|
-pnm = g.proj_name
|
|
|
-
|
|
|
-def dmsg_sc(desc,data):
|
|
|
- if g.debug_addrlist:
|
|
|
- Msg(f'sc_debug_{desc}: {data}')
|
|
|
+from .protocol import hash160
|
|
|
|
|
|
class AddrGenerator(MMGenObject):
|
|
|
def __new__(cls,proto,addr_type):
|
|
@@ -295,536 +287,9 @@ class KeyGeneratorDummy(KeyGenerator):
|
|
|
s = privhex,
|
|
|
privkey = privhex )
|
|
|
|
|
|
-class AddrListEntryBase(MMGenListItem):
|
|
|
- invalid_attrs = {'proto'}
|
|
|
- def __init__(self,proto,**kwargs):
|
|
|
- self.__dict__['proto'] = proto
|
|
|
- MMGenListItem.__init__(self,**kwargs)
|
|
|
-
|
|
|
-class AddrListEntry(AddrListEntryBase):
|
|
|
- addr = ListItemAttr('CoinAddr',include_proto=True)
|
|
|
- idx = ListItemAttr('AddrIdx') # not present in flat addrlists
|
|
|
- label = ListItemAttr('TwComment',reassign_ok=True)
|
|
|
- sec = ListItemAttr('PrivKey',include_proto=True)
|
|
|
- viewkey = ListItemAttr('ViewKey',include_proto=True)
|
|
|
- wallet_passwd = ListItemAttr('WalletPassword')
|
|
|
-
|
|
|
-class PasswordListEntry(AddrListEntryBase):
|
|
|
- passwd = ListItemAttr(str,typeconv=False) # TODO: create Password type
|
|
|
- idx = ImmutableAttr('AddrIdx')
|
|
|
- label = ListItemAttr('TwComment',reassign_ok=True)
|
|
|
- sec = ListItemAttr('PrivKey',include_proto=True)
|
|
|
-
|
|
|
-class AddrListChksum(str,Hilite):
|
|
|
- color = 'pink'
|
|
|
- trunc_ok = False
|
|
|
-
|
|
|
- def __new__(cls,addrlist):
|
|
|
- ea = addrlist.al_id.mmtype.extra_attrs # add viewkey and passwd to the mix, if present
|
|
|
- if ea == None: ea = ()
|
|
|
- lines = [' '.join(
|
|
|
- addrlist.chksum_rec_f(e) +
|
|
|
- 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))
|
|
|
-
|
|
|
-class AddrListIDStr(str,Hilite):
|
|
|
- color = 'green'
|
|
|
- trunc_ok = False
|
|
|
-
|
|
|
- def __new__(cls,addrlist,fmt_str=None):
|
|
|
- idxs = [e.idx for e in addrlist.data]
|
|
|
- prev = idxs[0]
|
|
|
- ret = prev,
|
|
|
- for i in idxs[1:]:
|
|
|
- if i == prev + 1:
|
|
|
- if i == idxs[-1]: ret += '-', i
|
|
|
- else:
|
|
|
- if prev != ret[-1]: ret += '-', prev
|
|
|
- ret += ',', i
|
|
|
- prev = i
|
|
|
- s = ''.join(map(str,ret))
|
|
|
-
|
|
|
- if fmt_str:
|
|
|
- ret = fmt_str.format(s)
|
|
|
- else:
|
|
|
- bc = (addrlist.proto.base_coin,addrlist.proto.coin)[addrlist.proto.base_coin=='ETH']
|
|
|
- mt = addrlist.al_id.mmtype
|
|
|
- ret = '{}{}{}[{}]'.format(
|
|
|
- addrlist.al_id.sid,
|
|
|
- ('-'+bc,'')[bc == 'BTC'],
|
|
|
- ('-'+mt,'')[mt in ('L','E')],
|
|
|
- s )
|
|
|
-
|
|
|
- dmsg_sc('id_str',ret[8:].split('[')[0])
|
|
|
-
|
|
|
- return str.__new__(cls,ret)
|
|
|
-
|
|
|
-class AddrList(MMGenObject): # Address info for a single seed ID
|
|
|
- msgs = {
|
|
|
- 'record_chksum': """
|
|
|
-Record this checksum: it will be used to verify the address file in the future
|
|
|
-""".strip(),
|
|
|
- 'check_chksum': 'Check this value against your records',
|
|
|
- 'removed_dup_keys': f"""
|
|
|
-Removed {{}} duplicate WIF key{{}} from keylist (also in {pnm} key-address file
|
|
|
-""".strip(),
|
|
|
- }
|
|
|
- entry_type = AddrListEntry
|
|
|
- main_attr = 'addr'
|
|
|
- desc = 'address'
|
|
|
- gen_desc = 'address'
|
|
|
- gen_desc_pl = 'es'
|
|
|
- gen_addrs = True
|
|
|
- gen_passwds = False
|
|
|
- gen_keys = False
|
|
|
- has_keys = False
|
|
|
- chksum_rec_f = lambda foo,e: (str(e.idx), e.addr)
|
|
|
-
|
|
|
- def __init__(self,proto,
|
|
|
- addrfile = '',
|
|
|
- al_id = '',
|
|
|
- adata = [],
|
|
|
- seed = '',
|
|
|
- addr_idxs = '',
|
|
|
- src = '',
|
|
|
- addrlist = '',
|
|
|
- keylist = '',
|
|
|
- mmtype = None,
|
|
|
- skip_key_address_validity_check = False,
|
|
|
- skip_chksum = False ):
|
|
|
-
|
|
|
- self.skip_ka_check = skip_key_address_validity_check
|
|
|
- self.update_msgs()
|
|
|
- mmtype = mmtype or proto.dfl_mmtype
|
|
|
- assert mmtype in MMGenAddrType.mmtypes, f'{mmtype}: mmtype not in {MMGenAddrType.mmtypes!r}'
|
|
|
-
|
|
|
- from .protocol import CoinProtocol
|
|
|
- self.bitcoin_addrtypes = tuple(
|
|
|
- MMGenAddrType(CoinProtocol.Bitcoin,key).name for key in CoinProtocol.Bitcoin.mmtypes)
|
|
|
-
|
|
|
- self.proto = proto
|
|
|
-
|
|
|
- do_chksum = False
|
|
|
- if seed and addr_idxs: # data from seed + idxs
|
|
|
- self.al_id,src = AddrListID(seed.sid,mmtype),'gen'
|
|
|
- adata = self.generate(seed,addr_idxs)
|
|
|
- do_chksum = True
|
|
|
- elif addrfile: # data from MMGen address file
|
|
|
- self.infile = addrfile
|
|
|
- adata = self.get_file().parse_file(addrfile) # sets self.al_id
|
|
|
- do_chksum = True
|
|
|
- elif al_id and adata: # data from tracking wallet
|
|
|
- self.al_id = al_id
|
|
|
- elif addrlist: # data from flat address list
|
|
|
- self.al_id = None
|
|
|
- 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
|
|
|
- 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')
|
|
|
- elif al_id or adata:
|
|
|
- die(3,'Must specify both al_id and adata')
|
|
|
- else:
|
|
|
- die(3,f'Incorrect arguments for {type(self).__name__}')
|
|
|
-
|
|
|
- # al_id,adata now set
|
|
|
- self.data = adata
|
|
|
- self.num_addrs = len(adata)
|
|
|
- self.fmt_data = ''
|
|
|
- self.chksum = None
|
|
|
-
|
|
|
- if self.al_id == None: return
|
|
|
-
|
|
|
- self.id_str = AddrListIDStr(self)
|
|
|
- if type(self) == KeyList: return
|
|
|
-
|
|
|
- if do_chksum and not skip_chksum:
|
|
|
- self.chksum = AddrListChksum(self)
|
|
|
- qmsg(
|
|
|
- f'Checksum for {self.desc} data {self.id_str.hl()}: {self.chksum.hl()}\n' +
|
|
|
- self.msgs[('check_chksum','record_chksum')[src=='gen']] )
|
|
|
-
|
|
|
- def update_msgs(self):
|
|
|
- self.msgs = AddrList.msgs
|
|
|
- self.msgs.update(type(self).msgs)
|
|
|
-
|
|
|
- def generate(self,seed,addrnums):
|
|
|
- assert type(addrnums) is AddrIdxList
|
|
|
-
|
|
|
- seed = self.scramble_seed(seed.data)
|
|
|
- dmsg_sc('seed',seed[:8].hex())
|
|
|
-
|
|
|
- compressed = self.al_id.mmtype.compressed
|
|
|
- pubkey_type = self.al_id.mmtype.pubkey_type
|
|
|
-
|
|
|
- gen_wallet_passwd = type(self) == KeyAddrList and 'wallet_passwd' in self.al_id.mmtype.extra_attrs
|
|
|
- gen_viewkey = type(self) == KeyAddrList and 'viewkey' in self.al_id.mmtype.extra_attrs
|
|
|
-
|
|
|
- if self.gen_addrs:
|
|
|
- kg = KeyGenerator(self.proto,self.al_id.mmtype)
|
|
|
- ag = AddrGenerator(self.proto,self.al_id.mmtype)
|
|
|
-
|
|
|
- t_addrs,num,pos,out = len(addrnums),0,0,AddrListData()
|
|
|
- le = self.entry_type
|
|
|
-
|
|
|
- while pos != t_addrs:
|
|
|
- seed = sha512(seed).digest()
|
|
|
- num += 1 # round
|
|
|
-
|
|
|
- if num != addrnums[pos]: continue
|
|
|
-
|
|
|
- pos += 1
|
|
|
-
|
|
|
- if not g.debug:
|
|
|
- qmsg_r(f'\rGenerating {self.gen_desc} #{num} ({pos} of {t_addrs})')
|
|
|
-
|
|
|
- e = le(proto=self.proto,idx=num)
|
|
|
-
|
|
|
- # Secret key is double sha256 of seed hash round /num/
|
|
|
- e.sec = PrivKey(
|
|
|
- self.proto,
|
|
|
- sha256(sha256(seed).digest()).digest(),
|
|
|
- compressed = compressed,
|
|
|
- pubkey_type = pubkey_type )
|
|
|
-
|
|
|
- if self.gen_addrs:
|
|
|
- pubhex = kg.to_pubhex(e.sec)
|
|
|
- e.addr = ag.to_addr(pubhex)
|
|
|
- if gen_viewkey:
|
|
|
- e.viewkey = ag.to_viewkey(pubhex)
|
|
|
- if gen_wallet_passwd:
|
|
|
- e.wallet_passwd = ag.to_wallet_passwd(e.sec)
|
|
|
-
|
|
|
- if type(self) == PasswordList:
|
|
|
- e.passwd = str(self.make_passwd(e.sec)) # TODO - own type
|
|
|
- dmsg(f'Key {pos:>03}: {e.passwd}')
|
|
|
-
|
|
|
- out.append(e)
|
|
|
- if g.debug_addrlist:
|
|
|
- Msg(f'generate():\n{e.pfmt()}')
|
|
|
-
|
|
|
- qmsg('\r{}: {} {}{} generated{}'.format(
|
|
|
- self.al_id.hl(),
|
|
|
- t_addrs,
|
|
|
- self.gen_desc,
|
|
|
- suf(t_addrs,self.gen_desc_pl),
|
|
|
- ' ' * 15 ))
|
|
|
-
|
|
|
- return out
|
|
|
-
|
|
|
- def check_format(self,addr):
|
|
|
- return True # format is checked when added to list entry object
|
|
|
-
|
|
|
- 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:
|
|
|
- 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
|
|
|
- from .crypto import scramble_seed
|
|
|
- if self.proto.testnet:
|
|
|
- scramble_key += ':' + self.proto.network
|
|
|
- dmsg_sc('str',scramble_key)
|
|
|
- return scramble_seed(seed,scramble_key.encode())
|
|
|
-
|
|
|
- def idxs(self):
|
|
|
- return [e.idx for e in self.data]
|
|
|
-
|
|
|
- def addrs(self):
|
|
|
- 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]
|
|
|
-
|
|
|
- def coinaddrs(self):
|
|
|
- return [e.addr for e in self.data]
|
|
|
-
|
|
|
- def comments(self):
|
|
|
- return [e.label for e in self.data]
|
|
|
-
|
|
|
- def entry(self,idx):
|
|
|
- for e in self.data:
|
|
|
- if idx == e.idx: return e
|
|
|
-
|
|
|
- def coinaddr(self,idx):
|
|
|
- for e in self.data:
|
|
|
- if idx == e.idx: return e.addr
|
|
|
-
|
|
|
- def comment(self,idx):
|
|
|
- for e in self.data:
|
|
|
- if idx == e.idx: return e.label
|
|
|
-
|
|
|
- def set_comment(self,idx,comment):
|
|
|
- for e in self.data:
|
|
|
- if idx == e.idx:
|
|
|
- e.label = comment
|
|
|
-
|
|
|
- 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.label )
|
|
|
- except ValueError:
|
|
|
- pass
|
|
|
- return d
|
|
|
-
|
|
|
- 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):
|
|
|
- at = self.proto.addr_type(t)
|
|
|
- kg = KeyGenerator(self.proto,at.pubkey_type)
|
|
|
- ag = AddrGenerator(self.proto,at)
|
|
|
- return ag.to_addr(kg.to_pubhex(pk))
|
|
|
-
|
|
|
- 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)
|
|
|
- for t in (compressed_types if pk.compressed else uncompressed_types):
|
|
|
- yield ( gen_addr(pk,t), pk )
|
|
|
-
|
|
|
- addrs4keys = dict(gen())
|
|
|
-
|
|
|
- for d in self.data:
|
|
|
- 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 get_file(self):
|
|
|
- import mmgen.addrfile as mod
|
|
|
- return getattr( mod, type(self).__name__.replace('List','File') )(self)
|
|
|
-
|
|
|
-class KeyAddrList(AddrList):
|
|
|
- desc = 'key-address'
|
|
|
- gen_desc = 'key/address pair'
|
|
|
- gen_desc_pl = 's'
|
|
|
- gen_addrs = True
|
|
|
- gen_keys = True
|
|
|
- has_keys = True
|
|
|
- chksum_rec_f = lambda foo,e: (str(e.idx), e.addr, e.sec.wif)
|
|
|
-
|
|
|
-class KeyList(AddrList):
|
|
|
- desc = 'key'
|
|
|
- gen_desc = 'key'
|
|
|
- gen_desc_pl = 's'
|
|
|
- gen_addrs = False
|
|
|
- gen_keys = True
|
|
|
- has_keys = True
|
|
|
- chksum_rec_f = lambda foo,e: (str(e.idx), e.addr, e.sec.wif)
|
|
|
-
|
|
|
def is_bip39_str(s):
|
|
|
from .bip39 import bip39
|
|
|
return bool(bip39.tohex(s.split(),wl_id='bip39'))
|
|
|
|
|
|
def is_xmrseed(s):
|
|
|
return bool(baseconv.tobytes(s.split(),wl_id='xmrseed'))
|
|
|
-
|
|
|
-from collections import namedtuple
|
|
|
-class PasswordList(AddrList):
|
|
|
- msgs = {
|
|
|
- 'record_chksum': """
|
|
|
-Record this checksum: it will be used to verify the password file in the future
|
|
|
-""".strip()
|
|
|
- }
|
|
|
- entry_type = PasswordListEntry
|
|
|
- main_attr = 'passwd'
|
|
|
- desc = 'password'
|
|
|
- gen_desc = 'password'
|
|
|
- gen_desc_pl = 's'
|
|
|
- gen_addrs = False
|
|
|
- gen_keys = False
|
|
|
- gen_passwds = True
|
|
|
- has_keys = False
|
|
|
- pw_len = None
|
|
|
- dfl_pw_fmt = 'b58'
|
|
|
- pwinfo = namedtuple('passwd_info',['min_len','max_len','dfl_len','valid_lens','desc','chk_func'])
|
|
|
- pw_info = {
|
|
|
- 'b32': pwinfo(10, 42 ,24, None, 'base32 password', is_b32_str), # 32**24 < 2**128
|
|
|
- 'b58': pwinfo(8, 36 ,20, None, 'base58 password', is_b58_str), # 58**20 < 2**128
|
|
|
- 'bip39': pwinfo(12, 24 ,24, [12,18,24],'BIP39 mnemonic', is_bip39_str),
|
|
|
- 'xmrseed': pwinfo(25, 25, 25, [25], 'Monero new-style mnemonic',is_xmrseed),
|
|
|
- 'hex': pwinfo(32, 64 ,64, [32,48,64],'hexadecimal password', is_hex_str),
|
|
|
- }
|
|
|
- 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
|
|
|
-
|
|
|
- def __init__(self,proto,
|
|
|
- infile = None,
|
|
|
- seed = None,
|
|
|
- pw_idxs = None,
|
|
|
- pw_id_str = None,
|
|
|
- pw_len = None,
|
|
|
- pw_fmt = None,
|
|
|
- chk_params_only = False
|
|
|
- ):
|
|
|
-
|
|
|
- self.proto = proto # proto is ignored
|
|
|
- self.update_msgs()
|
|
|
-
|
|
|
- if infile:
|
|
|
- self.infile = infile
|
|
|
- self.data = self.get_file().parse_file(infile) # sets self.pw_id_str,self.pw_fmt,self.pw_len
|
|
|
- else:
|
|
|
- if not chk_params_only:
|
|
|
- for k in (seed,pw_idxs):
|
|
|
- assert k
|
|
|
- self.pw_id_str = MMGenPWIDString(pw_id_str)
|
|
|
- self.set_pw_fmt(pw_fmt)
|
|
|
- self.set_pw_len(pw_len)
|
|
|
- if chk_params_only:
|
|
|
- return
|
|
|
- if self.hex2bip39:
|
|
|
- ymsg(self.feature_warn_fs.format(pw_fmt))
|
|
|
- self.set_pw_len_vs_seed_len(pw_len,seed)
|
|
|
- self.al_id = AddrListID(seed.sid,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)
|
|
|
- qmsg(
|
|
|
- f'Checksum for {self.desc} data {self.id_str.hl()}: {self.chksum.hl()}\n' +
|
|
|
- self.msgs[('record_chksum','check_chksum')[bool(infile)]] )
|
|
|
-
|
|
|
- def set_pw_fmt(self,pw_fmt):
|
|
|
- if pw_fmt == 'hex2bip39':
|
|
|
- self.hex2bip39 = True
|
|
|
- self.pw_fmt = 'bip39'
|
|
|
- self.pw_fmt_disp = 'hex2bip39'
|
|
|
- else:
|
|
|
- self.pw_fmt = pw_fmt
|
|
|
- self.pw_fmt_disp = pw_fmt
|
|
|
- if self.pw_fmt not in self.pw_info:
|
|
|
- raise InvalidPasswdFormat(
|
|
|
- '{!r}: invalid password format. Valid formats: {}'.format(
|
|
|
- self.pw_fmt,
|
|
|
- ', '.join(self.pw_info) ))
|
|
|
-
|
|
|
- def chk_pw_len(self,passwd=None):
|
|
|
- if passwd is None:
|
|
|
- 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:
|
|
|
- pw_len = len(passwd)
|
|
|
- fs = '{pw}: {b} has invalid length {l} ({c}{m} characters)'
|
|
|
- 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 ))
|
|
|
- elif pw_len > d.max_len:
|
|
|
- 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 ))
|
|
|
-
|
|
|
- def set_pw_len(self,pw_len):
|
|
|
- d = self.pw_info[self.pw_fmt]
|
|
|
-
|
|
|
- if pw_len is None:
|
|
|
- self.pw_len = d.dfl_len
|
|
|
- return
|
|
|
-
|
|
|
- if not is_int(pw_len):
|
|
|
- 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,pw_len,seed):
|
|
|
- pf = self.pw_fmt
|
|
|
- if pf == 'hex':
|
|
|
- pw_bytes = self.pw_len // 2
|
|
|
- good_pw_len = seed.byte_len * 2
|
|
|
- elif pf == 'bip39':
|
|
|
- from .bip39 import bip39
|
|
|
- pw_bytes = bip39.nwords2seedlen(self.pw_len,in_bytes=True)
|
|
|
- good_pw_len = bip39.seedlen2nwords(seed.byte_len,in_bytes=True)
|
|
|
- elif pf == 'xmrseed':
|
|
|
- pw_bytes = baseconv.seedlen_map_rev['xmrseed'][self.pw_len]
|
|
|
- try:
|
|
|
- good_pw_len = baseconv.seedlen_map['xmrseed'][seed.byte_len]
|
|
|
- except:
|
|
|
- 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
|
|
|
- good_pw_len = len(baseconv.frombytes(b'\xff'*seed.byte_len,wl_id=pf))
|
|
|
- else:
|
|
|
- raise NotImplementedError(f'{pf!r}: unknown password format')
|
|
|
-
|
|
|
- if pw_bytes > seed.byte_len:
|
|
|
- die(1,
|
|
|
- 'Cannot generate passwords with more entropy than underlying seed! ({} bits)\n'.format(
|
|
|
- len(seed.data) * 8 ) + (
|
|
|
- 'Re-run the command with --passwd-len={}' if pf in ('bip39','hex') else
|
|
|
- 'Re-run the command, specifying a password length of {} or less'
|
|
|
- ).format(good_pw_len) )
|
|
|
-
|
|
|
- if pf in ('bip39','hex') and pw_bytes < seed.byte_len:
|
|
|
- if not keypress_confirm(
|
|
|
- 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')
|
|
|
-
|
|
|
- def make_passwd(self,hex_sec):
|
|
|
- assert self.pw_fmt in self.pw_info
|
|
|
- if self.pw_fmt == 'hex':
|
|
|
- # take most significant part
|
|
|
- return hex_sec[:self.pw_len]
|
|
|
- elif self.pw_fmt == 'bip39':
|
|
|
- from .bip39 import bip39
|
|
|
- pw_len_hex = bip39.nwords2seedlen(self.pw_len,in_hex=True)
|
|
|
- # take most significant part
|
|
|
- return ' '.join(bip39.fromhex(hex_sec[:pw_len_hex],wl_id='bip39'))
|
|
|
- elif self.pw_fmt == 'xmrseed':
|
|
|
- pw_len_hex = baseconv.seedlen_map_rev['xmrseed'][self.pw_len] * 2
|
|
|
- # take most significant part
|
|
|
- bytes_trunc = bytes.fromhex(hex_sec[:pw_len_hex])
|
|
|
- bytes_preproc = init_proto('xmr').preprocess_key(bytes_trunc,None)
|
|
|
- return ' '.join(baseconv.frombytes(bytes_preproc,wl_id='xmrseed'))
|
|
|
- else:
|
|
|
- # take least significant part
|
|
|
- return baseconv.fromhex(hex_sec,self.pw_fmt,pad=self.pw_len,tostr=True)[-self.pw_len:]
|
|
|
-
|
|
|
- def check_format(self,pw):
|
|
|
- if not self.pw_info[self.pw_fmt].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)
|
|
|
- if pwlen != self.pw_len:
|
|
|
- raise ValueError(f'Password has incorrect length ({pwlen} != {self.pw_len})')
|
|
|
- return True
|
|
|
-
|
|
|
- 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:
|
|
|
- from .bip39 import bip39
|
|
|
- pwlen = bip39.nwords2seedlen(self.pw_len,in_hex=True)
|
|
|
- scramble_key = f'hex:{pwlen}:{self.pw_id_str}'
|
|
|
-
|
|
|
- from .crypto import scramble_seed
|
|
|
- dmsg_sc('str',scramble_key)
|
|
|
- return scramble_seed(seed,scramble_key.encode())
|