Browse Source

test/test.py:
To make sure backwards compatibility is maintained, added reference files
and tests for all MMGen wallet formats, tx, address and key-address files.

philemon 10 years ago
parent
commit
4a93cdee6a
42 changed files with 561 additions and 267 deletions
  1. 40 24
      mmgen/addr.py
  2. 6 9
      mmgen/config.py
  3. 10 5
      mmgen/crypto.py
  4. 1 1
      mmgen/filename.py
  5. 2 2
      mmgen/license.py
  6. 11 16
      mmgen/main_addrgen.py
  7. 4 4
      mmgen/main_addrimport.py
  8. 7 7
      mmgen/main_passchg.py
  9. 3 2
      mmgen/main_tool.py
  10. 18 15
      mmgen/main_txcreate.py
  11. 2 1
      mmgen/main_txsend.py
  12. 36 28
      mmgen/main_txsign.py
  13. 5 5
      mmgen/main_walletchk.py
  14. 12 9
      mmgen/main_walletgen.py
  15. 1 1
      mmgen/mn_electrum.py
  16. 4 5
      mmgen/opts.py
  17. 7 5
      mmgen/seed.py
  18. 4 3
      mmgen/test.py
  19. 7 5
      mmgen/tool.py
  20. 16 11
      mmgen/tx.py
  21. 4 2
      mmgen/util.py
  22. 6 0
      test/ref/1378FC64-6F0F9BB4[192,1].mmdat
  23. BIN
      test/ref/1378FC64-B55E9958-77256FC1[192:1].incog.offset123
  24. BIN
      test/ref/1378FC64-B55E9958-D85FF20C[192:1].incog-old.offset123
  25. 1 0
      test/ref/1378FC64.mmseed
  26. 1 0
      test/ref/1378FC64.mmwords
  27. 6 0
      test/ref/98831F3A-27F2BF93[256,1].mmdat
  28. BIN
      test/ref/98831F3A-F59B07A0-559CEF19[256:1].incog.offset123
  29. BIN
      test/ref/98831F3A-F59B07A0-848535F3[256:1].incog-old.offset123
  30. 1 0
      test/ref/98831F3A.mmseed
  31. 1 0
      test/ref/98831F3A.mmwords
  32. 19 0
      test/ref/98831F3A[1,31-33,500-501,1010-1011].addrs
  33. BIN
      test/ref/98831F3A[1,31-33,500-501,1010-1011].akeys.mmenc
  34. 6 0
      test/ref/FE3C6545-161E495F-9860A85B[128:1].incog-old.offset123
  35. BIN
      test/ref/FE3C6545-161E495F-BEB7548E[128:1].incog-offset123
  36. 6 0
      test/ref/FE3C6545-D782B529[128,1].mmdat
  37. 1 0
      test/ref/FE3C6545.mmseed
  38. 1 0
      test/ref/FE3C6545.mmwords
  39. 1 0
      test/ref/brainwallet
  40. 11 0
      test/ref/brainwallet-spaced
  41. 5 0
      test/ref/tx_FFB367[1.234].raw
  42. 295 107
      test/test.py

+ 40 - 24
mmgen/addr.py

@@ -29,24 +29,27 @@ from mmgen.bitcoin import numtowif
 # from mmgen.util import msg,qmsg,qmsg_r,make_chksum_N,get_lines_from_file,get_data_from_file,get_extension
 from mmgen.util import *
 from mmgen.tx import *
+from mmgen.obj import *
 import mmgen.config as g
 import mmgen.opt as opt
 
+pnm = g.proj_name
+
 addrmsgs = {
 	'addrfile_header': """
-# MMGen address file
+# {pnm} address file
 #
 # This file is editable.
 # Everything following a hash symbol '#' is a comment and ignored by {pnm}.
-# A text label of {} characters or less may be added to the right of each
+# A text label of {n} characters or less may be added to the right of each
 # address, and it will be appended to the bitcoind wallet label upon import.
 # The label may contain any printable ASCII symbol.
-""".strip().format(g.max_addr_label_len,pnm=g.proj_name),
+""".strip().format(n=g.max_addr_label_len,pnm=pnm),
 	'no_keyconv_msg': """
-Executable '{kcexe}' unavailable. Falling back on (slow) internal ECDSA library.
-Please install '{kcexe}' from the {vanityg} package on your system for much
+Executable '{kconv}' unavailable. Falling back on (slow) internal ECDSA library.
+Please install '{kconv}' from the {vgen} package on your system for much
 faster address generation.
-""".format(kcexe=g.keyconv_exec, vanityg="vanitygen")
+""".format(kconv=g.keyconv_exec, vgen="vanitygen")
 }
 
 def test_for_keyconv(silent=False):
@@ -61,7 +64,7 @@ def test_for_keyconv(silent=False):
 	return True
 
 
-def generate_addrs(seed, addrnums):
+def generate_addrs(seed, addrnums, source="addrgen"):
 
 	from util import make_chksum_8
 	seed_id = make_chksum_8(seed) # Must do this before seed gets clobbered
@@ -116,7 +119,7 @@ def generate_addrs(seed, addrnums):
 
 	m = w[0] if t_addrs == 1 else w[0]+w[1]
 	qmsg("\r%s: %s %s generated%s" % (seed_id,t_addrs,m," "*15))
-	a = AddrInfo(has_keys='k' in opt.gen_what)
+	a = AddrInfo(has_keys='k' in opt.gen_what, source=source)
 	a.initialize(seed_id,out)
 	return a
 
@@ -182,7 +185,7 @@ def _parse_addrfile(fn,buf=[],has_keys=False,exit_on_error=True):
 		elif cbrace != '}':
 			errmsg = "'%s': invalid last line" % cbrace
 		elif not is_mmgen_seed_id(sid):
-			errmsg = "'%s': invalid Seed ID" % sid
+			errmsg = "'%s': invalid seed ID" % sid
 		else:
 			ret = _parse_addrfile_body(lines[1:-1],has_keys)
 			if type(ret) == list: return sid,ret
@@ -196,7 +199,7 @@ def _parse_addrfile(fn,buf=[],has_keys=False,exit_on_error=True):
 
 
 def _parse_keyaddr_file(infile):
-	d = get_data_from_file(infile,"%s key-address file data" % g.proj_name)
+	d = get_data_from_file(infile,"{pnm} key-address file data".format(pnm=pnm))
 	enc_ext = get_extension(infile) == g.mmenc_ext
 	if enc_ext or not is_utf8(d):
 		m = "Decrypting" if enc_ext else "Attempting to decrypt"
@@ -206,7 +209,7 @@ def _parse_keyaddr_file(infile):
 	return _parse_addrfile("",buf=d,has_keys=True,exit_on_error=False)
 
 
-class AddrInfoList(object):
+class AddrInfoList(MMGenObject):
 
 	def __init__(self,addrinfo=None,bitcoind_connection=None):
 		self.data = {}
@@ -239,7 +242,8 @@ class AddrInfoList(object):
 				a.idx,a.addr,a.comment = \
 					int(idx),unicode(addrlist[0]),unicode(comment)
 				data[seed_id].append(a)
-		vmsg("%s %s addresses found, %s accounts total" % (i,g.proj_name,len(accts)))
+		vmsg("{n} {pnm} addresses found, {m} accounts total".format(
+				n=i,pnm=pnm,m=len(accts)))
 		for sid in data:
 			self.add(AddrInfo(sid=sid,adata=data[sid]))
 
@@ -257,22 +261,25 @@ class AddrInfoList(object):
 			d.update(self.data[k].make_reverse_dict(btcaddrs))
 		return d
 
-class AddrInfoEntry(object):
+class AddrInfoEntry(MMGenObject):
 
 	def __init__(self): pass
 
-class AddrInfo(object):
+class AddrInfo(MMGenObject):
 
-	def __init__(self,addrfile="",has_keys=False,sid="",adata=[]):
-		self.has_keys=has_keys
+	def __init__(self,addrfile="",has_keys=False,sid="",adata=[], source=""):
+		self.has_keys = has_keys
+		do_chksum = True
 		if addrfile:
 			f = _parse_keyaddr_file if has_keys else _parse_addrfile
 			sid,adata = f(addrfile)
+			self.source = "addrfile"
 		elif sid and adata: # data from wallet
-			pass
+			self.source = "wallet"
 		elif sid or adata:
 			die(3,"Must specify address file, or seed_id + adata")
 		else:
+			self.source = source if source else "unknown"
 			return
 
 		self.initialize(sid,adata)
@@ -284,12 +291,21 @@ class AddrInfo(object):
 		self.seed_id = seed_id
 		self.addrdata = addrdata
 		self.num_addrs = len(addrdata)
