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 = {
 help_data = {
 	'prog_name': sys.argv[0].split("/")[-1],
 	'prog_name': sys.argv[0].split("/")[-1],
 	'desc': """Generate a list or range of {} from a {} wallet,
 	'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>",
 	'usage':"[opts] [infile] <address list>",
 	'options': """
 	'options': """
 -h, --help               Print this help message{}
 -h, --help               Print this help message{}
 -d, --outdir          d  Specify an alternate directory 'd' for output
 -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
 -K, --no-keyconv         Use internal libraries for address generation
                          instead of 'keyconv'
                          instead of 'keyconv'
 -l, --seed-len        N  Length of seed.  Options: {}
 -l, --seed-len        N  Length of seed.  Options: {}
                          (default: {})
                          (default: {})
 -p, --hash-preset     p  Use scrypt.hash() parameters from preset 'p'
 -p, --hash-preset     p  Use scrypt.hash() parameters from preset 'p'
                          when hashing password (default: '{}')
                          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
 -S, --stdout             Print {W} to stdout
 -v, --verbose            Produce more verbose output{}
 -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 '--from-brain' option also requires the user to specify a seed length
 (the 'l' parameter)
 (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(
 """.format(
 		extra_help_data[0],
 		extra_help_data[0],
 		", ".join([str(i) for i in seed_lens]),
 		", ".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":
 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)
 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
 # Exits on invalid input
 check_opts(opts,('hash_preset','seed_len','outdir','from_brain'))
 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 (
 if   len(cmd_args) == 1 and (
 			'from_mnemonic' in opts or
 			'from_mnemonic' in opts or
 			'from_brain' in opts or
 			'from_brain' in opts or

+ 3 - 3
mmgen-addrimport

@@ -33,9 +33,9 @@ help_data = {
                      watching wallet""",
                      watching wallet""",
 	'usage':"[opts] [mmgen address file]",
 	'usage':"[opts] [mmgen address file]",
 	'options': """
 	'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': """
 	'options': """
 -h, --help                  Print this help message
 -h, --help                  Print this help message
 -d, --outdir             d  Specify an alternate directory 'd' for output
 -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
 -k, --keep-old-passphrase   Keep old passphrase (use when changing hash
                             strength or label only)
                             strength or label only)
 -L, --label              l  Change the wallet's label to 'l'
 -L, --label              l  Change the wallet's label to 'l'
 -p, --hash-preset        p  Change scrypt.hash() parameters to preset 'p'
 -p, --hash-preset        p  Change scrypt.hash() parameters to preset 'p'
                             (default: '{}')
                             (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
 -v, --verbose               Produce more verbose output
 
 
 NOTE: The key ID will change if either the passphrase or hash preset
 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)
 """.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)
 opts,cmd_args = Opts.process_opts(sys.argv,help_data,short_opts,long_opts)
 
 
@@ -65,11 +66,12 @@ infile = cmd_args[0]
 # Old key:
 # Old key:
 label,metadata,hash_preset,salt,enc_seed = get_data_from_wallet(infile,opts)
 label,metadata,hash_preset,salt,enc_seed = get_data_from_wallet(infile,opts)
 seed_id,key_id = metadata[:2]
 seed_id,key_id = metadata[:2]
-oldp = "" if 'keep_old_passphrase' in opts else "old "
 
 
 # Repeat on incorrect pw entry
 # Repeat on incorrect pw entry
+prompt = "Enter %spassphrase: " % (""
+		if 'keep_old_passphrase' in opts else "old ")
 while True:
 while True:
-	passwd = " ".join(get_words("","",("Enter %spassphrase: " % oldp),opts))
+	passwd = get_mmgen_passphrase(prompt,{})
 	key = make_key(passwd, salt, hash_preset)
 	key = make_key(passwd, salt, hash_preset)
 	seed = decrypt_seed(enc_seed, key, seed_id, key_id)
 	seed = decrypt_seed(enc_seed, key, seed_id, key_id)
 	if seed: break
 	if seed: break
@@ -97,8 +99,7 @@ else:
 if 'keep_old_passphrase' in opts:
 if 'keep_old_passphrase' in opts:
 	msg("Keeping old passphrase by user request")
 	msg("Keeping old passphrase by user request")
 else:
 else:
-	new_passwd = get_first_passphrase_from_user(
-			"new passphrase", {'quiet': True })
+	new_passwd = get_new_passphrase("new passphrase", opts)
 
 
 	if new_passwd == passwd:
 	if new_passwd == passwd:
 		msg("Passphrase is unchanged")
 		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.Opts import *
 from mmgen.utils import msg
 from mmgen.utils import msg
 from bsddb.db import *
 from bsddb.db import *
-import os, sys, time
+import sys, time
 import json
 import json
 import logging
 import logging
 import struct
 import struct
@@ -75,17 +75,20 @@ help_data = {
 	'options': """
 	'options': """
 -h, --help             Print this help message
 -h, --help             Print this help message
 -d, --outdir        d  Specify an alternate directory 'd' for output
 -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
 -j, --json             Dump wallet in json format
 -k, --keys             Dump all private keys (flat list)
 -k, --keys             Dump all private keys (flat list)
 -a, --addrs            Dump all addresses (flat list)
 -a, --addrs            Dump all addresses (flat list)
 -K, --keysforaddrs  f  Dump private keys for addresses listed in file 'f'
 -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
 -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)
 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 \
 			if password == None and \
 				('json' in opts or 'keysforaddrs' in opts or 'keys' in opts):
 				('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:
 			if password != None:
 				global crypter
 				global crypter
@@ -1646,21 +1649,14 @@ def read_wallet(json_db, db_env, db_file, print_wallet, print_wallet_transaction
 	del(json_db['names'])
 	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()
 # 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)
 #	print "[%s] [%s]" % (db_dir,db_file)
 
 
 db_env = create_env(db_dir)
 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']]
 wallet_addrs = [i['addr'] for i in json_db['keys']]
 
 
-data,ext,what = [],"",""
-
 if 'json' in opts:
 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"
 	ext,what = "json","json dump"
 
 
 elif 'keys' in opts:
 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"
 	ext,what = "keys","private keys"
 
 
 elif 'addrs' in opts:
 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"
 	ext,what = "addrs","addresses"
 
 
 elif 'keysforaddrs' in opts:
 elif 'keysforaddrs' in opts:
@@ -1696,7 +1690,7 @@ elif 'keysforaddrs' in opts:
 			data.append(json_db['keys'][idx]['sec'])
 			data.append(json_db['keys'][idx]['sec'])
 		except:
 		except:
 			msg("WARNING: Address '%s' not found" % addr)
 			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) \
 len_arg = "%s" % len(wallet_addrs) \
    if len(data) == len(wallet_addrs) or ext == "json" \
    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.license import *
 from mmgen.config import *
 from mmgen.config import *
 from mmgen.tx 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
 from decimal import Decimal
 
 
 prog_name = sys.argv[0].split("/")[-1]
 prog_name = sys.argv[0].split("/")[-1]
@@ -40,12 +40,13 @@ help_data = {
 -d, --outdir           d  Specify an alternate directory 'd' for output
 -d, --outdir           d  Specify an alternate directory 'd' for output
 -e, --echo-passphrase     Print passphrase to screen when typing it
 -e, --echo-passphrase     Print passphrase to screen when typing it
 -i, --info                Display unspent outputs and exit
 -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.
 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
 """ % mins_per_block
 }
 }
 
 
@@ -88,21 +89,29 @@ if not 'quiet' in opts and not 'info' in opts: do_license_msg()
 # End test
 # End test
 
 
 us = c.listunspent()
 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))
 # write_to_file("listunspent.json",repr(us))
 # sys.exit()
 # sys.exit()
 unspent = sort_and_view(us)
 unspent = sort_and_view(us)
 
 
 total = trim_exponent(sum([i.amount for i in unspent]))
 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)
 if 'info' in opts: sys.exit(0)
 
 
 send_amt = sum(tx_out.values())
 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:
 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))
 	msg("Selected outputs: %s" % " ".join(str(i) for i in sel_nums))
 	sel_unspent = [unspent[i-1] 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': """
 	'options': """
 -h, --help          Print this help message
 -h, --help          Print this help message
 -d, --outdir     d  Specify an alternate directory 'd' for output
 -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
 -h, --help               Print this help message
 -d, --outdir          d  Specify an alternate directory 'd' for output
 -d, --outdir          d  Specify an alternate directory 'd' for output
 -e, --echo-passphrase    Print passphrase to screen when typing it
 -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, --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'
 -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,
 -b, --from-brain     l,p Generate keys from a user-created password,
                          i.e. a "brainwallet", using seed length 'l' and
                          i.e. a "brainwallet", using seed length 'l' and
                          hash preset 'p' (comma-separated)
                          hash preset 'p' (comma-separated)
 -m, --from-mnemonic      Generate keys from an electrum-like mnemonic
 -m, --from-mnemonic      Generate keys from an electrum-like mnemonic
 -s, --from-seed          Generate keys from a seed in .{} format
 -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.
 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.
 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)
 """.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)
 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)
 if not infiles: usage(help_data)
 for i in infiles: check_infile(i)
 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]))
 	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:
 	while seed_ids:
 		infile = False
 		infile = False
 		if infiles:
 		if infiles:
 			infile = infiles.pop()
 			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:
 		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])
 			msg("Need data for seed ID %s" % seed_ids[0])
 			seed = get_seed_retry("",opts)
 			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
 			from mmgen.addr import generate_keys
 			keys += [i['wif'] for i in generate_keys(seed, seed_id_addrs)]
 			keys += [i['wif'] for i in generate_keys(seed, seed_id_addrs)]
 		else:
 		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])
 	" ".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:
 	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']:
 if sig_tx['complete']:
 	msg("Signing completed")
 	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:
 else:
 	msg("Some keys were missing.  Transaction could not be signed.")
 	msg("Some keys were missing.  Transaction could not be signed.")
 	sys.exit(3)
 	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
 -d, --outdir        d  Specify an alternate directory 'd' for output
 -e, --echo-passphrase  Print passphrase to screen when typing it
 -e, --echo-passphrase  Print passphrase to screen when typing it
 -m, --export-mnemonic  Export the wallet's mnemonic to file
 -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, --export-seed      Export the wallet's seed to file
 -S, --stdout           Print seed or mnemonic data to standard output
 -S, --stdout           Print seed or mnemonic data to standard output
 -v, --verbose          Produce more verbose output
 -v, --verbose          Produce more verbose output
 """
 """
 }
 }
 
 
