Browse Source

baseconv: set constants in initializer

The MMGen Project 3 years ago
parent
commit
67067aa7d5
8 changed files with 77 additions and 74 deletions
  1. 40 38
      mmgen/baseconv.py
  2. 7 7
      mmgen/bip39.py
  3. 1 1
      mmgen/data/version
  4. 2 2
      mmgen/mn_entry.py
  5. 3 3
      mmgen/passwdlist.py
  6. 12 11
      mmgen/wallet.py
  7. 11 11
      mmgen/xmrseed.py
  8. 1 1
      test/unit_tests_d/ut_baseconv.py

+ 40 - 38
mmgen/baseconv.py

@@ -23,82 +23,84 @@ baseconv.py:  base conversion class for the MMGen suite
 from hashlib import sha256
 from .exception import *
 from .util import die
+from collections import namedtuple
 
 def is_b58_str(s):
-	return set(list(s)) <= set(baseconv.digits['b58'])
+	return set(list(s)) <= set(baseconv('b58').digits)
 
 def is_b32_str(s):
-	return set(list(s)) <= set(baseconv.digits['b32'])
+	return set(list(s)) <= set(baseconv('b32').digits)
 
 class baseconv(object):
-
-	desc = {
-		'b58':   ('base58',            'base58-encoded data'),
-		'b32':   ('MMGen base32',      'MMGen base32-encoded data created using simple base conversion'),
-		'b16':   ('hexadecimal string','base16 (hexadecimal) string data'),
-		'b10':   ('base10 string',     'base10 (decimal) string data'),
-		'b8':    ('base8 string',      'base8 (octal) string data'),
-		'b6d':   ('base6d (die roll)', 'base6 data using the digits from one to six'),
+	mn_base = 1626
+	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
-		'mmgen': ('MMGen native mnemonic',
+		'mmgen': dt('MMGen native mnemonic',
 		'MMGen native mnemonic seed phrase created using old Electrum wordlist and simple base conversion'),
-	}
+	},
 	# https://en.wikipedia.org/wiki/Base32#RFC_4648_Base32_alphabet
 	# https://tools.ietf.org/html/rfc4648
-	digits = {
+	'digits': {
 		'b58': tuple('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'),
 		'b32': tuple('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'), # RFC 4648 alphabet
 		'b16': tuple('0123456789abcdef'),
 		'b10': tuple('0123456789'),
 		'b8':  tuple('01234567'),
 		'b6d': tuple('123456'),
-	}
-	mn_base = 1626
-	wl_chksums = {
+	},
+	'wl_chksum': {
 		'mmgen':  '5ca31424',
 #		'tirosh': '48f05e1f', # tirosh truncated to mn_base
 #		'tirosh1633': '1a5faeff' # tirosh list is 1633 words long!
-	}
-	seedlen_map = {
+	},
+	'seedlen_map': {
 		'b58': { 16:22, 24:33, 32:44 },
 		'b6d': { 16:50, 24:75, 32:100 },
 		'mmgen': { 16:12, 24:18, 32:24 },
-	}
-	seedlen_map_rev = {
+	},
+	'seedlen_map_rev': {
 		'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):
 
 		if wl_id == 'mmgen':
 			from .mn_electrum import words
-			self.digits[wl_id] = words
-		elif wl_id not in self.digits:
+			self.constants['digits'][wl_id] = words
+		elif wl_id not in self.constants['digits']:
 			raise ValueError(f'{wl_id}: unrecognized mnemonic ID')
 
+		for k,v in self.constants.items():
+			if wl_id in v:
+				setattr(self,k,v[wl_id])
+
 		self.wl_id = wl_id
 
 	def get_wordlist(self):
-		return self.digits[self.wl_id]
+		return self.digits
 
 	def get_wordlist_chksum(self):
-		return sha256(' '.join(self.digits[self.wl_id]).encode()).hexdigest()[:8]
+		return sha256( ' '.join(self.digits).encode() ).hexdigest()[:8]
 
 	def check_wordlist(self):
 
-		wl = self.digits[self.wl_id]
+		wl = self.digits
 		from .util import qmsg,compare_chksums
 		ret = f'Wordlist: {self.wl_id}\nLength: {len(wl)} words'
 		new_chksum = self.get_wordlist_chksum()
 
-		compare_chksums(
-			new_chksum,
-			'generated',
-			self.wl_chksums[self.wl_id],
-			'saved',
-			die_on_fail = True )
+		compare_chksums( new_chksum, 'generated', self.wl_chksum, 'saved', die_on_fail=True )
 
 		if tuple(sorted(wl)) == wl:
 			return ret + '\nList is sorted'
@@ -130,21 +132,21 @@ class baseconv(object):
 		"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 = self.desc[self.wl_id][0]
+		desc = self.desc.short
 
 		if len(words) == 0:
 			raise BaseConversionError(f'empty {desc} data')
 
 		def get_seed_pad():
-			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]
+			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:
 				raise 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)
