baseconv: set constants in initializer

This commit is contained in:
The MMGen Project 2022-01-18 09:11:00 +00:00
commit 67067aa7d5
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
8 changed files with 77 additions and 74 deletions

View file

@ -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')

View file

@ -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

View file

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

View file

@ -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:

View file

@ -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

View file

@ -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)

View file

@ -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):

View file

@ -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()