tooltest2.py - add BTC test vectors

tooltest.py  - bugfixes, remove some commands covered in tooltest2.py
mmgen-tool   - bugfixes, cleanups, rename some commands, change some command
               options
			 - all commands taking binary input can now receive it from file
			   or stdin

+ numerous minor fixes throughout
This commit is contained in:
The MMGen Project 2019-03-06 20:58:59 +00:00
commit 0879e53e74
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
20 changed files with 819 additions and 431 deletions

View file

@ -65,6 +65,9 @@
# Set the maximum transaction file size:
# max_tx_file_size 100000
# Set the maximum input size - applies both to files and standard input:
# max_input_size 1048576
# Set the Ethereum mainnet name
# eth_mainnet_chain_name foundation

View file

@ -627,15 +627,16 @@ Removed {{}} duplicate WIF key{{}} from keylist (also in {pnm} key-address file
ret.append(a)
if self.has_keys and keypress_confirm('Check key-to-address validity?'):
kg = KeyGenerator(self.al_id.mmtype)
ag = AddrGenerator(self.al_id.mmtype)
llen = len(ret)
for n,e in enumerate(ret):
msg_r('\rVerifying keys {}/{}'.format(n+1,llen))
assert e.addr == ag.to_addr(kg.to_pubhex(e.sec)),(
"Key doesn't match address!\n {}\n {}".format(e.sec.wif,e.addr))
msg(' - done')
if self.has_keys:
if (hasattr(opt,'yes') and opt.yes) or keypress_confirm('Check key-to-address validity?'):
kg = KeyGenerator(self.al_id.mmtype)
ag = AddrGenerator(self.al_id.mmtype)
llen = len(ret)
for n,e in enumerate(ret):
qmsg_r('\rVerifying keys {}/{}'.format(n+1,llen))
assert e.addr == ag.to_addr(kg.to_pubhex(e.sec)),(
"Key doesn't match address!\n {}\n {}".format(e.sec.wif,e.addr))
qmsg(' - done')
return ret

View file

@ -182,7 +182,8 @@ def mmgen_encrypt(data,desc='data',hash_preset=''):
salt = get_random(_salt_len)
iv = get_random(g.aesctr_iv_len)
nonce = get_random(_nonce_len)
hp = hash_preset or get_hash_preset_from_user('3',desc)
hp = hash_preset or (
opt.hash_preset if 'hash_preset' in opt.set_by_user else get_hash_preset_from_user('3',desc))
m = ('user-requested','default')[hp=='3']
vmsg('Encrypting {}'.format(desc))
qmsg("Using {} hash preset of '{}'".format(m,hp))
@ -192,12 +193,13 @@ def mmgen_encrypt(data,desc='data',hash_preset=''):
return salt+iv+enc_d
def mmgen_decrypt(data,desc='data',hash_preset=''):
vmsg('Preparing to decrypt {}'.format(desc))
dstart = _salt_len + g.aesctr_iv_len
salt = data[:_salt_len]
iv = data[_salt_len:dstart]
enc_d = data[dstart:]
vmsg('Preparing to decrypt {}'.format(desc))
hp = hash_preset or get_hash_preset_from_user('3',desc)
hp = hash_preset or (
opt.hash_preset if 'hash_preset' in opt.set_by_user else get_hash_preset_from_user('3',desc))
m = ('user-requested','default')[hp=='3']
qmsg("Using {} hash preset of '{}'".format(m,hp))
passwd = get_mmgen_passphrase(desc)

View file

@ -32,6 +32,7 @@ class TokenNotInBlockchain(Exception): mmcode = 2
# 3: yellow hl, 'MMGen Error' + exception + message
class RPCFailure(Exception): mmcode = 3
class BadTxSizeEstimate(Exception): mmcode = 3
class MaxInputSizeExceeded(Exception): mmcode = 3
# 4: red hl, 'MMGen Fatal Error' + exception + message
class BadMMGenTxID(Exception): mmcode = 4

View file

@ -140,7 +140,7 @@ class g(object):
'daemon_data_dir','force_256_color','regtest',
'btc_max_tx_fee','ltc_max_tx_fee','bch_max_tx_fee','eth_max_tx_fee',
'eth_mainnet_chain_name','eth_testnet_chain_name',
'max_tx_file_size'
'max_tx_file_size','max_input_size'
)
env_opts = (
'MMGEN_BOGUS_WALLET_DATA',
@ -163,6 +163,7 @@ class g(object):
min_screen_width = 80
minconf = 1
max_tx_file_size = 100000
max_input_size = 1024 * 1024
# Global var sets user opt:
global_sets_opt = ['minconf','seed_len','hash_preset','usr_randchars','debug',

View file

@ -38,7 +38,7 @@ def make_cmd_help():
out.append('')
out.append('')
cls_funcs = bc.user_commands()
cls_funcs = bc._user_commands()
max_w = max(map(len,cls_funcs))
fs = ' {{:{}}} - {{}}'.format(max_w)
for func in cls_funcs:
@ -60,6 +60,8 @@ opts_data = lambda: {
-d, --outdir= d Specify an alternate directory 'd' for output
-h, --help Print this help message
--, --longhelp Print help message for long options (common options)
-p, --hash-preset= p Use the scrypt hash parameters defined by preset 'p'
for password hashing (default: '{g.hash_preset}')
-P, --passwd-file= f Get passphrase from file 'f'.
-q, --quiet Produce quieter output
-r, --usr-randchars=n Get 'n' characters of additional randomness from
@ -95,4 +97,4 @@ args,kwargs = tool._process_args(cmd,cmd_args)
ret = getattr(tc,cmd)(*args,**kwargs)
tool._process_result(ret,to_screen=True,pager='pager' in kwargs and kwargs['pager'])
tool._process_result(ret,pager='pager' in kwargs and kwargs['pager'],print_result=True)

View file

@ -95,6 +95,7 @@ class BitcoinProtocol(MMGenObject):
bech32_hrp = 'bc'
sign_mode = 'daemon'
secp256k1_ge = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141
privkey_len = 32
@classmethod
def is_testnet(cls):
@ -124,6 +125,7 @@ class BitcoinProtocol(MMGenObject):
@classmethod
def hex2wif(cls,hexpriv,pubkey_type,compressed): # PrivKey
assert len(hexpriv) == cls.privkey_len*2, '{} bytes: incorrect private key length!'.format(len(hexpriv)//2)
return _b58chk_encode(cls.wif_ver_num[pubkey_type] + hexpriv + (b'',b'01')[bool(compressed)])
@classmethod
@ -405,9 +407,9 @@ class MoneroProtocol(DummyWIF,BitcoinProtocolAddrgen):
def b58dec(addr_str):
from mmgen.util import baseconv
dec,l = baseconv.tohex,len(addr_str)
a = ''.join([dec(addr_str[i*11:i*11+11],'b58',pad=16) for i in range(l//11)])
b = dec(addr_str[-(l%11):],'b58',pad=10)
l = len(addr_str)
a = b''.join([baseconv.tohex(addr_str[i*11:i*11+11],'b58',pad=16) for i in range(l//11)])
b = baseconv.tohex(addr_str[-(l%11):],'b58',pad=10)
return a + b
from mmgen.util import is_b58_str
@ -417,9 +419,9 @@ class MoneroProtocol(DummyWIF,BitcoinProtocolAddrgen):
ret = b58dec(addr)
import sha3
chk = sha3.keccak_256(unhexlify(ret)[:-4]).hexdigest()[:8]
assert chk == ret[-8:],'Incorrect checksum'
assert chk.encode() == ret[-8:],'Incorrect checksum'
return { 'hex': ret.encode(), 'format': 'monero' } if return_dict else True
return { 'hex': ret, 'format': 'monero' } if return_dict else True
class MoneroTestnetProtocol(MoneroProtocol):
addr_ver_num = { 'monero': (b'35','4'), 'monero_sub': (b'3f','8') } # 53,63

View file

@ -34,6 +34,18 @@ def check_usr_seed_len(seed_len):
m = "ERROR: requested seed length ({}) doesn't match seed length of source ({})"
die(1,m.format((opt.seed_len,seed_len)))
def is_mnemonic(s):
oq_save = opt.quiet
opt.quiet = True
try:
SeedSource(in_data=s,in_fmt='words')
ret = True
except:
ret = False
finally:
opt.quiet = oq_save
return ret
class Seed(MMGenObject):
def __init__(self,seed_bin=None):
if not seed_bin:
@ -63,7 +75,9 @@ class SeedSource(MMGenObject):
class SeedSourceData(MMGenObject): pass
def __new__(cls,fn=None,ss=None,seed=None,ignore_in_fmt=False,passchg=False):
def __new__(cls,fn=None,ss=None,seed=None,ignore_in_fmt=False,passchg=False,in_data=None,in_fmt=None):
in_fmt = in_fmt or opt.in_fmt
def die_on_opt_mismatch(opt,sstype):
opt_sstype = cls.fmt_code_to_type(opt)
@ -86,13 +100,13 @@ class SeedSource(MMGenObject):
# permit comma in filename
fn = ','.join(opt.hidden_incog_input_params.split(',')[:-1])
f = Filename(fn,ftype=IncogWalletHidden)
if opt.in_fmt and not ignore_in_fmt:
die_on_opt_mismatch(opt.in_fmt,f.ftype)
if in_fmt and not ignore_in_fmt:
die_on_opt_mismatch(in_fmt,f.ftype)
me = super(cls,cls).__new__(f.ftype)
me.infile = f
me.op = ('old','pwchg_old')[bool(passchg)]
elif opt.in_fmt: # Input format
sstype = cls.fmt_code_to_type(opt.in_fmt)
elif in_fmt: # Input format
sstype = cls.fmt_code_to_type(in_fmt)
me = super(cls,cls).__new__(sstype)
me.op = ('old','pwchg_old')[bool(passchg)]
else: # Called with no inputs - initialize with random seed
@ -104,10 +118,11 @@ class SeedSource(MMGenObject):
return me
def __init__(self,fn=None,ss=None,seed=None,ignore_in_fmt=False,passchg=False):
def __init__(self,fn=None,ss=None,seed=None,ignore_in_fmt=False,passchg=False,in_data=None,in_fmt=None):
self.ssdata = self.SeedSourceData()
self.msg = {}
self.in_data = in_data
for c in reversed(self.__class__.__mro__):
if hasattr(c,'_msg'):
@ -116,7 +131,7 @@ class SeedSource(MMGenObject):
if hasattr(self,'seed'):
self._encrypt()
return
elif hasattr(self,'infile') or not g.stdin_tty:
elif hasattr(self,'infile') or self.in_data or not g.stdin_tty:
self._deformat_once()
self._decrypt_retry()
else:
@ -130,8 +145,9 @@ class SeedSource(MMGenObject):
def _get_data(self):
if hasattr(self,'infile'):
self.fmt_data = get_data_from_file(self.infile.name,self.desc,
binary=self.file_mode=='binary',require_utf8=self.require_utf8_input)
self.fmt_data = get_data_from_file(self.infile.name,self.desc,binary=self.file_mode=='binary')
elif self.in_data:
self.fmt_data = self.in_data
else:
self.fmt_data = self._get_data_from_user(self.desc)
@ -437,7 +453,7 @@ class Mnemonic (SeedSourceUnenc):
hexseed = self.seed.hexdata
mn = baseconv.fromhex(hexseed,self.wl_id,self._hex2mn_pad(hexseed))
ret = baseconv.tohex(mn,self.wl_id,self._mn2hex_pad(mn)).encode()
ret = baseconv.tohex(mn,self.wl_id,self._mn2hex_pad(mn))
# Internal error, so just die on fail
compare_or_die(ret,'recomputed seed',hexseed,'original',e='Internal error')
@ -459,7 +475,7 @@ class Mnemonic (SeedSourceUnenc):
msg('Invalid mnemonic: word #{} is not in the wordlist'.format(n))
return False
hexseed = baseconv.tohex(mn,self.wl_id,self._mn2hex_pad(mn)).encode()
hexseed = baseconv.tohex(mn,self.wl_id,self._mn2hex_pad(mn))
ret = baseconv.fromhex(hexseed,self.wl_id,self._hex2mn_pad(hexseed))
if len(hexseed) * 4 not in g.seed_lens:

View file

@ -43,12 +43,15 @@ def _create_call_sig(cmd,parsed=False):
nargs = len(args) - len(dfls)
def get_type_from_ann(arg):
return ann[arg][1:] + (' or STDIN','')[parsed] if ann[arg] == 'sstr' else ann[arg].__name__
if parsed:
c_args = [(a,'str' if ann[a] == 'sstr' else ann[a].__name__) for a in args[:nargs]]
c_args = [(a,get_type_from_ann(a)) for a in args[:nargs]]
c_kwargs = [(a,dfls[n]) for n,a in enumerate(args[nargs:])]
return c_args,dict(c_kwargs),'STDIN_OK' if c_args and ann[args[0]] == 'sstr' else flag
else:
c_args = ['{} [{}]'.format(a,'str or STDIN' if ann[a] == 'sstr' else ann[a].__name__) for a in args[:nargs]]
c_args = ['{} [{}]'.format(a,get_type_from_ann(a)) for a in args[:nargs]]
c_kwargs = ['"{}" [{}={!r}{}]'.format(
a, type(dfls[n]).__name__, dfls[n],
(' ' + ann[a] if a in ann else ''))
@ -85,14 +88,14 @@ def _usage(cmd=None,exit_val=1):
for bc in MMGenToolCmd.__bases__:
cls_info = bc.__doc__.strip().split('\n')[0]
Msg(' {}{}\n'.format(cls_info[0].upper(),cls_info[1:]))
ucmds = bc.user_commands()
ucmds = bc._user_commands()
max_w = max(map(len,ucmds))
for cmd in ucmds:
if getattr(MMGenToolCmd,cmd).__doc__:
Msg(' {:{w}} {}'.format(cmd,_create_call_sig(cmd),w=max_w))
Msg('')
Msg(m2)
elif cmd in MMGenToolCmd.user_commands():
elif cmd in MMGenToolCmd._user_commands():
msg('USAGE: {} {} {}'.format(g.prog_name,cmd,_create_call_sig(cmd)))
else:
die(1,"'{}': no such tool command".format(cmd))
@ -101,6 +104,7 @@ def _usage(cmd=None,exit_val=1):
def _process_args(cmd,cmd_args):
c_args,c_kwargs,flag = _create_call_sig(cmd,parsed=True)
have_stdin_input = False
if flag != 'VAR_ARGS':
if len(cmd_args) < len(c_args):
@ -115,7 +119,14 @@ def _process_args(cmd,cmd_args):
if sys.stdin.isatty():
raise BadFilename("Standard input is a TTY. Can't use '-' as a filename")
else:
u_args[0] = sys.stdin.read().strip()
max_dlen_spec = '10kB' # limit input to 10KB for now
max_dlen = MMGenToolCmdUtil().bytespec(max_dlen_spec)
u_args[0] = os.read(0,max_dlen)
# try: u_args[0] = u_args[0].decode()
# except: pass
have_stdin_input = True
if len(u_args[0]) >= max_dlen:
die(2,'Maximum data input for this command is {}'.format(max_dlen_spec))
if not u_args[0]:
die(2,'{}: ERROR: no output from previous command in pipe'.format(cmd))
@ -144,13 +155,19 @@ def _process_args(cmd,cmd_args):
_usage(cmd)
def conv_type(arg,arg_name,arg_type):
if arg_type == 'bytes': pdie(arg,arg_name,arg_type)
if arg_type == 'bytes' and type(arg) != bytes:
die(1,"'Binary input data must be supplied via STDIN")
if have_stdin_input and arg_type == 'str' and type(arg) == bytes:
arg = arg.decode().rstrip('\n')
if arg_type == 'bool':
if arg.lower() in ('true','yes','1','on'): arg = True
elif arg.lower() in ('false','no','0','off'): arg = False
else:
msg("'{}': invalid boolean value for keyword argument".format(arg))
_usage(cmd)
try:
return __builtins__[arg_type](arg)
except:
@ -164,24 +181,31 @@ def _process_args(cmd,cmd_args):
return args,kwargs
def _process_result(ret,to_screen=False,pager=False): # returns a string or string subclass
do_ret = not to_screen
def _process_result(ret,pager=False,print_result=False):
"""
Convert result to something suitable for output to screen and return it.
If result is bytes and not convertible to utf8, output as binary using os.write().
If 'print_result' is True, send the converted result directly to screen or
pager instead of returning it.
"""
def triage_result(o):
return o if not print_result else do_pager(o) if pager else Msg(o)
if issubclass(type(ret),str):
return ret if do_ret else do_pager(ret) if pager else Msg(ret)
return triage_result(ret)
elif issubclass(type(ret),int):
return triage_result(str(ret))
elif type(ret) == tuple:
o = '\n'.join([r.decode() if issubclass(type(r),bytes) else r for r in ret])
return o if do_ret else do_pager(o) if pager else Msg(o)
return triage_result('\n'.join([r.decode() if issubclass(type(r),bytes) else r for r in ret]))
elif issubclass(type(ret),bytes):
if do_ret:
try: return ret.decode()
except: return repr(ret)
else:
try:
o = ret.decode()
do_pager(o) if pager else Msg(o)
except: os.write(1,ret)
try:
o = ret.decode()
return o if not print_result else do_pager(o) if pager else Msg(o)
except:
# don't add NL to binary data if it can't be converted to utf8
return ret if not print_result else os.write(1,ret)
elif ret == True:
if do_ret: return ''
return True
elif ret in (False,None):
ydie(1,"tool command returned '{}'".format(ret))
else:
@ -202,7 +226,7 @@ dfl_wl_id = 'electrum'
class MMGenToolCmdBase(object):
@classmethod
def user_commands(cls):
def _user_commands(cls):
return [e for e in dir(cls) if e[0] != '_' and getattr(cls,e).__doc__]
@ -220,7 +244,7 @@ class MMGenToolCmdUtil(MMGenToolCmdBase):
def bytespec(self,dd_style_byte_specifier:str):
"convert a byte specifier such as '1GB' into an integer"
return str(parse_bytespec(dd_style_byte_specifier))
return parse_bytespec(dd_style_byte_specifier)
def randhex(self,nbytes='32'):
"print 'n' bytes (default 32) of random data in hex format"
@ -230,18 +254,22 @@ class MMGenToolCmdUtil(MMGenToolCmdBase):
"reverse bytes of a hexadecimal string"
return hexlify(unhexlify(hexstr.strip())[::-1])
def hexlify(self,hexstr:'sstr'):
"display string in hexadecimal format"
return hexlify(hexstr.encode())
def hexlify(self,infile:str):
"convert bytes in file to hexadecimal (use '-' for stdin)"
data = get_data_from_file(infile,dash=True,silent=True,binary=True)
return hexlify(data)
def unhexlify(self,hexstr:'sstr'):
"convert hexadecimal value to bytes (warning: outputs binary data)"
return unhexlify(hexstr.encode())
def hexdump(self,infile:str,cols=8,line_nums=True):
"encode data into formatted hexadecimal form (file or stdin)"
return pretty_hexdump(
get_data_from_file(infile,dash=True,silent=True,binary=True),
cols=cols,line_nums=line_nums)
"create hexdump of data from file (use '-' for stdin)"
data = get_data_from_file(infile,dash=True,silent=True,binary=True)
return pretty_hexdump(data,cols=cols,line_nums=line_nums).rstrip()
def unhexdump(self,infile:str):
"decode formatted hexadecimal data (file or stdin)"
"decode hexdump from file (use '-' for stdin) (warning: outputs binary data)"
if g.platform == 'win':
import msvcrt
msvcrt.setmode(sys.stdout.fileno(),os.O_BINARY)
@ -258,10 +286,10 @@ class MMGenToolCmdUtil(MMGenToolCmdBase):
if file_input: b = get_data_from_file(string_or_bytes,binary=True)
elif hex_input: b = decode_pretty_hexdump(string_or_bytes)
else: b = string_or_bytes
return sha256(sha256(b.encode()).digest()).hexdigest()
return sha256(sha256(b.encode()).digest()).hexdigest().encode()
def id6(self,infile:str):
"generate 6-character MMGen ID for a file (or stdin)"
"generate 6-character MMGen ID for a file (use '-' for stdin)"
return make_chksum_6(
get_data_from_file(infile,dash=True,silent=True,binary=True))
@ -270,30 +298,30 @@ class MMGenToolCmdUtil(MMGenToolCmdBase):
return make_chksum_6(''.join(string.split()))
def id8(self,infile:str):
"generate 8-character MMGen ID for a file (or stdin)"
"generate 8-character MMGen ID for a file (use '-' for stdin)"
return make_chksum_8(
get_data_from_file(infile,dash=True,silent=True,binary=True))
def b58randenc(self):
"generate a random 32-byte number and convert it to base 58"
r = get_random(32)
return baseconv.b58encode(r,pad=True)
def randb58(self,nbytes=32,pad=True):
"generate random data (default: 32 bytes) and convert it to base 58"
return baseconv.b58encode(get_random(nbytes),pad=pad)
def strtob58(self,string:'sstr',pad=0):
"convert a string to base 58"
return baseconv.fromhex(hexlify(string.encode()),'b58',pad,tostr=True)
def bytestob58(self,infile:str,pad=0):
"convert bytes to base 58 (supply data via STDIN)"
data = get_data_from_file(infile,dash=True,silent=True,binary=True)
return baseconv.fromhex(hexlify(data),'b58',pad=pad,tostr=True)
def b58tostr(self,b58num:'sstr'):
"convert a base 58 number to a string"
return unhexlify(baseconv.tohex(b58num,'b58'))
def b58tobytes(self,b58num:'sstr',pad=0):
"convert a base 58 number to bytes (warning: outputs binary data)"
return unhexlify(baseconv.tohex(b58num,'b58',pad=pad))
def hextob58(self,hexstr:'sstr',pad=0):
"convert a hexadecimal number to base 58"
return baseconv.fromhex(hexstr.encode(),'b58',pad,tostr=True)
return baseconv.fromhex(hexstr.encode(),'b58',pad=pad,tostr=True)
def b58tohex(self,b58num:'sstr',pad=0):
"convert a base 58 number to hexadecimal"
return baseconv.tohex(b58num,'b58',pad)
return baseconv.tohex(b58num,'b58',pad=pad)
def hextob58chk(self,hexstr:'sstr'):
"convert a hexadecimal number to base58-check encoding"
@ -306,11 +334,11 @@ class MMGenToolCmdUtil(MMGenToolCmdBase):
return _b58chk_decode(b58chk_num)
def hextob32(self,hexstr:'sstr',pad=0):
"convert a hexadecimal number to base 32"
"convert a hexadecimal number to MMGen's flavor of base 32"
return baseconv.fromhex(hexstr.encode(),'b32',pad,tostr=True)
def b32tohex(self,b32num:'sstr',pad=0):
"convert a base 32 number to hexadecimal"
"convert an MMGen-flavor base 32 number to hexadecimal"
return baseconv.tohex(b32num.upper(),'b32',pad)
class MMGenToolCmdCoin(MMGenToolCmdBase):
@ -353,12 +381,14 @@ class MMGenToolCmdCoin(MMGenToolCmdBase):
def wif2redeem_script(self,wifkey:'sstr'): # new
"convert a WIF private key to a Segwit P2SH-P2WPKH redeem script"
assert opt.type == 'segwit','This command is meaningful only for --type=segwit'
init_generators()
privhex = PrivKey(wif=wifkey)
return ag.to_segwit_redeem_script(kg.to_pubhex(privhex))
def wif2segwit_pair(self,wifkey:'sstr'):
"generate both a Segwit P2SH-P2WPKH redeem script and address from WIF"
assert opt.type == 'segwit','This command is meaningful only for --type=segwit'
init_generators()
pubhex = kg.to_pubhex(PrivKey(wif=wifkey))
addr = ag.to_addr(pubhex)
@ -378,10 +408,14 @@ class MMGenToolCmdCoin(MMGenToolCmdBase):
def pubhex2addr(self,pubkeyhex:'sstr'):
"convert a hex pubkey to an address"
return self.pubhash2addr(hash160(pubkeyhex.encode()).decode())
if opt.type == 'segwit':
return g.proto.pubhex2segwitaddr(pubkeyhex.encode())
else:
return self.pubhash2addr(hash160(pubkeyhex.encode()).decode())
def pubhex2redeem_script(self,pubkeyhex:'sstr'): # new
"convert a hex pubkey to a Segwit P2SH-P2WPKH redeem script"
assert opt.type == 'segwit','This command is meaningful only for --type=segwit'
return g.proto.pubhex2redeem_script(pubkeyhex)
def pubhash2addr(self,pubhashhex:'sstr'):
@ -392,8 +426,8 @@ class MMGenToolCmdCoin(MMGenToolCmdBase):
init_generators('at')
return g.proto.pubhash2addr(pubhashhex.encode(),at.addr_fmt=='p2sh')
def addr2hexaddr(self,addr:'sstr'):
"convert coin address from base58 to hex format"
def addr2pubhash(self,addr:'sstr'):
"convert coin address to public key hash"
return g.proto.verify_addr(addr,CoinAddr.hex_width,return_dict=True)['hex']
class MMGenToolCmdMnemonic(MMGenToolCmdBase):
@ -403,14 +437,11 @@ class MMGenToolCmdMnemonic(MMGenToolCmdBase):
IMPORTANT NOTE: Though MMGen mnemonics use the Electrum wordlist, they're
computed using a different algorithm and are NOT Electrum-compatible!
"""
def _do_random_mn(self,nbytes:int,wordlist:str):
def _do_random_mn(self,nbytes:int,wordlist_id:str):
assert nbytes in (16,24,32), 'nbytes must be 16, 24 or 32'
hexrand = hexlify(get_random(nbytes))
Vmsg('Seed: {}'.format(hexrand))
for wl_id in ([wordlist],wordlists)[wordlist=='all']:
if wordlist == 'all': # TODO
Msg('{} mnemonic:'.format(capfirst(wl_id)))
mn = baseconv.fromhex(hexrand,wl_id)
return ' '.join(mn)
return self.hex2mn(hexrand,wordlist_id=wordlist_id)
def mn_rand128(self,wordlist=dfl_wl_id):
"generate random 128-bit mnemonic"
@ -424,13 +455,19 @@ class MMGenToolCmdMnemonic(MMGenToolCmdBase):
"generate random 256-bit mnemonic"
return self._do_random_mn(32,wordlist)
def hex2mn(self,hexstr:'sstr',wordlist=dfl_wl_id):
def hex2mn(self,hexstr:'sstr',wordlist_id=dfl_wl_id):
"convert a 16, 24 or 32-byte hexadecimal number to a mnemonic"
return ' '.join(baseconv.fromhex(hexstr.encode(),wordlist))
opt.out_fmt = 'words'
from mmgen.seed import SeedSource
s = SeedSource(seed=unhexlify(hexstr))
s._format()
return ' '.join(s.ssdata.mnemonic)
def mn2hex(self,seed_mnemonic:'sstr',wordlist=dfl_wl_id):
"convert a 12, 18 or 24-word mnemonic to a hexadecimal number"
return baseconv.tohex(seed_mnemonic.split(),wordlist)
opt.quiet = True
from mmgen.seed import SeedSource
return SeedSource(in_data=seed_mnemonic,in_fmt='words').seed.hexdata
def mn_stats(self,wordlist=dfl_wl_id):
"show stats for mnemonic wordlist"
@ -448,11 +485,15 @@ class MMGenToolCmdFile(MMGenToolCmdBase):
def addrfile_chksum(self,mmgen_addrfile:str):
"compute checksum for MMGen address file"
opt.yes = True
opt.quiet = True
from mmgen.addr import AddrList
return AddrList(mmgen_addrfile).chksum
def keyaddrfile_chksum(self,mmgen_keyaddrfile:str):
"compute checksum for MMGen key-address file"
opt.yes = True
opt.quiet = True
from mmgen.addr import KeyAddrList
return KeyAddrList(mmgen_keyaddrfile).chksum

View file

@ -309,7 +309,8 @@ class baseconv(object):
if pad:
assert type(pad) == bool, "'pad' must be boolean type"
d = dict(pad_map)
assert len(s) in d, 'Invalid data length for b58{}code(pad=True)'.format(op)
m = 'Invalid data length for b58{}code(pad=True) (must be one of {})'
assert len(s) in d, m.format(op,repr([e[0] for e in pad_map]))
return d[len(s)]
else:
return None
@ -349,7 +350,7 @@ class baseconv(object):
deconv = [wl.index(words[::-1][i])*(base**i) for i in range(len(words))]
ret = ('{:0{w}x}'.format(sum(deconv),w=pad or 0))
return ('','0')[len(ret) % 2] + ret
return (('','0')[len(ret) % 2] + ret).encode() # return bytes, for consistency with hexlify()
@classmethod
def fromhex(cls,hexnum,wl_id,pad=None,tostr=False):
@ -465,11 +466,10 @@ def compare_or_die(val1, desc1, val2, desc2, e='Error'):
def open_file_or_exit(filename,mode,silent=False):
try:
f = open(filename, mode)
return open(filename, mode)
except:
op = ('writing','reading')['r' in mode]
die(2,("Unable to open file '{}' for {}".format(filename,op),'')[silent])
return f
def check_file_type_and_access(fname,ftype,blkdev_ok=False):
@ -681,7 +681,7 @@ def mmgen_decrypt_file_maybe(fn,desc='',silent=False):
have_enc_ext = get_extension(fn) == g.mmenc_ext
if have_enc_ext or not is_utf8(d):
m = ('Attempting to decrypt','Decrypting')[have_enc_ext]
msg("{} {} '{}'".format(m,desc,fn))
qmsg("{} {} '{}'".format(m,desc,fn))
from mmgen.crypto import mmgen_decrypt_retry
d = mmgen_decrypt_retry(d,desc)
return d
@ -699,18 +699,21 @@ def get_data_from_user(desc='data',silent=False): # user input MUST be UTF-8
dmsg('User input: [{}]'.format(data))
return data
def get_data_from_file(infile,desc='data',dash=False,silent=False,binary=False,require_utf8=False):
if dash and infile == '-': return sys.stdin.read()
def get_data_from_file(infile,desc='data',dash=False,silent=False,binary=False):
if not opt.quiet and not silent and desc:
qmsg("Getting {} from file '{}'".format(desc,infile))
f = open_file_or_exit(infile,('r','rb')[bool(binary)],silent=silent)
data = f.read()
f.close()
if require_utf8:
try:
if binary: data = data.decode()
else: data.encode()
except: die(1,'{} data must be UTF-8 encoded.'.format(capfirst(desc)))
mode = ('r','rb')[bool(binary)]
if dash and infile == '-':
data = os.fdopen(0,mode).read(g.max_input_size+1)
else:
data = open_file_or_exit(infile,mode,silent=silent).read(g.max_input_size+1)
if len(data) == g.max_input_size + 1:
raise MaxInputSizeExceeded('Too much input data! Max input data size: {} bytes'.format(g.max_input_size))
return data
def pwfile_reuse_warning():

View file

@ -331,28 +331,27 @@ f_ltc_rt='Regtest (Bob and Alice) mode tests for LTC completed'
i_tool2='Tooltest2'
s_tool2="The following tests will run '$tooltest2_py' for all supported coins"
t_tool2=(
"$tooltest2_py --quiet --non-coin-dependent"
"$tooltest2_py --quiet --coin=btc --coin-dependent"
"$tooltest2_py --quiet --coin=btc --testnet=1 --coin-dependent"
"$tooltest2_py --quiet --coin=ltc --coin-dependent"
"$tooltest2_py --quiet --coin=ltc --testnet=1 --coin-dependent"
"$tooltest2_py --quiet --coin=bch --coin-dependent"
"$tooltest2_py --quiet --coin=bch --testnet=1 --coin-dependent"
"$tooltest2_py --quiet --coin=zec --coin-dependent"
"$tooltest2_py --quiet --coin=zec --type=zcash_z --coin-dependent"
"$tooltest2_py --quiet --coin=xmr --coin-dependent"
"$tooltest2_py --quiet --coin=dash --coin-dependent"
"$tooltest2_py --quiet --coin=eth --coin-dependent"
"$tooltest2_py --quiet --coin=eth --testnet=1 --coin-dependent"
"$tooltest2_py --quiet --coin=eth --token=mm1 --coin-dependent"
"$tooltest2_py --quiet --coin=eth --token=mm1 --testnet=1 --coin-dependent"
"$tooltest2_py --quiet --coin=etc --coin-dependent")
"$tooltest2_py --quiet"
"$tooltest2_py --quiet --coin=btc"
"$tooltest2_py --quiet --coin=btc --testnet=1"
"$tooltest2_py --quiet --coin=ltc"
"$tooltest2_py --quiet --coin=ltc --testnet=1"
"$tooltest2_py --quiet --coin=bch"
"$tooltest2_py --quiet --coin=bch --testnet=1"
"$tooltest2_py --quiet --coin=zec"
"$tooltest2_py --quiet --coin=zec --type=zcash_z"
"$tooltest2_py --quiet --coin=xmr"
"$tooltest2_py --quiet --coin=dash"
"$tooltest2_py --quiet --coin=eth"
"$tooltest2_py --quiet --coin=eth --testnet=1"
"$tooltest2_py --quiet --coin=eth --token=mm1"
"$tooltest2_py --quiet --coin=eth --token=mm1 --testnet=1"
"$tooltest2_py --quiet --coin=etc")
f_tool2='tooltest2 tests completed'
i_tool='Tooltest'
s_tool="The following tests will run '$tooltest_py' for all supported coins"
t_tool=(
"$tooltest_py --coin=btc util"
"$tooltest_py --coin=btc cryptocoin"
"$tooltest_py --coin=btc mnemonic"
"$tooltest_py --coin=ltc cryptocoin"

View file

@ -17,9 +17,14 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
common.py: Shared routines for the test suites
common.py: Shared routines and data for the MMGen test suites
"""
sample_text = 'The Times 03/Jan/2009 Chancellor on brink of second bailout for banks'
ref_kafile_pass = 'kafile password'
ref_kafile_hash_preset = '1'
class TestSuiteException(Exception): pass
class TestSuiteFatalException(Exception): pass

View file

@ -68,10 +68,11 @@ class MMGenPexpect(object):
self.req_exit_val = 0
self.skip_ok = False
def do_decrypt_ka_data(self,hp,pw,desc='key-address data',check=True):
self.hash_preset(desc,hp)
def do_decrypt_ka_data(self,hp,pw,desc='key-address data',check=True,have_yes_opt=False):
# self.hash_preset(desc,hp)
self.passphrase(desc,pw)
self.expect('Check key-to-address validity? (y/N): ',('n','y')[check])
if not have_yes_opt:
self.expect('Check key-to-address validity? (y/N): ',('n','y')[check])
def view_tx(self,view):
self.expect('View.* transaction.*\? .*: ',view,regex=True)

View file

@ -0,0 +1 @@
kafile password

View file

@ -62,12 +62,8 @@ ref_bw_hash_preset = '1'
ref_bw_file = 'wallet.mmbrain'
ref_bw_file_spc = 'wallet-spaced.mmbrain'
ref_kafile_pass = 'kafile password'
ref_kafile_hash_preset = '1'
ref_enc_fn = 'sample-text.mmenc'
tool_enc_passwd = "Scrypt it, don't hash it!"
sample_text = 'The Times 03/Jan/2009 Chancellor on brink of second bailout for banks\n'
chksum_pat = r'\b[A-F0-9]{4} [A-F0-9]{4} [A-F0-9]{4} [A-F0-9]{4}\b'
def ok_msg():

View file

@ -362,7 +362,7 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
return t
def txcreate1(self):
# valid_keypresses = 'adrMmeqpvwl'
# valid_keypresses = EthereumTwUnspentOutputs.key_mappings.keys()
menu = ['a','d','r','M','D','e','m','m'] # include one invalid keypress, 'D'
args = ['98831F3A:E:1,123.456']
return self.txcreate(args=args,menu=menu,acct='1',non_mmgen_inputs=1)

View file

@ -277,7 +277,7 @@ class TestSuiteMain(TestSuiteBase,TestSuiteShared):
out = []
for d in tx_data.values():
al = adata.addrlist(d['al_id'])
al = adata.addrlist(al_id=d['al_id'])
for n,(idx,coinaddr) in enumerate(al.addrpairs()):
lbl = get_label(do_shuffle=True)
out.append(self._create_fake_unspent_entry(coinaddr,d['al_id'],idx,lbl,segwit=d['segwit']))
@ -556,7 +556,7 @@ class TestSuiteMain(TestSuiteBase,TestSuiteShared):
args=['-H','{},{}'.format(rf,hincog_offset),'-l',str(hincog_seedlen)])
def txsign_keyaddr(self,keyaddr_file,txfile):
t = self.spawn('mmgen-txsign', ['-d',self.tmpdir,'-M',keyaddr_file,txfile])
t = self.spawn('mmgen-txsign', ['-d',self.tmpdir,'-p1','-M',keyaddr_file,txfile])
t.license()
t.do_decrypt_ka_data(hp='1',pw=self.kapasswd)
t.view_tx('n')

View file

@ -108,9 +108,9 @@ class TestSuiteRef(TestSuiteBase,TestSuiteShared):
af = joinpath(ref_dir,(subdir or self.ref_subdir,'')[ftype=='passwd'],af_fn)
coin_arg = [] if coin == None else ['--coin='+coin]
tool_cmd = ftype.replace('segwit','').replace('bech32','')+'file_chksum'
t = self.spawn('mmgen-tool',coin_arg+[tool_cmd,af]+add_args)
t = self.spawn('mmgen-tool',coin_arg+['-p1',tool_cmd,af]+add_args)
if ftype == 'keyaddr':
t.do_decrypt_ka_data(hp=ref_kafile_hash_preset,pw=ref_kafile_pass)
t.do_decrypt_ka_data(hp=ref_kafile_hash_preset,pw=ref_kafile_pass,have_yes_opt=True)
rc = self.chk_data[ 'ref_' + ftype + 'file_chksum' +
('_'+coin.lower() if coin else '') +
('_'+mmtype if mmtype else '')]
@ -161,5 +161,5 @@ class TestSuiteRef(TestSuiteBase,TestSuiteShared):
t.written_to_file('Decrypted data')
dec_txt = read_from_file(dec_file)
imsg_r(dec_txt)
cmp_or_die(sample_text,dec_txt)
cmp_or_die(sample_text+'\n',dec_txt) # file adds a newline to sample_text
return t

View file

@ -58,30 +58,6 @@ cmd_args = opts.init(opts_data,add_opts=['exact_output','profile'])
from collections import OrderedDict
cmd_data = OrderedDict([
('util', {
'desc': 'base conversion, hashing and file utilities',
'cmd_data': OrderedDict([
('strtob58', ()),
('b58tostr', ('strtob58','io')),
('hextob58', ()),
('b58tohex', ('hextob58','io')),
('b58randenc', ()),
('hextob32', ()),
('b32tohex', ('hextob32','io')),
('randhex', ()),
('id6', ()),
('id8', ()),
('str2id6', ()),
('hash160', ()),
('hash256', ()),
('hexreverse', ()),
('hexlify', ()),
('hexdump', ()),
('unhexdump', ('hexdump','io')),
('rand2file', ()),
])
}
),
('cryptocoin', {
'desc': 'Cryptocoin address/key commands',
'cmd_data': OrderedDict([
@ -93,9 +69,9 @@ cmd_data = OrderedDict([
('privhex2pubhex', ('wif2hex','o3')), # segwit only
('pubhex2addr', ('privhex2pubhex','o3')), # segwit only
('hex2wif', ('wif2hex','io2')), # uncomp, comp
('addr2hexaddr', ('randpair','o4'))] + # uncomp, comp, bech32
('addr2pubhash', ('randpair','o4'))] + # uncomp, comp, bech32
([],[
('pubhash2addr', ('addr2hexaddr','io4')) # uncomp, comp, bech32
('pubhash2addr', ('addr2pubhash','io4')) # uncomp, comp, bech32
])[opt.type != 'zcash_z'] +
([],[
('pubhex2redeem_script', ('privhex2pubhex','o3')),
@ -196,7 +172,7 @@ if opt.list_names:
ignore = ()
from mmgen.tool import MMGenToolCmd
uc = sorted(
set(MMGenToolCmd.user_commands()) -
set(MMGenToolCmd._user_commands()) -
set(ignore) -
set(tested_in['tooltest.py']) -
set(tested_in['tooltest2.py']) -
@ -313,47 +289,6 @@ def ok_or_die(val,chk_func,s,skip_ok=False):
class MMGenToolTestCmds(object):
# Util
def strtob58(self,name): tu.run_cmd_out(name,getrandstr(16))
def b58tostr(self,name,f1,f2): tu.run_cmd_chk(name,f1,f2)
def hextob58(self,name): tu.run_cmd_out(name,getrandhex(32))
def b58tohex(self,name,f1,f2): tu.run_cmd_chk(name,f1,f2,strip_hex=True)
def b58randenc(self,name):
ret = tu.run_cmd_out(name,Return=True)
ok_or_die(ret,is_b58_str,'base 58 string')
def hextob32(self,name): tu.run_cmd_out(name,getrandhex(24))
def b32tohex(self,name,f1,f2): tu.run_cmd_chk(name,f1,f2,strip_hex=True)
def randhex(self,name):
ret = tu.run_cmd_out(name,Return=True)
ok_or_die(ret,binascii.unhexlify,'hex string')
def id6(self,name): tu.run_cmd_randinput(name)
def id8(self,name): tu.run_cmd_randinput(name)
def str2id6(self,name):
s = getrandstr(120,no_space=True)
s2 = ' {} {} {} {} {} '.format(s[:3],s[3:9],s[9:29],s[29:50],s[50:120])
ret1 = tu.run_cmd(name,[s],extra_msg='unspaced input'); ok()
ret2 = tu.run_cmd(name,[s2],extra_msg='spaced input')
cmp_or_die(ret1,ret2)
vmsg('Returned: {}'.format(ret1))
ok()
def hash160(self,name): tu.run_cmd_out(name,getrandhex(16))
def hash256(self,name): tu.run_cmd_out(name,getrandstr(16))
def hexreverse(self,name): tu.run_cmd_out(name,getrandhex(24))
def hexlify(self,name): tu.run_cmd_out(name,getrandstr(24))
def hexdump(self,name): tu.run_cmd_randinput(name,strip=False)
def unhexdump(self,name,fn1,fn2):
ret = tu.run_cmd(name,[fn2],strip=False,binary=True)
orig = read_from_file(fn1,binary=True)
cmp_or_die(orig,ret)
ok()
def rand2file(self,name):
of = name + '.out'
dlen = 1024
tu.run_cmd(name,[of,str(1024),'threads=4','silent=1'],strip=False)
d = read_from_tmpfile(cfg,of,binary=True)
cmp_or_die(dlen,len(d))
ok()
# Cryptocoin
def randwif(self,name):
for n,k in enumerate(['',maybe_compressed]):
@ -398,7 +333,7 @@ class MMGenToolTestCmds(object):
for n,fi,fo,k in ((1,f1,f2,''),(2,f3,f4,maybe_compressed)):
ao = ['--type='+k] if k else []
ret = tu.run_cmd_chk(name,fi,fo,add_opts=ao)
def addr2hexaddr(self,name,f1,f2,f3,f4):
def addr2pubhash(self,name,f1,f2,f3,f4):
for n,f,m,ao in (
(1,f1,'',[]),
(2,f2,'from {}'.format(maybe_compressed),[]),
@ -418,9 +353,12 @@ class MMGenToolTestCmds(object):
tu.run_cmd_out(name,addr,add_opts=maybe_type_compressed,fn_idx=3) # what about uncompressed?
def pubhex2redeem_script(self,name,f1,f2,f3): # from above
addr = read_from_file(f3).strip()
tu.run_cmd_out(name,addr,fn_idx=3)
rs = read_from_tmpfile(cfg,name+'3.out').strip()
tu.run_cmd_out(name,addr,add_opts=maybe_type_segwit,fn_idx=3)
type_save = opt.type
opt.type = 'segwit'
rs = read_from_tmpfile(cfg,'privhex2pubhex3.out').strip()
tu.run_cmd_out('pubhex2addr',rs,add_opts=maybe_type_segwit,fn_idx=3,hush=True)
opt.type = type_save
addr1 = read_from_tmpfile(cfg,'pubhex2addr3.out').strip()
addr2 = read_from_tmpfile(cfg,'randpair3.out').split()[1]
cmp_or_die(addr1,addr2)
@ -440,20 +378,22 @@ class MMGenToolTestCmds(object):
def pipetest(self,name,f1,f2,f3):
wif = read_from_file(f3).split()[0]
cmd = ( '{c} {a} wif2hex {wif} | ' +
'{c} {a} --type=compressed privhex2pubhex - | ' +
'{c} {a} pubhex2redeem_script - | ' +
'{c} {a} --type=segwit pubhex2addr -').format(
cmd = ( '{c} {a} wif2hex {wif}' +
' | {c} {a} --type=compressed privhex2pubhex -' +
' | {c} {a} --type=segwit pubhex2redeem_script -' +
' | {c} {a} hash160 -' +
' | {c} {a} --type=segwit pubhash2addr -').format(
c=' '.join(spawn_cmd),
a=' '.join(add_spawn_args),
wif=wif)
test_msg('command piping')
if opt.verbose:
sys.stderr.write(green('Executing ') + cyan(cmd) + '\n')
p = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE,shell=True)
p = subprocess.Popen(cmd,stdout=subprocess.PIPE,shell=True)
res = p.stdout.read().decode().strip()
p.wait()
addr = read_from_tmpfile(cfg,'wif2addr3.out').strip()
cmp_or_die(res,addr)
cmp_or_die(addr,res)
ok()
# Mnemonic

View file

@ -36,6 +36,9 @@ os.environ['MMGEN_TEST_SUITE'] = '1'
from mmgen.common import *
from test.common import *
from mmgen.obj import is_wif,is_coin_addr
from mmgen.seed import is_mnemonic
def is_str(s): return type(s) == str
opts_data = lambda: {
'desc': "Simple test suite for the 'mmgen-tool' utility",
@ -43,10 +46,9 @@ opts_data = lambda: {
'options': """
-h, --help Print this help message
-C, --coverage Produce code coverage info using trace module
-d, --coin-dependent Run only coin-dependent tests
-D, --non-coin-dependent Run only non-coin-dependent tests
-d, --die-on-missing Abort if no test data found for given command
--, --longhelp Print help message for long options (common options)
-l, --list-tests List the tests in this test suite
-l, --list-tests List the test groups in this test suite
-L, --list-tested-cmds Output the 'mmgen-tool' commands that are tested by this test suite
-n, --names Print command names instead of descriptions
-q, --quiet Produce quieter output
@ -63,170 +65,480 @@ If no command is given, the whole suite of tests is run.
"""
}
tests = (
('util', 'base conversion, hashing and file utilities',
(
('b58chktohex','conversion from base58chk to hex', [
( ['eFGDJPketnz'], 'deadbeef' ),
( ['5CizhNNRPYpBjrbYX'], 'deadbeefdeadbeef' ),
( ['5qCHTcgbQwprzjWrb'], 'ffffffffffffffff' ),
( ['111111114FCKVB'], '0000000000000000' ),
( ['3QJmnh'], '' ),
( ['1111111111111111111114oLvT2'], '000000000000000000000000000000000000000000' ),
]),
('hextob58chk','conversion from hex to base58chk', [
( ['deadbeef'], 'eFGDJPketnz' ),
( ['deadbeefdeadbeef'], '5CizhNNRPYpBjrbYX' ),
( ['ffffffffffffffff'], '5qCHTcgbQwprzjWrb' ),
( ['0000000000000000'], '111111114FCKVB' ),
( [''], '3QJmnh' ),
( ['000000000000000000000000000000000000000000'], '1111111111111111111114oLvT2' ),
]),
('bytespec',"conversion of 'dd'-style byte specifier to bytes", [
( ['1G'], str(1024*1024*1024) ),
( ['1234G'], str(1234*1024*1024*1024) ),
( ['1GB'], str(1000*1000*1000) ),
( ['1234GB'], str(1234*1000*1000*1000) ),
( ['1.234MB'], str(1234*1000) ),
( ['1.234567M'], str(int(Decimal('1.234567')*1024*1024)) ),
( ['1234'], str(1234) ),
]),
),
),
('wallet', 'MMGen wallet operations',
(
('gen_key','generation of single key from wallet', [
( ['98831F3A:11','wallet=test/ref/98831F3A.mmwords'],
'5JKLcdYbhP6QQ4BXc9HtjfqJ79FFRXP2SZTKUyEuyXJo9QSFUkv'
),
( ['98831F3A:C:11','wallet=test/ref/98831F3A.mmwords'],
'L2LwXv94XTU2HjCbJPXCFuaHjrjucGipWPWUi1hkM5EykgektyqR'
),
( ['98831F3A:B:11','wallet=test/ref/98831F3A.mmwords'],
'L2K4Y9MWb5oUfKKZtwdgCm6FLZdUiWJDHjh9BYxpEvtfcXt4iM5g'
),
( ['98831F3A:S:11','wallet=test/ref/98831F3A.mmwords'],
'KwmkkfC9GghnJhnKoRXRn5KwGCgXrCmDw6Uv83NzE4kJS5axCR9A'
),
]),
('gen_addr','generation of single address from wallet', [
( ['98831F3A:11','wallet=test/ref/98831F3A.mmwords'],
'12bYUGXS8SRArZneQDN9YEEYAtEa59Rykm'
),
( ['98831F3A:L:11','wallet=test/ref/98831F3A.mmwords'],
'12bYUGXS8SRArZneQDN9YEEYAtEa59Rykm'
),
( ['98831F3A:C:11','wallet=test/ref/98831F3A.mmwords'],
'1MPsZ7BY9qikqfPxqmrovE8gLDX2rYArZk'
),
( ['98831F3A:B:11','wallet=test/ref/98831F3A.mmwords'],
'bc1qxptlvmwaymaxa7pxkr2u5pn7c0508stcncv7ms'
),
( ['98831F3A:S:11','wallet=test/ref/98831F3A.mmwords'],
'3Eevao3DRVXnYym3tdrJDqS3Wc39PQzahn'
),
]),
),
),
('cryptocoin', 'coin-dependent utilities',
(
('randwif','random WIF key', {
'btc_mainnet': [ ( [], is_wif, ['-r0'] ) ],
'btc_testnet': [ ( [], is_wif, ['-r0'] ) ],
}),
('randpair','random key/address pair', {
'btc_mainnet': [ ( [], [is_wif,is_coin_addr], ['-r0'] ) ],
'btc_testnet': [ ( [], [is_wif,is_coin_addr], ['-r0'] ) ],
}),
('wif2addr','WIF-to-address conversion', {
'btc_mainnet': [
( ['5JKLcdYbhP6QQ4BXc9HtjfqJ79FFRXP2SZTKUyEuyXJo9QSFUkv'],
'12bYUGXS8SRArZneQDN9YEEYAtEa59Rykm', ['--type=legacy'], 'opt.type="legacy"' ),
( ['L2LwXv94XTU2HjCbJPXCFuaHjrjucGipWPWUi1hkM5EykgektyqR'],
'1MPsZ7BY9qikqfPxqmrovE8gLDX2rYArZk', ['--type=compressed'], 'opt.type="compressed"' ),
( ['KwmkkfC9GghnJhnKoRXRn5KwGCgXrCmDw6Uv83NzE4kJS5axCR9A'],
'3Eevao3DRVXnYym3tdrJDqS3Wc39PQzahn', ['--type=segwit'], 'opt.type="segwit"' ),
( ['L2K4Y9MWb5oUfKKZtwdgCm6FLZdUiWJDHjh9BYxpEvtfcXt4iM5g'],
'bc1qxptlvmwaymaxa7pxkr2u5pn7c0508stcncv7ms', ['--type=bech32'], 'opt.type="bech32"' ),
],
}),
),
),
# TODO: compressed address files are missing
# 'addrfile_compressed_chk': {
# 'btc': ('A33C 4FDE F515 F5BC','6C48 AA57 2056 C8C8'),
# 'ltc': ('3FC0 8F03 C2D6 BD19','4C0A 49B6 2DD1 1BE0'),
('file', 'Operations with MMGen files',
(
('addrfile_chksum','address file checksums', {
'btc_mainnet': [
( ['test/ref/98831F3A[1,31-33,500-501,1010-1011].addrs'],
'6FEF 6FB9 7B13 5D91'),
( ['test/ref/98831F3A-S[1,31-33,500-501,1010-1011].addrs'],
'06C1 9C87 F25C 4EE6'),
( ['test/ref/98831F3A-B[1,31-33,500-501,1010-1011].addrs'],
'9D2A D4B6 5117 F02E'),
],
'btc_testnet': [
( ['test/ref/98831F3A[1,31-33,500-501,1010-1011].testnet.addrs'],
'424E 4326 CFFE 5F51'),
( ['test/ref/98831F3A-S[1,31-33,500-501,1010-1011].testnet.addrs'],
'072C 8B07 2730 CB7A'),
( ['test/ref/98831F3A-B[1,31-33,500-501,1010-1011].testnet.addrs'],
'0527 9C39 6C1B E39A'),
],
'ltc_mainnet': [
( ['test/ref/litecoin/98831F3A-LTC[1,31-33,500-501,1010-1011].addrs'],
'AD52 C3FE 8924 AAF0'),
( ['test/ref/litecoin/98831F3A-LTC-S[1,31-33,500-501,1010-1011].addrs'],
'63DF E42A 0827 21C3'),
( ['test/ref/litecoin/98831F3A-LTC-B[1,31-33,500-501,1010-1011].addrs'],
'FF1C 7939 5967 AB82'),
],
'ltc_testnet': [
( ['test/ref/litecoin/98831F3A-LTC[1,31-33,500-501,1010-1011].testnet.addrs'],
'4EBE 2E85 E969 1B30'),
( ['test/ref/litecoin/98831F3A-LTC-S[1,31-33,500-501,1010-1011].testnet.addrs'],
'5DD1 D186 DBE1 59F2'),
( ['test/ref/litecoin/98831F3A-LTC-B[1,31-33,500-501,1010-1011].testnet.addrs'],
'ED3D 8AA4 BED4 0B40'),
],
'zec_mainnet': [
( ['test/ref/zcash/98831F3A-ZEC-C[1,31-33,500-501,1010-1011].addrs'],'903E 7225 DD86 6E01'), ],
'zec_z_mainnet': [
( ['test/ref/zcash/98831F3A-ZEC-Z[1,31-33,500-501,1010-1011].addrs'],'9C7A 72DC 3D4A B3AF'), ],
'xmr_mainnet': [
( ['test/ref/monero/98831F3A-XMR-M[1,31-33,500-501,1010-1011].addrs'],'4369 0253 AC2C 0E38'), ],
'dash_mainnet': [
( ['test/ref/dash/98831F3A-DASH-C[1,31-33,500-501,1010-1011].addrs'],'FBC1 6B6A 0988 4403'), ],
'eth_mainnet': [
( ['test/ref/ethereum/98831F3A-ETH[1,31-33,500-501,1010-1011].addrs'],'E554 076E 7AF6 66A3'), ],
'etc_mainnet': [
( ['test/ref/ethereum_classic/98831F3A-ETC[1,31-33,500-501,1010-1011].addrs'],
'E97A D796 B495 E8BC'), ],
}),
('txview','transaction file view', {
'btc_mainnet': [ ( ['test/ref/0B8D5A[15.31789,14,tl=1320969600].rawtx'], None ), ],
'btc_testnet': [ ( ['test/ref/0C7115[15.86255,14,tl=1320969600].testnet.rawtx'], None ), ],
'bch_mainnet': [ ( ['test/ref/460D4D-BCH[10.19764,tl=1320969600].rawtx'], None ), ],
'bch_testnet': [ ( ['test/ref/359FD5-BCH[6.68868,tl=1320969600].testnet.rawtx'], None ), ],
'ltc_mainnet': [ ( ['test/ref/litecoin/AF3CDF-LTC[620.76194,1453,tl=1320969600].rawtx'], None ), ],
'ltc_testnet': [ ( ['test/ref/litecoin/A5A1E0-LTC[1454.64322,1453,tl=1320969600].testnet.rawtx'],
None ), ],
'eth_mainnet': [ ( ['test/ref/ethereum/88FEFD-ETH[23.45495,40000].rawtx'], None ), ],
'eth_testnet': [ ( ['test/ref/ethereum/B472BD-ETH[23.45495,40000].testnet.rawtx'], None ), ],
'mm1_mainnet': [ ( ['test/ref/ethereum/5881D2-MM1[1.23456,50000].rawtx'], None ), ],
'mm1_testnet': [ ( ['test/ref/ethereum/6BDB25-MM1[1.23456,50000].testnet.rawtx'], None ), ],
'etc_mainnet': [ ( ['test/ref/ethereum_classic/ED3848-ETC[1.2345,40000].rawtx'], None ), ],
}),
),
),
)
sample_text_hexdump = (
'000000: 5468 6520 5469 6d65 7320 3033 2f4a 616e\n' +
'000010: 2f32 3030 3920 4368 616e 6365 6c6c 6f72\n' +
'000020: 206f 6e20 6272 696e 6b20 6f66 2073 6563\n' +
'000030: 6f6e 6420 6261 696c 6f75 7420 666f 7220\n' +
'000040: 6261 6e6b 73' )
def do_cmd(cdata):
cmd_name,desc,data = cdata
if type(data) == dict:
if opt.non_coin_dependent: return
kafile_opts = ['-p1','-Ptest/ref/keyaddrfile_password']
kafile_code = (
"\nopt.hash_preset = '1'" +
"\nopt.set_by_user = ['hash_preset']" +
"\nopt.use_old_ed25519 = None" +
"\nopt.passwd_file = 'test/ref/keyaddrfile_password'" )
tests = {
'Mnemonic': {
'hex2mn': [
( ['deadbeefdeadbeefdeadbeefdeadbeef'],
'table cast forgive master funny gaze sadness ripple million paint moral match' ),
( ['deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef'],
('swirl maybe anymore mix scale stray fog use approach page crime rhyme ' +
'class former strange window snap soon') ),
( ['deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef'],
('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'],
'yellow yeah show bowl season spider cling defeat poison law shelter reflect' ),
( ['ffffffffffffffffffffffffffffffffffffffffffffffff'],
('yeah youth quit fail perhaps drum out person young click skin ' +
'weird inside silently perfectly together anyone memory') ),
( ['ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'],
('wrote affection object cell opinion here laughter stare honest north cost begin ' +
'murder something yourself effort acid dot doubt game broke tell guilt innocent') ),
( ['0000000000000000000000000000000000000000000000000000000000000001'],
('able able able able able able able able able able able able ' +
'able able able able able able able able able able able about') ),
],
'mn2hex': [
( ['table cast forgive master funny gaze sadness ripple million paint moral match'],
'deadbeefdeadbeefdeadbeefdeadbeef' ),
( ['swirl maybe anymore mix scale stray fog use approach page crime rhyme ' +
'class former strange window snap soon'],
'deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef'),
( ['swell type milk figure cheese phone fill black test bloom heard comfort ' +
'image terrible radio lesson own reply battle goal goodbye need laugh stream'],
'deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef' ),
( ['yellow yeah show bowl season spider cling defeat poison law shelter reflect'],
'ffffffffffffffffffffffffffffffff' ),
( ['yeah youth quit fail perhaps drum out person young click skin ' +
'weird inside silently perfectly together anyone memory'],
'ffffffffffffffffffffffffffffffffffffffffffffffff' ) ,
( ['wrote affection object cell opinion here laughter stare honest north cost begin ' +
'murder something yourself effort acid dot doubt game broke tell guilt innocent'],
'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'),
( ['able able able able able able able able able able able able ' +
'able able able able able able able able able able able about'],
'0000000000000000000000000000000000000000000000000000000000000001'),
],
'mn_rand128': [ ( [], is_mnemonic, ['-r0']), ( ['wordlist=tirosh'], is_mnemonic, ['-r0']), ],
'mn_rand192': [ ( [], is_mnemonic, ['-r0']), ( ['wordlist=tirosh'], is_mnemonic, ['-r0']), ],
'mn_rand256': [ ( [], is_mnemonic, ['-r0']), ( ['wordlist=tirosh'], is_mnemonic, ['-r0']), ],
'mn_stats': [ ( [], is_str ), ( ['wordlist=tirosh'], is_str ), ],
'mn_printlist': [ ( [], is_str ), ( ['wordlist=tirosh'], is_str ), ],
},
'Util': {
'hextob32': [
( ['deadbeef'], 'DPK3PXP' ),
( ['deadbeefdeadbeef'], 'N5LN657PK3PXP' ),
( ['ffffffffffffffff'], 'P777777777777' ),
( ['0000000000000000'], '' ),
( ['0000000000000000','pad=10'], 'AAAAAAAAAA' ),
( ['ff','pad=10'], 'AAAAAAAAH7' ),
],
'b32tohex': [
( ['DPK3PXP'], 'deadbeef' ),
( ['N5LN657PK3PXP'], 'deadbeefdeadbeef' ),
( ['P777777777777'], 'ffffffffffffffff' ),
( ['','pad=16'], '0000000000000000' ),
( ['AAAAAAAAAA','pad=16'], '0000000000000000' ),
( ['AAAAAAAAH7','pad=2'], 'ff' ),
],
'hextob58chk': [
( ['deadbeef'], 'eFGDJPketnz' ),
( ['deadbeefdeadbeef'], '5CizhNNRPYpBjrbYX' ),
( ['ffffffffffffffff'], '5qCHTcgbQwprzjWrb' ),
( ['0000000000000000'], '111111114FCKVB' ),
( [''], '3QJmnh' ),
( ['000000000000000000000000000000000000000000'], '1111111111111111111114oLvT2' ),
],
'b58chktohex': [
( ['eFGDJPketnz'], 'deadbeef' ),
( ['5CizhNNRPYpBjrbYX'], 'deadbeefdeadbeef' ),
( ['5qCHTcgbQwprzjWrb'], 'ffffffffffffffff' ),
( ['111111114FCKVB'], '0000000000000000' ),
( ['3QJmnh'], '' ),
( ['1111111111111111111114oLvT2'], '000000000000000000000000000000000000000000' ),
],
'bytestob58': [
( [b'\xde\xad\xbe\xef'], '6h8cQN' ),
( [b'\xde\xad\xbe\xef\xde\xad\xbe\xef'], 'eFGDJURJykA' ),
( [b'\xff\xff\xff\xff\xff\xff\xff\xff'], 'jpXCZedGfVQ' ),
( [b'\x00\x00\x00\x00\x00\x00\x00\x00'], '' ),
( [b'\x00\x00\x00\x00\x00\x00\x00\x00','pad=10'], '1111111111' ),
( [b'\xff','pad=10'], '111111115Q' ),
],
'b58tobytes': [
( ['6h8cQN'], b'\xde\xad\xbe\xef' ),
( ['eFGDJURJykA'], b'\xde\xad\xbe\xef\xde\xad\xbe\xef' ),
( ['jpXCZedGfVQ'], b'\xff\xff\xff\xff\xff\xff\xff\xff' ),
( ['','pad=16'], b'\x00\x00\x00\x00\x00\x00\x00\x00' ),
( ['1111111111','pad=16'], b'\x00\x00\x00\x00\x00\x00\x00\x00' ),
( ['111111115Q','pad=2'], b'\xff' ),
],
'hextob58': [
( ['deadbeef'], '6h8cQN' ),
( ['deadbeefdeadbeef'], 'eFGDJURJykA' ),
( ['ffffffffffffffff'], 'jpXCZedGfVQ' ),
( ['0000000000000000'], '' ),
( ['0000000000000000','pad=10'], '1111111111' ),
( ['ff','pad=10'], '111111115Q' ),
],
'b58tohex': [
( ['6h8cQN'], 'deadbeef' ),
( ['eFGDJURJykA'], 'deadbeefdeadbeef' ),
( ['jpXCZedGfVQ'], 'ffffffffffffffff' ),
( ['','pad=16'], '0000000000000000' ),
( ['1111111111','pad=16'], '0000000000000000' ),
( ['111111115Q','pad=2'], 'ff' ),
],
'bytespec': [
( ['1G'], str(1024*1024*1024) ),
( ['1234G'], str(1234*1024*1024*1024) ),
( ['1GB'], str(1000*1000*1000) ),
( ['1234GB'], str(1234*1000*1000*1000) ),
( ['1.234MB'], str(1234*1000) ),
( ['1.234567M'], str(int(Decimal('1.234567')*1024*1024)) ),
( ['1234'], str(1234) ),
],
'hash160': [ # TODO: check that hextob58chk(hash160) = pubhex2addr
( ['deadbeef'], 'f04df4c4b30d2b7ac6e1ed2445aeb12a9cb4d2ec' ),
( ['000000000000000000000000000000000000000000'], '2db95e704e2d9b0474acf76182f3f985b7064a8a' ),
( [''], 'b472a266d0bd89c13706a4132ccfb16f7c3b9fcb' ),
( ['ffffffffffffffff'], 'f86221f5a1fca059a865c0b7d374dfa9d5f3aeb4' ),
],
'hash256': [
( ['deadbeef'], 'e107944e77a688feae4c2d4db5951923812dd0f72026a11168104ee1b248f8a9' ),
( ['000000000000000000000000000000000000000000'], 'fd5181fcd097a334ab340569e5edcd09f702fef7994abab01f4b66e86b32ebbe' ),
( [''], '5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456' ),
( ['ffffffffffffffff'], '57b2d2c3455e0f76c61c5237ff04fc9fc0f3fe691e587ea9c951949e1a5e0fed' ),
],
'hexdump': [
( [sample_text.encode()], sample_text_hexdump ),
],
'unhexdump': [
( [sample_text_hexdump.encode()], sample_text.encode() ),
],
'hexlify': [
( [b'foobar'], '666f6f626172' ),
],
'unhexlify': [
( ['666f6f626172'], 'foobar' ),
],
'hexreverse': [
( ['deadbeefcafe'], 'fecaefbeadde' ),
],
'id6': [
( [sample_text.encode()], 'a6d72b' ),
],
'id8': [
( [sample_text.encode()], '687C09C2' ),
],
'str2id6': [
( ['74ev zjeq Zw2g DspF RKpE 7H'], '70413d' ), # checked
],
'randhex': [
( [], {'boolfunc':is_hex_str,'len':64}, ['-r0'] ),
( ['nbytes=16'], {'boolfunc':is_hex_str,'len':32}, ['-r0'] ),
( ['nbytes=6'], {'boolfunc':is_hex_str,'len':12}, ['-r0'] ),
],
'randb58': [
( [], {'boolfunc':is_b58_str,'len':44}, ['-r0'] ),
( ['nbytes=16'], {'boolfunc':is_b58_str,'len':22}, ['-r0'] ),
( ['nbytes=12','pad=false'], is_b58_str, ['-r0'] ),
],
},
'Wallet': {
'gen_key': [
( ['98831F3A:11','wallet=test/ref/98831F3A.mmwords'],
'5JKLcdYbhP6QQ4BXc9HtjfqJ79FFRXP2SZTKUyEuyXJo9QSFUkv'
),
( ['98831F3A:C:11','wallet=test/ref/98831F3A.mmwords'],
'L2LwXv94XTU2HjCbJPXCFuaHjrjucGipWPWUi1hkM5EykgektyqR'
),
( ['98831F3A:B:11','wallet=test/ref/98831F3A.mmwords'],
'L2K4Y9MWb5oUfKKZtwdgCm6FLZdUiWJDHjh9BYxpEvtfcXt4iM5g'
),
( ['98831F3A:S:11','wallet=test/ref/98831F3A.mmwords'],
'KwmkkfC9GghnJhnKoRXRn5KwGCgXrCmDw6Uv83NzE4kJS5axCR9A'
),
],
'gen_addr': [
( ['98831F3A:11','wallet=test/ref/98831F3A.mmwords'],
'12bYUGXS8SRArZneQDN9YEEYAtEa59Rykm'
),
( ['98831F3A:L:11','wallet=test/ref/98831F3A.mmwords'],
'12bYUGXS8SRArZneQDN9YEEYAtEa59Rykm'
),
( ['98831F3A:C:11','wallet=test/ref/98831F3A.mmwords'],
'1MPsZ7BY9qikqfPxqmrovE8gLDX2rYArZk'
),
( ['98831F3A:B:11','wallet=test/ref/98831F3A.mmwords'],
'bc1qxptlvmwaymaxa7pxkr2u5pn7c0508stcncv7ms'
),
( ['98831F3A:S:11','wallet=test/ref/98831F3A.mmwords'],
'3Eevao3DRVXnYym3tdrJDqS3Wc39PQzahn'
),
],
},
'Coin': {
'addr2pubhash': {
'btc_mainnet': [
( ['12bYUGXS8SRArZneQDN9YEEYAtEa59Rykm'], '118089d66b4a5853765e94923abdd5de4616c6e5' ),
( ['3Eevao3DRVXnYym3tdrJDqS3Wc39PQzahn'], '8e34586186551f6320fa3eb2d238a9c61ab8264b' ),
( ['bc1qxptlvmwaymaxa7pxkr2u5pn7c0508stcncv7ms'], '3057f66ddd26fa6ef826b0d5ca067ec3e8f3c178' ),
],
},
'pubhash2addr': {
'btc_mainnet': [
( ['118089d66b4a5853765e94923abdd5de4616c6e5'], '12bYUGXS8SRArZneQDN9YEEYAtEa59Rykm',
None, 'opt.type="legacy"' ),
( ['8e34586186551f6320fa3eb2d238a9c61ab8264b'], '3Eevao3DRVXnYym3tdrJDqS3Wc39PQzahn',
['--type=segwit'], 'opt.type="segwit"' ),
( ['3057f66ddd26fa6ef826b0d5ca067ec3e8f3c178'], 'bc1qxptlvmwaymaxa7pxkr2u5pn7c0508stcncv7ms',
['--type=bech32'], 'opt.type="bech32"' ),
],
},
'hex2wif': {
'btc_mainnet': [
( ['118089d66b4a5853765e94923abdd5de4616c6e5118089d66b4a5853765e9492'],
'5HwzecKMWD82ppJK3qMKpC7ohXXAwcyAN5VgdJ9PLFaAzpBG4sX',
None, 'opt.type="legacy"' ),
( ['118089d66b4a5853765e94923abdd5de4616c6e5118089d66b4a5853765e9492'],
'KwojSzt1VvW343mQfWQi3J537siAt5ktL2qbuCg1ZyKR8BLQ6UJm',
['--type=compressed'], 'opt.type="compressed"' ),
( ['118089d66b4a5853765e94923abdd5de4616c6e5118089d66b4a5853765e9492'],
'KwojSzt1VvW343mQfWQi3J537siAt5ktL2qbuCg1ZyKR8BLQ6UJm',
['--type=segwit'], 'opt.type="segwit"' ),
( ['118089d66b4a5853765e94923abdd5de4616c6e5118089d66b4a5853765e9492'],
'KwojSzt1VvW343mQfWQi3J537siAt5ktL2qbuCg1ZyKR8BLQ6UJm',
['--type=bech32'], 'opt.type="bech32"' ),
],
},
'privhex2addr': {
'btc_mainnet': [
( ['118089d66b4a5853765e94923abdd5de4616c6e5118089d66b4a5853765e9492'],
'1C5VPtgq9xQ6AcTgMAR3J6GDrs72HC4pS1',
None, 'opt.type="legacy"' ),
( ['118089d66b4a5853765e94923abdd5de4616c6e5118089d66b4a5853765e9492'],
'1Kz9fVSUMshzPejpzW9D95kScgA3rY6QxF',
['--type=compressed'], 'opt.type="compressed"' ),
( ['118089d66b4a5853765e94923abdd5de4616c6e5118089d66b4a5853765e9492'],
'3AhjTiWHhVJAi1s5CfKMcLzYps12x3gZhg',
['--type=segwit'], 'opt.type="segwit"' ),
( ['118089d66b4a5853765e94923abdd5de4616c6e5118089d66b4a5853765e9492'],
'bc1q6pqnfwwakuuejpm9w52ds342f9d5u36v0qnz7c',
['--type=bech32'], 'opt.type="bech32"' ),
],
},
'privhex2pubhex': {
'btc_mainnet': [
( ['118089d66b4a5853765e94923abdd5de4616c6e5118089d66b4a5853765e9492'],
'044281a85c9ce87279e028410b851410d65136304cfbbbeaaa8e2e3931cf4e972757f3254c322eeaa3cb6bf97cc5ecf8d4387b0df2c0b1e6ee18fe3a6977a7d57a',
None, 'opt.type="legacy"' ),
( ['118089d66b4a5853765e94923abdd5de4616c6e5118089d66b4a5853765e9492'],
'024281a85c9ce87279e028410b851410d65136304cfbbbeaaa8e2e3931cf4e9727',
['--type=compressed'], 'opt.type="compressed"' ),
( ['118089d66b4a5853765e94923abdd5de4616c6e5118089d66b4a5853765e9492'],
'024281a85c9ce87279e028410b851410d65136304cfbbbeaaa8e2e3931cf4e9727',
['--type=segwit'], 'opt.type="segwit"' ),
( ['118089d66b4a5853765e94923abdd5de4616c6e5118089d66b4a5853765e9492'],
'024281a85c9ce87279e028410b851410d65136304cfbbbeaaa8e2e3931cf4e9727',
['--type=bech32'], 'opt.type="bech32"' ),
],
},
'pubhex2addr': {
'btc_mainnet': [
( ['044281a85c9ce87279e028410b851410d65136304cfbbbeaaa8e2e3931cf4e972757f3254c322eeaa3cb6bf97cc5ecf8d4387b0df2c0b1e6ee18fe3a6977a7d57a'],
'1C5VPtgq9xQ6AcTgMAR3J6GDrs72HC4pS1',
None, 'opt.type="legacy"' ),
( ['024281a85c9ce87279e028410b851410d65136304cfbbbeaaa8e2e3931cf4e9727'],
'1Kz9fVSUMshzPejpzW9D95kScgA3rY6QxF',
['--type=compressed'], 'opt.type="compressed"' ),
( ['024281a85c9ce87279e028410b851410d65136304cfbbbeaaa8e2e3931cf4e9727'],
'3AhjTiWHhVJAi1s5CfKMcLzYps12x3gZhg',
['--type=segwit'], 'opt.type="segwit"' ),
( ['024281a85c9ce87279e028410b851410d65136304cfbbbeaaa8e2e3931cf4e9727'],
'bc1q6pqnfwwakuuejpm9w52ds342f9d5u36v0qnz7c',
['--type=bech32'], 'opt.type="bech32"' ),
],
},
'pubhex2redeem_script': {
'btc_mainnet': [
( ['024281a85c9ce87279e028410b851410d65136304cfbbbeaaa8e2e3931cf4e9727'],
'0014d04134b9ddb7399907657514d846aa495b4e474c',
['--type=segwit'], 'opt.type="segwit"' ),
],
},
'randpair': {
'btc_mainnet': [ ( [], [is_wif,is_coin_addr], ['-r0'] ) ],
'btc_testnet': [ ( [], [is_wif,is_coin_addr], ['-r0'] ) ],
},
'randwif': {
'btc_mainnet': [ ( [], is_wif, ['-r0'] ) ],
'btc_testnet': [ ( [], is_wif, ['-r0'] ) ],
},
'wif2addr': {
'btc_mainnet': [
( ['5HwzecKMWD82ppJK3qMKpC7ohXXAwcyAN5VgdJ9PLFaAzpBG4sX'],
'1C5VPtgq9xQ6AcTgMAR3J6GDrs72HC4pS1', ['--type=legacy'], 'opt.type="legacy"' ),
( ['KwojSzt1VvW343mQfWQi3J537siAt5ktL2qbuCg1ZyKR8BLQ6UJm'],
'1Kz9fVSUMshzPejpzW9D95kScgA3rY6QxF', ['--type=compressed'], 'opt.type="compressed"' ),
( ['KwojSzt1VvW343mQfWQi3J537siAt5ktL2qbuCg1ZyKR8BLQ6UJm'],
'3AhjTiWHhVJAi1s5CfKMcLzYps12x3gZhg', ['--type=segwit'], 'opt.type="segwit"' ),
( ['KwojSzt1VvW343mQfWQi3J537siAt5ktL2qbuCg1ZyKR8BLQ6UJm'],
'bc1q6pqnfwwakuuejpm9w52ds342f9d5u36v0qnz7c', ['--type=bech32'], 'opt.type="bech32"' ),
],
},
'wif2hex': {
'btc_mainnet': [
( ['5HwzecKMWD82ppJK3qMKpC7ohXXAwcyAN5VgdJ9PLFaAzpBG4sX'],
'118089d66b4a5853765e94923abdd5de4616c6e5118089d66b4a5853765e9492',
None, 'opt.type="legacy"' ),
( ['KwojSzt1VvW343mQfWQi3J537siAt5ktL2qbuCg1ZyKR8BLQ6UJm'],
'118089d66b4a5853765e94923abdd5de4616c6e5118089d66b4a5853765e9492',
['--type=compressed'], 'opt.type="compressed"' ),
( ['KwojSzt1VvW343mQfWQi3J537siAt5ktL2qbuCg1ZyKR8BLQ6UJm'],
'118089d66b4a5853765e94923abdd5de4616c6e5118089d66b4a5853765e9492',
['--type=segwit'], 'opt.type="segwit"' ),
( ['KwojSzt1VvW343mQfWQi3J537siAt5ktL2qbuCg1ZyKR8BLQ6UJm'],
'118089d66b4a5853765e94923abdd5de4616c6e5118089d66b4a5853765e9492',
['--type=bech32'], 'opt.type="bech32"' ),
],
},
'wif2redeem_script': {
'btc_mainnet': [
( ['KwojSzt1VvW343mQfWQi3J537siAt5ktL2qbuCg1ZyKR8BLQ6UJm'],
'0014d04134b9ddb7399907657514d846aa495b4e474c',
['--type=segwit'], 'opt.type="segwit"' ),
],
},
'wif2segwit_pair': {
'btc_mainnet': [
( ['KwojSzt1VvW343mQfWQi3J537siAt5ktL2qbuCg1ZyKR8BLQ6UJm'],
('0014d04134b9ddb7399907657514d846aa495b4e474c','3AhjTiWHhVJAi1s5CfKMcLzYps12x3gZhg'),
['--type=segwit'], 'opt.type="segwit"' ),
],
},
},
# TODO: compressed address files are missing
# 'addrfile_compressed_chk':
# 'btc': ('A33C 4FDE F515 F5BC','6C48 AA57 2056 C8C8'),
# 'ltc': ('3FC0 8F03 C2D6 BD19','4C0A 49B6 2DD1 1BE0'),
'File': {
'addrfile_chksum': {
'btc_mainnet': [
( ['test/ref/98831F3A[1,31-33,500-501,1010-1011].addrs'],
'6FEF 6FB9 7B13 5D91'),
( ['test/ref/98831F3A-S[1,31-33,500-501,1010-1011].addrs'],
'06C1 9C87 F25C 4EE6'),
( ['test/ref/98831F3A-B[1,31-33,500-501,1010-1011].addrs'],
'9D2A D4B6 5117 F02E'),
],
'btc_testnet': [
( ['test/ref/98831F3A[1,31-33,500-501,1010-1011].testnet.addrs'],
'424E 4326 CFFE 5F51'),
( ['test/ref/98831F3A-S[1,31-33,500-501,1010-1011].testnet.addrs'],
'072C 8B07 2730 CB7A'),
( ['test/ref/98831F3A-B[1,31-33,500-501,1010-1011].testnet.addrs'],
'0527 9C39 6C1B E39A'),
],
'ltc_mainnet': [
( ['test/ref/litecoin/98831F3A-LTC[1,31-33,500-501,1010-1011].addrs'],
'AD52 C3FE 8924 AAF0'),
( ['test/ref/litecoin/98831F3A-LTC-S[1,31-33,500-501,1010-1011].addrs'],
'63DF E42A 0827 21C3'),
( ['test/ref/litecoin/98831F3A-LTC-B[1,31-33,500-501,1010-1011].addrs'],
'FF1C 7939 5967 AB82'),
],
'ltc_testnet': [
( ['test/ref/litecoin/98831F3A-LTC[1,31-33,500-501,1010-1011].testnet.addrs'],
'4EBE 2E85 E969 1B30'),
( ['test/ref/litecoin/98831F3A-LTC-S[1,31-33,500-501,1010-1011].testnet.addrs'],
'5DD1 D186 DBE1 59F2'),
( ['test/ref/litecoin/98831F3A-LTC-B[1,31-33,500-501,1010-1011].testnet.addrs'],
'ED3D 8AA4 BED4 0B40'),
],
'zec_mainnet': [
( ['test/ref/zcash/98831F3A-ZEC-C[1,31-33,500-501,1010-1011].addrs'],'903E 7225 DD86 6E01'), ],
'zec_z_mainnet': [
( ['test/ref/zcash/98831F3A-ZEC-Z[1,31-33,500-501,1010-1011].addrs'],'9C7A 72DC 3D4A B3AF'), ],
'xmr_mainnet': [
( ['test/ref/monero/98831F3A-XMR-M[1,31-33,500-501,1010-1011].addrs'],'4369 0253 AC2C 0E38'), ],
'dash_mainnet': [
( ['test/ref/dash/98831F3A-DASH-C[1,31-33,500-501,1010-1011].addrs'],'FBC1 6B6A 0988 4403'), ],
'eth_mainnet': [
( ['test/ref/ethereum/98831F3A-ETH[1,31-33,500-501,1010-1011].addrs'],'E554 076E 7AF6 66A3'), ],
'etc_mainnet': [
( ['test/ref/ethereum_classic/98831F3A-ETC[1,31-33,500-501,1010-1011].addrs'],
'E97A D796 B495 E8BC'), ],
},
'keyaddrfile_chksum': {
'btc_mainnet': [
( ['test/ref/98831F3A[1,31-33,500-501,1010-1011].akeys.mmenc'],
'9F2D D781 1812 8BAD', kafile_opts, kafile_code ),
],
'btc_testnet': [
( ['test/ref/98831F3A[1,31-33,500-501,1010-1011].testnet.akeys.mmenc'],
'88CC 5120 9A91 22C2', kafile_opts, kafile_code ),
],
'ltc_mainnet': [
( ['test/ref/litecoin/98831F3A-LTC[1,31-33,500-501,1010-1011].akeys.mmenc'],
'B804 978A 8796 3ED4', kafile_opts, kafile_code ),
],
'ltc_testnet': [
( ['test/ref/litecoin/98831F3A-LTC[1,31-33,500-501,1010-1011].testnet.akeys.mmenc'],
'98B5 AC35 F334 0398', kafile_opts, kafile_code ),
],
'zec_mainnet': [
( ['test/ref/zcash/98831F3A-ZEC-C[1,31-33,500-501,1010-1011].akeys.mmenc'],
'F05A 5A5C 0C8E 2617', kafile_opts, kafile_code ), ],
'zec_z_mainnet': [
( ['test/ref/zcash/98831F3A-ZEC-Z[1,31-33,500-501,1010-1011].akeys.mmenc'],
'6B87 9B2D 0D8D 8D1E', kafile_opts, kafile_code ), ],
'xmr_mainnet': [
( ['test/ref/monero/98831F3A-XMR-M[1,31-33,500-501,1010-1011].akeys.mmenc'],
'E0D7 9612 3D67 404A', kafile_opts, kafile_code ), ],
'dash_mainnet': [
( ['test/ref/dash/98831F3A-DASH-C[1,31-33,500-501,1010-1011].akeys.mmenc'],
'E83D 2C63 FEA2 4142', kafile_opts, kafile_code ), ],
'eth_mainnet': [
( ['test/ref/ethereum/98831F3A-ETH[1,31-33,500-501,1010-1011].akeys.mmenc'],
'E400 70D9 0AE3 C7C2', kafile_opts, kafile_code ), ],
'etc_mainnet': [
( ['test/ref/ethereum_classic/98831F3A-ETC[1,31-33,500-501,1010-1011].akeys.mmenc'],
'EF49 967D BD6C FE45', kafile_opts, kafile_code ), ],
},
'passwdfile_chksum': {
'btc_mainnet': [
( ['test/ref/98831F3A-фубар@crypto.org-b58-20[1,4,9-11,1100].pws'],
'A983 DAB9 5514 27FB', kafile_opts, kafile_code ), ],
},
'txview': {
'btc_mainnet': [ ( ['test/ref/0B8D5A[15.31789,14,tl=1320969600].rawtx'], None ), ],
'btc_testnet': [ ( ['test/ref/0C7115[15.86255,14,tl=1320969600].testnet.rawtx'], None ), ],
'bch_mainnet': [ ( ['test/ref/460D4D-BCH[10.19764,tl=1320969600].rawtx'], None ), ],
'bch_testnet': [ ( ['test/ref/359FD5-BCH[6.68868,tl=1320969600].testnet.rawtx'], None ), ],
'ltc_mainnet': [ ( ['test/ref/litecoin/AF3CDF-LTC[620.76194,1453,tl=1320969600].rawtx'], None ), ],
'ltc_testnet': [ ( ['test/ref/litecoin/A5A1E0-LTC[1454.64322,1453,tl=1320969600].testnet.rawtx'],
None ), ],
'eth_mainnet': [ ( ['test/ref/ethereum/88FEFD-ETH[23.45495,40000].rawtx'], None ), ],
'eth_testnet': [ ( ['test/ref/ethereum/B472BD-ETH[23.45495,40000].testnet.rawtx'], None ), ],
'mm1_mainnet': [ ( ['test/ref/ethereum/5881D2-MM1[1.23456,50000].rawtx'], None ), ],
'mm1_testnet': [ ( ['test/ref/ethereum/6BDB25-MM1[1.23456,50000].testnet.rawtx'], None ), ],
'etc_mainnet': [ ( ['test/ref/ethereum_classic/ED3848-ETC[1.2345,40000].rawtx'], None ), ],
},
},
}
coin_dependent_groups = ('Coin','File') # TODO: do this as attr of each group in tool.py
def run_test(gid,cmd_name):
data = tests[gid][cmd_name]
# behavior is like test.py: run coin-dependent tests only if g.testnet or g.coin != BTC
if gid in coin_dependent_groups:
k = '{}_{}net'.format((g.token.lower() if g.token else g.coin.lower()),('main','test')[g.testnet])
if k in data:
data = data[k]
@ -235,78 +547,142 @@ def do_cmd(cdata):
msg("-- no data for {} ({}) - skipping".format(cmd_name,k))
return
else:
if opt.coin_dependent: return
if g.coin != 'BTC' or g.testnet: return
m2 = ''
m = '{} {}{}'.format(cyan('Testing'),cmd_name if opt.names else desc,m2)
m = '{} {}{}'.format(purple('Testing'), cmd_name if opt.names else
extract_docstring(getattr(getattr(tool,'MMGenToolCmd'+gid),cmd_name)),m2)
msg_r(green(m)+'\n' if opt.verbose else m)
def fork_cmd(cmd_name,args,out,opts,exec_code):
cmd = list(tool_cmd) + (opts or []) + [cmd_name] + args
vmsg('{} {}'.format(green('Executing'),cyan(' '.join(cmd))))
p = Popen(cmd,stdin=(PIPE if stdin_input else None),stdout=PIPE,stderr=PIPE)
if stdin_input:
p.stdin.write(stdin_input)
p.stdin.close()
cmd_out = p.stdout.read()
try:
cmd_out = cmd_out.decode().strip()
except:
pass
cmd_err = p.stderr.read()
if cmd_err: vmsg(cmd_err.strip().decode())
if p.wait() != 0:
die(1,'Spawned program exited with error')
return cmd_out
def run_func(cmd_name,args,out,opts,exec_code):
vmsg('{}: {}{}'.format(purple('Running'),
' '.join([cmd_name]+[repr(e) for e in args]),
' '+exec_code if exec_code else '' ))
if exec_code: exec(exec_code)
aargs,kwargs = tool._process_args(cmd_name,args)
oq_save = opt.quiet
if not opt.verbose: opt.quiet = True
if stdin_input:
fd0,fd1 = os.pipe()
if os.fork(): # parent
os.close(fd1)
stdin_save = os.dup(0)
os.dup2(fd0,0)
cmd_out = getattr(tc,cmd_name)(*aargs,**kwargs)
os.dup2(stdin_save,0)
os.wait()
opt.quiet = oq_save
return cmd_out
else: # child
os.close(fd0)
os.write(fd1,stdin_input)
vmsg('Input: {!r}'.format(stdin_input))
sys.exit(0)
else:
ret = getattr(tc,cmd_name)(*aargs,**kwargs)
opt.quiet = oq_save
return ret
for d in data:
args,out,opts,exec_code = d + tuple([None] * (4-len(d)))
stdin_input = None
if args and type(args[0]) == bytes:
stdin_input = args[0]
args[0] = '-'
if opt.fork:
cmd = list(tool_cmd) + (opts or []) + [cmd_name] + args
vmsg('{} {}'.format(green('Executing'),cyan(' '.join(cmd))))
p = Popen(cmd,stdout=PIPE,stderr=PIPE)
cmd_out = p.stdout.read()
if type(out) != bytes:
cmd_out = cmd_out.strip().decode()
cmd_err = p.stderr.read()
if cmd_err: vmsg(cmd_err.strip().decode())
if p.wait() != 0:
die(1,'Spawned program exited with error')
cmd_out = fork_cmd(cmd_name,args,out,opts,exec_code)
else:
vmsg('{}: {}'.format(purple('Running'),' '.join([cmd_name]+args)))
if exec_code: exec(exec_code)
aargs,kwargs = tool._process_args(cmd_name,args)
oq_save = opt.quiet
if not opt.verbose: opt.quiet = True
cmd_out = tool._process_result(getattr(tc,cmd_name)(*aargs,**kwargs))
opt.quiet = oq_save
cmd_out = run_func(cmd_name,args,out,opts,exec_code)
if type(out) != bytes:
cmd_out = cmd_out.strip()
vmsg('Output: {}\n'.format(cmd_out))
vmsg('Output: {}\n'.format(cmd_out if issubclass(type(out),str) else repr(cmd_out)))
def check_output(cmd_out,out):
if issubclass(type(out),str): out = out.encode()
if issubclass(type(cmd_out),int): cmd_out = str(cmd_out).encode()
if issubclass(type(cmd_out),str): cmd_out = cmd_out.encode()
if type(out).__name__ == 'function':
assert out(cmd_out.decode()),"{}({}) failed!".format(out.__name__,cmd_out.decode())
elif type(out) == dict:
for k in out:
if k == 'boolfunc':
assert out[k](cmd_out.decode()),"{}({}) failed!".format(out[k].__name__,cmd_out.decod())
else:
if not getattr(__builtins__,k)(cmd_out) == out[k]:
die(1,"{}({}) did not return {}!".format(k,cmd_out,out[k]))
elif out is not None:
assert cmd_out == out,"Output ({!r}) doesn't match expected output ({!r})".format(cmd_out,out)
if type(out) in (list,tuple):
for co,o in zip(cmd_out.split('\n') if opt.fork else cmd_out,out):
check_output(co,o)
else:
vmsg('Output: {}\n'.format(repr(cmd_out)))
if type(out).__name__ == 'function':
assert out(cmd_out),"{}({}) failed!".format(out.__name__,cmd_out)
elif type(out) == list and type(out[0]).__name__ == 'function':
for i in range(len(out)):
s = cmd_out.split('\n')[i]
assert out[i](s),"{}({}) failed!".format(out[i].__name__,s)
elif out is not None:
assert cmd_out == out,"Output ({}) doesn't match expected output ({})".format(cmd_out,out)
check_output(cmd_out,out)
if not opt.verbose: msg_r('.')
if not opt.verbose:
msg('OK')
def do_group(garg):
gid,gdesc,gdata = garg
qmsg(blue("Testing {}".format("command group '{}'".format(gid) if opt.names else gdesc)))
for cdata in gdata:
do_cmd(cdata)
def extract_docstring(obj):
return obj.__doc__.strip().split('\n')[0]
def do_group(gid):
qmsg(blue("Testing {}".format(
"command group '{}'".format(gid) if opt.names
else extract_docstring(getattr(tool,'MMGenToolCmd'+gid)))))
for cname in [e for e in dir(getattr(tool,'MMGenToolCmd'+gid)) if e[0] != '_']:
if cname not in tests[gid]:
m = 'No test for command {!r} in group {!r}!'.format(cname,gid)
if opt.die_on_missing:
die(1,m+' Aborting')
else:
msg(m)
continue
run_test(gid,cname)
def do_cmd_in_group(cmd):
for g in tests:
for cdata in g[2]:
if cdata[0] == cmd:
do_cmd(cdata)
for gid in tests:
for cname in tests[gid]:
if cname == cmd:
run_test(gid,cname)
return True
return False
def list_tested_cmds():
for g in tests:
for cdata in g[2]:
Msg(cdata[0])
for gid in tests:
for cname in [e for e in dir(getattr(tool,'MMGenToolCmd'+gid)) if e[0] != '_']:
Msg(cname)
sys.argv = [sys.argv[0]] + ['--skip-cfg-file'] + sys.argv[1:]
cmd_args = opts.init(opts_data)
import mmgen.tool as tool
if opt.list_tests:
Msg('Available commands:')
for gid,gdesc,gdata in tests:
Msg(' {:12} - {}'.format(gid,gdesc))
Msg('Available tests:')
for gid in tests:
Msg(' {:6} - {}'.format(gid,extract_docstring(getattr(tool,'MMGenToolCmd'+gid))))
sys.exit(0)
if opt.list_tested_cmds:
@ -338,7 +714,6 @@ if opt.fork:
tool_cmd = ('python3') + tool_cmd
else:
opt.usr_randchars = 0
import mmgen.tool as tool
tc = tool.MMGenToolCmd()
start_time = int(time.time())
@ -348,9 +723,8 @@ try:
if len(cmd_args) != 1:
die(1,'Only one command may be specified')
cmd = cmd_args[0]
group = [e for e in tests if e[0] == cmd]
if group:
do_group(group[0])
if cmd in tests:
do_group(cmd)
else:
if not do_cmd_in_group(cmd):
die(1,"'{}': not a recognized test or test group".format(cmd))