From 7b890a2c959a0df00783193dfd8bd15dccad2b55 Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Tue, 18 Jan 2022 09:10:58 +0000 Subject: [PATCH] baseconv.py: convert class methods to instance methods --- mmgen/addrgen.py | 6 +- mmgen/baseconv.py | 127 +++++++++++++------------------ mmgen/bip39.py | 40 +++++----- mmgen/mn_entry.py | 9 +-- mmgen/passwdlist.py | 9 +-- mmgen/protocol.py | 5 +- mmgen/tool.py | 30 ++++---- mmgen/txfile.py | 4 +- mmgen/wallet.py | 28 +++---- mmgen/xmrwallet.py | 2 +- test/unit_tests_d/ut_baseconv.py | 54 +++++++------ test/unit_tests_d/ut_bip39.py | 32 ++++---- test/unit_tests_d/ut_rpc.py | 2 +- 13 files changed, 165 insertions(+), 183 deletions(-) diff --git a/mmgen/addrgen.py b/mmgen/addrgen.py index f8406c39..0cf2be94 100755 --- a/mmgen/addrgen.py +++ b/mmgen/addrgen.py @@ -97,10 +97,10 @@ class addr_generator: def b58enc(self,addr_bytes): from .baseconv import baseconv - enc = baseconv.frombytes + enc = baseconv('b58').frombytes l = len(addr_bytes) - a = ''.join([enc( addr_bytes[i*8:i*8+8], 'b58', pad=11, tostr=True ) for i in range(l//8)]) - b = enc( addr_bytes[l-l%8:], 'b58', pad=7, tostr=True ) + a = ''.join([enc( addr_bytes[i*8:i*8+8], pad=11, tostr=True ) for i in range(l//8)]) + b = enc( addr_bytes[l-l%8:], pad=7, tostr=True ) return a + b @check_data diff --git a/mmgen/baseconv.py b/mmgen/baseconv.py index dd0785ee..f575ba09 100755 --- a/mmgen/baseconv.py +++ b/mmgen/baseconv.py @@ -31,7 +31,7 @@ def is_b32_str(s): return set(list(s)) <= set(baseconv.digits['b32']) def is_xmrseed(s): - return bool(baseconv.tobytes(s.split(),wl_id='xmrseed')) + return bool( baseconv('xmrseed').tobytes(s.split()) ) class baseconv(object): @@ -77,58 +77,49 @@ class baseconv(object): 'xmrseed': { 25:32 }, } - @classmethod - def init_mn(cls,mn_id): - if mn_id in cls.digits: - return - if mn_id == 'mmgen': + def __init__(self,wl_id): + + if wl_id == 'mmgen': from .mn_electrum import words - cls.digits[mn_id] = words - elif mn_id == 'xmrseed': + self.digits[wl_id] = words + elif wl_id == 'xmrseed': from .mn_monero import words - cls.digits[mn_id] = words - elif mn_id == 'tirosh': + self.digits[wl_id] = words + elif wl_id == 'tirosh': from .mn_tirosh import words - cls.digits[mn_id] = words[:cls.mn_base] - else: - raise ValueError(f'{mn_id}: unrecognized mnemonic ID') + self.digits[wl_id] = words[:self.mn_base] + elif wl_id not in self.digits: + raise ValueError(f'{wl_id}: unrecognized mnemonic ID') - @classmethod - def get_wordlist(cls,wl_id): - cls.init_mn(wl_id) - return cls.digits[wl_id] + self.wl_id = wl_id - @classmethod - def get_wordlist_chksum(cls,wl_id): - cls.init_mn(wl_id) - return sha256(' '.join(cls.digits[wl_id]).encode()).hexdigest()[:8] + def get_wordlist(self): + return self.digits[self.wl_id] - @classmethod - def check_wordlists(cls): - for k,v in list(cls.wl_chksums.items()): - res = cls.get_wordlist_chksum(k) - assert res == v,f'{res}: checksum mismatch for {k} (should be {v})' - return True + def get_wordlist_chksum(self): + return sha256(' '.join(self.digits[self.wl_id]).encode()).hexdigest()[:8] - @classmethod - def check_wordlist(cls,wl_id): - cls.init_mn(wl_id) + def check_wordlist(self): - wl = cls.digits[wl_id] + wl = self.digits[self.wl_id] from .util import qmsg,compare_chksums - ret = f'Wordlist: {wl_id}\nLength: {len(wl)} words' - new_chksum = cls.get_wordlist_chksum(wl_id) + ret = f'Wordlist: {self.wl_id}\nLength: {len(wl)} words' + new_chksum = self.get_wordlist_chksum() - a,b = 'generated','saved' - compare_chksums(new_chksum,a,cls.wl_chksums[wl_id],b,die_on_fail=True) + compare_chksums( + new_chksum, + 'generated', + self.wl_chksums[self.wl_id], + 'saved', + die_on_fail = True ) if tuple(sorted(wl)) == wl: return ret + '\nList is sorted' else: die(3,'ERROR: List is not sorted!') - @classmethod - def get_pad(cls,pad,seed_pad_func): + @staticmethod + 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. @@ -150,34 +141,29 @@ class baseconv(object): wstr = ''.join(word[:3] for word in words) return words[crc32(wstr.encode()) % len(words)] - @classmethod - def tohex(cls,words_arg,wl_id,pad=None): - "convert string or list data of base 'wl_id' to hex string" - return cls.tobytes(words_arg,wl_id,pad//2 if type(pad)==int else pad).hex() + def tohex(self,words_arg,pad=None): + "convert string or list data of instance base to hex string" + return self.tobytes(words_arg,pad//2 if type(pad)==int else pad).hex() - @classmethod - def tobytes(cls,words_arg,wl_id,pad=None): - "convert string or list data of base 'wl_id' to byte string" - - if wl_id not in cls.digits: - cls.init_mn(wl_id) + 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()) - desc = cls.desc[wl_id][0] + desc = self.desc[self.wl_id][0] if len(words) == 0: raise BaseConversionError(f'empty {desc} data') def get_seed_pad(): - assert wl_id in cls.seedlen_map_rev,f'seed padding not supported for base {wl_id!r}' - d = cls.seedlen_map_rev[wl_id] + assert self.wl_id in self.seedlen_map_rev, f'seed padding not supported for base {self.wl_id!r}' + d = self.seedlen_map_rev[self.wl_id] if not len(words) in d: raise BaseConversionError( f'{len(words)}: invalid length for seed-padded {desc} data in base conversion' ) return d[len(words)] - pad_val = max(cls.get_pad(pad,get_seed_pad),1) - wl = cls.digits[wl_id] + pad_val = max(self.get_pad(pad,get_seed_pad),1) + wl = self.digits[self.wl_id] base = len(wl) if not set(words) <= set(wl): @@ -185,11 +171,11 @@ class baseconv(object): ( 'seed data' if pad == 'seed' else f'{words_arg!r}:' ) + f' not in {desc} format' ) - if wl_id == 'xmrseed': - if len(words) not in cls.seedlen_map_rev['xmrseed']: + if self.wl_id == 'xmrseed': + if len(words) not in self.seedlen_map_rev['xmrseed']: die(2,f'{len(words)}: invalid length for Monero mnemonic') - z = cls.monero_mn_checksum(words[:-1]) + z = self.monero_mn_checksum(words[:-1]) assert z == words[-1],'invalid Monero mnemonic checksum' words = tuple(words[:-1]) @@ -204,9 +190,8 @@ class baseconv(object): bl = ret.bit_length() return ret.to_bytes(max(pad_val,bl//8+bool(bl%8)),'big') - @classmethod - def fromhex(cls,hexstr,wl_id,pad=None,tostr=False): - "convert hex string to list or string data of base 'wl_id'" + def fromhex(self,hexstr,pad=None,tostr=False): + "convert hex string to list or string data of instance base" from .util import is_hex_str if not is_hex_str(hexstr): @@ -214,32 +199,28 @@ class baseconv(object): ( 'seed data' if pad == 'seed' else f'{hexstr!r}:' ) + ' not a hexadecimal string' ) - return cls.frombytes(bytes.fromhex(hexstr),wl_id,pad,tostr) + return self.frombytes( bytes.fromhex(hexstr), pad, tostr ) - @classmethod - def frombytes(cls,bytestr,wl_id,pad=None,tostr=False): - "convert byte string to list or string data of base 'wl_id'" - - if wl_id not in cls.digits: - cls.init_mn(wl_id) + def frombytes(self,bytestr,pad=None,tostr=False): + "convert byte string to list or string data of instance base" if not bytestr: raise BaseConversionError('empty data not allowed in base conversion') def get_seed_pad(): - assert wl_id in cls.seedlen_map, f'seed padding not supported for base {wl_id!r}' - d = cls.seedlen_map[wl_id] + assert self.wl_id in self.seedlen_map, f'seed padding not supported for base {self.wl_id!r}' + d = self.seedlen_map[self.wl_id] if not len(bytestr) in d: raise SeedLengthError( f'{len(bytestr)}: invalid byte length for seed data in seed-padded base conversion' ) return d[len(bytestr)] - pad = max(cls.get_pad(pad,get_seed_pad),1) - wl = cls.digits[wl_id] + pad = max(self.get_pad(pad,get_seed_pad),1) + wl = self.digits[self.wl_id] base = len(wl) - if wl_id == 'xmrseed': - if len(bytestr) not in cls.seedlen_map['xmrseed']: + if self.wl_id == 'xmrseed': + if len(bytestr) not in self.seedlen_map['xmrseed']: die(2, f'{len(bytestr)}: invalid seed byte length for Monero mnemonic') def num2base_monero(num): @@ -251,7 +232,7 @@ class baseconv(object): o = [] for i in range(len(bytestr)//4): o += num2base_monero(int.from_bytes(bytestr[i*4:i*4+4][::-1],'big')) - o.append(cls.monero_mn_checksum(o)) + o.append(self.monero_mn_checksum(o)) else: num = int.from_bytes(bytestr,'big') ret = [] @@ -260,4 +241,4 @@ class baseconv(object): num //= base o = [wl[n] for n in [0] * (pad-len(ret)) + ret[::-1]] - return (' ' if wl_id in ('mmgen','xmrseed') else '').join(o) if tostr else o + return (' ' if self.wl_id in ('mmgen','xmrseed') else '').join(o) if tostr else o diff --git a/mmgen/bip39.py b/mmgen/bip39.py index 44656c82..c014690d 100755 --- a/mmgen/bip39.py +++ b/mmgen/bip39.py @@ -27,7 +27,7 @@ from .baseconv import baseconv from .util import is_hex_str def is_bip39_str(s): - return bool( bip39.tohex(s.split(), wl_id='bip39') ) + return bool( bip39().tohex(s.split()) ) # implements a subset of the baseconv API class bip39(baseconv): @@ -47,8 +47,12 @@ class bip39(baseconv): 224: bc(7, 21), 256: bc(8, 24), } - from .mn_bip39 import words - digits = { 'bip39': words } + + def __init__(self,wl_id='bip39'): + assert wl_id == 'bip39', "initialize with 'bip39' for compatibility with baseconv API" + from .mn_bip39 import words + self.digits = { 'bip39': words } + self.wl_id = 'bip39' @classmethod def nwords2seedlen(cls,nwords,in_bytes=False,in_hex=False): @@ -65,21 +69,17 @@ class bip39(baseconv): except: raise ValueError(f'{seed_bits!r}: invalid seed length for BIP39 mnemonic') - @classmethod - def tobytes(cls,*args,**kwargs): + def tobytes(self,*args,**kwargs): raise NotImplementedError('Method not supported') - @classmethod - def frombytes(cls,*args,**kwargs): + def frombytes(self,*args,**kwargs): raise NotImplementedError('Method not supported') - @classmethod - def tohex(cls,words,wl_id,pad=None): + def tohex(self,words,pad=None): assert isinstance(words,(list,tuple)),'words must be list or tuple' - assert wl_id == 'bip39',"'wl_id' must be 'bip39'" assert pad == None, f"{pad}: invalid 'pad' argument (must be None)" - wl = cls.digits[wl_id] + wl = self.digits['bip39'] for n,w in enumerate(words): if w not in wl: @@ -87,7 +87,7 @@ class bip39(baseconv): res = ''.join(['{:011b}'.format(wl.index(w)) for w in words]) - for k,v in cls.constants.items(): + for k,v in self.constants.items(): if len(words) == v.mn_len: bitlen = k break @@ -100,7 +100,7 @@ class bip39(baseconv): seed_hex = '{:0{w}x}'.format(int(seed_bin,2),w=bitlen//4) seed_bytes = bytes.fromhex(seed_hex) - chk_len = cls.constants[bitlen].chk_len + chk_len = self.constants[bitlen].chk_len chk_hex_chk = sha256(seed_bytes).hexdigest() chk_bin_chk = '{:0{w}b}'.format(int(chk_hex_chk,16),w=256)[:chk_len] @@ -109,19 +109,17 @@ class bip39(baseconv): return seed_hex - @classmethod - def fromhex(cls,seed_hex,wl_id,pad=None,tostr=False): + def fromhex(self,seed_hex,pad=None,tostr=False): assert is_hex_str(seed_hex),'seed data not a hexadecimal string' - assert wl_id == 'bip39',"'wl_id' must be 'bip39'" assert tostr == False,"'tostr' must be False for 'bip39'" assert pad == None, f"{pad}: invalid 'pad' argument (must be None)" - wl = cls.digits[wl_id] + wl = self.digits['bip39'] seed_bytes = bytes.fromhex(seed_hex) bitlen = len(seed_bytes) * 8 - assert bitlen in cls.constants, f'{bitlen}: invalid seed bit length' - c = cls.constants[bitlen] + assert bitlen in self.constants, f'{bitlen}: invalid seed bit length' + c = self.constants[bitlen] chk_hex = sha256(seed_bytes).hexdigest() @@ -131,7 +129,3 @@ class bip39(baseconv): res = seed_bin + chk_bin return tuple(wl[int(res[i*11:(i+1)*11],2)] for i in range(c.mn_len)) - - @classmethod - def init_mn(cls,mn_id): - assert mn_id == 'bip39', "'mn_id' must be 'bip39'" diff --git a/mmgen/mn_entry.py b/mmgen/mn_entry.py index 65f9fc93..536200ac 100755 --- a/mmgen/mn_entry.py +++ b/mmgen/mn_entry.py @@ -219,9 +219,8 @@ def mn_entry(wl_id,entry_mode=None): wl_id = 'mmgen' me = MnemonicEntry.get_cls_by_wordlist(wl_id) import importlib - me.conv_cls = getattr(importlib.import_module(f'mmgen.{me.modname}'),me.modname) - me.conv_cls.init_mn(wl_id) - me.wl = me.conv_cls.digits[wl_id] + me.bconv = getattr(importlib.import_module(f'mmgen.{me.modname}'),me.modname)(wl_id) + me.wl = me.bconv.digits[wl_id] obj = me() if entry_mode: obj.em = globals()['MnEntryMode'+capfirst(entry_mode)](obj) @@ -335,7 +334,7 @@ class MnemonicEntry(object): msg_r(erase) def get_mnemonic_from_user(self,mn_len,validate=True): - mll = list(self.conv_cls.seedlen_map_rev[self.wl_id]) + mll = list(self.bconv.seedlen_map_rev[self.wl_id]) assert mn_len in mll, f'{mn_len}: invalid mnemonic length (must be one of {mll})' if self.usr_dfl_entry_mode: @@ -375,7 +374,7 @@ class MnemonicEntry(object): words = [self.wl[i] for i in idxs] if validate: - self.conv_cls.tohex(words,self.wl_id) + self.bconv.tohex(words) if self.has_chksum: qmsg('Mnemonic is valid') else: diff --git a/mmgen/passwdlist.py b/mmgen/passwdlist.py index 5f525e67..3f5550c1 100755 --- a/mmgen/passwdlist.py +++ b/mmgen/passwdlist.py @@ -166,7 +166,7 @@ class PasswordList(AddrList): 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)) + good_pw_len = len( baseconv(pf).frombytes(b'\xff'*seed.byte_len) ) else: raise NotImplementedError(f'{pf!r}: unknown password format') @@ -194,19 +194,18 @@ class PasswordList(AddrList): from .bip39 import bip39 pw_len_bytes = bip39.nwords2seedlen( self.pw_len, in_bytes=True ) # take most significant part - return ' '.join( bip39.fromhex( secbytes[:pw_len_bytes].hex(), wl_id='bip39' ) ) + return ' '.join( bip39().fromhex(secbytes[:pw_len_bytes].hex()) ) elif self.pw_fmt == 'xmrseed': pw_len_bytes = baseconv.seedlen_map_rev['xmrseed'][self.pw_len] from .protocol import init_proto bytes_preproc = init_proto('xmr').preprocess_key( secbytes[:pw_len_bytes], # take most significant part None ) - return ' '.join( baseconv.frombytes( bytes_preproc, wl_id='xmrseed' ) ) + return ' '.join( baseconv('xmrseed').frombytes(bytes_preproc) ) else: # take least significant part - return baseconv.frombytes( + return baseconv(self.pw_fmt).frombytes( secbytes, - self.pw_fmt, pad = self.pw_len, tostr = True )[-self.pw_len:] diff --git a/mmgen/protocol.py b/mmgen/protocol.py index b694870e..2e4bbf5b 100755 --- a/mmgen/protocol.py +++ b/mmgen/protocol.py @@ -512,9 +512,10 @@ class CoinProtocol(MMGenObject): from .baseconv import baseconv,is_b58_str def b58dec(addr_str): + bc = baseconv('b58') l = len(addr_str) - a = b''.join([baseconv.tobytes(addr_str[i*11:i*11+11],'b58',pad=8) for i in range(l//11)]) - b = baseconv.tobytes(addr_str[-(l%11):],'b58',pad=5) + a = b''.join([bc.tobytes( addr_str[i*11:i*11+11], pad=8 ) for i in range(l//11)]) + b = bc.tobytes( addr_str[-(l%11):], pad=5 ) return a + b ret = b58dec(addr) diff --git a/mmgen/tool.py b/mmgen/tool.py index 7702f386..b605595f 100755 --- a/mmgen/tool.py +++ b/mmgen/tool.py @@ -390,24 +390,24 @@ class MMGenToolCmdUtil(MMGenToolCmds): def randb58(self,nbytes=32,pad=0): "generate random data (default: 32 bytes) and convert it to base 58" - return baseconv.frombytes(get_random(nbytes),'b58',pad=pad,tostr=True) + return baseconv('b58').frombytes(get_random(nbytes),pad=pad,tostr=True) def bytestob58(self,infile:str,pad=0): "convert bytes to base 58 (supply data via STDIN)" data = get_data_from_file(infile,dash=True,quiet=True,binary=True) - return baseconv.frombytes(data,'b58',pad=pad,tostr=True) + return baseconv('b58').frombytes(data,pad=pad,tostr=True) def b58tobytes(self,b58num:'sstr',pad=0): "convert a base 58 number to bytes (warning: outputs binary data)" - return baseconv.tobytes(b58num,'b58',pad=pad) + return baseconv('b58').tobytes(b58num,pad=pad) def hextob58(self,hexstr:'sstr',pad=0): "convert a hexadecimal number to base 58" - return baseconv.fromhex(hexstr,'b58',pad=pad,tostr=True) + return baseconv('b58').fromhex(hexstr,pad=pad,tostr=True) def b58tohex(self,b58num:'sstr',pad=0): "convert a base 58 number to hexadecimal" - return baseconv.tohex(b58num,'b58',pad=pad) + return baseconv('b58').tohex(b58num,pad=pad) def hextob58chk(self,hexstr:'sstr'): "convert a hexadecimal number to base58-check encoding" @@ -421,20 +421,20 @@ class MMGenToolCmdUtil(MMGenToolCmds): def hextob32(self,hexstr:'sstr',pad=0): "convert a hexadecimal number to MMGen's flavor of base 32" - return baseconv.fromhex(hexstr,'b32',pad,tostr=True) + return baseconv('b32').fromhex(hexstr,pad,tostr=True) def b32tohex(self,b32num:'sstr',pad=0): "convert an MMGen-flavor base 32 number to hexadecimal" - return baseconv.tohex(b32num.upper(),'b32',pad) + return baseconv('b32').tohex(b32num.upper(),pad) def hextob6d(self,hexstr:'sstr',pad=0,add_spaces=True): "convert a hexadecimal number to die roll base6 (base6d)" - ret = baseconv.fromhex(hexstr,'b6d',pad,tostr=True) + ret = baseconv('b6d').fromhex(hexstr,pad,tostr=True) return block_format(ret,gw=5,cols=None).strip() if add_spaces else ret def b6dtohex(self,b6d_num:'sstr',pad=0): "convert a die roll base6 (base6d) number to hexadecimal" - return baseconv.tohex(remove_whitespace(b6d_num),'b6d',pad) + return baseconv('b6d').tohex(remove_whitespace(b6d_num),pad) class MMGenToolCmdCoin(MMGenToolCmds): """ @@ -625,20 +625,20 @@ class MMGenToolCmdMnemonic(MMGenToolCmds): "convert a 16, 24 or 32-byte hexadecimal number to a mnemonic seed phrase" if fmt == 'bip39': from .bip39 import bip39 - return ' '.join(bip39.fromhex(hexstr,fmt)) + return ' '.join(bip39(fmt).fromhex(hexstr)) else: bytestr = bytes.fromhex(hexstr) if fmt == 'xmrseed': bytestr = self._xmr_reduce(bytestr) - return baseconv.frombytes(bytestr,fmt,'seed',tostr=True) + return baseconv(fmt).frombytes(bytestr,'seed',tostr=True) def mn2hex( self, seed_mnemonic:'sstr', fmt:mn_opts_disp = dfl_mnemonic_fmt ): "convert a mnemonic seed phrase to a hexadecimal number" if fmt == 'bip39': from .bip39 import bip39 - return bip39.tohex(seed_mnemonic.split(),fmt) + return bip39(fmt).tohex(seed_mnemonic.split()) else: - return baseconv.tohex(seed_mnemonic.split(),fmt,'seed') + return baseconv(fmt).tohex(seed_mnemonic.split(),'seed') def mn2hex_interactive( self, fmt:mn_opts_disp = dfl_mnemonic_fmt, mn_len=24, print_mn=False ): "convert an interactively supplied mnemonic seed phrase to a hexadecimal number" @@ -651,12 +651,12 @@ class MMGenToolCmdMnemonic(MMGenToolCmds): def mn_stats(self, fmt:mn_opts_disp = dfl_mnemonic_fmt ): "show stats for mnemonic wordlist" conv_cls = mnemonic_fmts[fmt]['conv_cls']() - return conv_cls.check_wordlist(fmt) + return conv_cls(fmt).check_wordlist() def mn_printlist( self, fmt:mn_opts_disp = dfl_mnemonic_fmt, enum=False, pager=False ): "print mnemonic wordlist" conv_cls = mnemonic_fmts[fmt]['conv_cls']() - ret = conv_cls.get_wordlist(fmt) + ret = conv_cls(fmt).get_wordlist() if enum: ret = [f'{n:>4} {e}' for n,e in enumerate(ret)] return '\n'.join(ret) diff --git a/mmgen/txfile.py b/mmgen/txfile.py index e031c3cb..4fea57d9 100755 --- a/mmgen/txfile.py +++ b/mmgen/txfile.py @@ -81,7 +81,7 @@ class MMGenTxFile: if c != '-': desc = 'encoded comment (not base58)' from .baseconv import baseconv - comment = baseconv.tobytes(c,'b58').decode() + comment = baseconv('b58').tobytes(c).decode() assert comment != False,'invalid comment' desc = 'comment' tx.label = MMGenTxLabel(comment) @@ -177,7 +177,7 @@ class MMGenTxFile: if tx.label: from .baseconv import baseconv - lines.append(baseconv.frombytes(tx.label.encode(),'b58',tostr=True)) + lines.append(baseconv('b58').frombytes(tx.label.encode(),tostr=True)) if tx.coin_txid: if not tx.label: diff --git a/mmgen/wallet.py b/mmgen/wallet.py index 45b3a00e..b11004e9 100755 --- a/mmgen/wallet.py +++ b/mmgen/wallet.py @@ -425,8 +425,9 @@ class Mnemonic(WalletUnenc): hexseed = self.seed.hexdata - mn = self.conv_cls.fromhex(hexseed,self.wl_id,self._hex2mn_pad(hexseed)) - ret = self.conv_cls.tohex(mn,self.wl_id,self._mn2hex_pad(mn)) + bc = self.conv_cls(self.wl_id) + mn = bc.fromhex( hexseed, self._hex2mn_pad(hexseed) ) + ret = bc.tohex( mn, self._mn2hex_pad(mn) ) # Internal error, so just die on fail compare_or_die(ret,'recomputed seed',hexseed,'original',e='Internal error') @@ -436,7 +437,7 @@ class Mnemonic(WalletUnenc): def _deformat(self): - self.conv_cls.init_mn(self.wl_id) + bc = self.conv_cls(self.wl_id) mn = self.fmt_data.split() if len(mn) not in self.mn_lens: @@ -446,12 +447,12 @@ class Mnemonic(WalletUnenc): return False for n,w in enumerate(mn,1): - if w not in self.conv_cls.digits[self.wl_id]: + if w not in bc.digits[self.wl_id]: msg(f'Invalid mnemonic: word #{n} is not in the {self.wl_id.upper()} wordlist') return False - hexseed = self.conv_cls.tohex(mn,self.wl_id,self._mn2hex_pad(mn)) - ret = self.conv_cls.fromhex(hexseed,self.wl_id,self._hex2mn_pad(hexseed)) + hexseed = bc.tohex( mn, self._mn2hex_pad(mn) ) + ret = bc.fromhex( hexseed, self._hex2mn_pad(hexseed) ) if len(hexseed) * 4 not in g.seed_lens: msg('Invalid mnemonic (produces too large a number)') @@ -502,7 +503,7 @@ class MMGenSeedFile(WalletUnenc): ext = 'mmseed' def _format(self): - b58seed = baseconv.frombytes(self.seed.data,'b58',pad='seed',tostr=True) + b58seed = baseconv('b58').frombytes(self.seed.data,pad='seed',tostr=True) self.ssdata.chksum = make_chksum_6(b58seed) self.ssdata.b58seed = b58seed self.fmt_data = '{} {}\n'.format( @@ -532,7 +533,7 @@ class MMGenSeedFile(WalletUnenc): if not compare_chksums(a,'file',make_chksum_6(b),'computed',verbose=True): return False - ret = baseconv.tobytes(b,'b58',pad='seed') + ret = baseconv('b58').tobytes(b,pad='seed') if ret == False: msg(f'Invalid base-58 encoded seed: {val}') @@ -562,7 +563,7 @@ class DieRollSeedFile(WalletUnenc): interactive_input = False def _format(self): - d = baseconv.frombytes(self.seed.data,'b6d',pad='seed',tostr=True) + '\n' + d = baseconv('b6d').frombytes(self.seed.data,pad='seed',tostr=True) + '\n' self.fmt_data = block_format(d,gw=5,cols=5) def _deformat(self): @@ -578,7 +579,7 @@ class DieRollSeedFile(WalletUnenc): # truncate seed to correct length, discarding high bits seed_len = rmap[len(d)] - seed_bytes = baseconv.tobytes(d,'b6d',pad='seed')[-seed_len:] + seed_bytes = baseconv('b6d').tobytes(d,pad='seed')[-seed_len:] if self.interactive_input and opt.usr_randchars: if keypress_confirm(self.user_entropy_prompt): @@ -781,8 +782,9 @@ class MMGenWallet(WalletEnc): def _format(self): d = self.ssdata s = self.seed - slt_fmt = baseconv.frombytes(d.salt,'b58',pad='seed',tostr=True) - es_fmt = baseconv.frombytes(d.enc_seed,'b58',pad='seed',tostr=True) + bc = baseconv('b58') + slt_fmt = bc.frombytes(d.salt,pad='seed',tostr=True) + es_fmt = bc.frombytes(d.enc_seed,pad='seed',tostr=True) lines = ( d.label, '{} {} {} {} {}'.format( s.sid.lower(), d.key_id.lower(), s.bitlen, d.pw_status, d.timestamp ), @@ -852,7 +854,7 @@ class MMGenWallet(WalletEnc): make_chksum_6(b58_val),'computed checksum',verbose=True): return False - val = baseconv.tobytes(b58_val,'b58',pad='seed') + val = baseconv('b58').tobytes(b58_val,pad='seed') if val == False: msg(f'Invalid base 58 number: {b58_val}') return False diff --git a/mmgen/xmrwallet.py b/mmgen/xmrwallet.py index 2ad15ff0..d6d5f2e3 100755 --- a/mmgen/xmrwallet.py +++ b/mmgen/xmrwallet.py @@ -584,7 +584,7 @@ class MoneroWalletOps: 'restore_deterministic_wallet', filename = os.path.basename(fn), password = d.wallet_passwd, - seed = baseconv.fromhex(d.sec.wif,'xmrseed',tostr=True), + seed = baseconv('xmrseed').fromhex(d.sec.wif,tostr=True), restore_height = uopt.restore_height, language = 'English' ) diff --git a/test/unit_tests_d/ut_baseconv.py b/test/unit_tests_d/ut_baseconv.py index 5e5f46e7..0937b84f 100755 --- a/test/unit_tests_d/ut_baseconv.py +++ b/test/unit_tests_d/ut_baseconv.py @@ -162,7 +162,7 @@ class unit_test(object): vmsg(f'\nBase: {base}') vmsg(fs.format(h='Input',p='Pad',r='Output')) for (hexstr,pad),ret_chk in data: - ret = baseconv.fromhex(hexstr,wl_id=base,pad=pad,tostr=True) + ret = baseconv(base).fromhex(hexstr,pad=pad,tostr=True) if pad != 'seed': assert len(ret) >= (pad or 0), perr.format(ret,pad or 0) assert ret == ret_chk, rerr.format(ret,ret_chk) @@ -180,9 +180,8 @@ class unit_test(object): for (hexstr,pad),ret_chk in data: if type(pad) == int: pad = len(hexstr) - ret = baseconv.tohex( + ret = baseconv(base).tohex( ret_chk.split() if base == 'xmrseed' else ret_chk, - wl_id=base, pad=pad) if pad == None: assert int(ret,16) == int(hexstr,16), rerr.format(int(ret,16),int(hexstr,16)) @@ -191,34 +190,43 @@ class unit_test(object): vmsg(fs.format(h=ret_chk,r=ret,p=str(pad))) # msg("(('{h}',{p}),'{r}'),".format(h=hexstr,r=ret_chk,c=ret_chk,p=pad)) + qmsg_r('\nChecking wordlist checksums:') + vmsg('') + + for wl_id in baseconv.wl_chksums: + vmsg_r(f' {wl_id+":":9}') + baseconv(wl_id).check_wordlist() + qmsg('') - vmsg('') qmsg('Checking error handling:') bad_b58 = 'I'*22 bad_b58len = 'a'*23 - th = baseconv.tohex - fh = baseconv.fromhex + fr58 = baseconv('b58').fromhex + to58 = baseconv('b58').tohex + to32 = baseconv('b32').tohex + to8 = baseconv('b8').tohex + bad_data = ( -('hexstr', 'HexadecimalStringError', ': not a hexadecimal str', lambda:fh('x','b58')), -('hexstr (seed)', 'HexadecimalStringError', 'seed data not a hexadec', lambda:fh('x','b58',pad='seed')), -('hexstr (empty)', 'BaseConversionError', 'empty data not allowed', lambda:fh('','b58')), -('b58 data', 'BaseConversionError', ': not in base58', lambda:th('IfFzZ','b58')), -('b58 data (seed)', 'BaseConversionError', 'seed data not in base58', lambda:th(bad_b58,'b58',pad='seed')), -('b58 len (seed)', 'BaseConversionError', 'invalid length for', lambda:th(bad_b58len,'b58',pad='seed')), -('b58 data (empty)','BaseConversionError', 'empty base58 data', lambda:th('','b58')), -('b8 data (empty)' ,'BaseConversionError', 'empty base8 string data', lambda:th('','b8')), -('b32 data', 'BaseConversionError', 'not in MMGen base32', lambda:th('1az','b32')), -('pad arg (in)', 'BaseConversionPadError', "illegal value for 'pad'", lambda:fh('ff','b58',pad='foo')), -('pad arg (in)', 'BaseConversionPadError', "illegal value for 'pad'", lambda:fh('ff','b58',pad=False)), -('pad arg (in)', 'BaseConversionPadError', "illegal value for 'pad'", lambda:fh('ff','b58',pad=True)), -('seedlen (in)', 'SeedLengthError', 'invalid byte length', lambda:fh('ff','b58',pad='seed')), -('pad arg (out)', 'BaseConversionPadError', "illegal value for 'pad'", lambda:th('Z','b58',pad='foo')), -('pad arg (out)', 'BaseConversionPadError', "illegal value for 'pad'", lambda:th('Z','b58',pad=False)), -('pad arg (out)', 'BaseConversionPadError', "illegal value for 'pad'", lambda:th('Z','b58',pad=True)), -('seedlen (out)', 'BaseConversionError', 'invalid length for seed', lambda:th('Z','b58',pad='seed')), +('hexstr', 'HexadecimalStringError', ': not a hexadecimal str', lambda:fr58('x')), +('hexstr (seed)', 'HexadecimalStringError', 'seed data not a hexadec', lambda:fr58('x',pad='seed')), +('hexstr (empty)', 'BaseConversionError', 'empty data not allowed', lambda:fr58('')), +('b58 data', 'BaseConversionError', ': not in base58', lambda:to58('IfFzZ')), +('b58 data (seed)', 'BaseConversionError', 'seed data not in base58', lambda:to58(bad_b58,pad='seed')), +('b58 len (seed)', 'BaseConversionError', 'invalid length for', lambda:to58(bad_b58len,pad='seed')), +('b58 data (empty)','BaseConversionError', 'empty base58 data', lambda:to58('')), +('b8 data (empty)' ,'BaseConversionError', 'empty base8 string data', lambda:to8('')), +('b32 data', 'BaseConversionError', 'not in MMGen base32', lambda:to32('1az')), +('pad arg (in)', 'BaseConversionPadError', "illegal value for 'pad'", lambda:fr58('ff',pad='foo')), +('pad arg (in)', 'BaseConversionPadError', "illegal value for 'pad'", lambda:fr58('ff',pad=False)), +('pad arg (in)', 'BaseConversionPadError', "illegal value for 'pad'", lambda:fr58('ff',pad=True)), +('seedlen (in)', 'SeedLengthError', 'invalid byte length', lambda:fr58('ff',pad='seed')), +('pad arg (out)', 'BaseConversionPadError', "illegal value for 'pad'", lambda:to58('Z',pad='foo')), +('pad arg (out)', 'BaseConversionPadError', "illegal value for 'pad'", lambda:to58('Z',pad=False)), +('pad arg (out)', 'BaseConversionPadError', "illegal value for 'pad'", lambda:to58('Z',pad=True)), +('seedlen (out)', 'BaseConversionError', 'invalid length for seed', lambda:to58('Z',pad='seed')), ) ut.process_bad_data(bad_data) diff --git a/test/unit_tests_d/ut_bip39.py b/test/unit_tests_d/ut_bip39.py index aa9b019d..9342092b 100755 --- a/test/unit_tests_d/ut_bip39.py +++ b/test/unit_tests_d/ut_bip39.py @@ -90,15 +90,15 @@ class unit_test(object): from mmgen.bip39 import bip39 - bip39.check_wordlists() - bip39.check_wordlist('bip39') + b = bip39() + b.check_wordlist() vmsg('') qmsg('Checking seed to mnemonic conversion:') for v in self.vectors: chk = tuple(v[1].split()) vmsg(' '+v[1]) - res = bip39.fromhex(v[0],'bip39') + res = b.fromhex( v[0] ) assert res == chk, f'mismatch:\nres: {res}\nchk: {chk}' vmsg('') @@ -106,7 +106,7 @@ class unit_test(object): for v in self.vectors: chk = v[0] vmsg(' '+chk) - res = bip39.tohex(v[1].split(),'bip39') + res = b.tohex( v[1].split() ) assert res == chk, f'mismatch:\nres: {res}\nchk: {chk}' vmsg('') @@ -119,20 +119,18 @@ class unit_test(object): bad_seed = 'deadbeef' good_seed = 'deadbeef' * 4 - th = bip39.tohex - fh = bip39.fromhex + th = b.tohex + fh = b.fromhex bad_data = ( -('hex', 'AssertionError', 'not a hexadecimal',lambda:fh('xx','bip39')), -('id (tohex)', 'AssertionError', "must be 'bip39'", lambda:fh(good_seed,'foo')), -('seed len', 'AssertionError', 'invalid seed bit', lambda:fh(bad_seed,'bip39')), -('mnemonic type', 'AssertionError', 'must be list', lambda:th('string','bip39')), -('id (fromhex)', 'AssertionError', "must be 'bip39'", lambda:th(good_mn,'foo')), -('arg (tostr=True)', 'AssertionError', "'tostr' must be", lambda:fh(good_seed,'bip39',tostr=True)), -('pad len (fromhex)','AssertionError', "invalid 'pad' arg",lambda:fh(good_seed,'bip39',pad=23)), -('pad len (tohex)', 'AssertionError', "invalid 'pad' arg",lambda:th(good_mn,'bip39',pad=23)), -('word', 'MnemonicError', "not in the BIP39", lambda:th(bad_word_mn,'bip39')), -('checksum', 'MnemonicError', "checksum", lambda:th(bad_chksum_mn,'bip39')), -('seed phrase len', 'MnemonicError', "phrase len", lambda:th(bad_len_mn,'bip39')), +('hex', 'AssertionError', 'not a hexadecimal',lambda:fh('xx')), +('seed len', 'AssertionError', 'invalid seed bit', lambda:fh(bad_seed)), +('mnemonic type', 'AssertionError', 'must be list', lambda:th('string')), +('arg (tostr=True)', 'AssertionError', "'tostr' must be", lambda:fh(good_seed,tostr=True)), +('pad len (fromhex)','AssertionError', "invalid 'pad' arg",lambda:fh(good_seed,pad=23)), +('pad len (tohex)', 'AssertionError', "invalid 'pad' arg",lambda:th(good_mn,pad=23)), +('word', 'MnemonicError', "not in the BIP39", lambda:th(bad_word_mn)), +('checksum', 'MnemonicError', "checksum", lambda:th(bad_chksum_mn)), +('seed phrase len', 'MnemonicError', "phrase len", lambda:th(bad_len_mn)), ) ut.process_bad_data(bad_data) diff --git a/test/unit_tests_d/ut_rpc.py b/test/unit_tests_d/ut_rpc.py index 3768dc92..1e8ec9ba 100755 --- a/test/unit_tests_d/ut_rpc.py +++ b/test/unit_tests_d/ut_rpc.py @@ -139,7 +139,7 @@ class unit_tests: 'restore_deterministic_wallet', filename = fn, password = 'foo', - seed = baseconv.fromhex('beadface'*8,'xmrseed',tostr=True) ) + seed = baseconv('xmrseed').fromhex('beadface'*8,tostr=True) ) qmsg(f'Opening {wd.network} wallet') await c.call( 'open_wallet', filename=fn, password='foo' )