baseconv: set constants in initializer
This commit is contained in:
parent
e7d531db07
commit
67067aa7d5
8 changed files with 77 additions and 74 deletions
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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 @@
|
|||
13.1.dev003
|
||||
13.1.dev004
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue