Browse Source

Code cleanups, streamlined code in 'mmgen/addr.py'

philemon 10 years ago
parent
commit
7e079f1b7a
17 changed files with 220 additions and 450 deletions
  1. 25 24
      mmgen-addrgen
  2. 1 0
      mmgen-passchg
  3. 2 0
      mmgen-pywallet
  4. 1 4
      mmgen-tool
  5. 5 0
      mmgen-txsign
  6. 0 1
      mmgen-walletgen
  7. 98 181
      mmgen/Opts.py
  8. 53 65
      mmgen/addr.py
  9. 0 27
      mmgen/tests/addr.py
  10. 0 24
      mmgen/tests/mn_electrum.py
  11. 0 24
      mmgen/tests/mn_tirosh.py
  12. 0 27
      mmgen/tests/util.py
  13. 0 27
      mmgen/tests/walletgen.py
  14. 12 7
      mmgen/tool.py
  15. 5 5
      mmgen/tx.py
  16. 18 7
      mmgen/util.py
  17. 0 27
      mmgen/walletgen.py

+ 25 - 24
mmgen-addrgen

@@ -31,12 +31,12 @@ from mmgen.util import *
 from mmgen.addr import *
 from mmgen.tx import make_addr_data_chksum
 
-gen_what = "addresses" if sys.argv[0].split("-")[-1] == "addrgen" else "keys"
+what = "keys" if sys.argv[0].split("-")[-1] == "keygen" else "addresses"
 
 help_data = {
 	'prog_name': g.prog_name,
 	'desc': """Generate a list or range of {} from an {g.proj_name} wallet,
-                  mnemonic, seed or password""".format(gen_what,g=g),
+                  mnemonic, seed or password""".format(what,g=g),
 	'usage':"[opts] [infile] <address list>",
 	'options': """
 -h, --help              Print this help message{}
@@ -73,9 +73,9 @@ help_data = {
 "\n                        '{}-txsign'".format(g.proj_name.lower()),
 "\n-x, --b16               Print secret keys in hexadecimal too"
 			)
-		if gen_what == "keys" else ("","","")),
+		if what == "keys" else ("","","")),
 		seed_lens=", ".join([str(i) for i in g.seed_lens]),
-		what=gen_what, g=g
+		what=what, g=g
 ),
 	'notes': """
 
@@ -106,7 +106,7 @@ For a brainwallet passphrase to always generate the same keys and addresses,
 the same 'l' and 'p' parameters to '--from-brain' must be used in all future
 invocations with that passphrase
 """.format("\n\nBy default, both addresses and secret keys are generated."
-				if gen_what == "keys" else "")
+				if what == "keys" else "")
 }
 
 opts,cmd_args = parse_opts(sys.argv,help_data)
@@ -117,8 +117,6 @@ if 'quiet' in opts: g.quiet = True
 if 'from_incog_hex' in opts or 'from_incog_hidden' in opts:
 	opts['from_incog'] = True
 
-opts['gen_what'] = gen_what
-
 if g.debug: show_opts_and_cmd_args(opts,cmd_args)
 
 if len(cmd_args) == 1 and (
@@ -140,22 +138,26 @@ if not addr_idxs: sys.exit(2)
 do_license_msg()
 
 # Interact with user:
-if gen_what == "keys" and not g.quiet:
+if what == "keys" and not g.quiet:
 	confirm_or_exit(cmessages['unencrypted_secret_keys'], 'continue')
 
 # Generate data:
 
-if gen_what == "addresses":
-	opts['print_addresses_only'] = True
-else:
-	if not 'no_addresses' in opts: opts['print_secret'] = True
-
 seed    = get_seed_retry(infile,opts)
 seed_id = make_chksum_8(seed)
 
+for l in (
+	('flat_list', 'no_addresses'),
+	('flat_list', 'b16'),
+): check_incompatible_opts(opts,l)
+
+opts['gen_what'] = \
+	("addrs") if what == "addresses" else (
+	("keys") if 'no_addresses' in opts else ("addrs","keys"))
+
 addr_data        = generate_addrs(seed, addr_idxs, opts)
-addr_data_chksum = make_addr_data_chksum([(a['num'],a['addr'])
-		for a in addr_data]) if not 'no_addresses' in opts else ""
+addr_data_chksum = make_addr_data_chksum([(a.num,a.addr)
+		for a in addr_data]) if 'addrs' in opts['gen_what'] else ""
 addr_data_str    = format_addr_data(
 		addr_data, addr_data_chksum, seed_id, addr_idxs, opts)
 
@@ -163,20 +165,19 @@ outfile_base = "{}[{}]".format(seed_id, fmt_addr_idxs(addr_idxs))
 
 # Output data:
 if 'stdout' in opts:
-	confirm = True if (gen_what == "keys" and not g.quiet) else False
-	write_to_stdout(addr_data_str,gen_what,confirm)
+	confirm = True if (what == "keys" and not g.quiet) else False
+	write_to_stdout(addr_data_str,what,confirm)
 elif not sys.stdout.isatty():
-	write_to_stdout(addr_data_str,gen_what,confirm=False)
+	write_to_stdout(addr_data_str,what,confirm=False)
 else:
 	confirm = False if g.quiet else True
 	outfile = outfile_base + "." + (
-		g.addrfile_ext if 'print_addresses_only' in opts else (
-		g.keyfile_ext if 'no_addresses' in opts else (
-		g.keylist_ext if 'flat_list' in opts else "akeys"))
-	)
-	write_to_file(outfile,addr_data_str,opts,gen_what,confirm,True)
+		g.keylist_ext if 'flat_list' in opts else (
+		g.keyfile_ext if opts['gen_what'] == ("keys") else (
+		g.addrfile_ext if opts['gen_what'] == ("addrs") else "akeys")))
+	write_to_file(outfile,addr_data_str,opts,what,confirm,True)
 
