Browse Source

baseconv.py: convert class methods to instance methods

The MMGen Project 3 years ago
parent
commit
7b890a2c95

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

+ 59 - 78
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')
-
-	@classmethod
-	def get_wordlist(cls,wl_id):
-		cls.init_mn(wl_id)
-		return cls.digits[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]
-
-	@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
-
-	@classmethod
-	def check_wordlist(cls,wl_id):
-		cls.init_mn(wl_id)
-
-		wl = cls.digits[wl_id]
+			self.digits[wl_id] = words[:self.mn_base]
+		elif wl_id not in self.digits:
+			raise ValueError(f'{wl_id}: unrecognized mnemonic ID')
+
+		self.wl_id = wl_id
+
+	def get_wordlist(self):
+		return self.digits[self.wl_id]
+
+	def get_wordlist_chksum(self):
+		return sha256(' '.join(self.digits[self.wl_id]).encode()).hexdigest()[:8]
+
+	def check_wordlist(self):
+
+		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)
-
-	@classmethod
-	def frombytes(cls,bytestr,wl_id,pad=None,tostr=False):
-		"convert byte string to list or string data of base 'wl_id'"
+		return self.frombytes( bytes.fromhex(hexstr), pad, tostr )
 
-		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

+ 17 - 23
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'"

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

+ 4 - 5
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:]
 

+ 3 - 2
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)

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

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

+ 15 - 13
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

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

+ 31 - 23
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)

+ 15 - 17
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)

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