From 4496f795d7a5ff5c2c0b602288db2f438f1a6ddd Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Tue, 18 Jan 2022 09:10:58 +0000 Subject: [PATCH] bip39.py: cleanups --- mmgen/bip39.py | 55 +++++++++++++++++------------------ mmgen/wallet.py | 12 ++++++-- test/unit_tests_d/ut_bip39.py | 4 +-- 3 files changed, 39 insertions(+), 32 deletions(-) diff --git a/mmgen/bip39.py b/mmgen/bip39.py index 65ef0972..44656c82 100755 --- a/mmgen/bip39.py +++ b/mmgen/bip39.py @@ -23,23 +23,29 @@ bip39.py - Data and routines for BIP39 mnemonic seed phrases from hashlib import sha256 from .exception import * -from .baseconv import * +from .baseconv import baseconv from .util import is_hex_str +def is_bip39_str(s): + return bool( bip39.tohex(s.split(), wl_id='bip39') ) + # implements a subset of the baseconv API class bip39(baseconv): - mn_base = 2048 - wl_chksums = { 'bip39': 'f18b9a84' } + 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 } } + + from collections import namedtuple + bc = namedtuple('bip39_constants',['chk_len','mn_len']) # ENT CS MS constants = { - '128': (4, 12), - '160': (5, 15), - '192': (6, 18), - '224': (7, 21), - '256': (8, 24), + 128: bc(4, 12), + 160: bc(5, 15), + 192: bc(6, 18), + 224: bc(7, 21), + 256: bc(8, 24), } from .mn_bip39 import words digits = { 'bip39': words } @@ -47,15 +53,15 @@ class bip39(baseconv): @classmethod def nwords2seedlen(cls,nwords,in_bytes=False,in_hex=False): for k,v in cls.constants.items(): - if v[1] == nwords: - return int(k)//8 if in_bytes else int(k)//4 if in_hex else int(k) + if v.mn_len == nwords: + return k//8 if in_bytes else k//4 if in_hex else k raise MnemonicError(f'{nwords!r}: invalid word length for BIP39 mnemonic') @classmethod def seedlen2nwords(cls,seed_len,in_bytes=False,in_hex=False): seed_bits = seed_len * 8 if in_bytes else seed_len * 4 if in_hex else seed_len try: - return cls.constants[str(seed_bits)][1] + return cls.constants[seed_bits].mn_len except: raise ValueError(f'{seed_bits!r}: invalid seed length for BIP39 mnemonic') @@ -71,6 +77,7 @@ class bip39(baseconv): def tohex(cls,words,wl_id,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] @@ -80,23 +87,20 @@ class bip39(baseconv): res = ''.join(['{:011b}'.format(wl.index(w)) for w in words]) - for k in cls.constants: - if len(words) == cls.constants[k][1]: - bitlen = int(k) + for k,v in cls.constants.items(): + if len(words) == v.mn_len: + bitlen = k break else: raise MnemonicError(f'{len(words)}: invalid BIP39 seed phrase length') - if pad != None: - assert pad * 4 == bitlen, f'{pad}: invalid pad length' - seed_bin = res[:bitlen] chk_bin = res[bitlen:] seed_hex = '{:0{w}x}'.format(int(seed_bin,2),w=bitlen//4) seed_bytes = bytes.fromhex(seed_hex) - chk_len = cls.constants[str(bitlen)][0] + chk_len = cls.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] @@ -110,29 +114,24 @@ class bip39(baseconv): 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] seed_bytes = bytes.fromhex(seed_hex) bitlen = len(seed_bytes) * 8 - assert str(bitlen) in cls.constants, f'{bitlen}: invalid seed bit length' - chk_len,mn_len = cls.constants[str(bitlen)] - - if pad != None: - assert mn_len == pad, f'{pad}: invalid pad length' + assert bitlen in cls.constants, f'{bitlen}: invalid seed bit length' + c = cls.constants[bitlen] chk_hex = sha256(seed_bytes).hexdigest() seed_bin = '{:0{w}b}'.format(int(seed_hex,16),w=bitlen) - chk_bin = '{:0{w}b}'.format(int(chk_hex,16),w=256)[:chk_len] + chk_bin = '{:0{w}b}'.format(int(chk_hex,16),w=256)[:c.chk_len] res = seed_bin + chk_bin - return tuple(wl[int(res[i*11:(i+1)*11],2)] for i in range(mn_len)) + 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'" - -def is_bip39_str(s): - return bool(bip39.tohex(s.split(),wl_id='bip39')) diff --git a/mmgen/wallet.py b/mmgen/wallet.py index ee2bcf54..45b3a00e 100755 --- a/mmgen/wallet.py +++ b/mmgen/wallet.py @@ -414,10 +414,12 @@ class Mnemonic(WalletUnenc): return mn_entry(self.wl_id).get_mnemonic_from_user(mn_len) @staticmethod - def _mn2hex_pad(mn): return len(mn) * 8 // 3 + def _mn2hex_pad(mn): + return len(mn) * 8 // 3 @staticmethod - def _hex2mn_pad(hexnum): return len(hexnum) * 3 // 8 + def _hex2mn_pad(hexnum): + return len(hexnum) * 3 // 8 def _format(self): @@ -486,6 +488,12 @@ class BIP39Mnemonic(Mnemonic): self.conv_cls = bip39 super().__init__(*args,**kwargs) + @staticmethod + def _mn2hex_pad(mn): return None + + @staticmethod + def _hex2mn_pad(hexnum): return None + class MMGenSeedFile(WalletUnenc): stdin_ok = True diff --git a/test/unit_tests_d/ut_bip39.py b/test/unit_tests_d/ut_bip39.py index 65e0500b..aa9b019d 100755 --- a/test/unit_tests_d/ut_bip39.py +++ b/test/unit_tests_d/ut_bip39.py @@ -128,8 +128,8 @@ class unit_test(object): ('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 len", lambda:fh(good_seed,'bip39',pad=23)), -('pad len (tohex)', 'AssertionError', "invalid pad len", lambda:th(good_mn,'bip39',pad=23)), +('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')),