-if not 'no_addresses' in opts:
+if 'addrs' in opts['gen_what']:
 	msg("Checksum for address data {}: {}".format(outfile_base,addr_data_chksum))
 	if 'save_checksum' in opts:
 		a = "address data checksum"

+ 1 - 0
mmgen-passchg

@@ -26,6 +26,7 @@ from mmgen.util import *
 import mmgen.config as g
 
 help_data = {
+	'prog_name': g.prog_name,
 	'desc':  """Change the passphrase, hash preset or label of an {}
                   deterministic wallet""".format(g.proj_name),
 	'usage':   "[opts] [filename]",

+ 2 - 0
mmgen-pywallet

@@ -86,6 +86,8 @@ help_data = {
 }
 
 opts,cmd_args = parse_opts(sys.argv,help_data)
+from mmgen.Opts import check_incompatible_opts
+check_incompatible_opts(opts,('json','keys','addrs','keysforaddrs'))
 
 if len(cmd_args) == 1:
 	from mmgen.util import check_infile

+ 1 - 4
mmgen-tool

@@ -19,13 +19,10 @@
 mmgen-tool: Perform various Bitcoin-related operations - part of the MMGen suite
 """
 
-import sys, os
-from hashlib import sha256
-
+import sys
 import mmgen.config as g
 import mmgen.tool as tool
 from mmgen.Opts import *
-from mmgen.util import pretty_hexdump
 
 help_data = {
 	'prog_name': g.prog_name,

+ 5 - 0
mmgen-txsign

@@ -89,6 +89,11 @@ Seed data supplied in files must have the following extensions:
 
 opts,infiles = parse_opts(sys.argv,help_data)
 
+for l in (
+('tx_id', 'info'),
+('keys_from_file','all_keys_from_file')
+): check_incompatible_opts(opts,l)
+
 if "quiet" in opts: g.quiet = True
 if 'from_incog_hex' in opts or 'from_incog_hidden' in opts:
 	opts['from_incog'] = True

+ 0 - 1
mmgen-walletgen

@@ -25,7 +25,6 @@ from hashlib import sha256
 import mmgen.config as g
 from mmgen.Opts import *
 from mmgen.license import *
-from mmgen.walletgen import *
 from mmgen.util import *
 
 help_data = {

+ 98 - 181
mmgen/Opts.py

@@ -16,111 +16,100 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-import sys, getopt
+import sys
 import mmgen.config as g
+import mmgen.opt.Opts
+from mmgen.util import msg,check_infile,check_outfile,check_outdir
 
-def usage(hd):
-	print "USAGE: %s %s" % (hd['prog_name'], hd['usage'])
-	sys.exit(2)
+def usage(hd): mmgen.opt.Opts.usage(hd)
 
-def print_version_info(): # MMGen only
+def print_version_info():
 	print """
 '{g.prog_name}' version {g.version}.  Part of the {g.proj_name} suite.
 Copyright (C) {g.Cdates} by {g.author} {g.email}.
 """.format(g=g).strip()
 
-def print_help(help_data):
-	pn = help_data['prog_name']
-	pn_len = str(len(pn)+2)
-	print ("  %-"+pn_len+"s %s") % (pn.upper()+":", help_data['desc'].strip())
-	print ("  %-"+pn_len+"s %s %s")%("USAGE:", pn, help_data['usage'].strip())
-	sep = "\n    "
-	print "  OPTIONS:"+sep+"%s" % sep.join(help_data['options'].strip().split("\n"))
-	if "notes" in help_data:
-		print "  %s" % "\n  ".join(help_data['notes'][1:-1].split("\n"))
-
+def check_incompatible_opts(opts,incompat_list):
+	bad = [k for k in opts.keys() if k in incompat_list]
+	if len(bad) > 1:
+		msg("Mutually exclusive options: %s" % " ".join(
+					["--"+b.replace("_","-") for b in bad]))
+		sys.exit(1)
 
-def process_opts(argv,help_data,short_opts,long_opts):
+def parse_opts(argv,help_data):
 
-	if len(argv) == 2 and argv[1] == '--version': # MMGen only!
+	if len(argv) == 2 and argv[1] == '--version':
 		print_version_info(); sys.exit()
 
-	if g.debug:
-		print "Short opts: %s" % repr(short_opts)
-		print "Long opts:  %s" % repr(long_opts)
-
-	long_opts  = [i.replace("_","-") for i in long_opts]
-
-	try: cl_opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
-	except getopt.GetoptError as err:
-		print str(err); sys.exit(2)
-
-	opts,short_opts_l = {},[]
-
-	for i in short_opts:
-		if i == ":": short_opts_l[-1] += i
-		else:        short_opts_l     += i
-
-	for opt, arg in cl_opts:
-		if   opt in ("-h","--help"): print_help(help_data); sys.exit()
-		elif opt[:2] == "--" and opt[2:] in long_opts:
-			opts[opt[2:].replace("-","_")] = True
-		elif opt[:2] == "--" and opt[2:]+"=" in long_opts:
-			opts[opt[2:].replace("-","_")] = arg
-		elif opt[0] == "-" and opt[1]     in short_opts_l:
-			opts[long_opts[short_opts_l.index(opt[1:])].replace("-","_")] = True
-		elif opt[0] == "-" and opt[1:]+":" in short_opts_l:
-			opts[long_opts[short_opts_l.index(opt[1:]+":")][:-1].replace("-","_")] = arg
-		else: assert False, "Invalid option"
-
-	if g.debug: print "User-selected options: %s" % repr(opts)
-
-	return opts,args
-
-
-def parse_opts(argv,help_data):
-
-	lines = help_data['options'].strip().split("\n")
-	import re
-	pat = r"^-([a-zA-Z0-9]), --([a-zA-Z0-9-]{1,64})(=| )(.+)"
-	rep = r"-{0}, --{1}{w}{3}"
-	opt_data = [list(m.groups()) for m in [re.match(pat,l) for l in lines] if m]
-#	for o in opt_data: print o
-#	sys.exit()
+	opts,args,short_opts,long_opts = mmgen.opt.Opts.parse_opts(argv,help_data)
 
-	for d in opt_data:
-		if d[2] == " ": d[2] = ""
-	short_opts = "".join([d[0]+d[2].replace("=",":") for d in opt_data])
-	long_opts = [d[1].replace("-","_")+d[2] for d in opt_data]
-	help_data['options'] = "\n".join(
-		[rep.format(w=" ", *m.groups())
-			if m else k for m,k in [(re.match(pat,l),l) for l in lines]]
-	)
-	opts,infiles = process_opts(argv,help_data,short_opts,long_opts)
+	if g.debug:
+		print "short opts: %s" % repr(short_opts)
+		print "long opts:  %s" % repr(long_opts)
+		print "user-selected opts: %s" % repr(opts)
+		print "cmd args:           %s" % repr(args)
+
+	for l in (
+	('outdir', 'export_incog_hidden'),
+	('from_incog_hidden','from_incog','from_seed','from_mnemonic','from_brain'),
+	('export_incog','export_incog_hex','export_incog_hidden','export_mnemonic',
+	 'export_seed'),
+	('quiet','verbose')
+	): check_incompatible_opts(opts,l)
 
 	# check_opts() doesn't touch opts[]
 	if not check_opts(opts,long_opts): sys.exit(1)
 
-	# If unset, set these to the default values in mmgen.config:
+	# If unset, set these to default values in mmgen.config:
 	for v in g.cl_override_vars:
 		if v in opts: typeconvert_override_var(opts,v)
 		else: opts[v] = eval("g."+v)
 
-	if g.debug: print "processed opts: %s" % opts
+	if g.debug: print "opts after typeconvert: %s" % opts
 
-	return opts,infiles
+	return opts,args
 
 
 def show_opts_and_cmd_args(opts,cmd_args):
 	print "Processed options: %s" % repr(opts)
 	print "Cmd args:          %s" % repr(cmd_args)
 
+def check_opts(opts,long_opts):
 
-# Everything below here is MMGen-specific:
-
-from mmgen.util import msg,check_infile
+	def opt_splits(val,sep,n,what):
+		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))
+			return False
 
-def check_opts(opts,long_opts):
+		if len(l) == n: return True
+		else:
+			msg("'%s': invalid %s (%s %s-separated items required)" %
+					(val,what,n,sepword))
+			return False
+
+	def opt_compares(val,op,target,what):
+		if not eval("%s %s %s" % (val, op, target)):
+			msg("%s: invalid %s (not %s %s)" % (val,what,op,target))
+			return False
+		return True
+
+	def opt_is_int(val,what):
+		try: int(val)
+		except:
+			msg("'%s': invalid %s (not an integer)" % (val,what))
+			return False
+		return True
+
+	def opt_is_in_list(val,lst,what):
+		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))
+			return False
+		return True
 
 	for opt,val in opts.items():
 
@@ -133,121 +122,49 @@ def check_opts(opts,long_opts):
 			continue
 
 		if opt == 'outdir':
-			what = "output directory"
-			import os
-			if os.path.isdir(val):
-				if os.access(val, os.W_OK|os.X_OK):
-					opts[opt] = os.path.normpath(val)
-				else:
-					msg("Requested %s '%s' is unwritable by you" % (what,val))
-					return False
-			else:
-				msg("Requested %s '%s' does not exist" % (what,val))
-				return False
-
+			check_outdir(val)  # exits on error
 		elif opt == 'label':
-
-			if len(val) > g.max_wallet_label_len:
-				msg("Label must be %s characters or less" %
-					g.max_wallet_label_len)
+			if not opt_compares(len(val),"<=",g.max_wallet_label_len,"label length"):
 				return False
-
-			for ch in list(val):
-				chs = g.wallet_label_symbols
-				if ch not in chs:
-					msg("'%s': ERROR: label contains an illegal symbol" % val)
-					msg("The following symbols are permitted:\n%s" % "".join(chs))
-					return False
-		elif opt == 'export_incog_hidden' or opt == 'from_incog_hidden':
-			try:
-				if opt == 'export_incog_hidden':
-					outfile,offset = val.split(",")
-				else:
-					outfile,offset,seed_len = val.split(",")
+			try: val.decode("ascii")
 			except:
-				msg("'%s': invalid %s" % (val,what))
+				msg("ERROR: label contains a non-ASCII symbol")
 				return False
-
-			try:
-				o = int(offset)
-			except:
-				msg("'%s': invalid 'o' %s (not an integer)" % (offset,what))
-				return False
-
-			if o < 0:
-				msg("'%s': invalid 'o' %s (less than zero)" % (offset,what))
-				return False
-
+			w = "character in label"
+			for ch in list(val):
+				if not opt_is_in_list(ch,g.wallet_label_symbols,w): return False
+		elif opt == 'export_incog_hidden' or opt == 'from_incog_hidden':
 			if opt == 'from_incog_hidden':
-				try:
-					sl = int(seed_len)
-				except:
-					msg("'%s': invalid 'l' %s (not an integer)" % (sl,what))
-					return False
-
-				if sl not in g.seed_lens:
-					msg("'%s': invalid 'l' %s (valid choices: %s)" %
-						(sl,what," ".join(str(i) for i in g.seed_lens)))
-					return False
-
-			import os, stat
-			try: mode = os.stat(outfile).st_mode
-			except:
-				msg("Unable to stat requested %s '%s'" % (what,outfile))
-				return False
-
-			if not (stat.S_ISREG(mode) or stat.S_ISBLK(mode)):
-				msg("Requested %s '%s' is not a file or block device" %
-						(what,outfile))
-				return False
-
-			ac,m = (os.W_OK,"writ") \
-				if "export_incog_hidden" in opts else (os.R_OK,"read")
-			if not os.access(outfile, ac):
-				msg("Requested %s '%s' is un%sable by you" % (what,outfile,m))
-				return False
-
+				if not opt_splits(val,",",3,what): return False
+				infile,offset,seed_len = val.split(",")
+				check_infile(infile)
+				w = "seed length " + what
+				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:
+				if not opt_splits(val,",",2,what): return False
+				outfile,offset = val.split(",")
+				check_outfile(outfile)
+			w = "offset " + what
+			if not opt_is_int(offset,w): return False
+			if not opt_compares(offset,">=",0,what): return False
 		elif opt == 'from_brain':
-			try:
-				l,p = val.split(",")
-			except:
-				msg("'%s': invalid %s" % (val,what))
-				return False
-
-			try:
-				int(l)
-			except:
-				msg("'%s': invalid 'l' %s (not an integer)" % (l,what))
-				return False
-
-			if int(l) not in g.seed_lens:
-				msg("'%s': invalid 'l' %s.  Options: %s" %
-						(l, what, ", ".join([str(i) for i in g.seed_lens])))
-				return False
-
-			if p not in g.hash_presets:
-				hps = ", ".join([i for i in sorted(g.hash_presets.keys())])
-				msg("'%s': invalid 'p' %s.  Options: %s" % (p, what, hps))
-				return False
+			if not opt_splits(val,",",2,what): return False
+			l,p = val.split(",")
+			w = "seed length " + what
+			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
+			if not opt_is_in_list(p,g.hash_presets.keys(),w): return False
 		elif opt == 'seed_len':
-			if val not in g.seed_lens:
-				msg("'%s': invalid %s.  Options: %s"
-				% (val,what,", ".join([str(i) for i in g.seed_lens])))
-				return False
+			if not opt_is_int(val,what): return False
+			if not opt_is_in_list(int(val),g.seed_lens,what): return False
 		elif opt == 'hash_preset':
-			if val not in g.hash_presets:
-				msg("'%s': invalid %s.  Options: %s"
-				% (val,what,", ".join(sorted(g.hash_presets.keys()))))
-				return False
+			if not opt_is_in_list(val,g.hash_presets.keys(),what): return False
 		elif opt == 'usr_randchars':
-			try: v = int(val)
-			except:
-				msg("'%s': invalid value for %s (not an integer)" % (val,what))
-				return False
-			if v != 0 and not (g.min_urandchars <= v <= g.max_urandchars):
-				msg("'%s': invalid %s (must be >= %s and <= %s (or zero))"
-				% (v,what,g.min_urandchars,g.max_urandchars))
-				return False
+			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
 		else:
 			if g.debug: print "check_opts(): No test for opt '%s'" % opt
 

+ 53 - 65
mmgen/addr.py

@@ -33,36 +33,33 @@ addrmsgs = {
 # MMGen address file
 #
 # This file is editable.
-# Everything following a hash symbol '#' is a comment and ignored by {}.
+# Everything following a hash symbol '#' is a comment and ignored by {pnm}.
 # A text label of {} characters or less may be added to the right of each
 # address, and it will be appended to the bitcoind wallet label upon import.
 # The label may contain any printable ASCII symbol.
-""".strip().format(g.proj_name,g.max_addr_label_len)
+""".strip().format(g.max_addr_label_len,pnm=g.proj_name),
+	'no_keyconv_msg': """
+Executable '{kcexe}' unavailable. Falling back on (slow) internal ECDSA library.
+Please install '{kcexe}' from the {vanityg} package on your system for much
+faster address generation.
+""".format(kcexe=g.keyconv_exec, vanityg="vanitygen")
 }
 
 def test_for_keyconv():
-	"""
-	Test for the presence of 'keyconv' utility on system
-	"""
 
 	from subprocess import Popen, PIPE
 	try:
 		p = Popen([g.keyconv_exec, '-h'], stdout=PIPE, stderr=PIPE)
 	except:
-		sys.stderr.write("""
-Executable '%s' unavailable. Falling back on (slow) internal ECDSA library.
-Please install '%s' from the %s package on your system for much
-faster address generation.
-
-""" % (g.keyconv_exec, g.keyconv_exec, "vanitygen"))
+		msg(addrmsgs['no_keyconv_msg'])
 		return False
-	else:
-		return True
+
+	return True
 
 
 def generate_addrs(seed, addrnums, opts):
 
-	if not 'no_addresses' in opts:
+	if 'addrs' in opts['gen_what']:
 		if 'no_keyconv' in opts or test_for_keyconv() == False:
 			msg("Using (slow) internal ECDSA library for address generation")
 			from mmgen.bitcoin import privnum2addr
@@ -71,73 +68,62 @@ def generate_addrs(seed, addrnums, opts):
 			from subprocess import Popen, PIPE
 			keyconv = "keyconv"
 
-	a,t_addrs,i,out = sorted(addrnums),len(addrnums),0,[]
-
-	while a:
-		seed = sha512(seed).digest()
-		i += 1 # round /i/
+	fmt = "num addr" if opts['gen_what'] == ("addrs") else (
+		"num sec wif" if opts['gen_what'] == ("keys") else "num sec wif addr")
 
-		if g.debug: print "Seed round %s: %s" % (i, hexlify(seed))
+	from collections import namedtuple
+	addrinfo = namedtuple("addrinfo",fmt)
+	addrinfo_args = "%s" % ",".join(fmt.split())
 
-		if i < a[0]: continue
+	t_addrs,num,pos,out = len(addrnums),0,0,[]
+	addrnums.sort()  # needed only if caller didn't sort
 
-		a.pop(0)
-
-		qmsg_r("\rGenerating %s %s (%s of %s)" %
-			(opts['gen_what'], i, t_addrs-len(a), t_addrs))
+	try:
+		while pos != t_addrs:
+			seed = sha512(seed).digest()
+			num += 1 # round
 
-		# Secret key is double sha256 of seed hash round /i/
-		sec = sha256(sha256(seed).digest()).hexdigest()
-		wif = numtowif(int(sec,16))
+			if g.debug: print "Seed round %s: %s" % (num, hexlify(seed))
+			if num != addrnums[pos]: continue
 
-		if g.debug:
-			print "Privkey round %s:\n  hex: %s\n  wif: %s" % (i, sec, wif)
+			pos += 1
 
-		d = { 'num': i }
+			qmsg_r("\rGenerating %s %s (%s of %s)" %
+						(opts['gen_what'][-1],num,pos,t_addrs))
 
-		if not 'print_addresses_only' in opts:
-			d['sec'] = sec
-			d['wif'] = wif
+			# Secret key is double sha256 of seed hash round /num/
+			sec = sha256(sha256(seed).digest()).hexdigest()
+			wif = numtowif(int(sec,16))
 
-		if not 'no_addresses' in opts:
-			if keyconv:
-				p = Popen([keyconv, wif], stdout=PIPE)
-				addr = dict([j.split() for j in \
-						p.stdout.readlines()])['Address:']
-			else:
-				addr = privnum2addr(int(sec,16))
+			if 'addrs' in opts['gen_what']: addr = \
+				Popen([keyconv, wif], stdout=PIPE).stdout.readline().split()[1] \
+				if keyconv else privnum2addr(int(sec,16))
 
-			d['addr'] = addr
+			out.append(eval("addrinfo("+addrinfo_args+")"))
 
-		out.append(d)
+	except KeyboardInterrupt:
+		msg("\nUser interrupt")
+		sys.exit(1)
 
-	w = opts['gen_what']
-	if t_addrs == 1:
-		import re
-		w = re.sub('e*s$','',w)
+	w = 'key' if 'keys' in opts['gen_what'] else 'address'
+	if t_addrs != 1: w = w+"s" if w == 'key' else w+"es"
 
 	qmsg("\rGenerated %s %s%s"%(t_addrs, w, " "*15))
 
 	return out
 
-def generate_keys(seed, addrnums):
-	o = {'no_addresses': True, 'gen_what': "keys"}
-	return generate_addrs(seed, addrnums, o)
-
 
 def format_addr_data(addr_data, addr_data_chksum, seed_id, addr_idxs, opts):
 
 	if 'flat_list' in opts:
-		return "\n\n".join(["# %s:%s %s\n%s" % (seed_id,d['num'],d['addr'],d['wif'])
+		return "\n\n".join(["# {}:{d.num} {d.addr}\n{d.wif}".format(seed_id,d=d)
 			for d in addr_data])+"\n\n"
 
-	start = addr_data[0]['num']
-	end   = addr_data[-1]['num']
-	fs = "  %-{}s  %s".format(len(str(end)))
-	out = []
+	fs = "  {:<%s}  {}" % len(str(addr_data[-1].num))
 
-	if not 'no_addresses' in opts:
-		if not 'stdout' in opts: out.append(addrmsgs['addrfile_header'] + "\n")
+	if 'addrs' not in opts['gen_what']: out = []
+	else:
+		out = [] if 'stdout' in opts else [addrmsgs['addrfile_header']+"\n"]
 		out.append("# Address data checksum for {}[{}]: {}".format(
 					seed_id, fmt_addr_idxs(addr_idxs), addr_data_chksum))
 		out.append("# Record this value to a secure location\n")
@@ -145,14 +131,16 @@ def format_addr_data(addr_data, addr_data_chksum, seed_id, addr_idxs, opts):
 	out.append("%s {" % seed_id.upper())
 
 	for d in addr_data:
-		if 'no_addresses' in opts:
-			out.append(fs % (d['num'], "wif: " + d['wif']))
+		if 'addrs' in opts['gen_what']:  # First line with number
+			out.append(fs.format(d.num, d.addr))
 		else:
-			out.append(fs % (d['num'], d['addr']))
-		if 'b16' in opts:
-			out.append(fs % ("", "hex: " + d['sec']))
-		if 'print_secret' in opts and not 'no_addresses' in opts:
-			out.append(fs % ("", "wif: " + d['wif']))
+			out.append(fs.format(d.num, "wif: "+d.wif))
+
+		if 'keys' in opts['gen_what']:   # Subsequent lines
+			if 'b16' in opts:
+				out.append(fs.format("", "hex: "+d.sec))
+			if 'addrs' in opts['gen_what']:
+				out.append(fs.format("", "wif: "+d.wif))
 
 	out.append("}")
 

+ 0 - 27
mmgen/tests/addr.py

@@ -1,27 +0,0 @@
-#!/usr/bin/env python
-#
-# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C) 2013 by 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/>.
-"""
-addr.py:  Test suite for mmgen.addr module
-"""
-
-from mmgen.addr import *
-
-tests = "none",
-
-if (len(sys.argv) == 1):
-	print "Available tests: %s" % " ".join(tests)

+ 0 - 24
mmgen/tests/mn_electrum.py

@@ -1,24 +0,0 @@
-#!/usr/bin/env python
-#
-# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C) 2013 by 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/>.
-"""
-mn_electrum.py:  Test suite for mmgen.mn_electrum module
-"""
-
-from mmgen.mn_electrum import *
-
-print electrum_words.strip()

+ 0 - 24
mmgen/tests/mn_tirosh.py

@@ -1,24 +0,0 @@
-#!/usr/bin/env python
-#
-# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C) 2013 by 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/>.
-"""
-mn_tirosh.py:  Test suite for mmgen.mn_tirosh module
-"""
-
-from mmgen.mn_tirosh import *
-
-print tirosh_words.strip()

+ 0 - 27
mmgen/tests/util.py

@@ -1,27 +0,0 @@
-#!/usr/bin/env python
-#
-# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C) 2013 by 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/>.
-"""
-util.py:  Test suite for mmgen.util module
-"""
-
-from mmgen.util import *
-
-tests = "none",
-
-if (len(sys.argv) == 1):
-	print "Available tests: %s" % " ".join(tests)

+ 0 - 27
mmgen/tests/walletgen.py

@@ -1,27 +0,0 @@
-#!/usr/bin/env python
-#
-# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C) 2013 by 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/>.
-"""
-walletgen.py:  Test suite for mmgen.walletgen module
-"""
-
-from mmgen.walletgen import *
-
-tests = "none",
-
-if (len(sys.argv) == 1):
-	print "Available tests: %s" % " ".join(tests)

+ 12 - 7
mmgen/tool.py

@@ -424,8 +424,10 @@ def encrypt(infile,outfile="",hash_preset=''):
 	enc_d = encrypt_data(sha256(nonce+d).digest() + nonce + d, key,
 				int(ba.hexlify(iv),16))
 	if outfile == '-':  sys.stdout.write(salt+iv+enc_d)
-	else: write_to_file((outfile or infile+"."+g.mmenc_ext),
-			salt+iv+enc_d,opts,"encrypted data",True,True)
+	else:
+		if not outfile:
+			outfile = os.path.basename(infile) + "." + g.mmenc_ext
+		write_to_file(outfile, salt+iv+enc_d, opts,"encrypted data",True,True)
 
 def decrypt(infile,outfile="",hash_preset=''):
 	d = get_data_from_file(infile,"encrypted data")
@@ -439,12 +441,15 @@ def decrypt(infile,outfile="",hash_preset=''):
 	from hashlib import sha256
 	if dec_d[:sha256_len] == sha256(dec_d[sha256_len:]).digest():
 		out = dec_d[sha256_len+nonce_len:]
-		if outfile == '-': sys.stdout.write(out)
+		if outfile == '-':  sys.stdout.write(out)
 		else:
-			import re
-			of = re.sub(r'\.%s$'%g.mmenc_ext,r'',infile)
-			if of == infile: of = infile+".dec"
-			write_to_file((outfile or of),out,opts,"decrypted data",True,True)
+			if not outfile:
+				outfile = os.path.basename(infile)
+				if outfile[-len(g.mmenc_ext)-1:] == "."+g.mmenc_ext:
+					outfile = outfile[:-len(g.mmenc_ext)-1]
+				else:
+					outfile = outfile + ".dec"
+			write_to_file(outfile, out, opts,"decrypted data",True,True)
 	else:
 		msg("Incorrect passphrase or hash preset")
 

+ 5 - 5
mmgen/tx.py

@@ -628,13 +628,13 @@ def get_keys_for_mmgen_addrs(mmgen_addrs,infiles,saved_seeds,opts,gen_pairs=Fals
 		seed = get_seed_for_seed_id(seed_id,infiles,saved_seeds,opts)
 
 		addr_ids = [int(i[9:]) for i in mmgen_addrs if i[:8] == seed_id]
-		from mmgen.addr import generate_keys,generate_addrs
+		from mmgen.addr import generate_addrs
 		if gen_pairs:
-			o = {"gen_what":"addresses"}
-			ret += [("%s:%s" % (seed_id,i['num']),i['addr'])
-				for i in generate_addrs(seed, addr_ids, o)]
+			ret += [("{}:{}".format(seed_id,i.num),i.addr)
+				for i in generate_addrs(seed, addr_ids, {'gen_what':("addrs")})]
 		else:
-			ret += [i['wif'] for i in generate_keys(seed, addr_ids)]
+			ret += [i.wif for i in generate_addrs(
+						seed,addr_ids,{'gen_what':("keys")})]
 
 	return ret
 

+ 18 - 7
mmgen/util.py

@@ -256,25 +256,36 @@ def make_iv_chksum(s):
 	return sha256(s).hexdigest()[:8].upper()
 
 
-def check_infile(f):
+def check_file_type_and_access(fname,ftype):
 
 	import os, stat
 
-	try: mode = os.stat(f).st_mode
+	typ2,tdesc2,access,action  = (stat.S_ISLNK,"symbolic link",os.R_OK,"read")\
+	if ftype == "input file" else (stat.S_ISBLK,"block device",os.W_OK,"writ")
+
+	if ftype == "directory":
+		typ1,typ2,tdesc = stat.S_ISDIR,stat.S_ISDIR,"directory"
+	else:
+		typ1,tdesc = stat.S_ISREG,"regular file or "+tdesc2
+
+	try: mode = os.stat(fname).st_mode
 	except:
-		msg("Unable to stat requested input file '%s'" % f)
+		msg("Unable to stat requested %s '%s'" % (ftype,fname))
 		sys.exit(1)
 
-	if not stat.S_ISREG(mode) or stat.S_ISLNK(mode):
-		msg("Requested input file '%s' is not a file" % f)
+	if not (typ1(mode) or typ2(mode)):
+		msg("Requested %s '%s' is not a %s" % (ftype,fname,tdesc))
 		sys.exit(1)
 
-	if not os.access(f, os.R_OK):
-		msg("Requested input file '%s' is unreadable by you" % f)
+	if not os.access(fname, access):
+		msg("Requested %s '%s' is un%sable by you" % (ftype,fname,action))
 		sys.exit(1)
 
 	return True
 
+def check_infile(f):  return check_file_type_and_access(f,"input file")
+def check_outfile(f): return check_file_type_and_access(f,"output file")
+def check_outdir(f):  return check_file_type_and_access(f,"directory")
 
 def _validate_addr_num(n):
 

+ 0 - 27
mmgen/walletgen.py

@@ -1,27 +0,0 @@
-#!/usr/bin/env python
-#
-# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C) 2013-2014 by 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/>.
-"""
-walletgen.py:  Routines used for seed generation and wallet creation
-"""
-
-import sys
-import mmgen.config as g
-from mmgen.util import msg, msg_r, qmsg, qmsg_r, get_char, prompt_and_get_char
-from binascii import hexlify
-
-