-short_opts = "hd:emsSv"
+short_opts = "hd:emP:sSv"
 long_opts  = "help","outdir=","echo_passphrase","export_mnemonic",\
 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)
 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
 -h, --help                 Print this help message
 -d, --outdir            d  Specify an alternate directory 'd' for output
 -d, --outdir            d  Specify an alternate directory 'd' for output
 -e, --echo-passphrase      Print passphrase to screen when typing it
 -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: {}
 -l, --seed-len          n  Create seed of length 'n'. Options: {}
                            (default: {})
                            (default: {})
 -L, --label             l  Label to identify this wallet (32 chars max.
 -L, --label             l  Label to identify this wallet (32 chars max.
                            Allowed symbols: A-Z, a-z, 0-9, " ", "_", ".")
                            Allowed symbols: A-Z, a-z, 0-9, " ", "_", ".")
 -p, --hash-preset       p  Use scrypt.hash() parameters from preset 'p'
 -p, --hash-preset       p  Use scrypt.hash() parameters from preset 'p'
                            (default: '{}')
                            (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
 -u, --usr-randlen       n  Get 'n' characters of randomness from the user
                            (default: {})
                            (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 'l' parameter), which overrides both the default and any one given in
 the '--seed-len' option.
 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(
 """.format(
-		", ".join([str(i) for i in seed_lens]),
+		",".join([str(i) for i in seed_lens]),
 		seed_len,
 		seed_len,
 		hash_preset,
 		hash_preset,
 		usr_randlen,
 		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)
 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...")
 msg_r("Acquiring random data from your computer...")
 
 
+from time import sleep
+sleep(1)
+
 try:
 try:
 	from Crypto import Random
 	from Crypto import Random
 	r = Random.new()
 	r = Random.new()
@@ -162,8 +167,15 @@ else:
 salt = os_rand_data[1] + usr_rand_data
 salt = os_rand_data[1] + usr_rand_data
 salt = sha256(salt).digest()[:salt_len]
 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'])
 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)
 		p = Popen([keyconv_exec, '-h'], stdout=PIPE, stderr=PIPE)
 	except:
 	except:
 		sys.stderr.write("""
 		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"))
 """ % (keyconv_exec, keyconv_exec, "vanitygen"))
 		return False
 		return False
@@ -152,12 +152,14 @@ def format_addr_data(addrlist, seed_chksum, opts):
 # MMGen address file
 # MMGen address file
 #
 #
 # This file is editable.
 # 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 = []
 	data = []
 	if not 'stdout' in opts: data.append(header + "\n")
 	if not 'stdout' in opts: data.append(header + "\n")
 	data.append("%s {" % seed_chksum.upper())
 	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
 config.py:  Constants and configuration options for the mmgen suite
 """
 """
 proj_name     = "mmgen"
 proj_name     = "mmgen"
+
 wallet_ext    = "mmdat"
 wallet_ext    = "mmdat"
 seed_ext      = "mmseed"
 seed_ext      = "mmseed"
 mn_ext        = "mmwords"
 mn_ext        = "mmwords"
 brain_ext     = "mmbrain"
 brain_ext     = "mmbrain"
+
+seed_exts = wallet_ext, seed_ext, mn_ext, brain_ext
+
 default_wl    = "electrum"
 default_wl    = "electrum"
 #default_wl    = "tirosh"
 #default_wl    = "tirosh"
 
 

+ 10 - 50
mmgen/license.py

@@ -30,7 +30,7 @@ gpl = {
   and you are welcome to redistribute it under certain conditions.
   and you are welcome to redistribute it under certain conditions.
 """,
 """,
 	'prompt': """
 	'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': """
 	'conditions': """
                        TERMS AND 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
 an absolute waiver of all civil liability in connection with the
 Program, unless a warranty or assumption of liability accompanies a
 Program, unless a warranty or assumption of liability accompanies a
 copy of the Program in return for a fee.
 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():
 def do_license_msg():
 	msg(gpl['warning'])
 	msg(gpl['warning'])
+	prompt = "%s " % gpl['prompt'].strip()
 
 
 	while True:
 	while True:
-
-		prompt = "%s " % gpl['prompt'].strip()
 		reply = get_char(prompt)
 		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:
 	if len(mn) not in mnemonic_lens:
 		msg("Bad mnemonic (%i words).  Allowed numbers of words: %s" %
 		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
 		return False
 
 
-	for w in mn:
+	for n,w in enumerate(mn,1):
 		if w not in wl:
 		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
 			return False
 
 
 	from binascii import unhexlify
 	from binascii import unhexlify

+ 6 - 1
mmgen/rpc/connection.py

@@ -64,7 +64,12 @@ class BitcoinConnection(object):
 		try:
 		try:
 			return self.proxy.importaddress(address,label)
 			return self.proxy.importaddress(address,label)
 		except JSONRPCException as e:
 		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]
 # sendrawtransaction <hex string> [allowhighfees=false]
 	def sendrawtransaction(self,tx):
 	def sendrawtransaction(self,tx):

+ 2 - 1
mmgen/rpc/proxy.py

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

+ 35 - 14
mmgen/tx.py

@@ -65,9 +65,10 @@ def connect_to_bitcoind():
 	return c
 	return c
 
 
 
 
-def trim_exponent(d):
+def trim_exponent(n):
 	'''Remove exponent and trailing zeros.
 	'''Remove exponent and trailing zeros.
 	'''
 	'''
+	d = Decimal(n)
 	return d.quantize(Decimal(1)) if d == d.to_integral() else d.normalize()
 	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):
 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:
 	try:
 		f = open(cfg_file)
 		f = open(cfg_file)
 	except:
 	except:
@@ -186,7 +196,7 @@ def sort_and_view(unspent):
 				total
 				total
 			)]
 			)]
 		output.append(fs % ("Num","TX id  Vout","","Address","Amount (BTC)",
 		output.append(fs % ("Num","TX id  Vout","","Address","Amount (BTC)",
-					"Age (days)"))
+					"Age(days)"))
 
 
 		for i in out:
 		for i in out:
 			amt = str(trim_exponent(i.amount))
 			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))
 			output.append(fs % (str(n+1)+")",txid,i.vout,addr,i.amt,i.days))
 
 