-		wl = self.digits[self.wl_id]
+		wl = self.digits
 		base = len(wl)
 
 		if not set(words) <= set(wl):
@@ -174,15 +176,15 @@ class baseconv(object):
 			raise BaseConversionError('empty data not allowed in base conversion')
 
 		def get_seed_pad():
-			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]
+			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:
 				raise 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)
-		wl = self.digits[self.wl_id]
+		wl = self.digits
 
 		def gen():
 			num = int.from_bytes(bytestr,'big')

+ 7 - 7
mmgen/bip39.py

@@ -32,10 +32,10 @@ def is_bip39_str(s):
 # implements a subset of the baseconv API
 class bip39(baseconv):
 
-	desc            = { 'bip39': ('BIP39 mnemonic', 'BIP39 mnemonic seed phrase') }
-	wl_chksums      = { 'bip39': 'f18b9a84' }
-	seedlen_map     = { 'bip39': { 16:12, 24:18, 32:24 } }
-	seedlen_map_rev = { 'bip39': { 12:16, 18:24, 24:32 } }
+	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 }
 
 	from collections import namedtuple
 	bc = namedtuple('bip39_constants',['chk_len','mn_len'])
@@ -51,7 +51,7 @@ class bip39(baseconv):
 	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.digits = words
 		self.wl_id = 'bip39'
 
 	@classmethod
@@ -79,7 +79,7 @@ class bip39(baseconv):
 		assert isinstance(words,(list,tuple)),'words must be list or tuple'
 		assert pad in (None,'seed'), f"{pad}: invalid 'pad' argument (must be None or 'seed')"
 
-		wl = self.digits['bip39']
+		wl = self.digits
 
 		for n,w in enumerate(words):
 			if w not in wl:
@@ -114,7 +114,7 @@ class bip39(baseconv):
 		assert tostr == 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['bip39']
+		wl = self.digits
 		seed_bytes = bytes.fromhex(seed_hex)
 		bitlen = len(seed_bytes) * 8
 

+ 1 - 1
mmgen/data/version

@@ -1 +1 @@
-13.1.dev003
+13.1.dev004

+ 2 - 2
mmgen/mn_entry.py

@@ -220,7 +220,7 @@ def mn_entry(wl_id,entry_mode=None):
 	me = MnemonicEntry.get_cls_by_wordlist(wl_id)
 	import importlib
 	me.bconv = getattr(importlib.import_module(f'mmgen.{me.modname}'),me.modname)(wl_id)
-	me.wl = me.bconv.digits[wl_id]
+	me.wl = me.bconv.digits
 	obj = me()
 	if entry_mode:
 		obj.em = globals()['MnEntryMode'+capfirst(entry_mode)](obj)
@@ -334,7 +334,7 @@ class MnemonicEntry(object):
 				msg_r(erase)
 
 	def get_mnemonic_from_user(self,mn_len,validate=True):
-		mll = list(self.bconv.seedlen_map_rev[self.wl_id])
+		mll = list(self.bconv.seedlen_map_rev)
 		assert mn_len in mll, f'{mn_len}: invalid mnemonic length (must be one of {mll})'
 
 		if self.usr_dfl_entry_mode:

+ 3 - 3
mmgen/passwdlist.py

