Browse Source

New 'mmgen-walletconv' utility converts to and from all available MMGen wallet
formats, thus obsoleting much of the functionality in '-walletgen' and
'-walletchk'. This is the first script to utilize the new OO wallet code in
'seed.py'.

New tests in the 'test.py' suite.

new file: mmgen-walletconv
new file: mmgen/main_walletconv.py
modified: mmgen/seed.py
modified: test/test.py

philemon 10 years ago
parent
commit
03b1b4b00e

+ 25 - 0
mmgen-walletconv

@@ -0,0 +1,25 @@
+#!/usr/bin/env python
+
+# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
+# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+"""
+mmgen-walletconv: Convert an MMGen deterministic wallet from one format
+                  to another
+"""
+
+from mmgen.main import launch
+launch("walletconv")

+ 17 - 7
mmgen/addr.py

@@ -224,6 +224,13 @@ class AddrInfoList(MMGenObject):
 		if sid in self.data:
 			return self.data[sid]
 
+	def mmaddr2btcaddr(self,mmaddr):
+		btcaddr = ""
+		sid,idx = mmaddr.split(":")
+		if sid in self.seed_ids():
+			btcaddr = self.addrinfo(sid).btcaddr(int(idx))
+		return btcaddr
+
 	def add_wallet_data(self,c):
 		vmsg_r("Getting account data from wallet...")
 		data,accts,i = {},c.listaccounts(minconf=0,includeWatchonly=True),0
@@ -291,10 +298,12 @@ class AddrInfo(MMGenObject):
 		self.seed_id = seed_id
 		self.addrdata = addrdata
 		self.num_addrs = len(addrdata)
-		if self.source in ("wallet","txsign") or \
-				(self.source == "addrgen" and opt.gen_what == "k"):
+		if self.source in ("wallet","txsign"):
 			self.checksum = None
 			self.idxs_fmt = None
+		elif self.source == "addrgen" and opt.gen_what == "k":
+			self.checksum = None
+			self.fmt_addr_idxs()
 		else: # self.source in addrfile, addrgen
 			self.make_addrdata_chksum()
 			self.fmt_addr_idxs()
@@ -383,10 +392,11 @@ class AddrInfo(MMGenObject):
 		out = []
 		from mmgen.addr import addrmsgs
 		out.append(addrmsgs['addrfile_header'] + "\n")
-		w = "Key-address" if status[1] else "Address"
-		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")
+		if self.checksum:
+			w = "Key-address" if status[1] else "Address"
+			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("%s {" % self.seed_id)
 
 		# Body
@@ -409,7 +419,7 @@ class AddrInfo(MMGenObject):
 
 		out.append("}")
 
-		return "\n".join([l.rstrip() for l in out])
+		return "\n".join([l.rstrip() for l in out]) + "\n"
 
 
 	def fmt_addr_idxs(self):

+ 47 - 49
mmgen/crypto.py

@@ -17,7 +17,7 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 """
-crypto.py:  Cryptographic and related routines for the 'mmgen-tool' utility
+crypto.py:  Cryptographic and related routines for the MMGen suite
 """
 
 import sys
@@ -58,7 +58,7 @@ to exit and re-run the program with the '--old-incog-fmt' option.
 }
 
 def encrypt_seed(seed, key):
-	return encrypt_data(seed, key, iv=1, what="seed")
+	return encrypt_data(seed, key, iv=1, desc="seed")
 
 
 def decrypt_seed(enc_seed, key, seed_id, key_id):
@@ -66,21 +66,21 @@ def decrypt_seed(enc_seed, key, seed_id, key_id):
 	vmsg_r("Checking key...")
 	chk1 = make_chksum_8(key)
 	if key_id:
-		if not compare_chksums(key_id,"key id",chk1,"computed",die=False):
+		if not compare_chksums(key_id,"key ID",chk1,"computed"):
 			msg("Incorrect passphrase")
 			return False
 
-	dec_seed = decrypt_data(enc_seed, key, iv=1, what="seed")
+	dec_seed = decrypt_data(enc_seed, key, iv=1, desc="seed")
 
 	chk2 = make_chksum_8(dec_seed)
 
 	if seed_id:
-		if compare_chksums(seed_id,"seed id",chk2,"decrypted seed",die=False):
+		if compare_chksums(seed_id,"seed ID",chk2,"decrypted seed"):
 			qmsg("Passphrase is OK")
 		else:
 			if not opt.debug:
 				msg_r("Checking key ID...")
-				if compare_chksums(key_id,"key id",chk1,"computed",die=False):
+				if compare_chksums(key_id,"key ID",chk1,"computed"):
 					msg("Key ID is correct but decryption of seed failed")
 				else:
 					msg("Incorrect passphrase")
@@ -90,29 +90,25 @@ def decrypt_seed(enc_seed, key, seed_id, key_id):
 #	else:
 #		qmsg("Generated IDs (Seed/Key): %s/%s" % (chk2,chk1))
 
-	if opt.debug: Msg("Decrypted seed: %s" % hexlify(dec_seed))
+	dmsg("Decrypted seed: %s" % hexlify(dec_seed))
 
-	vmsg("OK")
 	return dec_seed
 
 
-def encrypt_data(data, key, iv=1, what="data", verify=True):
-	"""
-	Encrypt arbitrary data using AES256 in counter mode
-	"""
+def encrypt_data(data, key, iv=1, desc="data", verify=True):
 
 	# 192-bit seed is 24 bytes -> not multiple of 16.  Must use MODE_CTR
 	from Crypto.Cipher import AES
 	from Crypto.Util import Counter
 
-	vmsg("Encrypting %s" % what)
+	vmsg("Encrypting %s" % desc)
 
 	c = AES.new(key, AES.MODE_CTR,
 			counter=Counter.new(g.aesctr_iv_len*8,initial_value=iv))
 	enc_data = c.encrypt(data)
 
 	if verify:
-		vmsg_r("Performing a test decryption of the %s..." % what)
+		vmsg_r("Performing a test decryption of the %s..." % desc)
 
 		c = AES.new(key, AES.MODE_CTR,
 				counter=Counter.new(g.aesctr_iv_len*8,initial_value=iv))
@@ -120,15 +116,15 @@ def encrypt_data(data, key, iv=1, what="data", verify=True):
 
 		if dec_data == data: vmsg("done")
 		else:
-			msg("ERROR.\nDecrypted %s doesn't match original %s" % (what,what))
+			msg("ERROR.\nDecrypted %s doesn't match original %s" % (desc,desc))
 			sys.exit(2)
 
 	return enc_data
 
 
-def decrypt_data(enc_data, key, iv=1, what="data"):
+def decrypt_data(enc_data, key, iv=1, desc="data"):
 
-	vmsg_r("Decrypting %s with key..." % what)
+	vmsg_r("Decrypting %s with key..." % desc)
 
 	from Crypto.Cipher import AES
 	from Crypto.Util import Counter
@@ -151,18 +147,18 @@ def scrypt_hash_passphrase(passwd, salt, hash_preset, buflen=32):
 
 
 def make_key(passwd,salt,hash_preset,
-		what="encryption key",from_what="passphrase",verbose=False):
+		desc="encryption key",from_what="passphrase",verbose=False):
 
-	if from_what: what += " from "
+	if from_what: desc += " from "
 	if opt.verbose or verbose:
-		msg_r("Generating %s%s..." % (what,from_what))
+		msg_r("Generating %s%s..." % (desc,from_what))
 	key = scrypt_hash_passphrase(passwd, salt, hash_preset)
 	if opt.verbose or verbose: msg("done")
-	if opt.debug: Msg("Key: %s" % hexlify(key))
+	dmsg("Key: %s" % hexlify(key))
 	return key
 
 
-def get_random_data_from_user(uchars):
+def _get_random_data_from_user(uchars):
 
 	if opt.quiet: msg("Enter %s random symbols" % uchars)
 	else:       msg(crmsg['usr_rand_notice'] % uchars)
@@ -189,8 +185,7 @@ def get_random_data_from_user(uchars):
 
 	fmt_time_data = ["{:.22f}".format(i) for i in time_data]
 
-	if opt.debug:
-		msg("\nUser input:\n%s\nKeystroke time intervals:\n%s\n" %
+	dmsg("\nUser input:\n%s\nKeystroke time intervals:\n%s\n" %
 				(key_data,"\n".join(fmt_time_data)))
 
 	prompt = "User random data successfully acquired.  Press ENTER to continue"
@@ -202,23 +197,23 @@ def get_random_data_from_user(uchars):
 def get_random(length):
 	from Crypto import Random
 	os_rand = Random.new().read(length)
-	if g.use_urandchars:
+	if g.use_urandchars and opt.usr_randchars:
 		from_what = "OS random data"
 		if not g.user_entropy:
 			g.user_entropy = \
-				sha256(get_random_data_from_user(opt.usr_randchars)).digest()
+				sha256(_get_random_data_from_user(opt.usr_randchars)).digest()
 			from_what += " plus user-supplied entropy"
 		else:
 			from_what += " plus saved user-supplied entropy"
 		key = make_key(g.user_entropy, "", '2', from_what=from_what, verbose=True)
-		return encrypt_data(os_rand,key,what="random data",verify=False)
+		return encrypt_data(os_rand,key,desc="random data",verify=False)
 	else:
 		return os_rand
 
 
 def get_seed_from_wallet(
 		infile,
-		prompt_info="{pnm} wallet".format(pnm=g.proj_name),
+		desc="{pnm} wallet".format(pnm=g.proj_name),
 		silent=False
 		):
 
@@ -228,7 +223,7 @@ def get_seed_from_wallet(
 	if opt.debug: display_control_data(*wdata)
 
 	padd = " "+infile if opt.quiet else ""
-	passwd = get_mmgen_passphrase(prompt_info+padd)
+	passwd = get_mmgen_passphrase(desc+padd)
 
 	key = make_key(passwd, salt, hash_preset)
 
@@ -270,17 +265,17 @@ def confirm_old_format():
 
 def get_seed_from_incog_wallet(
 		infile,
-		prompt_info="{pnm} incognito wallet".format(pnm=g.proj_name),
+		desc="{pnm} incognito wallet".format(pnm=g.proj_name),
 		silent=False,
 		hex_input=False
 	):
 
-	what = "incognito wallet data"
+	desc = "incognito wallet data"
 
 	if opt.from_incog_hidden:
 		d = get_hidden_incog_data()
 	else:
-		d = get_data_from_file(infile,what)
+		d = get_data_from_file(infile,desc)
 		if hex_input:
 			try:
 				d = unhexlify("".join(d.split()).strip())
@@ -310,10 +305,13 @@ def get_seed_from_incog_wallet(
 			else 'incog_iv_id'])
 
 	while True:
-		passwd = get_mmgen_passphrase(prompt_info+" "+incog_id)
+		passwd = get_mmgen_passphrase(desc+" "+incog_id)
 
 		qmsg("Configured hash presets: %s" % " ".join(sorted(g.hash_presets)))
-		hp = get_hash_preset_from_user(what="incog wallet")
+		if 'hash_preset' in opt.set_by_user:
+			hp = opt.hash_preset
+		else:
+			hp = get_hash_preset_from_user(desc="incog wallet")
 
 		# IV is used BOTH to initialize counter and to salt password!
 		key = make_key(passwd, iv, hp, "wrapper key")
@@ -399,7 +397,7 @@ def _get_seed(infile,silent=False,seed_id=""):
 		msg("Invalid %s file '%s'" % (source,infile))
 		sys.exit(2)
 
-	if opt.debug: Msg("Seed: %s" % hexlify(seed))
+	dmsg("Seed: %s" % hexlify(seed))
 
 	return seed
 
@@ -415,9 +413,9 @@ def get_seed_retry(infile,seed_id=""):
 
 def _get_seed_from_brain_passphrase(words,silent=False):
 	bp = " ".join(words)
-	if opt.debug: Msg("Sanitized brain passphrase: %s" % bp)
+	dmsg("Sanitized brain passphrase: %s" % bp)
 	seed_len,hash_preset = get_from_brain_opt_params()
-	if opt.debug: Msg("Brainwallet l = %s, p = %s" % (seed_len,hash_preset))
+	dmsg("Brainwallet l = %s, p = %s" % (seed_len,hash_preset))
 	vmsg_r("Hashing brainwallet data.  Please wait...")
 	# Use buflen arg of scrypt.hash() to get seed of desired length
 	seed = scrypt_hash_passphrase(bp, "", hash_preset, buflen=seed_len/8)
@@ -432,31 +430,31 @@ def _get_seed_from_brain_passphrase(words,silent=False):
 # Vars for mmgen_*crypt functions only
 salt_len,sha256_len,nonce_len = 32,32,32
 
-def mmgen_encrypt(data,what="data",hash_preset=''):
+def mmgen_encrypt(data,desc="data",hash_preset=''):
 	salt,iv,nonce = get_random(salt_len),\
 					get_random(g.aesctr_iv_len), \
 					get_random(nonce_len)
-	hp = hash_preset or get_hash_preset_from_user('3',what)
+	hp = hash_preset or get_hash_preset_from_user('3',desc)
 	m = "default" if hp == '3' else "user-requested"
-	vmsg("Encrypting %s" % what)
+	vmsg("Encrypting %s" % desc)
 	qmsg("Using %s hash preset of '%s'" % (m,hp))
-	passwd = get_new_passphrase(what, {})
+	passwd = get_new_passphrase(desc, {})
 	key = make_key(passwd, salt, hp)
 	enc_d = encrypt_data(sha256(nonce+data).digest() + nonce + data, key,
-				int(hexlify(iv),16), what=what)
+				int(hexlify(iv),16), desc=desc)
 	return salt+iv+enc_d
 
 
-def mmgen_decrypt(data,what="data",hash_preset=""):
+def mmgen_decrypt(data,desc="data",hash_preset=""):
 	dstart = salt_len + g.aesctr_iv_len
 	salt,iv,enc_d = data[:salt_len],data[salt_len:dstart],data[dstart:]
-	vmsg("Preparing to decrypt %s" % what)
-	hp = hash_preset or get_hash_preset_from_user('3',what)
+	vmsg("Preparing to decrypt %s" % desc)
+	hp = hash_preset or get_hash_preset_from_user('3',desc)
 	m = "default" if hp == '3' else "user-requested"
 	qmsg("Using %s hash preset of '%s'" % (m,hp))
-	passwd = get_mmgen_passphrase(what)
+	passwd = get_mmgen_passphrase(desc)
 	key = make_key(passwd, salt, hp)
-	dec_d = decrypt_data(enc_d, key, int(hexlify(iv),16), what)
+	dec_d = decrypt_data(enc_d, key, int(hexlify(iv),16), desc)
 	if dec_d[:sha256_len] == sha256(dec_d[sha256_len:]).digest():
 		vmsg("OK")
 		return dec_d[sha256_len+nonce_len:]
@@ -464,8 +462,8 @@ def mmgen_decrypt(data,what="data",hash_preset=""):
 		msg("Incorrect passphrase or hash preset")
 		return False
 
-def mmgen_decrypt_retry(d,what="data"):
+def mmgen_decrypt_retry(d,desc="data"):
 	while True:
-		d_dec = mmgen_decrypt(d,what)
+		d_dec = mmgen_decrypt(d,desc)
 		if d_dec: return d_dec
 		msg("Trying again...")

+ 9 - 50
mmgen/filename.py

@@ -20,66 +20,25 @@
 filename.py:  Filename class and methods for the MMGen suite
 """
 import sys,os
-import mmgen.opt as opt
 from mmgen.obj import *
-import mmgen.globalvars as g
-from mmgen.util import msg,fmt_code_to_sstype
+from mmgen.util import die,get_extension,check_infile
 
 class Filename(MMGenObject):
 
-	exts = {
-		'seed': {
-			"mmdat":   "Wallet",
-			"mmseed":  "SeedFile",
-			"mmwords": "Mnemonic",
-			"mmbrain": "Brainwallet",
-			"mmincog": "IncogWallet",
-			"mmincox": "IncogWalletHex",
-		},
-		'tx': {
-			"raw":         "RawTX",
-			"sig":         "SigTX",
-		},
-		'addr': {
-			"addrs":       "AddrInfo",
-			"keys":        "KeyInfo",
-			"akeys":       "KeyAddrInfo",
-			"akeys.mmenc": "KeyAddrInfoEnc",
-		},
-		'other': {
-			"chk":         "AddrInfoChecksum",
-			"mmenc":       "MMEncInfo",
-		},
-	}
-
-	ftypes = {
-		'seed': {
-			"hincog":   "IncogWalletHidden",
-		},
-	}
-
-	def __init__(self,fn,ftype=""):
+	def __init__(self,fn,ftype=None):
 		self.name     = fn
 		self.dirname  = os.path.dirname(fn)
 		self.basename = os.path.basename(fn)
 		self.ext      = None
+		self.ftype    = ftype
 
-		def mf1(k): return k == ftype
-		def mf2(k): return '.'+k == fn[-len('.'+k):]
-
-		# find file info for ftype or extension
-		e,attr,have_match = (self.ftypes,"ftype",mf1) if ftype else \
-							(self.exts,"ext",mf2)
-
-		for k in e:
-			for j in e[k]:
-				if have_match(j):
-					setattr(self,attr,j)
-					self.fclass = k
-					self.sstype = e[k][j]
+# This should be done before license msg instead
+#		check_infile(fn)
 
-		if not hasattr(self,attr):
-			die(2,"Unrecognized %s for file '%s'" % (attr,fn))
+		if not ftype:
+			self.ext = get_extension(fn)
+			if not (self.ext):
+				die(2,"Unrecognized extension '.%s' for file '%s'" % (self.ext,fn))
 
 		# TODO: Check for Windows
 		import stat

+ 2 - 14
mmgen/globalvars.py

@@ -53,7 +53,8 @@ version   = '0.8.0'
 
 required_opts = [
 	"quiet","verbose","debug","outdir","echo_passphrase","passwd_file",
-	"usr_randchars","stdout","show_hash_presets"
+	"usr_randchars","stdout","show_hash_presets","label",
+	"keep_passphrase","keep_hash_preset"
 ]
 min_screen_width = 80
 
@@ -119,16 +120,3 @@ addr_label_symbols = wallet_label_symbols = printable_nonl
 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
-
-wallet_fmt_codes = (
-	( "Wallet",            "wallet", "w" ),
-	( "SeedFile",          "mmseed","seed", "s" ),
-	( "Mnemonic",          "mmwords","words","mnemonic","mnem","m" ),
-	( "Brainwallet",       "mmbrain","brainwallet","brain","bw","b" ),
-	( "IncogWallet",       "mmincog","incog","i" ),
-	( "IncogWalletHex",    "mmincox","incog_hex","ix" ),
-	( "IncogWalletHidden", "incog_hidden","ih" ),
-)
-#addr_label_punc = ".","_",",","-"," ","(",")"
-#addr_label_symbols = tuple(ascii_letters + digits) + addr_label_punc
-#wallet_label_punc = addr_label_punc

+ 5 - 8
mmgen/license.py

@@ -19,18 +19,16 @@
 """
 license.py:  Text of GPLv3
 """
+
 import mmgen.globalvars as g
 
-gpl = {
-	'warning': """
+warning = """
   {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,pnm=g.proj_name),
-	'prompt': """
-Press 'w' for conditions and warranty info, or 'c' to continue:
-""",
-	'conditions': """
+""".format(g=g,pnm=g.proj_name)
+
+conditions = """
                        TERMS AND CONDITIONS
 
   0. Definitions.
@@ -581,4 +579,3 @@ an absolute waiver of all civil liability in connection with the
 Program, unless a warranty or assumption of liability accompanies a
 copy of the Program in return for a fee.
 """
-}

+ 1 - 0
mmgen/main.py

@@ -31,6 +31,7 @@ def launch(what):
 	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
 
 	try: import termios

+ 2 - 2
mmgen/main_addrgen.py

@@ -161,8 +161,8 @@ 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,
-		(what=="keys"and not opt.quiet and sys.stdout.isatty()))
+	write_to_stdout(addrdata_str,what,ask_terminal=(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 (

+ 1 - 1
mmgen/main_addrimport.py

@@ -64,7 +64,7 @@ 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-{pnm} addresses
+You must specify an {pnm} address file (or a list of non-{pnm} addresses
 with the '--addrlist' option)
 """.strip().format(pnm=g.proj_name))
 	sys.exit(1)

+ 2 - 2
mmgen/main_passchg.py

@@ -67,8 +67,8 @@ seed_id,key_id = metadata[:2]
 
 # Repeat on incorrect pw entry
 while True:
-	p = "{pnm} wallet".format(pnm=g.proj_name)
-	passwd = get_mmgen_passphrase(p,not opt.keep_old_passphrase)
+	desc = "{pnm} wallet".format(pnm=g.proj_name)
+	passwd = get_mmgen_passphrase(desc,not opt.keep_old_passphrase)
 	key = make_key(passwd, salt, hash_preset)
 	seed = decrypt_seed(enc_seed, key, seed_id, key_id)
 	if seed: break

+ 16 - 16
mmgen/main_pywallet.py

@@ -180,8 +180,8 @@ class AES(object):
 	def rotate(self, word):
 		""" Rijndael's key schedule rotate operation.
 
-		Rotate a word eight bits to the left: eg, rotate(1d2c3a4f) == 2c3a4f1d
-		Word is an char list of size 4 (32 bits overall).
+        Rotate a word eight bits to the left: eg, rotate(1d2c3a4f) == 2c3a4f1d
+        Word is an char list of size 4 (32 bits overall).
 		"""
 		return word[1:] + word[:1]
 
@@ -230,10 +230,10 @@ class AES(object):
 	def expandKey(self, key, size, expandedKeySize):
 		"""Rijndael's key expansion.
 
-		Expands an 128,192,256 key into an 176,208,240 bytes key
+        Expands an 128,192,256 key into an 176,208,240 bytes key
 
-		expandedKey is a char list of large enough size,
-		key is the non-expanded key.
+        expandedKey is a char list of large enough size,
+        key is the non-expanded key.
 		"""
 		# current expanded keySize, in bytes
 		currentSize = 0
@@ -276,8 +276,8 @@ class AES(object):
 
 	def createRoundKey(self, expandedKey, roundKeyPointer):
 		"""Create a round key.
-		Creates a round key from the given expanded key and the
-		position within the expanded key.
+        Creates a round key from the given expanded key and the
+        position within the expanded key.
 		"""
 		roundKey = [0] * 16
 		for i in range(4):
@@ -1183,8 +1183,8 @@ def parse_BlockLocator(vds):
 		return d
 
 def deserialize_BlockLocator(d):
-  result = "Block Locator top: "+d['hashes'][0][::-1].encode('hex_codec')
-  return result
+	result = "Block Locator top: "+d['hashes'][0][::-1].encode('hex_codec')
+	return result
 
 def parse_setting(setting, vds):
 	if setting[0] == "f":    # flag (boolean) settings
@@ -1404,11 +1404,11 @@ def parse_wallet(db, item_callback):
 
 def update_wallet(db, type, data):
 	"""Write a single item to the wallet.
-	db must be open with writable=True.
-	type and data are the type code and data dictionary as parse_wallet would
-	give to item_callback.
-	data's __key__, __value__ and __type__ are ignored; only the primary data
-	fields are used.
+    db must be open with writable=True.
+    type and data are the type code and data dictionary as parse_wallet would
+    give to item_callback.
+    data's __key__, __value__ and __type__ are ignored; only the primary data
+    fields are used.
 	"""
 	d = data
 	kds = BCDataStream()
@@ -1656,8 +1656,8 @@ elif opt.keysforaddrs:
 		msg("Warning: not all requested keys found")
 
 len_arg = "%s" % len(wallet_addrs) \
-   if len(data) == len(wallet_addrs) or ext == "json" \
-   else "%s:%s" % (len(data),len(wallet_addrs))
+	if len(data) == len(wallet_addrs) or ext == "json" \
+		else "%s:%s" % (len(data),len(wallet_addrs))
 
 from mmgen.util import make_chksum_8,write_to_file,write_to_stdout
 wallet_id = make_chksum_8(str(sorted(wallet_addrs)))

+ 9 - 13
mmgen/main_txcreate.py

@@ -26,6 +26,7 @@ from decimal import Decimal
 
 import mmgen.globalvars as g
 import mmgen.opt as opt
+from mmgen.util import dmsg
 from mmgen.tx import *
 
 pnm = g.proj_name
@@ -196,9 +197,9 @@ Display options: show [D]ays, [g]roup, show [m]mgen addr, r[e]draw screen
 					i.addr = "%s%s %s" % (
 						i.address[:btaddr_w-len(dots)],
 						dots, (
-                        ("{:<{w}} ".format(i.mmid,w=mmid_w) if i.mmid else "")
-                            + i.comment)[:acct_w]
-                        )
+						("{:<{w}} ".format(i.mmid,w=mmid_w) if i.mmid else "")
+							+ i.comment)[:acct_w]
+						)
 				else:
 					i.addr = i.address
 
@@ -294,16 +295,13 @@ def mmaddr2btcaddr_unspent(unspent,mmaddr):
 
 
 def mmaddr2btcaddr(c,mmaddr,ail_w,ail_f):
-	# assume mmaddr has already been checked
-	sid,idx = mmaddr.split(":")
-	btcaddr = ""
 
-	if sid in ail_w.seed_ids():
-		btcaddr = ail_w.addrinfo(sid).btcaddr(int(idx))
+	# assume mmaddr has already been checked
+	btcaddr = ail_w.mmaddr2btcaddr(mmaddr)
 
 	if not btcaddr:
-		if ail_f and sid in ail_f.seed_ids():
-			btcaddr = ail_f.addrinfo(sid).btcaddr(int(idx))
+		if ail_f:
+			btcaddr = ail_f.mmaddr2btcaddr(mmaddr)
 			if btcaddr:
 				msg(wmsg['addr_in_addrfile_only'].format(mmgenaddr=mmaddr))
 				if not keypress_confirm("Continue anyway?"):
@@ -449,9 +447,7 @@ if change > 0: tx_out[change_addr] = float(change)
 
 tx_in = [{"txid":i.txid, "vout":i.vout} for i in sel_unspent]
 
-if opt.debug:
-	Msg("tx_in:  " + repr(tx_in))
-	Msg("tx_out: " + repr(tx_out))
+dmsg("tx_in:  %s\ntx_out: %s" % (repr(tx_in),repr(tx_out)))
 
 if opt.comment_file:
 	if keypress_confirm("Edit comment?",False):

+ 2 - 2
mmgen/main_txsend.py

@@ -68,12 +68,12 @@ if keypress_confirm("Edit transaction comment?"):
 	write_to_file(outfile,data,w,False,True,True)
 
 warn   = "Once this transaction is sent, there's no taking it back!"
-what   = "broadcast this transaction to the network"
+action = "broadcast this transaction to the network"
 expect =  "YES, I REALLY WANT TO DO THIS"
 
 if opt.quiet: warn,expect = "","YES"
 
-confirm_or_exit(warn, what, expect)
+confirm_or_exit(warn, action, expect)
 
 msg("Sending transaction")
 

+ 9 - 9
mmgen/main_txsign.py

@@ -25,7 +25,7 @@ import sys
 import mmgen.globalvars as g
 import mmgen.opt as opt
 from mmgen.tx import *
-from mmgen.util import do_license_msg
+from mmgen.util import do_license_msg,dmsg
 
 pnm = g.proj_name
 pnl = pnm.lower()
@@ -150,7 +150,7 @@ def sign_transaction(c,tx_hex,tx_num_str,sig_data,keys=None):
 
 	if keys:
 		qmsg("Passing %s key%s to bitcoind" % (len(keys),suf(keys,"k")))
-		if opt.debug: Msg("Keys:\n  %s" % "\n  ".join(keys))
+		dmsg("Keys:\n  %s" % "\n  ".join(keys))
 
 	msg_r("Signing transaction{}...".format(tx_num_str))
 	from mmgen.rpc import exceptions
@@ -192,11 +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,what,infiles,saved_seeds,return_keys=False):
+def check_maps_from_seeds(maplist,desc,infiles,saved_seeds,return_keys=False):
 
 	if not maplist: return []
 	qmsg("Checking {pnm} -> BTC address mappings for {w}s (from seed(s))".format(
-				pnm=pnm,w=what))
+				pnm=pnm,w=desc))
 	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])
@@ -253,9 +253,9 @@ 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):
+def check_maps_from_kafile(imap,desc,kadata,return_keys=False):
 	if not kadata: return []
-	qmsg("Checking {pnm} -> BTC address mappings for {w}s (from key-address file)".format(pnm=pnm,w=what))
+	qmsg("Checking {pnm} -> BTC address mappings for {w}s (from key-address file)".format(pnm=pnm,w=desc))
 	ret = []
 	for k in imap.keys():
 		if k in kadata.keys():
@@ -266,9 +266,9 @@ def check_maps_from_kafile(imap,what,kadata,return_keys=False):
 				kl,il = "key-address file:","tx file:"
 				msg(wmsg['mm2btc_mapping_error']%(kl,k,kadata[k][0],il,k,imap[k]))
 				sys.exit(2)
-	if ret: vmsg("Removed %s address%s from %ss map" % (len(ret),suf(ret,"a"),what))
+	if ret: vmsg("Removed %s address%s from %ss map" % (len(ret),suf(ret,"a"),desc))
 	if return_keys:
-		vmsg("Added %s wif key%s from %ss map" % (len(ret),suf(ret,"k"),what))
+		vmsg("Added %s wif key%s from %ss map" % (len(ret),suf(ret,"k"),desc))
 		return ret
 
 
@@ -288,7 +288,7 @@ infiles = opt.opts.init(opts_data,add_opts=["b16"])
 for l in (
 ('tx_id', 'info'),
 ('tx_id', 'terse_info'),
-): opt.opts.warn_incompatible_opts(l)
+): opt.opts.die_on_incompatible_opts(l)
 
 if opt.from_incog_hex or opt.from_incog_hidden: opt.from_incog = True
 

+ 2 - 3
mmgen/main_walletchk.py

@@ -95,8 +95,7 @@ def export_to_hidden_incog(incog_enc):
 	outfile,offset = opt.export_incog_hidden.split(",") #Already sanity-checked
 	if opt.outdir: outfile = make_full_path(opt.outdir,outfile)
 
-	if opt.debug:
-		Msg("Incog data len %s, offset %s" % (len(incog_enc),offset))
+	dmsg("Incog data len %s, offset %s" % (len(incog_enc),offset))
 	check_data_fits_file_at_offset(outfile,int(offset),len(incog_enc),"write")
 
 	if not opt.quiet: confirm_or_exit("","alter file '%s'" % outfile)
@@ -162,7 +161,7 @@ if opt.export_mnemonic:
 
 elif opt.export_seed:
 	from mmgen.bitcoin import b58encode_pad
-	data = split_into_columns(4,b58encode_pad(seed))
+	data = split_into_cols(4,b58encode_pad(seed))
 	chk = make_chksum_6(b58encode_pad(seed))
 	fn = "%s.%s" % (make_chksum_8(seed).upper(), g.seed_ext)
 	write_to_file_or_stdout(fn, "%s %s\n" % (chk,data), "seed data")

+ 89 - 0
mmgen/main_walletconv.py

@@ -0,0 +1,89 @@
+#!/usr/bin/env python
+#
+# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
+# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+"""
+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()

+ 7 - 7
mmgen/obj.py

@@ -20,7 +20,7 @@
 obj.py:  The MMGenObject class and methods
 """
 import mmgen.globalvars as g
-from mmgen.util import msgrepr_exit,msgrepr
+from mmgen.util import mdie,mmsg
 
 lvl = 0
 
@@ -72,18 +72,18 @@ class MMGenObject(object):
 		col_w = max(len(k) for k in keys)
 		fs = "{}%-{}s: %s".format(indent,col_w)
 
-  		methods = [k for k in keys if repr(getattr(self,k))[:14] == '<bound method ']
+		methods = [k for k in keys if repr(getattr(self,k))[:14] == '<bound method ']
 
-  		def f(k): return repr(getattr(self,k))[:14] == '<bound method '
-  		methods = filter(f,keys)
-  		def f(k): return repr(getattr(self,k))[:7] == '<mmgen.'
-  		objects = filter(f,keys)
+		def f(k): return repr(getattr(self,k))[:14] == '<bound method '
+		methods = filter(f,keys)
+		def f(k): return repr(getattr(self,k))[:7] == '<mmgen.'
+		objects = filter(f,keys)
 		other = list(set(keys) - set(methods) - set(objects))
 
 		for k in sorted(methods) + sorted(other) + sorted(objects):
 			val = getattr(self,k)
 			if str(type(val))[:13] == "<class 'mmgen": # recurse into sub-objects
-				out.append("\n%s%s (%s):" % (indent,k,repr(type(val))))
+				out.append("\n%s%s (%s):" % (indent,k,type(val)))
 				lvl += 1
 				out.append(str(getattr(self,k))+"\n")
 				lvl -= 1

+ 101 - 44
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,msgrepr_exit,msgrepr,Msg,fmt_type
+from mmgen.util import msg,msg_r,mdie,mmsg,Msg,die
 
 def usage():
 	Msg("USAGE: %s %s" % (g.prog_name, usage_txt))
@@ -32,17 +32,15 @@ def usage():
 
 def print_version_info():
 	Msg("""
-{progname_uc} version {g.version}
+{pgnm_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())
+""".format(pnm=g.proj_name, g=g, pgnm_uc=g.prog_name.upper()).strip())
 
-def warn_incompatible_opts(incompat_list):
+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:
-		msg("Mutually exclusive options: %s" % " ".join(
-					["--"+b.replace("_","-") for b in bad]))
-		sys.exit(1)
+		die(1,"Conflicting options: %s" % ", ".join([fmt_opt(b) for b in bad]))
 
 def _typeconvert_from_dfl(key):
 
@@ -51,7 +49,6 @@ def _typeconvert_from_dfl(key):
 	gval = g.__dict__[key]
 	uval = opt.__dict__[key]
 	gtype = type(gval)
-	utype = type(uval)
 
 	try:
 		opt.__dict__[key] = gtype(opt.__dict__[key])
@@ -62,16 +59,18 @@ def _typeconvert_from_dfl(key):
 			'float': 'a float',
 			'bool':  'a boolean value',
 		}
-		m = [d[k] for k in d if __builtins__[k] == gtype]
-		fs = "'%s': invalid parameter for '--%s' option (not %s)"
-		msg(fs % (opt.__dict__[key],opt.replace("_","-"),m))
-		sys.exit(1)
+		die(1, "'%s': invalid parameter for '--%s' option (not %s)" % (
+			opt.__dict__[key],
+			key.replace("_","-"),
+			d[gtype.__name__]
+		))
 
 	if g.debug:
 		Msg("Opt overriden by user:\n    %-18s: %s" % (
 				key, ("%s -> %s" % (gval,uval))
 			))
 
+def fmt_opt(o): return "--" + o.replace("_","-")
 
 def _show_hash_presets():
 	fs = "  {:<7} {:<6} {:<3}  {}"
@@ -116,21 +115,25 @@ def init(opts_data,add_opts=[]):
 	for o in [s.rstrip("=") for s in long_opts] + g.required_opts + add_opts:
 		opt.__dict__[o] = uopts[o] if o in uopts else None
 
-	# check user-set opts without modifying them
-	if not check_opts(uopts): sys.exit(1)
-
 	# A special case - do this here, before opt gets set from g.dfl_vars
 	if opt.usr_randchars: g.use_urandchars = True
 
 	# If user opt is set, convert its type based on value in mmgen.globalvars
 	# If unset, set it to default value in mmgen.globalvars (g):
+	opt.__dict__['set_by_user'] = []
 	for k in g.dfl_vars:
 		if k in opt.__dict__ and opt.__dict__[k] != None:
 			_typeconvert_from_dfl(k)
-		else: opt.__dict__[k] = g.__dict__[k]
+			opt.set_by_user.append(k)
+		else:
+			opt.__dict__[k] = g.__dict__[k]
+
+	# Check user-set opts without modifying them
+	if not check_opts(uopts): sys.exit(1)
 
 	if opt.show_hash_presets:
 		_show_hash_presets(); sys.exit(0)
+
 	if opt.debug: opt.verbose = True
 
 	if g.debug:
@@ -138,7 +141,7 @@ def init(opts_data,add_opts=[]):
 		for k in opt.__dict__:
 			v = opt.__dict__[k]
 			if v != None and k != "opts":
- 				Msg("    %-18s: %-6s [%s]" % (k,v,fmt_type(v)))
+				Msg("    %-18s: %-6s [%s]" % (k,v,type(v).__name__))
 		Msg("### END OPTS.PY ###\n")
 
 	for l in (
@@ -146,7 +149,7 @@ def init(opts_data,add_opts=[]):
 	('export_incog','export_incog_hex','export_incog_hidden','export_mnemonic',
 	'export_seed'),
 	('quiet','verbose')
-	): warn_incompatible_opts(l)
+	): die_on_incompatible_opts(l)
 
 	return args
 
@@ -160,50 +163,61 @@ def show_all_opts():
 
 def check_opts(usr_opts):       # Returns false if any check fails
 
-	def opt_splits(val,sep,n,what):
+	def opt_splits(val,sep,n,desc):
 		sepword = "comma" if sep == "," else (
 					"colon" if sep == ":" else ("'"+sep+"'"))
 		try: l = val.split(sep)
 		except:
-			msg("'%s': invalid %s (not %s-separated list)" % (val,what,sepword))
+			msg("'%s': invalid %s (not %s-separated list)" % (val,desc,sepword))
 			return False
 
 		if len(l) == n: return True
 		else:
 			msg("'%s': invalid %s (%s %s-separated items required)" %
-					(val,what,n,sepword))
+					(val,desc,n,sepword))
 			return False
 
-	def opt_compares(val,op,target,what):
+	def opt_compares(val,op,target,desc):
 		if not eval("%s %s %s" % (val, op, target)):
-			msg("%s: invalid %s (not %s %s)" % (val,what,op,target))
+			msg("%s: invalid %s (not %s %s)" % (val,desc,op,target))
 			return False
 		return True
 
-	def opt_is_int(val,what):
+	def opt_is_int(val,desc):
 		try: int(val)
 		except:
-			msg("'%s': invalid %s (not an integer)" % (val,what))
+			msg("'%s': invalid %s (not an integer)" % (val,desc))
 			return False
 		return True
 
-	def opt_is_in_list(val,lst,what):
+	def opt_is_in_list(val,lst,desc):
 		if val not in lst:
 			q,sep = ("'","','") if type(lst[0]) == str else ("",",")
-			msg("{q}{}{q}: invalid {}\nValid options: {q}{}{q}".format(
-					val,what,sep.join([str(i) for i in sorted(lst)]),q=q))
+			msg("{q}{v}{q}: invalid {w}\nValid choices: {q}{o}{q}".format(
+					v=val,w=desc,q=q,
+					o=sep.join([str(i) for i in sorted(lst)])
+				))
 			return False
 		return True
 
+	def opt_unrecognized(key,val,desc):
+		msg("'%s': unrecognized %s for option '%s'"
+				% (val,desc,fmt_opt(key)))
+		return False
+
+	def opt_display(key,val='',beg="For selected",end=":\n"):
+		s = "%s=%s" % (fmt_opt(key),val) if val else fmt_opt(key)
+		msg_r("%s option '%s'%s" % (beg,s,end))
+
 	global opt
 	for key,val in [(k,getattr(opt,k)) for k in usr_opts]:
 
-		what = "parameter for '--%s' option" % key.replace("_","-")
+		desc = "parameter for '%s' option" % fmt_opt(key)
 
 		# Check for file existence and readability
+		from mmgen.util import check_infile
 		if key in ('keys_from_file','mmgen_keys_from_file',
 				'passwd_file','keysforaddrs','comment_file'):
-			from mmgen.util import check_infile
 			check_infile(val)  # exits on error
 			continue
 
@@ -220,40 +234,83 @@ def check_opts(usr_opts):       # Returns false if any check fails
 			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
+			sstype = SeedSource.fmt_code_to_sstype(val)
+			if not sstype:
+				return opt_unrecognized(key,val,"format code")
+			if key == 'out_fmt':
+				p = 'hidden_incog_output_params'
+				if sstype == IncogWalletHidden and not getattr(opt,p):
+						die(1,"Hidden incog format output requested. You must supply"
+						+ " a file and offset with the '%s' option" % fmt_opt(p))
+				if issubclass(sstype,IncogWallet) and opt.old_incog_fmt:
+					opt_display(key,val,beg="Selected",end=" ")
+					opt_display('old_incog_fmt',beg="conflicts with",end=":\n")
+					die(1,"Export to old incog wallet format unsupported")
+				elif issubclass(sstype,Brainwallet):
+					die(1,"Output to brainwallet format unsupported")
+		elif key in ('hidden_incog_input_params','hidden_incog_output_params'):
+			if key == 'hidden_incog_input_params':
+				check_infile(val.split(",")[0])
+				key2 = 'in_fmt'
+			else:
+				key2 = 'out_fmt'
+			if hasattr(opt,key2):
+				val2 = getattr(opt,key2)
+				from mmgen.seed import IncogWalletHidden
+				if val2 and val2 not in IncogWalletHidden.fmt_codes:
+					die(1,
+						"Option conflict:\n  %s, with\n  %s=%s" % (
+						fmt_opt(key),fmt_opt(key2),val2
+					))
+
+		# begin OLD, deprecated
+		elif key == 'hidden_incog_params':
+			from mmgen.util import check_outfile
+			if not opt_splits(val,",",2,desc): return False
+			outfile,offset = val.split(",")
+			check_outfile(outfile)
+			w = "offset " + desc
+			if not opt_is_int(offset,w): return False
+			if not opt_compares(offset,">=",0,desc): return False
 		elif key == 'export_incog_hidden' or key == 'from_incog_hidden':
 			if key == 'from_incog_hidden':
-				if not opt_splits(val,",",3,what): return False
+				if not opt_splits(val,",",3,desc): return False
 				infile,offset,seed_len = val.split(",")
 				from mmgen.util import check_infile
 				check_infile(infile)
-				w = "seed length " + what
+				w = "seed length " + desc
 				if not opt_is_int(seed_len,w): return False
 				if not opt_is_in_list(int(seed_len),g.seed_lens,w): return False
 			else:
 				from mmgen.util import check_outfile
-				if not opt_splits(val,",",2,what): return False
+				if not opt_splits(val,",",2,desc): return False
 				outfile,offset = val.split(",")
 				check_outfile(outfile)
-			w = "offset " + what
+			w = "offset " + desc
 			if not opt_is_int(offset,w): return False
-			if not opt_compares(offset,">=",0,what): return False
+			if not opt_compares(offset,">=",0,desc): return False
 		elif key == 'from_brain':
-			if not opt_splits(val,",",2,what): return False
+			if not opt_splits(val,",",2,desc): return False
 			l,p = val.split(",")
-			w = "seed length " + what
+			w = "seed length " + desc
 			if not opt_is_int(l,w): return False
 			if not opt_is_in_list(int(l),g.seed_lens,w): return False
-			w = "hash preset " + what
+			w = "hash preset " + desc
 			if not opt_is_in_list(p,g.hash_presets.keys(),w): return False
+		# end OLD
 		elif key == 'seed_len':
-			if not opt_is_int(val,what): return False
-			if not opt_is_in_list(int(val),g.seed_lens,what): return False
+			if not opt_is_int(val,desc): return False
+			if not opt_is_in_list(int(val),g.seed_lens,desc): return False
 		elif key == 'hash_preset':
-			if not opt_is_in_list(val,g.hash_presets.keys(),what): return False
+			if not opt_is_in_list(val,g.hash_presets.keys(),desc): return False
 		elif key == 'usr_randchars':
-			if not opt_is_int(val,what): return False
-			if not opt_compares(val,">=",g.min_urandchars,what): return False
-			if not opt_compares(val,"<=",g.max_urandchars,what): return False
+			if val == 0: continue
+			if not opt_is_int(val,desc): return False
+			if not opt_compares(val,">=",g.min_urandchars,desc): return False
+			if not opt_compares(val,"<=",g.max_urandchars,desc): return False
 		else:
 			if g.debug: Msg("check_opts(): No test for opt '%s'" % key)
 

+ 13 - 14
mmgen/rpc/__init__.py

@@ -23,7 +23,7 @@ bitcoin-python - Easy-to-use Bitcoin API client
 
 
 def connect_to_local(filename=None):
-    """
+	"""
     Connect to default bitcoin instance owned by this user, on this machine.
 
     Returns a :class:`~mmgen.rpc.connection.BitcoinConnection` object.
@@ -31,24 +31,23 @@ def connect_to_local(filename=None):
     Arguments:
 
         - `filename`: Path to a configuration file in a non-standard location (optional)
-    """
-    from mmgen.rpc.connection import BitcoinConnection
-    from mmgen.rpc.config import read_default_config
+	"""
+	from mmgen.rpc.connection import BitcoinConnection
+	from mmgen.rpc.config import read_default_config
 
-    cfg = read_default_config(filename)
-    port = int(cfg.get('rpcport', '18332' if cfg.get('testnet') else '8332'))
-    rcpuser = cfg.get('rpcuser', '')
+	cfg = read_default_config(filename)
+	port = int(cfg.get('rpcport', '18332' if cfg.get('testnet') else '8332'))
+	rcpuser = cfg.get('rpcuser', '')
 
-    return BitcoinConnection(rcpuser, cfg['rpcpassword'], 'localhost', port)
+	return BitcoinConnection(rcpuser, cfg['rpcpassword'], 'localhost', port)
 
 
-def connect_to_remote(user, password, host='localhost', port=8332,
-                      use_https=False):
-    """
+def connect_to_remote(user,password,host='localhost',port=8332,use_https=False):
+	"""
     Connect to remote or alternative local bitcoin client instance.
 
     Returns a :class:`~mmgen.rpc.connection.BitcoinConnection` object.
-    """
-    from mmgen.rpc.connection import BitcoinConnection
+	"""
+	from mmgen.rpc.connection import BitcoinConnection
 
-    return BitcoinConnection(user, password, host, port, use_https)
+	return BitcoinConnection(user, password, host, port, use_https)

+ 36 - 36
mmgen/rpc/config.py

@@ -23,53 +23,53 @@ Utilities for reading bitcoin configuration files.
 
 
 def read_config_file(filename):
-    """
+	"""
     Read a simple ``'='``-delimited config file.
     Raises :const:`IOError` if unable to open file, or :const:`ValueError`
     if an parse error occurs.
-    """
-    f = open(filename)
-    try:
-        cfg = {}
-        for line in f:
-            line = line.strip()
-            if line and not line.startswith("#"):
-                try:
-                    (key, value) = line.split('=', 1)
-                    cfg[key] = value
-                except ValueError:
-                    pass  # Happens when line has no '=', ignore
-    finally:
-        f.close()
-    return cfg
+	"""
+	f = open(filename)
+	try:
+		cfg = {}
+		for line in f:
+			line = line.strip()
+			if line and not line.startswith("#"):
+				try:
+					(key, value) = line.split('=', 1)
+					cfg[key] = value
+				except ValueError:
+					pass  # Happens when line has no '=', ignore
+	finally:
+		f.close()
+	return cfg
 
 
 def read_default_config(filename=None):
-    """
+	"""
     Read bitcoin default configuration from the current user's home directory.
 
     Arguments:
 
     - `filename`: Path to a configuration file in a non-standard location (optional)
-    """
-    if filename is None:
-        import os
-        import platform
-        home = os.getenv("HOME")
-        if not home:
-            raise IOError("Home directory not defined, don't know where to look for config file")
+	"""
+	if filename is None:
+		import os
+		import platform
+		home = os.getenv("HOME")
+		if not home:
+			raise IOError("Home directory not defined, don't know where to look for config file")
 
-        if platform.system() == "Darwin":
-            location = 'Library/Application Support/Bitcoin/bitcoin.conf'
-        else:
-            location = '.bitcoin/bitcoin.conf'
-        filename = os.path.join(home, location)
+		if platform.system() == "Darwin":
+			location = 'Library/Application Support/Bitcoin/bitcoin.conf'
+		else:
+			location = '.bitcoin/bitcoin.conf'
+		filename = os.path.join(home, location)
 
-    elif filename.startswith("~"):
-        import os
-        filename = os.path.expanduser(filename)
+	elif filename.startswith("~"):
+		import os
+		filename = os.path.expanduser(filename)
 
-    try:
-        return read_config_file(filename)
-    except (IOError, ValueError):
-        pass  # Cannot read config file, ignore
+	try:
+		return read_config_file(filename)
+	except (IOError, ValueError):
+		pass  # Cannot read config file, ignore

+ 201 - 205
mmgen/rpc/connection.py

@@ -32,21 +32,21 @@ from mmgen.rpc.data import (ServerInfo, AccountInfo, AddressInfo, TransactionInf
 
 class BitcoinConnection(object):
 	"""
-	A BitcoinConnection object defines a connection to a bitcoin server.
-	It is a thin wrapper around a JSON-RPC API connection.
+    A BitcoinConnection object defines a connection to a bitcoin server.
+    It is a thin wrapper around a JSON-RPC API connection.
 
-	Up-to-date for SVN revision 198.
+    Up-to-date for SVN revision 198.
 
-	Arguments to constructor:
+    Arguments to constructor:
 
-	- *user* -- Authenticate as user.
-	- *password* -- Authentication password.
-	- *host* -- Bitcoin JSON-RPC host.
-	- *port* -- Bitcoin JSON-RPC port.
+    - *user* -- Authenticate as user.
+    - *password* -- Authentication password.
+    - *host* -- Bitcoin JSON-RPC host.
+    - *port* -- Bitcoin JSON-RPC port.
 	"""
 	def __init__(self, user, password, host='localhost', port=8332, use_https=False):
 		"""
-		Create a new bitcoin server connection.
+        Create a new bitcoin server connection.
 		"""
 		url = 'http{s}://{user}:{password}@{host}:{port}/'.format(
 			s='s' if use_https else '',
@@ -59,8 +59,6 @@ class BitcoinConnection(object):
 
 # importaddress <address> [label] [rescan=true]
 	def importaddress(self,address,label=None,rescan=True):
-		"""
-		"""
 		try:
 #			return self.proxy.badmethod(address,label) # DEBUG
 			return self.proxy.importaddress(address,label,rescan)
@@ -78,8 +76,6 @@ ERROR: 'importaddress' not found.  Does your bitcoind support watch-only addrs?
 
 # sendrawtransaction <hex string> [allowhighfees=false]
 	def sendrawtransaction(self,tx):
-		"""
-		"""
 		try:
 			return self.proxy.sendrawtransaction(tx)
 		except JSONRPCException as e:
@@ -87,7 +83,7 @@ ERROR: 'importaddress' not found.  Does your bitcoind support watch-only addrs?
 
 	def stop(self):
 		"""
-		Stop bitcoin server.
+        Stop bitcoin server.
 		"""
 		try:
 			self.proxy.stop()
@@ -96,7 +92,7 @@ ERROR: 'importaddress' not found.  Does your bitcoind support watch-only addrs?
 
 	def getblock(self, hash):
 		"""
-		Returns information about the given block hash.
+        Returns information about the given block hash.
 		"""
 		try:
 			return self.proxy.getblock(hash)
@@ -105,7 +101,7 @@ ERROR: 'importaddress' not found.  Does your bitcoind support watch-only addrs?
 
 	def getblockcount(self):
 		"""
-		Returns the number of blocks in the longest block chain.
+        Returns the number of blocks in the longest block chain.
 		"""
 		try:
 			return self.proxy.getblockcount()
@@ -114,9 +110,9 @@ ERROR: 'importaddress' not found.  Does your bitcoind support watch-only addrs?
 
 	def getblockhash(self, index):
 		"""
-		Returns hash of block in best-block-chain at index.
+        Returns hash of block in best-block-chain at index.
 
-		:param index: index ob the block
+        :param index: index ob the block
 
 		"""
 		try:
@@ -126,14 +122,14 @@ ERROR: 'importaddress' not found.  Does your bitcoind support watch-only addrs?
 
 	def getblocknumber(self):
 		"""
-		Returns the block number of the latest block in the longest block chain.
-		Deprecated. Use getblockcount instead.
+        Returns the block number of the latest block in the longest block chain.
+        Deprecated. Use getblockcount instead.
 		"""
 		return self.getblockcount()
 
 	def getconnectioncount(self):
 		"""
-		Returns the number of connections to other nodes.
+        Returns the number of connections to other nodes.
 		"""
 		try:
 			return self.proxy.getconnectioncount()
@@ -142,7 +138,7 @@ ERROR: 'importaddress' not found.  Does your bitcoind support watch-only addrs?
 
 	def getdifficulty(self):
 		"""
-		Returns the proof-of-work difficulty as a multiple of the minimum difficulty.
+        Returns the proof-of-work difficulty as a multiple of the minimum difficulty.
 		"""
 		try:
 			return self.proxy.getdifficulty()
@@ -151,8 +147,8 @@ ERROR: 'importaddress' not found.  Does your bitcoind support watch-only addrs?
 
 	def getgenerate(self):
 		"""
-		Returns :const:`True` or :const:`False`, depending on whether
-		generation is enabled.
+        Returns :const:`True` or :const:`False`, depending on whether
+        generation is enabled.
 		"""
 		try:
 			return self.proxy.getgenerate()
@@ -161,14 +157,14 @@ ERROR: 'importaddress' not found.  Does your bitcoind support watch-only addrs?
 
 	def setgenerate(self, generate, genproclimit=None):
 		"""
-		Enable or disable generation (mining) of coins.
+        Enable or disable generation (mining) of coins.
 
-		Arguments:
+        Arguments:
 
-		- *generate* -- is :const:`True` or :const:`False` to turn generation
-		on or off.
-		- *genproclimit* -- Number of processors that are used for generation,
-		-1 is unlimited.
+        - *generate* -- is :const:`True` or :const:`False` to turn generation
+        on or off.
+        - *genproclimit* -- Number of processors that are used for generation,
+        -1 is unlimited.
 
 		"""
 		try:
@@ -181,7 +177,7 @@ ERROR: 'importaddress' not found.  Does your bitcoind support watch-only addrs?
 
 	def gethashespersec(self):
 		"""
-		Returns a recent hashes per second performance measurement while generating.
+        Returns a recent hashes per second performance measurement while generating.
 		"""
 		try:
 			return self.proxy.gethashespersec()
@@ -190,8 +186,8 @@ ERROR: 'importaddress' not found.  Does your bitcoind support watch-only addrs?
 
 	def getinfo(self):
 		"""
-		Returns an :class:`~mmgen.rpc.data.ServerInfo` object containing
-		various state info.
+        Returns an :class:`~mmgen.rpc.data.ServerInfo` object containing
+        various state info.
 		"""
 		try:
 			return ServerInfo(**self.proxy.getinfo())
@@ -200,8 +196,8 @@ ERROR: 'importaddress' not found.  Does your bitcoind support watch-only addrs?
 
 	def getmininginfo(self):
 		"""
-		Returns an :class:`~mmgen.rpc.data.MiningInfo` object containing various
-		mining state info.
+        Returns an :class:`~mmgen.rpc.data.MiningInfo` object containing various
+        mining state info.
 		"""
 		try:
 			return MiningInfo(**self.proxy.getmininginfo())
@@ -210,13 +206,13 @@ ERROR: 'importaddress' not found.  Does your bitcoind support watch-only addrs?
 
 	def getnewaddress(self, account=None):
 		"""
-		Returns a new bitcoin address for receiving payments.
+        Returns a new bitcoin address for receiving payments.
 
-		Arguments:
+        Arguments:
 
-		- *account* -- If account is specified (recommended), it is added to the
-		address book so that payments received with the address will be
-		credited to it.
+        - *account* -- If account is specified (recommended), it is added to the
+        address book so that payments received with the address will be
+        credited to it.
 
 		"""
 		try:
@@ -229,11 +225,11 @@ ERROR: 'importaddress' not found.  Does your bitcoind support watch-only addrs?
 
 	def getaccountaddress(self, account):
 		"""
-		Returns the current bitcoin address for receiving payments to an account.
+        Returns the current bitcoin address for receiving payments to an account.
 
-		Arguments:
+        Arguments:
 
-		- *account* -- Account for which the address should be returned.
+        - *account* -- Account for which the address should be returned.
 
 		"""
 		try:
@@ -243,12 +239,12 @@ ERROR: 'importaddress' not found.  Does your bitcoind support watch-only addrs?
 
 	def setaccount(self, bitcoinaddress, account):
 		"""
-		Sets the account associated with the given address.
+        Sets the account associated with the given address.
 
-		Arguments:
+        Arguments:
 
-		- *bitcoinaddress* -- Bitcoin address to associate.
-		- *account* -- Account to associate the address to.
+        - *bitcoinaddress* -- Bitcoin address to associate.
+        - *account* -- Account to associate the address to.
 
 		"""
 		try:
@@ -258,11 +254,11 @@ ERROR: 'importaddress' not found.  Does your bitcoind support watch-only addrs?
 
 	def getaccount(self, bitcoinaddress):
 		"""
-		Returns the account associated with the given address.
+        Returns the account associated with the given address.
 
-		Arguments:
+        Arguments:
 
-		- *bitcoinaddress* -- Bitcoin address to get account for.
+        - *bitcoinaddress* -- Bitcoin address to get account for.
 		"""
 		try:
 			return self.proxy.getaccount(bitcoinaddress)
@@ -271,11 +267,11 @@ ERROR: 'importaddress' not found.  Does your bitcoind support watch-only addrs?
 
 	def getaddressesbyaccount(self, account):
 		"""
-		Returns the list of addresses for the given account.
+        Returns the list of addresses for the given account.
 
-		Arguments:
+        Arguments:
 
-		- *account* -- Account to get list of addresses for.
+        - *account* -- Account to get list of addresses for.
 		"""
 		try:
 			return self.proxy.getaddressesbyaccount(account)
@@ -284,16 +280,16 @@ ERROR: 'importaddress' not found.  Does your bitcoind support watch-only addrs?
 
 	def sendtoaddress(self, bitcoinaddress, amount, comment=None, comment_to=None):
 		"""
-		Sends *amount* from the server's available balance to *bitcoinaddress*.
+        Sends *amount* from the server's available balance to *bitcoinaddress*.
 
-		Arguments:
+        Arguments:
 
-		- *bitcoinaddress* -- Bitcoin address to send to.
-		- *amount* -- Amount to send (float, rounded to the nearest 0.01).
-		- *minconf* -- Minimum number of confirmations required for transferred
-		balance.
-		- *comment* -- Comment for transaction.
-		- *comment_to* -- Comment for to-address.
+        - *bitcoinaddress* -- Bitcoin address to send to.
+        - *amount* -- Amount to send (float, rounded to the nearest 0.01).
+        - *minconf* -- Minimum number of confirmations required for transferred
+        balance.
+        - *comment* -- Comment for transaction.
+        - *comment_to* -- Comment for to-address.
 
 		"""
 		try:
@@ -308,14 +304,14 @@ ERROR: 'importaddress' not found.  Does your bitcoind support watch-only addrs?
 
 	def getreceivedbyaddress(self, bitcoinaddress, minconf=1):
 		"""
-		Returns the total amount received by a bitcoin address in transactions
-		with at least a certain number of confirmations.
+        Returns the total amount received by a bitcoin address in transactions
+        with at least a certain number of confirmations.
 
-		Arguments:
+        Arguments:
 
-		- *bitcoinaddress* -- Address to query for total amount.
+        - *bitcoinaddress* -- Address to query for total amount.
 
-		- *minconf* -- Number of confirmations to require, defaults to 1.
+        - *minconf* -- Number of confirmations to require, defaults to 1.
 		"""
 		try:
 			return self.proxy.getreceivedbyaddress(bitcoinaddress, minconf)
@@ -324,13 +320,13 @@ ERROR: 'importaddress' not found.  Does your bitcoind support watch-only addrs?
 
 	def getreceivedbyaccount(self, account, minconf=1):
 		"""
-		Returns the total amount received by addresses with an account in
-		transactions with at least a certain number of confirmations.
+        Returns the total amount received by addresses with an account in
+        transactions with at least a certain number of confirmations.
 
-		Arguments:
+        Arguments:
 
-		- *account* -- Account to query for total amount.
-		- *minconf* -- Number of confirmations to require, defaults to 1.
+        - *account* -- Account to query for total amount.
+        - *minconf* -- Number of confirmations to require, defaults to 1.
 
 		"""
 		try:
@@ -340,11 +336,11 @@ ERROR: 'importaddress' not found.  Does your bitcoind support watch-only addrs?
 
 	def gettransaction(self, txid):
 		"""
-		Get detailed information about transaction
+        Get detailed information about transaction
 
-		Arguments:
+        Arguments:
 
-		- *txid* -- Transactiond id for which the info should be returned
+        - *txid* -- Transactiond id for which the info should be returned
 
 		"""
 		try:
@@ -354,12 +350,12 @@ ERROR: 'importaddress' not found.  Does your bitcoind support watch-only addrs?
 
 	def getrawtransaction(self, txid, verbose=True):
 		"""
-		Get transaction raw info
+        Get transaction raw info
 
-		Arguments:
+        Arguments:
 
-		- *txid* -- Transactiond id for which the info should be returned.
-		- *verbose* -- If False, return only the "hex" of the transaction.
+        - *txid* -- Transactiond id for which the info should be returned.
+        - *verbose* -- If False, return only the "hex" of the transaction.
 
 		"""
 		try:
@@ -371,24 +367,24 @@ ERROR: 'importaddress' not found.  Does your bitcoind support watch-only addrs?
 
 	def createrawtransaction(self, inputs, outputs):
 		"""
-		Creates a raw transaction spending given inputs
-		(a list of dictionaries, each containing a transaction id and an output
-		number), sending to given address(es).
+        Creates a raw transaction spending given inputs
+        (a list of dictionaries, each containing a transaction id and an output
+        number), sending to given address(es).
 
-		Returns hex-encoded raw transaction.
+        Returns hex-encoded raw transaction.
 
-		Example usage:
-		>>> conn.createrawtransaction(
-				[{"txid": "a9d4599e15b53f3eb531608ddb31f48c695c3d0b3538a6bda871e8b34f2f430c",
-				"vout": 0}],
-				{"mkZBYBiq6DNoQEKakpMJegyDbw2YiNQnHT":50})
+        Example usage:
+        >>> conn.createrawtransaction(
+               [{"txid": "a9d4599e15b53f3eb531608ddb31f48c695c3d0b3538a6bda871e8b34f2f430c",
+               "vout": 0}],
+               {"mkZBYBiq6DNoQEKakpMJegyDbw2YiNQnHT":50})
 
 
-		Arguments:
+        Arguments:
 
-		- *inputs* -- A list of {"txid": txid, "vout": n} dictionaries.
-		- *outputs* -- A dictionary mapping (public) addresses to the amount
-					they are to be paid.
+        - *inputs* -- A list of {"txid": txid, "vout": n} dictionaries.
+        - *outputs* -- A dictionary mapping (public) addresses to the amount
+                    they are to be paid.
 		"""
 		try:
 			return self.proxy.createrawtransaction(inputs, outputs)
@@ -397,22 +393,22 @@ ERROR: 'importaddress' not found.  Does your bitcoind support watch-only addrs?
 
 	def signrawtransaction(self, hexstring, previous_transactions=None, private_keys=None):
 		"""
-		Sign inputs for raw transaction (serialized, hex-encoded).
+        Sign inputs for raw transaction (serialized, hex-encoded).
 
-		Returns a dictionary with the keys:
-			"hex": raw transaction with signature(s) (hex-encoded string)
-			"complete": 1 if transaction has a complete set of signature(s), 0 if not
+        Returns a dictionary with the keys:
+            "hex": raw transaction with signature(s) (hex-encoded string)
+            "complete": 1 if transaction has a complete set of signature(s), 0 if not
 
-		Arguments:
+        Arguments:
 
-		- *hexstring* -- A hex string of the transaction to sign.
-		- *previous_transactions* -- A (possibly empty) list of dictionaries of
-		the form:
-			{"txid": txid, "vout": n, "scriptPubKey": hex, "redeemScript": hex},
-			representing previous transaction outputs that this transaction depends
-			on but may not yet be in the block chain.
-		- *private_keys* -- A (possibly empty) list of base58-encoded private
-		keys that, if given, will be the only keys used to sign the transaction.
+        - *hexstring* -- A hex string of the transaction to sign.
+        - *previous_transactions* -- A (possibly empty) list of dictionaries of
+        the form:
+            {"txid": txid, "vout": n, "scriptPubKey": hex, "redeemScript": hex},
+            representing previous transaction outputs that this transaction depends
+            on but may not yet be in the block chain.
+        - *private_keys* -- A (possibly empty) list of base58-encoded private
+        keys that, if given, will be the only keys used to sign the transaction.
 		"""
 		try:
 			return dict(self.proxy.signrawtransaction(hexstring,
@@ -422,11 +418,11 @@ ERROR: 'importaddress' not found.  Does your bitcoind support watch-only addrs?
 
 	def decoderawtransaction(self, hexstring):
 		"""
-		Produces a human-readable JSON object for a raw transaction.
+        Produces a human-readable JSON object for a raw transaction.
 
-		Arguments:
+        Arguments:
 
-		- *hexstring* -- A hex string of the transaction to be decoded.
+        - *hexstring* -- A hex string of the transaction to be decoded.
 		"""
 		try:
 			return dict(self.proxy.decoderawtransaction(hexstring))
@@ -443,16 +439,16 @@ ERROR: 'importaddress' not found.  Does your bitcoind support watch-only addrs?
 
 	def listreceivedbyaddress(self, minconf=1, includeempty=False):
 		"""
-		Returns a list of addresses.
+        Returns a list of addresses.
 
-		Each address is represented with a
-		:class:`~mmgen.rpc.data.AddressInfo` object.
+        Each address is represented with a
+        :class:`~mmgen.rpc.data.AddressInfo` object.
 
-		Arguments:
+        Arguments:
 
-		- *minconf* -- Minimum number of confirmations before payments are included.
-		- *includeempty* -- Whether to include addresses that haven't received
-		any payments.
+        - *minconf* -- Minimum number of confirmations before payments are included.
+        - *includeempty* -- Whether to include addresses that haven't received
+        any payments.
 
 		"""
 		try:
@@ -463,12 +459,12 @@ ERROR: 'importaddress' not found.  Does your bitcoind support watch-only addrs?
 
 	def listaccounts(self, minconf=1, includeWatchonly=False, as_dict=False):
 		"""
-		Returns a list of account names.
+        Returns a list of account names.
 
-		Arguments:
+        Arguments:
 
-		- *minconf* -- Minimum number of confirmations before payments are included.
-		- *as_dict* -- Returns a dictionary of account names, with their balance as values.
+        - *minconf* -- Minimum number of confirmations before payments are included.
+        - *as_dict* -- Returns a dictionary of account names, with their balance as values.
 		"""
 		try:
 			if as_dict:
@@ -488,15 +484,15 @@ ERROR: 'listaccounts' failed.  Does your bitcoind support watch-only addresses?
 
 	def listreceivedbyaccount(self, minconf=1, includeempty=False):
 		"""
-		Returns a list of accounts.
+        Returns a list of accounts.
 
-		Each account is represented with a :class:`~mmgen.rpc.data.AccountInfo` object.
+        Each account is represented with a :class:`~mmgen.rpc.data.AccountInfo` object.
 
-		Arguments:
+        Arguments:
 
-		- *minconf* -- Minimum number of confirmations before payments are included.
+        - *minconf* -- Minimum number of confirmations before payments are included.
 
-		- *includeempty* -- Whether to include addresses that haven't received any payments.
+        - *includeempty* -- Whether to include addresses that haven't received any payments.
 		"""
 		try:
 			return [AccountInfo(**x) for x in
@@ -506,17 +502,17 @@ ERROR: 'listaccounts' failed.  Does your bitcoind support watch-only addresses?
 
 	def listtransactions(self, account=None, count=10, from_=0, address=None):
 		"""
-		Returns a list of the last transactions for an account.
+        Returns a list of the last transactions for an account.
 
-		Each transaction is represented with a :class:`~mmgen.rpc.data.TransactionInfo` object.
+        Each transaction is represented with a :class:`~mmgen.rpc.data.TransactionInfo` object.
 
-		Arguments:
+        Arguments:
 
-		- *account* -- Account to list transactions from. Return transactions from
-					all accounts if None.
-		- *count* -- Number of transactions to return.
-		- *from_* -- Skip the first <from_> transactions.
-		- *address* -- Receive address to consider
+        - *account* -- Account to list transactions from. Return transactions from
+                    all accounts if None.
+        - *count* -- Number of transactions to return.
+        - *from_* -- Skip the first <from_> transactions.
+        - *address* -- Receive address to consider
 		"""
 		accounts = [account] if account is not None else self.listaccounts(as_dict=True).iterkeys()
 		try:
@@ -528,11 +524,11 @@ ERROR: 'listaccounts' failed.  Does your bitcoind support watch-only addresses?
 
 	def backupwallet(self, destination):
 		"""
-		Safely copies ``wallet.dat`` to *destination*, which can be a directory or a path
-		with filename.
+        Safely copies ``wallet.dat`` to *destination*, which can be a directory or a path
+        with filename.
 
-		Arguments:
-		- *destination* -- directory or path with filename to backup wallet to.
+        Arguments:
+        - *destination* -- directory or path with filename to backup wallet to.
 
 		"""
 		try:
@@ -542,14 +538,14 @@ ERROR: 'listaccounts' failed.  Does your bitcoind support watch-only addresses?
 
 	def validateaddress(self, validateaddress):
 		"""
-		Validate a bitcoin address and return information for it.
+        Validate a bitcoin address and return information for it.
 
-		The information is represented by a :class:`~mmgen.rpc.data.AddressValidation` object.
+        The information is represented by a :class:`~mmgen.rpc.data.AddressValidation` object.
 
-		Arguments: -- Address to validate.
+        Arguments: -- Address to validate.
 
 
-		- *validateaddress*
+        - *validateaddress*
 		"""
 		try:
 			return AddressValidation(**self.proxy.validateaddress(validateaddress))
@@ -558,11 +554,11 @@ ERROR: 'listaccounts' failed.  Does your bitcoind support watch-only addresses?
 
 	def getbalance(self, account=None, minconf=None):
 		"""
-		Get the current balance, either for an account or the total server balance.
+        Get the current balance, either for an account or the total server balance.
 
-		Arguments:
-		- *account* -- If this parameter is specified, returns the balance in the account.
-		- *minconf* -- Minimum number of confirmations required for transferred balance.
+        Arguments:
+        - *account* -- If this parameter is specified, returns the balance in the account.
+        - *minconf* -- Minimum number of confirmations required for transferred balance.
 
 		"""
 		args = []
@@ -577,15 +573,15 @@ ERROR: 'listaccounts' failed.  Does your bitcoind support watch-only addresses?
 
 	def move(self, fromaccount, toaccount, amount, minconf=1, comment=None):
 		"""
-		Move from one account in your wallet to another.
+        Move from one account in your wallet to another.
 
-		Arguments:
+        Arguments:
 
-		- *fromaccount* -- Source account name.
-		- *toaccount* -- Destination account name.
-		- *amount* -- Amount to transfer.
-		- *minconf* -- Minimum number of confirmations required for transferred balance.
-		- *comment* -- Comment to add to transaction log.
+        - *fromaccount* -- Source account name.
+        - *toaccount* -- Destination account name.
+        - *amount* -- Amount to transfer.
+        - *minconf* -- Minimum number of confirmations required for transferred balance.
+        - *comment* -- Comment to add to transaction log.
 
 		"""
 		try:
@@ -599,19 +595,19 @@ ERROR: 'listaccounts' failed.  Does your bitcoind support watch-only addresses?
 	def sendfrom(self, fromaccount, tobitcoinaddress, amount, minconf=1, comment=None,
 				comment_to=None):
 		"""
-		Sends amount from account's balance to bitcoinaddress. This method will fail
-		if there is less than amount bitcoins with minconf confirmations in the account's
-		balance (unless account is the empty-string-named default account; it
-		behaves like the sendtoaddress method). Returns transaction ID on success.
+        Sends amount from account's balance to bitcoinaddress. This method will fail
+        if there is less than amount bitcoins with minconf confirmations in the account's
+        balance (unless account is the empty-string-named default account; it
+        behaves like the sendtoaddress method). Returns transaction ID on success.
 
-		Arguments:
+        Arguments:
 
-		- *fromaccount* -- Account to send from.
-		- *tobitcoinaddress* -- Bitcoin address to send to.
-		- *amount* -- Amount to send (float, rounded to the nearest 0.01).
-		- *minconf* -- Minimum number of confirmations required for transferred balance.
-		- *comment* -- Comment for transaction.
-		- *comment_to* -- Comment for to-address.
+        - *fromaccount* -- Account to send from.
+        - *tobitcoinaddress* -- Bitcoin address to send to.
+        - *amount* -- Amount to send (float, rounded to the nearest 0.01).
+        - *minconf* -- Minimum number of confirmations required for transferred balance.
+        - *comment* -- Comment for transaction.
+        - *comment_to* -- Comment for to-address.
 
 		"""
 		try:
@@ -626,20 +622,20 @@ ERROR: 'listaccounts' failed.  Does your bitcoind support watch-only addresses?
 
 	def sendmany(self, fromaccount, todict, minconf=1, comment=None):
 		"""
-		Sends specified amounts from account's balance to bitcoinaddresses.
-		This method will fail if there is less than total amount bitcoins with
-		minconf confirmations in the account's balance (unless account is the
-		empty-string-named default account; Returns transaction ID on
-		success.
+        Sends specified amounts from account's balance to bitcoinaddresses.
+        This method will fail if there is less than total amount bitcoins with
+        minconf confirmations in the account's balance (unless account is the
+        empty-string-named default account; Returns transaction ID on
+        success.
 
-		Arguments:
+        Arguments:
 
-		- *fromaccount* -- Account to send from.
-		- *todict* -- Dictionary with Bitcoin addresses as keys and amounts as
-		values.
-		- *minconf* -- Minimum number of confirmations required for transferred
-		balance.
-		- *comment* -- Comment for transaction.
+        - *fromaccount* -- Account to send from.
+        - *todict* -- Dictionary with Bitcoin addresses as keys and amounts as
+        values.
+        - *minconf* -- Minimum number of confirmations required for transferred
+        balance.
+        - *comment* -- Comment for transaction.
 
 		"""
 		try:
@@ -652,15 +648,15 @@ ERROR: 'listaccounts' failed.  Does your bitcoind support watch-only addresses?
 
 	def verifymessage(self, bitcoinaddress, signature, message):
 		"""
-		Verifies a signature given the bitcoinaddress used to sign,
-		the signature itself, and the message that was signed.
-		Returns :const:`True` if the signature is valid, and :const:`False` if it is invalid.
+        Verifies a signature given the bitcoinaddress used to sign,
+        the signature itself, and the message that was signed.
+        Returns :const:`True` if the signature is valid, and :const:`False` if it is invalid.
 
-		Arguments:
+        Arguments:
 
-		- *bitcoinaddress* -- the bitcoinaddress used to sign the message
-		- *signature* -- the signature to be verified
-		- *message* -- the message that was originally signed
+        - *bitcoinaddress* -- the bitcoinaddress used to sign the message
+        - *signature* -- the signature to be verified
+        - *message* -- the message that was originally signed
 
 		"""
 		try:
@@ -670,15 +666,15 @@ ERROR: 'listaccounts' failed.  Does your bitcoind support watch-only addresses?
 
 	def getwork(self, data=None):
 		"""
-		Get work for remote mining, or submit result.
-		If data is specified, the server tries to solve the block
-		using the provided data and returns :const:`True` if it was successful.
-		If not, the function returns formatted hash data (:class:`~mmgen.rpc.data.WorkItem`)
-		to work on.
+        Get work for remote mining, or submit result.
+        If data is specified, the server tries to solve the block
+        using the provided data and returns :const:`True` if it was successful.
+        If not, the function returns formatted hash data (:class:`~mmgen.rpc.data.WorkItem`)
+        to work on.
 
-		Arguments:
+        Arguments:
 
-		- *data* -- Result from remote mining.
+        - *data* -- Result from remote mining.
 
 		"""
 		try:
@@ -692,13 +688,13 @@ ERROR: 'listaccounts' failed.  Does your bitcoind support watch-only addresses?
 
 	def listunspent(self, minconf=1, maxconf=999999):
 		"""
-		Returns a list of unspent transaction inputs in the wallet.
+        Returns a list of unspent transaction inputs in the wallet.
 
-		Arguments:
+        Arguments:
 
-		- *minconf* -- Minimum number of confirmations required to be listed.
+        - *minconf* -- Minimum number of confirmations required to be listed.
 
-		- *maxconf* -- Maximal number of confirmations allowed to be listed.
+        - *maxconf* -- Maximal number of confirmations allowed to be listed.
 
 
 		"""
@@ -717,15 +713,15 @@ ERROR: 'listaccounts' failed.  Does your bitcoind support watch-only addresses?
 
 	def walletpassphrase(self, passphrase, timeout, dont_raise=False):
 		"""
-		Stores the wallet decryption key in memory for <timeout> seconds.
+        Stores the wallet decryption key in memory for <timeout> seconds.
 
-		- *passphrase* -- The wallet passphrase.
+        - *passphrase* -- The wallet passphrase.
 
-		- *timeout* -- Time in seconds to keep the wallet unlocked
-					(by keeping the passphrase in memory).
+        - *timeout* -- Time in seconds to keep the wallet unlocked
+                    (by keeping the passphrase in memory).
 
-		- *dont_raise* -- instead of raising `~mmgen.rpc.exceptions.WalletPassphraseIncorrect`
-						return False.
+        - *dont_raise* -- instead of raising `~mmgen.rpc.exceptions.WalletPassphraseIncorrect`
+                       return False.
 		"""
 		try:
 			self.proxy.walletpassphrase(passphrase, timeout)
@@ -741,10 +737,10 @@ ERROR: 'listaccounts' failed.  Does your bitcoind support watch-only addresses?
 
 	def walletlock(self):
 		"""
-		Removes the wallet encryption key from memory, locking the wallet.
-		After calling this method, you will need to call walletpassphrase
-		again before being able to call any methods which require the wallet
-		to be unlocked.
+        Removes the wallet encryption key from memory, locking the wallet.
+        After calling this method, you will need to call walletpassphrase
+        again before being able to call any methods which require the wallet
+        to be unlocked.
 		"""
 		try:
 			return self.proxy.walletlock()
@@ -753,12 +749,12 @@ ERROR: 'listaccounts' failed.  Does your bitcoind support watch-only addresses?
 
 	def walletpassphrasechange(self, oldpassphrase, newpassphrase, dont_raise=False):
 		"""
-		Changes the wallet passphrase from <oldpassphrase> to <newpassphrase>.
+        Changes the wallet passphrase from <oldpassphrase> to <newpassphrase>.
 
-		Arguments:
+        Arguments:
 
-		- *dont_raise* -- instead of raising
-			`~mmgen.rpc.exceptions.WalletPassphraseIncorrect` return False.
+        - *dont_raise* -- instead of raising
+               `~mmgen.rpc.exceptions.WalletPassphraseIncorrect` return False.
 		"""
 		try:
 			self.proxy.walletpassphrasechange(oldpassphrase, newpassphrase)

+ 91 - 91
mmgen/rpc/exceptions.py

@@ -23,181 +23,181 @@ Exception definitions.
 
 
 class BitcoinException(Exception):
-    """
+	"""
     Base class for exceptions received from Bitcoin server.
 
     - *code* -- Error code from ``bitcoind``.
-    """
-    # Standard JSON-RPC 2.0 errors
-    INVALID_REQUEST  = -32600,
-    METHOD_NOT_FOUND = -32601,
-    INVALID_PARAMS   = -32602,
-    INTERNAL_ERROR   = -32603,
-    PARSE_ERROR      = -32700,
-
-    # General application defined errors
-    MISC_ERROR                  = -1  # std::exception thrown in command handling
-    FORBIDDEN_BY_SAFE_MODE      = -2  # Server is in safe mode, and command is not allowed in safe mode
-    TYPE_ERROR                  = -3  # Unexpected type was passed as parameter
-    INVALID_ADDRESS_OR_KEY      = -5  # Invalid address or key
-    OUT_OF_MEMORY               = -7  # Ran out of memory during operation
-    INVALID_PARAMETER           = -8  # Invalid, missing or duplicate parameter
-    DATABASE_ERROR              = -20 # Database error
-    DESERIALIZATION_ERROR       = -22 # Error parsing or validating structure in raw format
-
-    # P2P client errors
-    CLIENT_NOT_CONNECTED        = -9  # Bitcoin is not connected
-    CLIENT_IN_INITIAL_DOWNLOAD  = -10 # Still downloading initial blocks
-
-    # Wallet errors
-    WALLET_ERROR                = -4  # Unspecified problem with wallet (key not found etc.)
-    WALLET_INSUFFICIENT_FUNDS   = -6  # Not enough funds in wallet or account
-    WALLET_INVALID_ACCOUNT_NAME = -11 # Invalid account name
-    WALLET_KEYPOOL_RAN_OUT      = -12 # Keypool ran out, call keypoolrefill first
-    WALLET_UNLOCK_NEEDED        = -13 # Enter the wallet passphrase with walletpassphrase first
-    WALLET_PASSPHRASE_INCORRECT = -14 # The wallet passphrase entered was incorrect
-    WALLET_WRONG_ENC_STATE      = -15 # Command given in wrong wallet encryption state (encrypting an encrypted wallet etc.)
-    WALLET_ENCRYPTION_FAILED    = -16 # Failed to encrypt the wallet
-    WALLET_ALREADY_UNLOCKED     = -17 # Wallet is already unlocked
-
-    def __init__(self, error):
-        Exception.__init__(self, error['message'])
-        self.code = error['code']
+	"""
+	# Standard JSON-RPC 2.0 errors
+	INVALID_REQUEST  = -32600,
+	METHOD_NOT_FOUND = -32601,
+	INVALID_PARAMS   = -32602,
+	INTERNAL_ERROR   = -32603,
+	PARSE_ERROR      = -32700,
+
+	# General application defined errors
+	MISC_ERROR                  = -1  # std::exception thrown in command handling
+	FORBIDDEN_BY_SAFE_MODE      = -2  # Server is in safe mode, and command is not allowed in safe mode
+	TYPE_ERROR                  = -3  # Unexpected type was passed as parameter
+	INVALID_ADDRESS_OR_KEY      = -5  # Invalid address or key
+	OUT_OF_MEMORY               = -7  # Ran out of memory during operation
+	INVALID_PARAMETER           = -8  # Invalid, missing or duplicate parameter
+	DATABASE_ERROR              = -20 # Database error
+	DESERIALIZATION_ERROR       = -22 # Error parsing or validating structure in raw format
+
+	# P2P client errors
+	CLIENT_NOT_CONNECTED        = -9  # Bitcoin is not connected
+	CLIENT_IN_INITIAL_DOWNLOAD  = -10 # Still downloading initial blocks
+
+	# Wallet errors
+	WALLET_ERROR                = -4  # Unspecified problem with wallet (key not found etc.)
+	WALLET_INSUFFICIENT_FUNDS   = -6  # Not enough funds in wallet or account
+	WALLET_INVALID_ACCOUNT_NAME = -11 # Invalid account name
+	WALLET_KEYPOOL_RAN_OUT      = -12 # Keypool ran out, call keypoolrefill first
+	WALLET_UNLOCK_NEEDED        = -13 # Enter the wallet passphrase with walletpassphrase first
+	WALLET_PASSPHRASE_INCORRECT = -14 # The wallet passphrase entered was incorrect
+	WALLET_WRONG_ENC_STATE      = -15 # Command given in wrong wallet encryption state (encrypting an encrypted wallet etc.)
+	WALLET_ENCRYPTION_FAILED    = -16 # Failed to encrypt the wallet
+	WALLET_ALREADY_UNLOCKED     = -17 # Wallet is already unlocked
+
+	def __init__(self, error):
+		Exception.__init__(self, error['message'])
+		self.code = error['code']
 
 
 ##### General application defined errors
 class SafeMode(BitcoinException):
-    """
+	"""
     Operation denied in safe mode (run ``bitcoind`` with ``-disablesafemode``).
-    """
+	"""
 
 
 class JSONTypeError(BitcoinException):
-    """
+	"""
     Unexpected type was passed as parameter
-    """
+	"""
 InvalidAmount = JSONTypeError  # Backwards compatibility
 
 
 class InvalidAddressOrKey(BitcoinException):
-    """
+	"""
     Invalid address or key.
-    """
+	"""
 InvalidTransactionID = InvalidAddressOrKey  # Backwards compatibility
 
 
 class OutOfMemory(BitcoinException):
-    """
+	"""
     Out of memory during operation.
-    """
+	"""
 
 
 class InvalidParameter(BitcoinException):
-    """
+	"""
     Invalid parameter provided to RPC call.
-    """
+	"""
 
 
 ##### Client errors
 class ClientException(BitcoinException):
-    """
+	"""
     P2P network error.
     This exception is never raised but functions as a superclass
     for other P2P client exceptions.
-    """
+	"""
 
 
 class NotConnected(ClientException):
-    """
+	"""
     Not connected to any peers.
-    """
+	"""
 
 
 class DownloadingBlocks(ClientException):
-    """
+	"""
     Client is still downloading blocks.
-    """
+	"""
 
 
 ##### Wallet errors
 class WalletError(BitcoinException):
-    """
+	"""
     Unspecified problem with wallet (key not found etc.)
-    """
+	"""
 SendError = WalletError  # Backwards compatibility
 
 class InsufficientFunds(WalletError):
-    """
+	"""
     Insufficient funds to complete transaction in wallet or account
-    """
+	"""
 
 class InvalidAccountName(WalletError):
-    """
+	"""
     Invalid account name
-    """
+	"""
 
 
 class KeypoolRanOut(WalletError):
-    """
+	"""
     Keypool ran out, call keypoolrefill first
-    """
+	"""
 
 
 class WalletUnlockNeeded(WalletError):
-    """
+	"""
     Enter the wallet passphrase with walletpassphrase first
-    """
+	"""
 
 
 class WalletPassphraseIncorrect(WalletError):
-    """
+	"""
     The wallet passphrase entered was incorrect
-    """
+	"""
 
 
 class WalletWrongEncState(WalletError):
-    """
+	"""
     Command given in wrong wallet encryption state (encrypting an encrypted wallet etc.)
-    """
+	"""
 
 
 class WalletEncryptionFailed(WalletError):
-    """
+	"""
     Failed to encrypt the wallet
-    """
+	"""
 
 
 class WalletAlreadyUnlocked(WalletError):
-    """
+	"""
     Wallet is already unlocked
-    """
+	"""
 
 
 # For convenience, we define more specific exception classes
 # for the more common errors.
 _exception_map = {
-    BitcoinException.FORBIDDEN_BY_SAFE_MODE: SafeMode,
-    BitcoinException.TYPE_ERROR: JSONTypeError,
-    BitcoinException.WALLET_ERROR: WalletError,
-    BitcoinException.INVALID_ADDRESS_OR_KEY: InvalidAddressOrKey,
-    BitcoinException.WALLET_INSUFFICIENT_FUNDS: InsufficientFunds,
-    BitcoinException.OUT_OF_MEMORY: OutOfMemory,
-    BitcoinException.INVALID_PARAMETER: InvalidParameter,
-    BitcoinException.CLIENT_NOT_CONNECTED: NotConnected,
-    BitcoinException.CLIENT_IN_INITIAL_DOWNLOAD: DownloadingBlocks,
-    BitcoinException.WALLET_INSUFFICIENT_FUNDS: InsufficientFunds,
-    BitcoinException.WALLET_INVALID_ACCOUNT_NAME: InvalidAccountName,
-    BitcoinException.WALLET_KEYPOOL_RAN_OUT: KeypoolRanOut,
-    BitcoinException.WALLET_UNLOCK_NEEDED: WalletUnlockNeeded,
-    BitcoinException.WALLET_PASSPHRASE_INCORRECT: WalletPassphraseIncorrect,
-    BitcoinException.WALLET_WRONG_ENC_STATE: WalletWrongEncState,
-    BitcoinException.WALLET_ENCRYPTION_FAILED: WalletEncryptionFailed,
-    BitcoinException.WALLET_ALREADY_UNLOCKED: WalletAlreadyUnlocked,
+	BitcoinException.FORBIDDEN_BY_SAFE_MODE: SafeMode,
+	BitcoinException.TYPE_ERROR: JSONTypeError,
+	BitcoinException.WALLET_ERROR: WalletError,
+	BitcoinException.INVALID_ADDRESS_OR_KEY: InvalidAddressOrKey,
+	BitcoinException.WALLET_INSUFFICIENT_FUNDS: InsufficientFunds,
+	BitcoinException.OUT_OF_MEMORY: OutOfMemory,
+	BitcoinException.INVALID_PARAMETER: InvalidParameter,
+	BitcoinException.CLIENT_NOT_CONNECTED: NotConnected,
+	BitcoinException.CLIENT_IN_INITIAL_DOWNLOAD: DownloadingBlocks,
+	BitcoinException.WALLET_INSUFFICIENT_FUNDS: InsufficientFunds,
+	BitcoinException.WALLET_INVALID_ACCOUNT_NAME: InvalidAccountName,
+	BitcoinException.WALLET_KEYPOOL_RAN_OUT: KeypoolRanOut,
+	BitcoinException.WALLET_UNLOCK_NEEDED: WalletUnlockNeeded,
+	BitcoinException.WALLET_PASSPHRASE_INCORRECT: WalletPassphraseIncorrect,
+	BitcoinException.WALLET_WRONG_ENC_STATE: WalletWrongEncState,
+	BitcoinException.WALLET_ENCRYPTION_FAILED: WalletEncryptionFailed,
+	BitcoinException.WALLET_ALREADY_UNLOCKED: WalletAlreadyUnlocked,
 }
 
 
 def _wrap_exception(error):
-    """
+	"""
     Convert a JSON error object to a more specific Bitcoin exception.
-    """
-    return _exception_map.get(error['code'], BitcoinException)(error)
+	"""
+	return _exception_map.get(error['code'], BitcoinException)(error)

+ 1 - 1
mmgen/rpc/proxy.py

@@ -10,7 +10,7 @@
   ServiceProxy class:
 
   - HTTP connections persist for the life of the AuthServiceProxy object
-	(if server supports HTTP/1.1)
+    (if server supports HTTP/1.1)
   - sends protocol 'version', per JSON-RPC 1.1
   - sends proper, incrementing 'id'
   - sends Basic HTTP authentication headers

+ 21 - 21
mmgen/rpc/util.py

@@ -22,28 +22,28 @@ from copy import copy
 
 
 class DStruct(object):
-    """
+	"""
     Simple dynamic structure, like :const:`collections.namedtuple` but more flexible
     (and less memory-efficient)
-    """
-    # Default arguments. Defaults are *shallow copied*, to allow defaults such as [].
-    _fields = []
-    _defaults = {}
+	"""
+	# Default arguments. Defaults are *shallow copied*, to allow defaults such as [].
+	_fields = []
+	_defaults = {}
 
-    def __init__(self, *args_t, **args_d):
-        # order
-        if len(args_t) > len(self._fields):
-            raise TypeError("Number of arguments is larger than of predefined fields")
-        # Copy default values
-        for (k, v) in self._defaults.iteritems():
-            self.__dict__[k] = copy(v)
-        # Set pass by value arguments
-        self.__dict__.update(zip(self._fields, args_t))
-        # dict
-        self.__dict__.update(args_d)
+	def __init__(self, *args_t, **args_d):
+		# order
+		if len(args_t) > len(self._fields):
+			raise TypeError("Number of arguments is larger than of predefined fields")
+		# Copy default values
+		for (k, v) in self._defaults.iteritems():
+			self.__dict__[k] = copy(v)
+		# Set pass by value arguments
+		self.__dict__.update(zip(self._fields, args_t))
+		# dict
+		self.__dict__.update(args_d)
 
-    def __repr__(self):
-        return '{module}.{classname}({slots})'.format(
-            module=self.__class__.__module__, classname=self.__class__.__name__,
-            slots=", ".join('{k}={v!r}'.format(k=k, v=v) for k, v in
-                            self.__dict__.iteritems()))
+	def __repr__(self):
+		return '{module}.{classname}({slots})'.format(
+			module=self.__class__.__module__, classname=self.__class__.__name__,
+			slots=", ".join('{k}={v!r}'.format(k=k, v=v) for k, v in
+							self.__dict__.iteritems()))

File diff suppressed because it is too large
+ 463 - 249
mmgen/seed.py


+ 15 - 0
mmgen/share/Opts.py

@@ -62,6 +62,21 @@ def process_opts(argv,opts_data,short_opts,long_opts):
 					opt[1:]+":")][:-1].replace("-","_")] = arg
 		else: assert False, "Invalid option"
 
+	if 'sets' in opts_data:
+		for o_in,v_in,o_out,v_out in opts_data['sets']:
+			if o_in in opts:
+				v = opts[o_in]
+				if (v and v_in == bool) or v == v_in:
+					if o_out in opts and opts[o_out] != v_out:
+						sys.stderr.write(
+				"Option conflict:\n  --%s=%s, with\n  --%s=%s\n" % (
+					o_out.replace("_","-"),opts[o_out],
+					o_in.replace("_","-"),opts[o_in]
+				))
+						sys.exit(1)
+					else:
+						opts[o_out] = v_out
+
 	return opts,args
 
 

+ 2 - 2
mmgen/test.py

@@ -77,14 +77,14 @@ def ok_or_die(val,chk_func,s,skip_ok=False):
 	try: ret = chk_func(val)
 	except: ret = False
 	if ret:
-	   if not skip_ok: ok()
+		if not skip_ok: ok()
 	else:
 		msg(red("Returned value '%s' is not a %s" % (val,s)))
 		sys.exit(3)
 
 def cmp_or_die(s,t,skip_ok=False):
 	if s == t:
-	   if not skip_ok: ok()
+		if not skip_ok: ok()
 	else:
 		sys.stderr.write(red(
 			"ERROR: recoded data:\n%s\ndiffers from original data:\n%s\n" %

+ 6 - 8
mmgen/tool.py

@@ -439,19 +439,17 @@ def add_label(mmaddr,label,remove=False):
 	check_addr_label(label)  # Exits on failure
 
 	c = connect_to_bitcoind()
+
 	from mmgen.addr import AddrInfoList
-	ail = AddrInfoList(bitcoind_connection=c)
+	btcaddr = AddrInfoList(bitcoind_connection=c).mmaddr2btcaddr(mmaddr)
 
-	btcaddr = ""
-	sid,idx = mmaddr.split(":")
-	if sid in ail.seed_ids():
-		btcaddr = ail.addrinfo(sid).btcaddr(int(idx))
 	if not btcaddr:
 		die(1,"{pnm} address {a} not found in tracking wallet".format(
 				pnm=pnm,a=mmaddr))
 
 	try:
-		c.importaddress(btcaddr," ".join((mmaddr,label)),rescan=False)
+		l = " " + label if label else ""
+		c.importaddress(btcaddr,mmaddr+l,rescan=False)
 	except:
 		die(1,"Unable to add label")
 
@@ -508,7 +506,7 @@ def encrypt(infile,outfile="",hash_preset=""):
 	data = get_data_from_file(infile,"data for encryption")
 	enc_d = mmgen_encrypt(data,"user data",hash_preset)
 	if outfile == '-':
-		write_to_stdout(enc_d,"encrypted data",confirm=True)
+		write_to_stdout(enc_d,"encrypted data")
 	else:
 		if not outfile:
 			outfile = os.path.basename(infile) + "." + g.mmenc_ext
@@ -522,7 +520,7 @@ def decrypt(infile,outfile="",hash_preset=""):
 		if dec_d: break
 		msg("Trying again...")
 	if outfile == '-':
-		write_to_stdout(dec_d,"decrypted data",confirm=not opt.quiet)
+		write_to_stdout(dec_d,"decrypted data",ask_terminal=not opt.quiet)
 	else:
 		if not outfile:
 			outfile = os.path.basename(infile)

+ 1 - 2
mmgen/tx.py

@@ -46,8 +46,7 @@ def normalize_btc_amt(amt):
 		msg("%s: Invalid amount" % amt)
 		return False
 
-	if opt.debug:
-		Msg("Decimal(amt): %s\nAs tuple: %s" % (amt,repr(ret.as_tuple())))
+	dmsg("Decimal(amt): %s\nAs tuple: %s" % (amt,repr(ret.as_tuple())))
 
 	if ret.as_tuple()[-1] < -8:
 		msg("%s: Too many decimal places in amount" % amt)

+ 173 - 100
mmgen/util.py

@@ -42,10 +42,10 @@ def msg_r(s):  sys.stderr.write(s)
 def Msg(s):    sys.stdout.write(s + "\n")
 def Msg_r(s):  sys.stdout.write(s)
 def msgred(s): sys.stderr.write(red(s+"\n"))
-def msgrepr(*args):
+def mmsg(*args):
 	for d in args:
 		sys.stdout.write(repr(d)+"\n")
-def msgrepr_exit(*args):
+def mdie(*args):
 	for d in args:
 		sys.stdout.write(repr(d)+"\n")
 	sys.exit()
@@ -55,20 +55,8 @@ def die(ev,s):
 def Die(ev,s):
 	sys.stdout.write(s+"\n"); sys.exit(ev)
 
-def fmt_type(x): return "%s" % str(type(x)).split("'")[1]
-
 import opt
 
-def fmt_code_to_sstype(fmt_code):
-	for e in g.wallet_fmt_codes:
-		if fmt_code in e: return e[0]
-	die(2,"'%s': unrecognized format code" % fmt_code)
-
-def format_fmt_codes():
-	return "".join(
-		["%-20s  " % (e[0]+":") + ",".join(e[1:]) + "\n"
-			for e in g.wallet_fmt_codes])
-
 def qmsg(s,alt=False):
 	if opt.quiet:
 		if alt != False: sys.stderr.write(alt + "\n")
@@ -87,7 +75,10 @@ def Vmsg(s):
 def Vmsg_r(s):
 	if opt.verbose: sys.stdout.write(s)
 
-def suf(arg,what):
+def dmsg(s):
+	if opt.debug: sys.stdout.write(s + "\n")
+
+def suf(arg,suf_type):
 	t = type(arg)
 	if t == int:
 		n = arg
@@ -97,9 +88,9 @@ def suf(arg,what):
 		msg("%s: invalid parameter" % arg)
 		return ""
 
-	if what in "a":
+	if suf_type in ("a","es"):
 		return "" if n == 1 else "es"
-	if what in "k":
+	if suf_type in ("k","s"):
 		return "" if n == 1 else "s"
 
 def get_extension(f):
@@ -125,10 +116,14 @@ def splitN(s,n,sep=None):                      # always return an n-element list
 def split2(s,sep=None): return splitN(s,2,sep) # always return a 2-element list
 def split3(s,sep=None): return splitN(s,3,sep) # always return a 3-element list
 
-def split_into_columns(col_wid,s):
+def split_into_cols(col_wid,s):
 	return " ".join([s[col_wid*i:col_wid*(i+1)]
 					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 "")
+
 def make_timestamp():
 	tv = time.gmtime(time.time())[:6]
 	return "{:04d}{:02d}{:02d}_{:02d}{:02d}{:02d}".format(*tv)
@@ -166,6 +161,13 @@ def is_utf8(s):
 def match_ext(addr,ext):
 	return addr.split(".")[-1] == ext
 
+def file_exists(f):
+	try:
+		os.stat(f)
+		return True
+	except:
+		return False
+
 import opt as opt
 
 def get_from_brain_opt_params():
@@ -186,7 +188,11 @@ def pretty_hexdump(data,gw=2,cols=8,line_nums=False):
 def decode_pretty_hexdump(data):
 	from string import hexdigits
 	lines = [re.sub('^['+hexdigits+']+:\s+','',l) for l in data.split("\n")]
-	return unhexlify("".join(("".join(lines).split())))
+	try:
+		return unhexlify("".join(("".join(lines).split())))
+	except:
+		msg("Data not in hexdump format")
+		return False
 
 def get_hash_params(hash_preset):
 	if hash_preset in g.hash_presets:
@@ -195,22 +201,25 @@ def get_hash_params(hash_preset):
 		msg("%s: invalid 'hash_preset' value" % hash_preset)
 		sys.exit(3)
 
-def compare_chksums(chk1, desc1, chk2, desc2, die=True):
+def compare_chksums(chk1, desc1, chk2, desc2, hdr="", die_on_fail=False):
 
 	if not chk1 == chk2:
-		if die:
-			die(3,"Checksum error: %s checksum (%s) doesn't match %s checksum (%s)"
-				% (desc2,chk2,desc1,chk1))
-		else: return False
+		m = "%s ERROR: %s checksum (%s) doesn't match %s checksum (%s)"\
+				% ((hdr+":\n   " if hdr else "CHECKSUM"),desc2,chk2,desc1,chk1)
+		if die_on_fail:
+			die(3,m)
+		else:
+			msg(m)
+			return False
 
-	vmsg("%s checksum OK (%s)" % (desc1.capitalize(),chk1))
+	vmsg("%s checksum OK (%s)" % (capfirst(desc1),chk1))
 	return True
 
-def compare_or_die(val1, desc1, val2, desc2):
+def compare_or_die(val1, desc1, val2, desc2, e="Error"):
 	if cmp(val1,val2):
-		die(3,"Error: %s (%s) doesn't match %s (%s)"
-				% (desc2,val2,desc1,val1))
-	vmsg("%s OK (%s)" % (desc2.capitalize(),val2))
+		die(3,"%s: %s (%s) doesn't match %s (%s)"
+				% (e,desc2,val2,desc1,val1))
+	dmsg("%s OK (%s)" % (capfirst(desc2),val2))
 	return True
 
 def get_default_wordlist():
@@ -224,8 +233,8 @@ def open_file_or_exit(filename,mode):
 	try:
 		f = open(filename, mode)
 	except:
-		what = "reading" if 'r' in mode else "writing"
-		msg("Unable to open file '%s' for %s" % (filename,what))
+		op = "reading" if 'r' in mode else "writing"
+		msg("Unable to open file '%s' for %s" % (filename,op))
 		sys.exit(2)
 	return f
 
@@ -307,9 +316,9 @@ def parse_addr_idxs(arg,sep=","):
 	return sorted(set(ret))
 
 
-def get_new_passphrase(what,passchg=False):
+def get_new_passphrase(desc,passchg=False):
 
-	w = "{}passphrase for {}".format("new " if passchg else "", what)
+	w = "{}passphrase for {}".format("new " if passchg else "", desc)
 	if opt.passwd_file:
 		pw = " ".join(_get_words_from_file(opt.passwd_file,w))
 	elif opt.echo_passphrase:
@@ -318,7 +327,7 @@ def get_new_passphrase(what,passchg=False):
 		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: "))
-			if opt.debug: Msg("Passphrases: [%s] [%s]" % (pw,pw2))
+			dmsg("Passphrases: [%s] [%s]" % (pw,pw2))
 			if pw == pw2:
 				vmsg("Passphrases match"); break
 			else: msg("Passphrases do not match.  Try again.")
@@ -332,27 +341,21 @@ def get_new_passphrase(what,passchg=False):
 
 
 def confirm_or_exit(message, question, expect="YES"):
-	if not confirm_or_false(message, question, expect):
-		msg("Exiting at user request")
-		sys.exit(2)
-
-def confirm_or_false(message, question, expect="YES"):
 
 	m = message.strip()
 	if m: msg(m)
 
-	conf_msg = "Type uppercase '%s' to confirm: " % expect
-
-	p = question+"  "+conf_msg if question[0].isupper() else \
-		"Are you sure you want to %s?\n%s" % (question,conf_msg)
+	a = question+"  " if question[0].isupper() else \
+			"Are you sure you want to %s?\n" % question
+	b = "Type uppercase '%s' to confirm: " % expect
 
-	vmsg("")
-	return my_raw_input(p).strip() == expect
+	if my_raw_input(a+b).strip() != expect:
+		die(2,"Exiting at user request")
 
 
-def write_to_stdout(data, what, confirm=True):
-	if sys.stdout.isatty() and confirm:
-		confirm_or_exit("",'output {} to screen'.format(what))
+def write_to_stdout(data, desc, ask_terminal=True):
+	if sys.stdout.isatty() and ask_terminal:
+		confirm_or_exit("",'output {} to screen'.format(desc))
 	elif not sys.stdout.isatty():
 		try:
 			of = os.readlink("/proc/%d/fd/1" % os.getpid())
@@ -363,8 +366,83 @@ def write_to_stdout(data, what, confirm=True):
 			msg("Redirecting output to file")
 	sys.stdout.write(data)
 
+# New function
+def write_data_to_file(
+		outfile,
+		data,
+		desc="data",
+		ask_write=False,
+		ask_write_prompt="",
+		ask_write_default_yes=False,
+		ask_overwrite=True,
+		ask_tty=True,
+		no_tty=False,
+		silent=False
+	):
+	if opt.stdout or not sys.stdout.isatty():
+		qmsg("Output to STDOUT requested")
+		write_ok = False
+		if sys.stdout.isatty():
+			if no_tty:
+				die(2,"Printing %s to screen is not allowed" % desc)
+			if ask_tty:
+				confirm_or_exit("",'output %s to screen' % desc)
+		else:
+			try:    of = os.readlink("/proc/%d/fd/1" % os.getpid()) # Linux
+			except: of = None # Windows
+
+			if of:
+				if of[:5] == "pipe:":
+					if no_tty:
+						die(2,"Writing %s to pipe is not allowed" % desc)
+					if ask_tty:
+						confirm_or_exit("",'output %s to pipe' % desc)
+						msg("")
+				of2,pd = os.path.relpath(of),os.path.pardir
+				msg("Redirecting output to file '%s'" %
+						(of if of2[:len(pd)] == pd else of2))
+			else:
+				msg("Redirecting output to file")
+
+		sys.stdout.write(data)
+	else:
+		if opt.outdir: outfile = make_full_path(opt.outdir,outfile)
 
-def write_to_file(outfile,data,what="data",confirm_overwrite=False,verbose=False,exit_on_error=True,silent=False):
+		if ask_write:
+			if not keypress_confirm(ask_write_prompt,
+						default_yes=ask_write_default_yes):
+				die(1,"Exiting at user request")
+
+		hush = False
+		if file_exists(outfile):
+			if ask_overwrite:
+				q = "File '%s' already exists\nOverwrite?" % outfile
+				confirm_or_exit("",q)
+			if not silent: msg("Overwriting file '%s'" % outfile)
+			hush = True
+
+		f = open_file_or_exit(outfile,'wb')
+		try:
+			f.write(data)
+		except:
+			if not silent: msg("Failed to write %s to file '%s'" % (desc,outfile))
+			sys.exit(2)
+		f.close
+
+		if not hush:
+			msg("%s written to file '%s'" % (capfirst(desc),outfile))
+
+		return True
+
+
+def write_to_file(
+		outfile,
+		data,
+		desc="data",
+		confirm_overwrite=False,
+		verbose=False,
+		silent=False
+	):
 
 	if opt.outdir: outfile = make_full_path(opt.outdir,outfile)
 
@@ -373,12 +451,7 @@ def write_to_file(outfile,data,what="data",confirm_overwrite=False,verbose=False
 	else:
 		if confirm_overwrite:
 			q = "File '%s' already exists\nOverwrite?" % outfile
-			if exit_on_error:
-				confirm_or_exit("",q)
-			else:
-				if not confirm_or_false("",q):
-					msg("Not overwriting file at user request")
-					return False
+			confirm_or_exit("",q)
 		else:
 			if not silent: msg("Overwriting file '%s'" % outfile)
 
@@ -386,20 +459,20 @@ def write_to_file(outfile,data,what="data",confirm_overwrite=False,verbose=False
 	try:
 		f.write(data)
 	except:
-		if not silent: msg("Failed to write %s to file '%s'" % (what,outfile))
+		if not silent: msg("Failed to write %s to file '%s'" % (desc,outfile))
 		sys.exit(2)
 	f.close
 
-	if verbose: msg("%s written to file '%s'" % (what.capitalize(),outfile))
+	if verbose: msg("%s written to file '%s'" % (capfirst(desc),outfile))
 	return True
 
 
-def write_to_file_or_stdout(outfile, data,  what="data"):
+def write_to_file_or_stdout(outfile, data,  desc="data"):
 
 	if opt.stdout or not sys.stdout.isatty():
-		write_to_stdout(data, what, confirm=True)
+		write_to_stdout(data, desc)
 	else:
-		write_to_file(outfile,data,what,not opt.quiet,True)
+		write_to_file(outfile,data,desc,not opt.quiet,True)
 
 
 from mmgen.bitcoin import b58decode_pad,b58encode_pad
@@ -437,7 +510,7 @@ def write_wallet_to_file(seed, passwd, key_id, salt, enc_seed):
 	seed_len = str(len(seed)*8)
 	pw_status = "NE" if len(passwd) else "E"
 	hash_preset = opt.hash_preset
-	label = opt.label if opt.label else "No Label"
+	label = opt.label or "No Label"
 	metadata = seed_id.lower(),key_id.lower(),seed_len,\
 		pw_status,make_timestamp()
 	sf  = b58encode_pad(salt)
@@ -447,8 +520,8 @@ def write_wallet_to_file(seed, passwd, key_id, salt, enc_seed):
 		label,
 		"{} {} {} {} {}".format(*metadata),
 		"{}: {} {} {}".format(hash_preset,*get_hash_params(hash_preset)),
-		"{} {}".format(make_chksum_6(sf),  split_into_columns(4,sf)),
-		"{} {}".format(make_chksum_6(esf), split_into_columns(4,esf))
+		"{} {}".format(make_chksum_6(sf),  split_into_cols(4,sf)),
+		"{} {}".format(make_chksum_6(esf), split_into_cols(4,esf))
 	)
 
 	chk = make_chksum_6(" ".join(lines))
@@ -465,18 +538,18 @@ def write_wallet_to_file(seed, passwd, key_id, salt, enc_seed):
 def _check_mmseed_format(words):
 
 	valid = False
-	what = "%s data" % g.seed_ext
+	desc = "%s data" % g.seed_ext
 	try:
 		chklen = len(words[0])
 	except:
 		return False
 
 	if len(words) < 3 or len(words) > 12:
-		msg("Invalid data length (%s) in %s" % (len(words),what))
+		msg("Invalid data length (%s) in %s" % (len(words),desc))
 	elif not is_hexstring(words[0]):
-		msg("Invalid format of checksum '%s' in %s"%(words[0], what))
+		msg("Invalid format of checksum '%s' in %s"%(words[0], desc))
 	elif chklen != 6:
-		msg("Incorrect length of checksum (%s) in %s" % (chklen,what))
+		msg("Incorrect length of checksum (%s) in %s" % (chklen,desc))
 	else: valid = True
 
 	return valid
@@ -484,19 +557,19 @@ def _check_mmseed_format(words):
 
 def _check_wallet_format(infile, lines):
 
-	what = "wallet file '%s'" % infile
+	desc = "wallet file '%s'" % infile
 	valid = False
 	chklen = len(lines[0])
 	if len(lines) != 6:
-		vmsg("Invalid number of lines (%s) in %s" % (len(lines),what))
+		vmsg("Invalid number of lines (%s) in %s" % (len(lines),desc))
 	elif chklen != 6:
-		vmsg("Incorrect length of Master checksum (%s) in %s" % (chklen,what))
+		vmsg("Incorrect length of Master checksum (%s) in %s" % (chklen,desc))
 	elif not is_hexstring(lines[0]):
-		vmsg("Invalid format of Master checksum '%s' in %s"%(lines[0], what))
+		vmsg("Invalid format of Master checksum '%s' in %s"%(lines[0], desc))
 	else: valid = True
 
 	if valid == False:
-		msg("Invalid %s" % what)
+		msg("Invalid %s" % desc)
 		sys.exit(2)
 
 
@@ -506,8 +579,7 @@ def _check_chksum_6(chk,val,desc,infile):
 		msg("%s checksum incorrect in file '%s'!" % (desc,infile))
 		msg("Checksum: %s. Computed value: %s" % (chk,comp_chk))
 		sys.exit(2)
-	elif opt.debug:
-		Msg("%s checksum passed: %s" % (desc.capitalize(),chk))
+	dmsg("%s checksum passed: %s" % (capfirst(desc),chk))
 
 
 def get_data_from_wallet(infile,silent=False):
@@ -557,23 +629,23 @@ def get_data_from_wallet(infile,silent=False):
 def _get_words_from_user(prompt):
 	# split() also strips
 	words = my_raw_input(prompt, echo=opt.echo_passphrase).split()
-	if opt.debug: Msg("Sanitized input: [%s]" % " ".join(words))
+	dmsg("Sanitized input: [%s]" % " ".join(words))
 	return words
 
 
-def _get_words_from_file(infile,what):
-	qmsg("Getting %s from file '%s'" % (what,infile))
+def _get_words_from_file(infile,desc):
+	qmsg("Getting %s from file '%s'" % (desc,infile))
 	f = open_file_or_exit(infile, 'r')
 	# split() also strips
 	words = f.read().split()
 	f.close()
-	if opt.debug: Msg("Sanitized input: [%s]" % " ".join(words))
+	dmsg("Sanitized input: [%s]" % " ".join(words))
 	return words
 
 
-def get_words(infile,what,prompt):
+def get_words(infile,desc,prompt):
 	if infile:
-		return _get_words_from_file(infile,what)
+		return _get_words_from_file(infile,desc)
 	else:
 		return _get_words_from_user(prompt)
 
@@ -586,24 +658,24 @@ def remove_comments(lines):
 		if i: ret.append(i)
 	return ret
 
-def get_lines_from_file(infile,what="",trim_comments=False):
-	if what != "":
-		qmsg("Getting %s from file '%s'" % (what,infile))
+def get_lines_from_file(infile,desc="",trim_comments=False):
+	if desc != "":
+		qmsg("Getting %s from file '%s'" % (desc,infile))
 	f = open_file_or_exit(infile,'r')
 	lines = f.read().splitlines()
 	f.close()
 	return remove_comments(lines) if trim_comments else lines
 
 
-def get_data_from_user(what="data",silent=False):
-	data = my_raw_input("Enter %s: " % what, echo=opt.echo_passphrase)
-	if opt.debug: Msg("User input: [%s]" % data)
+def get_data_from_user(desc="data",silent=False):
+	data = my_raw_input("Enter %s: " % desc, echo=opt.echo_passphrase)
+	dmsg("User input: [%s]" % data)
 	return data
 
-def get_data_from_file(infile,what="data",dash=False,silent=False):
+def get_data_from_file(infile,desc="data",dash=False,silent=False):
 	if dash and infile == "-": return sys.stdin.read()
 	if not silent:
-		qmsg("Getting %s from file '%s'" % (what,infile))
+		qmsg("Getting %s from file '%s'" % (desc,infile))
 	f = open_file_or_exit(infile,'rb')
 	data = f.read()
 	f.close()
@@ -622,7 +694,7 @@ def get_seed_from_seed_data(words):
 	chk = make_chksum_6(seed_b58)
 	vmsg_r("Validating %s checksum..." % g.seed_ext)
 
-	if compare_chksums(chk, "seed", stored_chk, "input",die=False):
+	if compare_chksums(chk, "seed", stored_chk, "input"):
 		seed = b58decode_pad(seed_b58)
 		if seed == False:
 			msg("Invalid b58 number: %s" % val)
@@ -645,9 +717,8 @@ def mark_passwd_file_as_used():
 	passwd_file_used = True
 
 
-def get_mmgen_passphrase(prompt_info,passchg=False):
-	prompt = "Enter {}passphrase for {}: ".format(
-			"old " if passchg else "",prompt_info)
+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"))
@@ -674,17 +745,18 @@ def check_data_fits_file_at_offset(fname,offset,dlen,action):
 		fsize = os.stat(fname).st_size
 
 	if fsize < offset + dlen:
+		m = "Destination" if action == "write" else "Input"
 		msg(
-"Destination file has length %s, too short to %s %s bytes of data at offset %s"
-			% (fsize,action,dlen,offset))
+	"%s file has length %s, too short to %s %s bytes of data at offset %s"
+			% (m,fsize,action,dlen,offset))
 		sys.exit(1)
 
 
 from mmgen.term import kb_hold_protect,get_char
 
-def get_hash_preset_from_user(hp=g.hash_preset,what="data"):
+def get_hash_preset_from_user(hp=g.hash_preset,desc="data"):
 	p = """Enter hash preset for %s,
-or hit ENTER to accept the default value ('%s'): """ % (what,hp)
+ or hit ENTER to accept the default value ('%s'): """ % (desc,hp)
 	while True:
 		ret = my_raw_input(p)
 		if ret:
@@ -751,17 +823,18 @@ def prompt_and_get_char(prompt,chars,enter_ok=False,verbose=False):
 
 def do_license_msg(immed=False):
 
-	from mmgen.license import gpl
+	import mmgen.license as gpl
 	if opt.quiet or g.no_license: return
 
-	msg(gpl['warning'])
-	prompt = "%s " % gpl['prompt'].strip()
+	p = "Press 'w' for conditions and warranty info, or 'c' to continue:"
+	msg(gpl.warning)
+	prompt = "%s " % p.strip()
 
 	while True:
 		reply = get_char(prompt, immed_chars="wc" if immed else "")
 		if reply == 'w':
 			from mmgen.term import do_pager
-			do_pager(gpl['conditions'])
+			do_pager(gpl.conditions)
 		elif reply == 'c':
 			msg(""); break
 		else:

+ 2 - 0
setup.py

@@ -59,6 +59,7 @@ setup(
 			'mmgen.main_txsend',
 			'mmgen.main_txsign',
 			'mmgen.main_walletchk',
+			'mmgen.main_walletconv',
 			'mmgen.main_walletgen',
 
 			'mmgen.share.__init__',
@@ -83,6 +84,7 @@ setup(
 			'mmgen-addrimport',
 			'mmgen-passchg',
 			'mmgen-walletchk',
+			'mmgen-walletconv',
 			'mmgen-walletgen',
 			'mmgen-txcreate',
 			'mmgen-txsign',

+ 5 - 3
test/gentest.py

@@ -11,8 +11,8 @@ sys.path.__setitem__(0,os.path.abspath(os.curdir))
 from binascii import hexlify
 
 import mmgen.opt as opt
-import mmgen.config as g
-from mmgen.util import msg,msg_r,msgrepr,msgrepr_exit,red,green
+import mmgen.globalvars as g
+from mmgen.util import msg,msg_r,mmsg,mdie,red,green,vmsg
 from mmgen.bitcoin import hextowif,privnum2addr
 
 rounds = 100
@@ -23,6 +23,7 @@ opts_data = {
 -h, --help         Print this help message
 -s, --system       Test scripts and modules installed on system rather than
                    those in the repo root
+-v, --verbose      Produce more verbose output
 """,
 	'notes': """
 
@@ -63,6 +64,7 @@ for i in range(1,rounds+1):
 	sec = hexlify(os.urandom(32))
 	wif = hextowif(sec)
 	a = privnum2addr(int(sec,16))
+	vmsg("\nkey:  %s\naddr: %s\n" % (wif,a))
 	b = check_output(["keyconv", wif]).split()[1]
 	if a != b:
 		msg_r(red("\nERROR: Addresses do not match!"))
@@ -74,4 +76,4 @@ for i in range(1,rounds+1):
 """.format(sec,wif,a,b,pnm=g.proj_name).rstrip())
 		sys.exit(3)
 
-msg(green("\nOK"))
+msg(green("%sOK" % ("" if opt.verbose else "\n")))

+ 2 - 0
test/ref/1378FC64-2907DE97-F980D21F[192,1].mmincog

@@ -0,0 +1,2 @@
+H@&оRLrT.╝у%╛е7© ычeчЬа╧:x%ё5┐НoЫ*ю!вИ	д
+Мё0╬sЪ│ХёR%┤Pьг

+ 4 - 0
test/ref/1378FC64-4DCB5174-872806A7[192,1].mmincox

@@ -0,0 +1,4 @@
+8e7a aa61 cf9d acba 10ec cda2 a54c a64e
+ee54 970a ebdc f44d 53d2 c005 46ea 1ab3
+d940 0d78 6050 16a8 4ef6 e228 661a 803a
+38ea 6874 189f b022 db94 3e11 051b 0302

+ 5 - 0
test/ref/98831F3A-1630A9F2-870376A9[256,1].mmincox

@@ -0,0 +1,5 @@
+078b 15e6 42c1 8447 d5a4 f03c b3ae aab9
+7d79 2640 c307 bf2a 78a6 847b 44f2 6d96
+7d9b 44ac 193c a47a 9a12 6e60 9f8f fe96
+720b 0682 01d8 7da4 9eea be67 60c6 cbcf
+d522 5a97 8bc2 5554

+ 1 - 0
test/ref/98831F3A-5482381C-18460FB1[256,1].mmincog

@@ -0,0 +1 @@
+▒╧и7м8╩≈vЮЛP  0,Ъ%▐║F╖║ДБNИ`зtсЛ╫$╟@≥pk%┘рL}нЯQГА▄≥p/╨=З╘-rО~GОС9

+ 4 - 0
test/ref/FE3C6545-BC4BE3F2-32586837[128,1].mmincox

@@ -0,0 +1,4 @@
+80f5 2531 04ab 4de4 11fd ccb8 aae4 26ed
+2520 8218 e5e1 8744 d367 9e9d 3491 ff8e
+f83e 523b 36f7 dbe5 3c92 aeca c935 15fa
+1bc5 2c54 0e4c 0825

BIN
test/ref/FE3C6545-E29303EA-5E229E30[128,1].mmincog


BIN
test/ref/sample-text.mmenc


+ 331 - 125
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 msgrepr,msgrepr_exit,Msg,die
+from mmgen.util import mmsg,mdie,Msg,die
 from mmgen.test import *
 
 hincog_fn      = "rand_data"
@@ -35,6 +35,9 @@ 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': {
@@ -46,8 +49,11 @@ cfgs = {
 		'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",
+		'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':      "",
@@ -68,8 +74,11 @@ cfgs = {
 		'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",
+		'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':      "",
@@ -97,8 +106,11 @@ cfgs = {
 
 #		'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",
+		'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':      "",
@@ -108,7 +120,6 @@ cfgs = {
 			'addrs':       "refaddrgen3",
 			'akeys.mmenc': "refkeyaddrgen3"
 		},
-
 	},
 	'1': {
 		'tmpdir':        os.path.join("test","tmp1"),
@@ -133,7 +144,7 @@ cfgs = {
 		'tmpdir':        os.path.join("test","tmp2"),
 		'wpasswd':       "Hodling away",
 		'addr_idx_list': "37,45,3-6,22-23",  # 8 addresses
-        'seed_len':      128,
+		'seed_len':      128,
 		'dep_generators': {
 			'mmdat':       "walletgen2",
 			'addrs':       "addrgen2",
@@ -157,7 +168,7 @@ cfgs = {
 		'tmpdir':        os.path.join("test","tmp4"),
 		'wpasswd':       "Hashrate rising",
 		'addr_idx_list': "63,1004,542-544,7-9", # 8 addresses
-        'seed_len':      192,
+		'seed_len':      192,
 		'dep_generators': {
 			'mmdat':       "walletgen4",
 			'mmbrain':     "walletgen4",
@@ -177,9 +188,6 @@ cfgs = {
 	},
 	'9': {
 		'tmpdir':        os.path.join("test","tmp9"),
-		'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",
 		'tool_enc_infn':      "tool_encrypt.in",
 #		'tool_enc_ref_infn':  "tool_encrypt_ref.in",
 		'dep_generators': {
@@ -191,46 +199,14 @@ cfgs = {
 	},
 }
 
+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
-	# 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]])],
 	['passchg',         (5,'password, label and hash preset change',[[["mmdat"],1]])],
@@ -256,13 +232,11 @@ cmd_data = OrderedDict([
 	['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)',     [])],
 	['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]])],
-#	['export_mnemonic2', (2,'seed export to mmwords format (2), 128-bit seed (WIP)',[[["mmdat"],2]])],
 
 	['walletgen3',(3,'wallet generation (3)',                  [])],
 	['addrgen3',  (3,'address generation (3)',                 [[["mmdat"],3]])],
@@ -270,18 +244,83 @@ cmd_data = OrderedDict([
 	['txsign3',   (3,'tx signing with inputs and outputs from two wallets',[[["mmdat"],1],[["mmdat","raw"],3]])],
 
 	['walletgen4',(4,'wallet generation (4) (brainwallet)',    [])],
-#	['walletgen4',(4,'wallet generation (4) (brainwallet, 192-bit seed (WIP))', [])],
 	['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_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)',
@@ -296,23 +335,34 @@ for k in cfgs.keys():
 		cfgs[k]['amts'][idx] = "%s.%s" % ((getrandnum(2) % mod), str(getrandnum(4))[:5])
 
 meta_cmds = OrderedDict([
-	['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_find_incog_data"))],
+	['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 = {
@@ -350,8 +400,6 @@ else:
 	send_delay = 0
 	os.environ["MMGEN_DISABLE_HOLD_PROTECT"] = "1"
 
-if opt.debug: opt.verbose = True
-
 if opt.exact_output:
 	def msg(s): pass
 	vmsg = vmsg_r = msg_r = msg
@@ -378,15 +426,22 @@ def errmsg_r(s): stderr_save.write(s)
 
 if opt.list_cmds:
 	fs = "  {:<{w}} - {}"
-	Msg("Available commands:")
+	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))
-	Msg("\nAvailable metacommands:")
+
 	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][1]),w=w))
-	Msg("\nAvailable utilities:")
+		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))
@@ -428,7 +483,8 @@ def my_expect(p,s,t='',delay=send_delay,regex=False,nonl=False):
 	else:
 		if t == '':
 			if not nonl: vmsg("")
-		else: ret = my_send(p,t,delay,s)
+		else:
+			my_send(p,t,delay,s)
 		return ret
 
 def get_file_with_ext(ext,mydir,delete=True):
@@ -502,28 +558,32 @@ class MMGenExpect(object):
 			vmsg("EOT")
 		my_expect(self.p,"ENTER to continue: ",'\n')
 
-	def passphrase_new(self,what,passphrase):
-		my_expect(self.p,("Enter passphrase for %s: " % what), passphrase+"\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,what,passphrase,pwtype=""):
+	def passphrase(self,desc,passphrase,pwtype=""):
 		if pwtype: pwtype += " "
-		my_expect(self.p,("Enter %spassphrase for %s.*?: " % (pwtype,what)),
+		my_expect(self.p,("Enter %spassphrase for %s.*?: " % (pwtype,desc)),
 				passphrase+"\n",regex=True)
 
-	def hash_preset(self,what,preset=''):
-		my_expect(self.p,("Enter hash preset for %s," % what))
+	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,what,overwrite_unlikely=False,query="Overwrite?  "):
-		s1 = "%s written to file " % what
+	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")
-			ret = my_expect(self.p,s1)
+			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" % (what,cyan(outfile.replace("'",""))))
+		vmsg("%s file: %s" % (desc,cyan(outfile.replace("'",""))))
 		return outfile
 
 	def no_overwrite(self):
@@ -679,15 +739,15 @@ def check_needs_rerun(ts,cmd,build=False,root=True,force_delete=False,dpy=False)
 
 	return rerun
 
-def refcheck(what,chk,refchk):
-	vmsg("Comparing %s '%s' to stored reference" % (what,chk))
+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() % (what,chk,refchk)))
+""".strip() % (desc,chk,refchk)))
 		sys.exit(3)
 
 def check_deps(cmds):
@@ -711,7 +771,7 @@ def check_deps(cmds):
 		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))
-#			msgrepr(cmd,c)
+#			mmsg(cmd,c)
 
 
 def clean(dirs=[]):
@@ -738,7 +798,7 @@ class MMGenTestSuite(object):
 	def get_num_exts_for_cmd(self,cmd,dpy=False): # dpy ignored here
 		num = str(cmd_data[cmd][0])
 		dgl = cfgs[num]['dep_generators']
-#	msgrepr(num,cmd,dgl)
+#	mmsg(num,cmd,dgl)
 		if cmd in dgl.values():
 			exts = [k for k in dgl if dgl[k] == cmd]
 			return (num,exts)
@@ -801,9 +861,9 @@ class MMGenTestSuite(object):
 		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'])
+		refcheck("seed ID",seed_id,cfg['seed_id'])
 
- 	refwalletgen1 = refwalletgen2 = refwalletgen3 = refwalletgen
+	refwalletgen1 = refwalletgen2 = refwalletgen3 = refwalletgen
 
 	def passchg(self,name,walletfile):
 
@@ -850,7 +910,7 @@ class MMGenTestSuite(object):
 		d = " (%s-bit seed)" % cfg['seed_len']
 		self.addrgen(name,walletfile,check_ref=True)
 
- 	refaddrgen1 = refaddrgen2 = refaddrgen3 = refaddrgen
+	refaddrgen1 = refaddrgen2 = refaddrgen3 = refaddrgen
 
 	def addrimport(self,name,addrfile):
 		outfile = os.path.join(cfg['tmpdir'],"addrfile_w_comments")
@@ -1014,11 +1074,11 @@ class MMGenTestSuite(object):
 		t.written_to_file("Data",query="")
 		ok()
 
-	def addrgen_seed(self,name,walletfile,foo,what="seed data",arg="-s"):
+	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 " % what)
+		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)
@@ -1026,14 +1086,14 @@ class MMGenTestSuite(object):
 		ok()
 
 	def addrgen_mnemonic(self,name,walletfile,foo):
-		self.addrgen_seed(name,walletfile,foo,what="mnemonic",arg="-m")
+		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("MMGen incognito wallet \w{8}", cfg['wpasswd'])
+		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)
@@ -1068,7 +1128,7 @@ class MMGenTestSuite(object):
 	def refkeyaddrgen(self,name,walletfile):
 		self.keyaddrgen(name,walletfile,check_ref=True)
 
- 	refkeyaddrgen1 = refkeyaddrgen2 = refkeyaddrgen3 = refkeyaddrgen
+	refkeyaddrgen1 = refkeyaddrgen2 = refkeyaddrgen3 = refkeyaddrgen
 
 	def txsign_keyaddr(self,name,keyaddr_file,txfile):
 		t = MMGenExpect(name,"mmgen-txsign", ["-d",cfg['tmpdir'],"-M",keyaddr_file,txfile])
@@ -1136,9 +1196,9 @@ class MMGenTestSuite(object):
 		t.license()
 		t.tx_view()
 
-		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[cnum]['wpasswd'])
+		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')
 
@@ -1155,7 +1215,7 @@ class MMGenTestSuite(object):
 			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",cfg['tool_enc_passwd'])
+		t.passphrase_new("user data",tool_enc_passwd)
 		t.written_to_file("Encrypted data")
 		ok()
 # Generate the reference mmenc file
@@ -1168,7 +1228,7 @@ class MMGenTestSuite(object):
 		of = name + ".out"
 		t = MMGenExpect(name,"mmgen-tool",
 			["-d",cfg['tmpdir'],"decrypt",f2,"outfile="+of,"hash_preset=1"])
-		t.passphrase("user data",cfg['tool_enc_passwd'])
+		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))
@@ -1182,7 +1242,127 @@ 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))
 
+	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 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")
+		wf = t.written_to_file(desc[0].upper()+desc[1:],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)
@@ -1191,8 +1371,8 @@ class MMGenTestSuite(object):
 
 	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)
+		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
 
@@ -1203,37 +1383,60 @@ class MMGenTestSuite(object):
 	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])
+		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)
 
-	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)
+	# 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_chk3_spc(self,name):
+	def ref_brain_chk_spc3(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"]):
+	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("MMGen incognito wallet",cfg['wpasswd'])
+			t.passphrase("incognito wallet",cfg['wpasswd'])
 			t.hash_preset("incog wallet","1")
-			if wtype == 'ic_wallet_old':
+			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_incog_chk1 = ref_incog_chk2 = ref_incog_chk3 = ref_incog_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'])
@@ -1261,11 +1464,11 @@ class MMGenTestSuite(object):
 		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.passphrase("user data",tool_enc_passwd)
 		t.readline()
 		import re
 		o = re.sub('\r\n','\n',t.read())
-		cmp_or_die(cfg['sample_text'],o)
+		cmp_or_die(sample_text,o)
 
 # main()
 if opt.pause:
@@ -1288,8 +1491,11 @@ try:
 				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)
+				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:

+ 1 - 3
test/tooltest.py

@@ -9,7 +9,7 @@ os.chdir(os.path.join(pn,os.pardir))
 sys.path.__setitem__(0,os.path.abspath(os.curdir))
 
 import mmgen.opt as opt
-from mmgen.util import msg,msg_r,vmsg,vmsg_r,Msg,msgrepr, msgrepr_exit
+from mmgen.util import msg,msg_r,vmsg,vmsg_r,Msg,mmsg,mdie
 from collections import OrderedDict
 
 cmd_data = OrderedDict([
@@ -94,8 +94,6 @@ cmd_args = opt.opts.init(opts_data,add_opts=["exact_output"])
 
 if opt.system: sys.path.pop(0)
 
-if opt.debug: opt.verbose = True
-
 if opt.list_cmds:
 	fs = "  {:<{w}} - {}"
 	Msg("Available commands:")

Some files were not shown because too many files changed in this diff