+		skip_body = False
 		while True:
 		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
 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
 			if   reply == 'a': unspent.sort(s_amt);  sort = "amount"; break
 			elif reply == 't': unspent.sort(s_txid); sort = "txid"; break
 			elif reply == 't': unspent.sort(s_txid); sort = "txid"; break
 			elif reply == 'd': unspent.sort(s_addr); sort = "address"; 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)
 							i.address,mmid,i.amt,i.days,cmt)
 					pout.append(os.rstrip())
 					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)
 				write_to_file(outfile, outdata)
+				skip_body = True
 				msg("\nData written to '%s'" % outfile)
 				msg("\nData written to '%s'" % outfile)
-				sys.exit(1)
+			elif reply == 'P': do_pager("\n".join(output))
 			elif reply == 'q': break
 			elif reply == 'q': break
 			else: msg("Invalid input")
 			else: msg("Invalid input")
 
 
@@ -288,7 +309,7 @@ def view_tx_data(c,inputs_data,tx_hex,metadata=[]):
 	msg("TRANSACTION DATA:\n")
 	msg("TRANSACTION DATA:\n")
 
 
 	if metadata: msg(
 	if metadata: msg(
-		"Header: [ID: {}] [Amount: {} BTC] [Time: {}]\n".format(*metadata))
+		"Header: [Tx ID: {}] [Amount: {} BTC] [Time: {}]\n".format(*metadata))
 
 
 	msg("Inputs:")
 	msg("Inputs:")
 	total_in = 0
 	total_in = 0
@@ -300,7 +321,7 @@ def view_tx_data(c,inputs_data,tx_hex,metadata=[]):
 				msg(" " + """
 				msg(" " + """
 %-2s tx,vout: %s,%s
 %-2s tx,vout: %s,%s
     address:        %s
     address:        %s
-    label:          %s
+    ID/label:       %s
     amount:         %s BTC
     amount:         %s BTC
     confirmations:  %s (around %s days)
     confirmations:  %s (around %s days)
 """.strip() %
 """.strip() %

+ 182 - 77
mmgen/utils.py

@@ -40,20 +40,44 @@ def my_getpass(prompt):
 
 
 	return pw
 	return pw
 
 
-def get_char(prompt):
+term = False
+
+def get_char(prompt=""):
 
 
-	import os
 	msg_r(prompt)
 	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:
 	except:
-		os.system("stty sane")
 		msg("\nUser interrupt")
 		msg("\nUser interrupt")
 		sys.exit(1)
 		sys.exit(1)
-	else:
-		os.system("stty sane")
+	finally:
+		if term == "unix":
+			termios.tcsetattr(fd, termios.TCSADRAIN, old)
 
 
 	return ch
 	return ch
 
 
@@ -320,39 +344,29 @@ def parse_address_list(arg,sep=","):
 	return sorted(set(ret))
 	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:
 		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):
 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:
 	if sys.stdout.isatty() and confirm:
 		confirm_or_exit("",'output {} to screen'.format(what))
 		confirm_or_exit("",'output {} to screen'.format(what))
 	elif not sys.stdout.isatty():
 	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)
 	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']
 	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
 	# split() also strips
 	if 'echo_passphrase' in opts:
 	if 'echo_passphrase' in opts:
-		return my_raw_input(prompt).split()
+		words = my_raw_input(prompt).split()
 	else:
 	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):
 def _get_words_from_file(infile,what):
 	msg("Getting %s from file '%s'" % (what,infile))
 	msg("Getting %s from file '%s'" % (what,infile))
 	f = open_file_or_exit(infile, 'r')
 	f = open_file_or_exit(infile, 'r')
-	data = f.read(); f.close()
 	# split() also strips
 	# 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')
 	f = open_file_or_exit(infile,'r')
 	lines = f.read().splitlines(); f.close()
 	lines = f.read().splitlines(); f.close()
 	return lines
 	return lines
@@ -713,15 +734,6 @@ def get_data_from_file(infile,what="data"):
 	return 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):
 def _get_seed_from_seed_data(words):
 
 
 	if not _check_mmseed_format(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))
 		msg("Invalid checksum for {} seed".format(proj_name))
 		return False
 		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(
 def get_seed_from_wallet(
 		infile,
 		infile,
@@ -759,7 +799,7 @@ def get_seed_from_wallet(
 
 
 	if 'verbose' in opts: _display_control_data(*wdata)
 	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)
 	key = make_key(passwd, salt, hash_preset)
 
 
@@ -809,41 +849,63 @@ def decrypt_seed(enc_seed, key, seed_id, key_id):
 	return dec_seed
 	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):
 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()
 		wl = get_default_wordlist()
 		from mmgen.mnemonic import get_seed_from_mnemonic
 		from mmgen.mnemonic import get_seed_from_mnemonic
 		seed = get_seed_from_mnemonic(words,wl)
 		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:
 		if 'quiet' not in opts:
 			confirm_or_exit(
 			confirm_or_exit(
 				cmessages['brain_warning'].format(
 				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: "
 		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)
 		seed = _get_seed_from_brain_passphrase(words,opts)
-	elif 'from_seed' in opts:
+	elif source == "seed":
 		prompt = "Enter seed in %s format: " % seed_ext
 		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)
 		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:
 	if infile and not seed:
-		msg("Invalid %s file: %s" % (what,infile))
+		msg("Invalid %s file: %s" % (source,infile))
 		sys.exit(2)
 		sys.exit(2)
 
 
 	return seed
 	return seed
 
 
-# Repeat if data entry is incorrect
+# Repeat if entered data is invalid
 def get_seed_retry(infile,opts):
 def get_seed_retry(infile,opts):
 	silent = False
 	silent = False
 	while True:
 	while True:
@@ -863,5 +925,48 @@ def remove_blanks_comments(lines):
 
 
 	return ret
 	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__":
 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
 import sys
-from mmgen.utils import msg, msg_r
+from mmgen.utils import msg, msg_r, get_char
 from binascii import hexlify
 from binascii import hexlify
 
 
 def get_random_data_from_user(opts):
 def get_random_data_from_user(opts):
@@ -47,30 +47,18 @@ displayed on the screen.
 
 
 	user_rand_data,intervals = "",[]
 	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]
 	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(
 setup(
 		name         = 'mmgen',
 		name         = 'mmgen',
-		version      = '0.6.5',
+		version      = '0.6.7',
 		author       = 'Philemon',
 		author       = 'Philemon',
 		author_email = 'mmgen-py@yandex.com',
 		author_email = 'mmgen-py@yandex.com',
 		url          = 'https://github.com/mmgen/mmgen',
 		url          = 'https://github.com/mmgen/mmgen',