@@ -160,9 +160,9 @@ class PasswordList(AddrList):
 			good_pw_len = bip39.seedlen2nwords(seed.byte_len,in_bytes=True)
 		elif pf == 'xmrseed':
 			from .xmrseed import xmrseed
-			pw_bytes = xmrseed.seedlen_map_rev['xmrseed'][self.pw_len]
+			pw_bytes = xmrseed().seedlen_map_rev[self.pw_len]
 			try:
-				good_pw_len = xmrseed.seedlen_map['xmrseed'][seed.byte_len]
+				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'):
@@ -199,7 +199,7 @@ class PasswordList(AddrList):
 			return ' '.join( bip39().fromhex(secbytes[:pw_len_bytes].hex()) )
 		elif self.pw_fmt == 'xmrseed':
 			from .xmrseed import xmrseed
-			pw_len_bytes = xmrseed.seedlen_map_rev['xmrseed'][self.pw_len]
+			pw_len_bytes = xmrseed().seedlen_map_rev[self.pw_len]
 			from .protocol import init_proto
 			bytes_preproc = init_proto('xmr').preprocess_key(
 				secbytes[:pw_len_bytes], # take most significant part

+ 12 - 11
mmgen/wallet.py

@@ -402,7 +402,7 @@ class Mnemonic(WalletUnenc):
 
 	@property
 	def mn_lens(self):
-		return sorted(self.conv_cls.seedlen_map_rev[self.wl_id])
+		return sorted(self.conv_cls(self.wl_id).seedlen_map_rev)
 
 	def _get_data_from_user(self,desc):
 
@@ -439,7 +439,7 @@ class Mnemonic(WalletUnenc):
 			return False
 
 		for n,w in enumerate(mn,1):
-			if w not in bc.digits[self.wl_id]:
+			if w not in bc.digits:
 				msg(f'Invalid mnemonic: word #{n} is not in the {self.wl_id.upper()} wordlist')
 				return False
 
@@ -555,8 +555,9 @@ class DieRollSeedFile(WalletUnenc):
 	def _deformat(self):
 
 		d = remove_whitespace(self.fmt_data)
+		bc = baseconv('b6d')
+		rmap = bc.seedlen_map_rev
 
-		rmap = self.conv_cls.seedlen_map_rev['b6d']
 		if not len(d) in rmap:
 			raise SeedLengthError('{!r}: invalid length for {} (must be one of {})'.format(
 				len(d),
@@ -565,7 +566,7 @@ class DieRollSeedFile(WalletUnenc):
 
 		# truncate seed to correct length, discarding high bits
 		seed_len = rmap[len(d)]
-		seed_bytes = baseconv('b6d').tobytes(d,pad='seed')[-seed_len:]
+		seed_bytes = bc.tobytes( d, pad='seed' )[-seed_len:]
 
 		if self.interactive_input and opt.usr_randchars:
 			if keypress_confirm(self.user_entropy_prompt):
@@ -585,9 +586,11 @@ class DieRollSeedFile(WalletUnenc):
 		if not g.stdin_tty:
 			return get_data_from_user(desc)
 
-		seed_bitlens = [n*8 for n in sorted(self.conv_cls.seedlen_map['b6d'])]
-		seed_bitlen = self._choose_seedlen(self.wclass,seed_bitlens,self.mn_type)
-		nDierolls = self.conv_cls.seedlen_map['b6d'][seed_bitlen // 8]
+		bc = baseconv('b6d')
+
+		seed_bitlens = [ n*8 for n in sorted(bc.seedlen_map) ]
+		seed_bitlen = self._choose_seedlen( self.wclass, seed_bitlens, self.mn_type )
+		nDierolls = bc.seedlen_map[seed_bitlen // 8]
 
 		m = """
 			For a {sb}-bit seed you must roll the die {nd} times.  After each die roll,
@@ -596,8 +599,6 @@ class DieRollSeedFile(WalletUnenc):
 		"""
 		msg('\n'+fmt(m.strip()).format(sb=seed_bitlen,nd=nDierolls)+'\n')
 
-		b6d_digits = self.conv_cls.digits['b6d']
-
 		cr = '\n' if g.test_suite else '\r'
 		prompt_fs = f'\b\b\b   {cr}Enter die roll #{{}}: {CUR_SHOW}'
 		clear_line = '' if g.test_suite else '\r' + ' ' * 25
@@ -609,7 +610,7 @@ class DieRollSeedFile(WalletUnenc):
 			sleep = g.short_disp_timeout
 			while True:
 				ch = get_char(p.format(n),num_chars=1,sleep=sleep)
-				if ch in b6d_digits:
+				if ch in bc.digits:
 					msg_r(CUR_HIDE + ' OK')
 					return ch
 				else:
@@ -826,7 +827,7 @@ class MMGenWallet(WalletEnc):
 			msg(f'Hash parameters {" ".join(hash_params)!r} don’t match hash preset {d.hash_preset!r}')
 			return False
 
-		lmin,foo,lmax = sorted(baseconv.seedlen_map_rev['b58']) # 22,33,44
+		lmin,foo,lmax = sorted(baseconv('b58').seedlen_map_rev) # 22,33,44
 		for i,key in (4,'salt'),(5,'enc_seed'):
 			l = lines[i].split(' ')
 			chk = l.pop(0)

+ 11 - 11
mmgen/xmrseed.py

@@ -30,15 +30,15 @@ def is_xmrseed(s):
 # implements a subset of the baseconv API
 class xmrseed(baseconv):
 
-	desc            = { 'xmrseed': ('Monero mnemonic', 'Monero new-style mnemonic seed phrase') }
-	wl_chksums      = { 'xmrseed': '3c381ebb' }
-	seedlen_map     = { 'xmrseed': { 32:25 } }
-	seedlen_map_rev = { 'xmrseed': { 25:32 } }
+	desc            = baseconv.dt('Monero mnemonic', 'Monero new-style mnemonic seed phrase')
+	wl_chksum       = '3c381ebb'
+	seedlen_map     = { 32:25 }
+	seedlen_map_rev = { 25:32 }
 
 	def __init__(self,wl_id='xmrseed'):
 		assert wl_id == 'xmrseed', "initialize with 'xmrseed' for compatibility with baseconv API"
 		from .mn_monero import words
-		self.digits = { 'xmrseed': words }
+		self.digits = words
 		self.wl_id = 'xmrseed'
 
 	@staticmethod
@@ -51,14 +51,14 @@ class xmrseed(baseconv):
 		assert isinstance(words,(list,tuple)),'words must be list or tuple'
 		assert pad == None, f"{pad}: invalid 'pad' argument (must be None)"
 
-		desc = self.desc[self.wl_id][0]
-		wl = self.digits[self.wl_id]
+		desc = self.desc.short
+		wl = self.digits
 		base = len(wl)
 
 		if not set(words) <= set(wl):
 			raise MnemonicError( f'{words!r}: not in {desc} format' )
 
-		if len(words) not in self.seedlen_map_rev['xmrseed']:
+		if len(words) not in self.seedlen_map_rev:
 			raise MnemonicError( f'{len(words)}: invalid seed phrase length for {desc}' )
 
 		z = self.monero_mn_checksum(words[:-1])
@@ -77,11 +77,11 @@ class xmrseed(baseconv):
 	def frombytes(self,bytestr,pad=None,tostr=False):
 		assert pad == None, f"{pad}: invalid 'pad' argument (must be None)"
 
-		desc = self.desc[self.wl_id][0]
-		wl = self.digits[self.wl_id]
+		desc = self.desc.short
+		wl = self.digits
 		base = len(wl)
 
-		if len(bytestr) not in self.seedlen_map['xmrseed']:
+		if len(bytestr) not in self.seedlen_map:
 			raise SeedLengthError(f'{len(bytestr)}: invalid seed byte length for {desc}')
 
 		def num2base_monero(num):

+ 1 - 1
test/unit_tests_d/ut_baseconv.py

@@ -198,7 +198,7 @@ class unit_test(object):
 		qmsg_r('\nChecking wordlist checksums:')
 		vmsg('')
 
-		for wl_id in baseconv.wl_chksums:
+		for wl_id in baseconv.constants['wl_chksum']:
 			vmsg_r(f'  {wl_id+":":9}')
 			baseconv(wl_id).check_wordlist()