-		self.make_addrdata_chksum()
-		self.fmt_addr_idxs()
-		w = "key" if self.has_keys else "addr"
-		qmsg("Computed checksum for %s data %s[%s]: %s" %
-				(w,self.seed_id,self.idxs_fmt,self.checksum))
-		qmsg("Check this value against your records")
+		if self.source in ("wallet","txsign") or \
+				(self.source == "addrgen" and opt.gen_what == "k"):
+			self.checksum = None
+			self.idxs_fmt = None
+		else: # self.source in addrfile, addrgen
+			self.make_addrdata_chksum()
+			self.fmt_addr_idxs()
+			w = "key-address" if self.has_keys else "address"
+			qmsg("Checksum for %s data %s[%s]: %s" %
+					(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")
+			elif self.source == "addrfile":
+				qmsg("Check this value against your records")
 
 	def idxs(self):
 		return [e.idx for e in self.addrdata]
@@ -337,7 +353,7 @@ class AddrInfo(object):
 
 	def make_addrdata_chksum(self):
 		nchars = 24
-		lines = [" ".join([str(e.idx),e.addr]+([e.wif] if self.has_keys else []))
+		lines=[" ".join([str(e.idx),e.addr]+([e.wif] if self.has_keys else []))
 						for e in self.addrdata]
 		self.checksum = make_chksum_N(" ".join(lines), nchars, sep=True)
 

+ 6 - 9
mmgen/config.py

@@ -56,7 +56,6 @@ required_opts = [
 	"usr_randchars","stdout","show_hash_presets"
 ]
 min_screen_width = 80
-max_tx_comment_len = 72
 
 wallet_ext    = "mmdat"
 seed_ext      = "mmseed"
@@ -84,12 +83,11 @@ default_wordlist    = "electrum"
 dfl_vars = "seed_len","hash_preset","usr_randchars","debug"
 
 seed_lens = 128,192,256
-
 mn_lens = [i / 32 * 3 for i in seed_lens]
 
 keyconv_exec = "keyconv"
 
-mins_per_block   = 8.5
+mins_per_block   = 9
 passwd_max_tries = 5
 
 max_urandchars,min_urandchars = 80,10
@@ -113,16 +111,15 @@ hash_presets = {
 
 mmgen_idx_max_digits = 7
 
-from string import ascii_letters, digits
+printable_nonl = [chr(i+32) for i in range(95)]
+printable = printable_nonl + ['\n','\t']
 
-addr_label_symbols = tuple([chr(i) for i in range(0x20,0x7f)])
-max_addr_label_len = 32
+addr_label_symbols = wallet_label_symbols = printable_nonl
 
-wallet_label_symbols = addr_label_symbols
+max_addr_label_len = 32
 max_wallet_label_len = 48
+max_tx_comment_len = 72   # Comment is b58 encoded, so can permit all UTF-8
 
-printable_nospc = [chr(i+33) for i in range(94)]
-printable       = printable_nospc + [' ','\n','\t']
 #addr_label_punc = ".","_",",","-"," ","(",")"
 #addr_label_symbols = tuple(ascii_letters + digits) + addr_label_punc
 #wallet_label_punc = addr_label_punc

+ 10 - 5
mmgen/crypto.py

@@ -218,7 +218,7 @@ def get_random(length):
 
 def get_seed_from_wallet(
 		infile,
-		prompt_info="{} wallet".format(g.proj_name),
+		prompt_info="{pnm} wallet".format(pnm=g.proj_name),
 		silent=False
 		):
 
@@ -270,7 +270,7 @@ def confirm_old_format():
 
 def get_seed_from_incog_wallet(
 		infile,
-		prompt_info="{} incognito wallet".format(g.proj_name),
+		prompt_info="{pnm} incognito wallet".format(pnm=g.proj_name),
 		silent=False,
 		hex_input=False
 	):
@@ -305,7 +305,7 @@ def get_seed_from_incog_wallet(
 
 	incog_id = make_iv_chksum(iv)
 	msg("Incog ID: %s (IV ID: %s)" % (incog_id,make_chksum_8(iv)))
-	qmsg("Check the applicable value against your records.")
+	qmsg("Check the applicable value against your records")
 	vmsg(crmsg['incog_iv_id_hidden' if opt.from_incog_hidden
 			else 'incog_iv_id'])
 
@@ -328,7 +328,7 @@ def get_seed_from_incog_wallet(
 		old_fmt_sid = make_chksum_8(seed)
 
 		def confirm_correct_seed_id(sid):
-			m = "Seed ID: %s.  Is the Seed ID correct?" % sid
+			m = "Seed ID: %s.  Is the seed ID correct?" % sid
 			return keypress_confirm(m, True)
 
 		if opt.old_incog_fmt:
@@ -344,6 +344,7 @@ def get_seed_from_incog_wallet(
 				if confirm_correct_seed_id(old_fmt_sid):
 					break
 
+	msg("Valid incog data for seed ID %s" % make_chksum_8(seed))
 	return seed
 
 
@@ -412,7 +413,7 @@ def get_seed_retry(infile,seed_id=""):
 		if seed: return seed
 
 
-def _get_seed_from_brain_passphrase(words):
+def _get_seed_from_brain_passphrase(words,silent=False):
 	bp = " ".join(words)
 	if opt.debug: Msg("Sanitized brain passphrase: %s" % bp)
 	seed_len,hash_preset = get_from_brain_opt_params()
@@ -421,6 +422,10 @@ def _get_seed_from_brain_passphrase(words):
 	# Use buflen arg of scrypt.hash() to get seed of desired length
 	seed = scrypt_hash_passphrase(bp, "", hash_preset, buflen=seed_len/8)
 	vmsg("Done")
+
+	if not silent:
+		msg("Valid brainwallet for seed ID %s" % make_chksum_8(seed))
+
 	return seed
 
 

+ 1 - 1
mmgen/filename.py

@@ -20,7 +20,7 @@
 filename.py:  Filename class and methods for the MMGen suite
 """
 import sys,os
-from mmgen.obj import MMGenObject
+from mmgen.obj import *
 import mmgen.config as g
 from mmgen.util import msg
 

+ 2 - 2
mmgen/license.py

@@ -23,10 +23,10 @@ import mmgen.config as g
 
 gpl = {
 	'warning': """
-  MMGen Copyright (C) {g.Cdates} by {g.author} {g.email}.  This
+  {pnm} Copyright (C) {g.Cdates} by {g.author} {g.email}.  This
   program comes with ABSOLUTELY NO WARRANTY.  This is free software, and
   you are welcome to redistribute it under certain conditions.
-""".format(g=g),
+""".format(g=g,pnm=g.proj_name),
 	'prompt': """
 Press 'w' for conditions and warranty info, or 'c' to continue:
 """,

+ 11 - 16
mmgen/main_addrgen.py

@@ -32,8 +32,8 @@ from mmgen.addr import *
 what = "keys" if sys.argv[0].split("-")[-1] == "keygen" else "addresses"
 
 opts_data = {
-	'desc': """Generate a range or list of {} from an {g.proj_name} wallet,
-                  mnemonic, seed or password""".format(what,g=g),
+	'desc': """Generate a range or list of {w} from an {pnm} wallet,
+                  mnemonic, seed or password""".format(w=what,pnm=g.proj_name),
 	'usage':"[opts] [infile] <address range or list>",
 	'options': """
 -h, --help              Print this help message{}
@@ -47,7 +47,7 @@ opts_data = {
                         (default: {g.seed_len})
 -p, --hash-preset=  p   Use scrypt.hash() parameters from preset 'p' when
                         hashing password (default: '{g.hash_preset}')
--P, --passwd-file=  f   Get MMGen wallet passphrase from file 'f'
+-P, --passwd-file=  f   Get {pnm} wallet passphrase from file 'f'
 -q, --quiet             Suppress warnings; overwrite files without
                         prompting
 -S, --stdout            Print {what} to stdout
@@ -71,7 +71,7 @@ opts_data = {
 			)
 		if what == "keys" else ("","")),
 		seed_lens=", ".join([str(i) for i in g.seed_lens]),
-		what=what, g=g
+		what=what,g=g,pnm=g.proj_name
 ),
 	'notes': """
 
@@ -107,9 +107,9 @@ invocations with that passphrase
 
 wmsg = {
 	'unencrypted_secret_keys': """
-This program generates secret keys from your {} seed, outputting them in
+This program generates secret keys from your {pnm} seed, outputting them in
 UNENCRYPTED form.  Generate only the key(s) you need and guard them carefully.
-""".format(g.proj_name),
+""".format(pnm=g.proj_name),
 }
 
 cmd_args = opt.opts.init(opts_data,add_opts=["b16"])
@@ -136,25 +136,20 @@ if what == "keys" and not opt.quiet:
 
 # Generate data:
 
-seed    = get_seed_retry(infile)
+seed = get_seed_retry(infile)
 
 opt.gen_what = "a" if what == "addresses" else (
 	"k" if opt.no_addresses else "ka")
 
-ainfo = generate_addrs(seed, addr_idxs)
+ainfo = generate_addrs(seed,addr_idxs)
 
 addrdata_str = ainfo.fmt_data()
 outfile_base = "{}[{}]".format(make_chksum_8(seed), ainfo.idxs_fmt)
 
-if 'a' in opt.gen_what:
+if 'a' in opt.gen_what and opt.save_checksum:
 	w = "key-address" if 'k' in opt.gen_what else "address"
-	qmsg("Checksum for %s data %s: %s" % (w,outfile_base,ainfo.checksum))
-	if opt.save_checksum:
-		write_to_file(outfile_base+"."+g.addrfile_chksum_ext,
-			ainfo.checksum+"\n","%s data checksum" % w,True,True,False)
-	else:
-		qmsg("This checksum will be used to verify the %s file in the future."%w)
-		qmsg("Record it to a safe location.")
+	write_to_file(outfile_base+"."+g.addrfile_chksum_ext,
+		ainfo.checksum+"\n","%s data checksum" % w,True,True,False)
 
 if 'k' in opt.gen_what and keypress_confirm("Encrypt key list?"):
 	addrdata_str = mmgen_encrypt(addrdata_str,"new key list","")

+ 4 - 4
mmgen/main_addrimport.py

@@ -53,7 +53,7 @@ if len(cmd_args) == 1:
 	check_infile(infile)
 	if opt.addrlist:
 		lines = get_lines_from_file(
-			infile,"non-{} addresses".format(g.proj_name),trim_comments=True)
+			infile,"non-{pnm} addresses".format(pnm=g.proj_name),trim_comments=True)
 		ai,adata = AddrInfo(),[]
 		for btcaddr in lines:
 			a = AddrInfoEntry()
@@ -64,9 +64,9 @@ if len(cmd_args) == 1:
 		ai = AddrInfo(infile,has_keys=opt.keyaddr_file)
 else:
 	msg("""
-"You must specify an mmgen address file (or a list of non-%s addresses
+"You must specify an mmgen address file (or a list of non-{pnm} addresses
 with the '--addrlist' option)
-""".strip() % g.proj_name)
+""".strip().format(pnm=g.proj_name))
 	sys.exit(1)
 
 from mmgen.bitcoin import verify_addr
@@ -124,7 +124,7 @@ for n,e in enumerate(ai.addrdata):
 	if e.idx:
 		label = "%s:%s" % (ai.seed_id,e.idx)
 		if e.comment: label += " " + e.comment
-	else: label = "non-%s" % g.proj_name
+	else: label = "non-{pnm}".format(pnm=g.proj_name)
 
 	if opt.rescan:
 		t = threading.Thread(target=import_address, args=(e.addr,label,True))

+ 7 - 7
mmgen/main_passchg.py

@@ -28,8 +28,8 @@ import mmgen.config as g
 import mmgen.opt as opt
 
 opts_data = {
-	'desc':  """Change the passphrase, hash preset or label of an {}
-                  deterministic wallet""".format(g.proj_name),
+	'desc':  """Change the passphrase, hash preset or label of an {pnm}
+                  deterministic wallet""".format(pnm=g.proj_name),
 	'usage':   "[opts] [filename]",
 	'options': """
 -h, --help                Print this help message
@@ -40,13 +40,13 @@ opts_data = {
 -L, --label=            l Change the wallet's label to 'l'
 -p, --hash-preset=      p Change scrypt.hash() parameters to preset 'p'
                           (default: '{g.hash_preset}')
--P, --passwd-file=      f Get new MMGen wallet passphrase from file 'f'
+-P, --passwd-file=      f Get new {pnm} wallet passphrase from file 'f'
 -r, --usr-randchars=    n Get 'n' characters of additional randomness from
                           user (min={g.min_urandchars}, max={g.max_urandchars})
 -q, --quiet               Suppress warnings; overwrite files without
                           prompting
 -v, --verbose             Produce more verbose output
-""".format(g=g),
+""".format(g=g,pnm=g.proj_name),
 	'notes': """
 
 NOTE: The key ID will change if either the passphrase or hash preset are
@@ -67,7 +67,7 @@ seed_id,key_id = metadata[:2]
 
 # Repeat on incorrect pw entry
 while True:
-	p = "{} wallet".format(g.proj_name)
+	p = "{pnm} wallet".format(pnm=g.proj_name)
 	passwd = get_mmgen_passphrase(p,not opt.keep_old_passphrase)
 	key = make_key(passwd, salt, hash_preset)
 	seed = decrypt_seed(enc_seed, key, seed_id, key_id)
@@ -85,7 +85,7 @@ else: opt.label = label  # Copy the old label
 
 if opt.hash_preset:
 	if hash_preset != opt.hash_preset:
-		qmsg("Hash preset has changed (%s -> %s)" %
+		qmsg("Hash preset changed: '%s' -> '%s'" %
 			(hash_preset, opt.hash_preset))
 		changed['preset'] = True
 	else:
@@ -97,7 +97,7 @@ if opt.keep_old_passphrase:
 	msg("Keeping old passphrase by user request")
 else:
 	new_passwd = get_new_passphrase(
-			"{} wallet".format(g.proj_name), True)
+			"{pnm} wallet".format(pnm=g.proj_name), True)
 
 	if new_passwd == passwd:
 		qmsg("Passphrase is unchanged")

+ 3 - 2
mmgen/main_tool.py

@@ -27,7 +27,7 @@ import mmgen.opt as opt
 import mmgen.tool as tool
 
 opts_data = {
-	'desc':    "Perform various MMGen- and Bitcoin-related operations",
+	'desc':    "Perform various {pnm}- and Bitcoin-related operations".format(pnm=g.proj_name),
 	'usage':   "[opts] <command> <command args>",
 	'options': """
 -d, --outdir=       d Specify an alternate directory 'd' for output
@@ -45,7 +45,7 @@ command
 """.format(tool.cmd_help,g.prog_name)
 }
 
-cmd_args = opt.opts.init(opts_data)
+cmd_args = opt.opts.init(opts_data,add_opts=["no_keyconv"])
 
 if len(cmd_args) < 1:
 	opt.opts.usage()
@@ -54,6 +54,7 @@ if len(cmd_args) < 1:
 command = cmd_args.pop(0)
 
 if command not in tool.cmd_data:
+	from mmgen.util import msg
 	msg("'%s': No such command" % command)
 	sys.exit(1)
 

+ 18 - 15
mmgen/main_txcreate.py

@@ -17,8 +17,8 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 """
-mmgen-txcreate: Create a Bitcoin transaction from MMGen- or non-MMGen inputs
-                to MMGen- or non-MMGen outputs
+mmgen-txcreate: Create a Bitcoin transaction to and from MMGen- or non-MMGen
+                inputs and outputs
 """
 
 import sys
@@ -28,6 +28,8 @@ import mmgen.config as g
 import mmgen.opt as opt
 from mmgen.tx import *
 
+pnm = g.proj_name
+
 opts_data = {
 	'desc':    "Create a BTC transaction with outputs to specified addresses",
 	'usage':   "[opts]  <addr,amt> ... [change addr] [addr file] ...",
@@ -55,7 +57,7 @@ of the form <seed ID>:<number>.
 
 To send all inputs (minus TX fee) to a single output, specify one address
 with no amount on the command line.
-""".format(g=g,pnm=g.proj_name)
+""".format(g=g,pnm=pnm)
 }
 
 wmsg = {
@@ -64,34 +66,34 @@ ERROR: More than one address found for account: "%s".
 Your "wallet.dat" file appears to have been altered by a non-{pnm} program.
 Please restore your tracking wallet from a backup or create a new one and
 re-import your addresses.
-""".strip().format(pnm=g.proj_name),
+""".strip().format(pnm=pnm),
 	'addr_in_addrfile_only': """
 Warning: output address {mmgenaddr} is not in the tracking wallet, which means
 its balance will not be tracked.  You're strongly advised to import the address
 into your tracking wallet before broadcasting this transaction.
 """.strip(),
 	'addr_not_found': """
-No data for MMgen address {mmgenaddr} could be found in either the tracking
+No data for {pnm} address {mmgenaddr} could be found in either the tracking
 wallet or the supplied address file.  Please import this address into your
 tracking wallet, or supply an address file for it on the command line.
 """.strip(),
 	'addr_not_found_no_addrfile': """
-No data for MMgen address {mmgenaddr} could be found in the tracking wallet.
+No data for {pnm} address {mmgenaddr} could be found in the tracking wallet.
 Please import this address into your tracking wallet or supply an address file
 for it on the command line.
 """.strip(),
 	'no_spendable_outputs': """
 No spendable outputs found!  Import addresses with balances into your
 watch-only wallet using '{pnm}-addrimport' and then re-run this program.
-""".strip().format(pnm=g.proj_name.lower()),
+""".strip(),
 	'mixed_inputs': """
-NOTE: This transaction uses a mixture of both mmgen and non-mmgen inputs, which
+NOTE: This transaction uses a mixture of both {pnm} and non-{pnm} inputs, which
 makes the signing process more complicated.  When signing the transaction, keys
 for the non-{pnm} inputs must be supplied to '{pnl}-txsign' in a file with the
 '--keys-from-file' option.
 
 Selected mmgen inputs: %s
-""".strip().format(pnm=g.proj_name,pnl=g.proj_name.lower()),
+""".strip().format(pnm=pnm,pnl=pnm.lower()),
 	'not_enough_btc': """
 Not enough BTC in the inputs for this transaction (%s BTC)
 """.strip(),
@@ -104,7 +106,7 @@ was specified.
 def format_unspent_outputs_for_printing(out,sort_info,total):
 
 	pfs  = " %-4s %-67s %-34s %-12s %-13s %-8s %-10s %s"
-	pout = [pfs % ("Num","TX id,Vout","Address","MMgen ID",
+	pout = [pfs % ("Num","TX id,Vout","Address","{pnm} ID".format(pnm=pnm),
 		"Amount (BTC)","Conf.","Age (days)", "Comment")]
 
 	for n,i in enumerate(out):
@@ -158,8 +160,9 @@ Display options: show [D]ays, [g]roup, show [m]mgen addr, r[e]draw screen
 	while True:
 		cols = get_terminal_size()[0]
 		if cols < g.min_screen_width:
-			msg("%s-txcreate requires a screen at least %s characters wide" %
-					(g.proj_name.lower(),g.min_screen_width))
+			msg(
+	"{pnl}-txcreate requires a screen at least {w} characters wide".format(
+					pnl=pnm.lower(),w=g.min_screen_width))
 			sys.exit(2)
 
 		addr_w = min(34+((1+max_acct_len) if show_mmaddr else 0),cols-46)
@@ -277,7 +280,7 @@ def select_outputs(unspent,prompt):
 
 
 def mmaddr2btcaddr_unspent(unspent,mmaddr):
-	vmsg_r("Searching for {g.proj_name} address {m} in wallet...".format(g=g,m=mmaddr))
+	vmsg_r("Searching for {pnm} address {m} in wallet...".format(pnm=pnm,m=mmaddr))
 	m = [u for u in unspent if u.mmid == mmaddr]
 	if len(m) == 0:
 		vmsg("not found")
@@ -306,10 +309,10 @@ def mmaddr2btcaddr(c,mmaddr,ail_w,ail_f):
 				if not keypress_confirm("Continue anyway?"):
 					sys.exit(1)
 			else:
-				msg(wmsg['addr_not_found'].format(mmgenaddr=mmaddr))
+				msg(wmsg['addr_not_found'].format(pnm=pnm,mmgenaddr=mmaddr))
 				sys.exit(2)
 		else:
-			msg(wmsg['addr_not_found_no_addrfile'].format(mmgenaddr=mmaddr))
+			msg(wmsg['addr_not_found_no_addrfile'].format(pnm=pnm,mmgenaddr=mmaddr))
 			sys.exit(2)
 
 	return btcaddr

+ 2 - 1
mmgen/main_txsend.py

@@ -28,7 +28,8 @@ from mmgen.tx import *
 from mmgen.util import *
 
 opts_data = {
-	'desc':    "Send a Bitcoin transaction signed by {}-txsign".format(g.proj_name.lower()),
+	'desc':    "Send a Bitcoin transaction signed by {pnm}-txsign".format(
+					pnm=g.proj_name.lower()),
 	'usage':   "[opts] <signed transaction file>",
 	'options': """
 -h, --help      Print this help message

+ 36 - 28
mmgen/main_txsign.py

@@ -27,8 +27,11 @@ import mmgen.opt as opt
 from mmgen.tx import *
 from mmgen.util import do_license_msg
 
+pnm = g.proj_name
+pnl = pnm.lower()
+
 opts_data = {
-	'desc':    "Sign Bitcoin transactions generated by {}-txcreate".format(g.proj_name.lower()),
+	'desc':    "Sign Bitcoin transactions generated by {pnl}-txcreate".format(pnl=pnl),
 	'usage':   "[opts] <transaction file> .. [mmgen wallet/seed/words/brainwallet file] ..",
 	'options': """
 -h, --help               Print this help message
@@ -37,16 +40,16 @@ opts_data = {
 -i, --info               Display information about the transaction and exit
 -t, --terse-info         Like '--info', but produce more concise output
 -I, --tx-id              Display transaction ID and exit
--k, --keys-from-file= f  Provide additional keys for non-{MMG} addresses
+-k, --keys-from-file= f  Provide additional keys for non-{pnm} addresses
 -K, --no-keyconv         Force use of internal libraries for address gener-
                          ation, even if 'keyconv' is available
--M, --mmgen-keys-from-file=f  Provide keys for {MMG} addresses in a key-
-                         address file (output of '{mmg}-keygen'). Permits
-                         online signing without an {MMG} seed source.
+-M, --mmgen-keys-from-file=f  Provide keys for {pnm} addresses in a key-
+                         address file (output of '{pnl}-keygen'). Permits
+                         online signing without an {pnm} seed source.
                          The key-address file is also used to verify
-                         {MMG}-to-BTC mappings, so its checksum should
+                         {pnm}-to-BTC mappings, so its checksum should
                          be recorded by the user.
--P, --passwd-file=    f  Get MMGen wallet or bitcoind passphrase from file 'f'
+-P, --passwd-file=    f  Get {pnm} wallet or bitcoind passphrase from file 'f'
 -q, --quiet              Suppress warnings; overwrite files without
                          prompting
 -v, --verbose            Produce more verbose output
@@ -61,25 +64,25 @@ opts_data = {
 -o, --old-incog-fmt      Use old (pre-0.7.8) incog format
 -m, --from-mnemonic      Generate keys from an electrum-like mnemonic
 -s, --from-seed          Generate keys from a seed in .{g.seed_ext} format
-""".format(g=g,MMG=g.proj_name,mmg=g.proj_name.lower()),
+""".format(g=g,pnm=pnm,pnl=pnl),
 	'notes': """
 
-Transactions with either {MMG} or non-{MMG} input addresses may be signed.
-For non-{MMG} inputs, the bitcoind wallet.dat is used as the key source.
-For {MMG} inputs, key data is generated from your seed as with the
-{mmg}-addrgen and {mmg}-keygen utilities.
+Transactions with either {pnm} or non-{pnm} input addresses may be signed.
+For non-{pnm} inputs, the bitcoind wallet.dat is used as the key source.
+For {pnm} inputs, key data is generated from your seed as with the
+{pnl}-addrgen and {pnl}-keygen utilities.
 
 Data for the --from-<what> options will be taken from a file if a second
 file is specified on the command line.  Otherwise, the user will be
 prompted to enter the data.
 
-In cases of transactions with mixed {MMG} and non-{MMG} inputs, non-{MMG}
+In cases of transactions with mixed {pnm} and non-{pnm} inputs, non-{pnm}
 keys must be supplied in a separate file (WIF format, one key per line)
 using the '--keys-from-file' option.  Alternatively, one may get keys from
 a running bitcoind using the '--force-wallet-dat' option.  First import the
-required {MMG} keys using 'bitcoind importprivkey'.
+required {pnm} keys using 'bitcoind importprivkey'.
 
-For transaction outputs that are {MMG} addresses, {MMG}-to-Bitcoin address
+For transaction outputs that are {pnm} addresses, {pnm}-to-Bitcoin address
 mappings are verified.  Therefore, seed material or a key-address file for
 these addresses must be supplied on the command line.
 
@@ -88,18 +91,18 @@ Seed data supplied in files must have the following extensions:
    seed:        '.{g.seed_ext}'
    mnemonic:    '.{g.mn_ext}'
    brainwallet: '.{g.brain_ext}'
-""".format(g=g,MMG=g.proj_name,mmg=g.proj_name.lower())
+""".format(g=g,pnm=pnm,pnl=pnl)
 }
 
 wmsg = {
 	'mm2btc_mapping_error': """
-MMGen -> BTC address mappings differ!
+{pnm} -> BTC address mappings differ!
 From %-18s %s -> %s
 From %-18s %s -> %s
-""".strip(),
+""".strip().format(pnm=pnm),
 	'removed_dups': """
-Removed %s duplicate wif key%s from keylist (also in {MMG} key-address file
-""".strip().format(MMG=g.proj_name),
+Removed %s duplicate wif key%s from keylist (also in {pnm} key-address file
+""".strip().format(pnm=pnm),
 }
 
 def get_seed_for_seed_id(seed_id,infiles,saved_seeds):
@@ -138,7 +141,7 @@ def get_keys_for_mmgen_addrs(mmgen_addrs,infiles,saved_seeds):
 		seed = get_seed_for_seed_id(seed_id,infiles,saved_seeds)
 		addr_nums = [int(i[9:]) for i in mmgen_addrs if i[:8] == seed_id]
 		opt.gen_what = "ka"
-		ai = generate_addrs(seed,addr_nums)
+		ai = generate_addrs(seed,addr_nums,source="txsign")
 		d += [("{}:{}".format(seed_id,e.idx),e.addr,e.wif) for e in ai.addrdata]
 	return d
 
@@ -189,10 +192,11 @@ def sign_tx_with_bitcoind_wallet(c,tx_hex,tx_num_str,sig_data,keys):
 	return sig_tx
 
 
-def check_maps_from_seeds(maplist,label,infiles,saved_seeds,return_keys=False):
+def check_maps_from_seeds(maplist,what,infiles,saved_seeds,return_keys=False):
 
 	if not maplist: return []
-	qmsg("Checking MMGen -> BTC address mappings for %ss (from seeds)" % label)
+	qmsg("Checking {pnm} -> BTC address mappings for {w}s (from seed(s))".format(
+				pnm=pnm,w=what))
 	d = get_keys_for_mmgen_addrs(maplist.keys(),infiles,saved_seeds)
 #	0=mmaddr 1=addr 2=wif
 	m = dict([(e[0],e[1]) for e in d])
@@ -209,8 +213,8 @@ def check_maps_from_seeds(maplist,label,infiles,saved_seeds,return_keys=False):
 def missing_keys_errormsg(addrs):
 	Msg("""
 A key file must be supplied (or use the '--use-wallet-dat' option)
-for the following non-{} address{}:\n    {}""".format(
-	g.proj_name,suf(addrs,"a"),"\n    ".join(addrs)).strip())
+for the following non-{pnm} address{suf}:\n    {l}""".format(
+	pnm=pnm, suf=suf(addrs,"a"), l="\n    ".join(addrs)).strip())
 
 
 def parse_mmgen_keyaddr_file():
@@ -225,7 +229,7 @@ def parse_mmgen_keyaddr_file():
 
 def parse_keylist(from_file):
 	fn = opt.keys_from_file
-	d = get_data_from_file(fn,"non-%s keylist" % g.proj_name)
+	d = get_data_from_file(fn,"non-{pnm} keylist".format(pnm=pnm))
 	enc_ext = get_extension(fn) == g.mmenc_ext
 	if enc_ext or not is_utf8(d):
 		if not enc_ext: qmsg("Keylist file appears to be encrypted")
@@ -250,7 +254,8 @@ def parse_keylist(from_file):
 
 # Check inputs and outputs maps against key-address file, deleting entries:
 def check_maps_from_kafile(imap,what,kadata,return_keys=False):
-	qmsg("Checking MMGen -> BTC address mappings for %ss (from key-address file)" % what)
+	if not kadata: return []
+	qmsg("Checking {pnm} -> BTC address mappings for {w}s (from key-address file)".format(pnm=pnm,w=what))
 	ret = []
 	for k in imap.keys():
 		if k in kadata.keys():
@@ -372,7 +377,10 @@ for tx_num,tx_file in enumerate(tx_files,1):
 		data = make_tx_data("{} {} {t}".format(*metadata[:2], t=make_timestamp()),
 				sig_tx['hex'], inputs_data, b2m_map, comment)
 		w = "signed transaction{}".format(tx_num_str)
-		write_to_file(outfile,data,w,(not opt.quiet),True,False)
+		if keypress_confirm("Save signed transaction?",default_yes=False):
+			write_to_file(outfile,data,w,(not opt.quiet),True,False)
+		else:
+			msg("Signed transaction not saved")
 	else:
 		msg_r("failed\nSome keys were missing.  ")
 		msg("Transaction %scould not be signed." % tx_num_str)

+ 5 - 5
mmgen/main_walletchk.py

@@ -28,15 +28,15 @@ from mmgen.util import *
 from mmgen.crypto import *
 
 opts_data = {
-	'desc':  """Check integrity of an {} deterministic wallet, display
+	'desc':  """Check integrity of an {pnm} deterministic wallet, display
                     its information, and export seed and mnemonic data.
-             """.format(g.proj_name),
+             """.format(pnm=g.proj_name),
 	'usage':   "[opts] [filename]",
 	'options': """
 -h, --help             Print this help message
 -d, --outdir=       d  Specify an alternate directory 'd' for output
 -e, --echo-passphrase  Print passphrase to screen when typing it
--P, --passwd-file=  f  Get MMGen wallet passphrase from file 'f'
+-P, --passwd-file=  f  Get {pnm} wallet passphrase from file 'f'
 -q, --quiet            Suppress warnings; overwrite files without prompting
 -r, --usr-randchars= n Get 'n' characters of additional randomness from
                        user (min={g.min_urandchars}, max={g.max_urandchars})
@@ -49,7 +49,7 @@ opts_data = {
 -o, --old-incog-fmt    Use old (pre-0.7.8) incog format
 -m, --export-mnemonic  Export the wallet's mnemonic to file
 -s, --export-seed      Export the wallet's seed to file
-""".format(g=g),
+""".format(g=g,pnm=g.proj_name),
 	'notes': """
 
 Since good randomness is particularly important for incognito wallets,
@@ -67,7 +67,7 @@ def wallet_to_incog_data(infile):
 			d[1][0], d[1][1], d[2].split(":")[0], d[3], d[4]
 
 	while True:
-		passwd = get_mmgen_passphrase("{} wallet".format(g.proj_name))
+		passwd = get_mmgen_passphrase("{pnm} wallet".format(pnm=g.proj_name))
 		key = make_key(passwd, salt, preset, "main key")
 		seed = decrypt_seed(enc_seed, key, seed_id, key_id)
 		if seed: break

+ 12 - 9
mmgen/main_walletgen.py

@@ -28,8 +28,10 @@ import mmgen.opt as opt
 from mmgen.util import *
 from mmgen.crypto import *
 
+pnm = g.proj_name
+
 opts_data = {
-	'desc':    "Generate an {} deterministic wallet".format(g.proj_name),
+	'desc':    "Generate an {pnm} deterministic wallet".format(pnm=pnm),
 	'usage':   "[opts] [infile]",
 	'options': """
 -h, --help                 Print this help message
@@ -42,7 +44,7 @@ opts_data = {
                            Allowed symbols: A-Z, a-z, 0-9, " ", "_", ".")
 -p, --hash-preset=      p  Use scrypt.hash() parameters from preset 'p'
                            (default: '{g.hash_preset}')
--P, --passwd-file=      f  Get MMGen wallet passphrase from file 'f'
+-P, --passwd-file=      f  Get {pnm} wallet passphrase from file 'f'
 -q, --quiet                Produce quieter output; overwrite files without
                            prompting
 -r, --usr-randchars=    n  Get 'n' characters of additional randomness from
@@ -58,7 +60,7 @@ opts_data = {
 -o, --old-incog-fmt        Use old (pre-0.7.8) incog format
 -m, --from-mnemonic        Generate wallet from an Electrum-like mnemonic
 -s, --from-seed            Generate wallet from a seed in .{g.seed_ext} format
-""".format(seed_lens=",".join([str(i) for i in g.seed_lens]), g=g),
+""".format(seed_lens=",".join([str(i) for i in g.seed_lens]),g=g,pnm=pnm),
 	'notes': """
 
 By default (i.e. when invoked without any of the '--from-<what>' options),
@@ -106,16 +108,17 @@ just hit ENTER twice.
 ############################## EXPERTS ONLY! ##############################
 
 A brainwallet will be secure only if you really know what you're doing and
-have put much care into its creation.  {} assumes no responsibility for
-coins stolen as a result of a poorly crafted brainwallet passphrase.
+have put much care into its creation.  The creators of {pnm} assume no
+responsibility for coins stolen as a result of a poorly crafted brainwallet
+passphrase.
 
 A key will be generated from your passphrase using the parameters requested
 by you: seed length {}, hash preset '{}'.  For brainwallets it's highly
-recommended to use one of the higher-numbered presets
+recommended to use one of the higher-numbered presets.
 
 Remember the seed length and hash preset parameters you've specified.  To
 generate the correct keys/addresses associated with this passphrase in the
-future, you must continue using these same parameters
+future, you must continue using these same parameters.
 """,
 }
 
@@ -145,7 +148,7 @@ do_license_msg()
 
 if opt.from_brain and not opt.quiet:
 	confirm_or_exit(wmsg['brain_warning'].format(
-			g.proj_name, *get_from_brain_opt_params()),
+			pnm=pnm, *get_from_brain_opt_params()),
 		"continue")
 
 if infile or any([
@@ -160,7 +163,7 @@ salt = sha256(get_random(128)).digest()[:g.salt_len]
 
 qmsg(wmsg['choose_wallet_passphrase'] % opt.hash_preset)
 
-passwd = get_new_passphrase("new {} wallet".format(g.proj_name))
+passwd = get_new_passphrase("new {pnm} wallet".format(pnm=pnm))
 
 key = make_key(passwd, salt, opt.hash_preset)
 

+ 1 - 1
mmgen/mn_electrum.py

@@ -16,7 +16,7 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 # ------------------------------------------------------------------------------
-# MMgen note: this is a sorted version of the wordlist.  The original
+# MMGen note: this is a sorted version of the wordlist.  The original
 # can be found at:
 # https://github.com/spesmilo/electrum/blob/master/lib/mnemonic.py
 # and in the file 'wordlists/mnemonic.py' of the mmgen distribution.

+ 4 - 5
mmgen/opts.py

@@ -32,10 +32,10 @@ def usage():
 
 def print_version_info():
 	Msg("""
-{progname_uc} version {g.version}.  Part of the {g.proj_name} suite, a Bitcoin
-cold-storage solution for the command line.  Copyright (C) {g.Cdates}
-by {g.author} {g.email}
-""".format(g=g,progname_uc=g.prog_name.upper()).strip())
+{progname_uc} version {g.version}
+Part of the {pnm} suite, a Bitcoin cold-storage solution for the com-
+mand line.   Copyright (C) {g.Cdates} {g.author} {g.email}
+""".format(pnm=g.proj_name,g=g,progname_uc=g.prog_name.upper()).strip())
 
 def warn_incompatible_opts(incompat_list):
 	bad = [k for k in opt.__dict__ if opt.__dict__[k] and k in incompat_list]
@@ -61,7 +61,6 @@ def typeconvert_from_dfl(key):
 			'bool':  'a boolean value',
 		}
 		m = [d[k] for k in d if __builtins__[k] == vtype]
-		msgrepr_exit(key,vtype)
 		fs = "'%s': invalid parameter for '--%s' option (not %s)"
 		msg(fs % (opt.__dict__[key],opt.replace("_","-"),m))
 		sys.exit(1)

+ 7 - 5
mmgen/seed.py

@@ -29,6 +29,8 @@ from mmgen.util import *
 from mmgen.bitcoin import b58encode_pad,b58decode_pad
 from mmgen.crypto import *
 
+pnm = g.proj_name
+
 class Seed(MMGenObject):
 	def __init__(self,seed_bin=None):
 		if not seed_bin:
@@ -293,7 +295,7 @@ class SeedFile (SeedSourceUnenc):
 
 class Wallet (SeedSourceEnc):
 
-	desc = "%s wallet" % g.proj_name
+	desc = "{pnm} wallet".format(pnm=pnm)
 
 	def _encode(self):
 		d = self.ssdata
@@ -341,8 +343,8 @@ class Wallet (SeedSourceEnc):
 
 	def _deformat(self):
 
-		qmsg("Getting {} wallet data from file '{}'".format(
-			g.proj_name,self.infile.name))
+		qmsg("Getting {pnm} wallet data from file '{f}'".format(
+			pnm=pnm,f=self.infile.name))
 
 		lines = self.fmt_data.rstrip().split("\n")
 
@@ -518,13 +520,13 @@ to exit and re-run the program with the '--old-incog-fmt' option.
 		d.incog_id       = self._make_iv_chksum(d.iv)
 		d.enc_incog_data = raw_d[g.aesctr_iv_len:]
 		msg("Incog ID: %s" % d.incog_id)
-		qmsg("Check the applicable value against your records.")
+		qmsg("Check the applicable value against your records")
 		k = 'incog_iv_id_hidden' if opt.from_incog_hidden else 'incog_iv_id'
 		vmsg("\n%s\n" % self._icg_msg[k])
 
 	def _decode(self):
 		d = self.ssdata
-		prompt_info="{} incognito wallet".format(g.proj_name)
+		prompt_info="{pnm} incognito wallet".format(pnm=pnm)
 
 		while True:
 			passwd = get_mmgen_passphrase(prompt_info+" "+d.incog_id)

+ 4 - 3
mmgen/test.py

@@ -17,7 +17,7 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 """
-addr.py:  Shared routines for the test suites
+test.py:  Shared routines for the test suites
 """
 
 import sys,os
@@ -86,6 +86,7 @@ def cmp_or_die(s,t,skip_ok=False):
 	if s == t:
 	   if not skip_ok: ok()
 	else:
-		msg(red("Recoded data:\n%s\ndiffers from original data:\n%s\n" %
-					(repr(t),repr(s))))
+		sys.stderr.write(red(
+			"ERROR: recoded data:\n%s\ndiffers from original data:\n%s\n" %
+				(repr(t),repr(s))))
 		sys.exit(3)

+ 7 - 5
mmgen/tool.py

@@ -30,6 +30,8 @@ from mmgen.crypto import *
 from mmgen.util import *
 from mmgen.tx import *
 
+pnm = g.proj_name
+
 from collections import OrderedDict
 cmd_data = OrderedDict([
 	("help",         []),
@@ -75,10 +77,10 @@ cmd_data = OrderedDict([
 
 	("listaddresses",['minconf [int=1]','showempty [bool=False]','pager [bool=False]']),
 	("getbalance",   ['minconf [int=1]']),
-	("txview",       ['<MMGen tx file> [str]','pager [bool=False]','terse [bool=False]']),
+	("txview",       ['<{pnm} tx file> [str]','pager [bool=False]','terse [bool=False]'.format(pnm=pnm)]),
 
-	("addrfile_chksum", ['<MMGen addr file> [str]']),
-	("keyaddrfile_chksum", ['<MMGen addr file> [str]']),
+	("addrfile_chksum", ['<{pnm} addr file> [str]'.format(pnm=pnm)]),
+	("keyaddrfile_chksum", ['<{pnm} addr file> [str]'.format(pnm=pnm)]),
 	("find_incog_data", ['<file or device name> [str]','<Incog ID> [str]','keep_searching [bool=False]']),
 
 	("encrypt",      ['<infile> [str]','outfile [str=""]','hash_preset [str=""]']),
@@ -150,7 +152,7 @@ cmd_help = """
 
   IMPORTANT NOTE: Though {pnm} mnemonics use the Electrum wordlist, they're
   computed using a different algorithm and are NOT Electrum-compatible!
-""".format(pnm=g.proj_name)
+""".format(pnm=pnm)
 
 def tool_usage(prog_name, command):
 	Msg("USAGE: '%s %s%s'" % (prog_name, command,
@@ -486,7 +488,7 @@ def decrypt(infile,outfile="",hash_preset=""):
 		if dec_d: break
 		msg("Trying again...")
 	if outfile == '-':
-		write_to_stdout(dec_d,"decrypted data",confirm=True)
+		write_to_stdout(dec_d,"decrypted data",confirm=not opt.quiet)
 	else:
 		if not outfile:
 			outfile = os.path.basename(infile)

+ 16 - 11
mmgen/tx.py

@@ -100,16 +100,21 @@ def wiftoaddr(s):
 	if not hex_key: return False
 	return privnum2addr(int(hex_key,16),compressed)
 
-def is_valid_tx_comment(s, verbose=True):
+
+def is_valid_tx_comment(s):
+
+	try: s = s.decode("utf8")
+	except:
+		msg("Invalid transaction comment (not UTF-8)")
+		return False
+
 	if len(s) > g.max_tx_comment_len:
-		if verbose: msg("Invalid transaction comment (longer than %s characters)" %
+		msg("Invalid transaction comment (longer than %s characters)" %
 				g.max_tx_comment_len)
 		return False
-	try: s.decode("utf8")
-	except:
-		if verbose: msg("Invalid transaction comment (not UTF-8)")
-		return False
-	else: return True
+
+	return True
+
 
 def check_addr_label(label):
 
@@ -149,7 +154,7 @@ def view_tx_data(c,inputs_data,tx_hex,b2m_map,comment,metadata,pager=False,pause
 	if comment: out += "Comment: %s\n%s" % (comment,enl)
 	out += "Inputs:\n" + enl
 
-	nonmm_str = "non-%s address" % g.proj_name
+	nonmm_str = "non-{pnm} address".format(pnm=g.proj_name)
 
 	total_in = 0
 	for n,i in enumerate(td['vin']):
@@ -241,7 +246,7 @@ def parse_tx_file(tx_data,infile):
 							if comment == False:
 								err_str = "encoded comment (not base58)"
 							else:
-								if is_valid_tx_comment(comment,True):
+								if is_valid_tx_comment(comment):
 									comment = comment.decode("utf8")
 								else:
 									err_str = "comment"
@@ -268,7 +273,7 @@ def get_wif2addr_f():
 
 def get_tx_comment_from_file(infile):
 	s = get_data_from_file(infile,"transaction comment")
-	if is_valid_tx_comment(s, verbose=True):
+	if is_valid_tx_comment(s):
 		return s.decode("utf8").strip()
 	else:
 		sys.exit(2)
@@ -278,7 +283,7 @@ def get_tx_comment_from_user(comment=""):
 		while True:
 			s = my_raw_input("Comment: ",insert_txt=comment.encode("utf8"))
 			if s == "": return False
-			if is_valid_tx_comment(s, verbose=True):
+			if is_valid_tx_comment(s):
 				return s.decode("utf8")
 	except KeyboardInterrupt:
 		msg("User interrupt")

+ 4 - 2
mmgen/util.py

@@ -27,6 +27,8 @@ from string import hexdigits
 
 import mmgen.config as g
 
+pnm = g.proj_name
+
 _red,_grn,_yel,_cya,_reset = (
 	["\033[%sm" % c for c in "31;1","32;1","33;1","36;1","0"]
 )
@@ -493,7 +495,7 @@ def get_data_from_wallet(infile,silent=False):
 	# Don't make this a qmsg: User will be prompted for passphrase and must see
 	# the filename.
 	if not silent and not opt.quiet:
-		msg("Getting {} wallet data from file '{}'".format(g.proj_name,infile))
+		msg("Getting {pnm} wallet data from file '{f}'".format(pnm=pnm,f=infile))
 
 	f = open_file_or_exit(infile, 'r')
 
@@ -609,7 +611,7 @@ def get_seed_from_seed_data(words):
 		msg("Valid seed data for seed ID %s" % make_chksum_8(seed))
 		return seed
 	else:
-		msg("Invalid checksum for {} seed".format(g.proj_name))
+		msg("Invalid checksum for {pnm} seed".format(pnm=pnm))
 		return False
 
 

+ 6 - 0
test/ref/1378FC64-6F0F9BB4[192,1].mmdat

@@ -0,0 +1,6 @@
+5439c5
+No Label
+1378fc64 6f0f9bb4 192 NE 20150406_105042
+1: 12 8 1
+0e8caa N7Co eyLd LbJ1 ACfm s4g4 rV
+f5fdd7 CF2X P6rr ng9z uQev Ldpv 1NeA BarC 6vUC f

BIN
test/ref/1378FC64-B55E9958-77256FC1[192:1].incog.offset123


BIN
test/ref/1378FC64-B55E9958-D85FF20C[192:1].incog-old.offset123


+ 1 - 0
test/ref/1378FC64.mmseed

@@ -0,0 +1 @@
+6bbc91 7L6o G2dR nAFk hncM Q7TU 8Gcc CEGP oKxx C

+ 1 - 0
test/ref/1378FC64.mmwords

@@ -0,0 +1 @@
+duck memory screen world knee thank pray ignore physical season sanity anymore view prince fix compare mother make

+ 6 - 0
test/ref/98831F3A-27F2BF93[256,1].mmdat

@@ -0,0 +1,6 @@
+cd828f
+"#$%&()*+,- ./0123456789:;<=>?@AIZ[\]^_`aiz{|}~'
+98831f3a 27f2bf93 256 NE 20150405_075000
+1: 12 8 1
+9440eb NBDH bKqG a23q FtYi nRo1 kk
+7a2f32 5nZf LqjP R9bj vgzc sMGr WPtu PR7S 6NpZ pgGd fc4e QCKt

BIN
test/ref/98831F3A-F59B07A0-559CEF19[256:1].incog.offset123


BIN
test/ref/98831F3A-F59B07A0-848535F3[256:1].incog-old.offset123


+ 1 - 0
test/ref/98831F3A.mmseed

@@ -0,0 +1 @@
+3f96a4 5g1y LZ2a wULJ mffJ BkuG 4b1j w4RX u6V5 9Q5A XQg9 tPtd

+ 1 - 0
test/ref/98831F3A.mmwords

@@ -0,0 +1 @@
+dry satisfy pure admire around realize balance sentence husband weary dew trail desk velvet effort strange quite throw morning family drawn exactly block core

+ 19 - 0
test/ref/98831F3A[1,31-33,500-501,1010-1011].addrs

@@ -0,0 +1,19 @@
+# MMGen address file
+#
+# This file is editable.
+# Everything following a hash symbol '#' is a comment and ignored by MMGen.
+# A text label of 32 characters or less may be added to the right of each
+# address, and it will be appended to the bitcoind wallet label upon import.
+# The label may contain any printable ASCII symbol.
+# Address data checksum for 98831F3A[1,31-33,500-501,1010-1011]: 6FEF 6FB9 7B13 5D91 854A 0BD3
+# Record this value to a secure location
+98831F3A {
+  1     1MU7EdgqYy9JX35L25hR6CmXXcSEBDAwyv
+  31    1Kb8npGGTVtm5qf6Gik4WdLgQoznwufEc   "#$%&()*+,- ./09:;<=>?@AZ[]_az|'
+  32    19X3wLG3m7Dg6o5bppk1eqwkLiLVSJ8t1H
+  33    177t1mCffzMWihi9w2Jbb9MQ331zFTxmDJ
+  500   1EZNuddPnaZFah9QVbGvzvTcP4KeRrRFt8
+  501   1ET1CZJtXZanLqbGhFkd4h1hsHengakjqV
+  1010  1DysuxsnRA46wd9bpFhLog2jwtpARmP6BY
+  1011  1DD7mfGAPWme3p8f52AJA9rNQ2SxV1ZUe6
+}

BIN
test/ref/98831F3A[1,31-33,500-501,1010-1011].akeys.mmenc


+ 6 - 0
test/ref/FE3C6545-161E495F-9860A85B[128:1].incog-old.offset123

@@ -0,0 +1,6 @@
+.O¡ÎQe‚%S°)Àl[Ës!ÞÖŒ÷JÚU·zê¤]\q}²ä—Èjmçö	²Yr‚Ù¡M_/_³Q�#6„Þ·�¿Æ=˜›gzÈœEƒ<•sTíó�éL©ª#�õGóGöß="eÆ£h÷ªS†€y)–å`æúâmAI®ð¸j=tŽ91Ä„‘ŸnêYãdüȾå&FëO½ÞáèD~hE9ÃYòOyϪç»J‹iÝ}È+·8ÇtS`b&ƒÑ|�	«†BR©¤yÑò`üI;=ã}§ƒ:_„]?†hÇñ#!ŸÊm÷º1� ëeïC_+u;nxÌ)˜À�ýñ¢–g
+Í­Íšð’ƒ…œ�mÁ¼a	:‚¾ØÉâ±�†…~Aà÷§7ý 7ÿYî�t#Jô´ßµàÒ\_‘}¼¢	¹^£/…;ŠÈw•Ã×XŠÕö°§À9˜
+‘%&´sVWÀ~T"Ç}°–ý¼Vp	êˆ�EÍ*ÎOjÚÆÑHoY�–DG£ìÑu¹¿9[e�Œå`Õ©©¶OÎØÞCøT¶û�ôí<A~;Û�ÎØápbEöä8ü�=4�¢Ið¬$ii”&eݧOZ˜c3Gß9mð¼¹�Ôñ˜ãr9n�©7c…7¬=nß\åØIJ®ývüà·bJô®¦zìâÕ¶YlØvTi,Ù‚@>·Ë²ÎÝ ©9‚ÆzT ž',ħašþZÝ8_´N£Q±·æ™Uø§€ÞŠ°’¡¶?ûÝM‚/ËjP_ ÚÝ�ÂXën0Ëéâü¾ÔƒÜoï‡	ÔÖ§'+Ú?ÆöL6HÑn�w>¥º¢éˆH|\ž+3ÏI,Ϋf–HL�%°PÛ¡î_`‹ë�~,2vÝö”üáhäYÁÿíAMd‚®WÌ8¬%H¶Ç�LEÒ†�©×nüÀ•ÐÈ¥v´lêj�Q_�`'‹¯<¯Fg�„WŸu: ) Æ#‡ægíWÛ“áèÃ!Wl÷‘�]�]»õÍ´&HŽ@©sÁ¡«¦=ô[\^?¦$ˆ´)ò%E, â’3�YßD$à”ÖgNo‡e0×Ïðé9¶Ô…-½<•سà¢Õáß®#¬H·[áwh¦@D3ŸãÒb@ÃYKà`—OÚª YG‡³ 8[pYf½�,ræ�_/öþ�A“^‘VŸ?l¼ó°±¬4ÿÅñCQý'Yç¨�‹œ˜‚¬¼6œ
+
+ŽC5z^EPmWA$þµI›Äq‘u׸§03Ê¡)I‘p¥^X†Áô²$¢<F›™ûÌ‹å�éR�½=^²ÜXE¢i]ÈõI

BIN
test/ref/FE3C6545-161E495F-BEB7548E[128:1].incog-offset123


+ 6 - 0
test/ref/FE3C6545-D782B529[128,1].mmdat

@@ -0,0 +1,6 @@
+09f434
+No Label
+fe3c6545 d782b529 128 NE 20150406_104849
+1: 12 8 1
+efa838 6aNb kfGu YGBy SxTe amwC q4
+3548be 8MM2 fxNv Sr7N h4NA WXv2 wL

+ 1 - 0
test/ref/FE3C6545.mmseed

@@ -0,0 +1 @@
+0e6d52 9aFM LL5z pLA7 DNLu vAha hT

+ 1 - 0
test/ref/FE3C6545.mmwords

@@ -0,0 +1 @@
+dude foot desperate tie stood themselves trip descend cease suicide apple busy

+ 1 - 0
test/ref/brainwallet

@@ -0,0 +1 @@
+A brainwallet with	 some non-standard spacing

+ 11 - 0
test/ref/brainwallet-spaced

@@ -0,0 +1,11 @@
+
+		   A
+
+	brainwallet
+
+with some
+	non-standard
+
+	spacing
+
+

+ 5 - 0
test/ref/tx_FFB367[1.234].raw

@@ -0,0 +1,5 @@
+FFB367 1.234 20150405_102927
+01000000013364630b6d290a82c822facc2f7c1db4452cea459b2ce22371135530485a5d010600000000ffffffff0205d7d600010000001976a914bba3993079ccdf40c9bbbe495473f0b3d2dc5eec88ac40ef5a07000000001976a914abe58e1e45f6176910a4c1ac1ee62328d5cc4fd588ac00000000
+[{'comment': u'Test Wallet', 'mmid': u'98831F3A:500', 'vout': 6, 'txid': u'015d5a483055137123e22c9b45ea2c45b41d7c2fccfa22c8820a296d0b636433', 'amount': Decimal('44.32452045'), 'confirmations': 495L, 'address': u'1EZNuddPnaZFah9QVbGvzvTcP4KeRrRFt8', 'spendable': False, 'scriptPubKey': '76a91494b93bbe8a32f1db80b307482e83c25fa4e99b8c88ac'}]
+{u'1J79LtWctedRLnMfFNRgzzSFsozQqDeoKD': ('98831F3A:3', u''), u'1EZNuddPnaZFah9QVbGvzvTcP4KeRrRFt8': (u'98831F3A:500', u'Test Wallet'), u'1GfuYaKHrhdiVybXMGCcjadSgfjvpdt2x9': ('98831F3A:2', u'')}
+3SBcsGkhcKRVB2gr98BmscU8HtWJ12HTXpJa5XmvbEUateQ3bJBEgvLd5kPGAzg1rFkzjVpZJgiKGwvnq5mJpwnbJqcHpVEAopWyALDmtjrDwEvPiTY

+ 295 - 107
test/test.py

@@ -10,7 +10,7 @@ sys.path.__setitem__(0,os.path.abspath(os.curdir))
 
 import mmgen.config as g
 import mmgen.opt as opt
-from mmgen.util import msgrepr,msgrepr_exit,Msg
+from mmgen.util import msgrepr,msgrepr_exit,Msg,die
 from mmgen.test import *
 
 hincog_fn      = "rand_data"
@@ -21,29 +21,97 @@ 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"
+
 cfgs = {
 	'6': {
-		'name':            "reference wallet check",
-		'wallet_label':    "test.py reference wallet (password 'abc')",
-		'bw_passwd':       "abc",
-		'bw_hashparams':   "256,1",
-		'key_id':          "98831F3A",
+		'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-161E495F-BEB7548E[128:1].incog-offset123",
+		'ic_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-B55E9958-77256FC1[192:1].incog.offset123",
+		'ic_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",
-		'tmpdir':        "test/tmp6",
+		'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-F59B07A0-559CEF19[256:1].incog.offset123",
+		'ic_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':       "refwalletgen",
-			'addrs':       "refaddrgen",
-			'akeys.mmenc': "refkeyaddrgen"
+			'mmdat':       "refwalletgen3",
+			'addrs':       "refaddrgen3",
+			'akeys.mmenc': "refkeyaddrgen3"
 		},
 
 	},
 	'1': {
-		'tmpdir':        "test/tmp1",
+		'tmpdir':        os.path.join("test","tmp1"),
 		'wpasswd':       "Dorian",
 		'kapasswd':      "Grok the blockchain",
 		'addr_idx_list': "12,99,5-10,5,12", # 8 addresses
@@ -62,7 +130,7 @@ cfgs = {
 		},
 	},
 	'2': {
-		'tmpdir':        "test/tmp2",
+		'tmpdir':        os.path.join("test","tmp2"),
 		'wpasswd':       "Hodling away",
 		'addr_idx_list': "37,45,3-6,22-23",  # 8 addresses
         'seed_len':      128,
@@ -75,7 +143,7 @@ cfgs = {
 		},
 	},
 	'3': {
-		'tmpdir':        "test/tmp3",
+		'tmpdir':        os.path.join("test","tmp3"),
 		'wpasswd':       "Major miner",
 		'addr_idx_list': "73,54,1022-1023,2-5", # 8 addresses
 		'dep_generators': {
@@ -86,7 +154,7 @@ cfgs = {
 		},
 	},
 	'4': {
-		'tmpdir':        "test/tmp4",
+		'tmpdir':        os.path.join("test","tmp4"),
 		'wpasswd':       "Hashrate rising",
 		'addr_idx_list': "63,1004,542-544,7-9", # 8 addresses
         'seed_len':      192,
@@ -101,24 +169,24 @@ cfgs = {
 		'bw_params':   "192,1",
 	},
 	'5': {
-		'tmpdir':        "test/tmp5",
+		'tmpdir':        os.path.join("test","tmp5"),
 		'wpasswd':       "My changed password",
 		'dep_generators': {
 			'mmdat':       "passchg",
 		},
 	},
 	'9': {
-		'tmpdir':        "test/tmp9",
+		'tmpdir':        os.path.join("test","tmp9"),
 		'tool_enc_passwd': "Scrypt it, don't hash it!",
-		'tool_enc_reftext':
+		'sample_text':
 	"The Times 03/Jan/2009 Chancellor on brink of second bailout for banks\n",
 		'tool_enc_infn':      "tool_encrypt.in",
-		'tool_enc_ref_infn':  "tool_encrypt_ref.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",
+#			'tool_encrypt_ref.in':        "tool_encrypt_ref",
+#			'tool_encrypt_ref.in.mmenc':  "tool_encrypt_ref",
 		},
 	},
 }
@@ -126,12 +194,45 @@ cfgs = {
 from collections import OrderedDict
 cmd_data = OrderedDict([
 #     test               description                  depends
-	['refwalletgen',    (6,'reference wallet seed ID',    [[[],6]])],
-	['refaddrgen',      (6,'reference wallet address checksum', [[["mmdat"],6]])],
-	['refkeyaddrgen',   (6,'reference wallet key-address checksum', [[["mmdat"],6]])],
+	# Check saved reference files:
+	['ref_wallet_chk1', (6,'saved reference wallet (128-bit)', [[[],6]])],
+	['ref_wallet_chk2', (7,'saved reference wallet (192-bit)', [[[],7]])],
+	['ref_wallet_chk3', (8,'saved reference wallet (256-bit)', [[[],8]])],
+	['ref_seed_chk1',   (6,'saved seed file (128-bit)', [[[],6]])],
+	['ref_seed_chk2',   (7,'saved seed file (192-bit)', [[[],7]])],
+	['ref_seed_chk3',   (8,'saved seed file (256-bit)', [[[],8]])],
+	['ref_mn_chk1',     (6,'saved mnemonic file (128-bit)', [[[],6]])],
+	['ref_mn_chk2',     (7,'saved mnemonic file (192-bit)', [[[],7]])],
+	['ref_mn_chk3',     (8,'saved mnemonic file (256-bit)', [[[],8]])],
+	['ref_incog_chk1',  (6,'saved incog reference wallet (128-bit)', [[[],6]])],
+	['ref_incog_chk2',  (7,'saved incog reference wallet (192-bit)', [[[],7]])],
+	['ref_incog_chk3',  (8,'saved incog reference wallet (256-bit)', [[[],8]])],
+	['ref_brain_chk1',  (6,'saved brainwallet (128-bit)', [[[],6]])],
+	['ref_brain_chk2',  (7,'saved brainwallet (192-bit)', [[[],7]])],
+	['ref_brain_chk3',  (8,'saved brainwallet (256-bit)', [[[],8]])],
+	['ref_brain_chk3_spc', (8,'saved brainwallet (256-bit, non-standard spacing)', [[[],8]])],
+
+	['ref_addrfile_chk',  (8,'saved reference address file', [[[],8]])],
+	['ref_keyaddrfile_chk', (8,'saved reference key-address file', [[[],8]])],
+# Create the fake inputs:
+#	['txcreate8',        (8,'transaction creation (8)',  [[["addrs"],8]])],
+	['ref_tx_chk',       (8,'saved reference tx file', [[[],8]])],
+
+	['ref_tool_decrypt', (9,'decryption of saved MMGen-encrypted file', [[[],9]])],
+
+	# Generate new reference ('abc' brainwallet) files:
+	['refwalletgen1', (6,'gen new refwallet (128-bit)', [[[],6]])],
+	['refwalletgen2', (7,'gen new refwallet (192-bit)', [[[],7]])],
+	['refwalletgen3', (8,'gen new refwallet (256-bit)', [[[],8]])],
+	['refaddrgen1',   (6,'new refwallet addr chksum (128-bit)', [[["mmdat"],6]])],
+	['refaddrgen2',   (7,'new refwallet addr chksum (192-bit)', [[["mmdat"],7]])],
+	['refaddrgen3',   (8,'new refwallet addr chksum (256-bit)', [[["mmdat"],8]])],
+	['refkeyaddrgen1', (6,'new refwallet key-addr chksum (128-bit)', [[["mmdat"],6]])],
+	['refkeyaddrgen2', (7,'new refwallet key-addr chksum (192-bit)', [[["mmdat"],7]])],
+	['refkeyaddrgen3', (8,'new refwallet key-addr chksum (256-bit)', [[["mmdat"],8]])],
 
 	['walletgen',       (1,'wallet generation',        [[[],1]])],
-	['walletchk',       (1,'wallet check',             [[["mmdat"],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]])],
@@ -177,10 +278,7 @@ cmd_data = OrderedDict([
 	['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_decrypt_ref', (9,"'mmgen-tool decrypt' (reference text)",
-		[[[cfgs['9']['tool_enc_ref_infn'],
-		   cfgs['9']['tool_enc_ref_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]])],
 ])
 
@@ -198,22 +296,28 @@ for k in cfgs.keys():
 		cfgs[k]['amts'][idx] = "%s.%s" % ((getrandnum(2) % mod), str(getrandnum(4))[:5])
 
 meta_cmds = OrderedDict([
-	['ref',    (6,("refwalletgen","refaddrgen","refkeyaddrgen"))],
-	['gen',    (1,("walletgen","walletchk","addrgen"))],
-	['pass',   (5,("passchg","walletchk_newpass"))],
-	['tx',     (1,("txcreate","txsign","txsend"))],
+	['saved_ref1', (6,("ref_wallet_chk1","ref_seed_chk1","ref_mn_chk1","ref_brain_chk1","ref_incog_chk1"))],
+	['saved_ref2', (7,("ref_wallet_chk2","ref_seed_chk2","ref_mn_chk2","ref_brain_chk2","ref_incog_chk2"))],
+	['saved_ref3', (8,("ref_wallet_chk3","ref_seed_chk3","ref_mn_chk3","ref_brain_chk3","ref_incog_chk3","ref_brain_chk3_spc"))],
+	['saved_ref_other',  (8,("ref_addrfile_chk","ref_tx_chk","ref_tool_decrypt"))],
+	['ref1', (6,("refwalletgen1","refaddrgen1","refkeyaddrgen1"))],
+	['ref2', (7,("refwalletgen2","refaddrgen2","refkeyaddrgen2"))],
+	['ref3', (8,("refwalletgen3","refaddrgen3","refkeyaddrgen3"))],
+	['gen',  (1,("walletgen","addrgen"))],
+	['pass', (5,("passchg","walletchk_newpass"))],
+	['tx',   (1,("txcreate","txsign","txsend"))],
 	['export', (1,[k for k in cmd_data if k[:7] == "export_" and cmd_data[k][0] == 1])],
 	['gen_sp', (1,[k for k in cmd_data if k[:8] == "addrgen_" and cmd_data[k][0] == 1])],
 	['online', (1,("keyaddrgen","txsign_keyaddr"))],
 	['2', (2,[k for k in cmd_data if cmd_data[k][0] == 2])],
 	['3', (3,[k for k in cmd_data if cmd_data[k][0] == 3])],
 	['4', (4,[k for k in cmd_data if cmd_data[k][0] == 4])],
-	['tool', (9,("tool_encrypt","tool_decrypt","tool_encrypt_ref","tool_decrypt_ref","tool_find_incog_data"))],
+	['tool', (9,("tool_encrypt","tool_decrypt","tool_find_incog_data"))],
 ])
 
 opts_data = {
 	'desc': "Test suite for the MMGen suite",
-	'usage':"[options] [command or metacommand]",
+	'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
@@ -361,10 +465,11 @@ def verify_checksum_or_exit(checksum,chk):
 
 class MMGenExpect(object):
 
-	def __init__(self,name,mmgen_cmd,cmd_args=[]):
+	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]
+		if extra_desc: desc += " " + extra_desc
 		if opt.verbose or opt.exact_output:
 			sys.stderr.write(
 				green("Testing %s\nExecuting " % desc) +
@@ -449,6 +554,9 @@ class MMGenExpect(object):
 	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()]
 
@@ -681,17 +789,21 @@ class MMGenTestSuite(object):
 		ok()
 
 	def refwalletgen(self,name):
-		label = cfg['wallet_label']
-		args = ["-d",cfg['tmpdir'],"-p1","-r10",
-					"-b"+cfg['bw_hashparams'],"-L",label]
+		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: ",cfg['bw_passwd']+"\n")
+		t.expect("passphrase: ",ref_wallet_brainpass+"\n")
 		t.usr_rand(10)
 		t.passphrase_new("new MMGen wallet",cfg['wpasswd'])
-		key_id = t.written_to_file("Wallet").split("-")[0].split("/")[-1]
-		refcheck("key id",key_id,cfg['key_id'])
+		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):
 
@@ -699,7 +811,7 @@ class MMGenTestSuite(object):
 			["-d",cfg['tmpdir'],"-p","2","-L","New Label","-r","16",walletfile])
 		t.passphrase("MMGen wallet",cfgs['1']['wpasswd'],pwtype="old")
 		t.expect_getend("Label changed: ")
-		t.expect_getend("Hash preset has 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)
@@ -707,10 +819,6 @@ class MMGenTestSuite(object):
 		t.written_to_file("Wallet")
 		ok()
 
-	def walletchk_newpass(self,name,walletfile):
-		t = self.walletchk_beg(name,[walletfile])
-		ok()
-
 	def walletchk_beg(self,name,args):
 		t = MMGenExpect(name,"mmgen-walletchk", args)
 		t.expect("Getting MMGen wallet data from file '%s'" % args[-1])
@@ -720,9 +828,11 @@ class MMGenTestSuite(object):
 		return t
 
 	def walletchk(self,name,walletfile):
-		t = self.walletchk_beg(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()
@@ -737,13 +847,16 @@ class MMGenTestSuite(object):
 		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 addr data .*\[.*\]: ",regex=True)
+		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")
@@ -765,7 +878,7 @@ class MMGenTestSuite(object):
 			ail.add(ai)
 			aix = parse_addr_idxs(cfgs[s]['addr_idx_list'])
 			if len(aix) != addrs_per_wallet:
-				errmsg(red("Addr index list length != %s: %s" %
+				errmsg(red("Address index list length != %s: %s" %
 							(addrs_per_wallet,repr(aix))))
 				sys.exit()
 			tx_data[s] = {
@@ -804,7 +917,7 @@ class MMGenTestSuite(object):
 		t.license()
 		for num in tx_data.keys():
 			t.expect_getend("Getting address data from file ")
-			chk=t.expect_getend(r"Computed checksum for addr data .*?: ",regex=True)
+			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
@@ -831,13 +944,25 @@ class MMGenTestSuite(object):
 		t.written_to_file("Transaction")
 		ok()
 
-	def txsign(self,name,txfile,walletfile):
-		t = MMGenExpect(name,"mmgen-txsign", ["-d",cfg['tmpdir'],txfile,walletfile])
+	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'])
-		t.expect("Edit transaction comment? (y/N): ","\n")
-		t.written_to_file("Signed transaction")
+		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):
@@ -845,10 +970,10 @@ class MMGenTestSuite(object):
 		t.license()
 		t.tx_view()
 		t.expect("Edit transaction comment? (y/N): ","\n")
-		t.expect("Are you sure you want to broadcast this transaction to the network?")
-		t.expect("Type uppercase 'YES, I REALLY WANT TO DO THIS' to confirm: ","\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, so no transaction was sent")
+		vmsg("This is a simulation; no transaction was sent")
 		ok()
 
 	def export_seed(self,name,walletfile):
@@ -943,6 +1068,8 @@ class MMGenTestSuite(object):
 	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()
@@ -950,9 +1077,7 @@ class MMGenTestSuite(object):
 		t.passphrase("key-address file",cfg['kapasswd'])
 		t.expect("Check key-to-address validity? (y/N): ","y")
 		t.tx_view()
-		t.expect("Signing transaction...OK")
-		t.expect("Edit transaction comment? (y/N): ","\n")
-		t.written_to_file("Signed transaction")
+		self.txsign_end(t)
 		ok()
 
 	def walletgen2(self,name):
@@ -967,14 +1092,10 @@ class MMGenTestSuite(object):
 	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']:
+		for cnum in ('1','2'):
 			t.tx_view()
 			t.passphrase("MMGen wallet",cfgs[cnum]['wpasswd'])
-			t.expect_getend("Signing transaction ")
-			t.expect("Edit transaction comment? (y/N): ","\n")
-			t.written_to_file("Signed transaction #%s" % cnum)
-
+			self.txsign_end(t,cnum)
 		ok()
 
 	def export_mnemonic2(self,name,walletfile):
@@ -993,14 +1114,10 @@ class MMGenTestSuite(object):
 		t = MMGenExpect(name,"mmgen-txsign", ["-d",cfg['tmpdir'],wf1,wf2,txf2])
 		t.license()
 		t.tx_view()
-
-		for s in ['1','3']:
+		for cnum in ('1','3'):
 			t.expect_getend("Getting MMGen wallet data from file ")
-			t.passphrase("MMGen wallet",cfgs[s]['wpasswd'])
-
-		t.expect_getend("Signing transaction")
-		t.expect("Edit transaction comment? (y/N): ","\n")
-		t.written_to_file("Signed transaction")
+			t.passphrase("MMGen wallet",cfgs[cnum]['wpasswd'])
+		self.txsign_end(t)
 		ok()
 
 	def walletgen4(self,name):
@@ -1019,15 +1136,13 @@ class MMGenTestSuite(object):
 		t.license()
 		t.tx_view()
 
-		for cfgnum,what,app in ('1',"incognito"," incognito"),('3',"MMGen",""):
+		for cnum,what,app in ('1',"incognito"," incognito"),('3',"MMGen",""):
 			t.expect_getend("Getting %s wallet data from file " % what)
-			t.passphrase("MMGen%s wallet"%app,cfgs[cfgnum]['wpasswd'])
-			if cfgnum == '1':
+			t.passphrase("MMGen%s wallet"%app,cfgs[cnum]['wpasswd'])
+			if cnum == '1':
 				t.hash_preset("incog wallet",'1')
 
-		t.expect_getend("Signing transaction")
-		t.expect("Edit transaction comment? (y/N): ","\n")
-		t.written_to_file("Signed transaction")
+		self.txsign_end(t)
 		ok()
 
 	def tool_encrypt(self,name,infile=""):
@@ -1043,11 +1158,11 @@ class MMGenTestSuite(object):
 		t.passphrase_new("user data",cfg['tool_enc_passwd'])
 		t.written_to_file("Encrypted data")
 		ok()
-
-	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)
+# 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"
@@ -1059,9 +1174,6 @@ class MMGenTestSuite(object):
 		d2 = read_from_file(get_tmpfile_fn(cfg,of))
 		cmp_or_die(d1,d2)
 
-	def tool_decrypt_ref(self,name,f1,f2):
-		self.tool_decrypt(name,f1,f2)
-
 	def tool_find_incog_data(self,name,f1,f2):
 		i_id = read_from_file(f2).rstrip()
 		vmsg("Incog ID: %s" % cyan(i_id))
@@ -1070,6 +1182,91 @@ class MMGenTestSuite(object):
 		o = t.expect_getend("Incog data for ID \w{8} found at offset ",regex=True)
 		cmp_or_die(hincog_offset,int(o))
 
+	# Saved reference file tests
+	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))
+		what = "seed data" if ext == g.seed_ext else "mnemonic"
+		self.keygen_chksum_chk(name,wf,cfg['seed_id'],what)
+
+	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'],"brainwallet",[arg])
+
+	def keygen_chksum_chk(self,name,wf,seed_id,what,args=[]):
+		t = MMGenExpect(name,"mmgen-keygen", ["-q","-A"]+args+[wf,"1"])
+		chk = t.expect_getend("Valid %s for seed ID " % what)
+		t.close()
+		cmp_or_die(seed_id,chk)
+
+	ref_brain_chk1 = ref_brain_chk2 = ref_brain_chk3 = ref_brain_chk
+
+	def ref_brain_chk3_spc(self,name):
+		self.ref_brain_chk(name,bw_file=ref_bw_file_spc)
+
+	def ref_incog_chk(self,name):
+		for wtype,desc,earg in ('ic_wallet','',[]), \
+							   ('ic_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("MMGen incognito wallet",cfg['wpasswd'])
+			t.hash_preset("incog wallet","1")
+			if wtype == 'ic_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_incog_chk1 = ref_incog_chk2 = ref_incog_chk3 = ref_incog_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",cfg['tool_enc_passwd'])
+		t.readline()
+		import re
+		o = re.sub('\r\n','\n',t.read())
+		cmp_or_die(cfg['sample_text'],o)
+
 # main()
 if opt.pause:
 	import termios,atexit
@@ -1086,26 +1283,17 @@ for cfg in sorted(cfgs): mk_tmpdir(cfgs[cfg])
 
 try:
 	if cmd_args:
-		arg1 = cmd_args[0]
-		if arg1 in utils:
-			globals()[arg1](cmd_args[1:])
-			sys.exit()
-		elif arg1 in meta_cmds:
-			if len(cmd_args) == 1:
-				for cmd in meta_cmds[arg1][1]:
+		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][1]:
 					check_needs_rerun(ts,cmd,build=True,force_delete=True)
+			elif arg in cmd_data:
+				check_needs_rerun(ts,arg,build=True)
 			else:
-				msg("Only one meta command may be specified")
-				sys.exit(1)
-		elif arg1 in cmd_data.keys():
-			if len(cmd_args) == 1:
-				check_needs_rerun(ts,arg1,build=True)
-			else:
-				msg("Only one command may be specified")
-				sys.exit(1)
-		else:
-			errmsg("%s: unrecognized command" % arg1)
-			sys.exit(1)
+				die(1,"%s: unrecognized command" % arg)
 	else:
 		clean()
 		for cmd in cmd_data: