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)