Browse Source

Windows port fully functional; fixes, enhancements

philemon 11 years ago
parent
commit
dcab2602b6
19 changed files with 477 additions and 358 deletions
  1. 17 19
      mmgen-addrgen
  2. 3 3
      mmgen-addrimport
  3. 9 8
      mmgen-passchg
  4. 18 24
      mmgen-pywallet
  5. 17 8
      mmgen-txcreate
  6. 1 1
      mmgen-txsend
  7. 118 100
      mmgen-txsign
  8. 3 2
      mmgen-walletchk
  9. 24 12
      mmgen-walletgen
  10. 11 9
      mmgen/addr.py
  11. 4 0
      mmgen/config.py
  12. 10 50
      mmgen/license.py
  13. 3 3
      mmgen/mnemonic.py
  14. 6 1
      mmgen/rpc/connection.py
  15. 2 1
      mmgen/rpc/proxy.py
  16. 35 14
      mmgen/tx.py
  17. 182 77
      mmgen/utils.py
  18. 13 25
      mmgen/walletgen.py
  19. 1 1
      setup.py

+ 17 - 19
mmgen-addrgen

@@ -44,20 +44,22 @@ else: extra_help_data = ("","","")
 help_data = {
 	'prog_name': sys.argv[0].split("/")[-1],
 	'desc': """Generate a list or range of {} from a {} wallet,
-	              mnemonic, seed or password""".format(gen_what,proj_name),
+                  mnemonic, seed or password""".format(gen_what,proj_name),
 	'usage':"[opts] [infile] <address list>",
 	'options': """
 -h, --help               Print this help message{}
 -d, --outdir          d  Specify an alternate directory 'd' for output
--e, --echo-passphrase    Display passphrase or mnemonic on screen upon entry
+-e, --echo-passphrase    Echo passphrase or mnemonic to screen upon entry
+-H, --show-hash-presets  Show information on available hash presets
 -K, --no-keyconv         Use internal libraries for address generation
                          instead of 'keyconv'
 -l, --seed-len        N  Length of seed.  Options: {}
                          (default: {})
 -p, --hash-preset     p  Use scrypt.hash() parameters from preset 'p'
                          when hashing password (default: '{}')
--P, --show-hash-presets  Show information on available hash presets
--q, --quiet              Suppress warnings; overwrite files without asking
+-P, --passwd-file     f  Get passphrase from file 'f'
+-q, --quiet              Suppress warnings; overwrite files without
+                         prompting
 -S, --stdout             Print {W} to stdout
 -v, --verbose            Produce more verbose output{}
 
@@ -90,9 +92,9 @@ the 'p' parameter of the '--from-brain' option
 The '--from-brain' option also requires the user to specify a seed length
 (the 'l' parameter)
 
-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
+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(
 		extra_help_data[0],
 		", ".join([str(i) for i in seed_lens]),
@@ -105,16 +107,16 @@ invocations with that passphrase
 	)
 }
 
-so = "h","A","d:","e","K","l:","p:","P","q","S","v","x","b:","m","s"
-lo = "help","no_addresses","outdir=","echo_passphrase","no_keyconv",\
-		"seed_len=","hash_preset=","show_hash_presets","quiet","stdout",\
-		"verbose","b16","from_brain=","from_mnemonic","from_seed"
+short_opts = ["h","A","d:","e","H","K","l:","p:","P:","q","S",
+				"v","x","b:","m","s"]
+long_opts = ["help","no_addresses","outdir=","echo_passphrase",
+			"show_hash_presets","no_keyconv","seed_len=","hash_preset=",
+			"passwd_file=","quiet","stdout","verbose","b16","from_brain=",
+			"from_mnemonic","from_seed"]
 
 if invoked_as == "addrgen":
-	short_opts = so[0:1] + so[2:10] + so[11:]
-	long_opts  = lo[0:1] + lo[2:10] + lo[11:]
-else:
-	short_opts,long_opts = so,lo
+	for i in "A","x":              short_opts.remove(i)
+	for i in "no_addresses","b16": long_opts.remove(i)
 
 opts,cmd_args = process_opts(sys.argv,help_data,"".join(short_opts),long_opts)
 
