From d3f07f3c9f9be60b3384b629e83c6afd6f11a243 Mon Sep 17 00:00:00 2001 From: philemon Date: Thu, 30 Apr 2015 00:09:29 +0300 Subject: [PATCH] Various bugfixes. --- mmgen/addr.py | 24 +- mmgen/globalvars.py | 6 + mmgen/main.py | 25 +- mmgen/main_addrgen.py | 44 +- mmgen/main_pywallet.py | 6 +- mmgen/main_txsign.py | 5 - mmgen/main_wallet.py | 124 +++ mmgen/main_walletconv.py | 89 --- mmgen/opts.py | 36 +- mmgen/seed.py | 242 ++++-- mmgen/share/Opts.py | 32 +- mmgen/tool.py | 22 - mmgen/util.py | 78 +- setup.py | 2 +- test/test-oldscripts.py | 1535 ++++++++++++++++++++++++++++++++++++++ test/test.py | 211 +++--- 16 files changed, 2108 insertions(+), 373 deletions(-) create mode 100755 mmgen/main_wallet.py delete mode 100755 mmgen/main_walletconv.py create mode 100755 test/test-oldscripts.py diff --git a/mmgen/addr.py b/mmgen/addr.py index 78ae1acd..5cf9190b 100755 --- a/mmgen/addr.py +++ b/mmgen/addr.py @@ -45,6 +45,12 @@ addrmsgs = { # address, and it will be appended to the bitcoind wallet label upon import. # The label may contain any printable ASCII symbol. """.strip().format(n=g.max_addr_label_len,pnm=pnm), + 'keyfile_header': """ +# {pnm} key file +# +# This file is editable. +# Everything following a hash symbol '#' is a comment and ignored by {pnm}. +""".strip().format(pnm=pnm), 'no_keyconv_msg': """ Executable '{kconv}' unavailable. Falling back on (slow) internal ECDSA library. Please install '{kconv}' from the {vgen} package on your system for much @@ -312,7 +318,7 @@ class AddrInfo(MMGenObject): (w,self.seed_id,self.idxs_fmt,self.checksum)) if self.source == "addrgen": qmsg( - "This checksum will be used to verify the address file in the future") +"Record this checksum: it will be used to verify the address file in the future") elif self.source == "addrfile": qmsg("Check this value against your records") @@ -368,35 +374,33 @@ class AddrInfo(MMGenObject): def fmt_data(self,enable_comments=False): - # Check data integrity - either all or none must exist for each attr attrs = ['addr','wif','sec'] status = [0,0,0] - for i in range(self.num_addrs): + for d in self.addrdata: for j,attr in enumerate(attrs): - try: - getattr(self.addrdata[i],attr) + if hasattr(d,attr): status[j] += 1 - except: pass for i,s in enumerate(status): if s != 0 and s != self.num_addrs: msg("%s missing %s in addr data"% (self.num_addrs-s,attrs[i])) sys.exit(3) - if status[0] == None and status[1] == None: + if status[0] == status[1] == 0: msg("Addr data contains neither addresses nor keys") sys.exit(3) # Header out = [] from mmgen.addr import addrmsgs - out.append(addrmsgs['addrfile_header'] + "\n") + k = ('addrfile_header','keyfile_header')[int(status[0]==0)] + out.append(addrmsgs[k]+"\n") if self.checksum: - w = "Key-address" if status[1] else "Address" + w = ("Key-address","Address")[int(status[1]==0)] out.append("# {} data checksum for {}[{}]: {}".format( w, self.seed_id, self.idxs_fmt, self.checksum)) - out.append("# Record this value to a secure location\n") + out.append("# Record this value to a secure location.\n") out.append("%s {" % self.seed_id) # Body diff --git a/mmgen/globalvars.py b/mmgen/globalvars.py index bbe5f5fa..607505c9 100755 --- a/mmgen/globalvars.py +++ b/mmgen/globalvars.py @@ -56,6 +56,12 @@ required_opts = [ "usr_randchars","stdout","show_hash_presets","label", "keep_passphrase","keep_hash_preset" ] +incompatible_opts = ( + ("quiet","verbose"), + ("label","keep_label"), + ("tx_id", "info"), + ("tx_id", "terse_info"), +) min_screen_width = 80 wallet_ext = "mmdat" diff --git a/mmgen/main.py b/mmgen/main.py index 0313fc5e..b38801f0 100755 --- a/mmgen/main.py +++ b/mmgen/main.py @@ -21,21 +21,18 @@ main.py - Script launcher for the MMGen suite """ def launch(what): - def launch_addrgen(): import mmgen.main_addrgen - def launch_addrimport(): import mmgen.main_addrimport - def launch_keygen(): import mmgen.main_addrgen - def launch_passchg(): import mmgen.main_passchg - def launch_pywallet(): import mmgen.main_pywallet - def launch_tool(): import mmgen.main_tool - def launch_txcreate(): import mmgen.main_txcreate - def launch_txsend(): import mmgen.main_txsend - def launch_txsign(): import mmgen.main_txsign - def launch_walletchk(): import mmgen.main_walletchk - def launch_walletconv(): import mmgen.main_walletconv - def launch_walletgen(): import mmgen.main_walletgen + + import os + t = "MMGEN_USE_OLD_SCRIPTS" + if not (t in os.environ and os.environ[t]): + if what in ("walletgen","walletchk","passchg"): + what = "wallet" + + if what == "walletconv": what = "wallet" + if what == "keygen": what = "addrgen" try: import termios - except: locals()["launch_"+what]() # Windows + except: __import__("mmgen.main_" + what) # Windows else: import sys,atexit fd = sys.stdin.fileno() @@ -43,7 +40,7 @@ def launch(what): def at_exit(): termios.tcsetattr(fd, termios.TCSADRAIN, old) atexit.register(at_exit) - try: locals()["launch_"+what]() + try: __import__("mmgen.main_" + what) except KeyboardInterrupt: sys.stderr.write("\nUser interrupt\n") except EOFError: diff --git a/mmgen/main_addrgen.py b/mmgen/main_addrgen.py index fc02b11b..410962d5 100755 --- a/mmgen/main_addrgen.py +++ b/mmgen/main_addrgen.py @@ -29,14 +29,20 @@ from mmgen.util import * from mmgen.crypto import * from mmgen.addr import * -what = "keys" if sys.argv[0].split("-")[-1] == "keygen" else "addresses" +if sys.argv[0].split("-")[-1] == "keygen": + gen_what = "keys" + opt_filter = None +else: + gen_what = "addresses" + opt_filter = "hdceHKlpPqSvbgXGoms" opts_data = { - 'desc': """Generate a range or list of {w} from an {pnm} wallet, - mnemonic, seed or password""".format(w=what,pnm=g.proj_name), + 'desc': """Generate a range or list of {what} from an {pnm} wallet, + mnemonic, seed or password""".format(what=gen_what,pnm=g.proj_name), 'usage':"[opts] [infile]
", 'options': """ --h, --help Print this help message{} +-h, --help Print this help message +-A, --no-addresses Print only secret keys, no addresses -d, --outdir= d Specify an alternate directory 'd' for output -c, --save-checksum Save address list checksum to file -e, --echo-passphrase Echo passphrase or mnemonic to screen upon entry @@ -51,7 +57,8 @@ opts_data = { -q, --quiet Suppress warnings; overwrite files without prompting -S, --stdout Print {what} to stdout --v, --verbose Produce more verbose output{} +-v, --verbose Produce more verbose output +-x, --b16 Print secret keys in hexadecimal too -b, --from-brain= l,p Generate {what} from a user-created password, i.e. a "brainwallet", using seed length 'l' and @@ -64,19 +71,14 @@ opts_data = { -m, --from-mnemonic Generate {what} from an electrum-like mnemonic -s, --from-seed Generate {what} from a seed in .{g.seed_ext} format """.format( - *( - ( -"\n-A, --no-addresses Print only secret keys, no addresses", -"\n-x, --b16 Print secret keys in hexadecimal too" - ) - if what == "keys" else ("","")), - seed_lens=", ".join([str(i) for i in g.seed_lens]), - what=what,g=g,pnm=g.proj_name + seed_lens=", ".join([str(i) for i in g.seed_lens]), + pnm=g.proj_name, + what=gen_what,g=g ), 'notes': """ Addresses are given in a comma-separated list. Hyphen-separated ranges are -also allowed.{} +also allowed.{a} If available, the external 'keyconv' program will be used for address generation. @@ -101,8 +103,8 @@ The '--from-brain' option also requires the user to specify a seed length For a brainwallet passphrase to always generate the same keys and addresses, the same 'l' and 'p' parameters to '--from-brain' must be used in all future invocations with that passphrase -""".format("\n\nBy default, both addresses and secret keys are generated." - if what == "keys" else "") +""".format(a="\n\nBy default, both addresses and secret keys are generated." + if gen_what == "keys" else "") } wmsg = { @@ -112,7 +114,7 @@ UNENCRYPTED form. Generate only the key(s) you need and guard them carefully. """.format(pnm=g.proj_name), } -cmd_args = opt.opts.init(opts_data,add_opts=["b16"]) +cmd_args = opt.opts.init(opts_data,add_opts=["b16"],opt_filter=opt_filter) if opt.from_incog_hex or opt.from_incog_hidden: opt.from_incog = True @@ -131,14 +133,14 @@ if not addr_idxs: sys.exit(2) do_license_msg() # Interact with user: -if what == "keys" and not opt.quiet: +if gen_what == "keys" and not opt.quiet: confirm_or_exit(wmsg['unencrypted_secret_keys'], 'continue') # Generate data: seed = get_seed_retry(infile) -opt.gen_what = "a" if what == "addresses" else ( +opt.gen_what = "a" if gen_what == "addresses" else ( "k" if opt.no_addresses else "ka") ainfo = generate_addrs(seed,addr_idxs) @@ -161,11 +163,11 @@ if opt.stdout or not sys.stdout.isatty(): if enc_ext and sys.stdout.isatty(): msg("Cannot write encrypted data to screen. Exiting") sys.exit(2) - write_to_stdout(addrdata_str,what,ask_terminal=(what=="keys" + write_to_stdout(addrdata_str,gen_what,ask_terminal=(gen_what=="keys" and not opt.quiet and sys.stdout.isatty())) else: outfile = "%s.%s%s" % (outfile_base, ( g.keyaddrfile_ext if "ka" in opt.gen_what else ( g.keyfile_ext if "k" in opt.gen_what else g.addrfile_ext)), enc_ext) - write_to_file(outfile,addrdata_str,what,not opt.quiet,True) + write_to_file(outfile,addrdata_str,gen_what,not opt.quiet,True) diff --git a/mmgen/main_pywallet.py b/mmgen/main_pywallet.py index 5f8b0480..6c816f2c 100755 --- a/mmgen/main_pywallet.py +++ b/mmgen/main_pywallet.py @@ -59,9 +59,9 @@ import hashlib import random import math -import mmgen.config as g +import mmgen.globalvars as g import mmgen.opt as opt -from mmgen.util import msg,msgrepr,msgrepr_exit +from mmgen.util import msg,mdie,mmsg max_version = 60000 addrtype = 0 @@ -86,7 +86,7 @@ opts_data = { } cmd_args = opt.opts.init(opts_data) -opt.opts.warn_incompatible_opts(['json','keys','addrs','keysforaddrs']) +opt.opts.die_on_incompatible_opts(['json','keys','addrs','keysforaddrs']) if len(cmd_args) == 1: from mmgen.util import check_infile diff --git a/mmgen/main_txsign.py b/mmgen/main_txsign.py index 2d25635c..f3c8c4ae 100755 --- a/mmgen/main_txsign.py +++ b/mmgen/main_txsign.py @@ -285,11 +285,6 @@ def get_keys_from_keylist(kldata,other_addrs): infiles = opt.opts.init(opts_data,add_opts=["b16"]) -for l in ( -('tx_id', 'info'), -('tx_id', 'terse_info'), -): opt.opts.die_on_incompatible_opts(l) - if opt.from_incog_hex or opt.from_incog_hidden: opt.from_incog = True if not infiles: opt.opts.usage() diff --git a/mmgen/main_wallet.py b/mmgen/main_wallet.py new file mode 100755 index 00000000..a35358d7 --- /dev/null +++ b/mmgen/main_wallet.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python +# +# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution +# Copyright (C)2013-2015 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 . + +""" +mmgen/main_wallet: Entry point for MMGen wallet-related scripts +""" + +import sys,os,re +import mmgen.globalvars as g +import mmgen.opt as opt +from mmgen.util import die,msg,green,do_license_msg,check_infile,mdie,mmsg,qmsg,capfirst +from mmgen.seed import SeedSource + +bn = os.path.basename(sys.argv[0]) +invoked_as = re.sub(r'^wallet','',bn.split("-")[-1]) + +usage = "[opts] [infile]" +nargs = 1 +iaction = "convert" +oaction = "convert" + +if invoked_as == "gen": + desc = "Generate an {pnm} wallet from a random seed" + opt_filter = "hdoJlLpPqrSv" + usage = "[opts]" + oaction = "output" + nargs = 0 +elif invoked_as == "conv": + desc = "Convert an {pnm} wallet from one format to another" + opt_filter = None +elif invoked_as == "chk": + desc = "Check validity of an {pnm} wallet" + opt_filter = "hiHOlpPqrv" + iaction = "input" +elif invoked_as == "passchg": + desc = "Change the password, hash preset or label of an {pnm} wallet" + opt_filter = "hdiHkKOlLmpPqrSv" + iaction = "input" +else: + die(1,"'%s': unrecognized invocation" % bn) + +opts_data = { + 'desc': desc.format(pnm=g.proj_name), + 'usage': usage, + 'options': """ +-h, --help Print this help message. +-d, --outdir= d Output files to directory 'd' instead of working dir. +-i, --in-fmt= f {iaction} from wallet format 'f' (see FMT CODES below). +-o, --out-fmt= f {oaction} to wallet format 'f' (see FMT CODES below). +-H, --hidden-incog-input-params=f,o Read hidden incognito data from file + 'f' at offset 'o' (comma-separated). +-J, --hidden-incog-output-params=f,o Write hidden incognito data to file + 'f' at offset 'o' (comma-separated). If file 'f' + doesn't exist, it will be created and filled with + random data. +-O, --old-incog-fmt Specify old-format incognito input. +-k, --keep-passphrase Reuse passphrase of input wallet for output wallet. +-K, --keep-hash-preset Reuse hash preset of input wallet for output wallet. +-l, --seed-len= l Specify wallet seed length of 'l' bits. This option + is required only for brainwallet and incognito inputs + with non-standard (< {g.seed_len}-bit) seed lengths. +-L, --label= l Specify a label 'l' for output wallet. +-m, --keep-label Reuse label of input wallet for output wallet. +-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 wallet passphrase from file 'f' +-q, --quiet Produce quieter output; suppress some warnings. +-r, --usr-randchars=n Get 'n' characters of additional randomness from user + (min={g.min_urandchars}, max={g.max_urandchars}, default={g.usr_randchars}). +-S, --stdout Write wallet data to stdout instead of file. +-v, --verbose Produce more verbose output. + +FMT CODES: + {f} +""".format( + g=g, + iaction=capfirst(iaction), + oaction=capfirst(oaction), + f="\n ".join(SeedSource.format_fmt_codes().split("\n")) + ) +} + +cmd_args = opt.opts.init(opts_data,opt_filter=opt_filter) + +if len(cmd_args) < nargs \ + and not opt.hidden_incog_input_params and not opt.in_fmt: + die(1,"An input file or input format must be specified") +elif len(cmd_args) > nargs \ + or (len(cmd_args) == nargs and opt.hidden_incog_input_params): + msg("No input files may be specified" if invoked_as == "gen" + else "Too many input files specified") + opt.opts.usage() + +if cmd_args: check_infile(cmd_args[0]) + +if not invoked_as == "chk": do_license_msg() + +if invoked_as in ("conv","passchg"): msg(green("Processing input wallet")) + +ss_in = None if invoked_as == "gen" \ + else SeedSource(*cmd_args,passchg=invoked_as=="passchg") + +if invoked_as == "chk": + sys.exit() + +if invoked_as in ("conv","passchg"): msg(green("Processing output wallet")) + +ss_out = SeedSource(ss=ss_in,passchg=invoked_as=="passchg") +ss_out.write_to_file() diff --git a/mmgen/main_walletconv.py b/mmgen/main_walletconv.py deleted file mode 100755 index 72222ef2..00000000 --- a/mmgen/main_walletconv.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env python -# -# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C)2013-2015 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 . - -""" -mmgen-walletconv: Convert an MMGen deterministic wallet from one format - to another -""" - -import sys -import mmgen.globalvars as g -import mmgen.opt as opt -from mmgen.util import die,msg,green,do_license_msg,check_infile -from mmgen.seed import SeedSource - -opts_data = { - 'sets_disabled': ( - ('hidden_incog_input_params', bool, 'in_fmt', 'hi'), - ('hidden_incog_output_params', bool, 'out_fmt', 'hi') - ), - 'desc': "Convert an {pnm} wallet from one format to another".format( - pnm=g.proj_name), - 'usage':"[opts] [infile]", - 'options': """ --h, --help Print this help message. --d, --outdir= d Output files to directory 'd' instead of working dir. --i, --in-fmt= f Convert from wallet format 'f' (see FMT CODES below). --o, --out-fmt= f Convert to wallet format 'f' (see FMT CODES below). --H, --hidden-incog-input-params=f,o Use filename 'f' and offset 'o' (comma - separated) for hidden incognito input. --J, --hidden-incog-output-params=f,o Same above, but for output. If file - 'f' doesn't exist, it will be created and filled with - random data. --O, --old-incog-fmt Specify old-format incognito input. --k, --keep-passphrase Reuse input wallet passphrase for output wallet. --K, --keep-hash-preset Reuse input wallet hash preset for output wallet. --l, --seed-len= l Specify wallet seed length of 'l' bits. This option - is required only for brainwallet and incognito inputs - with non-standard (< {g.seed_len}-bit) seed lengths. --p, --hash-preset= p Use the scrypt hash parameters defined by preset 'p' - for password hashing (default: '{g.hash_preset}'). --q, --quiet Produce quieter output; suppress some warnings. --r, --usr-randchars=n Get 'n' characters of additional randomness from user - (min={g.min_urandchars}, max={g.max_urandchars}, default={g.usr_randchars}). --S, --stdout Write wallet data to stdout instead of file. --v, --verbose Produce more verbose output. - -FMT CODES: - {f} -""".format(g=g,f="\n ".join(SeedSource.format_fmt_codes().split("\n"))) -} - -cmd_args = opt.opts.init(opts_data) - -if len(cmd_args) == 0 \ - and not opt.hidden_incog_input_params \ - and not opt.in_fmt: - die(1,"An input file or input format must be specified") - -if len(cmd_args) > 1 or (len(cmd_args) == 1 and opt.hidden_incog_input_params): - die(1,"Only one input file may be specified") - -if len(cmd_args) == 1: - check_infile(cmd_args[0]) - -do_license_msg() - -msg(green("Processing input wallet")) - -ss_in = SeedSource(*cmd_args) - -msg(green("Processing output wallet")) - -ss_out = SeedSource(ss=ss_in) -ss_out.write_to_file() diff --git a/mmgen/opts.py b/mmgen/opts.py index 5d9ca5ae..6df0091b 100755 --- a/mmgen/opts.py +++ b/mmgen/opts.py @@ -24,7 +24,7 @@ import sys import mmgen.globalvars as g import mmgen.share.Opts import opt -from mmgen.util import msg,msg_r,mdie,mmsg,Msg,die +from mmgen.util import msg,msg_r,mdie,mmsg,Msg,die,is_mmgen_wallet_label def usage(): Msg("USAGE: %s %s" % (g.prog_name, usage_txt)) @@ -38,9 +38,10 @@ mand line. Copyright (C) {g.Cdates} {g.author} {g.email} """.format(pnm=g.proj_name, g=g, pgnm_uc=g.prog_name.upper()).strip()) def die_on_incompatible_opts(incompat_list): - bad = [k for k in opt.__dict__ if opt.__dict__[k] and k in incompat_list] - if len(bad) > 1: - die(1,"Conflicting options: %s" % ", ".join([fmt_opt(b) for b in bad])) + for group in incompat_list: + bad = [k for k in opt.__dict__ if opt.__dict__[k] and k in group] + if len(bad) > 1: + die(1,"Conflicting options: %s" % ", ".join([fmt_opt(b) for b in bad])) def _typeconvert_from_dfl(key): @@ -80,18 +81,19 @@ def _show_hash_presets(): msg(fs.format("'%s'" % i, *g.hash_presets[i])) msg("N = memory usage (power of two), p = iterations (rounds)") -def init(opts_data,add_opts=[]): +def init(opts_data,add_opts=[],opt_filter=None): if len(sys.argv) == 2 and sys.argv[1] == '--version': print_version_info(); sys.exit() - uopts,args,short_opts,long_opts = \ - mmgen.share.Opts.parse_opts(sys.argv,opts_data) + uopts,args,short_opts,long_opts,skipped_opts = \ + mmgen.share.Opts.parse_opts(sys.argv,opts_data,opt_filter=opt_filter) if g.debug: d = ( ("Short opts", short_opts), ("Long opts", long_opts), + ("Skipped opts", skipped_opts), ("User-selected opts", uopts), ("Cmd args", args), ) @@ -112,7 +114,8 @@ def init(opts_data,add_opts=[]): if k[:2] == "__": del opt.__dict__[k] # Transfer uopts into opt, setting required opts to None if not set by user - for o in [s.rstrip("=") for s in long_opts] + g.required_opts + add_opts: + for o in [s.rstrip("=") for s in long_opts] + \ + g.required_opts + add_opts + skipped_opts: opt.__dict__[o] = uopts[o] if o in uopts else None # A special case - do this here, before opt gets set from g.dfl_vars @@ -144,12 +147,7 @@ def init(opts_data,add_opts=[]): Msg(" %-18s: %-6s [%s]" % (k,v,type(v).__name__)) Msg("### END OPTS.PY ###\n") - for l in ( - ('from_incog_hidden','from_incog','from_seed','from_mnemonic','from_brain'), - ('export_incog','export_incog_hex','export_incog_hidden','export_mnemonic', - 'export_seed'), - ('quiet','verbose') - ): die_on_incompatible_opts(l) + die_on_incompatible_opts(g.incompatible_opts) return args @@ -225,15 +223,9 @@ def check_opts(usr_opts): # Returns false if any check fails from mmgen.util import check_outdir check_outdir(val) # exits on error elif key == 'label': - if not opt_compares(len(val),"<=",g.max_wallet_label_len,"label length"): + if not is_mmgen_wallet_label(val): + msg("Illegal value for option '%s': '%s'" % (fmt_opt(key),val)) return False - try: val.decode("ascii") - except: - msg("ERROR: label contains a non-ASCII symbol") - return False - w = "character in label" - for ch in list(val): - if not opt_is_in_list(ch,g.wallet_label_symbols,w): return False # NEW elif key in ('in_fmt','out_fmt'): from mmgen.seed import SeedSource,IncogWallet,Brainwallet,IncogWalletHidden diff --git a/mmgen/seed.py b/mmgen/seed.py index 4000c706..790268ee 100755 --- a/mmgen/seed.py +++ b/mmgen/seed.py @@ -59,24 +59,29 @@ class SeedSource(MMGenObject): stdin_ok = False ask_tty = True no_tty = False + op = None _msg = {} class SeedSourceData(MMGenObject): pass - def __new__(cls,fn=None,ss=None,ignore_in_fmt_opt=False): + def __new__(cls,fn=None,ss=None,ignore_in_fmt_opt=False,passchg=False): def die_on_opt_mismatch(opt,sstype): opt_sstype = cls.fmt_code_to_sstype(opt) compare_or_die( - opt_sstype.__name__, "input format specified on command line", + opt_sstype.__name__, "input format requested on command line", sstype.__name__, "input file format" ) if ss: - sstype = cls.fmt_code_to_sstype(opt.out_fmt) - me = super(cls,cls).__new__(sstype or Wallet) # output default: Wallet + if passchg: + sstype = ss.__class__ + else: + sstype = cls.fmt_code_to_sstype(opt.out_fmt) + me = super(cls,cls).__new__(sstype or Wallet) # default: Wallet me.seed = ss.seed me.ss_in = ss + me.op = ("conv","pwchg_new")[int(passchg)] elif fn or opt.hidden_incog_input_params: if fn: f = Filename(fn) @@ -91,17 +96,20 @@ class SeedSource(MMGenObject): me = super(cls,cls).__new__(sstype) me.infile = f + me.op = ("old","pwchg_old")[int(passchg)] elif opt.in_fmt: # Input format sstype = cls.fmt_code_to_sstype(opt.in_fmt) me = super(cls,cls).__new__(sstype) + me.op = ("old","pwchg_old")[int(passchg)] else: # Called with no inputs - initialize with random seed sstype = cls.fmt_code_to_sstype(opt.out_fmt) - me = super(cls,cls).__new__(sstype or Wallet) # output default: Wallet + me = super(cls,cls).__new__(sstype or Wallet) # default: Wallet me.seed = Seed() + me.op = "new" return me - def __init__(self,fn=None,ss=None,ignore_in_fmt_opt=False): + def __init__(self,fn=None,ss=None,ignore_in_fmt_opt=False,passchg=False): self.ssdata = self.SeedSourceData() self.msg = {} @@ -113,6 +121,7 @@ class SeedSource(MMGenObject): if hasattr(self,'seed'): g.use_urandchars = True self._encrypt() + return elif hasattr(self,'infile'): self._deformat_once() self._decrypt_retry() @@ -123,6 +132,9 @@ class SeedSource(MMGenObject): self._deformat_retry() self._decrypt_retry() + m = (""," length %s" % self.seed.length)[int(self.seed.length != 256)] + qmsg("Valid %s for seed ID %s%s" % (self.desc,self.seed.sid,m)) + def _get_data(self): if hasattr(self,'infile'): self.fmt_data = get_data_from_file(self.infile.name,self.desc) @@ -217,41 +229,113 @@ an empty passphrase, just hit ENTER twice. """.strip() } - def _get_pw(self,desc=None): - self.ssdata.passwd = get_mmgen_passphrase(desc) - - def _get_hash_preset(self,desc=None): - # Converting: - desc = desc or self.desc - if hasattr(self,'ss_in') and hasattr(self.ss_in.ssdata,'hash_preset'): - if opt.keep_hash_preset: - a = self.ss_in.ssdata.hash_preset - qmsg("Reusing hash preset '%s' as per user request" % a) - elif 'hash_preset' in opt.set_by_user: - # Prompt, but use user-requested value as default - a = get_hash_preset_from_user(hp=opt.hash_preset,desc=desc) + def _get_hash_preset_from_user(self,hp,desc_suf=""): +# hp=a, + n = ("","old ")[int(self.op=="pwchg_old")] + m,n = (("to accept the default",n),("to reuse the old","new "))[ + int(self.op=="pwchg_new")] + fs = "Enter {}hash preset for {}{}{},\n or hit ENTER {} value ('{}'): " + p = fs.format( + n, + ("","new ")[int(self.op=="new")], + self.desc, + (""," "+desc_suf)[int(bool(desc_suf))], + m, + hp + ) + while True: + ret = my_raw_input(p) + if ret: + if ret in g.hash_presets.keys(): + self.ssdata.hash_preset = ret + return ret + else: + msg("Invalid input. Valid choices are %s" % + ", ".join(sorted(g.hash_presets.keys()))) else: - a = get_hash_preset_from_user(desc=desc) + self.ssdata.hash_preset = hp + return hp + + def _get_hash_preset(self,desc_suf=""): + if hasattr(self,"ss_in") and hasattr(self.ss_in.ssdata,"hash_preset"): + old_hp = self.ss_in.ssdata.hash_preset + if opt.keep_hash_preset: + qmsg("Reusing hash preset '%s' at user request" % old_hp) + self.ssdata.hash_preset = old_hp + elif 'hash_preset' in opt.set_by_user: + hp = self.ssdata.hash_preset = opt.hash_preset + qmsg("Using hash preset '%s' requested on command line" + % opt.hash_preset) + else: # Prompt, using old value as default + hp = self._get_hash_preset_from_user(old_hp,desc_suf) + + if (not opt.keep_hash_preset) and self.op == "pwchg_new": + m = ("changed to '%s'" % hp,"unchanged")[int(hp==old_hp)] + qmsg("Hash preset %s" % m) elif 'hash_preset' in opt.set_by_user: - a = opt.hash_preset - qmsg("Using user-requested hash preset of '%s'" % a) + self.ssdata.hash_preset = opt.hash_preset + qmsg("Using hash preset '%s' requested on command line"%opt.hash_preset) else: - a = get_hash_preset_from_user(desc=self.desc) - self.ssdata.hash_preset = a + self._get_hash_preset_from_user(opt.hash_preset,desc_suf) + + def _get_new_passphrase(self): + desc = "{}passphrase for {}{}".format( + ("","new ")[int(self.op=="pwchg_new")], + ("","new ")[int(self.op in ("new","conv"))], + self.desc + ) + if opt.passwd_file: + w = pwfile_reuse_warning() + pw = " ".join(get_words_from_file(opt.passwd_file,desc,silent=w)) + elif opt.echo_passphrase: + pw = " ".join(get_words_from_user("Enter %s: " % desc)) + else: + for i in range(g.passwd_max_tries): + pw = " ".join(get_words_from_user("Enter %s: " % desc)) + pw2 = " ".join(get_words_from_user("Repeat passphrase: ")) + dmsg("Passphrases: [%s] [%s]" % (pw,pw2)) + if pw == pw2: + vmsg("Passphrases match"); break + else: msg("Passphrases do not match. Try again.") + else: + msg("User failed to duplicate passphrase in %s attempts" % + g.passwd_max_tries) + sys.exit(2) + + if pw == "": qmsg("WARNING: Empty passphrase") + self.ssdata.passwd = pw + return pw + + def _get_passphrase(self,desc_suf=""): + desc ="{}passphrase for {}{}".format( + ("","old ")[int(self.op=="pwchg_old")], + self.desc, + (""," "+desc_suf)[int(bool(desc_suf))] + ) + if opt.passwd_file: + w = pwfile_reuse_warning() + ret = " ".join(get_words_from_file(opt.passwd_file,desc,silent=w)) + else: + ret = " ".join(get_words_from_user("Enter %s: " % desc)) + self.ssdata.passwd = ret def _get_first_pw_and_hp_and_encrypt_seed(self): d = self.ssdata + self._get_hash_preset() - if hasattr(self,'ss_in') and hasattr(self.ss_in.ssdata,'passwd') \ - and opt.keep_passphrase: - d.passwd = self.ss_in.ssdata.passwd - qmsg("Reusing passphrase as per user request") - - self._get_hash_preset(desc="new " + self.desc) - - if not hasattr(d,'passwd'): - qmsg(self.msg['choose_passphrase'] % (self.desc,self.ssdata.hash_preset)) - d.passwd = get_new_passphrase(desc="new " + self.desc) + if hasattr(self,'ss_in') and hasattr(self.ss_in.ssdata,'passwd'): + old_pw = self.ss_in.ssdata.passwd + if opt.keep_passphrase: + d.passwd = old_pw + qmsg("Reusing passphrase at user request") + else: + pw = self._get_new_passphrase() + if self.op == "pwchg_new": + m = ("changed","unchanged")[int(pw==old_pw)] + qmsg("Passphrase %s" % m) + else: + qmsg(self.msg['choose_passphrase'] % (self.desc,d.hash_preset)) + self._get_new_passphrase() d.salt = sha256(get_random(128)).digest()[:g.salt_len] key = make_key(d.passwd, d.salt, d.hash_preset) @@ -279,7 +363,7 @@ class Mnemonic (SeedSourceUnenc): deconv = [wl.index(words[::-1][i])*(base**i) for i in range(len(words))] ret = ("{:0%sx}" % pad).format(sum(deconv)) - return "%s%s" % (('0' if len(ret) % 2 else ''), ret) + return ('','0')[len(ret) % 2] + ret def _hextobaseN(self,base,hexnum,wl,pad=0): num,ret = int(hexnum,16),[] @@ -344,7 +428,6 @@ class Mnemonic (SeedSourceUnenc): check_usr_seed_len(self.seed.length) - qmsg("Valid mnemonic for seed ID %s" % make_chksum_8(self.seed.data)) return True def _filename(self): @@ -403,8 +486,6 @@ class SeedFile (SeedSourceUnenc): check_usr_seed_len(self.seed.length) - qmsg("Valid seed data for seed ID %s" % make_chksum_8(self.seed.data)) - return True def _filename(self): @@ -417,11 +498,47 @@ class Wallet (SeedSourceEnc): desc = g.proj_name + " wallet" ext = "mmdat" + def _get_label_from_user(self,old_lbl=""): + d = ("to reuse the label '%s'" % old_lbl) if old_lbl else "for no label" + p = "Enter a wallet label, or hit ENTER %s: " % d + while True: + ret = my_raw_input(p) + if ret: + if is_mmgen_wallet_label(ret): + self.ssdata.label = ret; return ret + else: + msg("Invalid label. Trying again...") + else: + ret = old_lbl or "No Label" + self.ssdata.label = ret; return ret + + # nearly identical to _get_hash_preset() - factor? + def _get_label(self): + if hasattr(self,'ss_in') and hasattr(self.ss_in.ssdata,'label'): + old_lbl = self.ss_in.ssdata.label + if opt.keep_label: + qmsg("Reusing label '%s' at user request" % old_lbl) + self.ssdata.label = old_lbl + elif opt.label: + qmsg("Using label '%s' requested on command line" % opt.label) + lbl = self.ssdata.label = opt.label + else: # Prompt, using old value as default + lbl = self._get_label_from_user(old_lbl) + + if (not opt.keep_label) and self.op == "pwchg_new": + m = ("changed to '%s'" % lbl,"unchanged")[int(lbl==old_lbl)] + qmsg("Label %s" % m) + elif opt.label: + qmsg("Using label '%s' requested on command line" % opt.label) + self.ssdata.label = opt.label + else: + self._get_label_from_user() + def _encrypt(self): self._get_first_pw_and_hp_and_encrypt_seed() + self._get_label() d = self.ssdata - d.label = opt.label or "No Label" - d.pw_status = "NE" if len(d.passwd) else "E" + d.pw_status = ("NE","E")[int(len(d.passwd)==0)] d.timestamp = make_timestamp() def _format(self): @@ -477,9 +594,10 @@ class Wallet (SeedSourceEnc): d.hash_preset = hp = hpdata[0][:-1] # a string! qmsg("Hash preset of wallet: '%s'" % hp) - uhp = opt.hash_preset - if uhp and 'hash_preset' in opt.set_by_user and uhp != hp: - msg("Warning: ignoring user-requested hash preset '%s'" % uhp) + if 'hash_preset' in opt.set_by_user: + uhp = opt.hash_preset + if uhp != hp: + qmsg("Warning: ignoring user-requested hash preset '%s'" % uhp) hash_params = [int(i) for i in hpdata[1:]] @@ -514,8 +632,8 @@ class Wallet (SeedSourceEnc): def _decrypt(self): d = self.ssdata # Needed for multiple transactions with {}-txsign - add = " "+self.infile.name if opt.quiet else "" - self._get_pw(self.desc+add) + suf = ("",self.infile.name)[int(bool(opt.quiet))] + self._get_passphrase(desc_suf=suf) key = make_key(d.passwd, d.salt, d.hash_preset) ret = decrypt_seed(d.enc_seed, key, d.seed_id, d.key_id) if ret: @@ -572,6 +690,7 @@ class Brainwallet (SeedSourceEnc): fmt_codes = "mmbrain","brainwallet","brain","bw","b" desc = "brainwallet" ext = "mmbrain" + # brainwallet warning message? TODO def _deformat(self): self.brainpasswd = " ".join(self.fmt_data.split()) @@ -620,7 +739,7 @@ to exit and re-run the program with the '--old-incog-fmt' option. def _make_iv_chksum(self,s): return sha256(s).hexdigest()[:8].upper() def _get_incog_data_len(self,seed_len): - e = 0 if opt.old_incog_fmt else g.hincog_chk_len + e = (g.hincog_chk_len,0)[int(bool(opt.old_incog_fmt))] return g.aesctr_iv_len + g.salt_len + e + seed_len/8 def _incog_data_size_chk(self): @@ -718,9 +837,8 @@ to exit and re-run the program with the '--old-incog-fmt' option. def _decrypt(self): d = self.ssdata - desc = self.desc+" "+d.incog_id - self._get_hash_preset(desc) - self._get_pw(desc) + self._get_hash_preset(desc_suf=d.incog_id) + self._get_passphrase(desc_suf=d.incog_id) # IV is used BOTH to initialize counter and to salt password! key = make_key(d.passwd, d.iv, d.hash_preset, "wrapper key") @@ -731,10 +849,10 @@ to exit and re-run the program with the '--old-incog-fmt' option. d.enc_seed = dd[g.salt_len:] key = make_key(d.passwd, d.salt, d.hash_preset, "main key") - msg("Key ID: %s" % make_chksum_8(key)) + qmsg("Key ID: %s" % make_chksum_8(key)) - verify_seed = self._verify_seed_oldfmt if opt.old_incog_fmt else \ - self._verify_seed_newfmt + verify_seed = getattr(self,"_verify_seed_"+ + ("newfmt","oldfmt")[int(bool(opt.old_incog_fmt))]) seed = verify_seed(decrypt_seed(d.enc_seed, key, "", "")) @@ -749,7 +867,7 @@ to exit and re-run the program with the '--old-incog-fmt' option. class IncogWalletHex (IncogWallet): desc = "hex incognito data" - fmt_codes = "mmincox","incog_hex","xincog","ix","xi" + fmt_codes = "mmincox","incox","incog_hex","xincog","ix","xi" ext = "mmincox" no_tty = False @@ -800,7 +918,7 @@ harder to find, you're advised to choose a much larger file size than this. def _check_valid_offset(self,fn,action): d = self.ssdata - m = "Destination" if action == "write" else "Input" + m = ("Input","Destination")[int(action=="write")] if fn.size < d.hincog_offset + d.target_data_len: die(1, "%s file has length %s, too short to %s %s bytes of data at offset %s" @@ -830,9 +948,11 @@ harder to find, you're advised to choose a much larger file size than this. self._format() compare_or_die(d.target_data_len, "target data length", len(self.fmt_data),"length of formatted " + self.desc) - fn,d.hincog_offset = self._get_hincog_params("output") - self.hincog_data_is_new = False + k = ("output","input")[int(self.op=="pwchg_new")] + fn,d.hincog_offset = self._get_hincog_params(k) + + check_offset = True try: os.stat(fn) except: @@ -841,14 +961,14 @@ harder to find, you're advised to choose a much larger file size than this. min_fsize = d.target_data_len + d.hincog_offset msg(self.msg['choose_file_size'].format(min_fsize)) while True: - fsize = my_raw_input("Enter file size: ") - if is_int(fsize) and int(fsize) >= min_fsize: break + fsize = parse_nbytes(my_raw_input("Enter file size: ")) + if fsize >= min_fsize: break msg("File size must be an integer no less than %s" % min_fsize) from mmgen.tool import rand2file rand2file(fn, str(fsize)) - self.hincog_data_is_new = True + check_offset = False else: die(1,"Exiting at user request") @@ -856,7 +976,7 @@ harder to find, you're advised to choose a much larger file size than this. dmsg("Incog data len %s, offset %s" % (d.target_data_len,d.hincog_offset)) - if not self.hincog_data_is_new: + if check_offset: self._check_valid_offset(f,"write") if not opt.quiet: confirm_or_exit("","alter file '%s'" % f.name) @@ -865,5 +985,5 @@ harder to find, you're advised to choose a much larger file size than this. os.write(fh, self.fmt_data) os.close(fh) msg("%s written to file '%s' at offset %s" % ( - self.desc[0].upper()+self.desc[1:], + capfirst(self.desc), f.name,d.hincog_offset)) diff --git a/mmgen/share/Opts.py b/mmgen/share/Opts.py index b2de0d4d..76e98732 100755 --- a/mmgen/share/Opts.py +++ b/mmgen/share/Opts.py @@ -36,7 +36,7 @@ def print_help(opts_data): def process_opts(argv,opts_data,short_opts,long_opts): import os - opts_data['prog_name'] = os.path.split(sys.argv[0])[1] + opts_data['prog_name'] = os.path.basename(sys.argv[0]) long_opts = [i.replace("_","-") for i in long_opts] try: cl_opts, args = getopt.getopt(argv[1:], short_opts, long_opts) @@ -80,22 +80,28 @@ def process_opts(argv,opts_data,short_opts,long_opts): return opts,args -def parse_opts(argv,opts_data): +def parse_opts(argv,opts_data,opt_filter=None): - lines = opts_data['options'].strip().split("\n") import re - pat = r"^-([a-zA-Z0-9]), --([a-zA-Z0-9-]{1,64})(=| )(.+)" - rep = r"-{0}, --{1}{w}{3}" - opt_data = [list(m.groups()) for m in [re.match(pat,l) for l in lines] if m] + pat = r"^-([a-zA-Z0-9]), --([a-zA-Z0-9-]{2,64})(=| )(.+)" + od,skip = [],True + + for l in opts_data['options'].strip().split("\n"): + m = re.match(pat,l) + if m: + skip = True if (opt_filter and m.group(1) not in opt_filter) else False + app = [':','='] if (m.group(3) == '=') else ['',''] + od.append(list(m.groups()) + app + [skip]) + else: + if not skip: od[-1][3] += "\n" + l - for d in opt_data: - if d[2] == " ": d[2] = "" - short_opts = "".join([d[0]+d[2].replace("=",":") for d in opt_data]) - long_opts = [d[1].replace("-","_")+d[2] for d in opt_data] opts_data['options'] = "\n".join( - [rep.format(w=" ", *m.groups()) - if m else k for m,k in [(re.match(pat,l),l) for l in lines]] + ["-{}, --{} {}".format(d[0],d[1],d[3]) for d in od if d[6] == False] ) + short_opts = "".join([d[0]+d[4] for d in od if d[6] == False]) + long_opts = [d[1].replace("-","_")+d[5] for d in od if d[6] == False] + skipped_opts = [d[1].replace("-","_") for d in od if d[6] == True] + opts,args = process_opts(argv,opts_data,short_opts,long_opts) - return opts,args,short_opts,long_opts + return opts,args,short_opts,long_opts,skipped_opts diff --git a/mmgen/tool.py b/mmgen/tool.py index a3b557cf..4be2db2c 100755 --- a/mmgen/tool.py +++ b/mmgen/tool.py @@ -556,28 +556,6 @@ def find_incog_data(filename,iv_id,keep_searching=False): msg("") os.close(f) -# From "man dd": -# c=1, w=2, b=512, kB=1000, K=1024, MB=1000*1000, M=1024*1024, -# GB=1000*1000*1000, G=1024*1024*1024, and so on for T, P, E, Z, Y. - -def parse_nbytes(nbytes): - import re - m = re.match(r'([0123456789]+)(.*)',nbytes) - smap = ("c",1),("w",2),("b",512),("kB",1000),("K",1024),("MB",1000*1000),\ - ("M",1024*1024),("GB",1000*1000*1000),("G",1024*1024*1024) - if m: - if m.group(2): - for k,v in smap: - if k == m.group(2): - return int(m.group(1)) * v - else: - msg("Valid byte specifiers: '%s'" % "' '".join([i[0] for i in smap])) - else: - return int(nbytes) - - msg("'%s': invalid byte specifier" % nbytes) - sys.exit(1) - def rand2file(outfile, nbytes, threads=4, silent=False): nbytes = parse_nbytes(nbytes) diff --git a/mmgen/util.py b/mmgen/util.py index f0443abe..14784d15 100755 --- a/mmgen/util.py +++ b/mmgen/util.py @@ -55,6 +55,42 @@ def die(ev,s): def Die(ev,s): sys.stdout.write(s+"\n"); sys.exit(ev) +def is_mmgen_wallet_label(s): + if len(s) > g.max_wallet_label_len: + msg("ERROR: wallet label length (%s chars) > maximum allowed (%s chars)" % (len(s),g.max_wallet_label_len)) + return False + + try: s = s.decode("utf8") + except: pass + + for ch in s: + if ch not in g.wallet_label_symbols: + msg("ERROR: wallet label contains illegal symbol (%s)" % ch) + return False + return True + +# From "man dd": +# c=1, w=2, b=512, kB=1000, K=1024, MB=1000*1000, M=1024*1024, +# GB=1000*1000*1000, G=1024*1024*1024, and so on for T, P, E, Z, Y. + +def parse_nbytes(nbytes): + import re + m = re.match(r'([0123456789]+)(.*)',nbytes) + smap = ("c",1),("w",2),("b",512),("kB",1000),("K",1024),("MB",1000*1000),\ + ("M",1024*1024),("GB",1000*1000*1000),("G",1024*1024*1024) + if m: + if m.group(2): + for k,v in smap: + if k == m.group(2): + return int(m.group(1)) * v + else: + msg("Valid byte specifiers: '%s'" % "' '".join([i[0] for i in smap])) + else: + return int(nbytes) + + msg("'%s': invalid byte specifier" % nbytes) + sys.exit(1) + import opt def qmsg(s,alt=False): @@ -121,8 +157,8 @@ def split_into_cols(col_wid,s): for i in range(len(s)/col_wid+1)]).rstrip() def capfirst(s): - if len(s) == 0: return s - return s[0].upper() + (s[1:] if len(s) > 1 else "") + return s if len(s) == 0 else \ + (s[0].upper() + (s[1:] if len(s) > 1 else "")) def make_timestamp(): tv = time.gmtime(time.time())[:6] @@ -168,8 +204,6 @@ def file_exists(f): except: return False -import opt as opt - def get_from_brain_opt_params(): l,p = opt.from_brain.split(",") return(int(l),p) @@ -209,7 +243,7 @@ def compare_chksums(chk1, desc1, chk2, desc2, hdr="", die_on_fail=False): if die_on_fail: die(3,m) else: - msg(m) + vmsg(m) return False vmsg("%s checksum OK (%s)" % (capfirst(desc1),chk1)) @@ -320,13 +354,13 @@ def get_new_passphrase(desc,passchg=False): w = "{}passphrase for {}".format("new " if passchg else "", desc) if opt.passwd_file: - pw = " ".join(_get_words_from_file(opt.passwd_file,w)) + pw = " ".join(get_words_from_file(opt.passwd_file,w)) elif opt.echo_passphrase: - pw = " ".join(_get_words_from_user("Enter {}: ".format(w))) + pw = " ".join(get_words_from_user("Enter {}: ".format(w))) else: for i in range(g.passwd_max_tries): - pw = " ".join(_get_words_from_user("Enter {}: ".format(w))) - pw2 = " ".join(_get_words_from_user("Repeat passphrase: ")) + pw = " ".join(get_words_from_user("Enter {}: ".format(w))) + pw2 = " ".join(get_words_from_user("Repeat passphrase: ")) dmsg("Passphrases: [%s] [%s]" % (pw,pw2)) if pw == pw2: vmsg("Passphrases match"); break @@ -626,15 +660,16 @@ def get_data_from_wallet(infile,silent=False): return label,metadata,hash_preset,res['salt'],res['enc_seed'] -def _get_words_from_user(prompt): +def get_words_from_user(prompt): # split() also strips words = my_raw_input(prompt, echo=opt.echo_passphrase).split() dmsg("Sanitized input: [%s]" % " ".join(words)) return words -def _get_words_from_file(infile,desc): - qmsg("Getting %s from file '%s'" % (desc,infile)) +def get_words_from_file(infile,desc,silent=False): + if not silent: + qmsg("Getting %s from file '%s'" % (desc,infile)) f = open_file_or_exit(infile, 'r') # split() also strips words = f.read().split() @@ -645,9 +680,9 @@ def _get_words_from_file(infile,desc): def get_words(infile,desc,prompt): if infile: - return _get_words_from_file(infile,desc) + return get_words_from_file(infile,desc) else: - return _get_words_from_user(prompt) + return get_words_from_user(prompt) def remove_comments(lines): # re.sub(pattern, repl, string, count=0, flags=0) @@ -709,26 +744,27 @@ def get_seed_from_seed_data(words): passwd_file_used = False -def mark_passwd_file_as_used(): +def pwfile_reuse_warning(): global passwd_file_used if passwd_file_used: - msg_r("WARNING: Reusing passphrase from file '%s'." % opt.passwd_file) - msg(" This may not be what you want!") + qmsg("Reusing passphrase from file '%s' at user request" % opt.passwd_file) + return True passwd_file_used = True + return False def get_mmgen_passphrase(desc,passchg=False): prompt ="Enter {}passphrase for {}: ".format("old " if passchg else "",desc) if opt.passwd_file: - mark_passwd_file_as_used() - return " ".join(_get_words_from_file(opt.passwd_file,"passphrase")) + pwfile_reuse_warning() + return " ".join(get_words_from_file(opt.passwd_file,"passphrase")) else: - return " ".join(_get_words_from_user(prompt)) + return " ".join(get_words_from_user(prompt)) def get_bitcoind_passphrase(prompt): if opt.passwd_file: - mark_passwd_file_as_used() + pwfile_reuse_warning() return get_data_from_file(opt.passwd_file, "passphrase").strip("\r\n") else: diff --git a/setup.py b/setup.py index 5401f403..2be2f5db 100755 --- a/setup.py +++ b/setup.py @@ -58,8 +58,8 @@ setup( 'mmgen.main_txcreate', 'mmgen.main_txsend', 'mmgen.main_txsign', + 'mmgen.main_wallet', 'mmgen.main_walletchk', - 'mmgen.main_walletconv', 'mmgen.main_walletgen', 'mmgen.share.__init__', diff --git a/test/test-oldscripts.py b/test/test-oldscripts.py new file mode 100755 index 00000000..1a8596c5 --- /dev/null +++ b/test/test-oldscripts.py @@ -0,0 +1,1535 @@ +#!/usr/bin/python + +# Chdir to repo root. +# Since script is not in repo root, fix sys.path so that modules are +# imported from repo, not system. +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 mmgen.globalvars as g +import mmgen.opt as opt +from mmgen.util import mmsg,mdie,Msg,die,capfirst +from mmgen.test import * + +hincog_fn = "rand_data" +hincog_bytes = 1024*1024 +hincog_offset = 98765 +hincog_seedlen = 256 + +incog_id_fn = "incog_id" +non_mmgen_fn = "btckey" + +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 = "brainwallet" +ref_bw_file_spc = "brainwallet-spaced" + +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 = { + '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", + '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", + '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", + 'addrs': "refaddrgen3", + 'akeys.mmenc': "refkeyaddrgen3" + }, + }, + '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': { + '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", + 'dep_generators': { + 'mmdat': "passchg", + }, + }, + '9': { + 'tmpdir': os.path.join("test","tmp9"), + 'tool_enc_infn': "tool_encrypt.in", +# 'tool_enc_ref_infn': "tool_encrypt_ref.in", + '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_data = OrderedDict([ +# test description depends + ['walletgen', (1,'wallet generation', [[[],1]])], +# ['walletchk', (1,'wallet check', [[["mmdat"],1]])], + ['passchg', (5,'password, label and hash preset change',[[["mmdat"],1]])], + ['walletchk_newpass',(5,'wallet check with new pw, label and hash preset',[[["mmdat"],5]])], + ['addrgen', (1,'address generation', [[["mmdat"],1]])], + ['addrimport', (1,'address import', [[["addrs"],1]])], + ['txcreate', (1,'transaction creation', [[["addrs"],1]])], + ['txsign', (1,'transaction signing', [[["mmdat","raw"],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"],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]])], + ['tool_encrypt', (9,"'mmgen-tool encrypt' (random data)", [])], + ['tool_decrypt', (9,"'mmgen-tool decrypt' (random data)", [[[cfgs['9']['tool_enc_infn'],cfgs['9']['tool_enc_infn']+".mmenc"],9]])], +# ['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]])], +]) + +# saved reference data +cmd_data_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"],'new refwallet addr chksum')), + ('refkeyaddrgen', (["mmdat"],'new refwallet key-addr chksum')) +) + +# misc. saved reference data +cmd_data_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_data_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_data_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_groups = OrderedDict([ + ('main', cmd_data.keys()), + ('ref', [c[0]+str(i) for c in cmd_data_ref for i in (1,2,3)]), + ('ref_other', [c[0] for c in cmd_data_ref_other]), + ('conv_in', [c[0]+str(i) for c in cmd_data_conv_in for i in (1,2,3)]), + ('conv_out', [c[0]+str(i) for c in cmd_data_conv_out for i in (1,2,3)]), +]) + +for a,b in cmd_data_ref: + for i,j in (1,128),(2,192),(3,256): + cmd_data[a+str(i)] = (5+i,"%s (%s-bit)" % (b[1],j),[[b[0],5+i]]) + +for a,b in cmd_data_ref_other: + cmd_data[a] = (8,b,[[[],8]]) + +for a,b in cmd_data_conv_in: + for i,j in (1,128),(2,192),(3,256): + cmd_data[a+str(i)] = (10+i,"%s (%s-bit)" % (b,j),[[[],10+i]]) + +for a,b in cmd_data_conv_out: + for i,j in (1,128),(2,192),(3,256): + cmd_data[a+str(i)] = (10+i,"%s (%s-bit)" % (b,j),[[[],10+i]]) + +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.keys(): + 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]], + + ['tool', ("tool_encrypt","tool_decrypt","tool_find_incog_data")], + + ['saved_ref1', [c[0]+"1" for c in cmd_data_ref]], + ['saved_ref2', [c[0]+"2" for c in cmd_data_ref]], + ['saved_ref3', [c[0]+"3" for c in cmd_data_ref]], + + ['saved_ref_other', [c[0] for c in cmd_data_ref_other]], + + ['saved_ref_conv_in1', [c[0]+"1" for c in cmd_data_conv_in]], + ['saved_ref_conv_in2', [c[0]+"2" for c in cmd_data_conv_in]], + ['saved_ref_conv_in3', [c[0]+"3" for c in cmd_data_conv_in]], + + ['saved_ref_conv_out1', [c[0]+"1" for c in cmd_data_conv_out]], + ['saved_ref_conv_out2', [c[0]+"2" for c in cmd_data_conv_out]], + ['saved_ref_conv_out3', [c[0]+"3" for c in cmd_data_conv_out]], +]) + +opts_data = { + '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 tests and commands in the test suite +-n, --names Display command names instead of descriptions +-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 +-v, --verbose Produce more verbose output +""", + 'notes': """ + +If no command is given, the whole suite of tests is run. +""" +} + +cmd_args = opt.opts.init(opts_data) + +if opt.system: sys.path.pop(0) + +# temporary +os.environ["MMGEN_USE_OLD_SCRIPTS"] = "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): + sys.stderr = open("/dev/null","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("AVAILABLE COMMANDS:") + w = max([len(i) for i in cmd_data]) + for cmd in cmd_data: + Msg(fs.format(cmd,cmd_data[cmd][1],w=w)) + + w = max([len(i) for i in meta_cmds]) + Msg("\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_groups.keys()]) + Msg("\nAVAILABLE COMMAND GROUPS:") + for g in cmd_groups.keys(): + Msg(fs.format(g," ".join(cmd_groups[g]),w=w)) + + Msg("\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 pexpect,time,re +from mmgen.util import get_data_from_file,write_to_file,get_lines_from_file + +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 = "" if opt.debug or not s else " " + es = "" if s else " " + 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 = "'" if type(s) == str else "" + + if opt.verbose: msg_r("EXPECT %s" % yellow(quo+str(s)+quo)) + else: msg_r("+") + + try: + if s == '': ret = 0 + else: + f = p.expect if regex else p.expect_exact + ret = f(s,timeout=3) + 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): + + flist = [os.path.join(mydir,f) for f in os.listdir(mydir) + if f == ext or f[-(len(ext)+1):] == "."+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=""): + if not opt.system: + mmgen_cmd = os.path.join(os.curdir,mmgen_cmd) + desc = (cmd_data[name][1],name)[int(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: + msg_r("Testing %s: " % desc) + + if opt.direct_exec: + os.system(" ".join([mmgen_cmd] + cmd_args)) + sys.exit() + else: + self.p = pexpect.spawn(mmgen_cmd,cmd_args) + if opt.exact_output: self.p.logfile = sys.stdout + + def license(self): + 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 " if saved else "") + 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 if overwrite_unlikely else [s1,s2]) + 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 mmgen.rpc.data import TransactionInfo +from decimal import Decimal +from mmgen.bitcoin import verify_addr + +def add_fake_unspent_entry(out,address,comment): + out.append(TransactionInfo( + 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.keys(): + sid = tx_data[s]['sid'] + a = adata.addrinfo(sid) + for idx,btcaddr in a.addrpairs(): + add_fake_unspent_entry(out,btcaddr,"%s:%s Test Wallet" % (sid,idx)) + + 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_to_file(of, hextowif("{:064x}".format(privnum), + compressed=True)+"\n","compressed bitcoin key") + + add_fake_unspent_entry(out,btcaddr,"Non-MMGen address") + +# msg("\n".join([repr(o) for o in out])); sys.exit() + write_to_file(unspent_data_file,repr(out),"Unspent outputs",verbose=True) + + +def add_comments_to_addr_file(addrfile,tfile): + silence() + msg(green("Adding comments to address file '%s'" % addrfile)) + from mmgen.addr import AddrInfo + a = AddrInfo(addrfile) + for i in a.idxs(): a.set_comment(idx,"Test address %s" % idx) + write_to_file(tfile,a.fmt_data(),{}) + end_silence() + +def make_brainwallet_file(fn): + # Print random words with random whitespace in between + from mmgen.mn_tirosh import words + wl = words.split("\n") + 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_to_file(fn,d,"brainwallet password") + +def do_between(): + if opt.pause: + from mmgen.util import keypress_confirm + 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 = True if root else False # 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: + msg("Usage: %s check_deps " % g.prog_name) + sys.exit(1) + + cmd = cmds[0] + + if cmd not in cmd_data: + msg("'%s': unrecognized command" % cmd) + sys.exit(1) + + 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(dirs=[]): + ts = MMGenTestSuite() + dirlist = ts.list_tmp_dirs() + if not dirs: dirs = dirlist.keys() + for d in sorted(dirs): + if d in dirlist: + cleandir(dirlist[d]) + else: + msg("%s: invalid directory number" % d) + sys.exit(1) + +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): + + 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 walletgen(self,name,brain=False,seed_len=None): + + args = ["-d",cfg['tmpdir'],"-p1","-r10"] + if seed_len: args += ["-l",str(seed_len)] +# if 'seed_len' in cfg: args += ["-l",cfg['seed_len']] + if brain: + bwf = os.path.join(cfg['tmpdir'],cfg['bw_filename']) + args += ["-b",cfg['bw_params'],bwf] + make_brainwallet_file(bwf) + + t = MMGenExpect(name,"mmgen-walletgen", args) + t.license() + + if brain: + t.expect( + "A brainwallet will be secure only if you really know what you're doing") + t.expect("Type uppercase 'YES' to confirm: ","YES\n") + + t.usr_rand(10) +# t.usr_rand_out() + + t.passphrase_new("new MMGen wallet",cfg['wpasswd']) + t.written_to_file("Wallet") +# if not brain: +# t.usr_rand_out(saved=True) +# t.label() +# t.written_to_file("MMGen wallet") + ok() + + def refwalletgen(self,name): + label = "test.py ref. wallet (pw '%s', seed len %s)" \ + % (ref_wallet_brainpass,cfg['seed_len']) + bw_arg = "-b%s,%s" % (cfg['seed_len'], ref_wallet_hash_preset) + args = ["-d",cfg['tmpdir'],"-p1","-r10",bw_arg,"-L",label] + d = " (%s-bit seed)" % cfg['seed_len'] + t = MMGenExpect(name,"mmgen-walletgen", args) + t.license() + t.expect("Type uppercase 'YES' to confirm: ","YES\n") + t.expect("passphrase: ",ref_wallet_brainpass+"\n") + t.usr_rand(10) + t.passphrase_new("new MMGen wallet",cfg['wpasswd']) + seed_id = t.written_to_file("Wallet").split("-")[0].split("/")[-1] + refcheck("seed ID",seed_id,cfg['seed_id']) + + refwalletgen1 = refwalletgen2 = refwalletgen3 = refwalletgen + + def passchg(self,name,walletfile): + + t = MMGenExpect(name,"mmgen-passchg", + ["-d",cfg['tmpdir'],"-p","2","-L","New Label","-r","16",walletfile]) +# t.license() + t.passphrase("MMGen wallet",cfgs['1']['wpasswd'],pwtype="old") + t.expect_getend("Label changed: ") + t.expect_getend("Hash preset changed: ") + t.passphrase("MMGen wallet",cfg['wpasswd'],pwtype="new") + t.expect("Repeat passphrase: ",cfg['wpasswd']+"\n") + t.usr_rand(16) + t.expect_getend("Key ID changed: ") + t.written_to_file("Wallet") + ok() + + def walletchk_beg(self,name,args): + t = MMGenExpect(name,"mmgen-walletchk", args) + t.expect("Getting MMGen wallet data from file '%s'" % args[-1]) + t.passphrase("MMGen wallet",cfg['wpasswd']) + t.expect("Passphrase is OK") + t.expect("Wallet is OK") + return t + + def walletchk(self,name,walletfile): + self.walletchk_beg(name,[walletfile]) + ok() + + walletchk_newpass = walletchk + + def addrgen(self,name,walletfile,check_ref=False): + t = MMGenExpect(name,"mmgen-addrgen",["-d",cfg['tmpdir'],walletfile,cfg['addr_idx_list']]) + t.license() + t.passphrase("MMGen wallet",cfg['wpasswd']) + t.expect("Passphrase is OK") + t.expect("[0-9]+ addresses generated",regex=True) + 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") + ok() + + def refaddrgen(self,name,walletfile): + d = " (%s-bit seed)" % cfg['seed_len'] + self.addrgen(name,walletfile,check_ref=True) + + refaddrgen1 = refaddrgen2 = refaddrgen3 = refaddrgen + + def addrimport(self,name,addrfile): + outfile = os.path.join(cfg['tmpdir'],"addrfile_w_comments") + add_comments_to_addr_file(addrfile,outfile) + t = MMGenExpect(name,"mmgen-addrimport",[outfile]) + 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() + from mmgen.util import parse_addr_idxs + 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.keys(): + 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") + + t = MMGenExpect(name,"mmgen-txcreate",cmd_args) + t.license() + for num in tx_data.keys(): + 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.keys(): + 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") + 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) + + def txsign(self,name,txfile,walletfile,save=True): + t = MMGenExpect(name,"mmgen-txsign", + ["-d",cfg['tmpdir'],txfile,walletfile]) + 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.expect("Save signed transaction? (y/N): ","\n") + t.expect("Signed transaction not saved") + 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 export_seed(self,name,walletfile): + t = self.walletchk_beg(name,["-s","-d",cfg['tmpdir'],walletfile]) + f = t.written_to_file("Seed data") + silence() + msg("Seed data: %s" % cyan(get_data_from_file(f,"seed data"))) + end_silence() + ok() + + def export_mnemonic(self,name,walletfile): + t = self.walletchk_beg(name,["-m","-d",cfg['tmpdir'],walletfile]) + f = t.written_to_file("Mnemonic data") + silence() + msg_r("Mnemonic data: %s" % cyan(get_data_from_file(f,"mnemonic data"))) + end_silence() + ok() + + def export_incog(self,name,walletfile,args=["-g"]): + t = MMGenExpect(name,"mmgen-walletchk",args+["-d",cfg['tmpdir'],"-r","10",walletfile]) + t.passphrase("MMGen wallet",cfg['wpasswd']) + t.usr_rand(10) + incog_id = t.expect_getend("Incog ID: ") + write_to_tmpfile(cfg,incog_id_fn,incog_id+"\n") + if args[0] == "-G": return t + t.written_to_file("Incognito wallet data",overwrite_unlikely=True) + ok() + + def export_incog_hex(self,name,walletfile): + self.export_incog(name,walletfile,args=["-X"]) + + # TODO: make outdir and hidden incog compatible (ignore --outdir and warn user?) + def export_incog_hidden(self,name,walletfile): + rf,rd = os.path.join(cfg['tmpdir'],hincog_fn),os.urandom(hincog_bytes) + vmsg(green("Writing %s bytes of data to file '%s'" % (hincog_bytes,rf))) + write_to_file(rf,rd,verbose=opt.verbose) + t = self.export_incog(name,walletfile,args=["-G","%s,%s"%(rf,hincog_offset)]) + t.written_to_file("Data",query="") + ok() + + def addrgen_seed(self,name,walletfile,foo,desc="seed data",arg="-s"): + t = MMGenExpect(name,"mmgen-addrgen", + [arg,"-d",cfg['tmpdir'],walletfile,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) + verify_checksum_or_exit(get_addrfile_checksum(),chk) + t.no_overwrite() + ok() + + def addrgen_mnemonic(self,name,walletfile,foo): + self.addrgen_seed(name,walletfile,foo,desc="mnemonic",arg="-m") + + def addrgen_incog(self,name,walletfile,foo,args=["-g"]): + t = MMGenExpect(name,"mmgen-addrgen",args+["-d", + cfg['tmpdir'],walletfile,cfg['addr_idx_list']]) + t.license() + t.expect_getend("Incog ID: ") + t.passphrase("incognito wallet data \w{8}", cfg['wpasswd']) + t.hash_preset("incog wallet",'1') + vmsg("Comparing generated checksum with checksum from address file") + chk = t.expect_getend(r"Checksum for address data .*?: ",regex=True) + verify_checksum_or_exit(get_addrfile_checksum(),chk) + t.no_overwrite() + ok() + + def addrgen_incog_hex(self,name,walletfile,foo): + self.addrgen_incog(name,walletfile,foo,args=["-X"]) + + def addrgen_incog_hidden(self,name,walletfile,foo): + rf = os.path.join(cfg['tmpdir'],hincog_fn) + self.addrgen_incog(name,walletfile,foo, + args=["-G","%s,%s,%s"%(rf,hincog_offset,hincog_seedlen)]) + + def keyaddrgen(self,name,walletfile,check_ref=False): + t = MMGenExpect(name,"mmgen-keygen", + ["-d",cfg['tmpdir'],walletfile,cfg['addr_idx_list']]) + t.license() + t.expect("Type uppercase 'YES' to confirm: ","YES\n") + 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("Keys") + ok() + + def refkeyaddrgen(self,name,walletfile): + self.keyaddrgen(name,walletfile,check_ref=True) + + refkeyaddrgen1 = refkeyaddrgen2 = refkeyaddrgen3 = refkeyaddrgen + + 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,walletfile): + self.addrgen(name,walletfile) + + 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,walletfile): + self.export_mnemonic(name,walletfile) + + def walletgen3(self,name): + self.walletgen(name) + + def addrgen3(self,name,walletfile): + self.addrgen(name,walletfile) + + 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): + self.walletgen(name,brain=True) + + def addrgen4(self,name,walletfile): + self.addrgen(name,walletfile) + + 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'],"-b",cfg['bw_params'],"-k",non_mm_fn,f1,f2,f3,f4,f5]) + t.license() + t.tx_view() + + for cnum,desc,app in ('1',"incognito","incognito"),('3',"MMGen","MMGen"): + t.expect_getend("Getting %s wallet data from file " % desc) + t.passphrase("%s wallet"%app,cfgs[cnum]['wpasswd']) + if cnum == '1': + t.hash_preset("incog wallet",'1') + + 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) + infn = get_tmpfile_fn(cfg,tmp_fn) + t = MMGenExpect(name,"mmgen-tool",["-d",cfg['tmpdir'],"encrypt",infn]) + 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_to_file(infn,cfg['tool_enc_reftext'],silent=True) +# self.tool_encrypt(name,infn) + + def tool_decrypt(self,name,f1,f2): + of = name + ".out" + t = MMGenExpect(name,"mmgen-tool", + ["-d",cfg['tmpdir'],"decrypt",f2,"outfile="+of,"hash_preset=1"]) + t.passphrase("user data",tool_enc_passwd) + t.written_to_file("Decrypted data") + d1 = read_from_file(f1) + d2 = read_from_file(get_tmpfile_fn(cfg,of)) + cmp_or_die(d1,d2) + + 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]) + o = t.expect_getend("Incog data for ID \w{8} found at offset ",regex=True) + os.unlink(f1) + cmp_or_die(hincog_offset,int(o)) + + def walletconv_out(self,name,desc,out_fmt="w",uopts=[],uopts_chk=[],pw=False): + opts = ["-d",cfg['tmpdir'],"-r10","-p1","-o",out_fmt] + uopts + infile = os.path.join(ref_dir,cfg['seed_id']+".mmwords") + d = "(convert)" + t = MMGenExpect(name,"mmgen-walletconv",opts+[infile],extra_desc=d) + 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: "],"YES\n") + if ret == 0: + t.expect("Enter file size: ","1234\n") + if out_fmt == "w": t.label() + wf = t.written_to_file(capfirst(desc),oo=True) + ok() + + d = "(check)" + if desc == "hidden incognito data": + self.keygen_chksum_chk_hincog(name,cfg['seed_id'],uopts_chk) +# elif pw: +# self.walletchk_chksum_chk(name,wf,cfg['seed_id'],uopts=uopts_chk) + else: + self.keygen_chksum_chk(name,wf,cfg['seed_id'],pw=pw) + + 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)" + t = MMGenExpect(name,"mmgen-walletconv",opts+uopts+if_arg,extra_desc=d) + 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 + d = "(check)" + self.keygen_chksum_chk(name,wf,cfg['seed_id']) + + # 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'],"rand.data") + hi_parms = "%s,%s" % (ic_f,ref_wallet_incog_offset) + hi_parms_legacy = "%s,%s,%s"%(ic_f,ref_wallet_incog_offset,cfg['seed_len']) + self.walletconv_out(name, + "hidden incognito data", "hi", + uopts=["-J",hi_parms] + extra_uopts, + uopts_chk=["-G",hi_parms_legacy], + pw=True + ) + + ref_wallet_conv1 = ref_wallet_conv2 = ref_wallet_conv3 = ref_wallet_conv + ref_mn_conv1 = ref_mn_conv2 = ref_mn_conv3 = ref_mn_conv + ref_seed_conv1 = ref_seed_conv2 = ref_seed_conv3 = ref_seed_conv + ref_brain_conv1 = ref_brain_conv2 = ref_brain_conv3 = ref_brain_conv + ref_incog_conv1 = ref_incog_conv2 = ref_incog_conv3 = ref_incog_conv + ref_incox_conv1 = ref_incox_conv2 = ref_incox_conv3 = ref_incox_conv + ref_hincog_conv1 = ref_hincog_conv2 = ref_hincog_conv3 = ref_hincog_conv + ref_hincog_conv_old1 = ref_hincog_conv_old2 = ref_hincog_conv_old3 = ref_hincog_conv_old + + ref_wallet_conv_out1 = ref_wallet_conv_out2 = ref_wallet_conv_out3 = ref_wallet_conv_out + ref_mn_conv_out1 = ref_mn_conv_out2 = ref_mn_conv_out3 = ref_mn_conv_out + ref_seed_conv_out1 = ref_seed_conv_out2 = ref_seed_conv_out3 = ref_seed_conv_out + ref_incog_conv_out1 = ref_incog_conv_out2 = ref_incog_conv_out3 = ref_incog_conv_out + ref_incox_conv_out1 = ref_incox_conv_out2 = ref_incox_conv_out3 = ref_incox_conv_out + ref_hincog_conv_out1 = ref_hincog_conv_out2 = ref_hincog_conv_out3 = ref_hincog_conv_out + + def ref_wallet_chk(self,name): + wf = os.path.join(ref_dir,cfg['ref_wallet']) + self.walletchk(name,wf) + + ref_wallet_chk1 = ref_wallet_chk2 = ref_wallet_chk3 = ref_wallet_chk + + def ref_seed_chk(self,name,ext=g.seed_ext): + wf = os.path.join(ref_dir,"%s.%s" % (cfg['seed_id'],ext)) + desc = "seed data" if ext == g.seed_ext else "mnemonic" + self.keygen_chksum_chk(name,wf,cfg['seed_id']) + + ref_seed_chk1 = ref_seed_chk2 = ref_seed_chk3 = ref_seed_chk + + def ref_mn_chk(self,name): self.ref_seed_chk(name,ext=g.mn_ext) + + ref_mn_chk1 = ref_mn_chk2 = ref_mn_chk3 = ref_mn_chk + + def ref_brain_chk(self,name,bw_file=ref_bw_file): + wf = os.path.join(ref_dir,bw_file) + arg = "-b%s,%s" % (cfg['seed_len'],ref_bw_hash_preset) + self.keygen_chksum_chk(name,wf,cfg['ref_bw_seed_id'],[arg]) + + def keygen_chksum_chk_hincog(self,name,seed_id,hincog_parm): + t = MMGenExpect(name,"mmgen-keygen", ["-p1","-q","-S","-A"]+hincog_parm+["1"],extra_desc="(check)") + t.passphrase("",cfg['wpasswd']) + t.expect("Encrypt key list? (y/N): ","\n") + t.expect("any printable ASCII symbol.\r\n") + chk = t.readline()[:8] + vmsg("Seed ID: %s" % cyan(chk)) + cmp_or_die(seed_id,chk) + + def keygen_chksum_chk(self,name,wf,seed_id,args=[],pw=False): + hp_arg = ["-p1"] if pw else [] + t = MMGenExpect(name,"mmgen-keygen", ["-q","-S","-A"]+args+hp_arg+[wf,"1"],extra_desc="(check)") + if pw: + t.passphrase("",cfg['wpasswd']) + t.expect("Encrypt key list? (y/N): ","\n") + t.expect("any printable ASCII symbol.\r\n") + chk = t.readline()[:8] + vmsg("Seed ID: %s" % cyan(chk)) + cmp_or_die(seed_id,chk) + + # Use this for encrypted wallets instead of keygen_chksum_chk() + def walletchk_chksum_chk(self,name,wf,seed_id,uopts=[]): + t = MMGenExpect(name,"mmgen-walletchk",["-v", wf]+uopts, + extra_desc="(check)") + t.passphrase("",cfg['wpasswd']) + chk = t.expect_getend("Seed ID checksum OK (")[:8] + t.close() + cmp_or_die(seed_id,chk) + + ref_brain_chk1 = ref_brain_chk2 = ref_brain_chk3 = ref_brain_chk + + def ref_brain_chk_spc3(self,name): + self.ref_brain_chk(name,bw_file=ref_bw_file_spc) + + def ref_hincog_chk(self,name): + for wtype,desc,earg in ('hic_wallet','',[]), \ + ('hic_wallet_old','(old format)',["-o"]): + ic_arg = "%s,%s,%s" % ( + os.path.join(ref_dir,cfg[wtype]), + ref_wallet_incog_offset,cfg['seed_len'] + ) + t = MMGenExpect(name,"mmgen-keygen", + ["-q","-A"]+earg+["-G"]+[ic_arg]+['1'],extra_desc=desc) + t.passphrase("incognito wallet",cfg['wpasswd']) + t.hash_preset("incog wallet","1") + if wtype == 'hic_wallet_old': + t.expect("Is the seed ID correct? (Y/n): ","\n") + chk = t.expect_getend("Valid incog data for seed ID ") + t.close() + cmp_or_die(cfg['seed_id'],chk) + + ref_hincog_chk1 = ref_hincog_chk2 = ref_hincog_chk3 = ref_hincog_chk + + def ref_addrfile_chk(self,name,ftype="addr"): + wf = os.path.join(ref_dir,cfg['ref_'+ftype+'file']) + t = MMGenExpect(name,"mmgen-tool",[ftype+"file_chksum",wf]) + 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.expect_getend("Checksum for .*address data .*: ",regex=True) + 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']) + self.txsign(name,tf,wf,save=False) + + def ref_tool_decrypt(self,name): + f = os.path.join(ref_dir,ref_enc_fn) + t = MMGenExpect(name,"mmgen-tool", + ["-q","decrypt",f,"outfile=-","hash_preset=1"]) + 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) + +# 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() + +for cfg in sorted(cfgs): mk_tmpdir(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 arg in meta_cmds: + for cmd in meta_cmds[arg]: + check_needs_rerun(ts,cmd,build=True) + elif arg in cmd_groups.keys(): + for cmd in cmd_groups[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: + 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))) diff --git a/test/test.py b/test/test.py index a3867b58..6cc32545 100755 --- a/test/test.py +++ b/test/test.py @@ -10,7 +10,7 @@ sys.path.__setitem__(0,os.path.abspath(os.curdir)) import mmgen.globalvars as g import mmgen.opt as opt -from mmgen.util import mmsg,mdie,Msg,die +from mmgen.util import mmsg,mdie,Msg,die,capfirst from mmgen.test import * hincog_fn = "rand_data" @@ -376,6 +376,7 @@ opts_data = { debugging only) -e, --exact-output Show the exact output of the MMGen script(s) being run -l, --list-cmds List and describe the tests and commands in the test suite +-n, --names Display command names instead of descriptions -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 @@ -392,6 +393,9 @@ cmd_args = opt.opts.init(opts_data) if opt.system: sys.path.pop(0) +# temporary +#os.environ["MMGEN_USE_OLD_SCRIPTS"] = "1" + if opt.debug_scripts: os.environ["MMGEN_DEBUG"] = "1" if opt.buf_keypress: @@ -524,15 +528,15 @@ class MMGenExpect(object): def __init__(self,name,mmgen_cmd,cmd_args=[],extra_desc=""): if not opt.system: mmgen_cmd = os.path.join(os.curdir,mmgen_cmd) - desc = cmd_data[name][1] + desc = (cmd_data[name][1],name)[int(bool(opt.names))] if extra_desc: desc += " " + extra_desc if opt.verbose or opt.exact_output: sys.stderr.write( - green("Testing %s\nExecuting " % desc) + + green("Testing: %s\nExecuting " % desc) + cyan("'%s %s'\n" % (mmgen_cmd," ".join(cmd_args))) ) else: - msg_r("Testing %s " % (desc+":")) + msg_r("Testing %s: " % desc) if opt.direct_exec: os.system(" ".join([mmgen_cmd] + cmd_args)) @@ -545,6 +549,14 @@ class MMGenExpect(object): 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 " if saved else "") + 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') @@ -645,8 +657,9 @@ def create_fake_unspent_data(adata,unspent_data_file,tx_data,non_mmgen_input='') for s in tx_data.keys(): sid = tx_data[s]['sid'] a = adata.addrinfo(sid) - for idx,btcaddr in a.addrpairs(): - add_fake_unspent_entry(out,btcaddr,"%s:%s Test Wallet" % (sid,idx)) + for n,(idx,btcaddr) in enumerate(a.addrpairs(),1): + lbl = (""," addr %02i" % n)[int(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 @@ -821,47 +834,32 @@ class MMGenTestSuite(object): def generate_cmd_deps(self,fdeps): return [cfgs[str(n)]['dep_generators'][ext] for n,ext in fdeps] - def walletgen(self,name,brain=False,seed_len=None): - + def walletgen(self,name,seed_len=None): args = ["-d",cfg['tmpdir'],"-p1","-r10"] if seed_len: args += ["-l",str(seed_len)] -# if 'seed_len' in cfg: args += ["-l",cfg['seed_len']] - if brain: - bwf = os.path.join(cfg['tmpdir'],cfg['bw_filename']) - args += ["-b",cfg['bw_params'],bwf] - make_brainwallet_file(bwf) - t = MMGenExpect(name,"mmgen-walletgen", args) t.license() - - if brain: - t.expect( - "A brainwallet will be secure only if you really know what you're doing") - t.expect("Type uppercase 'YES' to confirm: ","YES\n") - t.usr_rand(10) - for s in "user-supplied entropy","saved user-supplied entropy": - t.expect("Generating encryption key from OS random data plus %s" % s) - if brain: break - t.passphrase_new("new MMGen wallet",cfg['wpasswd']) - t.written_to_file("Wallet") + t.label() + t.written_to_file("MMGen wallet") ok() - def refwalletgen(self,name): + 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']) - bw_arg = "-b%s,%s" % (cfg['seed_len'], ref_wallet_hash_preset) - args = ["-d",cfg['tmpdir'],"-p1","-r10",bw_arg,"-L",label] - d = " (%s-bit seed)" % cfg['seed_len'] - t = MMGenExpect(name,"mmgen-walletgen", args) + % (ref_wallet_brainpass,cfg['seed_len']) + args = ["-d",cfg['tmpdir'],hp_arg,"-r10",sl_arg,"-ib","-L",label] + t = MMGenExpect(name,"mmgen-walletconv", args) t.license() - t.expect("Type uppercase 'YES' to confirm: ","YES\n") - t.expect("passphrase: ",ref_wallet_brainpass+"\n") - t.usr_rand(10) + t.expect("Enter brainwallet: ", ref_wallet_brainpass+"\n") t.passphrase_new("new MMGen wallet",cfg['wpasswd']) - seed_id = t.written_to_file("Wallet").split("-")[0].split("/")[-1] - refcheck("seed ID",seed_id,cfg['seed_id']) + 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) refwalletgen1 = refwalletgen2 = refwalletgen3 = refwalletgen @@ -869,22 +867,23 @@ class MMGenTestSuite(object): t = MMGenExpect(name,"mmgen-passchg", ["-d",cfg['tmpdir'],"-p","2","-L","New Label","-r","16",walletfile]) + t.license() t.passphrase("MMGen wallet",cfgs['1']['wpasswd'],pwtype="old") - t.expect_getend("Label changed: ") - t.expect_getend("Hash preset changed: ") + 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("Key ID changed: ") - t.written_to_file("Wallet") + t.expect_getend("Label changed to ") +# t.expect_getend("Key ID changed: ") + t.written_to_file("MMGen wallet") ok() def walletchk_beg(self,name,args): t = MMGenExpect(name,"mmgen-walletchk", args) - t.expect("Getting MMGen wallet data from file '%s'" % args[-1]) + t.expect("Getting MMGen wallet from file '%s'" % args[-1]) t.passphrase("MMGen wallet",cfg['wpasswd']) t.expect("Passphrase is OK") - t.expect("Wallet is OK") + t.expect_getend("Valid MMGen wallet for seed ID ") return t def walletchk(self,name,walletfile): @@ -1036,53 +1035,67 @@ class MMGenTestSuite(object): vmsg("This is a simulation; no transaction was sent") ok() - def export_seed(self,name,walletfile): - t = self.walletchk_beg(name,["-s","-d",cfg['tmpdir'],walletfile]) - f = t.written_to_file("Seed data") - silence() - msg("Seed data: %s" % cyan(get_data_from_file(f,"seed data"))) - end_silence() - ok() - - def export_mnemonic(self,name,walletfile): - t = self.walletchk_beg(name,["-m","-d",cfg['tmpdir'],walletfile]) - f = t.written_to_file("Mnemonic data") - silence() - msg_r("Mnemonic data: %s" % cyan(get_data_from_file(f,"mnemonic data"))) - end_silence() - ok() - - def export_incog(self,name,walletfile,args=["-g"]): - t = MMGenExpect(name,"mmgen-walletchk",args+["-d",cfg['tmpdir'],"-r","10",walletfile]) + 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']) - t.usr_rand(10) - incog_id = t.expect_getend("Incog ID: ") - write_to_tmpfile(cfg,incog_id_fn,incog_id+"\n") - if args[0] == "-G": return t - t.written_to_file("Incognito wallet data",overwrite_unlikely=True) + 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_incog_hex(self,name,walletfile): - self.export_incog(name,walletfile,args=["-X"]) + 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,walletfile): - rf,rd = os.path.join(cfg['tmpdir'],hincog_fn),os.urandom(hincog_bytes) - vmsg(green("Writing %s bytes of data to file '%s'" % (hincog_bytes,rf))) - write_to_file(rf,rd,verbose=opt.verbose) - t = self.export_incog(name,walletfile,args=["-G","%s,%s"%(rf,hincog_offset)]) - t.written_to_file("Data",query="") - ok() + 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,walletfile,foo,desc="seed data",arg="-s"): - t = MMGenExpect(name,"mmgen-addrgen", + stdout = (False,True)[int(desc=="seed data")] #capture output to screen once + add_arg = ([],["-S"])[int(stdout)] + t = MMGenExpect(name,"mmgen-addrgen", add_arg + [arg,"-d",cfg['tmpdir'],walletfile,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() +# t.no_overwrite() ok() def addrgen_mnemonic(self,name,walletfile,foo): @@ -1097,8 +1110,9 @@ class MMGenTestSuite(object): t.hash_preset("incog wallet",'1') 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() +# t.no_overwrite() ok() def addrgen_incog_hex(self,name,walletfile,foo): @@ -1180,8 +1194,20 @@ class MMGenTestSuite(object): self.txsign_end(t) ok() - def walletgen4(self,name): - self.walletgen(name,brain=True) + def brainwalletgen_pwfile(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 walletgen4(self,name): self.brainwalletgen_pwfile(name) def addrgen4(self,name,walletfile): self.addrgen(name,walletfile) @@ -1239,30 +1265,33 @@ class MMGenTestSuite(object): vmsg("Incog ID: %s" % cyan(i_id)) t = MMGenExpect(name,"mmgen-tool", ["-d",cfg['tmpdir'],"find_incog_data",f1,i_id]) - o = t.expect_getend("Incog data for ID \w{8} found at offset ",regex=True) + 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 walletconv_out(self,name,desc,out_fmt="w",uopts=[],uopts_chk=[],pw=False): opts = ["-d",cfg['tmpdir'],"-r10","-p1","-o",out_fmt] + uopts infile = os.path.join(ref_dir,cfg['seed_id']+".mmwords") - d = "(convert)" - t = MMGenExpect(name,"mmgen-walletconv",opts+[infile],extra_desc=d) + t = MMGenExpect(name,"mmgen-walletconv",opts+[infile],extra_desc="(convert)") 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: "],"YES\n") + ret = t.expect(["Create? (Y/n): ","'YES' to confirm: "]) if ret == 0: - t.expect("Enter file size: ","1234\n") - wf = t.written_to_file(desc[0].upper()+desc[1:],oo=True) + 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) ok() - - d = "(check)" if desc == "hidden incognito data": self.keygen_chksum_chk_hincog(name,cfg['seed_id'],uopts_chk) -# elif pw: -# self.walletchk_chksum_chk(name,wf,cfg['seed_id'],uopts=uopts_chk) else: self.keygen_chksum_chk(name,wf,cfg['seed_id'],pw=pw) @@ -1389,7 +1418,7 @@ class MMGenTestSuite(object): t = MMGenExpect(name,"mmgen-keygen", ["-p1","-q","-S","-A"]+hincog_parm+["1"],extra_desc="(check)") t.passphrase("",cfg['wpasswd']) t.expect("Encrypt key list? (y/N): ","\n") - t.expect("any printable ASCII symbol.\r\n") + t.expect("ignored by MMGen.\r\n") chk = t.readline()[:8] vmsg("Seed ID: %s" % cyan(chk)) cmp_or_die(seed_id,chk) @@ -1400,7 +1429,7 @@ class MMGenTestSuite(object): if pw: t.passphrase("",cfg['wpasswd']) t.expect("Encrypt key list? (y/N): ","\n") - t.expect("any printable ASCII symbol.\r\n") + t.expect("ignored by MMGen.\r\n") chk = t.readline()[:8] vmsg("Seed ID: %s" % cyan(chk)) cmp_or_die(seed_id,chk)