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.addr import *
 from mmgen.tx import make_addr_data_chksum
 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 = {
 help_data = {
 	'prog_name': g.prog_name,
 	'prog_name': g.prog_name,
 	'desc': """Generate a list or range of {} from an {g.proj_name} wallet,
 	'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>",
 	'usage':"[opts] [infile] <address list>",
 	'options': """
 	'options': """
 -h, --help              Print this help message{}
 -h, --help              Print this help message{}
@@ -73,9 +73,9 @@ help_data = {
 "\n                        '{}-txsign'".format(g.proj_name.lower()),
 "\n                        '{}-txsign'".format(g.proj_name.lower()),
 "\n-x, --b16               Print secret keys in hexadecimal too"
 "\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]),
 		seed_lens=", ".join([str(i) for i in g.seed_lens]),
-		what=gen_what, g=g
+		what=what, g=g
 ),
 ),
 	'notes': """
 	'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
 the same 'l' and 'p' parameters to '--from-brain' must be used in all future
 invocations with that passphrase
 invocations with that passphrase
 """.format("\n\nBy default, both addresses and secret keys are generated."
 """.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)
 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:
 if 'from_incog_hex' in opts or 'from_incog_hidden' in opts:
 	opts['from_incog'] = True
 	opts['from_incog'] = True
 
 
-opts['gen_what'] = gen_what
-
 if g.debug: show_opts_and_cmd_args(opts,cmd_args)
 if g.debug: show_opts_and_cmd_args(opts,cmd_args)
 
 
 if len(cmd_args) == 1 and (
 if len(cmd_args) == 1 and (
@@ -140,22 +138,26 @@ if not addr_idxs: sys.exit(2)
 do_license_msg()
 do_license_msg()
 
 
 # Interact with user:
 # 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')
 	confirm_or_exit(cmessages['unencrypted_secret_keys'], 'continue')
 
 
 # Generate data:
 # 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    = get_seed_retry(infile,opts)
 seed_id = make_chksum_8(seed)
 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        = 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_str    = format_addr_data(
 		addr_data, addr_data_chksum, seed_id, addr_idxs, opts)
 		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:
 # Output data:
 if 'stdout' in opts:
 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():
 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:
 else:
 	confirm = False if g.quiet else True
 	confirm = False if g.quiet else True
 	outfile = outfile_base + "." + (
 	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))
 	msg("Checksum for address data {}: {}".format(outfile_base,addr_data_chksum))
 	if 'save_checksum' in opts:
 	if 'save_checksum' in opts:
 		a = "address data checksum"
 		a = "address data checksum"

+ 1 - 0
mmgen-passchg

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

+ 2 - 0
mmgen-pywallet

@@ -86,6 +86,8 @@ help_data = {
 }
 }
 
 
 opts,cmd_args = parse_opts(sys.argv,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:
 if len(cmd_args) == 1:
 	from mmgen.util import check_infile
 	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
 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.config as g
 import mmgen.tool as tool
 import mmgen.tool as tool
 from mmgen.Opts import *
 from mmgen.Opts import *
-from mmgen.util import pretty_hexdump
 
 
 help_data = {
 help_data = {
 	'prog_name': g.prog_name,
 	'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)
 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 "quiet" in opts: g.quiet = True
 if 'from_incog_hex' in opts or 'from_incog_hidden' in opts:
 if 'from_incog_hex' in opts or 'from_incog_hidden' in opts:
 	opts['from_incog'] = True
 	opts['from_incog'] = True

+ 0 - 1
mmgen-walletgen

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

+ 98 - 181
mmgen/Opts.py

@@ -16,111 +16,100 @@
 # You should have received a copy of the GNU General Public License
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 
-import sys, getopt
+import sys
 import mmgen.config as g
 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 """
 	print """
 '{g.prog_name}' version {g.version}.  Part of the {g.proj_name} suite.
 '{g.prog_name}' version {g.version}.  Part of the {g.proj_name} suite.
 Copyright (C) {g.Cdates} by {g.author} {g.email}.
 Copyright (C) {g.Cdates} by {g.author} {g.email}.
 """.format(g=g).strip()
 """.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()
 		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[]
 	# check_opts() doesn't touch opts[]
 	if not check_opts(opts,long_opts): sys.exit(1)
 	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:
 	for v in g.cl_override_vars:
 		if v in opts: typeconvert_override_var(opts,v)
 		if v in opts: typeconvert_override_var(opts,v)
 		else: opts[v] = eval("g."+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):
 def show_opts_and_cmd_args(opts,cmd_args):
 	print "Processed options: %s" % repr(opts)
 	print "Processed options: %s" % repr(opts)
 	print "Cmd args:          %s" % repr(cmd_args)
 	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():
 	for opt,val in opts.items():
 
 
@@ -133,121 +122,49 @@ def check_opts(opts,long_opts):
 			continue
 			continue
 
 
 		if opt == 'outdir':
 		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':
 		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
 				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:
 			except:
-				msg("'%s': invalid %s" % (val,what))
+				msg("ERROR: label contains a non-ASCII symbol")
 				return False
 				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':
 			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':
 		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':
 		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':
 		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':
 		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:
 		else:
 			if g.debug: print "check_opts(): No test for opt '%s'" % opt
 			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
 # MMGen address file
 #
 #
 # This file is editable.
 # 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
 # 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.
 # address, and it will be appended to the bitcoind wallet label upon import.
 # The label may contain any printable ASCII symbol.
 # 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():
 def test_for_keyconv():
-	"""
-	Test for the presence of 'keyconv' utility on system
-	"""
 
 
 	from subprocess import Popen, PIPE
 	from subprocess import Popen, PIPE
 	try:
 	try:
 		p = Popen([g.keyconv_exec, '-h'], stdout=PIPE, stderr=PIPE)
 		p = Popen([g.keyconv_exec, '-h'], stdout=PIPE, stderr=PIPE)
 	except:
 	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
 		return False
-	else:
-		return True
+
+	return True
 
 
 
 
 def generate_addrs(seed, addrnums, opts):
 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:
 		if 'no_keyconv' in opts or test_for_keyconv() == False:
 			msg("Using (slow) internal ECDSA library for address generation")
 			msg("Using (slow) internal ECDSA library for address generation")
 			from mmgen.bitcoin import privnum2addr
 			from mmgen.bitcoin import privnum2addr
@@ -71,73 +68,62 @@ def generate_addrs(seed, addrnums, opts):
 			from subprocess import Popen, PIPE
 			from subprocess import Popen, PIPE
 			keyconv = "keyconv"
 			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))
 	qmsg("\rGenerated %s %s%s"%(t_addrs, w, " "*15))
 
 
 	return out
 	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):
 def format_addr_data(addr_data, addr_data_chksum, seed_id, addr_idxs, opts):
 
 
 	if 'flat_list' in 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"
 			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(
 		out.append("# Address data checksum for {}[{}]: {}".format(
 					seed_id, fmt_addr_idxs(addr_idxs), addr_data_chksum))
 					seed_id, fmt_addr_idxs(addr_idxs), addr_data_chksum))
 		out.append("# Record this value to a secure location\n")
 		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())
 	out.append("%s {" % seed_id.upper())
 
 
 	for d in addr_data:
 	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:
 		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("}")
 	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,
 	enc_d = encrypt_data(sha256(nonce+d).digest() + nonce + d, key,
 				int(ba.hexlify(iv),16))
 				int(ba.hexlify(iv),16))
 	if outfile == '-':  sys.stdout.write(salt+iv+enc_d)
 	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=''):
 def decrypt(infile,outfile="",hash_preset=''):
 	d = get_data_from_file(infile,"encrypted data")
 	d = get_data_from_file(infile,"encrypted data")