@@ -132,10 +134,6 @@ set_if_unset_and_typeconvert(opts,(
 # Exits on invalid input
 check_opts(opts,('hash_preset','seed_len','outdir','from_brain'))
 
-if debug:
-	print "Processed options:     %s" % repr(opts)
-	print "Cmd args:              %s" % repr(cmd_args)
-
 if   len(cmd_args) == 1 and (
 			'from_mnemonic' in opts or
 			'from_brain' in opts or

+ 3 - 3
mmgen-addrimport

@@ -33,9 +33,9 @@ help_data = {
                      watching wallet""",
 	'usage':"[opts] [mmgen address file]",
 	'options': """
--h, --help         Print this help message
--l, --addrlist  f  Import the non-mmgen Bitcoin addresses listed in file 'f'
--q, --quiet        Suppress warnings
+-h, --help        Print this help message
+-l, --addrlist f  Import the non-mmgen Bitcoin addresses listed in file 'f'
+-q, --quiet       Suppress warnings
 """
 }
 

+ 9 - 8
mmgen-passchg

@@ -33,12 +33,13 @@ help_data = {
 	'options': """
 -h, --help                  Print this help message
 -d, --outdir             d  Specify an alternate directory 'd' for output
+-H, --show-hash-presets     Show information on available hash presets
 -k, --keep-old-passphrase   Keep old passphrase (use when changing hash
                             strength or label only)
 -L, --label              l  Change the wallet's label to 'l'
 -p, --hash-preset        p  Change scrypt.hash() parameters to preset 'p'
                             (default: '{}')
--P, --show-hash-presets     Show information on available hash presets
+-P, --passwd-file        f  Get new passphrase from file 'f'
 -v, --verbose               Produce more verbose output
 
 NOTE: The key ID will change if either the passphrase or hash preset
@@ -46,9 +47,9 @@ NOTE: The key ID will change if either the passphrase or hash preset
 """.format(hash_preset)
 }
 
-short_opts = "hd:kL:p:Pv"
-long_opts  = "help","outdir=","keep_old_passphrase","label=","hash_preset=",\
-				"show_hash_presets","verbose"
+short_opts = "hd:HkL:p:P:v"
+long_opts  = "help","outdir=","show_hash_presets","keep_old_passphrase",\
+			  "label=","hash_preset=","passwd_file=","verbose"
 
 opts,cmd_args = Opts.process_opts(sys.argv,help_data,short_opts,long_opts)
 
@@ -65,11 +66,12 @@ infile = cmd_args[0]
 # Old key:
 label,metadata,hash_preset,salt,enc_seed = get_data_from_wallet(infile,opts)
 seed_id,key_id = metadata[:2]
-oldp = "" if 'keep_old_passphrase' in opts else "old "
 
 # Repeat on incorrect pw entry
+prompt = "Enter %spassphrase: " % (""
+		if 'keep_old_passphrase' in opts else "old ")
 while True:
-	passwd = " ".join(get_words("","",("Enter %spassphrase: " % oldp),opts))
+	passwd = get_mmgen_passphrase(prompt,{})
 	key = make_key(passwd, salt, hash_preset)
 	seed = decrypt_seed(enc_seed, key, seed_id, key_id)
 	if seed: break
@@ -97,8 +99,7 @@ else:
 if 'keep_old_passphrase' in opts:
 	msg("Keeping old passphrase by user request")
 else:
-	new_passwd = get_first_passphrase_from_user(
-			"new passphrase", {'quiet': True })
+	new_passwd = get_new_passphrase("new passphrase", opts)
 
 	if new_passwd == passwd:
 		msg("Passphrase is unchanged")

+ 18 - 24
mmgen-pywallet

@@ -46,7 +46,7 @@ mmgen-pywallet: Dump contents of a bitcoind wallet to file
 from mmgen.Opts import *
 from mmgen.utils import msg
 from bsddb.db import *
-import os, sys, time
+import sys, time
 import json
 import logging
 import struct
@@ -75,17 +75,20 @@ help_data = {
 	'options': """
 -h, --help             Print this help message
 -d, --outdir        d  Specify an alternate directory 'd' for output
+-e, --echo-passphrase  Display passphrase on screen upon entry
 -j, --json             Dump wallet in json format
 -k, --keys             Dump all private keys (flat list)
 -a, --addrs            Dump all addresses (flat list)
 -K, --keysforaddrs  f  Dump private keys for addresses listed in file 'f'
--q, --quiet            Suppress warnings; overwrite files without asking
+-P, --passwd-file   f  Get passphrase from file 'f'
+-q, --quiet            Suppress warnings; overwrite files without prompting
 -S, --stdout           Dump to stdout rather than file
 """
 }
 
-short_opts = "hd:jkaK:qS"
-long_opts  = "help","outdir=","json","keys","addrs","keysforaddrs=","quiet","stdout"
+short_opts = "hd:ejkaK:P:qS"
+long_opts  = "help","outdir=","echo_passphrase","json","keys","addrs",\
+			  "keysforaddrs=","passwd_file=","quiet","stdout"
 
 opts,cmd_args = process_opts(sys.argv,help_data,short_opts,long_opts)
 
@@ -1572,8 +1575,8 @@ def read_wallet(json_db, db_env, db_file, print_wallet, print_wallet_transaction
 
 			if password == None and \
 				('json' in opts or 'keysforaddrs' in opts or 'keys' in opts):
-				from mmgen.utils import my_getpass
-				password = my_getpass("Enter password: ")
+				from mmgen.utils import get_bitcoind_passphrase
+				password = get_bitcoind_passphrase("Enter password: ",opts)
 
 			if password != None:
 				global crypter
@@ -1646,21 +1649,14 @@ def read_wallet(json_db, db_env, db_file, print_wallet, print_wallet_transaction
 	del(json_db['names'])
 
 
-def parse_wallet_file_arg(path):
-
-	s = path.rfind("/")
-
-	if path[0] == '/':
-		if s == 0: return "/", path
-		else:      return path[0:s], path[s+1:]
-	else:
-		curdir = os.path.abspath(".")
-		if s == -1: return curdir,path
-		else:       return curdir + "/" + path[0:s], path[s+1:]
+# Non-portable.  For Windows, works only if supplied filename is in current dir
 
 # main()
 
-db_dir,db_file = parse_wallet_file_arg(cmd_args[0])
+import os.path
+infile = os.path.abspath(cmd_args[0])
+db_dir,db_file = os.path.dirname(infile),os.path.basename(infile)
+
 #	print "[%s] [%s]" % (db_dir,db_file)
 
 db_env = create_env(db_dir)
@@ -1673,18 +1669,16 @@ if json_db.get('minversion') > max_version:
 
 wallet_addrs = [i['addr'] for i in json_db['keys']]
 
-data,ext,what = [],"",""
-
 if 'json' in opts:
-	data = [ json.dumps(json_db, sort_keys=True, indent=4) ]
+	data = [json.dumps(json_db, sort_keys=True, indent=4)]
 	ext,what = "json","json dump"
 
 elif 'keys' in opts:
-	for i in json_db['keys']: data.append(i['sec'])
+	data = sorted([i['sec'] for i in json_db['keys']])
 	ext,what = "keys","private keys"
 
 elif 'addrs' in opts:
-	for i in json_db['keys']: data.append(i['addr'])
+	data = sorted([i['addr'] for i in json_db['keys']])
 	ext,what = "addrs","addresses"
 
 elif 'keysforaddrs' in opts:
@@ -1696,7 +1690,7 @@ elif 'keysforaddrs' in opts:
 			data.append(json_db['keys'][idx]['sec'])
 		except:
 			msg("WARNING: Address '%s' not found" % addr)
-	ext,what = "keys","private keys"
+	data,ext,what = sorted(data),"keys","private keys"
 
 len_arg = "%s" % len(wallet_addrs) \
    if len(data) == len(wallet_addrs) or ext == "json" \

+ 17 - 8
mmgen-txcreate

@@ -26,7 +26,7 @@ from mmgen.Opts import *
 from mmgen.license import *
 from mmgen.config import *
 from mmgen.tx import *
-from mmgen.utils import check_opts, msg, user_confirm
+from mmgen.utils import check_opts, msg, msg_r, user_confirm
 from decimal import Decimal
 
 prog_name = sys.argv[0].split("/")[-1]
@@ -40,12 +40,13 @@ help_data = {
 -d, --outdir           d  Specify an alternate directory 'd' for output
 -e, --echo-passphrase     Print passphrase to screen when typing it
 -i, --info                Display unspent outputs and exit
--q, --quiet               Suppress warnings; overwrite files without asking
+-q, --quiet               Suppress warnings; overwrite files without
+                          prompting
 
 Outputs to spend are chosen by the user via a menu.
 
-Ages of transactions are approximate based on an estimated block discovery
-time of %s minutes.
+Ages of transactions are approximate based on an average block discovery
+interval of %s minutes.
 """ % mins_per_block
 }
 
@@ -88,21 +89,29 @@ if not 'quiet' in opts and not 'info' in opts: do_license_msg()
 # End test
 
 us = c.listunspent()
+
+if not us:
+	msg_r("""
+No spendable outputs found!  Import addresses with balances into your
+watch-only wallet using 'mmgen-addrimport' and then re-run this program.
+""")
+	sys.exit(2)
+
 # write_to_file("listunspent.json",repr(us))
 # sys.exit()
 unspent = sort_and_view(us)
 
 total = trim_exponent(sum([i.amount for i in unspent]))
 
-msg("Total unspent:   %s BTC" % total)
+msg("Total unspent: %s BTC (%s outputs)" % (total, len(unspent)))
 if 'info' in opts: sys.exit(0)
 
 send_amt = sum(tx_out.values())
-msg("Total amount to spend: %s BTC\n%s unspent outputs total" %
-		(send_amt, len(unspent)))
+msg("Total amount to spend: %s BTC" % send_amt)
 
 while True:
-	sel_nums = select_outputs(unspent,"Choose the outputs to spend: ")
+	sel_nums = select_outputs(unspent,
+			"Enter a range or space-separated list of outputs to spend: ")
 	msg("Selected outputs: %s" % " ".join(str(i) for i in sel_nums))
 	sel_unspent = [unspent[i-1] for i in sel_nums]
 

+ 1 - 1
mmgen-txsend

@@ -36,7 +36,7 @@ help_data = {
 	'options': """
 -h, --help          Print this help message
 -d, --outdir     d  Specify an alternate directory 'd' for output
--q, --quiet         Suppress warnings; overwrite files without asking
+-q, --quiet         Suppress warnings; overwrite files without prompting
 """
 }
 

+ 118 - 100
mmgen-txsign

@@ -36,16 +36,19 @@ help_data = {
 -h, --help               Print this help message
 -d, --outdir          d  Specify an alternate directory 'd' for output
 -e, --echo-passphrase    Print passphrase to screen when typing it
--f, --force-wallet-dat   Force the use of wallet.dat as a key source
 -i, --info               Display information about the transaction and exit
+-I, --tx_id              Display transaction ID and exit
 -k, --keys-from-file  k  Provide additional key data from file 'k'
--q, --quiet              Suppress warnings; overwrite files without asking
+-P, --passwd-file     f  Get passphrase from file 'f'
+-q, --quiet              Suppress warnings; overwrite files without
+                         prompting
 
 -b, --from-brain     l,p Generate keys from a user-created password,
                          i.e. a "brainwallet", using seed length 'l' and
                          hash preset 'p' (comma-separated)
 -m, --from-mnemonic      Generate keys from an electrum-like mnemonic
 -s, --from-seed          Generate keys from a seed in .{} format
+-w, --use-wallet-dat     Use the keys in the bitcoind wallet.dat file too
 
 Transactions with either mmgen or non-mmgen input addresses may be signed.
 For non-mmgen inputs, the bitcoind wallet.dat is used as the key source.
@@ -70,9 +73,10 @@ Seed data supplied in files must have the following extensions:
 """.format(seed_ext,wallet_ext,seed_ext,mn_ext,brain_ext)
 }
 
-short_opts = "hd:efik:qb:ms"
-long_opts  = "help","outdir=","echo_passphrase","force_wallet_dat","info",\
-		  "keys_from_file=","quiet","from_brain=","from_mnemonic","from_seed"
+short_opts = "hd:eiIk:P:qb:msw"
+long_opts  = "help","outdir=","echo_passphrase","info","tx_id",\
+			  "keys_from_file=","passwd_file=","quiet","from_brain=",\
+			  "from_mnemonic","from_seed","use_wallet_dat"
 
 opts,infiles = process_opts(sys.argv,help_data,short_opts,long_opts)
 
@@ -83,62 +87,18 @@ if 'keys_from_file' in opts: check_infile(opts['keys_from_file'])
 if not infiles: usage(help_data)
 for i in infiles: check_infile(i)
 
-# Begin execution
-
-c = connect_to_bitcoind()
 
-tx_file = infiles.pop(0)
-tx_data = get_lines_from_file(tx_file,"transaction data")
-
-metadata,tx_hex,sig_data,inputs_data = parse_tx_data(tx_data,tx_file)
+def get_keys_for_mmgen_addrs(mmgen_addrs,infiles):
 
-if 'info' in opts:
-	view_tx_data(c,inputs_data,tx_hex,metadata)
-	sys.exit(0)
-
-if not 'quiet' in opts: do_license_msg()
-
-msg("Successfully opened transaction file '%s'" % tx_file)
-
-if user_confirm("View transaction data? ",default_yes=False):
-	view_tx_data(c,inputs_data,tx_hex,metadata)
-
-# Are inputs mmgen addresses?
-mmgen_addrs,other_addrs,keys = [],[],[]
-
-for i in inputs_data:
-	if verify_mmgen_label(i['account']):
-		mmgen_addrs.append(i)
-	else:
-		other_addrs.append(i)
-
-if mmgen_addrs and not 'force_wallet_dat' in opts:
-	# Check that all the seed IDs are the same:
 	seed_ids = list(set([i['account'][:8] for i in mmgen_addrs]))
-	ext_data = (
-		(wallet_ext, {}),
-		(mn_ext,     {"from_mnemonic":True}),
-		(seed_ext,   {"from_seed":    True}),
-		(brain_ext,  opts)
-	)
+	seed_ids_save = seed_ids[0:]
+	keys = []
+
 	while seed_ids:
 		infile = False
 		if infiles:
 			infile = infiles.pop()
-			ext = infile.split(".")[-1]
-			for e,o in ext_data:
-				if e == ext:
-					if e == brain_ext:
-						if "from_brain" not in opts:
-							msg(
-				"'--from-brain' option must be specified for brainwallet file")
-							sys.exit(2)
-					seed = get_seed_retry(infile,o); break
-			else:
-				msg("Invalid file extension: '.%s'\nValid extensions: '.%s'" %
-					(ext,"' '.".join([i[0] for i in ext_data])))
-				sys.exit(2)
-
+			seed = get_seed(infile,opts)
 		elif "from_brain" in opts or "from_mnemonic" in opts or "from_seed" in opts:
 			msg("Need data for seed ID %s" % seed_ids[0])
 			seed = get_seed_retry("",opts)
@@ -158,65 +118,123 @@ if mmgen_addrs and not 'force_wallet_dat' in opts:
 			from mmgen.addr import generate_keys
 			keys += [i['wif'] for i in generate_keys(seed, seed_id_addrs)]
 		else:
-			msg("Seed source produced an invalid seed ID (%s)" % seed_id)
-			if infile:
-				msg("Invalid input file: %s" % infile)
-				sys.exit(2)
-
-	if other_addrs:
-		if 'keys_from_file' in opts:
-			keys += get_lines_from_file(opts['keys_from_file'],
-					"additional key data")
-		else:
-			msg("""
-A key file must be supplied (option '-f') for the following non-mmgen
-address%s: %s""" % (
-	"" if len(other_addrs) == 1 else "es",
+			if seed_id in seed_ids_save:
+				msg_r("Ignoring duplicate seed source")
+				if infile: msg(" '%s'" % infile)
+				else:      msg(" for ID %s" % seed_id)
+			else:
+				msg("Seed source produced an invalid seed ID (%s)" % seed_id)
+				if infile:
+					msg("Invalid input file: %s" % infile)
+					sys.exit(2)
+
+	return keys
+
+
+def sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys):
+
+	try:
+		sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
+	except:
+		from mmgen.rpc import exceptions
+		msg("Using keys in wallet.dat as per user request")
+		prompt = "Enter passphrase for bitcoind wallet: "
+		while True:
+			passwd = get_bitcoind_passphrase(prompt,opts)
+
+			try:
+				c.walletpassphrase(passwd, 9999)
+			except exceptions.WalletPassphraseIncorrect:
+				msg("Passphrase incorrect")
+			else:
+				msg("Passphrase OK"); break
+
+		sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
+
+		msg("Locking wallet")
+		try:
+			c.walletlock()
+		except:
+			msg("Failed to lock wallet")
+
+	return sig_tx
+
+
+def missing_keys_errormsg(other_addrs):
+	msg("""
+A key file (option '-f') or wallet.dat (option '-w') must be supplied
+for the following non-mmgen address%s: %s""" %
+	("" if len(other_addrs) == 1 else "es",
 	" ".join([i['address'] for i in other_addrs])
 	  ))
-			sys.exit(2)
 
-	sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
+# Begin execution
 
-elif 'keys_from_file' in opts:
-	keys = get_lines_from_file(opts['keys_from_file'],"key data")
+c = connect_to_bitcoind()
 
-	sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
-else:
-	prompt = "Enter passphrase for bitcoind wallet: "
-	if 'echo_passphrase' in opts:
-		password = my_raw_input(prompt)
-	else:
-		password = my_getpass(prompt)
+tx_file = infiles.pop(0)
+m = "" if 'tx_id' in opts else "transaction data"
+tx_data = get_lines_from_file(tx_file,m)
 
-	wallet_enc = True
-	from mmgen.rpc import exceptions
+metadata,tx_hex,sig_data,inputs_data = parse_tx_data(tx_data,tx_file)
 
-	try:
-		c.walletpassphrase(password, 9999)
-	except exceptions.WalletWrongEncState:
-		msg("Wallet is unencrypted")
-		wallet_enc = False
-	except exceptions.WalletPassphraseIncorrect:
-		msg("Passphrase incorrect")
-		sys.exit(3)
-	except exceptions.WalletAlreadyUnlocked:
-		msg("WARNING: Wallet already unlocked!")
+if 'tx_id' in opts:
+	msg(metadata[0])
+	sys.exit(0)
+
+if 'info' in opts:
+	view_tx_data(c,inputs_data,tx_hex,metadata)
+	sys.exit(0)
+
+if not 'quiet' in opts: do_license_msg()
+
+msg("Successfully opened transaction file '%s'" % tx_file)
+
+if user_confirm("View transaction data? ",default_yes=False):
+	view_tx_data(c,inputs_data,tx_hex,metadata)
+
+
+# Are inputs mmgen addresses?
+mmgen_addrs,other_addrs = [],[]
+
+for i in inputs_data:
+	if verify_mmgen_label(i['account']):
+		mmgen_addrs.append(i)
 	else:
-		msg("Passphrase OK")
+		other_addrs.append(i)
 
-	sig_tx = sign_transaction(c,tx_hex,sig_data)
 
-	if wallet_enc:
-		c.walletlock()
-		msg("Locking wallet")
+if 'keys_from_file' in opts:
+	keys = get_lines_from_file(opts['keys_from_file'],"key data")
+else:
+	keys = []
+
+if mmgen_addrs:
+	if other_addrs and not keys and not 'use_wallet_dat' in opts:
+		missing_keys_errormsg(other_addrs)
+		sys.exit(2)
+
+	keys += get_keys_for_mmgen_addrs(mmgen_addrs,infiles)
+
+	if 'use_wallet_dat' in opts:
+		sig_tx = sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys)
+	else:
+		sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
+elif other_addrs:
+	if 'use_wallet_dat' in opts:
+		sig_tx = sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys)
+	else:
+		if keys:
+			sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
+		else:
+			missing_keys_errormsg(other_addrs)
+			sys.exit(2)
 
 if sig_tx['complete']:
 	msg("Signing completed")
+	prompt = "Save signed transaction?"
+	if user_confirm(prompt,default_yes=True):
+		print_signed_tx_to_file(tx_hex,sig_tx['hex'],metadata,opts)
 else:
 	msg("Some keys were missing.  Transaction could not be signed.")
 	sys.exit(3)
-
-prompt = "Save signed transaction?"
-if user_confirm(prompt,default_yes=True):
-	print_signed_tx_to_file(tx_hex,sig_tx['hex'],metadata,opts)

+ 3 - 2
mmgen-walletchk

@@ -36,15 +36,16 @@ help_data = {
 -d, --outdir        d  Specify an alternate directory 'd' for output
 -e, --echo-passphrase  Print passphrase to screen when typing it
 -m, --export-mnemonic  Export the wallet's mnemonic to file
+-P, --passwd-file   f  Get passphrase from file 'f'
 -s, --export-seed      Export the wallet's seed to file
 -S, --stdout           Print seed or mnemonic data to standard output
 -v, --verbose          Produce more verbose output
 """
 }
 
-short_opts = "hd:emsSv"
+short_opts = "hd:emP:sSv"
 long_opts  = "help","outdir=","echo_passphrase","export_mnemonic",\
-				"export_seed","stdout","verbose"
+				"passwd_file=","export_seed","stdout","verbose"
 
 opts,cmd_args = process_opts(sys.argv,help_data,short_opts,long_opts)
 

+ 24 - 12
mmgen-walletgen

@@ -37,14 +37,16 @@ help_data = {
 -h, --help                 Print this help message
 -d, --outdir            d  Specify an alternate directory 'd' for output
 -e, --echo-passphrase      Print passphrase to screen when typing it
+-H, --show-hash-presets    Show information on available hash presets
 -l, --seed-len          n  Create seed of length 'n'. Options: {}
                            (default: {})
 -L, --label             l  Label to identify this wallet (32 chars max.
                            Allowed symbols: A-Z, a-z, 0-9, " ", "_", ".")
 -p, --hash-preset       p  Use scrypt.hash() parameters from preset 'p'
                            (default: '{}')
--P, --show-hash-presets    Show information on available hash presets
--q, --quiet                Suppress warnings; overwrite files without asking
+-P, --passwd-file       f  Get passphrase from file 'f'
+-q, --quiet                Suppress warnings; overwrite files without
+                           prompting
 -u, --usr-randlen       n  Get 'n' characters of randomness from the user
                            (default: {})
 
@@ -77,11 +79,11 @@ The '--from-brain' option also requires the user to specify a seed length
 (the 'l' parameter), which overrides both the default and any one given in
 the '--seed-len' option.
 
-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.
+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(
-		", ".join([str(i) for i in seed_lens]),
+		",".join([str(i) for i in seed_lens]),
 		seed_len,
 		hash_preset,
 		usr_randlen,
@@ -90,10 +92,10 @@ invocations with that passphrase.
 	)
 }
 
-short_opts = "hd:el:L:p:Pqu:b:ms"
-long_opts  = "help","outdir=","echo_passphrase","seed_len=","label=",\
-		"hash_preset=","show_hash_presets","quiet","usr_randlen=",\
-		"from_brain=","from_mnemonic","from_seed"
+short_opts = "hd:eHl:L:p:P:qu:b:ms"
+long_opts  = "help","outdir=","echo_passphrase","show_hash_presets","seed_len=",\
+			"label=","hash_preset=","passwd_file=","quiet","usr_randlen=",\
+			"from_brain=","from_mnemonic","from_seed"
 
 opts,cmd_args = process_opts(sys.argv,help_data,short_opts,long_opts)
 
@@ -131,6 +133,9 @@ if not 'quiet' in opts: do_license_msg()
 
 msg_r("Acquiring random data from your computer...")
 
+from time import sleep
+sleep(1)
+
 try:
 	from Crypto import Random
 	r = Random.new()
@@ -162,8 +167,15 @@ else:
 salt = os_rand_data[1] + usr_rand_data
 salt = sha256(salt).digest()[:salt_len]
 
-passwd = get_first_passphrase_from_user(
-		"{} wallet passphrase".format(proj_name), opts)
+if not 'quiet' in opts:
+	msg("""
+Now you must choose a passphrase to encrypt the seed with.  A key will be
+generated from your passphrase using a hash preset of '%s'.  Please note that
+no strength checking of passphrases is performed.  For an empty passphrase,
+just hit ENTER twice.
+""" % opts['hash_preset'])
+
+passwd = get_new_passphrase("{} wallet passphrase".format(proj_name), opts)
 
 key = make_key(passwd, salt, opts['hash_preset'])
 

+ 11 - 9
mmgen/addr.py

@@ -38,9 +38,9 @@ def test_for_keyconv():
 		p = Popen([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.
+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.
 
 """ % (keyconv_exec, keyconv_exec, "vanitygen"))
 		return False
@@ -152,12 +152,14 @@ def format_addr_data(addrlist, seed_chksum, opts):
 # MMGen address file
 #
 # This file is editable.
-# Everything following a hash symbol '#' is ignored.
-# A label may be added to the right of each address, and it will be
-# appended to the bitcoind wallet label upon import (max. {} characters,
-# allowed characters: A-Za-z0-9, plus '{}').
-""".format(max_wallet_addr_label_len,
-		"', '".join(wallet_addr_label_symbols)).strip()
+# Everything following a hash symbol '#' is a comment and ignored by {}.
+# 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 ASCII letters, numerals, and the symbols
+# '{}' and '{}'.
+""".format(proj_name.capitalize(),max_wallet_addr_label_len,
+		"', '".join(wallet_addr_label_symbols[0:-1]),
+		wallet_addr_label_symbols[-1]).strip()
 	data = []
 	if not 'stdout' in opts: data.append(header + "\n")
 	data.append("%s {" % seed_chksum.upper())

+ 4 - 0
mmgen/config.py

@@ -19,10 +19,14 @@
 config.py:  Constants and configuration options for the mmgen suite
 """
 proj_name     = "mmgen"
+
 wallet_ext    = "mmdat"
 seed_ext      = "mmseed"
 mn_ext        = "mmwords"
 brain_ext     = "mmbrain"
+
+seed_exts = wallet_ext, seed_ext, mn_ext, brain_ext
+
 default_wl    = "electrum"
 #default_wl    = "tirosh"
 

+ 10 - 50
mmgen/license.py

@@ -30,7 +30,7 @@ gpl = {
   and you are welcome to redistribute it under certain conditions.
 """,
 	'prompt': """
-Press 'c' for conditions, 'w' for warranty info, or ENTER to continue:
+Press 'w' for conditions and warranty info, or 'c' to continue:
 """,
 	'conditions': """
                        TERMS AND CONDITIONS
@@ -582,60 +582,20 @@ reviewing courts shall apply local law that most closely approximates
 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.
-""",
-	'warranty': """
-  15. Disclaimer of Warranty.
-
-  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
-APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
-HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
-OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
-THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
-IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
-ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
-
-  16. Limitation of Liability.
-
-  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
-THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
-GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
-USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
-DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
-PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
-EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
-SUCH DAMAGES.
-
-  17. Interpretation of Sections 15 and 16.
-
-  If the disclaimer of warranty and limitation of liability provided
-above cannot be given local legal effect according to their terms,
-reviewing courts shall apply local law that most closely approximates
-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.
 """
 }
 
-def do_pager(text):
-	import os
-	pager = os.environ['PAGER'] if 'PAGER' in os.environ else 'more'
-
-	p = os.popen(pager, 'w')
-	p.write(text)
-	p.close()
-	msg_r("\r")
-
-
 def do_license_msg():
 	msg(gpl['warning'])
+	prompt = "%s " % gpl['prompt'].strip()
 
 	while True:
-
-		prompt = "%s " % gpl['prompt'].strip()
 		reply = get_char(prompt)
-
-		if   reply == 'c': do_pager(gpl['conditions'])
-		elif reply == 'w': do_pager(gpl['warranty'])
-		else: msg(""); break
+		if reply == 'w':
+			from mmgen.utils import do_pager
+			do_pager(gpl['conditions'],"END OF CONDITIONS AND WARRANTY")
+		elif reply == 'c':
+			msg(""); break
+		else:
+			msg_r("\r")
+	msg("")

+ 3 - 3
mmgen/mnemonic.py

@@ -49,12 +49,12 @@ def get_seed_from_mnemonic(mn,wl):
 
 	if len(mn) not in mnemonic_lens:
 		msg("Bad mnemonic (%i words).  Allowed numbers of words: %s" %
-				(len(mn)," ".join([str(i) for i in mnemonic_lens])))
+				(len(mn),", ".join([str(i) for i in mnemonic_lens])))
 		return False
 
-	for w in mn:
+	for n,w in enumerate(mn,1):
 		if w not in wl:
-			msg("Bad mnemonic: '%s' is not in the wordlist" % w)
+			msg("Bad mnemonic: word number %s is not in the wordlist" % n)
 			return False
 
 	from binascii import unhexlify

+ 6 - 1
mmgen/rpc/connection.py

@@ -64,7 +64,12 @@ class BitcoinConnection(object):
 		try:
 			return self.proxy.importaddress(address,label)
 		except JSONRPCException as e:
-			raise _wrap_exception(e.error)
+			if e.error['message'] == "Method not found":
+				from mmgen.utils import msg
+				msg("""
+ERROR: 'importaddress' method not found.  Is your bitcoind enabled for
+watch-only addresses?""")
+			else: raise _wrap_exception(e.error)
 
 # sendrawtransaction <hex string> [allowhighfees=false]
 	def sendrawtransaction(self,tx):

+ 2 - 1
mmgen/rpc/proxy.py

@@ -104,8 +104,9 @@ class AuthServiceProxy(object):
 					'Authorization' : self.__authhdr,
 					'Content-type' : 'application/json' })
 		except:
-			print "Unable to connect to bitcoind.  Exiting"
+			from mmgen.utils import msg
 			import sys
+			msg("\nUnable to connect to bitcoind.")
 			sys.exit(2)
 
 		httpresp = self.__conn.getresponse()

+ 35 - 14
mmgen/tx.py

@@ -65,9 +65,10 @@ def connect_to_bitcoind():
 	return c
 
 
-def trim_exponent(d):
+def trim_exponent(n):
 	'''Remove exponent and trailing zeros.
 	'''
+	d = Decimal(n)
 	return d.quantize(Decimal(1)) if d == d.to_integral() else d.normalize()
 
 
@@ -95,7 +96,16 @@ def check_btc_amt(send_amt):
 
 def get_cfg_options(cfg_keys):
 
-	cfg_file = "%s/%s" % (os.environ["HOME"], ".bitcoin/bitcoin.conf")
+	if "HOME" in os.environ:
+		cfg_file = "%s/%s" % (os.environ["HOME"], ".bitcoin/bitcoin.conf")
+	elif "HOMEPATH" in os.environ:
+	# Windows:
+		cfg_file = "%s%s" % (os.environ["HOMEPATH"],
+						r"\Application Data\Bitcoin\bitcoin.conf")
+	else:
+		msg("Unable to find bitcoin configuration file")
+		sys.exit(3)
+
 	try:
 		f = open(cfg_file)
 	except:
@@ -186,7 +196,7 @@ def sort_and_view(unspent):
 				total
 			)]
 		output.append(fs % ("Num","TX id  Vout","","Address","Amount (BTC)",
-					"Age (days)"))
+					"Age(days)"))
 
 		for i in out:
 			amt = str(trim_exponent(i.amount))
@@ -209,12 +219,17 @@ def sort_and_view(unspent):
 
 			output.append(fs % (str(n+1)+")",txid,i.vout,addr,i.amt,i.days))
 
+		skip_body = False
 		while True:
-			reply = get_char("\n".join(output) +
-"""\n
+			if skip_body: skip_body = False
+			else:
+				msg("\n".join(output))
+				msg("""
 Sort options: [t]xid, [a]mount, a[d]dress, [A]ge, [r]everse, [M]mgen addr
-View options: [g]roup, show [m]mgen addr
-(Type 'q' to quit sorting, 'p' to print to file): """).strip()
+View options: [g]roup, show [m]mgen addr""")
+
+			reply = get_char(
+"(Type 'q' to quit sorting, 'p' to print to file, 'P' to view in pager): ")
 			if   reply == 'a': unspent.sort(s_amt);  sort = "amount"; break
 			elif reply == 't': unspent.sort(s_txid); sort = "txid"; break
 			elif reply == 'd': unspent.sort(s_addr); sort = "address"; break
@@ -241,13 +256,19 @@ View options: [g]roup, show [m]mgen addr
 							i.address,mmid,i.amt,i.days,cmt)
 					pout.append(os.rstrip())
 
-				outdata = "Unspent outputs ({} UTC)\n\n{}\n\nTotal BTC: {}\n".format(
-						make_timestr(), "\n".join(pout), total
-					)
-				outfile = "listunspent.out"
+				sort_info = (
+					("reverse," if reverse else "") +
+					(sort if sort else "unsorted")
+				)
+				outdata = \
+"Unspent outputs ({} UTC)\nSort order: {}\n\n{}\n\nTotal BTC: {}\n".format(
+					make_timestr(), sort_info, "\n".join(pout), total
+				)
+				outfile = "listunspent[%s].out" % sort_info
 				write_to_file(outfile, outdata)
+				skip_body = True
 				msg("\nData written to '%s'" % outfile)
-				sys.exit(1)
+			elif reply == 'P': do_pager("\n".join(output))
 			elif reply == 'q': break
 			else: msg("Invalid input")
 
@@ -288,7 +309,7 @@ def view_tx_data(c,inputs_data,tx_hex,metadata=[]):
 	msg("TRANSACTION DATA:\n")
 
 	if metadata: msg(
-		"Header: [ID: {}] [Amount: {} BTC] [Time: {}]\n".format(*metadata))
+		"Header: [Tx ID: {}] [Amount: {} BTC] [Time: {}]\n".format(*metadata))
 
 	msg("Inputs:")
 	total_in = 0
@@ -300,7 +321,7 @@ def view_tx_data(c,inputs_data,tx_hex,metadata=[]):
 				msg(" " + """
 %-2s tx,vout: %s,%s
     address:        %s
-    label:          %s
+    ID/label:       %s
     amount:         %s BTC
     confirmations:  %s (around %s days)
 """.strip() %

+ 182 - 77
mmgen/utils.py

@@ -40,20 +40,44 @@ def my_getpass(prompt):
 
 	return pw
 
-def get_char(prompt):
+term = False
+
+def get_char(prompt=""):
 
-	import os
 	msg_r(prompt)
-	os.system(
-"stty -icanon min 1 time 0 -echo -echoe -echok -echonl -crterase noflsh"
-	)
-	try: ch = sys.stdin.read(1)
+
+	global term
+
+	if not term:
+		try:
+			import tty, termios
+			term = "unix"
+		except:
+			try:
+				import msvcrt
+				term = "mswin"
+			except:
+				msg("Unable to set terminal mode")
+				sys.exit(2)
+
+	try:
+		if term == "unix":
+			import tty, termios
+			fd = sys.stdin.fileno()
+			old = termios.tcgetattr(fd)
+			tty.setcbreak(fd)
+			ch = sys.stdin.read(1)
+		elif term == "mswin":
+			import msvcrt
+			ch = msvcrt.getch()
+			if ord(ch) == 3:
+				raise KeyboardInterrupt
 	except:
-		os.system("stty sane")
 		msg("\nUser interrupt")
 		sys.exit(1)
-	else:
-		os.system("stty sane")
+	finally:
+		if term == "unix":
+			termios.tcsetattr(fd, termios.TCSADRAIN, old)
 
 	return ch
 
@@ -320,39 +344,29 @@ def parse_address_list(arg,sep=","):
 	return sorted(set(ret))
 
 
-def get_first_passphrase_from_user(what, opts):
-	"""
-	Prompt the user for a passphrase and return it
-
-	Supported options: echo_passphrase
-	"""
-
-	if not 'quiet' in opts:
-		msg("""
-Now you must choose a passphrase to encrypt the seed with.  A key will be
-generated from your passphrase using a hash preset of '%s'.  Please note that
-no strength checking of passphrases is performed.  For an empty passphrase,
-just hit ENTER twice.
-""" % opts['hash_preset'])
+def get_new_passphrase(what, opts):
 
-	if 'echo_passphrase' in opts:
-		ret = " ".join(_get_words_from_user(opts,"Enter %s: " % what))
-		if ret == "": msg("Empty passphrase")
-		return ret
-
-	for i in range(passwd_max_tries):
-		ret  = " ".join(_get_words_from_user(opts,"Enter %s: " % what))
-		ret2 = " ".join(_get_words_from_user(opts,"Repeat %s: " % what))
-		if debug: print "Passphrases: [%s] [%s]" % (ret,ret2)
-		if ret2 == ret:
-			s = " (empty)" if not len(ret) else ""
-			msg("%ss match%s" % (what.capitalize(),s))
-			return ret
+	if 'passwd_file' in opts:
+		pw = " ".join(_get_words_from_file(opts['passwd_file'],what))
+	elif 'echo_passphrase' in opts:
+		pw = " ".join(_get_words_from_user(("Enter %s: " % what), opts))
+	else:
+		for i in range(passwd_max_tries):
+			pw = " ".join(_get_words_from_user(("Enter %s: " % what),opts))
+			pw2 = " ".join(_get_words_from_user(("Repeat %s: " % what),opts))
+			if debug: print "Passphrases: [%s] [%s]" % (pw,pw2)
+			if pw == pw2:
+				msg("%ss match" % what.capitalize())
+				break
+			else:
+				msg("%ss do not match" % what.capitalize())
 		else:
-			msg("%ss do not match" % what.capitalize())
+			msg("User failed to duplicate passphrase in %s attempts" %
+					passwd_max_tries)
+			sys.exit(2)
 
-	msg("User failed to duplicate passphrase in %s attempts" % passwd_max_tries)
-	sys.exit(2)
+	if pw == "": msg("WARNING: Empty passphrase")
+	return pw
 
 
 def _scrypt_hash_passphrase(passwd, salt, hash_preset, buflen=32):
@@ -404,9 +418,12 @@ def write_to_stdout(data, what, confirm=True):
 	if sys.stdout.isatty() and confirm:
 		confirm_or_exit("",'output {} to screen'.format(what))
 	elif not sys.stdout.isatty():
-		import os
-		of = os.readlink("/proc/%d/fd/1" % os.getpid())
-		msg("Writing data to file '%s'" % of)
+		try:
+			import os
+			of = os.readlink("/proc/%d/fd/1" % os.getpid())
+			msg("Redirecting output to file '%s'" % of)
+		except:
+			msg("Redirecting output to file")
 	sys.stdout.write(data)
 
 
@@ -682,24 +699,28 @@ def get_data_from_wallet(infile,opts,silent=False):
 	return label,metadata,hash_preset,res['salt'],res['enc_seed']
 
 
-def _get_words_from_user(opts, prompt):
+def _get_words_from_user(prompt, opts):
 	# split() also strips
 	if 'echo_passphrase' in opts:
-		return my_raw_input(prompt).split()
+		words = my_raw_input(prompt).split()
 	else:
-		return my_getpass(prompt).split()
+		words = my_getpass(prompt).split()
+	if debug: print "Sanitized input: [%s]" % " ".join(words)
+	return words
 
 
 def _get_words_from_file(infile,what):
 	msg("Getting %s from file '%s'" % (what,infile))
 	f = open_file_or_exit(infile, 'r')
-	data = f.read(); f.close()
 	# split() also strips
-	return data.split()
+	words = f.read().split()
+	f.close()
+	if debug: print "Sanitized input: [%s]" % " ".join(words)
+	return words
 
 
-def get_lines_from_file(infile,what):
-	msg("Getting %s from file '%s'" % (what,infile))
+def get_lines_from_file(infile,what=""):
+	if what != "": msg("Getting %s from file '%s'" % (what,infile))
 	f = open_file_or_exit(infile,'r')
 	lines = f.read().splitlines(); f.close()
 	return lines
@@ -713,15 +734,6 @@ def get_data_from_file(infile,what="data"):
 	return data
 
 
-def get_words(infile,what,prompt,opts):
-	if infile:
-		words = _get_words_from_file(infile,what)
-	else:
-		words = _get_words_from_user(opts,prompt)
-	if debug: print "Sanitized input: [%s]" % " ".join(words)
-	return words
-
-
 def _get_seed_from_seed_data(words):
 
 	if not _check_mmseed_format(words):
@@ -746,6 +758,34 @@ def _get_seed_from_seed_data(words):
 		msg("Invalid checksum for {} seed".format(proj_name))
 		return False
 
+passwd_file_used = False
+
+def mark_passwd_file_as_used(opts):
+	global passwd_file_used
+	if passwd_file_used:
+		msg_r("WARNING: Reusing passphrase from file '%s'." % opts['passwd_file'])
+		msg(" This may not be what you want!")
+	passwd_file_used = True
+
+
+def get_mmgen_passphrase(prompt,opts):
+	if 'passwd_file' in opts:
+		mark_passwd_file_as_used(opts)
+		return " ".join(_get_words_from_file(opts['passwd_file'],"passphrase"))
+	else:
+		return " ".join(_get_words_from_user(prompt,opts))
+
+
+def get_bitcoind_passphrase(prompt,opts):
+	if 'passwd_file' in opts:
+		mark_passwd_file_as_used(opts)
+		return get_data_from_file(opts['passwd_file'],"passphrase").strip("\r\n")
+	else:
+		if 'echo_passphrase' in opts:
+			return my_raw_input(prompt)
+		else:
+			return my_getpass(prompt)
+
 
 def get_seed_from_wallet(
 		infile,
@@ -759,7 +799,7 @@ def get_seed_from_wallet(
 
 	if 'verbose' in opts: _display_control_data(*wdata)
 
-	passwd = " ".join(get_words("","",prompt,opts))
+	passwd = get_mmgen_passphrase(prompt,opts)
 
 	key = make_key(passwd, salt, hash_preset)
 
@@ -809,41 +849,63 @@ def decrypt_seed(enc_seed, key, seed_id, key_id):
 	return dec_seed
 
 
+def _get_words(infile,what,prompt,opts):
+	if infile:
+		return _get_words_from_file(infile,what)
+	else:
+		return _get_words_from_user(prompt,opts)
+
+
 def get_seed(infile,opts,silent=False):
-	if 'from_mnemonic' in opts:
-		prompt = "Enter mnemonic: "
-		what = "mnemonic"
-		words = get_words(infile,"mnemonic data",prompt,opts)
 
+	ext = infile.split(".")[-1]
+
+	if   ext == mn_ext:           source = "mnemonic"
+	elif ext == brain_ext:        source = "brainwallet"
+	elif ext == seed_ext:         source = "seed"
+	elif ext == wallet_ext:       source = "wallet"
+	elif 'from_mnemonic' in opts: source = "mnemonic"
+	elif 'from_brain'    in opts: source = "brainwallet"
+	elif 'from_seed'     in opts: source = "seed"
+	else:
+		if infile: msg(
+			"Invalid file extension for file: %s\nValid extensions: '.%s'" %
+			(infile, "', '.".join(seed_exts)))
+		else: msg("No seed source type specified and no file supplied")
+		sys.exit(2)
+
+	if source == "mnemonic":
+		prompt = "Enter mnemonic: "
+		words = _get_words(infile,"mnemonic data",prompt,opts)
 		wl = get_default_wordlist()
 		from mmgen.mnemonic import get_seed_from_mnemonic
 		seed = get_seed_from_mnemonic(words,wl)
-	elif 'from_brain' in opts:
+	elif source == "brainwallet":
+		if 'from_brain' not in opts:
+			msg("'--from-brain' parameters must be specified for brainwallet file")
+			sys.exit(2)
 		if 'quiet' not in opts:
 			confirm_or_exit(
 				cmessages['brain_warning'].format(
-					proj_name.capitalize(),
-					*_get_from_brain_opt_params(opts)),
-			"continue")
+					proj_name.capitalize(), *_get_from_brain_opt_params(opts)),
+				"continue")
 		prompt = "Enter brainwallet passphrase: "
-		what = "brainwallet"
-		words = get_words(infile,"brainwallet data",prompt,opts)
+		words = _get_words(infile,"brainwallet data",prompt,opts)
 		seed = _get_seed_from_brain_passphrase(words,opts)
-	elif 'from_seed' in opts:
+	elif source == "seed":
 		prompt = "Enter seed in %s format: " % seed_ext
-		what = "seed"
-		words = get_words(infile,"seed data",prompt,opts)
+		words = _get_words(infile,"seed data",prompt,opts)
 		seed = _get_seed_from_seed_data(words)
-	else:
-		return get_seed_from_wallet(infile, opts, silent=silent)
+	elif source == "wallet":
+		seed = get_seed_from_wallet(infile, opts, silent=silent)
 
 	if infile and not seed:
-		msg("Invalid %s file: %s" % (what,infile))
+		msg("Invalid %s file: %s" % (source,infile))
 		sys.exit(2)
 
 	return seed
 
-# Repeat if data entry is incorrect
+# Repeat if entered data is invalid
 def get_seed_retry(infile,opts):
 	silent = False
 	while True:
@@ -863,5 +925,48 @@ def remove_blanks_comments(lines):
 
 	return ret
 
+def do_pager(text,endmsg=""):
+	import os
+	if sys.platform.startswith("linux"):
+		if 'PAGER' in os.environ and os.environ['PAGER']:
+			try:
+				p = os.popen(os.environ['PAGER'], 'w')
+			except:
+				print text
+			else:
+				try:
+					p.write(text)
+					p.close()
+				except:
+					p.close()
+				msg_r("\r")
+		else:
+			print text
+	elif sys.platform.startswith("win"):
+		try:
+			import msvcrt
+		except:
+			print text
+		else:
+			try:
+				from subprocess import Popen, PIPE, STDOUT
+				p = Popen(["more","/C"], stdin=PIPE, shell=True)
+				if endmsg:
+					p.stdin.write("%s\n%s\n\n" % (text,endmsg))
+				else:
+					p.stdin.write(text)
+			except:
+				msg("\nUser exit")
+
+			from time import sleep
+			# Flush stdin
+			while msvcrt.kbhit(): msvcrt.getch()
+			sleep(1)
+			while msvcrt.kbhit(): msvcrt.getch()
+			msg("")
+	else:
+		print text
+
+
 if __name__ == "__main__":
-	print get_lines_from_file("/tmp/lines","test file")
+	print "utils.py"

+ 13 - 25
mmgen/walletgen.py

@@ -20,7 +20,7 @@ walletgen.py:  Routines used for seed generation and wallet creation
 """
 
 import sys
-from mmgen.utils import msg, msg_r
+from mmgen.utils import msg, msg_r, get_char
 from binascii import hexlify
 
 def get_random_data_from_user(opts):
@@ -47,30 +47,18 @@ displayed on the screen.
 
 	user_rand_data,intervals = "",[]
 
-	try:
-		import os
-		os.system(
-		"stty -icanon min 1 time 0 -echo -echoe -echok -echonl -crterase noflsh"
-		)
-		for i in range(ulen):
-			user_rand_data += sys.stdin.read(1)
-			msg_r("\r" + prompt % (ulen - i - 1))
-			now = time.time()
-			intervals.append(now - saved_time)
-			saved_time = now
-		if 'quiet' in opts:
-			msg_r("\r")
-		else:
-			msg_r("\rThank you.  That's enough." + " "*15 + "\n\n")
-		time.sleep(0.5)
-		msg_r(
-		"User random data successfully acquired.  Press ENTER to continue: ")
-		raw_input()
-	except:
-		msg("\nUser random input interrupted")
-		sys.exit(1)
-	finally:
-		os.system("stty sane")
+	for i in range(ulen):
+		user_rand_data += get_char()
+		msg_r("\r" + prompt % (ulen - i - 1))
+		now = time.time()
+		intervals.append(now - saved_time)
+		saved_time = now
+	if 'quiet' in opts:
+		msg_r("\r")
+	else:
+		msg_r("\rThank you.  That's enough." + " "*15 + "\n\n")
+	time.sleep(0.5)
+	get_char("User random data successfully acquired.  Press ENTER to continue: ")
 
 	return user_rand_data, ["{:.22f}".format(i) for i in intervals]
 

+ 1 - 1
setup.py

@@ -3,7 +3,7 @@ from distutils.core import setup
 
 setup(
 		name         = 'mmgen',
-		version      = '0.6.5',
+		version      = '0.6.7',
 		author       = 'Philemon',
 		author_email = 'mmgen-py@yandex.com',
 		url          = 'https://github.com/mmgen/mmgen',