#!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution # Copyright (C)2013-2016 Philemon # # 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 . """ test/test.py: Test suite for the MMGen suite """ import sys,os pn = os.path.dirname(sys.argv[0]) os.chdir(os.path.join(pn,os.pardir)) sys.path.__setitem__(0,os.path.abspath(os.curdir)) # Import these _after_ local path's been added to sys.path from mmgen.common import * from mmgen.test import * start_mscolor() scripts = ( 'addrgen', 'addrimport', 'keygen', 'passchg', 'tool', 'txcreate', 'txsend', 'txsign', 'walletchk', 'walletconv', 'walletgen' ) tb_cmd = 'scripts/traceback.py' hincog_fn = 'rand_data' hincog_bytes = 1024*1024 hincog_offset = 98765 hincog_seedlen = 256 incog_id_fn = 'incog_id' non_mmgen_fn = 'btckey' pwfile = 'passwd_file' ref_dir = os.path.join('test','ref') ref_wallet_brainpass = 'abc' ref_wallet_hash_preset = '1' ref_wallet_incog_offset = 123 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' cfgs = { '1': { 'tmpdir': os.path.join('test','tmp1'), 'wpasswd': 'Dorian', 'kapasswd': 'Grok the blockchain', 'addr_idx_list': '12,99,5-10,5,12', # 8 addresses 'dep_generators': { pwfile: 'walletgen', 'mmdat': 'walletgen', 'addrs': 'addrgen', 'raw': 'txcreate', 'sig': 'txsign', 'mmwords': 'export_mnemonic', 'mmseed': 'export_seed', 'mmincog': 'export_incog', 'mmincox': 'export_incog_hex', hincog_fn: 'export_incog_hidden', incog_id_fn: 'export_incog_hidden', 'akeys.mmenc': 'keyaddrgen' }, }, '2': { 'tmpdir': os.path.join('test','tmp2'), 'wpasswd': 'Hodling away', 'addr_idx_list': '37,45,3-6,22-23', # 8 addresses 'seed_len': 128, 'dep_generators': { 'mmdat': 'walletgen2', 'addrs': 'addrgen2', 'raw': 'txcreate2', 'sig': 'txsign2', 'mmwords': 'export_mnemonic2', }, }, '3': { 'tmpdir': os.path.join('test','tmp3'), 'wpasswd': 'Major miner', 'addr_idx_list': '73,54,1022-1023,2-5', # 8 addresses 'dep_generators': { 'mmdat': 'walletgen3', 'addrs': 'addrgen3', 'raw': 'txcreate3', 'sig': 'txsign3' }, }, '4': { 'tmpdir': os.path.join('test','tmp4'), 'wpasswd': 'Hashrate rising', 'addr_idx_list': '63,1004,542-544,7-9', # 8 addresses 'seed_len': 192, 'dep_generators': { 'mmdat': 'walletgen4', 'mmbrain': 'walletgen4', 'addrs': 'addrgen4', 'raw': 'txcreate4', 'sig': 'txsign4', }, 'bw_filename': 'brainwallet.mmbrain', 'bw_params': '192,1', }, '5': { 'tmpdir': os.path.join('test','tmp5'), 'wpasswd': 'My changed password', 'hash_preset': '2', 'dep_generators': { 'mmdat': 'passchg', pwfile: 'passchg', }, }, '6': { 'name': 'reference wallet check (128-bit)', 'seed_len': 128, 'seed_id': 'FE3C6545', 'ref_bw_seed_id': '33F10310', 'addrfile_chk': 'B230 7526 638F 38CB 8FDC 8B76', 'keyaddrfile_chk': 'CF83 32FB 8A8B 08E2 0F00 D601', 'wpasswd': 'reference password', 'ref_wallet': 'FE3C6545-D782B529[128,1].mmdat', 'ic_wallet': 'FE3C6545-E29303EA-5E229E30[128,1].mmincog', 'ic_wallet_hex': 'FE3C6545-BC4BE3F2-32586837[128,1].mmincox', 'hic_wallet': 'FE3C6545-161E495F-BEB7548E[128:1].incog-offset123', 'hic_wallet_old': 'FE3C6545-161E495F-9860A85B[128:1].incog-old.offset123', 'tmpdir': os.path.join('test','tmp6'), 'kapasswd': '', 'addr_idx_list': '1010,500-501,31-33,1,33,500,1011', # 8 addresses 'dep_generators': { 'mmdat': 'refwalletgen1', pwfile: 'refwalletgen1', 'addrs': 'refaddrgen1', 'akeys.mmenc': 'refkeyaddrgen1' }, }, '7': { 'name': 'reference wallet check (192-bit)', 'seed_len': 192, 'seed_id': '1378FC64', 'ref_bw_seed_id': 'CE918388', 'addrfile_chk': '8C17 A5FA 0470 6E89 3A87 8182', 'keyaddrfile_chk': '9648 5132 B98E 3AD9 6FC3 C5AD', 'wpasswd': 'reference password', 'ref_wallet': '1378FC64-6F0F9BB4[192,1].mmdat', 'ic_wallet': '1378FC64-2907DE97-F980D21F[192,1].mmincog', 'ic_wallet_hex': '1378FC64-4DCB5174-872806A7[192,1].mmincox', 'hic_wallet': '1378FC64-B55E9958-77256FC1[192:1].incog.offset123', 'hic_wallet_old': '1378FC64-B55E9958-D85FF20C[192:1].incog-old.offset123', 'tmpdir': os.path.join('test','tmp7'), 'kapasswd': '', 'addr_idx_list': '1010,500-501,31-33,1,33,500,1011', # 8 addresses 'dep_generators': { 'mmdat': 'refwalletgen2', pwfile: 'refwalletgen2', 'addrs': 'refaddrgen2', 'akeys.mmenc': 'refkeyaddrgen2' }, }, '8': { 'name': 'reference wallet check (256-bit)', 'seed_len': 256, 'seed_id': '98831F3A', 'ref_bw_seed_id': 'B48CD7FC', 'addrfile_chk': '6FEF 6FB9 7B13 5D91 854A 0BD3', 'keyaddrfile_chk': '9F2D D781 1812 8BAD C396 9DEB', 'wpasswd': 'reference password', 'ref_wallet': '98831F3A-27F2BF93[256,1].mmdat', 'ref_addrfile': '98831F3A[1,31-33,500-501,1010-1011].addrs', 'ref_keyaddrfile': '98831F3A[1,31-33,500-501,1010-1011].akeys.mmenc', 'ref_addrfile_chksum': '6FEF 6FB9 7B13 5D91 854A 0BD3', 'ref_keyaddrfile_chksum': '9F2D D781 1812 8BAD C396 9DEB', # 'ref_fake_unspent_data':'98831F3A_unspent.json', 'ref_tx_file': 'tx_FFB367[1.234].raw', 'ic_wallet': '98831F3A-5482381C-18460FB1[256,1].mmincog', 'ic_wallet_hex': '98831F3A-1630A9F2-870376A9[256,1].mmincox', 'hic_wallet': '98831F3A-F59B07A0-559CEF19[256:1].incog.offset123', 'hic_wallet_old': '98831F3A-F59B07A0-848535F3[256:1].incog-old.offset123', 'tmpdir': os.path.join('test','tmp8'), 'kapasswd': '', 'addr_idx_list': '1010,500-501,31-33,1,33,500,1011', # 8 addresses 'dep_generators': { 'mmdat': 'refwalletgen3', pwfile: 'refwalletgen3', 'addrs': 'refaddrgen3', 'akeys.mmenc': 'refkeyaddrgen3' }, }, '9': { 'tmpdir': os.path.join('test','tmp9'), 'tool_enc_infn': 'tool_encrypt.in', # 'tool_enc_ref_infn': 'tool_encrypt_ref.in', 'wpasswd': 'reference password', 'dep_generators': { 'tool_encrypt.in': 'tool_encrypt', 'tool_encrypt.in.mmenc': 'tool_encrypt', # 'tool_encrypt_ref.in': 'tool_encrypt_ref', # 'tool_encrypt_ref.in.mmenc': 'tool_encrypt_ref', }, }, } from copy import deepcopy for a,b in ('6','11'),('7','12'),('8','13'): cfgs[b] = deepcopy(cfgs[a]) cfgs[b]['tmpdir'] = os.path.join('test','tmp'+b) from collections import OrderedDict cmd_group = OrderedDict() cmd_group['help'] = OrderedDict([ # test description depends ['helpscreens', (1,'help screens', [],1)], ]) cmd_group['main'] = OrderedDict([ ['walletgen', (1,'wallet generation', [[[],1]],1)], # ['walletchk', (1,'wallet check', [[['mmdat'],1]])], ['passchg', (5,'password, label and hash preset change',[[['mmdat',pwfile],1]],1)], ['walletchk_newpass',(5,'wallet check with new pw, label and hash preset',[[['mmdat',pwfile],5]],1)], ['addrgen', (1,'address generation', [[['mmdat',pwfile],1]],1)], ['addrimport', (1,'address import', [[['addrs'],1]],1)], ['txcreate', (1,'transaction creation', [[['addrs'],1]],1)], ['txsign', (1,'transaction signing', [[['mmdat','raw',pwfile],1]],1)], ['txsend', (1,'transaction sending', [[['sig'],1]])], ['export_seed', (1,'seed export to mmseed format', [[['mmdat'],1]])], ['export_mnemonic', (1,'seed export to mmwords format', [[['mmdat'],1]])], ['export_incog', (1,'seed export to mmincog format', [[['mmdat'],1]])], ['export_incog_hex',(1,'seed export to mmincog hex format', [[['mmdat'],1]])], ['export_incog_hidden',(1,'seed export to hidden mmincog format', [[['mmdat'],1]])], ['addrgen_seed', (1,'address generation from mmseed file', [[['mmseed','addrs'],1]])], ['addrgen_mnemonic',(1,'address generation from mmwords file',[[['mmwords','addrs'],1]])], ['addrgen_incog', (1,'address generation from mmincog file',[[['mmincog','addrs'],1]])], ['addrgen_incog_hex',(1,'address generation from mmincog hex file',[[['mmincox','addrs'],1]])], ['addrgen_incog_hidden',(1,'address generation from hidden mmincog file', [[[hincog_fn,'addrs'],1]])], ['keyaddrgen', (1,'key-address file generation', [[['mmdat',pwfile],1]])], ['txsign_keyaddr',(1,'transaction signing with key-address file', [[['akeys.mmenc','raw'],1]])], ['walletgen2',(2,'wallet generation (2), 128-bit seed', [])], ['addrgen2', (2,'address generation (2)', [[['mmdat'],2]])], ['txcreate2', (2,'transaction creation (2)', [[['addrs'],2]])], ['txsign2', (2,'transaction signing, two transactions',[[['mmdat','raw'],1],[['mmdat','raw'],2]])], ['export_mnemonic2', (2,'seed export to mmwords format (2)',[[['mmdat'],2]])], ['walletgen3',(3,'wallet generation (3)', [])], ['addrgen3', (3,'address generation (3)', [[['mmdat'],3]])], ['txcreate3', (3,'tx creation with inputs and outputs from two wallets', [[['addrs'],1],[['addrs'],3]])], ['txsign3', (3,'tx signing with inputs and outputs from two wallets',[[['mmdat'],1],[['mmdat','raw'],3]])], ['walletgen4',(4,'wallet generation (4) (brainwallet)', [])], ['addrgen4', (4,'address generation (4)', [[['mmdat'],4]])], ['txcreate4', (4,'tx creation with inputs and outputs from four seed sources, plus non-MMGen inputs and outputs', [[['addrs'],1],[['addrs'],2],[['addrs'],3],[['addrs'],4]])], ['txsign4', (4,'tx signing with inputs and outputs from incog file, mnemonic file, wallet and brainwallet, plus non-MMGen inputs and outputs', [[['mmincog'],1],[['mmwords'],2],[['mmdat'],3],[['mmbrain','raw'],4]])], ]) cmd_group['tool'] = OrderedDict([ ['tool_encrypt', (9,"'mmgen-tool encrypt' (random data)", [],1)], ['tool_decrypt', (9,"'mmgen-tool decrypt' (random data)", [[[cfgs['9']['tool_enc_infn'],cfgs['9']['tool_enc_infn']+'.mmenc'],9]],1)], # ['tool_encrypt_ref', (9,"'mmgen-tool encrypt' (reference text)", [])], ['tool_find_incog_data', (9,"'mmgen-tool find_incog_data'", [[[hincog_fn],1],[[incog_id_fn],1]])], # ['pywallet', (9,"'mmgen-pywallet'", [],1)], ]) # saved reference data cmd_group['ref'] = ( # reading ('ref_wallet_chk', ([],'saved reference wallet')), ('ref_seed_chk', ([],'saved seed file')), ('ref_mn_chk', ([],'saved mnemonic file')), ('ref_hincog_chk', ([],'saved hidden incog reference wallet')), ('ref_brain_chk', ([],'saved brainwallet')), # generating new reference ('abc' brainwallet) files: ('refwalletgen', ([],'gen new refwallet')), ('refaddrgen', (['mmdat',pwfile],'new refwallet addr chksum')), ('refkeyaddrgen', (['mmdat',pwfile],'new refwallet key-addr chksum')) ) # misc. saved reference data cmd_group['ref_other'] = ( ('ref_addrfile_chk', 'saved reference address file'), ('ref_keyaddrfile_chk','saved reference key-address file'), # Create the fake inputs: # ('txcreate8', 'transaction creation (8)'), ('ref_tx_chk', 'saved reference tx file'), ('ref_brain_chk_spc3', 'saved brainwallet (non-standard spacing)'), ('ref_tool_decrypt', 'decryption of saved MMGen-encrypted file'), ) # mmgen-walletconv: cmd_group['conv_in'] = ( # reading ('ref_wallet_conv', 'conversion of saved reference wallet'), ('ref_mn_conv', 'conversion of saved mnemonic'), ('ref_seed_conv', 'conversion of saved seed file'), ('ref_brain_conv', 'conversion of ref brainwallet'), ('ref_incog_conv', 'conversion of saved incog wallet'), ('ref_incox_conv', 'conversion of saved hex incog wallet'), ('ref_hincog_conv', 'conversion of saved hidden incog wallet'), ('ref_hincog_conv_old','conversion of saved hidden incog wallet (old format)') ) cmd_group['conv_out'] = ( # writing ('ref_wallet_conv_out', 'ref seed conversion to wallet'), ('ref_mn_conv_out', 'ref seed conversion to mnemonic'), ('ref_seed_conv_out', 'ref seed conversion to seed'), ('ref_incog_conv_out', 'ref seed conversion to incog data'), ('ref_incox_conv_out', 'ref seed conversion to hex incog data'), ('ref_hincog_conv_out', 'ref seed conversion to hidden incog data') ) cmd_list = OrderedDict() for k in cmd_group: cmd_list[k] = [] cmd_data = OrderedDict() for k,v in ( ('help', ('help screens',[])), ('main', ('basic operations',[1,2,3,4,5])), ('tool', ('tools',[9])) ): cmd_data['info_'+k] = v for i in cmd_group[k]: cmd_list[k].append(i) cmd_data[i] = cmd_group[k][i] cmd_data['info_ref'] = 'reference data',[6,7,8] for a,b in cmd_group['ref']: for i,j in (1,128),(2,192),(3,256): k = a+str(i) cmd_list['ref'].append(k) cmd_data[k] = (5+i,'%s (%s-bit)' % (b[1],j),[[b[0],5+i]],1) cmd_data['info_ref_other'] = 'other reference data',[8] for a,b in cmd_group['ref_other']: cmd_list['ref_other'].append(a) cmd_data[a] = (8,b,[[[],8]],1) cmd_data['info_conv_in'] = 'wallet conversion from reference data',[11,12,13] for a,b in cmd_group['conv_in']: for i,j in (1,128),(2,192),(3,256): k = a+str(i) cmd_list['conv_in'].append(k) cmd_data[k] = (10+i,'%s (%s-bit)' % (b,j),[[[],10+i]],1) cmd_data['info_conv_out'] = 'wallet conversion to reference data',[11,12,13] for a,b in cmd_group['conv_out']: for i,j in (1,128),(2,192),(3,256): k = a+str(i) cmd_list['conv_out'].append(k) cmd_data[k] = (10+i,'%s (%s-bit)' % (b,j),[[[],10+i]],1) utils = { 'check_deps': 'check dependencies for specified command', 'clean': 'clean specified tmp dir(s) 1,2,3,4,5 or 6 (no arg = all dirs)', } addrs_per_wallet = 8 # total of two outputs must be < 10 BTC for k in cfgs: cfgs[k]['amts'] = [0,0] for idx,mod in (0,6),(1,4): cfgs[k]['amts'][idx] = '%s.%s' % ((getrandnum(2) % mod), str(getrandnum(4))[:5]) meta_cmds = OrderedDict([ ['ref1', ('refwalletgen1','refaddrgen1','refkeyaddrgen1')], ['ref2', ('refwalletgen2','refaddrgen2','refkeyaddrgen2')], ['ref3', ('refwalletgen3','refaddrgen3','refkeyaddrgen3')], ['gen', ('walletgen','addrgen')], ['pass', ('passchg','walletchk_newpass')], ['tx', ('addrimport','txcreate','txsign','txsend')], ['export', [k for k in cmd_data if k[:7] == 'export_' and cmd_data[k][0] == 1]], ['gen_sp', [k for k in cmd_data if k[:8] == 'addrgen_' and cmd_data[k][0] == 1]], ['online', ('keyaddrgen','txsign_keyaddr')], ['2', [k for k in cmd_data if cmd_data[k][0] == 2]], ['3', [k for k in cmd_data if cmd_data[k][0] == 3]], ['4', [k for k in cmd_data if cmd_data[k][0] == 4]], ['saved_ref1', [c[0]+'1' for c in cmd_group['ref']]], ['saved_ref2', [c[0]+'2' for c in cmd_group['ref']]], ['saved_ref3', [c[0]+'3' for c in cmd_group['ref']]], ['saved_ref_other', [c[0] for c in cmd_group['ref_other']]], ['saved_ref_conv_in1', [c[0]+'1' for c in cmd_group['conv_in']]], ['saved_ref_conv_in2', [c[0]+'2' for c in cmd_group['conv_in']]], ['saved_ref_conv_in3', [c[0]+'3' for c in cmd_group['conv_in']]], ['saved_ref_conv_out1', [c[0]+'1' for c in cmd_group['conv_out']]], ['saved_ref_conv_out2', [c[0]+'2' for c in cmd_group['conv_out']]], ['saved_ref_conv_out3', [c[0]+'3' for c in cmd_group['conv_out']]], ]) del cmd_group opts_data = { # 'sets': [('non_interactive',bool,'verbose',None)], 'desc': 'Test suite for the MMGen suite', 'usage':'[options] [command(s) or metacommand(s)]', 'options': """ -h, --help Print this help message. -b, --buf-keypress Use buffered keypresses as with real human input. -d, --debug-scripts Turn on debugging output in executed scripts. -D, --direct-exec Bypass pexpect and execute a command directly (for debugging only). -e, --exact-output Show the exact output of the MMGen script(s) being run. -l, --list-cmds List and describe the commands in the test suite. -n, --names Display command names instead of descriptions. -I, --non-interactive Non-interactive operation (MS Windows mode) -p, --pause Pause between tests, resuming on keypress. -q, --quiet Produce minimal output. Suppress dependency info. -s, --system Test scripts and modules installed on system rather than those in the repo root. -t, --traceback Run the command inside the '{tb_cmd}' script. -v, --verbose Produce more verbose output. """.format(tb_cmd=tb_cmd), 'notes': """ If no command is given, the whole suite of tests is run. """ } cmd_args = opts.init(opts_data) if opt.system: sys.path.pop(0) ni = bool(opt.non_interactive) # Disable MS color in spawned scripts due to bad interactions os.environ['MMGEN_NOMSCOLOR'] = '1' os.environ['MMGEN_NOLICENSE'] = '1' if opt.debug_scripts: os.environ['MMGEN_DEBUG'] = '1' if opt.buf_keypress: send_delay = 0.3 else: send_delay = 0 os.environ['MMGEN_DISABLE_HOLD_PROTECT'] = '1' if opt.exact_output: def msg(s): pass vmsg = vmsg_r = msg_r = msg else: def msg(s): sys.stderr.write(s+'\n') def vmsg(s): if opt.verbose: sys.stderr.write(s+'\n') def msg_r(s): sys.stderr.write(s) def vmsg_r(s): if opt.verbose: sys.stderr.write(s) stderr_save = sys.stderr def silence(): if not (opt.verbose or opt.exact_output): f = ('/dev/null','stderr.out')[sys.platform[:3]=='win'] sys.stderr = open(f,'a') def end_silence(): if not (opt.verbose or opt.exact_output): sys.stderr = stderr_save def errmsg(s): stderr_save.write(s+'\n') def errmsg_r(s): stderr_save.write(s) if opt.list_cmds: fs = ' {:<{w}} - {}' Msg(green('AVAILABLE COMMANDS:')) w = max([len(i) for i in cmd_data]) for cmd in cmd_data: if cmd[:5] == 'info_': m = capfirst(cmd_data[cmd][0]) msg(green(' %s:' % m)) continue Msg(' '+fs.format(cmd,cmd_data[cmd][1],w=w)) w = max([len(i) for i in meta_cmds]) Msg(green('\nAVAILABLE METACOMMANDS:')) for cmd in meta_cmds: Msg(fs.format(cmd,' '.join(meta_cmds[cmd]),w=w)) w = max([len(i) for i in cmd_list]) Msg(green('\nAVAILABLE COMMAND GROUPS:')) for g in cmd_list: Msg(fs.format(g,' '.join(cmd_list[g]),w=w)) Msg(green('\nAVAILABLE UTILITIES:')) w = max([len(i) for i in utils]) for cmd in sorted(utils): Msg(fs.format(cmd,utils[cmd],w=w)) sys.exit() import time,re try: import pexpect except: # Windows m1 = green('MS Windows detected (or missing pexpect module). Skipping some tests.\n') m2 = green('Interactive mode. User prompts will be ') m3 = grnbg('HIGHLIGHTED IN GREEN') m4 = green('.\nContinue?') ni = True if not keypress_confirm(m1+m2+m3+m4,default_yes=True): errmsg('Exiting at user request') sys.exit() def my_send(p,t,delay=send_delay,s=False): if delay: time.sleep(delay) ret = p.send(t) # returns num bytes written if delay: time.sleep(delay) if opt.verbose: ls = (' ','')[bool(opt.debug or not s)] es = (' ','')[bool(s)] msg('%sSEND %s%s' % (ls,es,yellow("'%s'"%t.replace('\n',r'\n')))) return ret def my_expect(p,s,t='',delay=send_delay,regex=False,nonl=False): quo = ('',"'")[type(s) == str] if opt.verbose: msg_r('EXPECT %s' % yellow(quo+str(s)+quo)) else: msg_r('+') try: if s == '': ret = 0 else: f = (p.expect_exact,p.expect)[bool(regex)] ret = f(s,timeout=60) except pexpect.TIMEOUT: errmsg(red('\nERROR. Expect %s%s%s timed out. Exiting' % (quo,s,quo))) sys.exit(1) if opt.debug or (opt.verbose and type(s) != str): msg_r(' ==> %s ' % ret) if ret == -1: errmsg('Error. Expect returned %s' % ret) sys.exit(1) else: if t == '': if not nonl: vmsg('') else: my_send(p,t,delay,s) return ret def get_file_with_ext(ext,mydir,delete=True,no_dot=False): dot = ('.','')[bool(no_dot)] flist = [os.path.join(mydir,f) for f in os.listdir(mydir) if f == ext or f[-len(dot+ext):] == dot+ext] if not flist: return False if len(flist) > 1: if delete: if not opt.quiet: msg("Multiple *.%s files in '%s' - deleting" % (ext,mydir)) for f in flist: os.unlink(f) return False else: return flist[0] def get_addrfile_checksum(display=False): addrfile = get_file_with_ext('addrs',cfg['tmpdir']) silence() from mmgen.addr import AddrInfo chk = AddrInfo(addrfile).checksum if opt.verbose and display: msg('Checksum: %s' % cyan(chk)) end_silence() return chk def verify_checksum_or_exit(checksum,chk): if checksum != chk: errmsg(red('Checksum error: %s' % chk)) sys.exit(1) vmsg(green('Checksums match: %s') % (cyan(chk))) class MMGenExpect(object): def __init__(self,name,mmgen_cmd,cmd_args=[],extra_desc='',no_output=False): if not opt.system: mmgen_cmd = os.path.join(os.curdir,mmgen_cmd) desc = (cmd_data[name][1],name)[bool(opt.names)] if extra_desc: desc += ' ' + extra_desc if opt.verbose or opt.exact_output: sys.stderr.write( green('Testing: %s\nExecuting ' % desc) + cyan("'%s %s'\n" % (mmgen_cmd,' '.join(cmd_args))) ) else: m = 'Testing %s: ' % desc msg_r((m,yellow(m))[ni]) if opt.direct_exec or ni: msg('') from subprocess import call,check_output f = (call,check_output)[bool(no_output)] ret = f(['python', mmgen_cmd] + cmd_args) if f == call and ret != 0: m = 'Warning: process returned a non-zero exit status (%s)' msg(red(m % ret)) else: if opt.traceback: cmd_args = [mmgen_cmd] + cmd_args mmgen_cmd = tb_cmd self.p = pexpect.spawn(mmgen_cmd,cmd_args) if opt.exact_output: self.p.logfile = sys.stdout def license(self): if 'MMGEN_NOLICENSE' in os.environ: return p = "'w' for conditions and warranty info, or 'c' to continue: " my_expect(self.p,p,'c') def label(self,label='Test Label'): p = 'Enter a wallet label, or hit ENTER for no label: ' my_expect(self.p,p,label+'\n') def usr_rand_out(self,saved=False): m = '%suser-supplied entropy' % (('','saved ')[saved]) my_expect(self.p,'Generating encryption key from OS random data plus ' + m) def usr_rand(self,num_chars): rand_chars = list(getrandstr(num_chars,no_space=True)) my_expect(self.p,'symbols left: ','x') try: vmsg_r('SEND ') while self.p.expect('left: ',0.1) == 0: ch = rand_chars.pop(0) msg_r(yellow(ch)+' ' if opt.verbose else '+') self.p.send(ch) except: vmsg('EOT') my_expect(self.p,'ENTER to continue: ','\n') def passphrase_new(self,desc,passphrase): my_expect(self.p,('Enter passphrase for %s: ' % desc), passphrase+'\n') my_expect(self.p,'Repeat passphrase: ', passphrase+'\n') def passphrase(self,desc,passphrase,pwtype=''): if pwtype: pwtype += ' ' my_expect(self.p,('Enter %spassphrase for %s.*?: ' % (pwtype,desc)), passphrase+'\n',regex=True) def hash_preset(self,desc,preset=''): my_expect(self.p,('Enter hash preset for %s' % desc)) my_expect(self.p,('or hit ENTER .*?:'), str(preset)+'\n',regex=True) def written_to_file(self,desc,overwrite_unlikely=False,query='Overwrite? ',oo=False): s1 = '%s written to file ' % desc s2 = query + "Type uppercase 'YES' to confirm: " ret = my_expect(self.p,([s1,s2],s1)[overwrite_unlikely]) if ret == 1: my_send(self.p,'YES\n') # if oo: outfile = self.expect_getend("Overwriting file '").rstrip("'") return outfile # else: # ret = my_expect(self.p,s1) outfile = self.p.readline().strip().strip("'") vmsg('%s file: %s' % (desc,cyan(outfile.replace("'",'')))) return outfile def no_overwrite(self): self.expect("Overwrite? Type uppercase 'YES' to confirm: ",'\n') self.expect('Exiting at user request') def tx_view(self): my_expect(self.p,r'View .*?transaction.*? \(y\)es, \(N\)o, pager \(v\)iew.*?: ','\n',regex=True) def expect_getend(self,s,regex=False): ret = self.expect(s,regex=regex,nonl=True) end = self.readline().strip() vmsg(' ==> %s' % cyan(end)) return end def interactive(self): return self.p.interact() def logfile(self,arg): self.p.logfile = arg def expect(self,*args,**kwargs): return my_expect(self.p,*args,**kwargs) def send(self,*args,**kwargs): return my_send(self.p,*args,**kwargs) def readline(self): return self.p.readline() def close(self): return self.p.close() def readlines(self): return [l.rstrip()+'\n' for l in self.p.readlines()] def read(self,n=None): return self.p.read(n) from decimal import Decimal from mmgen.bitcoin import verify_addr def add_fake_unspent_entry(out,address,comment): out.append({ 'account': unicode(comment), 'vout': int(getrandnum(4) % 8), 'txid': unicode(hexlify(os.urandom(32))), 'amount': Decimal('%s.%s' % (10+(getrandnum(4) % 40), getrandnum(4) % 100000000)), 'address': address, 'spendable': False, 'scriptPubKey': ('76a914'+verify_addr(address,return_hex=True)+'88ac'), 'confirmations': getrandnum(4) % 500 }) def create_fake_unspent_data(adata,unspent_data_file,tx_data,non_mmgen_input=''): out = [] for s in tx_data: sid = tx_data[s]['sid'] a = adata.addrinfo(sid) for n,(idx,btcaddr) in enumerate(a.addrpairs(),1): lbl = ('',' addr %02i' % n)[bool(n%3)] add_fake_unspent_entry(out,btcaddr,'%s:%s%s' % (sid,idx,lbl)) if non_mmgen_input: from mmgen.bitcoin import privnum2addr,hextowif privnum = getrandnum(32) btcaddr = privnum2addr(privnum,compressed=True) of = os.path.join(cfgs[non_mmgen_input]['tmpdir'],non_mmgen_fn) write_data_to_file(of, hextowif('{:064x}'.format(privnum), compressed=True)+'\n','compressed bitcoin key',silent=True) add_fake_unspent_entry(out,btcaddr,'Non-MMGen address') # msg('\n'.join([repr(o) for o in out])); sys.exit() write_data_to_file(unspent_data_file,repr(out),'Unspent outputs',silent=True) def add_comments_to_addr_file(addrfile,outfile): silence() msg(green("Adding comments to address file '%s'" % addrfile)) from mmgen.addr import AddrInfo a = AddrInfo(addrfile) for n,idx in enumerate(a.idxs(),1): if n % 2: a.set_comment(idx,'Test address %s' % n) write_data_to_file(outfile,a.fmt_data(enable_comments=True),silent=True) end_silence() def make_brainwallet_file(fn): # Print random words with random whitespace in between from mmgen.mn_tirosh import words wl = words.split() nwords,ws_list,max_spaces = 10,' \n',5 def rand_ws_seq(): nchars = getrandnum(1) % max_spaces + 1 return ''.join([ws_list[getrandnum(1)%len(ws_list)] for i in range(nchars)]) rand_pairs = [wl[getrandnum(4) % len(wl)] + rand_ws_seq() for i in range(nwords)] d = ''.join(rand_pairs).rstrip() + '\n' if opt.verbose: msg_r('Brainwallet password:\n%s' % cyan(d)) write_data_to_file(fn,d,'brainwallet password',silent=True) def do_between(): if opt.pause: if keypress_confirm(green('Continue?'),default_yes=True): if opt.verbose or opt.exact_output: sys.stderr.write('\n') else: errmsg('Exiting at user request') sys.exit() elif opt.verbose or opt.exact_output: sys.stderr.write('\n') rebuild_list = OrderedDict() def check_needs_rerun( ts, cmd, build=False, root=True, force_delete=False, dpy=False ): rerun = (False,True)[root] # force_delete is not passed to recursive call fns = [] if force_delete or not root: # does cmd produce a needed dependency(ies)? ret = ts.get_num_exts_for_cmd(cmd,dpy) if ret: for ext in ret[1]: fn = get_file_with_ext(ext,cfgs[ret[0]]['tmpdir'],delete=build) if fn: if force_delete: os.unlink(fn) else: fns.append(fn) else: rerun = True fdeps = ts.generate_file_deps(cmd) cdeps = ts.generate_cmd_deps(fdeps) for fn in fns: my_age = os.stat(fn).st_mtime for num,ext in fdeps: f = get_file_with_ext(ext,cfgs[num]['tmpdir'],delete=build) if f and os.stat(f).st_mtime > my_age: rerun = True for cdep in cdeps: if check_needs_rerun(ts,cdep,build=build,root=False,dpy=cmd): rerun = True if build: if rerun: for fn in fns: if not root: os.unlink(fn) ts.do_cmd(cmd) if not root: do_between() else: # If prog produces multiple files: if cmd not in rebuild_list or rerun == True: rebuild_list[cmd] = (rerun,fns[0] if fns else '') # FIX return rerun def refcheck(desc,chk,refchk): vmsg("Comparing %s '%s' to stored reference" % (desc,chk)) if chk == refchk: ok() else: if not opt.verbose: errmsg('') errmsg(red(""" Fatal error - %s '%s' does not match reference value '%s'. Aborting test """.strip() % (desc,chk,refchk))) sys.exit(3) def check_deps(cmds): if len(cmds) != 1: die(1,'Usage: %s check_deps ' % g.prog_name) cmd = cmds[0] if cmd not in cmd_data: die(1,"'%s': unrecognized command" % cmd) if not opt.quiet: msg("Checking dependencies for '%s'" % (cmd)) check_needs_rerun(ts,cmd,build=False) w = max(len(i) for i in rebuild_list) + 1 for cmd in rebuild_list: c = rebuild_list[cmd] m = 'Rebuild' if (c[0] and c[1]) else 'Build' if c[0] else 'OK' msg('cmd {:<{w}} {}'.format(cmd+':', m, w=w)) # mmsg(cmd,c) def clean(usr_dirs=[]): all_dirs = MMGenTestSuite().list_tmp_dirs() dirs = (usr_dirs or all_dirs) for d in sorted(dirs): if str(d) in all_dirs: cleandir(all_dirs[str(d)]) else: die(1,'%s: invalid directory number' % d) class MMGenTestSuite(object): def __init__(self): pass def list_tmp_dirs(self): d = {} for k in cfgs: d[k] = cfgs[k]['tmpdir'] return d def get_num_exts_for_cmd(self,cmd,dpy=False): # dpy ignored here num = str(cmd_data[cmd][0]) dgl = cfgs[num]['dep_generators'] # mmsg(num,cmd,dgl) if cmd in dgl.values(): exts = [k for k in dgl if dgl[k] == cmd] return (num,exts) else: return None def do_cmd(self,cmd): if ni and (len(cmd_data[cmd]) < 4 or cmd_data[cmd][3] != 1): return d = [(str(num),ext) for exts,num in cmd_data[cmd][2] for ext in exts] al = [get_file_with_ext(ext,cfgs[num]['tmpdir']) for num,ext in d] global cfg cfg = cfgs[str(cmd_data[cmd][0])] self.__class__.__dict__[cmd](*([self,cmd] + al)) def generate_file_deps(self,cmd): return [(str(n),e) for exts,n in cmd_data[cmd][2] for e in exts] def generate_cmd_deps(self,fdeps): return [cfgs[str(n)]['dep_generators'][ext] for n,ext in fdeps] def helpscreens(self,name): for s in scripts: t = MMGenExpect(name,('mmgen-'+s),['--help'], extra_desc='(mmgen-%s)'%s,no_output=True) if not ni: t.read(); ok() def walletgen(self,name,seed_len=None): write_to_tmpfile(cfg,pwfile,cfg['wpasswd']+'\n') add_args = (['-r10'], ['-q','-r0','-L','NI Wallet','-P',get_tmpfile_fn(cfg,pwfile)])[bool(ni)] args = ['-d',cfg['tmpdir'],'-p1'] if seed_len: args += ['-l',str(seed_len)] t = MMGenExpect(name,'mmgen-walletgen', args + add_args) if ni: return t.license() t.usr_rand(10) t.passphrase_new('new MMGen wallet',cfg['wpasswd']) t.label() t.written_to_file('MMGen wallet') ok() def brainwalletgen_ref(self,name): sl_arg = '-l%s' % cfg['seed_len'] hp_arg = '-p%s' % ref_wallet_hash_preset label = "test.py ref. wallet (pw '%s', seed len %s)" \ % (ref_wallet_brainpass,cfg['seed_len']) bf = 'ref.mmbrain' args = ['-d',cfg['tmpdir'],hp_arg,sl_arg,'-ib','-L',label] write_to_tmpfile(cfg,bf,ref_wallet_brainpass) write_to_tmpfile(cfg,pwfile,cfg['wpasswd']) if ni: add_args = ['-r0', '-q', '-P%s' % get_tmpfile_fn(cfg,pwfile), get_tmpfile_fn(cfg,bf)] else: add_args = ['-r10'] t = MMGenExpect(name,'mmgen-walletconv', args + add_args) if ni: return t.license() t.expect('Enter brainwallet: ', ref_wallet_brainpass+'\n') t.passphrase_new('new MMGen wallet',cfg['wpasswd']) t.usr_rand(10) sid = t.written_to_file('MMGen wallet').split('-')[0].split('/')[-1] refcheck('Seed ID',sid,cfg['seed_id']) def refwalletgen(self,name): self.brainwalletgen_ref(name) def passchg(self,name,wf,pf): # ni: reuse password, since there's no way to change it non-interactively silence() write_to_tmpfile(cfg,pwfile,get_data_from_file(pf)) end_silence() add_args = (['-r16'],['-q','-r0','-P',pf])[bool(ni)] t = MMGenExpect(name,'mmgen-passchg', add_args + ['-d',cfg['tmpdir'],'-p','2','-L','New Label',wf]) if ni: return t.license() t.passphrase('MMGen wallet',cfgs['1']['wpasswd'],pwtype='old') t.expect_getend('Hash preset changed to ') t.passphrase('MMGen wallet',cfg['wpasswd'],pwtype='new') t.expect('Repeat passphrase: ',cfg['wpasswd']+'\n') t.usr_rand(16) t.expect_getend('Label changed to ') # t.expect_getend('Key ID changed: ') t.written_to_file('MMGen wallet') ok() def walletchk(self,name,wf,pf,desc='MMGen wallet', add_args=[],sid=None,pw=False,extra_desc=''): args = ([],['-P',pf,'-q'])[bool(ni and pf)] hp = cfg['hash_preset'] if 'hash_preset' in cfg else '1' wf_arg = ([],[wf])[bool(wf)] t = MMGenExpect(name,'mmgen-walletchk', add_args+args+['-p',hp]+wf_arg, extra_desc=extra_desc) if ni: if sid: n = (' should be','')[desc=='MMGen wallet'] m = grnbg('Seed ID%s:' % n) msg(grnbg('%s %s' % (m,cyan(sid)))) return if desc != 'hidden incognito data': t.expect("Getting %s from file '%s'" % (desc,wf)) if pw: t.passphrase(desc,cfg['wpasswd']) t.expect( ['Passphrase is OK', 'Passphrase.* are correct'], regex=True ) chk = t.expect_getend('Valid %s for Seed ID ' % desc)[:8] if sid: cmp_or_die(chk,sid) else: ok() def walletchk_newpass (self,name,wf,pf): return self.walletchk(name,wf,pf,pw=True) def addrgen(self,name,wf,pf,check_ref=False): add_args = ([],['-P',pf,'-q'])[ni] t = MMGenExpect(name,'mmgen-addrgen', add_args + ['-d',cfg['tmpdir'],wf,cfg['addr_idx_list']]) if ni: return t.license() t.passphrase('MMGen wallet',cfg['wpasswd']) t.expect('Passphrase is OK') chk = t.expect_getend(r'Checksum for address data .*?: ',regex=True) if check_ref: refcheck('address data checksum',chk,cfg['addrfile_chk']) return t.written_to_file('Addresses',oo=True) ok() def refaddrgen(self,name,wf,pf): d = ' (%s-bit seed)' % cfg['seed_len'] self.addrgen(name,wf,pf=pf,check_ref=True) def addrimport(self,name,addrfile): add_args = ([],['-q','-t'])[ni] outfile = os.path.join(cfg['tmpdir'],'addrfile_w_comments') add_comments_to_addr_file(addrfile,outfile) t = MMGenExpect(name,'mmgen-addrimport', add_args + [outfile]) if ni: return t.expect_getend(r'Checksum for address data .*\[.*\]: ',regex=True) t.expect_getend('Validating addresses...OK. ') t.expect("Type uppercase 'YES' to confirm: ",'\n') vmsg('This is a simulation, so no addresses were actually imported into the tracking\nwallet') ok() def txcreate(self,name,addrfile): self.txcreate_common(name,sources=['1']) def txcreate_common(self,name,sources=['1'],non_mmgen_input=''): if opt.verbose or opt.exact_output: sys.stderr.write(green('Generating fake transaction info\n')) silence() from mmgen.addr import AddrInfo,AddrInfoList tx_data,ail = {},AddrInfoList() for s in sources: afile = get_file_with_ext('addrs',cfgs[s]['tmpdir']) ai = AddrInfo(afile) ail.add(ai) aix = parse_addr_idxs(cfgs[s]['addr_idx_list']) if len(aix) != addrs_per_wallet: errmsg(red('Address index list length != %s: %s' % (addrs_per_wallet,repr(aix)))) sys.exit() tx_data[s] = { 'addrfile': afile, 'chk': ai.checksum, 'sid': ai.seed_id, 'addr_idxs': aix[-2:], } unspent_data_file = os.path.join(cfg['tmpdir'],'unspent.json') create_fake_unspent_data(ail,unspent_data_file,tx_data,non_mmgen_input) # make the command line from mmgen.bitcoin import privnum2addr btcaddr = privnum2addr(getrandnum(32),compressed=True) cmd_args = ['-d',cfg['tmpdir']] for num in tx_data: s = tx_data[num] cmd_args += [ '%s:%s,%s' % (s['sid'],s['addr_idxs'][0],cfgs[num]['amts'][0]), ] # + one BTC address # + one change address and one BTC address if num is tx_data.keys()[-1]: cmd_args += ['%s:%s' % (s['sid'],s['addr_idxs'][1])] cmd_args += ['%s,%s' % (btcaddr,cfgs[num]['amts'][1])] for num in tx_data: cmd_args += [tx_data[num]['addrfile']] os.environ['MMGEN_BOGUS_WALLET_DATA'] = unspent_data_file end_silence() if opt.verbose or opt.exact_output: sys.stderr.write('\n') add_args = ([],['-q'])[ni] if ni: m = '\nAnswer the interactive prompts as follows:\n' + \ " 'y', 'y', 'q', '1-8', ENTER, ENTER, ENTER, 'y'" msg(grnbg(m)) t = MMGenExpect(name,'mmgen-txcreate',add_args + cmd_args) if ni: return t.license() for num in tx_data: t.expect_getend('Getting address data from file ') chk=t.expect_getend(r'Checksum for address data .*?: ',regex=True) verify_checksum_or_exit(tx_data[num]['chk'],chk) # not in tracking wallet warning, (1 + num sources) times if t.expect(['Continue anyway? (y/N): ', 'Unable to connect to bitcoind']) == 0: t.send('y') else: errmsg(red('Error: unable to connect to bitcoind. Exiting')) sys.exit(1) for num in tx_data: t.expect('Continue anyway? (y/N): ','y') t.expect(r"'q' = quit sorting, .*?: ",'M', regex=True) t.expect(r"'q' = quit sorting, .*?: ",'q', regex=True) outputs_list = [addrs_per_wallet*i + 1 for i in range(len(tx_data))] if non_mmgen_input: outputs_list.append(len(tx_data)*addrs_per_wallet + 1) t.expect('Enter a range or space-separated list of outputs to spend: ', ' '.join([str(i) for i in outputs_list])+'\n') if non_mmgen_input: t.expect('Accept? (y/N): ','y') t.expect('OK? (Y/n): ','y') # fee OK? t.expect('OK? (Y/n): ','y') # change OK? t.expect('Add a comment to transaction? (y/N): ','\n') t.tx_view() t.expect('Save transaction? (y/N): ','y') t.written_to_file('Transaction') ok() def txsign_end(self,t,tnum=None): t.expect('Signing transaction') t.expect('Edit transaction comment? (y/N): ','\n') t.expect('Save signed transaction? (Y/n): ','y') add = ' #' + tnum if tnum else '' t.written_to_file('Signed transaction' + add, oo=True) def txsign(self,name,txfile,wf,pf='',save=True): add_args = ([],['-q','-P',pf])[ni] if ni: m = '\nAnswer the interactive prompts as follows:\n ENTER, ENTER, ENTER' msg(grnbg(m)) t = MMGenExpect(name,'mmgen-txsign', add_args+['-d',cfg['tmpdir'],txfile,wf]) if ni: return t.license() t.tx_view() t.passphrase('MMGen wallet',cfg['wpasswd']) if save: self.txsign_end(t) else: t.expect('Edit transaction comment? (y/N): ','\n') t.close() ok() def txsend(self,name,sigfile): t = MMGenExpect(name,'mmgen-txsend', ['-d',cfg['tmpdir'],sigfile]) t.license() t.tx_view() t.expect('Edit transaction comment? (y/N): ','\n') t.expect('broadcast this transaction to the network?') t.expect("'YES, I REALLY WANT TO DO THIS' to confirm: ",'\n') t.expect('Exiting at user request') vmsg('This is a simulation; no transaction was sent') ok() def walletconv_export(self,name,wf,desc,uargs=[],out_fmt='w',pw=False): opts = ['-d',cfg['tmpdir'],'-o',out_fmt] + uargs + [wf] t = MMGenExpect(name,'mmgen-walletconv',opts) t.license() t.passphrase('MMGen wallet',cfg['wpasswd']) if pw: t.passphrase_new('new '+desc,cfg['wpasswd']) t.usr_rand(10) if ' '.join(desc.split()[-2:]) == 'incognito data': t.expect('Generating encryption key from OS random data ') t.expect('Generating encryption key from OS random data ') ic_id = t.expect_getend('New Incog Wallet ID: ') t.expect('Generating encryption key from OS random data ') if desc == 'hidden incognito data': write_to_tmpfile(cfg,incog_id_fn,ic_id) ret = t.expect(['Create? (Y/n): ',"'YES' to confirm: "]) if ret == 0: t.send('\n') t.expect('Enter file size: ',str(hincog_bytes)+'\n') else: t.send('YES\n') if out_fmt == 'w': t.label() return t.written_to_file(capfirst(desc),oo=True) def export_seed(self,name,wf,desc='seed data',out_fmt='seed'): f = self.walletconv_export(name,wf,desc=desc,out_fmt=out_fmt) silence() msg('%s: %s' % (capfirst(desc),cyan(get_data_from_file(f,desc)))) end_silence() ok() def export_mnemonic(self,name,wf): self.export_seed(name,wf,desc='mnemonic data',out_fmt='words') def export_incog(self,name,wf,desc='incognito data',out_fmt='i',add_args=[]): uargs = ['-p1','-r10'] + add_args self.walletconv_export(name,wf,desc=desc,out_fmt=out_fmt,uargs=uargs,pw=True) ok() def export_incog_hex(self,name,wf): self.export_incog(name,wf,desc='hex incognito data',out_fmt='xi') # TODO: make outdir and hidden incog compatible (ignore --outdir and warn user?) def export_incog_hidden(self,name,wf): rf = os.path.join(cfg['tmpdir'],hincog_fn) add_args = ['-J','%s,%s'%(rf,hincog_offset)] self.export_incog( name,wf,desc='hidden incognito data',out_fmt='hi',add_args=add_args) def addrgen_seed(self,name,wf,foo,desc='seed data',in_fmt='seed'): stdout = (False,True)[desc=='seed data'] #capture output to screen once add_arg = ([],['-S'])[bool(stdout)] t = MMGenExpect(name,'mmgen-addrgen', add_arg + ['-i'+in_fmt,'-d',cfg['tmpdir'],wf,cfg['addr_idx_list']]) t.license() t.expect_getend('Valid %s for Seed ID ' % desc) vmsg('Comparing generated checksum with checksum from previous address file') chk = t.expect_getend(r'Checksum for address data .*?: ',regex=True) if stdout: t.read() verify_checksum_or_exit(get_addrfile_checksum(),chk) # t.no_overwrite() ok() def addrgen_mnemonic(self,name,wf,foo): self.addrgen_seed(name,wf,foo,desc='mnemonic data',in_fmt='words') def addrgen_incog(self,name,wf=[],foo='',in_fmt='i',desc='incognito data',args=[]): t = MMGenExpect(name,'mmgen-addrgen', args+['-i'+in_fmt,'-d',cfg['tmpdir']]+ ([],[wf])[bool(wf)] + [cfg['addr_idx_list']]) t.license() t.expect_getend('Incog Wallet ID: ') t.hash_preset(desc,'1') t.passphrase('%s \w{8}' % desc, cfg['wpasswd']) vmsg('Comparing generated checksum with checksum from address file') chk = t.expect_getend(r'Checksum for address data .*?: ',regex=True) t.close() verify_checksum_or_exit(get_addrfile_checksum(),chk) # t.no_overwrite() ok() def addrgen_incog_hex(self,name,wf,foo): self.addrgen_incog(name,wf,'',in_fmt='xi',desc='hex incognito data') def addrgen_incog_hidden(self,name,wf,foo): rf = os.path.join(cfg['tmpdir'],hincog_fn) self.addrgen_incog(name,[],'',in_fmt='hi',desc='hidden incognito data', args=['-H','%s,%s'%(rf,hincog_offset),'-l',str(hincog_seedlen)]) def keyaddrgen(self,name,wf,pf,check_ref=False): args = ['-d',cfg['tmpdir'],wf,cfg['addr_idx_list']] if ni: m = "\nAnswer 'n' at the interactive prompt" msg(grnbg(m)) args = ['-q','-P',pf] + args t = MMGenExpect(name,'mmgen-keygen', args) if ni: return t.license() t.passphrase('MMGen wallet',cfg['wpasswd']) chk = t.expect_getend(r'Checksum for key-address data .*?: ',regex=True) if check_ref: refcheck('key-address data checksum',chk,cfg['keyaddrfile_chk']) return t.expect('Encrypt key list? (y/N): ','y') t.hash_preset('new key list','1') t.passphrase_new('new key list',cfg['kapasswd']) t.written_to_file('Secret keys',oo=True) ok() def refkeyaddrgen(self,name,wf,pf): self.keyaddrgen(name,wf,pf,check_ref=True) def txsign_keyaddr(self,name,keyaddr_file,txfile): t = MMGenExpect(name,'mmgen-txsign', ['-d',cfg['tmpdir'],'-M',keyaddr_file,txfile]) t.license() t.hash_preset('key-address file','1') t.passphrase('key-address file',cfg['kapasswd']) t.expect('Check key-to-address validity? (y/N): ','y') t.tx_view() self.txsign_end(t) ok() def walletgen2(self,name): self.walletgen(name,seed_len=128) def addrgen2(self,name,wf): self.addrgen(name,wf,pf='') def txcreate2(self,name,addrfile): self.txcreate_common(name,sources=['2']) def txsign2(self,name,txf1,wf1,txf2,wf2): t = MMGenExpect(name,'mmgen-txsign', ['-d',cfg['tmpdir'],txf1,wf1,txf2,wf2]) t.license() for cnum in ('1','2'): t.tx_view() t.passphrase('MMGen wallet',cfgs[cnum]['wpasswd']) self.txsign_end(t,cnum) ok() def export_mnemonic2(self,name,wf): self.export_mnemonic(name,wf) def walletgen3(self,name): self.walletgen(name) def addrgen3(self,name,wf): self.addrgen(name,wf,pf='') def txcreate3(self,name,addrfile1,addrfile2): self.txcreate_common(name,sources=['1','3']) def txsign3(self,name,wf1,wf2,txf2): t = MMGenExpect(name,'mmgen-txsign', ['-d',cfg['tmpdir'],wf1,wf2,txf2]) t.license() t.tx_view() for cnum in ('1','3'): # t.expect_getend('Getting MMGen wallet data from file ') t.passphrase('MMGen wallet',cfgs[cnum]['wpasswd']) self.txsign_end(t) ok() def walletgen4(self,name): bwf = os.path.join(cfg['tmpdir'],cfg['bw_filename']) make_brainwallet_file(bwf) seed_len = str(cfg['seed_len']) args = ['-d',cfg['tmpdir'],'-p1','-r10','-l'+seed_len,'-ib'] t = MMGenExpect(name,'mmgen-walletconv', args + [bwf]) t.license() t.passphrase_new('new MMGen wallet',cfg['wpasswd']) t.usr_rand(10) t.label() t.written_to_file('MMGen wallet') ok() def addrgen4(self,name,wf): self.addrgen(name,wf,pf='') def txcreate4(self,name,f1,f2,f3,f4): self.txcreate_common(name,sources=['1','2','3','4'],non_mmgen_input='4') def txsign4(self,name,f1,f2,f3,f4,f5): non_mm_fn = os.path.join(cfg['tmpdir'],non_mmgen_fn) t = MMGenExpect(name,'mmgen-txsign', ['-d',cfg['tmpdir'],'-i','brain','-b'+cfg['bw_params'],'-p1','-k',non_mm_fn,f1,f2,f3,f4,f5]) t.license() t.tx_view() for cnum,desc in ('1','incognito data'),('3','MMGen wallet'): t.passphrase(('%s' % desc),cfgs[cnum]['wpasswd']) self.txsign_end(t) ok() def tool_encrypt(self,name,infile=''): if infile: infn = infile else: d = os.urandom(1033) tmp_fn = cfg['tool_enc_infn'] write_to_tmpfile(cfg,tmp_fn,d,binary=True) infn = get_tmpfile_fn(cfg,tmp_fn) if ni: pwfn = 'ni_pw' write_to_tmpfile(cfg,pwfn,tool_enc_passwd+'\n') pre = ['-P', get_tmpfile_fn(cfg,pwfn)] app = ['hash_preset=1'] else: pre,app = [],[] t = MMGenExpect(name,'mmgen-tool',pre+['-d',cfg['tmpdir'],'encrypt',infn]+app) if ni: return t.hash_preset('user data','1') t.passphrase_new('user data',tool_enc_passwd) t.written_to_file('Encrypted data') ok() # Generate the reference mmenc file # def tool_encrypt_ref(self,name): # infn = get_tmpfile_fn(cfg,cfg['tool_enc_ref_infn']) # write_data_to_file(infn,cfg['tool_enc_reftext'],silent=True) # self.tool_encrypt(name,infn) def tool_decrypt(self,name,f1,f2): of = name + '.out' if ni: pwfn = 'ni_pw' pre = ['-P', get_tmpfile_fn(cfg,pwfn)] else: pre = [] t = MMGenExpect(name,'mmgen-tool', pre+['-d',cfg['tmpdir'],'decrypt',f2,'outfile='+of,'hash_preset=1']) if not ni: t.passphrase('user data',tool_enc_passwd) t.written_to_file('Decrypted data') d1 = read_from_file(f1,binary=True) d2 = read_from_file(get_tmpfile_fn(cfg,of),binary=True) cmp_or_die(d1,d2,skip_ok=ni) def tool_find_incog_data(self,name,f1,f2): i_id = read_from_file(f2).rstrip() vmsg('Incog ID: %s' % cyan(i_id)) t = MMGenExpect(name,'mmgen-tool', ['-d',cfg['tmpdir'],'find_incog_data',f1,i_id]) if ni: return o = t.expect_getend('Incog data for ID %s found at offset ' % i_id) os.unlink(f1) cmp_or_die(hincog_offset,int(o)) # def pywallet(self,name): # TODO - check output # pf = get_tmpfile_fn(cfg,pwfile) # write_data_to_file(pf,cfg['wpasswd']+'\n',silent=True) # args = ([],['-q','-P',pf])[ni] # unenc_wf = os.path.join(ref_dir,'wallet-unenc.dat') # enc_wf = os.path.join(ref_dir,'wallet-enc.dat') # for wf,enc in (unenc_wf,False),(enc_wf,True): # for w,o,pk in ( # ('addresses','a',False), # ('private keys','k',True), # ('json dump','j',True) # ): # ed = '(%sencrypted wallet, %s)' % (('un','')[bool(enc)],w) # t = MMGenExpect(name,'mmgen-pywallet', args + # ['-'+o,'-d',cfg['tmpdir']] + [wf], extra_desc=ed) # if ni: continue # if pk and enc and not ni: # t.expect('Enter password: ',cfg['wpasswd']+'\n') # t.written_to_file(capfirst(w),oo=True) # if not ni: ok() # Saved reference file tests def ref_wallet_conv(self,name): wf = os.path.join(ref_dir,cfg['ref_wallet']) self.walletconv_in(name,wf,'MMGen wallet',pw=True,oo=True) def ref_mn_conv(self,name,ext='mmwords',desc='Mnemonic data'): wf = os.path.join(ref_dir,cfg['seed_id']+'.'+ext) self.walletconv_in(name,wf,desc,oo=True) def ref_seed_conv(self,name): self.ref_mn_conv(name,ext='mmseed',desc='Seed data') def ref_brain_conv(self,name): uopts = ['-i','b','-p','1','-l',str(cfg['seed_len'])] self.walletconv_in(name,None,'brainwallet',uopts,oo=True) def ref_incog_conv(self,name,wfk='ic_wallet',in_fmt='i',desc='incognito data'): uopts = ['-i',in_fmt,'-p','1','-l',str(cfg['seed_len'])] wf = os.path.join(ref_dir,cfg[wfk]) self.walletconv_in(name,wf,desc,uopts,oo=True,pw=True) def ref_incox_conv(self,name): self.ref_incog_conv(name,in_fmt='xi',wfk='ic_wallet_hex',desc='hex incognito data') def ref_hincog_conv(self,name,wfk='hic_wallet',add_uopts=[]): ic_f = os.path.join(ref_dir,cfg[wfk]) uopts = ['-i','hi','-p','1','-l',str(cfg['seed_len'])] + add_uopts hi_opt = ['-H','%s,%s' % (ic_f,ref_wallet_incog_offset)] self.walletconv_in(name,None,'hidden incognito data',uopts+hi_opt,oo=True,pw=True) def ref_hincog_conv_old(self,name): self.ref_hincog_conv(name,wfk='hic_wallet_old',add_uopts=['-O']) def ref_wallet_conv_out(self,name): self.walletconv_out(name,'MMGen wallet','w',pw=True) def ref_mn_conv_out(self,name): self.walletconv_out(name,'mnemonic data','mn') def ref_seed_conv_out(self,name): self.walletconv_out(name,'seed data','seed') def ref_incog_conv_out(self,name): self.walletconv_out(name,'incognito data',out_fmt='i',pw=True) def ref_incox_conv_out(self,name): self.walletconv_out(name,'hex incognito data',out_fmt='xi',pw=True) def ref_hincog_conv_out(self,name,extra_uopts=[]): ic_f = os.path.join(cfg['tmpdir'],hincog_fn) hi_parms = '%s,%s' % (ic_f,ref_wallet_incog_offset) sl_parm = '-l' + str(cfg['seed_len']) self.walletconv_out(name, 'hidden incognito data', 'hi', uopts=['-J',hi_parms,sl_parm] + extra_uopts, uopts_chk=['-H',hi_parms,sl_parm], pw=True ) def ref_wallet_chk(self,name): wf = os.path.join(ref_dir,cfg['ref_wallet']) if ni: write_to_tmpfile(cfg,pwfile,cfg['wpasswd']) pf = get_tmpfile_fn(cfg,pwfile) else: pf = None self.walletchk(name,wf,pf=pf,pw=True,sid=cfg['seed_id']) def ref_seed_chk(self,name,ext=g.seed_ext): wf = os.path.join(ref_dir,'%s.%s' % (cfg['seed_id'],ext)) desc = ('mnemonic data','seed data')[ext==g.seed_ext] self.walletchk(name,wf,pf=None,desc=desc,sid=cfg['seed_id']) def ref_mn_chk(self,name): self.ref_seed_chk(name,ext=g.mn_ext) def ref_brain_chk(self,name,bw_file=ref_bw_file): wf = os.path.join(ref_dir,bw_file) add_args = ['-l%s' % cfg['seed_len'], '-p'+ref_bw_hash_preset] self.walletchk(name,wf,pf=None,add_args=add_args, desc='brainwallet',sid=cfg['ref_bw_seed_id']) def ref_brain_chk_spc3(self,name): self.ref_brain_chk(name,bw_file=ref_bw_file_spc) def ref_hincog_chk(self,name,desc='hidden incognito data'): for wtype,edesc,of_arg in ('hic_wallet','',[]), \ ('hic_wallet_old','(old format)',['-O']): ic_arg = ['-H%s,%s' % ( os.path.join(ref_dir,cfg[wtype]), ref_wallet_incog_offset )] slarg = ['-l%s ' % cfg['seed_len']] hparg = ['-p1'] if ni: write_to_tmpfile(cfg,pwfile,cfg['wpasswd']) add_args = ['-q','-P%s' % get_tmpfile_fn(cfg,pwfile)] else: add_args = [] if ni and wtype == 'hic_wallet_old': m = grnbg("Answer 'y' at the interactive prompt if Seed ID is") n = cyan(cfg['seed_id']) msg('\n%s %s' % (m,n)) t = MMGenExpect(name,'mmgen-walletchk', add_args + slarg + hparg + of_arg + ic_arg, extra_desc=edesc) if ni: continue t.passphrase(desc,cfg['wpasswd']) if wtype == 'hic_wallet_old': t.expect('Is the Seed ID correct? (Y/n): ','\n') chk = t.expect_getend('Seed ID: ') t.close() cmp_or_die(cfg['seed_id'],chk) def ref_addrfile_chk(self,name,ftype='addr'): wf = os.path.join(ref_dir,cfg['ref_'+ftype+'file']) if ni: m = "\nAnswer the interactive prompts as follows: '1', ENTER" msg(grnbg(m)) pfn = 'ref_kafile_passwd' write_to_tmpfile(cfg,pfn,ref_kafile_pass) aa = ['-P',get_tmpfile_fn(cfg,pfn)] else: aa = [] t = MMGenExpect(name,'mmgen-tool',aa+[ftype+'file_chksum',wf]) if ni: k = 'ref_%saddrfile_chksum' % ('','key')[ftype == 'keyaddr'] m = grnbg('Checksum should be:') n = cyan(cfg[k]) msg(grnbg('%s %s' % (m,n))) return if ftype == 'keyaddr': w = 'key-address file' t.hash_preset(w,ref_kafile_hash_preset) t.passphrase(w,ref_kafile_pass) t.expect('Check key-to-address validity? (y/N): ','y') o = t.read().strip().split('\n')[-1] cmp_or_die(cfg['ref_'+ftype+'file_chksum'],o) def ref_keyaddrfile_chk(self,name): self.ref_addrfile_chk(name,ftype='keyaddr') # def txcreate8(self,name,addrfile): # self.txcreate_common(name,sources=['8']) def ref_tx_chk(self,name): tf = os.path.join(ref_dir,cfg['ref_tx_file']) wf = os.path.join(ref_dir,cfg['ref_wallet']) write_to_tmpfile(cfg,pwfile,cfg['wpasswd']) pf = get_tmpfile_fn(cfg,pwfile) self.txsign(name,tf,wf,pf,save=False) def ref_tool_decrypt(self,name): f = os.path.join(ref_dir,ref_enc_fn) aa = [] if ni: pfn = 'tool_enc_passwd' write_to_tmpfile(cfg,pfn,tool_enc_passwd) aa = ['-P',get_tmpfile_fn(cfg,pfn)] t = MMGenExpect(name,'mmgen-tool', aa + ['-q','decrypt',f,'outfile=-','hash_preset=1']) if ni: return t.passphrase('user data',tool_enc_passwd) t.readline() import re o = re.sub('\r\n','\n',t.read()) cmp_or_die(sample_text,o) # wallet conversion tests def walletconv_in(self,name,infile,desc,uopts=[],pw=False,oo=False): opts = ['-d',cfg['tmpdir'],'-o','words','-r10'] if_arg = [infile] if infile else [] d = '(convert)' if ni: opts += ['-q'] msg('') if pw: pfn = 'ni_passwd' write_to_tmpfile(cfg,pfn,cfg['wpasswd']) opts += ['-P',get_tmpfile_fn(cfg,pfn)] if desc == 'brainwallet': m = "\nAnswer the interactive prompt as follows: '%s'" msg(grnbg(m % ref_wallet_brainpass)) if '-O' in uopts: m = grnbg("Answer 'y' at the interactive prompt if Seed ID is") n = cyan(cfg['seed_id']) msg('\n%s %s' % (m,n)) t = MMGenExpect(name,'mmgen-walletconv',opts+uopts+if_arg,extra_desc=d) if ni: m = grnbg('Seed ID should be:') n = cyan(cfg['seed_id']) msg(grnbg('%s %s' % (m,n))) return t.license() if desc == 'brainwallet': t.expect('Enter brainwallet: ',ref_wallet_brainpass+'\n') if pw: t.passphrase(desc,cfg['wpasswd']) if name[:19] == 'ref_hincog_conv_old': t.expect('Is the Seed ID correct? (Y/n): ','\n') else: t.expect(['Passphrase is OK',' are correct']) # Output wf = t.written_to_file('Mnemonic data',oo=oo) t.close() ok() # back check of result self.walletchk(name,wf,pf=None, desc='mnemonic data', sid=cfg['seed_id'], extra_desc='(check)' ) def walletconv_out(self,name,desc,out_fmt='w',uopts=[],uopts_chk=[],pw=False): opts = ['-d',cfg['tmpdir'],'-p1','-o',out_fmt] + uopts if ni: pfn = 'ni_passwd' write_to_tmpfile(cfg,pfn,cfg['wpasswd']) l = 'Non-Interactive Test Wallet' aa = ['-q','-L',l,'-r0','-P',get_tmpfile_fn(cfg,pfn)] if desc == 'hidden incognito data': rd = os.urandom(ref_wallet_incog_offset+128) write_to_tmpfile(cfg,hincog_fn,rd) else: aa = ['-r10'] infile = os.path.join(ref_dir,cfg['seed_id']+'.mmwords') t = MMGenExpect(name,'mmgen-walletconv',aa+opts+[infile],extra_desc='(convert)') add_args = ['-l%s' % cfg['seed_len']] if ni: pfn = 'ni_passwd' write_to_tmpfile(cfg,pfn,cfg['wpasswd']) pf = get_tmpfile_fn(cfg,pfn) if desc != 'hidden incognito data': from mmgen.seed import SeedSource ext = SeedSource.fmt_code_to_sstype(out_fmt).ext hps = ('',',1')[bool(pw)] # TODO real hp pre_ext = '[%s%s].' % (cfg['seed_len'],hps) wf = get_file_with_ext(pre_ext+ext,cfg['tmpdir'],no_dot=True) else: t.license() if pw: t.passphrase_new('new '+desc,cfg['wpasswd']) t.usr_rand(10) if ' '.join(desc.split()[-2:]) == 'incognito data': for i in (1,2,3): t.expect('Generating encryption key from OS random data ') if desc == 'hidden incognito data': ret = t.expect(['Create? (Y/n): ',"'YES' to confirm: "]) if ret == 0: t.send('\n') t.expect('Enter file size: ',str(hincog_bytes)+'\n') else: t.send('YES\n') if out_fmt == 'w': t.label() wf = t.written_to_file(capfirst(desc),oo=True) pf = None ok() if desc == 'hidden incognito data': add_args += uopts_chk wf = None self.walletchk(name,wf,pf=pf, desc=desc,sid=cfg['seed_id'],pw=pw, add_args=add_args, extra_desc='(check)') for k in ( 'ref_wallet_conv', 'ref_mn_conv', 'ref_seed_conv', 'ref_brain_conv', 'ref_incog_conv', 'ref_incox_conv', 'ref_hincog_conv', 'ref_hincog_conv_old', 'ref_wallet_conv_out', 'ref_mn_conv_out', 'ref_seed_conv_out', 'ref_incog_conv_out', 'ref_incox_conv_out', 'ref_hincog_conv_out', 'ref_wallet_chk', 'refwalletgen', 'refaddrgen', 'ref_seed_chk', 'ref_mn_chk', 'ref_brain_chk', 'ref_hincog_chk', 'refkeyaddrgen', ): for i in ('1','2','3'): locals()[k+i] = locals()[k] # main() if opt.pause: import termios,atexit fd = sys.stdin.fileno() old = termios.tcgetattr(fd) def at_exit(): termios.tcsetattr(fd, termios.TCSADRAIN, old) atexit.register(at_exit) start_time = int(time.time()) ts = MMGenTestSuite() # Laggy flash media cause pexpect to crash, so read and write all temporary # files to volatile memory in '/dev/shm' if sys.platform[:3] == 'win': for cfg in sorted(cfgs): mk_tmpdir(cfgs[cfg]) else: d,pfx = '/dev/shm','mmgen-test-' try: import subprocess subprocess.call('rm -rf %s/%s*'%(d,pfx),shell=True) except Exception as e: die(2,'Unable to delete directory tree %s/%s* (%s)'%(d,pfx,e)) try: import tempfile shm_dir = tempfile.mkdtemp('',pfx,d) except Exception as e: die(2,'Unable to create temporary directory in %s (%s)'%(d,e)) for cfg in sorted(cfgs): mk_tmpdir_path(shm_dir,cfgs[cfg]) try: if cmd_args: for arg in cmd_args: if arg in utils: globals()[arg](cmd_args[cmd_args.index(arg)+1:]) sys.exit() elif 'info_'+arg in cmd_data: dirs = cmd_data['info_'+arg][1] if dirs: clean(dirs) for cmd in cmd_list[arg]: check_needs_rerun(ts,cmd,build=True) elif arg in meta_cmds: for cmd in meta_cmds[arg]: check_needs_rerun(ts,cmd,build=True) elif arg in cmd_data: check_needs_rerun(ts,arg,build=True) else: die(1,'%s: unrecognized command' % arg) else: clean() for cmd in cmd_data: if cmd[:5] == 'info_': msg(green('\nTesting ' + cmd_data[cmd][0])) continue ts.do_cmd(cmd) if cmd is not cmd_data.keys()[-1]: do_between() except: sys.stderr = stderr_save raise t = int(time.time()) - start_time sys.stderr.write(green( 'All requested tests finished OK, elapsed time: %02i:%02i\n' % (t/60,t%60)))