@@ -439,12 +441,15 @@ def decrypt(infile,outfile="",hash_preset=''):
 	from hashlib import sha256
 	from hashlib import sha256
 	if dec_d[:sha256_len] == sha256(dec_d[sha256_len:]).digest():
 	if dec_d[:sha256_len] == sha256(dec_d[sha256_len:]).digest():
 		out = dec_d[sha256_len+nonce_len:]
 		out = dec_d[sha256_len+nonce_len:]
-		if outfile == '-': sys.stdout.write(out)
+		if outfile == '-':  sys.stdout.write(out)
 		else:
 		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:
 	else:
 		msg("Incorrect passphrase or hash preset")
 		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)
 		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]
 		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:
 		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:
 		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
 	return ret
 
 

+ 18 - 7
mmgen/util.py

@@ -256,25 +256,36 @@ def make_iv_chksum(s):
 	return sha256(s).hexdigest()[:8].upper()
 	return sha256(s).hexdigest()[:8].upper()
 
 
 
 
-def check_infile(f):
+def check_file_type_and_access(fname,ftype):
 
 
 	import os, stat
 	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:
 	except:
-		msg("Unable to stat requested input file '%s'" % f)
+		msg("Unable to stat requested %s '%s'" % (ftype,fname))
 		sys.exit(1)
 		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)
 		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)
 		sys.exit(1)
 
 
 	return True
 	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):
 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
-
-