#!/usr/bin/env python3 """ test.modtest_d.ut_baseconv: Base conversion unit test for the MMGen suite """ from mmgen.util import msg, msg_r from ..include.common import cfg, qmsg, qmsg_r, vmsg, vmsg_r class unit_test: vectors = { 'b58': ( (('00', None), '1'), (('00', 1), '1'), (('00', 2), '11'), (('01', None), '2'), (('01', 1), '2'), (('01', 2), '12'), (('0f', None), 'G'), (('0f', 1), 'G'), (('0f', 2), '1G'), (('deadbeef', None), '6h8cQN'), (('deadbeef', 20), '111111111111116h8cQN'), (('00000000', None), '1'), (('00000000', 20), '11111111111111111111'), (('ffffffff', None), '7YXq9G'), (('ffffffff', 20), '111111111111117YXq9G'), (('ff'*16, 'seed'), 'YcVfxkQb6JRzqk5kF2tNLv'), (('ff'*24, 'seed'), 'QLbz7JHiBTspS962RLKV8GndWFwiEaqKL'), (('ff'*32, 'seed'), 'JEKNVnkbo3jma5nREBBJCDoXFVeKkD56V3xKrvRmWxFG'), (('00'*16, 'seed'), '1111111111111111111111'), (('00'*24, 'seed'), '111111111111111111111111111111111'), (('00'*32, 'seed'), '11111111111111111111111111111111111111111111'), ), # MMGen-flavored base32 using simple base conversion 'b32': ( (('00', None), 'A'), (('00', 1), 'A'), (('00', 2), 'AA'), (('01', None), 'B'), (('01', 1), 'B'), (('01', 2), 'AB'), (('0f', None), 'P'), (('0f', 1), 'P'), (('0f', 2), 'AP'), (('deadbeef', None), 'DPK3PXP'), (('deadbeef', 20), 'AAAAAAAAAAAAADPK3PXP'), (('00000000', None), 'A'), (('00000000', 20), 'AAAAAAAAAAAAAAAAAAAA'), (('ffffffff', None), 'D777777'), (('ffffffff', 20), 'AAAAAAAAAAAAAD777777'), ), 'b16': ( (('00', None), '0'), (('00', 1), '0'), (('00', 2), '00'), (('01', None), '1'), (('01', 1), '1'), (('01', 2), '01'), (('0f', None), 'f'), (('0f', 1), 'f'), (('0f', 2), '0f'), (('deadbeef', None), 'deadbeef'), (('deadbeef', 20), '000000000000deadbeef'), (('00000000', None), '0'), (('00000000', 20), '00000000000000000000'), (('ffffffff', None), 'ffffffff'), (('ffffffff', 20), '000000000000ffffffff'), ), 'b10': ( (('00', None), '0'), (('00', 1), '0'), (('00', 2), '00'), (('01', None), '1'), (('01', 1), '1'), (('01', 2), '01'), (('0f', None), '15'), (('0f', 1), '15'), (('0f', 2), '15'), (('deadbeef', None), '3735928559'), (('deadbeef', 20), '00000000003735928559'), (('00000000', None), '0'), (('00000000', 20), '00000000000000000000'), (('ffffffff', None), '4294967295'), (('ffffffff', 20), '00000000004294967295'), ), 'b8': ( (('00', None), '0'), (('00', 1), '0'), (('00', 2), '00'), (('01', None), '1'), (('01', 1), '1'), (('01', 2), '01'), (('0f', None), '17'), (('0f', 1), '17'), (('0f', 2), '17'), (('deadbeef', None), '33653337357'), (('deadbeef', 20), '00000000033653337357'), (('00000000', None), '0'), (('00000000', 20), '00000000000000000000'), (('ffffffff', None), '37777777777'), (('ffffffff', 20), '00000000037777777777'), ), 'b6d': ( (('00', None), '1'), (('00', 1), '1'), (('00', 2), '11'), (('01', None), '2'), (('01', 1), '2'), (('01', 2), '12'), (('0f', None), '34'), (('0f', 1), '34'), (('0f', 2), '34'), (('010f', None), '2242'), (('010f', 20), '11111111111111112242'), (('deadbeef', None), '2525524636426'), (('deadbeef', 20), '11111112525524636426'), (('00000000', None), '1'), (('00000000', 20), '11111111111111111111'), (('ffffffff', None), '2661215126614'), (('ffffffff', 20), '11111112661215126614'), (('ff'*16, 'seed'), '34164464641266661652465154654653354436664555521414'), (('ff'*24, 'seed'), '246111411433323364222465566552324652566121541623426135163525451613554313654'), (('ff'*32, 'seed'), '2132521653312613134145131423465414636252114131225324246311141642456513416322412146151432142242565134'), (('00'*16, 'seed'), '1'*50), (('00'*24, 'seed'), '1'*75), (('00'*32, 'seed'), '1'*100), ), 'mmgen': ( ( ('deadbeefdeadbeefdeadbeefdeadbeef', 'seed'), 'table cast forgive master funny gaze sadness ripple million paint moral match' ), ( ('deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef', 'seed'), 'swirl maybe anymore mix scale stray fog use approach page crime rhyme ' + 'class former strange window snap soon' ), ( ('deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef', 'seed'), 'swell type milk figure cheese phone fill black test bloom heard comfort ' + 'image terrible radio lesson own reply battle goal goodbye need laugh stream' ), ( ('ffffffffffffffffffffffffffffffff', 'seed'), 'yellow yeah show bowl season spider cling defeat poison law shelter reflect' ), ( ('ffffffffffffffffffffffffffffffffffffffffffffffff', 'seed'), 'yeah youth quit fail perhaps drum out person young click skin ' + 'weird inside silently perfectly together anyone memory' ), ( ('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 'seed'), 'wrote affection object cell opinion here laughter stare honest north cost begin ' + 'murder something yourself effort acid dot doubt game broke tell guilt innocent' ), ( ('00000000000000000000000000000001', 'seed'), 'able ' * 11 + 'about' ), ( ('000000000000000000000000000000000000000000000001', 'seed'), 'able ' * 17 + 'about' ), ( ('0000000000000000000000000000000000000000000000000000000000000001', 'seed'), 'able ' * 23 + 'about' ), ), } def run_test(self, name, ut): msg_r('Testing base conversion routines...') from mmgen.baseconv import baseconv perr = "length of {!r} less than pad length ({})" rerr = "return value ({!r}) does not match reference value ({!r})" qmsg_r('\nChecking hex-to-base conversion:') for base, data in self.vectors.items(): fs = " {h:%s} {p:<6} {r}" % max(len(d[0][0]) for d in data) if not cfg.verbose: qmsg_r(f' {base}') vmsg(f'\nBase: {base}') vmsg(fs.format(h='Input', p='Pad', r='Output')) for (hexstr, pad), ret_chk in data: ret = baseconv(base).fromhex(hexstr, pad=pad, tostr=True) if pad != 'seed': assert len(ret) >= (pad or 0), perr.format(ret, pad or 0) assert ret == ret_chk, rerr.format(ret, ret_chk) vmsg(fs.format(h=hexstr, r=ret, p=str(pad))) qmsg_r('\nChecking base-to-hex conversion:') for base, data in self.vectors.items(): fs = " {h:%s} {p:<6} {r}" % max(len(d[1]) for d in data) if not cfg.verbose: qmsg_r(f' {base}') vmsg(f'\nBase: {base}') vmsg(fs.format(h='Input', p='Pad', r='Output')) for (hexstr, pad), ret_chk in data: if type(pad) is int: pad = len(hexstr) ret = baseconv(base).tohex( ret_chk.split() if base == 'mmgen' else ret_chk, pad=pad ) if pad is None: assert int(ret, 16) == int(hexstr, 16), rerr.format(int(ret, 16), int(hexstr, 16)) else: assert ret == hexstr, rerr.format(ret, hexstr) vmsg(fs.format(h=ret_chk, r=ret, p=str(pad))) qmsg_r('\nChecking wordlist checksums:') vmsg('') for wl_id in baseconv.constants['wl_chksum']: vmsg_r(f' {wl_id+":":9}') baseconv(wl_id).check_wordlist(cfg) qmsg('') qmsg('Checking error handling:') bad_b58 = 'I'*22 bad_b58len = 'a'*23 fr58 = baseconv('b58').fromhex to58 = baseconv('b58').tohex to32 = baseconv('b32').tohex to8 = baseconv('b8').tohex hse = 'HexadecimalStringError' bce = 'BaseConversionError' bpe = 'BaseConversionPadError' sle = 'SeedLengthError' bad_data = ( ('hexstr', hse, ': not a hexadecimal str', lambda: fr58('x')), ('hexstr (seed)', hse, 'seed data not a hexadec', lambda: fr58('x', pad='seed')), ('hexstr (empty)', bce, 'empty data not allowed', lambda: fr58('')), ('b58 data', bce, ': not in base58', lambda: to58('IfFzZ')), ('b58 data (seed)', bce, 'seed data not in base58', lambda: to58(bad_b58, pad='seed')), ('b58 len (seed)', bce, 'invalid length for', lambda: to58(bad_b58len, pad='seed')), ('b58 data (empty)', bce, 'empty base58 data', lambda: to58('')), ('b8 data (empty)' , bce, 'empty base8 string data', lambda: to8('')), ('b32 data', bce, 'not in MMGen base32', lambda: to32('1az')), ('pad arg (in)', bpe, "illegal value for 'pad'", lambda: fr58('ff', pad='foo')), ('pad arg (in)', bpe, "illegal value for 'pad'", lambda: fr58('ff', pad=False)), ('pad arg (in)', bpe, "illegal value for 'pad'", lambda: fr58('ff', pad=True)), ('seedlen (in)', sle, 'invalid byte length', lambda: fr58('ff', pad='seed')), ('pad arg (out)', bpe, "illegal value for 'pad'", lambda: to58('Z', pad='foo')), ('pad arg (out)', bpe, "illegal value for 'pad'", lambda: to58('Z', pad=False)), ('pad arg (out)', bpe, "illegal value for 'pad'", lambda: to58('Z', pad=True)), ('seedlen (out)', bce, 'invalid length for seed', lambda: to58('Z', pad='seed')), ) ut.process_bad_data(bad_data) msg('OK') return True