baseconv: move Monero mnemonic code to new xmrseed class
- a new unit test has also been added
Testing:
$ test/unit_tests.py mn_entry xmrseed
$ test/tooltest2.py hex2mn mn2hex
$ test/test.py ref ref3_addr
This commit is contained in:
parent
786a10c937
commit
d6f82fb6c9
10 changed files with 251 additions and 82 deletions
|
|
@ -30,9 +30,6 @@ def is_b58_str(s):
|
|||
def is_b32_str(s):
|
||||
return set(list(s)) <= set(baseconv.digits['b32'])
|
||||
|
||||
def is_xmrseed(s):
|
||||
return bool( baseconv('xmrseed').tobytes(s.split()) )
|
||||
|
||||
class baseconv(object):
|
||||
|
||||
desc = {
|
||||
|
|
@ -45,7 +42,6 @@ class baseconv(object):
|
|||
# 'tirosh':('Tirosh mnemonic', 'base1626 mnemonic using truncated Tirosh wordlist'), # not used by wallet
|
||||
'mmgen': ('MMGen native mnemonic',
|
||||
'MMGen native mnemonic seed phrase created using old Electrum wordlist and simple base conversion'),
|
||||
'xmrseed': ('Monero mnemonic', 'Monero new-style mnemonic seed phrase'),
|
||||
}
|
||||
# https://en.wikipedia.org/wiki/Base32#RFC_4648_Base32_alphabet
|
||||
# https://tools.ietf.org/html/rfc4648
|
||||
|
|
@ -60,7 +56,6 @@ class baseconv(object):
|
|||
mn_base = 1626
|
||||
wl_chksums = {
|
||||
'mmgen': '5ca31424',
|
||||
'xmrseed':'3c381ebb',
|
||||
# 'tirosh': '48f05e1f', # tirosh truncated to mn_base
|
||||
# 'tirosh1633': '1a5faeff' # tirosh list is 1633 words long!
|
||||
}
|
||||
|
|
@ -68,13 +63,11 @@ class baseconv(object):
|
|||
'b58': { 16:22, 24:33, 32:44 },
|
||||
'b6d': { 16:50, 24:75, 32:100 },
|
||||
'mmgen': { 16:12, 24:18, 32:24 },
|
||||
'xmrseed': { 32:25 },
|
||||
}
|
||||
seedlen_map_rev = {
|
||||
'b58': { 22:16, 33:24, 44:32 },
|
||||
'b6d': { 50:16, 75:24, 100:32 },
|
||||
'mmgen': { 12:16, 18:24, 24:32 },
|
||||
'xmrseed': { 25:32 },
|
||||
}
|
||||
|
||||
def __init__(self,wl_id):
|
||||
|
|
@ -82,9 +75,6 @@ class baseconv(object):
|
|||
if wl_id == 'mmgen':
|
||||
from .mn_electrum import words
|
||||
self.digits[wl_id] = words
|
||||
elif wl_id == 'xmrseed':
|
||||
from .mn_monero import words
|
||||
self.digits[wl_id] = words
|
||||
elif wl_id not in self.digits:
|
||||
raise ValueError(f'{wl_id}: unrecognized mnemonic ID')
|
||||
|
||||
|
|
@ -132,12 +122,6 @@ class baseconv(object):
|
|||
else:
|
||||
raise BaseConversionPadError(f"{pad!r}: illegal value for 'pad' (must be None,'seed' or int)")
|
||||
|
||||
@staticmethod
|
||||
def monero_mn_checksum(words):
|
||||
from binascii import crc32
|
||||
wstr = ''.join(word[:3] for word in words)
|
||||
return words[crc32(wstr.encode()) % len(words)]
|
||||
|
||||
def tohex(self,words_arg,pad=None):
|
||||
"convert string or list data of instance base to hex string"
|
||||
return self.tobytes(words_arg,pad//2 if type(pad)==int else pad).hex()
|
||||
|
|
@ -168,21 +152,6 @@ class baseconv(object):
|
|||
( 'seed data' if pad == 'seed' else f'{words_arg!r}:' ) +
|
||||
f' not in {desc} format' )
|
||||
|
||||
if self.wl_id == 'xmrseed':
|
||||
if len(words) not in self.seedlen_map_rev['xmrseed']:
|
||||
die(2,f'{len(words)}: invalid length for Monero mnemonic')
|
||||
|
||||
z = self.monero_mn_checksum(words[:-1])
|
||||
assert z == words[-1],'invalid Monero mnemonic checksum'
|
||||
words = tuple(words[:-1])
|
||||
|
||||
ret = b''
|
||||
for i in range(len(words)//3):
|
||||
w1,w2,w3 = [wl.index(w) for w in words[3*i:3*i+3]]
|
||||
x = w1 + base*((w2-w1)%base) + base*base*((w3-w2)%base)
|
||||
ret += x.to_bytes(4,'big')[::-1]
|
||||
return ret
|
||||
|
||||
ret = sum([wl.index(words[::-1][i])*(base**i) for i in range(len(words))])
|
||||
bl = ret.bit_length()
|
||||
return ret.to_bytes(max(pad_val,bl//8+bool(bl%8)),'big')
|
||||
|
|
@ -214,28 +183,15 @@ class baseconv(object):
|
|||
|
||||
pad = max(self.get_pad(pad,get_seed_pad),1)
|
||||
wl = self.digits[self.wl_id]
|
||||
base = len(wl)
|
||||
|
||||
if self.wl_id == 'xmrseed':
|
||||
if len(bytestr) not in self.seedlen_map['xmrseed']:
|
||||
die(2, f'{len(bytestr)}: invalid seed byte length for Monero mnemonic')
|
||||
|
||||
def num2base_monero(num):
|
||||
w1 = num % base
|
||||
w2 = (num//base + w1) % base
|
||||
w3 = (num//base//base + w2) % base
|
||||
return [wl[w1], wl[w2], wl[w3]]
|
||||
|
||||
o = []
|
||||
for i in range(len(bytestr)//4):
|
||||
o += num2base_monero(int.from_bytes(bytestr[i*4:i*4+4][::-1],'big'))
|
||||
o.append(self.monero_mn_checksum(o))
|
||||
else:
|
||||
def gen():
|
||||
num = int.from_bytes(bytestr,'big')
|
||||
ret = []
|
||||
base = len(wl)
|
||||
while num:
|
||||
ret.append(num % base)
|
||||
yield num % base
|
||||
num //= base
|
||||
o = [wl[n] for n in [0] * (pad-len(ret)) + ret[::-1]]
|
||||
|
||||
return (' ' if self.wl_id in ('mmgen','xmrseed') else '').join(o) if tostr else o
|
||||
ret = list(gen())
|
||||
o = [wl[n] for n in [0] * (pad-len(ret)) + ret[::-1]]
|
||||
|
||||
return (' ' if self.wl_id == 'mmgen' else '').join(o) if tostr else o
|
||||
|
|
|
|||
|
|
@ -420,7 +420,7 @@ class MnemonicEntryBIP39(MnemonicEntry):
|
|||
|
||||
class MnemonicEntryMonero(MnemonicEntry):
|
||||
wl_id = 'xmrseed'
|
||||
modname = 'baseconv'
|
||||
modname = 'xmrseed'
|
||||
entry_modes = ('full','short')
|
||||
dfl_entry_mode = 'short'
|
||||
has_chksum = True
|
||||
|
|
|
|||
|
|
@ -25,8 +25,9 @@ from collections import namedtuple
|
|||
from .exception import InvalidPasswdFormat
|
||||
from .util import ymsg,is_hex_str,is_int,keypress_confirm
|
||||
from .obj import ImmutableAttr,ListItemAttr,MMGenPWIDString
|
||||
from .baseconv import baseconv,is_b32_str,is_b58_str,is_xmrseed
|
||||
from .baseconv import baseconv,is_b32_str,is_b58_str
|
||||
from .bip39 import is_bip39_str
|
||||
from .xmrseed import is_xmrseed
|
||||
from .key import PrivKey
|
||||
from .addr import MMGenPasswordType,AddrIdx,AddrListID
|
||||
from .addrlist import (
|
||||
|
|
@ -158,9 +159,10 @@ class PasswordList(AddrList):
|
|||
pw_bytes = bip39.nwords2seedlen(self.pw_len,in_bytes=True)
|
||||
good_pw_len = bip39.seedlen2nwords(seed.byte_len,in_bytes=True)
|
||||
elif pf == 'xmrseed':
|
||||
pw_bytes = baseconv.seedlen_map_rev['xmrseed'][self.pw_len]
|
||||
from .xmrseed import xmrseed
|
||||
pw_bytes = xmrseed.seedlen_map_rev['xmrseed'][self.pw_len]
|
||||
try:
|
||||
good_pw_len = baseconv.seedlen_map['xmrseed'][seed.byte_len]
|
||||
good_pw_len = xmrseed.seedlen_map['xmrseed'][seed.byte_len]
|
||||
except:
|
||||
die(1,f'{seed.byte_len*8}: unsupported seed length for Monero new-style mnemonic')
|
||||
elif pf in ('b32','b58'):
|
||||
|
|
@ -196,12 +198,13 @@ class PasswordList(AddrList):
|
|||
# take most significant part
|
||||
return ' '.join( bip39().fromhex(secbytes[:pw_len_bytes].hex()) )
|
||||
elif self.pw_fmt == 'xmrseed':
|
||||
pw_len_bytes = baseconv.seedlen_map_rev['xmrseed'][self.pw_len]
|
||||
from .xmrseed import xmrseed
|
||||
pw_len_bytes = xmrseed.seedlen_map_rev['xmrseed'][self.pw_len]
|
||||
from .protocol import init_proto
|
||||
bytes_preproc = init_proto('xmr').preprocess_key(
|
||||
secbytes[:pw_len_bytes], # take most significant part
|
||||
None )
|
||||
return ' '.join( baseconv('xmrseed').frombytes(bytes_preproc) )
|
||||
return ' '.join( xmrseed().frombytes(bytes_preproc) )
|
||||
else:
|
||||
# take least significant part
|
||||
return baseconv(self.pw_fmt).frombytes(
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ from .addr import *
|
|||
from .addrlist import AddrList,KeyAddrList
|
||||
from .passwdlist import PasswordList
|
||||
from .baseconv import baseconv
|
||||
from .xmrseed import xmrseed
|
||||
from .bip39 import bip39
|
||||
|
||||
NL = ('\n','\r\n')[g.platform=='win']
|
||||
|
|
@ -238,7 +239,7 @@ mft = namedtuple('mnemonic_format',['fmt','pad','conv_cls'])
|
|||
mnemonic_fmts = {
|
||||
'mmgen': mft( 'words', 'seed', baseconv ),
|
||||
'bip39': mft( 'bip39', None, bip39 ),
|
||||
'xmrseed': mft( 'xmrseed', None, baseconv ),
|
||||
'xmrseed': mft( 'xmrseed', None, xmrseed ),
|
||||
}
|
||||
mn_opts_disp = _options_annot_str(mnemonic_fmts)
|
||||
|
||||
|
|
|
|||
101
mmgen/xmrseed.py
Executable file
101
mmgen/xmrseed.py
Executable file
|
|
@ -0,0 +1,101 @@
|
|||
#!/usr/bin/env python3
|
||||
#
|
||||
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
|
||||
# Copyright (C)2013-2022 The MMGen Project <mmgen@tuta.io>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
xmrseed.py: Monero mnemonic conversion class for the MMGen suite
|
||||
"""
|
||||
|
||||
from .exception import *
|
||||
from .baseconv import baseconv
|
||||
from .util import die
|
||||
|
||||
def is_xmrseed(s):
|
||||
return bool( xmrseed().tobytes(s.split()) )
|
||||
|
||||
# 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 } }
|
||||
|
||||
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.wl_id = 'xmrseed'
|
||||
|
||||
@staticmethod
|
||||
def monero_mn_checksum(words):
|
||||
from binascii import crc32
|
||||
wstr = ''.join(word[:3] for word in words)
|
||||
return words[crc32(wstr.encode()) % len(words)]
|
||||
|
||||
def tobytes(self,words,pad=None):
|
||||
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]
|
||||
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']:
|
||||
raise MnemonicError( f'{len(words)}: invalid seed phrase length for {desc}' )
|
||||
|
||||
z = self.monero_mn_checksum(words[:-1])
|
||||
if z != words[-1]:
|
||||
raise MnemonicError(f'invalid {desc} checksum')
|
||||
words = tuple(words[:-1])
|
||||
|
||||
def gen():
|
||||
for i in range(len(words)//3):
|
||||
w1,w2,w3 = [wl.index(w) for w in words[3*i:3*i+3]]
|
||||
x = w1 + base*((w2-w1)%base) + base*base*((w3-w2)%base)
|
||||
yield x.to_bytes(4,'big')[::-1]
|
||||
|
||||
return b''.join(gen())
|
||||
|
||||
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]
|
||||
base = len(wl)
|
||||
|
||||
if len(bytestr) not in self.seedlen_map['xmrseed']:
|
||||
raise SeedLengthError(f'{len(bytestr)}: invalid seed byte length for {desc}')
|
||||
|
||||
def num2base_monero(num):
|
||||
w1 = num % base
|
||||
w2 = (num//base + w1) % base
|
||||
w3 = (num//base//base + w2) % base
|
||||
return ( wl[w1], wl[w2], wl[w3] )
|
||||
|
||||
def gen():
|
||||
for i in range(len(bytestr)//4):
|
||||
for e in num2base_monero( int.from_bytes( bytestr[i*4:i*4+4][::-1], 'big' ) ):
|
||||
yield e
|
||||
|
||||
o = list(gen())
|
||||
o.append( self.monero_mn_checksum(o) )
|
||||
|
||||
return ' '.join(o) if tostr else tuple(o)
|
||||
|
|
@ -579,12 +579,12 @@ class MoneroWalletOps:
|
|||
async def process_wallet(self,d,fn,last):
|
||||
msg_r('') # for pexpect
|
||||
|
||||
from .baseconv import baseconv
|
||||
from .xmrseed import xmrseed
|
||||
ret = await self.c.call(
|
||||
'restore_deterministic_wallet',
|
||||
filename = os.path.basename(fn),
|
||||
password = d.wallet_passwd,
|
||||
seed = baseconv('xmrseed').fromhex(d.sec.wif,tostr=True),
|
||||
seed = xmrseed().fromhex(d.sec.wif,tostr=True),
|
||||
restore_height = uopt.restore_height,
|
||||
language = 'English' )
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ sys.path.insert(0,overlay_setup(repo_root))
|
|||
from mmgen.common import *
|
||||
from test.include.common import *
|
||||
from mmgen.wallet import is_bip39_mnemonic,is_mmgen_mnemonic
|
||||
from mmgen.xmrseed import is_xmrseed
|
||||
from mmgen.baseconv import *
|
||||
|
||||
skipped_tests = ['mn2hex_interactive']
|
||||
|
|
|
|||
|
|
@ -9,23 +9,6 @@ from mmgen.exception import *
|
|||
class unit_test(object):
|
||||
|
||||
vectors = {
|
||||
'xmrseed': (
|
||||
# 42nsXK8WbVGTNayQ6Kjw5UdgqbQY5KCCufdxdCgF7NgTfjC69Mna7DJSYyie77hZTQ8H92G2HwgFhgEUYnDzrnLnQdF28r3
|
||||
(('0000000000000000000000000000000000000000000000000000000000000001','seed'), # 0x1
|
||||
'abbey abbey abbey abbey abbey abbey abbey abbey abbey abbey abbey abbey abbey abbey abbey abbey abbey abbey abbey abbey abbey bamboo jaws jerseys abbey'),
|
||||
|
||||
# 49voQEbjouUQSDikRWKUt1PGbS47TBde4hiGyftN46CvTDd8LXCaimjHRGtofCJwY5Ed5QhYwc12P15AH5w7SxUAMCz1nr1
|
||||
(('1c95988d7431ecd670cf7d73f45befc6feffffffffffffffffffffffffffff0f','seed'), # 0xffffffff * 8
|
||||
'powder directed sayings enmity bacon vapidly entrance bumper noodles iguana sleepless nasty flying soil software foamy solved soggy foamy solved soggy jury yawning ankle solved'),
|
||||
|
||||
# 41i7saPWA53EoHenmJVRt34dubPxsXwoWMnw8AdMyx4mTD1svf7qYzcVjxxRfteLNdYrAxWUMmiPegFW9EfoNgXx7vDMExv
|
||||
(('e8164dda6d42bd1e261a3406b2038dcbddadbeefdeadbeefdeadbeefdeadbe0f','seed'), # 0xdeadbeef * 8
|
||||
'viewpoint donuts ardent template unveil agile meant unafraid urgent athlete rustled mime azure jaded hawk baby jagged haystack baby jagged haystack ramped oncoming point template'),
|
||||
|
||||
# 42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm
|
||||
(('148d78d2aba7dbca5cd8f6abcfb0b3c009ffbdbea1ff373d50ed94d78286640e','seed'), # Monero repo
|
||||
'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted'),
|
||||
),
|
||||
'b58': (
|
||||
(('00',None),'1'),
|
||||
(('00',1),'1'),
|
||||
|
|
@ -180,9 +163,7 @@ class unit_test(object):
|
|||
for (hexstr,pad),ret_chk in data:
|
||||
if type(pad) == int:
|
||||
pad = len(hexstr)
|
||||
ret = baseconv(base).tohex(
|
||||
ret_chk.split() if base == 'xmrseed' else ret_chk,
|
||||
pad=pad)
|
||||
ret = baseconv(base).tohex( ret_chk, pad=pad )
|
||||
if pad == None:
|
||||
assert int(ret,16) == int(hexstr,16), rerr.format(int(ret,16),int(hexstr,16))
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ class unit_tests:
|
|||
'restore_deterministic_wallet',
|
||||
filename = fn,
|
||||
password = 'foo',
|
||||
seed = baseconv('xmrseed').fromhex('beadface'*8,tostr=True) )
|
||||
seed = xmrseed().fromhex('beadface'*8,tostr=True) )
|
||||
qmsg(f'Opening {wd.network} wallet')
|
||||
await c.call( 'open_wallet', filename=fn, password='foo' )
|
||||
|
||||
|
|
@ -152,7 +152,7 @@ class unit_tests:
|
|||
|
||||
gmsg('OK')
|
||||
|
||||
from mmgen.baseconv import baseconv
|
||||
from mmgen.xmrseed import xmrseed
|
||||
import shutil
|
||||
shutil.rmtree('test/trash2',ignore_errors=True)
|
||||
os.makedirs('test/trash2')
|
||||
|
|
|
|||
126
test/unit_tests_d/ut_xmrseed.py
Executable file
126
test/unit_tests_d/ut_xmrseed.py
Executable file
|
|
@ -0,0 +1,126 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
test/unit_tests_d/ut_xmrseed: Monero mnemonic unit test for the MMGen suite
|
||||
"""
|
||||
|
||||
from mmgen.common import *
|
||||
from mmgen.exception import *
|
||||
|
||||
class unit_test(object):
|
||||
|
||||
vectors = ( # private keys are reduced
|
||||
( '148d78d2aba7dbca5cd8f6abcfb0b3c009ffbdbea1ff373d50ed94d78286640e', # Monero repo
|
||||
'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches ' +
|
||||
'lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted',
|
||||
),
|
||||
( 'e8164dda6d42bd1e261a3406b2038dcbddadbeefdeadbeefdeadbeefdeadbe0f',
|
||||
'viewpoint donuts ardent template unveil agile meant unafraid urgent athlete rustled mime azure ' +
|
||||
'jaded hawk baby jagged haystack baby jagged haystack ramped oncoming point template'
|
||||
),
|
||||
( '6900dea9753f5c7ced87b53bdcfb109a8417bca6a2797a708194157b227fb60b',
|
||||
'criminal bamboo scamper gnaw limits womanly wrong tuition birth mundane donuts square cohesive ' +
|
||||
'dolphin titans narrate fuel saved wrap aloof magically mirror together update wrap'
|
||||
),
|
||||
( '0000000000000000000000000000000000000000000000000000000000000001',
|
||||
'abbey abbey abbey abbey abbey abbey abbey abbey abbey abbey abbey abbey abbey abbey abbey abbey ' +
|
||||
'abbey abbey abbey abbey abbey bamboo jaws jerseys abbey'
|
||||
),
|
||||
( '1c95988d7431ecd670cf7d73f45befc6feffffffffffffffffffffffffffff0f',
|
||||
'powder directed sayings enmity bacon vapidly entrance bumper noodles iguana sleepless nasty flying ' +
|
||||
'soil software foamy solved soggy foamy solved soggy jury yawning ankle solved'
|
||||
),
|
||||
( '2c94988d7431ecd670cf7d73f45befc6feffffffffffffffffffffffffffff0f',
|
||||
'memoir apart olive enmity bacon vapidly entrance bumper noodles iguana sleepless nasty flying soil ' +
|
||||
'software foamy solved soggy foamy solved soggy jury yawning ankle foamy'
|
||||
),
|
||||
( '4bb0288c9673b69fa68c2174851884abbaaedce6af48a03bbfd25e8cd0364102',
|
||||
'rated bicycle pheasants dejected pouch fizzle shipped rash citadel queen avatar sample muzzle mews ' +
|
||||
'jagged origin yeti dunes obtains godfather unbending pastry vortex washing citadel'
|
||||
),
|
||||
( '4bb0288c9673b69fa68c2174851884abbaaedce6af48a03bbfd25e8cd0364100',
|
||||
'rated bicycle pheasants dejected pouch fizzle shipped rash citadel queen avatar sample muzzle mews ' +
|
||||
'jagged origin yeti dunes obtains godfather unbending kangaroo auctions audio citadel'
|
||||
),
|
||||
( '1d95988d7431ecd670cf7d73f45befc6feffffffffffffffffffffffffffff0e',
|
||||
'pram distance scamper enmity bacon vapidly entrance bumper noodles iguana sleepless nasty flying ' +
|
||||
'soil software foamy solved soggy foamy solved soggy hashing mullet onboard solved'
|
||||
),
|
||||
( 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0f',
|
||||
'foamy solved soggy foamy solved soggy foamy solved soggy foamy solved soggy foamy solved soggy ' +
|
||||
'foamy solved soggy foamy solved soggy jury yawning ankle soggy'
|
||||
),
|
||||
)
|
||||
|
||||
def run_test(self,name,ut):
|
||||
|
||||
def test_fromhex(b):
|
||||
vmsg('')
|
||||
qmsg('Checking seed to mnemonic conversion:')
|
||||
for privhex,chk in self.vectors:
|
||||
vmsg(f' {chk}')
|
||||
chk = tuple(chk.split())
|
||||
res = b.fromhex(privhex)
|
||||
if use_moneropy:
|
||||
mp_chk = tuple( mnemonic.mn_encode(privhex) )
|
||||
assert res[:24] == mp_chk, f'check failed:\nres: {res[:24]}\nchk: {chk}'
|
||||
assert res == chk, f'check failed:\nres: {res}\nchk: {chk}'
|
||||
|
||||
def test_tohex(b):
|
||||
vmsg('')
|
||||
qmsg('Checking mnemonic to seed conversion:')
|
||||
for chk,words in self.vectors:
|
||||
vmsg(f' {chk}')
|
||||
res = b.tohex( words.split() )
|
||||
if use_moneropy:
|
||||
mp_chk = mnemonic.mn_decode( words.split() )
|
||||
assert res == mp_chk, f'check failed:\nres: {res}\nchk: {mp_chk}'
|
||||
assert res == chk, f'check failed:\nres: {res}\nchk: {chk}'
|
||||
|
||||
msg_r('Testing xmrseed conversion routines...')
|
||||
qmsg('')
|
||||
|
||||
from mmgen.xmrseed import xmrseed
|
||||
|
||||
b = xmrseed()
|
||||
b.check_wordlist()
|
||||
|
||||
try:
|
||||
from moneropy import mnemonic
|
||||
except ImportError:
|
||||
use_moneropy = False
|
||||
ymsg('Warning: unable to import moneropy, skipping external library checks')
|
||||
else:
|
||||
use_moneropy = True
|
||||
|
||||
test_fromhex(b)
|
||||
test_tohex(b)
|
||||
|
||||
vmsg('')
|
||||
qmsg('Checking error handling:')
|
||||
|
||||
bad_chksum_mn = ('abbey ' * 21 + 'bamboo jaws jerseys donuts').split()
|
||||
bad_word_mn = "admire zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo".split()
|
||||
bad_seed = 'deadbeef'
|
||||
good_mn = self.vectors[0][1].split()
|
||||
good_hex = self.vectors[0][0]
|
||||
bad_len_mn = good_mn[:22]
|
||||
|
||||
th = b.tohex
|
||||
fh = b.fromhex
|
||||
bad_data = (
|
||||
('hex', 'HexadecimalStringError', 'not a hexadecimal', lambda:fh('xx')),
|
||||
('seed len', 'SeedLengthError', 'invalid seed byte len', lambda:fh(bad_seed)),
|
||||
('mnemonic type', 'AssertionError', 'must be list', lambda:th('string')),
|
||||
('pad arg (fromhex)', 'AssertionError', "invalid 'pad' arg", lambda:fh(good_hex,pad=23)),
|
||||
('pad arg (tohex)', 'AssertionError', "invalid 'pad' arg", lambda:th(good_mn,pad=23)),
|
||||
('word', 'MnemonicError', "not in Monero", lambda:th(bad_word_mn)),
|
||||
('checksum', 'MnemonicError', "checksum", lambda:th(bad_chksum_mn)),
|
||||
('seed phrase len', 'MnemonicError', "phrase len", lambda:th(bad_len_mn)),
|
||||
)
|
||||
|
||||
ut.process_bad_data(bad_data)
|
||||
|
||||
vmsg('')
|
||||
msg('OK')
|
||||
|
||||
return True
|
||||
Loading…
Add table
Add a link
Reference in a new issue