From 67067aa7d54566749220eafb9575bc7bc3a7b8ce Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Tue, 18 Jan 2022 09:11:00 +0000 Subject: [PATCH] baseconv: set constants in initializer --- mmgen/baseconv.py | 78 ++++++++++++++++---------------- mmgen/bip39.py | 14 +++--- mmgen/data/version | 2 +- mmgen/mn_entry.py | 4 +- mmgen/passwdlist.py | 6 +-- mmgen/wallet.py | 23 +++++----- mmgen/xmrseed.py | 22 ++++----- test/unit_tests_d/ut_baseconv.py | 2 +- 8 files changed, 77 insertions(+), 74 deletions(-) diff --git a/mmgen/baseconv.py b/mmgen/baseconv.py index 95d3b7a4..67d2a9b4 100755 --- a/mmgen/baseconv.py +++ b/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') diff --git a/mmgen/bip39.py b/mmgen/bip39.py index a06915ca..0cb2fcb3 100755 --- a/mmgen/bip39.py +++ b/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 diff --git a/mmgen/data/version b/mmgen/data/version index 9c6d5cd4..666e09b5 100644 --- a/mmgen/data/version +++ b/mmgen/data/version @@ -1 +1 @@ -13.1.dev003 +13.1.dev004 diff --git a/mmgen/mn_entry.py b/mmgen/mn_entry.py index ada445f7..7ce87117 100755 --- a/mmgen/mn_entry.py +++ b/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: diff --git a/mmgen/passwdlist.py b/mmgen/passwdlist.py index 33bfe120..6de7b414 100755 --- a/mmgen/passwdlist.py +++ b/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 diff --git a/mmgen/wallet.py b/mmgen/wallet.py index 5bbbecad..8825879f 100755 --- a/mmgen/wallet.py +++ b/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) diff --git a/mmgen/xmrseed.py b/mmgen/xmrseed.py index e6786d82..220df8a5 100755 --- a/mmgen/xmrseed.py +++ b/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): diff --git a/test/unit_tests_d/ut_baseconv.py b/test/unit_tests_d/ut_baseconv.py index a0686d8f..2aa61fbd 100755 --- a/test/unit_tests_d/ut_baseconv.py +++ b/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()