Browse Source

Version 0.7.0

philemon 11 years ago
parent
commit
08c5b76805
28 changed files with 934 additions and 627 deletions
  1. 12 12
      MANIFEST
  2. 2 2
      README.md
  3. 1 1
      doc/MMGenBuildBitcoindMSWin.md
  4. 2 6
      doc/MMGenGettingStarted.md
  5. 1 1
      doc/MMGenInstallLinux.md
  6. 1 1
      doc/MMGenTextEditors.md
  7. 5 3
      mmgen-addrgen
  8. 11 12
      mmgen-addrimport
  9. 14 11
      mmgen-passchg
  10. 10 16
      mmgen-pywallet
  11. 73 63
      mmgen-txcreate
  12. 3 4
      mmgen-txsend
  13. 49 38
      mmgen-txsign
  14. 8 5
      mmgen-walletchk
  15. 22 13
      mmgen-walletgen
  16. 4 2
      mmgen/Opts.py
  17. 11 25
      mmgen/addr.py
  18. 32 21
      mmgen/bitcoin.py
  19. 12 5
      mmgen/config.py
  20. 4 0
      mmgen/license.py
  21. 1 1
      mmgen/rpc/proxy.py
  22. 191 0
      mmgen/term.py
  23. 48 20
      mmgen/tests/bitcoin.py
  24. 1 1
      mmgen/tests/test.py
  25. 347 197
      mmgen/tx.py
  26. 63 163
      mmgen/util.py
  27. 4 3
      mmgen/walletgen.py
  28. 2 1
      setup.py

+ 12 - 12
MANIFEST

@@ -1,7 +1,7 @@
 # file GENERATED by distutils, do NOT edit
 # file GENERATED by distutils, do NOT edit
+__init__.py
 mmgen-addrgen
 mmgen-addrgen
 mmgen-addrimport
 mmgen-addrimport
-mmgen-keygen
 mmgen-passchg
 mmgen-passchg
 mmgen-pywallet
 mmgen-pywallet
 mmgen-txcreate
 mmgen-txcreate
@@ -19,8 +19,9 @@ mmgen/license.py
 mmgen/mn_electrum.py
 mmgen/mn_electrum.py
 mmgen/mn_tirosh.py
 mmgen/mn_tirosh.py
 mmgen/mnemonic.py
 mmgen/mnemonic.py
+mmgen/term.py
 mmgen/tx.py
 mmgen/tx.py
-mmgen/utils.py
+mmgen/util.py
 mmgen/walletgen.py
 mmgen/walletgen.py
 mmgen/rpc/__init__.py
 mmgen/rpc/__init__.py
 mmgen/rpc/config.py
 mmgen/rpc/config.py
@@ -29,13 +30,12 @@ mmgen/rpc/data.py
 mmgen/rpc/exceptions.py
 mmgen/rpc/exceptions.py
 mmgen/rpc/proxy.py
 mmgen/rpc/proxy.py
 mmgen/rpc/util.py
 mmgen/rpc/util.py
-scripts/bitcoind-walletunlock.py
-scripts/deinstall.sh
-tests/addr.py
-tests/bitcoin.py
-tests/mn_electrum.py
-tests/mn_tirosh.py
-tests/mnemonic.py
-tests/test.py
-tests/utils.py
-tests/walletgen.py
+mmgen/tests/__init__.py
+mmgen/tests/addr.py
+mmgen/tests/bitcoin.py
+mmgen/tests/mn_electrum.py
+mmgen/tests/mn_tirosh.py
+mmgen/tests/mnemonic.py
+mmgen/tests/test.py
+mmgen/tests/util.py
+mmgen/tests/walletgen.py

+ 2 - 2
README.md

@@ -13,8 +13,8 @@ addresses can be tracked and spent as well, creating an easy migration path from
 other wallets.
 other wallets.
 
 
 To track address balances, MMGen relies on a bitcoin daemon modified by
 To track address balances, MMGen relies on a bitcoin daemon modified by
-Bitcoin's core developer team to support watch-only addresses.  This feature
-is soon to be included in the mainline Satoshi build.  In the meantime,
+Bitcoin core developers to support watch-only addresses.  This feature
+will one day be included in the mainline Satoshi build.  In the meantime,
 instructions are provided below for compiling the modified bitcoind.  Under
 instructions are provided below for compiling the modified bitcoind.  Under
 Linux, this is a trivial task for even a casual user.  Unfortunately, the same
 Linux, this is a trivial task for even a casual user.  Unfortunately, the same
 can't be said for Windows, but the thoroughly tested, step-by-step Windows build
 can't be said for Windows, but the thoroughly tested, step-by-step Windows build

+ 1 - 1
doc/MMGenBuildBitcoindMSWin.md

@@ -66,7 +66,7 @@ time-consuming process of compiling the whole boost package.
 
 
 #### 5. Build Bitcoind:
 #### 5. Build Bitcoind:
 
 
-Download Sipa's watchonly bitcoind [zip archive][05] (commit a13f1e8 [[check][]])
+Download Sipa's watchonly bitcoind [zip archive][05] (commit #a13f1e8 [[check][]])
 from GitHub and unpack it.  At the MSYS prompt, run:
 from GitHub and unpack it.  At the MSYS prompt, run:
 
 
 		$ cd /c/bitcoin-watchonly
 		$ cd /c/bitcoin-watchonly

+ 2 - 6
doc/MMGenGettingStarted.md

@@ -154,7 +154,7 @@ sending it to the addresses labeled "Storage 1", "Storage 2" and "Storage 3"
 
 
 To refresh your memory, here are the three addresses in question:
 To refresh your memory, here are the three addresses in question:
 
 
-		$ cat my_addrs
+		$ cat my.addrs
 		# My first MMGen addresses
 		# My first MMGen addresses
 		89ABCDEF {
 		89ABCDEF {
 		  1    16bNmyYISiptuvJG3X7MPwiiS4HYvD7ksE  Donations
 		  1    16bNmyYISiptuvJG3X7MPwiiS4HYvD7ksE  Donations
@@ -175,11 +175,7 @@ the change amount (3.399 BTC in this case) automatically.
 Alternatively, and more conveniently, you can list your three addresses in MMGen
 Alternatively, and more conveniently, you can list your three addresses in MMGen
 format:
 format:
 
 
-		$ mmgen-txcreate 89ABCDEF:2,3.3 89ABCDEF:3,3.3 89ABCDEF:4 my_addrs
-
-Note that an MMGen address file containing the requested output addresses must
-be provided on the command line.  Any extra addresses in the file will be
-ignored.
+		$ mmgen-txcreate 89ABCDEF:2,3.3 89ABCDEF:3,3.3 89ABCDEF:4
 
 
 Now hit ENTER, choose the transaction's input from the list (10 BTC, address
 Now hit ENTER, choose the transaction's input from the list (10 BTC, address
 1F9495H8EJL..., txid 04f97185...,2), and confirm.  If all goes well,
 1F9495H8EJL..., txid 04f97185...,2), and confirm.  If all goes well,

+ 1 - 1
doc/MMGenInstallLinux.md

@@ -58,7 +58,7 @@ the version should be 1.48 or greater):
 		libboost-test-dev
 		libboost-test-dev
 		libboost-thread-dev
 		libboost-thread-dev
 
 
-Download the bitcoin-watchonly [zip archive][00] (commit a13f1e8 [[check][]])
+Download the bitcoin-watchonly [zip archive][00] (commit #a13f1e8 [[check][]])
 from GitHub, configure, and build:
 from GitHub, configure, and build:
 
 
 		$ unzip watchonly.zip
 		$ unzip watchonly.zip

+ 1 - 1
doc/MMGenTextEditors.md

@@ -2,7 +2,7 @@ MMGen = Multi-Mode GENerator
 ============================
 ============================
 ##### a Bitcoin cold storage solution for the command line
 ##### a Bitcoin cold storage solution for the command line
 
 
-#### A word on text editors:
+### A word on text editors:
 
 
 The text file editors that come with Windows, "edit" and "notepad", are
 The text file editors that come with Windows, "edit" and "notepad", are
 unsuitable for editing source code for many reasons, but above all because they
 unsuitable for editing source code for many reasons, but above all because they

+ 5 - 3
mmgen-addrgen

@@ -121,6 +121,8 @@ if invoked_as == "addrgen":
 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)
 
 
 if 'show_hash_presets' in opts: show_hash_presets()
 if 'show_hash_presets' in opts: show_hash_presets()
+if 'quiet' in opts: g.quiet = True
+if 'verbose' in opts: g.verbose = True
 
 
 opts['gen_what'] = gen_what
 opts['gen_what'] = gen_what
 
 
@@ -143,10 +145,10 @@ addr_list = parse_address_list(addr_list_arg)
 
 
 if not addr_list: sys.exit(2)
 if not addr_list: sys.exit(2)
 
 
-if not 'quiet' in opts: do_license_msg()
+do_license_msg()
 
 
 # Interact with user:
 # Interact with user:
-if invoked_as == "keygen" and not 'quiet' in opts:
+if invoked_as == "keygen" and not g.quiet:
 	confirm_or_exit(cmessages['unencrypted_secret_keys'], 'continue')
 	confirm_or_exit(cmessages['unencrypted_secret_keys'], 'continue')
 
 
 # Generate data:
 # Generate data:
@@ -163,7 +165,7 @@ addr_data_str = format_addr_data(addr_data, seed_id, opts)
 
 
 # Output data:
 # Output data:
 if 'stdout' in opts:
 if 'stdout' in opts:
-	if invoked_as == "keygen" and not 'quiet' in opts:
+	if invoked_as == "keygen" and not g.quiet:
 		confirm = True
 		confirm = True
 	else: confirm = False
 	else: confirm = False
 	write_to_stdout(addr_data_str,"secret keys",confirm)
 	write_to_stdout(addr_data_str,"secret keys",confirm)

+ 11 - 12
mmgen-addrimport

@@ -23,9 +23,8 @@ mmgen-addrimport: Import addresses into a bitcoind watching wallet.
 import sys
 import sys
 from mmgen.Opts   import *
 from mmgen.Opts   import *
 from mmgen.license import *
 from mmgen.license import *
-from mmgen.util import check_infile,confirm_or_exit,msg,msg_r,secs_to_hms,get_lines_from_file
+from mmgen.util import *
 from mmgen.tx import connect_to_bitcoind,parse_addrs_file
 from mmgen.tx import connect_to_bitcoind,parse_addrs_file
-from mmgen.bitcoin import verify_addr
 
 
 help_data = {
 help_data = {
 	'prog_name': sys.argv[0].split("/")[-1],
 	'prog_name': sys.argv[0].split("/")[-1],
@@ -43,6 +42,7 @@ short_opts = "hl:q"
 long_opts  = "help", "addrlist=", "quiet"
 long_opts  = "help", "addrlist=", "quiet"
 
 
 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)
+if 'quiet' in opts: g.quiet = True
 
 
 if len(cmd_args) != 1 and not 'addrlist' in opts:
 if len(cmd_args) != 1 and not 'addrlist' in opts:
 	msg("You must specify an mmgen address list (and/or non-mmgen addresses with the '--addrlist' option)")
 	msg("You must specify an mmgen address list (and/or non-mmgen addresses with the '--addrlist' option)")
@@ -57,28 +57,27 @@ else:
 	seed_id,addr_data = "",[]
 	seed_id,addr_data = "",[]
 
 
 if 'addrlist' in opts:
 if 'addrlist' in opts:
-	l = get_lines_from_file(
-			opts['addrlist'],"non-mmgen addresses",remove_comments=True)
+	l = get_lines_from_file(opts['addrlist'],"non-mmgen addresses",
+			remove_comments=True)
 	addr_data += [(None,i) for i in l]
 	addr_data += [(None,i) for i in l]
 
 
-msg_r("Validating addresses...")
+from mmgen.bitcoin import verify_addr
+qmsg_r("Validating addresses...")
 for i in addr_data:
 for i in addr_data:
-	if not verify_addr(i[1]):
+	if not verify_addr(i[1],verbose=True):
 		msg("%s: invalid address" % i)
 		msg("%s: invalid address" % i)
 		sys.exit(2)
 		sys.exit(2)
-
-msg("OK")
+qmsg("OK")
 
 
 import mmgen.config as g
 import mmgen.config as g
 g.http_timeout = 3600
 g.http_timeout = 3600
 
 
 c = connect_to_bitcoind()
 c = connect_to_bitcoind()
 
 
-if not 'quiet' in opts:
-	message = """
+m = """
 Importing addresses can take a long time (>30 min.) on a low-powered computer.
 Importing addresses can take a long time (>30 min.) on a low-powered computer.
-	"""
-	confirm_or_exit(message, "continue", expect="YES")
+""".strip()
+confirm_or_exit(m, "continue", expect="YES")
 
 
 import threading
 import threading
 import time
 import time

+ 14 - 11
mmgen-passchg

@@ -39,6 +39,8 @@ help_data = {
 -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, --passwd-file        f  Get new passphrase from file 'f'
 -P, --passwd-file        f  Get new passphrase from file 'f'
+-q, --quiet                 Suppress warnings; overwrite files without
+                            prompting
 -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,23 +48,24 @@ NOTE: The key ID will change if either the passphrase or hash preset
 """.format(g.hash_preset)
 """.format(g.hash_preset)
 }
 }
 
 
-short_opts = "hd:HkL:p:P:v"
+short_opts = "hd:HkL:p:P:qv"
 long_opts  = "help","outdir=","show_hash_presets","keep_old_passphrase",\
 long_opts  = "help","outdir=","show_hash_presets","keep_old_passphrase",\
-			  "label=","hash_preset=","passwd_file=","verbose"
+			  "label=","hash_preset=","passwd_file=","quiet","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)
+if 'quiet' in opts: g.quiet = True
+if 'verbose' in opts: g.verbose = True
+check_opts(opts,long_opts)
 
 
 if 'show_hash_presets' in opts: show_hash_presets()
 if 'show_hash_presets' in opts: show_hash_presets()
 
 
-check_opts(opts,long_opts)
-
 if len(cmd_args) != 1:
 if len(cmd_args) != 1:
 	msg("One input file must be specified")
 	msg("One input file must be specified")
 	sys.exit(2)
 	sys.exit(2)
 infile = cmd_args[0]
 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)
 seed_id,key_id = metadata[:2]
 seed_id,key_id = metadata[:2]
 
 
 # Repeat on incorrect pw entry
 # Repeat on incorrect pw entry
@@ -86,7 +89,7 @@ else: opts['label'] = label  # Copy the old label
 
 
 if 'hash_preset' in opts:
 if 'hash_preset' in opts:
 	if hash_preset != opts['hash_preset']:
 	if hash_preset != opts['hash_preset']:
-		msg("Hash preset has changed (%s -> %s)" %
+		qmsg("Hash preset has changed (%s -> %s)" %
 			(hash_preset, opts['hash_preset']))
 			(hash_preset, opts['hash_preset']))
 		changed['preset'] = True
 		changed['preset'] = True
 	else:
 	else:
@@ -100,14 +103,14 @@ else:
 	new_passwd = get_new_passphrase("new passphrase", opts)
 	new_passwd = get_new_passphrase("new passphrase", opts)
 
 
 	if new_passwd == passwd:
 	if new_passwd == passwd:
-		msg("Passphrase is unchanged")
+		qmsg("Passphrase is unchanged")
 	else:
 	else:
-		msg("Passphrase has changed")
+		qmsg("Passphrase has changed")
 		passwd = new_passwd
 		passwd = new_passwd
 		changed['passwd'] = True
 		changed['passwd'] = True
 
 
 if 'preset' in changed or 'passwd' in changed: # Update key ID, salt
 if 'preset' in changed or 'passwd' in changed: # Update key ID, salt
-	msg("Will update salt and key ID")
+	qmsg("Will update salt and key ID")
 
 
 	from hashlib import sha256
 	from hashlib import sha256
 	from Crypto import Random
 	from Crypto import Random
@@ -115,9 +118,9 @@ if 'preset' in changed or 'passwd' in changed: # Update key ID, salt
 	salt = sha256(salt + Random.new().read(128)).digest()[:g.salt_len]
 	salt = sha256(salt + Random.new().read(128)).digest()[:g.salt_len]
 	key = make_key(passwd, salt, opts['hash_preset'])
 	key = make_key(passwd, salt, opts['hash_preset'])
 	new_key_id = make_chksum_8(key)
 	new_key_id = make_chksum_8(key)
-	msg("Key ID changed: %s -> %s" % (key_id,new_key_id))
+	qmsg("Key ID changed: %s -> %s" % (key_id,new_key_id))
 	key_id = new_key_id
 	key_id = new_key_id
-	enc_seed = encrypt_seed(seed, key, opts)
+	enc_seed = encrypt_seed(seed, key)
 elif not 'label' in changed:
 elif not 'label' in changed:
 	msg("Data unchanged.  No file will be written")
 	msg("Data unchanged.  No file will be written")
 	sys.exit(2)
 	sys.exit(2)

+ 10 - 16
mmgen-pywallet

@@ -45,6 +45,7 @@ mmgen-pywallet: Dump contents of a bitcoind wallet to file
 
 
 from mmgen.Opts import *
 from mmgen.Opts import *
 from mmgen.util import msg
 from mmgen.util import msg
+import mmgen.config as g
 from bsddb.db import *
 from bsddb.db import *
 import sys, time
 import sys, time
 import json
 import json
@@ -81,21 +82,18 @@ help_data = {
 -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'
 -P, --passwd-file   f  Get passphrase from file 'f'
 -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:ejkaK:P:qS"
+short_opts = "hd:ejkaK:P:S"
 long_opts  = "help","outdir=","echo_passphrase","json","keys","addrs",\
 long_opts  = "help","outdir=","echo_passphrase","json","keys","addrs",\
-			  "keysforaddrs=","passwd_file=","quiet","stdout"
+			  "keysforaddrs=","passwd_file=","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)
-
-from mmgen.util import check_infile
-
 check_opts(opts,long_opts)
 check_opts(opts,long_opts)
 
 
+from mmgen.util import check_infile
 if len(cmd_args) == 1:
 if len(cmd_args) == 1:
 	check_infile(cmd_args[0])
 	check_infile(cmd_args[0])
 else:
 else:
@@ -1683,14 +1681,11 @@ elif 'addrs' in opts:
 
 
 elif 'keysforaddrs' in opts:
 elif 'keysforaddrs' in opts:
 	from mmgen.util import get_lines_from_file
 	from mmgen.util import get_lines_from_file
-	usr_addrs = get_lines_from_file(opts['keysforaddrs'],"addresses")
-	for addr in usr_addrs:
-		try:
-			idx = wallet_addrs.index(addr)
-			data.append(json_db['keys'][idx]['sec'])
-		except:
-			msg("WARNING: Address '%s' not found" % addr)
-	data,ext,what = sorted(data),"keys","private keys"
+	usr_addrs = set(get_lines_from_file(opts['keysforaddrs'],"addresses",remove_comments=True))
+	data = [i['sec'] for i in json_db['keys'] if i['addr'] in usr_addrs]
+	ext,what = "keys","private keys"
+	if len(data) < len(usr_addrs):
+		msg("Warning: not all requested keys found")
 
 
 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" \
@@ -1703,8 +1698,7 @@ data = "\n".join(data) + "\n"
 
 
 # Output data
 # Output data
 if 'stdout' in opts:
 if 'stdout' in opts:
-	if 'addrs' in opts or 'quiet' in opts: confirm = False
-	else: confirm = True
+	confirm = False if 'addrs' in opts else True
 	write_to_stdout(data,"secret keys",confirm)
 	write_to_stdout(data,"secret keys",confirm)
 elif not sys.stdout.isatty():
 elif not sys.stdout.isatty():
 	write_to_stdout(data,"secret keys",confirm=False)
 	write_to_stdout(data,"secret keys",confirm=False)

+ 73 - 63
mmgen-txcreate

@@ -51,77 +51,82 @@ via an interactive menu.
 Ages of transactions are approximate based on an average block creation
 Ages of transactions are approximate based on an average block creation
 interval of %s minutes.
 interval of %s minutes.
 
 
-Addresses on the command line can be Bitcoin addresses or MMGen
-addresses in the form <seed ID>:<number>
+Addresses on the command line can be Bitcoin addresses or MMGen addresses
+of the form <seed ID>:<number>.
+
+To send all inputs (minus TX fee) to a single output, specify one address
+with no amount on the command line.
 """ % (Decimal(g.tx_fee),g.mins_per_block)
 """ % (Decimal(g.tx_fee),g.mins_per_block)
 }
 }
 
 
-short_opts = "ha:d:eiqf:"
-long_opts  = "help","addr_file","outdir=","echo_passphrase","info","quiet",\
-				"tx_fee="
+short_opts = "hd:eiqf:"
+long_opts  = "help","outdir=","echo_passphrase","info","quiet","tx_fee="
 
 
 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)
-
+if 'quiet' in opts: g.quiet = True
 check_opts(opts,long_opts)
 check_opts(opts,long_opts)
 
 
 if g.debug: show_opts_and_cmd_args(opts,cmd_args)
 if g.debug: show_opts_and_cmd_args(opts,cmd_args)
 
 
+c = connect_to_bitcoind()
+
 if not 'info' in opts:
 if not 'info' in opts:
 
 
-	outputs,addr_files,change_addr = [],[],""
+	tx_out,addr_data,b2m_map,acct_data,change_addr = {},[],{},[],""
 
 
-	for a in cmd_args:
-		if a.split(".")[-1] == g.addrfile_ext:
+	for a in [i for i in cmd_args if match_ext(i,g.addrfile_ext)]:
+		if match_ext(a,g.addrfile_ext):
 			check_infile(a)
 			check_infile(a)
-			addr_files.append(a)
-		elif "," in a:
-			outputs.append(a)
-		else:
+			addr_data.append(parse_addrs_file(a))
+
+	def mm2btc_addr_proc(c,mmadr,acct_data,addr_data,b2m_map):
+		btaddr,label = mmgen_addr_to_walletd(c,mmadr,acct_data)
+		if not btaddr:
+			btaddr,label = mmgen_addr_to_addr_data(mmadr,addr_data)
+		b2m_map[btaddr] = mmadr,label
+		return btaddr
+
+	for a in [i for i in cmd_args if not match_ext(i,g.addrfile_ext)]:
+		if "," in a:
+			a1,a2 = a.split(",")
+			if is_mmgen_addr(a1) or is_btc_addr(a1):
+				if is_mmgen_addr(a1):
+					btaddr = mm2btc_addr_proc(c,a1,acct_data,addr_data,b2m_map)
+				if is_btc_amt(a2):
+					tx_out[btaddr] = check_btc_amt(a2)
+				else:
+					msg("%s: invalid amount in argument '%s'" % (a2,a))
+					sys.exit(2)
+			else:
+				msg("%s: unrecognized subargument in argument '%s'" % (a1,a))
+				sys.exit(2)
+		elif is_mmgen_addr(a) or is_btc_addr(a):
 			if change_addr:
 			if change_addr:
 				msg("More than one change address specified: %s, %s" %
 				msg("More than one change address specified: %s, %s" %
 						(change_addr, a))
 						(change_addr, a))
 				sys.exit(2)
 				sys.exit(2)
-			change_addr = a
+			if is_mmgen_addr(a):
+				change_addr = mm2btc_addr_proc(c,a,acct_data,addr_data,b2m_map)
+			else:
+				change_addr = a
+			tx_out[change_addr] = 0
+		else:
+			msg("%s: unrecognized argument" % a)
+			sys.exit(2)
 
 
-	if not outputs:
+	if not tx_out:
 		msg("At least one output must be specified on the command line")
 		msg("At least one output must be specified on the command line")
 		sys.exit(2)
 		sys.exit(2)
 
 
-	addr_data = [parse_addrs_file(f) for f in addr_files]
-
 	tx_fee = opts['tx_fee'] if 'tx_fee' in opts else g.tx_fee
 	tx_fee = opts['tx_fee'] if 'tx_fee' in opts else g.tx_fee
-
-	try:
-		tx_fee = Decimal(tx_fee)
-	except:
-		msg("Invalid transaction fee format: %s" % tx_fee)
-		sys.exit(2)
-
+	tx_fee = check_btc_amt(tx_fee)
 	if tx_fee > g.max_tx_fee:
 	if tx_fee > g.max_tx_fee:
 		msg("Transaction fee too large: %s > %s" % (tx_fee,g.max_tx_fee))
 		msg("Transaction fee too large: %s > %s" % (tx_fee,g.max_tx_fee))
 		sys.exit(2)
 		sys.exit(2)
 
 
-	if change_addr:
-		if ":" in change_addr:
-			change_addr = mmgen_addr_to_btc_addr(change_addr,addr_data)
-		else:
-			check_address(change_addr)
-
-
-	tx_out = make_tx_out(outputs,addr_data)
-
-	for i in tx_out.keys():   check_address(i)
-	for i in tx_out.values(): check_btc_amt(i)
-
-	tx_fee = check_btc_amt(tx_fee)
-
 if g.debug: show_opts_and_cmd_args(opts,cmd_args)
 if g.debug: show_opts_and_cmd_args(opts,cmd_args)
 
 
-# Begin execution
-c = connect_to_bitcoind()
-
-if not 'quiet' in opts and not 'info' in opts:
-	do_license_msg(immed=True)
+if not 'info' in opts: do_license_msg(immed=True)
 
 
 # Begin test
 # Begin test
 # import mmgen.rpc
 # import mmgen.rpc
@@ -131,10 +136,9 @@ if not 'quiet' in opts and not 'info' in opts:
 us = c.listunspent()
 us = c.listunspent()
 
 
 if not us:
 if not us:
-	msg_r("""
+	msg("""
 No spendable outputs found!  Import addresses with balances into your
 No spendable outputs found!  Import addresses with balances into your
-watch-only wallet using 'mmgen-addrimport' and then re-run this program.
-""")
+watch-only wallet using 'mmgen-addrimport' and then re-run this program.""")
 	sys.exit(2)
 	sys.exit(2)
 
 
 # write_to_file("listunspent.json",repr(us))
 # write_to_file("listunspent.json",repr(us))
@@ -146,8 +150,9 @@ total = trim_exponent(sum([i.amount for i in unspent]))
 msg("Total unspent: %s BTC (%s outputs)" % (total, len(unspent)))
 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())
-msg("Total amount to spend: %s BTC" % send_amt)
+send_amt = sum([tx_out[i] for i in tx_out.keys()])
+msg("Total amount to spend: %s%s" % (
+		(send_amt or "Unknown")," BTC" if send_amt else ""))
 
 
 while True:
 while True:
 	sel_nums = select_outputs(unspent,
 	sel_nums = select_outputs(unspent,
@@ -155,17 +160,15 @@ while True:
 	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]
 
 
-	lbls = set([verify_mmgen_label(
-				i.account,return_str=True,check_label_len=True)
-					for i in sel_unspent])
-	lbls.discard("")
+	mmaddrs = set([parse_mmgen_label(i.account)[0] for i in sel_unspent])
+	mmaddrs.discard("")
 
 
-	if lbls and len(lbls) < len(sel_unspent):
-		msg(txmsg['mixed_inputs'] % ", ".join(sorted(lbls)))
+	if mmaddrs and len(mmaddrs) < len(sel_unspent):
+		msg(txmsg['mixed_inputs'] % ", ".join(sorted(mmaddrs)))
 		if not user_confirm("Accept?"):
 		if not user_confirm("Accept?"):
 			continue
 			continue
 
 
-	total_in = trim_exponent(sum([o.amount for o in sel_unspent]))
+	total_in = trim_exponent(sum([i.amount for i in sel_unspent]))
 	change   = trim_exponent(total_in - (send_amt + tx_fee))
 	change   = trim_exponent(total_in - (send_amt + tx_fee))
 
 
 	if change >= 0:
 	if change >= 0:
@@ -175,28 +178,35 @@ while True:
 	else:
 	else:
 		msg(txmsg['not_enough_btc'] % change)
 		msg(txmsg['not_enough_btc'] % change)
 
 
-
 if change > 0 and not change_addr:
 if change > 0 and not change_addr:
-	msg(txmsg['throwaway_change'] % (change, total_in-tx_fee))
+	msg(txmsg['throwaway_change'] % change)
 	sys.exit(2)
 	sys.exit(2)
 
 
+if change_addr in tx_out and not change:
+	msg("Warning: Change address will be unused as transaction produces no change")
+	del tx_out[change_addr]
+
+for k,v in tx_out.items(): tx_out[k] = float(v)
+
+if change > 0: tx_out[change_addr] = float(change)
+
 tx_in = [{"txid":i.txid, "vout":i.vout} for i in sel_unspent]
 tx_in = [{"txid":i.txid, "vout":i.vout} for i in sel_unspent]
-for i in tx_out.keys(): tx_out[i] = float(tx_out[i])
-if change: tx_out[change_addr] = float(change)
+
 if g.debug:
 if g.debug:
 	print "tx_in:", repr(tx_in)
 	print "tx_in:", repr(tx_in)
 	print "tx_out:", repr(tx_out)
 	print "tx_out:", repr(tx_out)
-tx_hex = c.createrawtransaction(tx_in,tx_out)
 
 
-msg("Transaction successfully created")
+tx_hex = c.createrawtransaction(tx_in,tx_out)
+qmsg("Transaction successfully created")
 prompt = "View decoded transaction? (y)es, (N)o, (v)iew in pager"
 prompt = "View decoded transaction? (y)es, (N)o, (v)iew in pager"
 reply = prompt_and_get_char(prompt,"YyNnVv",enter_ok=True)
 reply = prompt_and_get_char(prompt,"YyNnVv",enter_ok=True)
+
 if reply and reply in "YyVv":
 if reply and reply in "YyVv":
 	pager = True if reply in "Vv" else False
 	pager = True if reply in "Vv" else False
-	view_tx_data(c,[i.__dict__ for i in sel_unspent],tx_hex,pager=pager)
+	view_tx_data(c,[i.__dict__ for i in sel_unspent],tx_hex,b2m_map,pager=pager)
 
 
 prompt = "Save transaction?"
 prompt = "Save transaction?"
 if user_confirm(prompt,default_yes=True):
 if user_confirm(prompt,default_yes=True):
-	print_tx_to_file(tx_hex,sel_unspent,send_amt,opts)
+	print_tx_to_file(tx_hex,sel_unspent,send_amt or change,b2m_map,opts)
 else:
 else:
 	msg("Transaction not saved")
 	msg("Transaction not saved")

+ 3 - 4
mmgen-txsend

@@ -44,7 +44,7 @@ short_opts = "hd:q"
 long_opts  = "help","outdir=","quiet"
 long_opts  = "help","outdir=","quiet"
 
 
 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)
-
+if 'quiet' in opts: g.quiet = True
 check_opts(opts,long_opts)
 check_opts(opts,long_opts)
 
 
 if len(cmd_args) == 1:
 if len(cmd_args) == 1:
@@ -71,9 +71,9 @@ except:
 	msg("Invalid signed transaction data")
 	msg("Invalid signed transaction data")
 	sys.exit(3)
 	sys.exit(3)
 
 
-if not 'quiet' in opts: do_license_msg()
+do_license_msg()
 
 
-msg("\nSigned transaction file '%s' appears valid" % infile)
+qmsg("Signed transaction file '%s' is valid" % infile)
 
 
 warn   = "Once this transaction is sent, there's no taking it back!"
 warn   = "Once this transaction is sent, there's no taking it back!"
 what   = "broadcast this transaction to the network"
 what   = "broadcast this transaction to the network"
@@ -86,7 +86,6 @@ c = connect_to_bitcoind()
 
 
 try:
 try:
 	tx = c.sendrawtransaction(tx_sig)
 	tx = c.sendrawtransaction(tx_sig)
-#	tx = "deadbeef"
 except:
 except:
 	msg("Unable to send transaction")
 	msg("Unable to send transaction")
 	sys.exit(3)
 	sys.exit(3)

+ 49 - 38
mmgen-txsign

@@ -25,7 +25,7 @@ from mmgen.Opts import *
 from mmgen.license import *
 from mmgen.license import *
 import mmgen.config as g
 import mmgen.config as g
 from mmgen.tx import *
 from mmgen.tx import *
-from mmgen.util import msg
+from mmgen.util import msg,qmsg
 
 
 help_data = {
 help_data = {
 	'prog_name': sys.argv[0].split("/")[-1],
 	'prog_name': sys.argv[0].split("/")[-1],
@@ -36,18 +36,19 @@ 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 information about the transaction and exit
 -i, --info               Display information about the transaction and exit
--I, --tx_id              Display transaction ID 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'
 -P, --passwd-file     f  Get passphrase from file 'f'
 -P, --passwd-file     f  Get passphrase from file 'f'
 -q, --quiet              Suppress warnings; overwrite files without
 -q, --quiet              Suppress warnings; overwrite files without
                          prompting
                          prompting
+-V, --skip-key-preverify Skip optional key pre-verification step
 
 
 -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'
 -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
+-w, --use-wallet-dat     Get keys from a running bitcoind
 
 
 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.
@@ -60,9 +61,13 @@ prompted to enter the data.
 
 
 In cases of transactions with mixed mmgen and non-mmgen inputs, non-mmgen
 In cases of transactions with mixed mmgen and non-mmgen inputs, non-mmgen
 keys must be supplied in a separate file (WIF format, one key per line)
 keys must be supplied in a separate file (WIF format, one key per line)
-using the '--keys-from-file' option.  Alternatively, one may import the
-required mmgen keys into the bitcoind wallet.dat and use the
-'--force-wallet-dat' option.
+using the '--keys-from-file' option.  Alternatively, one may get keys from
+a running bitcoind using the '--force-wallet-dat' option.  First import the
+required mmgen keys using 'bitcoind importprivkey'.
+
+For transaction outputs that are MMGen addresses, MMGen-to-Bitcoin address
+mappings are verified.  Therefore, seed material for these addresses must
+be supplied on the command line.
 
 
 Seed data supplied in files must have the following extensions:
 Seed data supplied in files must have the following extensions:
    wallet:      '.{}'
    wallet:      '.{}'
@@ -72,78 +77,84 @@ Seed data supplied in files must have the following extensions:
 """.format(g.seed_ext,g.wallet_ext,g.seed_ext,g.mn_ext,g.brain_ext)
 """.format(g.seed_ext,g.wallet_ext,g.seed_ext,g.mn_ext,g.brain_ext)
 }
 }
 
 
-short_opts = "hd:eiIk:P:qb:msw"
+short_opts = "hd:eiIk:P:qVb:msw"
 long_opts  = "help","outdir=","echo_passphrase","info","tx_id",\
 long_opts  = "help","outdir=","echo_passphrase","info","tx_id",\
-			  "keys_from_file=","passwd_file=","quiet","from_brain=",\
-			  "from_mnemonic","from_seed","use_wallet_dat"
+			"keys_from_file=","passwd_file=","quiet","skip_key_preverify",\
+			"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)
-
+if "quiet" in opts: g.quiet = True
 check_opts(opts,long_opts)
 check_opts(opts,long_opts)
 
 
 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()
 c = connect_to_bitcoind()
 
 
 tx_file = infiles.pop(0)
 tx_file = infiles.pop(0)
 m = "" if 'tx_id' in opts else "transaction data"
 m = "" if 'tx_id' in opts else "transaction data"
 tx_data = get_lines_from_file(tx_file,m)
 tx_data = get_lines_from_file(tx_file,m)
 
 
-metadata,tx_hex,sig_data,inputs_data = parse_tx_data(tx_data,tx_file)
+metadata,tx_hex,inputs_data,b2m_map = parse_tx_data(tx_data,tx_file)
 
 
 if 'tx_id' in opts:
 if 'tx_id' in opts:
 	msg(metadata[0])
 	msg(metadata[0])
 	sys.exit(0)
 	sys.exit(0)
 
 
 if 'info' in opts:
 if 'info' in opts:
-	view_tx_data(c,inputs_data,tx_hex,metadata)
+	view_tx_data(c,inputs_data,tx_hex,b2m_map,metadata)
 	sys.exit(0)
 	sys.exit(0)
 
 
-if not 'quiet' in opts: do_license_msg(immed=True)
+do_license_msg(immed=True)
+
+# Are inputs mmgen addresses?
+mmgen_addrs = [i for i in inputs_data if parse_mmgen_label(i['account'])[0]]
+other_addrs = [i for i in inputs_data if not parse_mmgen_label(i['account'])[0]]
+
+keys = get_lines_from_file(opts['keys_from_file'],"key data",remove_comments=True) \
+	if 'keys_from_file' in opts else []
+
+if other_addrs and not keys and not 'use_wallet_dat' in opts:
+	missing_keys_errormsg(other_addrs)
+	sys.exit(2)
+
+if other_addrs and keys and not 'skip_key_preverify' in opts:
+	a = [i['address'] for i in other_addrs]
+	preverify_keys(a, keys)
+
+seeds = {}
+check_mmgen_to_btc_addr_mappings(inputs_data,b2m_map,infiles,seeds,opts)
 
 
-msg("Successfully opened transaction file '%s'" % tx_file)
+qmsg("Successfully opened transaction file '%s'" % tx_file)
 
 
 prompt = "View transaction data? (y)es, (N)o, (v)iew in pager"
 prompt = "View transaction data? (y)es, (N)o, (v)iew in pager"
 reply = prompt_and_get_char(prompt,"YyNnVv",enter_ok=True)
 reply = prompt_and_get_char(prompt,"YyNnVv",enter_ok=True)
 if reply and reply in "YyVv":
 if reply and reply in "YyVv":
 	p = True if reply in "Vv" else False
 	p = True if reply in "Vv" else False
-	view_tx_data(c,inputs_data,tx_hex,metadata,pager=p)
+	view_tx_data(c,inputs_data,tx_hex,b2m_map,metadata,pager=p)
 
 
-# Are inputs mmgen addresses?
-mmgen_addrs = [i for i in inputs_data if verify_mmgen_label(i['account'])]
-other_addrs = [i for i in inputs_data if not verify_mmgen_label(i['account'])]
-
-keys = get_lines_from_file(opts['keys_from_file'],"key data") \
-	if 'keys_from_file' in opts else []
+sig_data = [
+	{"txid":i['txid'],"vout":i['vout'],"scriptPubKey":i['scriptPubKey']}
+		for i in inputs_data]
 
 
 if mmgen_addrs:
 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,opts)
+	ml = [i['account'].split()[0] for i in mmgen_addrs]
+	keys += get_keys_for_mmgen_addrs(ml,infiles,seeds,opts)
 
 
 	if 'use_wallet_dat' in opts:
 	if 'use_wallet_dat' in opts:
 		sig_tx = sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys,opts)
 		sig_tx = sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys,opts)
 	else:
 	else:
 		sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
 		sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
 elif other_addrs:
 elif other_addrs:
-	if 'use_wallet_dat' in opts:
-		sig_tx = sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys,opts)
+	if keys:
+		sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
 	else:
 	else:
-		if keys:
-			sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
-		else:
-			missing_keys_errormsg(other_addrs)
-			sys.exit(2)
+		sig_tx = sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys,opts)
 
 
 if sig_tx['complete']:
 if sig_tx['complete']:
-	msg("Signing completed")
-	prompt = "Save signed transaction?"
+	prompt = "OK\nSave signed transaction?"
 	if user_confirm(prompt,default_yes=True):
 	if user_confirm(prompt,default_yes=True):
 		print_signed_tx_to_file(tx_hex,sig_tx['hex'],metadata,opts)
 		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("failed\nSome keys were missing.  Transaction could not be signed.")
 	sys.exit(3)
 	sys.exit(3)

+ 8 - 5
mmgen-walletchk

@@ -37,17 +37,20 @@ help_data = {
 -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'
 -P, --passwd-file   f  Get passphrase from file 'f'
+-q, --quiet            Suppress warnings; overwrite files without prompting
 -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:emP:sSv"
+short_opts = "hd:emP:qsSv"
 long_opts  = "help","outdir=","echo_passphrase","export_mnemonic",\
 long_opts  = "help","outdir=","echo_passphrase","export_mnemonic",\
-				"passwd_file=","export_seed","stdout","verbose"
+				"passwd_file=","quiet","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)
+if 'quiet' in opts: g.quiet = True
+if 'verbose' in opts: g.verbose = True
 
 
 # Argument sanity checks and processing:
 # Argument sanity checks and processing:
 check_opts(opts,long_opts)
 check_opts(opts,long_opts)
@@ -57,12 +60,12 @@ if len(cmd_args) != 1: usage(help_data)
 check_infile(cmd_args[0])
 check_infile(cmd_args[0])
 
 
 if 'export_mnemonic' in opts:
 if 'export_mnemonic' in opts:
-	msg("Exporting mnemonic data to file by user request")
+	qmsg("Exporting mnemonic data to file by user request")
 if 'export_seed' in opts:
 if 'export_seed' in opts:
-	msg("Exporting seed data to file by user request")
+	qmsg("Exporting seed data to file by user request")
 
 
 seed = get_seed_from_wallet(cmd_args[0], opts)
 seed = get_seed_from_wallet(cmd_args[0], opts)
-if seed: msg("Wallet is OK")
+if seed: qmsg("Wallet is OK")
 
 
 if 'export_mnemonic' in opts:
 if 'export_mnemonic' in opts:
 	wl = get_default_wordlist()
 	wl = get_default_wordlist()

+ 22 - 13
mmgen-walletgen

@@ -45,10 +45,11 @@ help_data = {
 -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, --passwd-file       f  Get passphrase from file 'f'
 -P, --passwd-file       f  Get passphrase from file 'f'
--q, --quiet                Suppress warnings; overwrite files without
+-q, --quiet                Produce quieter output; overwrite files without
                            prompting
                            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: {})
+-v, --verbose              Produce more verbose output
 
 
 -b, --from-brain       l,p Generate wallet from a user-created passphrase,
 -b, --from-brain       l,p Generate wallet from a user-created passphrase,
                            i.e. a "brainwallet", using seed length 'l' and
                            i.e. a "brainwallet", using seed length 'l' and
@@ -92,13 +93,14 @@ in all future invocations with that passphrase.
 	)
 	)
 }
 }
 
 
-short_opts = "hd:eHl:L:p:P:qu:b:ms"
+short_opts = "hd:eHl:L:p:P:qu:vb:ms"
 long_opts  = "help","outdir=","echo_passphrase","show_hash_presets","seed_len=",\
 long_opts  = "help","outdir=","echo_passphrase","show_hash_presets","seed_len=",\
 			"label=","hash_preset=","passwd_file=","quiet","usr_randlen=",\
 			"label=","hash_preset=","passwd_file=","quiet","usr_randlen=",\
-			"from_brain=","from_mnemonic","from_seed"
+		    "verbose","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)
-
+if 'quiet' in opts: g.quiet = True
+if 'verbose' in opts: g.verbose = True
 if 'show_hash_presets' in opts: show_hash_presets()
 if 'show_hash_presets' in opts: show_hash_presets()
 
 
 check_opts(opts,long_opts)
 check_opts(opts,long_opts)
@@ -108,15 +110,23 @@ if g.debug: show_opts_and_cmd_args(opts,cmd_args)
 if len(cmd_args) == 1:
 if len(cmd_args) == 1:
 	infile = cmd_args[0]
 	infile = cmd_args[0]
 	check_infile(infile)
 	check_infile(infile)
+	ext = infile.split(".")[-1]
+	ok_exts = g.seed_ext, g.mn_ext, g.brain_ext
+	for e in ok_exts:
+		if e == ext: break
+	else:
+		msg(
+"Input file must have one of the following extensions: .%s" % ", .".join(ok_exts))
+		sys.exit(1)
 elif len(cmd_args) == 0:
 elif len(cmd_args) == 0:
 	infile = ""
 	infile = ""
 else: usage(help_data)
 else: usage(help_data)
 
 
 # Begin execution
 # Begin execution
 
 
-if not 'quiet' in opts: do_license_msg()
+do_license_msg()
 
 
-msg_r("Acquiring random data from your computer...")
+qmsg_r("Acquiring random data from your computer...")
 
 
 from time import sleep
 from time import sleep
 sleep(0.25)
 sleep(0.25)
@@ -129,12 +139,12 @@ except:
 	msg("FAILED\nUnable to generate random numbers. Exiting")
 	msg("FAILED\nUnable to generate random numbers. Exiting")
 	sys.exit(2)
 	sys.exit(2)
 
 
-msg("OK")
+qmsg("OK")
 
 
 if g.debug: display_os_random_data(os_rand_data)
 if g.debug: display_os_random_data(os_rand_data)
 
 
 usr_keys,key_timings = get_random_data_from_user(opts)
 usr_keys,key_timings = get_random_data_from_user(opts)
-msg("")
+qmsg("")
 
 
 if g.debug: display_user_random_data(usr_keys,key_timings)
 if g.debug: display_user_random_data(usr_keys,key_timings)
 
 
@@ -143,7 +153,8 @@ usr_rand_data = sha256(usr_keys).digest() + \
 
 
 for i in 'from_mnemonic','from_brain','from_seed':
 for i in 'from_mnemonic','from_brain','from_seed':
 	if infile or (i in opts):
 	if infile or (i in opts):
-		seed = get_seed_retry(infile,opts); break
+		seed = get_seed_retry(infile,opts)
+		qmsg(""); break
 else:
 else:
 	# Truncate random data for smaller seed lengths
 	# Truncate random data for smaller seed lengths
 	seed = os_rand_data[0] + usr_rand_data
 	seed = os_rand_data[0] + usr_rand_data
@@ -152,9 +163,7 @@ else:
 salt = os_rand_data[1] + usr_rand_data
 salt = os_rand_data[1] + usr_rand_data
 salt = sha256(salt).digest()[:g.salt_len]
 salt = sha256(salt).digest()[:g.salt_len]
 
 
-if not 'quiet' in opts:
-	msg("""
-Now you must choose a passphrase to encrypt the seed with.  A key will be
+qmsg("""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
 generated from your passphrase using a hash preset of '%s'.  Please note that
 no strength checking of passphrases is performed.  For an empty passphrase,
 no strength checking of passphrases is performed.  For an empty passphrase,
 just hit ENTER twice.
 just hit ENTER twice.
@@ -164,6 +173,6 @@ passwd = get_new_passphrase("{} wallet passphrase".format(g.proj_name), opts)
 
 
 key = make_key(passwd, salt, opts['hash_preset'])
 key = make_key(passwd, salt, opts['hash_preset'])
 
 
-enc_seed = encrypt_seed(seed, key, opts)
+enc_seed = encrypt_seed(seed, key)
 
 
 write_wallet_to_file(seed,passwd,make_chksum_8(key),salt,enc_seed,opts)
 write_wallet_to_file(seed,passwd,make_chksum_8(key),salt,enc_seed,opts)

+ 4 - 2
mmgen/Opts.py

@@ -18,7 +18,7 @@
 
 
 import sys, getopt
 import sys, getopt
 import mmgen.config as g
 import mmgen.config as g
-from mmgen.util import msg
+from mmgen.util import msg,check_infile
 
 
 def usage(hd):
 def usage(hd):
 	print "USAGE: %s %s" % (hd['prog_name'], hd['usage'])
 	print "USAGE: %s %s" % (hd['prog_name'], hd['usage'])
@@ -117,7 +117,9 @@ def check_opts(opts,long_opts):
 
 
 			for ch in list(label):
 			for ch in list(label):
 				if ch not in g.wallet_label_symbols:
 				if ch not in g.wallet_label_symbols:
-					msg("'%s': illegal character in label" % ch)
+					msg("""
+"%s": illegal character in label.  Only ASCII characters are permitted.
+""".strip() % ch)
 					sys.exit(1)
 					sys.exit(1)
 		elif opt == 'from_brain':
 		elif opt == 'from_brain':
 			try:
 			try:

+ 11 - 25
mmgen/addr.py

@@ -24,6 +24,7 @@ from hashlib import sha256, sha512
 from binascii import hexlify, unhexlify
 from binascii import hexlify, unhexlify
 
 
 from mmgen.bitcoin import numtowif
 from mmgen.bitcoin import numtowif
+from mmgen.util import msg,qmsg,qmsg_r
 import mmgen.config as g
 import mmgen.config as g
 
 
 def test_for_keyconv():
 def test_for_keyconv():
@@ -47,27 +48,12 @@ faster address generation.
 
 
 
 
 def generate_addrs(seed, addrnums, opts):
 def generate_addrs(seed, addrnums, opts):
-	"""
-	generate_addresses(start, end, seed, opts)  => None
-
-	Generate a Bitcoin address or addresses end based on a seed, optionally
-	outputting secret keys
-
-	The 'keyconv' utility will be used for address generation if installed.
-	Otherwise an internal function is used
-
-	Supported options:
-		print_secret, no_addresses, no_keyconv, gen_what
-
-	Addresses are returned in a list of dictionaries with the following keys:
-		num, sec, wif, addr
-	"""
 
 
 	if not 'no_addresses' in opts:
 	if not 'no_addresses' in opts:
 		if 'no_keyconv' in opts or test_for_keyconv() == False:
 		if 'no_keyconv' in opts or test_for_keyconv() == False:
-			sys.stderr.write("Using (slow) internal ECDSA library for address generation\n")
+			msg("Using (slow) internal ECDSA library for address generation")
 			from mmgen.bitcoin import privnum2addr
 			from mmgen.bitcoin import privnum2addr
-			keyconv = ""
+			keyconv = False
 		else:
 		else:
 			from subprocess import Popen, PIPE
 			from subprocess import Popen, PIPE
 			keyconv = "keyconv"
 			keyconv = "keyconv"
@@ -75,12 +61,13 @@ def generate_addrs(seed, addrnums, opts):
 	a,t_addrs,i,out = sorted(addrnums),len(addrnums),0,[]
 	a,t_addrs,i,out = sorted(addrnums),len(addrnums),0,[]
 
 
 	while a:
 	while a:
-		seed = sha512(seed).digest(); i += 1   # round /i/
+		seed = sha512(seed).digest()
+		i += 1 # round /i/
 		if i < a[0]: continue
 		if i < a[0]: continue
 
 
 		a.pop(0)
 		a.pop(0)
 
 
-		sys.stderr.write("\rGenerating %s %s (%s of %s)" %
+		qmsg_r("\rGenerating %s %s (%s of %s)" %
 			(opts['gen_what'], i, t_addrs-len(a), t_addrs))
 			(opts['gen_what'], i, t_addrs-len(a), t_addrs))
 
 
 		# Secret key is double sha256 of seed hash round /i/
 		# Secret key is double sha256 of seed hash round /i/
@@ -110,7 +97,7 @@ def generate_addrs(seed, addrnums, opts):
 		import re
 		import re
 		w = re.sub('e*s$','',w)
 		w = re.sub('e*s$','',w)
 
 
-	sys.stderr.write("\rGenerated %s %s%s\n"%(t_addrs, w, " "*15))
+	qmsg("\rGenerated %s %s%s"%(t_addrs, w, " "*15))
 
 
 	return out
 	return out
 
 
@@ -153,10 +140,9 @@ def format_addr_data(addrlist, seed_chksum, opts):
 # Everything following a hash symbol '#' is a comment and ignored by {}.
 # 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
 # A text label of {} characters or less may be added to the right of each
 # address, and it will be appended to the bitcoind wallet label upon import.
 # address, and it will be appended to the bitcoind wallet label upon import.
-# The label may contain ASCII letters, numerals, and the symbols
-# '{}' and '{}'.
-""".format(g.proj_name.capitalize(),g.max_addr_label_len,
-		"', '".join(g.addr_label_punc[0:-1]), g.addr_label_punc[-1]).strip()
+# The label may contain printable ASCII symbols.
+""".strip().format(g.proj_name_cap,g.max_addr_label_len)
+
 	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())
@@ -210,7 +196,7 @@ def write_addr_data_to_file(seed, data, addr_list, opts):
 	else:                              ext = "akeys"
 	else:                              ext = "akeys"
 
 
 	if 'b16' in opts: ext = ext.replace("keys","xkeys")
 	if 'b16' in opts: ext = ext.replace("keys","xkeys")
-	from mmgen.util import write_to_file, make_chksum_8, msg
+	from mmgen.util import write_to_file, make_chksum_8
 	outfile = "{}[{}].{}".format(
 	outfile = "{}[{}].{}".format(
 			make_chksum_8(seed),
 			make_chksum_8(seed),
 			fmt_addr_list(addr_list),
 			fmt_addr_list(addr_list),

+ 32 - 21
mmgen/bitcoin.py

@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #!/usr/bin/env python
 #
 #
-# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
+# MMGen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
 # Copyright (C) 2013 by philemon <mmgen-py@yandex.com>
 # Copyright (C) 2013 by philemon <mmgen-py@yandex.com>
 #
 #
 # This program is free software: you can redistribute it and/or modify
 # This program is free software: you can redistribute it and/or modify
@@ -61,15 +61,10 @@ def pubhex2addr(pubhex):
 	pubkey = int(step2 + step4[:8], 16)
 	pubkey = int(step2 + step4[:8], 16)
 	return "1" + ("1" * extra_ones) + _numtob58(pubkey)
 	return "1" + ("1" * extra_ones) + _numtob58(pubkey)
 
 
-def privnum2addr(numpriv):
-	pko = ecdsa.SigningKey.from_secret_exponent(numpriv,secp256k1)
-	pubkey = hexlify(pko.get_verifying_key().to_string())
-	return pubhex2addr('04'+pubkey)
-
-def verify_addr(addr):
+def verify_addr(addr,verbose=False):
 
 
 	if addr[0] != "1":
 	if addr[0] != "1":
-		print "%s: Invalid address" % addr
+		if verbose: print "%s: Invalid address" % addr
 		return False
 		return False
 
 
   	num = _b58tonum(addr[1:])
   	num = _b58tonum(addr[1:])
@@ -80,7 +75,7 @@ def verify_addr(addr):
 	step2 = sha256(step1).hexdigest()
 	step2 = sha256(step1).hexdigest()
 
 
 	if step2[:8] != addr_hex[40:]:
 	if step2[:8] != addr_hex[40:]:
-		print "Invalid checksum in address %s" % ("1" + addr)
+		if verbose: print "Invalid checksum in address %s" % ("1" + addr)
 		return False
 		return False
 
 
 	return True
 	return True
@@ -115,8 +110,7 @@ def numtowif(numpriv):
 	key = step1 + step3[:8]
 	key = step1 + step3[:8]
 	return _numtob58(int(key,16))
 	return _numtob58(int(key,16))
 
 
-
-# The following are mmgen internal (non-bitcoin) b58 functions
+# The following are MMGen internal (non-Bitcoin) b58 functions
 
 
 # Drop-in replacements for b64encode() and b64decode():
 # Drop-in replacements for b64encode() and b64decode():
 # (well, not exactly: they yield numeric but not bytewise equivalence)
 # (well, not exactly: they yield numeric but not bytewise equivalence)
@@ -159,19 +153,36 @@ def b58decode_pad(s):
 	return _b58_pad(s,
 	return _b58_pad(s,
 		a=b58_lens,b=bin_lens,pad='\0',f=b58decode,w="base 58 numbers")
 		a=b58_lens,b=bin_lens,pad='\0',f=b58decode,w="base 58 numbers")
 
 
+# Compressed address support:
 
 
-################### FUNCTIONS UNUSED BY MMGEN: ###################
-
-# To check validity, recode with numtowif()
-def wiftonum(wifpriv):
+def wiftohex(wifpriv,compressed=False):
+	idx = 68 if compressed else 66
 	num = _b58tonum(wifpriv)
 	num = _b58tonum(wifpriv)
 	if num == False: return False
 	if num == False: return False
-	return (num % (1<<288)) >> 32
+	key = hex(num)[2:].rstrip('L')
+	if compressed and key[66:68] != '01': return False
+	round1 = sha256(unhexlify(key[:idx])).digest()
+	round2 = sha256(round1).hexdigest()
+	return key[2:66] if (key[:2] == '80' and key[idx:] == round2[:8]) else False
 
 
-def wiftohex(wifpriv):
+def hextowif(hexpriv,compressed=False):
+	step1 = '80' + hexpriv + ('01' if compressed else '')
+	step2 = sha256(unhexlify(step1)).digest()
+	step3 = sha256(step2).hexdigest()
+	key = step1 + step3[:8]
+	return _numtob58(int(key,16))
+
+def privnum2addr(numpriv,compressed=False):
+	pko = ecdsa.SigningKey.from_secret_exponent(numpriv,secp256k1)
+	pubkey = hexlify(pko.get_verifying_key().to_string())
+	if compressed:
+		p = '03' if pubkey[-1] in "13579bdf" else '02'
+		return pubhex2addr(p+pubkey[:64])
+	else:
+		return pubhex2addr('04'+pubkey)
+
+# Used only in test suite.  To check validity, recode with numtowif()
+def wiftonum(wifpriv):
 	num = _b58tonum(wifpriv)
 	num = _b58tonum(wifpriv)
 	if num == False: return False
 	if num == False: return False
-	key = hex(num)[2:].rstrip('L')
-	round1 = sha256(unhexlify(key[:66])).digest()
-	round2 = sha256(round1).hexdigest()
-	return key[2:66] if (key[:2] == '80' and key[66:] == round2[:8]) else False
+	return (num % (1<<288)) >> 32

+ 12 - 5
mmgen/config.py

@@ -18,12 +18,15 @@
 """
 """
 config.py:  Constants and configuration options for the mmgen suite
 config.py:  Constants and configuration options for the mmgen suite
 """
 """
+quiet,verbose = False,False
+min_screen_width = 80
 
 
 from decimal import Decimal
 from decimal import Decimal
 tx_fee        = Decimal("0.001")
 tx_fee        = Decimal("0.001")
 max_tx_fee    = Decimal("0.1")
 max_tx_fee    = Decimal("0.1")
 
 
 proj_name     = "mmgen"
 proj_name     = "mmgen"
+proj_name_cap = "MMGen"
 
 
 wallet_ext    = "mmdat"
 wallet_ext    = "mmdat"
 seed_ext      = "mmseed"
 seed_ext      = "mmseed"
@@ -71,12 +74,16 @@ hash_presets = {
 	'6': [17, 8, 20],
 	'6': [17, 8, 20],
 }
 }
 
 
+mmgen_idx_max_digits = 7
+
 from string import ascii_letters, digits
 from string import ascii_letters, digits
 
 
-addr_label_punc = ".","_",",","-"," "
-addr_label_symbols = tuple(ascii_letters + digits) + addr_label_punc
-max_addr_label_len = 16
+addr_label_symbols = tuple([chr(i) for i in range(0x20,0x7f)])
+max_addr_label_len = 32
 
 
-wallet_label_punc = ".", "_", " "
-wallet_label_symbols = tuple(ascii_letters + digits) + wallet_label_punc
+wallet_label_symbols = addr_label_symbols
 max_wallet_label_len = 32
 max_wallet_label_len = 32
+
+#addr_label_punc = ".","_",",","-"," ","(",")"
+#addr_label_symbols = tuple(ascii_letters + digits) + addr_label_punc
+#wallet_label_punc = addr_label_punc

+ 4 - 0
mmgen/license.py

@@ -585,6 +585,10 @@ copy of the Program in return for a fee.
 }
 }
 
 
 def do_license_msg(immed=False):
 def do_license_msg(immed=False):
+
+	import mmgen.config as g
+	if g.quiet: return
+
 	msg(gpl['warning'])
 	msg(gpl['warning'])
 	prompt = "%s " % gpl['prompt'].strip()
 	prompt = "%s " % gpl['prompt'].strip()
 
 

+ 1 - 1
mmgen/rpc/proxy.py

@@ -106,7 +106,7 @@ class AuthServiceProxy(object):
 		except:
 		except:
 			from mmgen.util import msg
 			from mmgen.util import msg
 			import sys
 			import sys
-			msg("\nUnable to connect to bitcoind.")
+			msg("Unable to connect to bitcoind")
 			sys.exit(2)
 			sys.exit(2)
 
 
 		httpresp = self.__conn.getresponse()
 		httpresp = self.__conn.getresponse()

+ 191 - 0
mmgen/term.py

@@ -0,0 +1,191 @@
+#!/usr/bin/env python
+#
+# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
+# Copyright (C) 2013 by philemon <mmgen-py@yandex.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+term.py:  Terminal-handling routines for the mmgen suite
+"""
+
+import sys, os, struct
+
+def msg(s):   sys.stderr.write(s + "\n")
+def msg_r(s): sys.stderr.write(s)
+
+def _kb_hold_protect_unix():
+
+	fd = sys.stdin.fileno()
+	old = termios.tcgetattr(fd)
+	tty.setcbreak(fd)
+
+	timeout = float(0.3)
+
+	try:
+		while True:
+			key = select([sys.stdin], [], [], timeout)[0]
+			if key: sys.stdin.read(1)
+			else: break
+	except KeyboardInterrupt:
+		msg("\nUser interrupt")
+		sys.exit(1)
+	finally:
+		termios.tcsetattr(fd, termios.TCSADRAIN, old)
+
+
+def _get_keypress_unix(prompt="",immed_chars=""):
+
+	msg_r(prompt)
+	timeout = float(0.3)
+
+	fd = sys.stdin.fileno()
+	old = termios.tcgetattr(fd)
+	tty.setcbreak(fd)
+
+	try:
+		while True:
+			select([sys.stdin], [], [], False)
+			ch = sys.stdin.read(1)
+			if immed_chars == "ALL" or ch in immed_chars:
+				return ch
+			if immed_chars == "ALL_EXCEPT_ENTER" and not ch in "\n\r":
+				return ch
+			second_key = select([sys.stdin], [], [], timeout)[0]
+			if second_key: continue
+			else: return ch
+	except KeyboardInterrupt:
+		msg("\nUser interrupt")
+		sys.exit(1)
+	finally:
+		termios.tcsetattr(fd, termios.TCSADRAIN, old)
+
+
+def _kb_hold_protect_mswin():
+
+	timeout = float(0.5)
+
+	try:
+		while True:
+			hit_time = time.time()
+			while True:
+				if msvcrt.kbhit():
+					msvcrt.getch()
+					break
+				if float(time.time() - hit_time) > timeout:
+					return
+	except KeyboardInterrupt:
+		msg("\nUser interrupt")
+		sys.exit(1)
+
+
+def _get_keypress_mswin(prompt="",immed_chars=""):
+
+	msg_r(prompt)
+	timeout = float(0.5)
+
+	try:
+		while True:
+			if msvcrt.kbhit():
+				ch = msvcrt.getch()
+
+				if ord(ch) == 3: raise KeyboardInterrupt
+
+				if immed_chars == "ALL" or ch in immed_chars:
+					return ch
+				if immed_chars == "ALL_EXCEPT_ENTER" and not ch in "\n\r":
+					return ch
+
+				hit_time = time.time()
+
+				while True:
+					if msvcrt.kbhit(): break
+					if float(time.time() - hit_time) > timeout:
+						return ch
+	except KeyboardInterrupt:
+		msg("\nUser interrupt")
+		sys.exit(1)
+
+
+def _get_terminal_size_linux():
+
+	def ioctl_GWINSZ(fd):
+		try:
+			import fcntl
+			import termios
+			cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234'))
+			return cr
+		except:
+			pass
+
+	cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
+
+	if not cr:
+		try:
+			fd = os.open(os.ctermid(), os.O_RDONLY)
+			cr = ioctl_GWINSZ(fd)
+			os.close(fd)
+		except:
+			pass
+
+	if not cr:
+		try:
+			cr = (os.environ['LINES'], os.environ['COLUMNS'])
+		except:
+			return 80,25
+
+	return int(cr[1]), int(cr[0])
+
+
+def _get_terminal_size_mswin():
+	try:
+		from ctypes import windll, create_string_buffer
+		# stdin handle is -10
+		# stdout handle is -11
+		# stderr handle is -12
+		h = windll.kernel32.GetStdHandle(-12)
+		csbi = create_string_buffer(22)
+		res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi)
+		if res:
+			(bufx, bufy, curx, cury, wattr,
+			 left, top, right, bottom,
+			 maxx, maxy) = struct.unpack("hhhhHhhhhhh", csbi.raw)
+			sizex = right - left + 1
+			sizey = bottom - top + 1
+			return sizex, sizey
+	except:
+		return 80,25
+
+try:
+	import tty, termios
+	from select import select
+	get_char = _get_keypress_unix
+	kb_hold_protect = _kb_hold_protect_unix
+	get_terminal_size = _get_terminal_size_linux
+except:
+	try:
+		import msvcrt, time
+		get_char = _get_keypress_mswin
+		kb_hold_protect = _kb_hold_protect_mswin
+		get_terminal_size = _get_terminal_size_mswin
+	except:
+		if not sys.platform.startswith("linux") \
+				and not sys.platform.startswith("win"):
+			msg("Unsupported platform: %s" % sys.platform)
+			msg("This program currently runs only on Linux and Windows")
+		else:
+			msg("Unable to set terminal mode")
+		sys.exit(2)
+
+if __name__ == "__main__":
+	print "columns: {}, rows: {}".format(*get_terminal_size())

+ 48 - 20
mmgen/tests/bitcoin.py

@@ -19,18 +19,19 @@
 bitcoin.py:  Test suite for mmgen.bitcoin module
 bitcoin.py:  Test suite for mmgen.bitcoin module
 """
 """
 
 
-from mmgen.bitcoin import *
-from mmgen.util import msg
-from test import *
+import mmgen.bitcoin as b
+from   mmgen.util import msg
+from   mmgen.tests.test import *
+from   binascii import hexlify, unhexlify
 
 
 import sys
 import sys
 
 
 def b58_randenc():
 def b58_randenc():
 	r = get_random(24)
 	r = get_random(24)
-	r_enc = b58encode(r)
+	r_enc = b.b58encode(r)
 	print "Data (hex):    %s" % hexlify(r)
 	print "Data (hex):    %s" % hexlify(r)
 	print "Base 58:       %s" % r_enc
 	print "Base 58:       %s" % r_enc
-	r_dec = b58decode(r_enc)
+	r_dec = b.b58decode(r_enc)
 	print "Decoded data:  %s" % hexlify(r_dec)
 	print "Decoded data:  %s" % hexlify(r_dec)
 	if r_dec != r:
 	if r_dec != r:
 		print "ERROR!  Decoded data doesn't match original"
 		print "ERROR!  Decoded data doesn't match original"
@@ -55,9 +56,8 @@ def keyconv_compare_randloop(loops, quiet=False):
 		else:
 		else:
 			print "%s iterations completed" % i
 			print "%s iterations completed" % i
 
 
-	except:
-		print "\nUser interrupt"
-
+	except KeyboardInterrupt:
+		msg("\nUser interrupt")
 
 
 def keyconv_compare(wif,quiet=False):
 def keyconv_compare(wif,quiet=False):
 	do_msg = nomsg if quiet else msg
 	do_msg = nomsg if quiet else msg
@@ -69,7 +69,7 @@ def keyconv_compare(wif,quiet=False):
 		print "Error with execution of keyconv"
 		print "Error with execution of keyconv"
 		sys.exit(3)
 		sys.exit(3)
 	kc_addr = dict([j.split() for j in p.stdout.readlines()])['Address:']
 	kc_addr = dict([j.split() for j in p.stdout.readlines()])['Address:']
-	addr = privnum2addr(wiftonum(wif))
+	addr = b.privnum2addr(b.wiftonum(wif))
 	do_msg("Address (mmgen):   %s" % addr)
 	do_msg("Address (mmgen):   %s" % addr)
 	do_msg("Address (keyconv): %s" % kc_addr)
 	do_msg("Address (keyconv): %s" % kc_addr)
 	if (kc_addr != addr):
 	if (kc_addr != addr):
@@ -113,13 +113,13 @@ def numtowif_rand(quiet=False):
 
 
 def strtob58(s,quiet=False):
 def strtob58(s,quiet=False):
 	print "Input:         %s" % s
 	print "Input:         %s" % s
-	s_enc = b58encode(s)
+	s_enc = b.b58encode(s)
 	print "Encoded data:  %s" % s_enc
 	print "Encoded data:  %s" % s_enc
-	s_dec = b58decode(s_enc)
+	s_dec = b.b58decode(s_enc)
 	print "Decoded data:  %s" % s_dec
 	print "Decoded data:  %s" % s_dec
 	test_equality(s,s_dec,[""],quiet)
 	test_equality(s,s_dec,[""],quiet)
 
 
-def hextob58(s_in,f_enc=b58encode, f_dec=b58decode, quiet=False):
+def hextob58(s_in,f_enc=b.b58encode, f_dec=b.b58decode, quiet=False):
 	do_msg = nomsg if quiet else msg
 	do_msg = nomsg if quiet else msg
 	do_msg("Input:         %s" % s_in)
 	do_msg("Input:         %s" % s_in)
 	s_bin = unhexlify(s_in)
 	s_bin = unhexlify(s_in)
@@ -129,7 +129,7 @@ def hextob58(s_in,f_enc=b58encode, f_dec=b58decode, quiet=False):
 	do_msg("Recoded data:  %s" % s_dec)
 	do_msg("Recoded data:  %s" % s_dec)
 	test_equality(s_in,s_dec,["0"],quiet)
 	test_equality(s_in,s_dec,["0"],quiet)
 
 
-def b58tohex(s_in,f_dec=b58decode, f_enc=b58encode,quiet=False):
+def b58tohex(s_in,f_dec=b.b58decode, f_enc=b.b58encode,quiet=False):
 	print "Input:         %s" % s_in
 	print "Input:         %s" % s_in
 	s_dec = f_dec(s_in)
 	s_dec = f_dec(s_in)
 	print "Decoded data:  %s" % hexlify(s_dec)
 	print "Decoded data:  %s" % hexlify(s_dec)
@@ -138,25 +138,25 @@ def b58tohex(s_in,f_dec=b58decode, f_enc=b58encode,quiet=False):
 	test_equality(s_in,s_enc,["1"],quiet)
 	test_equality(s_in,s_enc,["1"],quiet)
 
 
 def hextob58_pad(s_in, quiet=False):
 def hextob58_pad(s_in, quiet=False):
-	hextob58(s_in,f_enc=b58encode_pad, f_dec=b58decode_pad, quiet=quiet)
+	hextob58(s_in,f_enc=b.b58encode_pad, f_dec=b.b58decode_pad, quiet=quiet)
 
 
 def b58tohex_pad(s_in, quiet=False):
 def b58tohex_pad(s_in, quiet=False):
-	b58tohex(s_in,f_dec=b58decode_pad, f_enc=b58encode_pad, quiet=quiet)
+	b58tohex(s_in,f_dec=b.b58decode_pad, f_enc=b.b58encode_pad, quiet=quiet)
 
 
 def	hextob58_pad_randloop(loops, quiet=False):
 def	hextob58_pad_randloop(loops, quiet=False):
 	try:
 	try:
 		for i in range(1,int(loops)+1):
 		for i in range(1,int(loops)+1):
 			r = hexlify(get_random(32))
 			r = hexlify(get_random(32))
-			hextob58(r,f_enc=b58encode_pad, f_dec=b58decode_pad, quiet=quiet)
+			hextob58(r,f_enc=b.b58encode_pad, f_dec=b.b58decode_pad, quiet=quiet)
 			if not quiet: print
 			if not quiet: print
 			if not i % 100 and quiet:
 			if not i % 100 and quiet:
 				sys.stderr.write("\riteration: %i " % i)
 				sys.stderr.write("\riteration: %i " % i)
 
 
 		sys.stderr.write("\r%s iterations completed\n" % i)
 		sys.stderr.write("\r%s iterations completed\n" % i)
-	except:
-		print "User interrupt"
+	except KeyboardInterrupt:
+		msg("\nUser interrupt")
 
 
-def test_wiftohex(s_in,f_dec=wiftohex,f_enc=numtowif):
+def test_wiftohex(s_in,f_dec=b.wiftohex,f_enc=b.numtowif):
 	print "Input:         %s" % s_in
 	print "Input:         %s" % s_in
 	s_dec = f_dec(s_in)
 	s_dec = f_dec(s_in)
 	print "Decoded data:  %s" % s_dec
 	print "Decoded data:  %s" % s_dec
@@ -170,7 +170,31 @@ def hextosha256(s_in):
 
 
 def pubhextoaddr(s_in):
 def pubhextoaddr(s_in):
 	print "Entered data:   %s" % s_in
 	print "Entered data:   %s" % s_in
-	s_enc = pubhex2addr(s_in)
+	s_enc = b.pubhex2addr(s_in)
+	print "Encoded data:   %s" % s_enc
+
+def hextowif_comp(s_in):
+	print "Entered data:   %s" % s_in
+	s_enc = b.hextowif(s_in,compressed=True)
+	print "Encoded data:   %s" % s_enc
+	s_dec = b.wiftohex(s_enc,compressed=True)
+	print "Decoded data:   %s" % s_dec
+
+def wiftohex_comp(s_in):
+	print "Entered data:   %s" % s_in
+	s_enc = b.wiftohex(s_in,compressed=True)
+	print "Encoded data:   %s" % s_enc
+	s_dec = b.hextowif(s_enc,compressed=True)
+	print "Decoded data:   %s" % s_dec
+
+def privhextoaddr_comp(hexpriv):
+	print b.privnum2addr(int(hexpriv,16),compressed=True)
+
+def wiftoaddr_comp(s_in):
+	print "Entered data:   %s" % s_in
+	s_enc = b.wiftohex(s_in,compressed=True)
+	print "Encoded data:   %s" % s_enc
+	s_enc = b.privnum2addr(int(s_enc,16),compressed=True)
 	print "Encoded data:   %s" % s_enc
 	print "Encoded data:   %s" % s_enc
 
 
 tests = {
 tests = {
@@ -188,6 +212,10 @@ tests = {
 	"hextosha256":              ['hexnum [str]','quiet [bool=False]'],
 	"hextosha256":              ['hexnum [str]','quiet [bool=False]'],
 	"hextowiftopubkey":         ['hexnum [str]','quiet [bool=False]'],
 	"hextowiftopubkey":         ['hexnum [str]','quiet [bool=False]'],
 	"pubhextoaddr":             ['hexnum [str]','quiet [bool=False]'],
 	"pubhextoaddr":             ['hexnum [str]','quiet [bool=False]'],
+	"hextowif_comp":            ['hexnum [str]'],
+	"wiftohex_comp":            ['wif [str]'],
+	"privhextoaddr_comp":       ['hexnum [str]'],
+	"wiftoaddr_comp":           ['wif [str]'],
 }
 }
 
 
 
 

+ 1 - 1
mmgen/tests/test.py

@@ -20,8 +20,8 @@ test.py:  Shared routines for mmgen test suite
 """
 """
 
 
 import sys
 import sys
-from mmgen.util import msg
 
 
+def msg(s): print s
 def nomsg(s): pass
 def nomsg(s): pass
 
 
 def test_equality(num_in,num_out,wl,quiet=False):
 def test_equality(num_in,num_out,wl,quiet=False):

+ 347 - 197
mmgen/tx.py

@@ -29,9 +29,7 @@ txmsg = {
 'not_enough_btc': "Not enough BTC in the inputs for this transaction (%s BTC)",
 'not_enough_btc': "Not enough BTC in the inputs for this transaction (%s BTC)",
 'throwaway_change': """
 'throwaway_change': """
 ERROR: This transaction produces change (%s BTC); however, no change
 ERROR: This transaction produces change (%s BTC); however, no change
-address was specified.  Total inputs - transaction fee = %s BTC.
-To create a valid transaction with no change address, send this sum to the
-specified recipient address.
+address was specified.
 """.strip(),
 """.strip(),
 'mixed_inputs': """
 'mixed_inputs': """
 NOTE: This transaction uses a mixture of both mmgen and non-mmgen inputs,
 NOTE: This transaction uses a mixture of both mmgen and non-mmgen inputs,
@@ -73,29 +71,35 @@ def trim_exponent(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()
 
 
 
 
-def check_address(rcpt_address):
-	from mmgen.bitcoin import verify_addr
-	if not verify_addr(rcpt_address):
-		sys.exit(3)
 
 
-
-def check_btc_amt(send_amt):
+def is_btc_amt(amt):
 
 
 	from decimal import Decimal
 	from decimal import Decimal
 	try:
 	try:
-		retval = Decimal(send_amt)
+		ret = Decimal(amt)
 	except:
 	except:
-		msg("%s: Invalid amount" % send_amt)
-		sys.exit(3)
+		msg("%s: Invalid amount" % amt)
+		return False
 
 
 	if g.debug:
 	if g.debug:
-		print "Decimal(amt): %s\nAs tuple: %s" % (send_amt,repr(retval.as_tuple()))
+		print "Decimal(amt): %s\nAs tuple: %s" % (amt,repr(ret.as_tuple()))
 
 
-	if retval.as_tuple()[-1] < -8:
-		msg("%s: Too many decimal places in amount" % send_amt)
-		sys.exit(3)
+	if ret.as_tuple()[-1] < -8:
+		msg("%s: Too many decimal places in amount" % amt)
+		return False
 
 
-	return trim_exponent(retval)
+	if ret == 0:
+		msg("Requested zero BTC amount")
+		return False
+
+	return trim_exponent(ret)
+
+def check_btc_amt(amt):
+	ret = is_btc_amt(amt)
+	if ret:
+		return ret
+	else:
+		sys.exit(3)
 
 
 
 
 def get_bitcoind_cfg_options(cfg_keys):
 def get_bitcoind_cfg_options(cfg_keys):
@@ -108,7 +112,7 @@ def get_bitcoind_cfg_options(cfg_keys):
 		data_dir = r"Application Data\Bitcoin"
 		data_dir = r"Application Data\Bitcoin"
 		cfg_file = "%s\%s\%s" % (os.environ["HOMEPATH"],data_dir,"bitcoin.conf")
 		cfg_file = "%s\%s\%s" % (os.environ["HOMEPATH"],data_dir,"bitcoin.conf")
 	else:
 	else:
-		msg("Neither $HOME nor %HOMEPATH% is set")
+		msg("Neither $HOME nor %HOMEPATH% are set")
 		msg("Don't know where to look for 'bitcoin.conf'")
 		msg("Don't know where to look for 'bitcoin.conf'")
 		sys.exit(3)
 		sys.exit(3)
 
 
@@ -135,17 +139,16 @@ def get_bitcoind_cfg_options(cfg_keys):
 	return cfg
 	return cfg
 
 
 
 
-def print_tx_to_file(tx,sel_unspent,send_amt,opts):
+def print_tx_to_file(tx,sel_unspent,send_amt,b2m_map,opts):
 	tx_id = make_chksum_6(unhexlify(tx)).upper()
 	tx_id = make_chksum_6(unhexlify(tx)).upper()
 	outfile = "tx_%s[%s].raw" % (tx_id,send_amt)
 	outfile = "tx_%s[%s].raw" % (tx_id,send_amt)
 	if 'outdir' in opts:
 	if 'outdir' in opts:
 		outfile = "%s/%s" % (opts['outdir'], outfile)
 		outfile = "%s/%s" % (opts['outdir'], outfile)
 	metadata = "%s %s %s" % (tx_id, send_amt, make_timestamp())
 	metadata = "%s %s %s" % (tx_id, send_amt, make_timestamp())
-	sig_data = [{"txid":i.txid,"vout":i.vout,"scriptPubKey":i.scriptPubKey}
-					for i in sel_unspent]
 	data = "%s\n%s\n%s\n%s\n" % (
 	data = "%s\n%s\n%s\n%s\n" % (
-			metadata, tx, repr(sig_data),
-			repr([i.__dict__ for i in sel_unspent])
+			metadata, tx,
+			repr([i.__dict__ for i in sel_unspent]),
+			repr(b2m_map)
 		)
 		)
 	write_to_file(outfile,data,confirm=False)
 	write_to_file(outfile,data,confirm=False)
 	msg("Transaction data saved to file '%s'" % outfile)
 	msg("Transaction data saved to file '%s'" % outfile)
@@ -176,17 +179,11 @@ def format_unspent_outputs_for_printing(out,sort_info,total):
 		"Amount (BTC)","Age (days)", "Comment")]
 		"Amount (BTC)","Age (days)", "Comment")]
 
 
 	for n,i in enumerate(out):
 	for n,i in enumerate(out):
-		if verify_mmgen_label(i.account):
-			s = i.account.split(None,1)
-			mmid,cmt = s[0],(s[1] if len(s) == 2 else "")
-		else:
-			mmid,cmt = "",i.account
-
 		addr = "=" if i.skip == "addr" and "grouped" in sort_info else i.address
 		addr = "=" if i.skip == "addr" and "grouped" in sort_info else i.address
 		tx = " " * 63 + "=" \
 		tx = " " * 63 + "=" \
 			  if i.skip == "txid" and "grouped" in sort_info else str(i.txid)
 			  if i.skip == "txid" and "grouped" in sort_info else str(i.txid)
 
 
-		s = pfs % (str(n+1)+")", tx+","+str(i.vout),addr,mmid,i.amt,i.days,cmt)
+		s = pfs % (str(n+1)+")", tx+","+str(i.vout),addr,i.mmid,i.amt,i.days,i.label)
 		pout.append(s.rstrip())
 		pout.append(s.rstrip())
 
 
 	return \
 	return \
@@ -197,23 +194,20 @@ def format_unspent_outputs_for_printing(out,sort_info,total):
 
 
 def sort_and_view(unspent):
 def sort_and_view(unspent):
 
 
-	def s_amt(a,b):  return cmp(a.amount,b.amount)
-	def s_txid(a,b):
-		return cmp("%s %03s" % (a.txid,a.vout), "%s %03s" % (b.txid,b.vout))
-	def s_addr(a,b): return cmp(a.address,b.address)
-	def s_age(a,b):  return cmp(b.confirmations,a.confirmations)
-	def s_mmgen(a,b): return cmp(a.account,b.account)
+	def s_amt(i):   return i.amount
+	def s_txid(i):  return "%s %03s" % (i.txid,i.vout)
+	def s_addr(i):  return i.address
+	def s_age(i):   return i.confirmations
+	def s_mmgen(i): return i.account
 
 
-	fs = " %-4s %-11s %-2s %-34s %-13s %-s"
 	sort,group,show_mmaddr,reverse = "",False,False,False
 	sort,group,show_mmaddr,reverse = "",False,False,False
 	total = trim_exponent(sum([i.amount for i in unspent]))
 	total = trim_exponent(sum([i.amount for i in unspent]))
 
 
 	hdr_fmt   = "UNSPENT OUTPUTS (sort order: %s)  Total BTC: %s"
 	hdr_fmt   = "UNSPENT OUTPUTS (sort order: %s)  Total BTC: %s"
-	table_hdr = fs % ("Num","TX id  Vout","","Address","Amount (BTC)", "Age(days)")
 
 
 	options_msg = """
 	options_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
-Format options: [g]roup, show [m]mgen addr
+Display options: [g]roup, show [m]mgen addr, r[e]draw screen
 """.strip()
 """.strip()
 	prompt = \
 	prompt = \
 "('q' = quit sorting, 'p' = print to file, 'v' = pager view, 'w' = wide view): "
 "('q' = quit sorting, 'p' = print to file, 'v' = pager view, 'w' = wide view): "
@@ -222,57 +216,82 @@ Format options: [g]roup, show [m]mgen addr
 	print_to_file_msg = ""
 	print_to_file_msg = ""
 	msg("")
 	msg("")
 
 
+	from mmgen.term import get_terminal_size
+
+	max_acct_len = max([len(i.account) for i in unspent])
+
 	while True:
 	while True:
-		out = deepcopy(unspent)
-		for i in out: i.skip = ""
+		cols = get_terminal_size()[0]
+		if cols < g.min_screen_width:
+			msg("mmgen-txcreate requires a screen at least %s characters wide" %
+					g.min_screen_width)
+			sys.exit(2)
+
+		addr_w = min(34+((1+max_acct_len) if show_mmaddr else 0),cols-46)
+		tx_w = max(11,min(64, cols-addr_w-32))
+		fs = " %-4s %-" + str(tx_w) + "s %-2s %-" + str(addr_w) + "s %-13s %-s"
+		table_hdr = fs % ("Num","TX id  Vout","","Address",
+							"Amount (BTC)","Age(d)")
+
+		unsp = deepcopy(unspent)
+		for i in unsp: i.skip = ""
 		if group and (sort == "address" or sort == "txid"):
 		if group and (sort == "address" or sort == "txid"):
-			for n in range(len(out)-1):
-				a,b = out[n],out[n+1]
+			for n in range(len(unsp)-1):
+				a,b = unsp[n],unsp[n+1]
 				if sort == "address" and a.address == b.address: b.skip = "addr"
 				if sort == "address" and a.address == b.address: b.skip = "addr"
 				elif sort == "txid" and a.txid == b.txid:        b.skip = "txid"
 				elif sort == "txid" and a.txid == b.txid:        b.skip = "txid"
 
 
-		for i in out:
+		for i in unsp:
 			amt = str(trim_exponent(i.amount))
 			amt = str(trim_exponent(i.amount))
 			lfill = 3 - len(amt.split(".")[0]) if "." in amt else 3 - len(amt)
 			lfill = 3 - len(amt.split(".")[0]) if "." in amt else 3 - len(amt)
 			i.amt = " "*lfill + amt
 			i.amt = " "*lfill + amt
 			i.days = int(i.confirmations * g.mins_per_block / (60*24))
 			i.days = int(i.confirmations * g.mins_per_block / (60*24))
 
 
+			i.mmid,i.label = parse_mmgen_label(i.account)
+
 			if i.skip == "addr":
 			if i.skip == "addr":
 				i.addr = "|" + "." * 33
 				i.addr = "|" + "." * 33
 			else:
 			else:
-				if show_mmaddr:
-					if verify_mmgen_label(i.account):
-						i.addr = "%s.. %s" % (i.address[:4],i.account)
-					else:
-						i.addr = i.address
+				if show_mmaddr and i.mmid:
+					acct_w   = min(max_acct_len, max(24,int(addr_w-10)))
+					btaddr_w = addr_w - acct_w - 1
+
+					dots = ".." if btaddr_w < len(i.address) else ""
+
+					i.addr = "%s%s %s" % (
+						i.address[:btaddr_w-len(dots)],
+						dots,
+						i.account[:acct_w])
 				else:
 				else:
 					i.addr = i.address
 					i.addr = i.address
 
 
-			i.tx = "       |..." if i.skip == "txid" else i.txid[:8]+"..."
+			dots = "..." if tx_w < 64 else ""
+			i.tx = " " * (tx_w-4) + "|..." if i.skip == "txid" \
+					else i.txid[:tx_w-len(dots)]+dots
 
 
 		sort_info = ["reverse"] if reverse else []
 		sort_info = ["reverse"] if reverse else []
 		sort_info.append(sort if sort else "unsorted")
 		sort_info.append(sort if sort else "unsorted")
 		if group and (sort == "address" or sort == "txid"):
 		if group and (sort == "address" or sort == "txid"):
 			sort_info.append("grouped")
 			sort_info.append("grouped")
 
 
-		output = [hdr_fmt % (" ".join(sort_info), total), table_hdr]
+		out = [hdr_fmt % (" ".join(sort_info), total), table_hdr]
 
 
-		for n,i in enumerate(out):
-			output.append(fs % (str(n+1)+")",i.tx,i.vout,i.addr,i.amt,i.days))
+		for n,i in enumerate(unsp):
+			out.append(fs % (str(n+1)+")",i.tx,i.vout,i.addr,i.amt,i.days))
 
 
-		msg("\n".join(output) +"\n\n" + print_to_file_msg + options_msg)
+		msg("\n".join(out) +"\n\n" + print_to_file_msg + options_msg)
 		print_to_file_msg = ""
 		print_to_file_msg = ""
 
 
-		immed_chars = "qpPtadArMgm"
+		immed_chars = "atdAMrgmeqpvw"
 		skip_prompt = False
 		skip_prompt = False
 
 
 		while True:
 		while True:
 			reply = get_char(prompt, immed_chars=immed_chars)
 			reply = get_char(prompt, immed_chars=immed_chars)
 
 
-			if   reply == 'a': unspent.sort(s_amt);  sort = "amount"
-			elif reply == 't': unspent.sort(s_txid); sort = "txid"
-			elif reply == 'd': unspent.sort(s_addr); sort = "address"
-			elif reply == 'A': unspent.sort(s_age);  sort = "age"
+			if   reply == 'a': unspent.sort(key=s_amt);  sort = "amount"
+			elif reply == 't': unspent.sort(key=s_txid); sort = "txid"
+			elif reply == 'd': unspent.sort(key=s_addr); sort = "address"
+			elif reply == 'A': unspent.sort(key=s_age);  sort = "age"
 			elif reply == 'M':
 			elif reply == 'M':
 				unspent.sort(s_mmgen)
 				unspent.sort(s_mmgen)
 				sort = "mmgen"
 				sort = "mmgen"
@@ -282,17 +301,18 @@ Format options: [g]roup, show [m]mgen addr
 				reverse = False if reverse else True
 				reverse = False if reverse else True
 			elif reply == 'g': group = False if group else True
 			elif reply == 'g': group = False if group else True
 			elif reply == 'm': show_mmaddr = False if show_mmaddr else True
 			elif reply == 'm': show_mmaddr = False if show_mmaddr else True
+			elif reply == 'e': pass
 			elif reply == 'q': pass
 			elif reply == 'q': pass
 			elif reply == 'p':
 			elif reply == 'p':
-				data = format_unspent_outputs_for_printing(out,sort_info,total)
+				data = format_unspent_outputs_for_printing(unsp,sort_info,total)
 				outfile = "listunspent[%s].out" % ",".join(sort_info)
 				outfile = "listunspent[%s].out" % ",".join(sort_info)
 				write_to_file(outfile, data)
 				write_to_file(outfile, data)
 				print_to_file_msg = "Data written to '%s'\n\n" % outfile
 				print_to_file_msg = "Data written to '%s'\n\n" % outfile
 			elif reply == 'v':
 			elif reply == 'v':
-				do_pager("\n".join(output))
+				do_pager("\n".join(out))
 				continue
 				continue
 			elif reply == 'w':
 			elif reply == 'w':
-				data = format_unspent_outputs_for_printing(out,sort_info,total)
+				data = format_unspent_outputs_for_printing(unsp,sort_info,total)
 				do_pager(data)
 				do_pager(data)
 				continue
 				continue
 			else:
 			else:
@@ -307,31 +327,19 @@ Format options: [g]roup, show [m]mgen addr
 	return tuple(unspent)
 	return tuple(unspent)
 
 
 
 
-def verify_mmgen_label(s,return_str=False,check_label_len=False):
+def parse_mmgen_label(s,check_label_len=False):
 
 
-	fail    = "" if return_str else False
-	success = s  if return_str else True
+	if not s: return "",""
 
 
-	if not s: return fail
+	try:    w1,w2 = s.split(None,1)
+	except: w1,w2 = s,""
 
 
-	try:
-		mminfo,comment = s.split(None,1)
-	except:
-		mminfo,comment = s,None
-
-	if mminfo[8] != ':': return fail
-	for i in mminfo[:8]:
-		if not i in "01234567890ABCDEF": return fail
-	for i in mminfo[9:]:
-		if not i in "0123456789": return fail
+	if not is_mmgen_addr(w1): return "",w1
+	if check_label_len: check_addr_label(w2)
+	return w1,w2
 
 
-	if check_label_len and comment:
-		check_addr_comment(comment)
 
 
-	return success
-
-
-def view_tx_data(c,inputs_data,tx_hex,metadata=[],pager=False):
+def view_tx_data(c,inputs_data,tx_hex,b2m_map,metadata=[],pager=False):
 
 
 	td = c.decoderawtransaction(tx_hex)
 	td = c.decoderawtransaction(tx_hex)
 
 
@@ -347,39 +355,51 @@ def view_tx_data(c,inputs_data,tx_hex,metadata=[],pager=False):
 			if j['txid'] == i['txid'] and j['vout'] == i['vout']:
 			if j['txid'] == i['txid'] and j['vout'] == i['vout']:
 				days = int(j['confirmations'] * g.mins_per_block / (60*24))
 				days = int(j['confirmations'] * g.mins_per_block / (60*24))
 				total_in += j['amount']
 				total_in += j['amount']
-				out += (" " + """
-%-2s tx,vout: %s,%s
-    address:        %s
-    ID/label:       %s
-    amount:         %s BTC
-    confirmations:  %s (around %s days)
-""".strip() %
-	(n+1,i['txid'],i['vout'],j['address'],verify_mmgen_label(j['account'],True),
-		trim_exponent(j['amount']),j['confirmations'],days)+"\n\n")
-				break
+				addr = j['address']
 
 
-	out += "Total input: %s BTC\n\n" % trim_exponent(total_in)
+				if j['account']:
+					tmp = j['account'].split(None,1)
+					mmid,label = tmp if len(tmp) == 2 else (tmp[0],"")
+					label = label or ""
+				else:
+					mmid,label = "",""
+
+				mmid_str = ((34-len(addr))*" " + " (%s)" % mmid) if mmid else ""
+
+				for d in (
+	(n+1, "tx,vout:",       "%s,%s" % (i['txid'], i['vout'])),
+	("",  "address:",       addr + mmid_str),
+	("",  "label:",         label),
+	("",  "amount:",        "%s BTC" % trim_exponent(j['amount'])),
+	("",  "confirmations:", "%s (around %s days)" % (j['confirmations'], days))
+					):
+					if d[2]: out += ("%3s %-8s %s\n" % d)
+				out += "\n"
 
 
+				break
 	total_out = 0
 	total_out = 0
 	out += "Outputs:\n\n"
 	out += "Outputs:\n\n"
 	for n,i in enumerate(td['vout']):
 	for n,i in enumerate(td['vout']):
+		addr = i['scriptPubKey']['addresses'][0]
+		mmid,label = b2m_map[addr] if addr in b2m_map else ("","")
+		mmid_str = ((34-len(addr))*" " + " (%s)" % mmid) if mmid else ""
 		total_out += i['value']
 		total_out += i['value']
-		out += (" " + """
-%-2s address: %s
-    amount:  %s BTC
-""".strip() % (
-		n,
-		i['scriptPubKey']['addresses'][0],
-		trim_exponent(i['value']))
-	+ "\n\n")
+		for d in (
+				(n+1, "address:",  addr + mmid_str),
+				("",  "label:",    label),
+				("",  "amount:",   trim_exponent(i['value']))
+			):
+			if d[2]: out += ("%3s %-8s %s\n" % d)
+		out += "\n"
+
+	out += "Total input:  %s BTC\n" % trim_exponent(total_in)
 	out += "Total output: %s BTC\n" % trim_exponent(total_out)
 	out += "Total output: %s BTC\n" % trim_exponent(total_out)
 	out += "TX fee:       %s BTC\n" % trim_exponent(total_in-total_out)
 	out += "TX fee:       %s BTC\n" % trim_exponent(total_in-total_out)
 
 
-	if pager: do_pager(out+"\n")
+	if pager: do_pager(out)
 	else:     msg("\n"+out)
 	else:     msg("\n"+out)
 
 
 
 
-
 def parse_tx_data(tx_data,infile):
 def parse_tx_data(tx_data,infile):
 
 
 	if len(tx_data) != 4:
 	if len(tx_data) != 4:
@@ -396,20 +416,28 @@ def parse_tx_data(tx_data,infile):
 	except:
 	except:
 		msg(err_fmt % "hex data")
 		msg(err_fmt % "hex data")
 		sys.exit(2)
 		sys.exit(2)
+	else:
+		if not tx_data:
+			msg("Transaction is empty!")
+			sys.exit(2)
 
 
 	try:
 	try:
-		sig_data = eval(tx_data[2])
+		inputs_data = eval(tx_data[2])
 	except:
 	except:
-		msg(err_fmt % "signature data")
+		msg(err_fmt % "inputs data")
 		sys.exit(2)
 		sys.exit(2)
+	else:
+		if not inputs_data:
+			msg("Transaction has no inputs!")
+			sys.exit(2)
 
 
 	try:
 	try:
-		inputs_data = eval(tx_data[3])
+		map_data = eval(tx_data[3])
 	except:
 	except:
-		msg(err_fmt % "inputs data")
+		msg(err_fmt % "mmgen to btc address map data")
 		sys.exit(2)
 		sys.exit(2)
 
 
-	return tx_data[0].split(),tx_data[1],sig_data,inputs_data
+	return tx_data[0].split(),tx_data[1],inputs_data,map_data
 
 
 
 
 def select_outputs(unspent,prompt):
 def select_outputs(unspent,prompt):
@@ -430,8 +458,83 @@ def select_outputs(unspent,prompt):
 
 
 		return selected
 		return selected
 
 
+def is_mmgen_seed(s):
+	import re
+	return len(s) == 8 and re.match(r"^[0123456789ABCDEF]*$",s)
+
+def is_mmgen_num(s):
+	import re
+	return len(s) <= g.mmgen_idx_max_digits \
+		and re.match(r"^[123456789]+[0123456789]*$",s)
+
+def is_mmgen_addr(s):
+	import re
+	return len(s) > 9 and s[8] == ':' \
+		and re.match(r"^[0123456789ABCDEF]*$",s[:8]) \
+		and len(s[9:]) <= g.mmgen_idx_max_digits \
+		and re.match(r"^[123456789]+[0123456789]*$",s[9:])
+
+def is_btc_addr(s):
+	from mmgen.bitcoin import verify_addr
+	return verify_addr(s)
+
+
+def btc_addr_to_mmgen_addr(btc_addr,b2m_map):
+	if btc_addr in b2m_map:
+		return b2m_map[btc_addr]
+	return "",""
+
+
+def mmgen_addr_to_walletd(c,mmaddr,acct_data):
+
+	# We don't want to create a new object, so we'll use append()
+	if not acct_data:
+		for i in c.listaccounts():
+			acct_data.append(i)
+
+	for a in acct_data:
+		if not a: continue
+		try:
+			w1,w2 = a.split(None,1)
+		except:
+			w1,w2 = a,""
+		if w1 == mmaddr:
+			acct = a
+			break
+	else:
+		return "",""
+
+	alist = c.getaddressesbyaccount(acct)
+
+	if len(alist) != 1:
+		msg("""
+ERROR: More than one address found for account: "%s".
+The tracking "wallet.dat" file appears to have been altered by a non-%s
+program.  Please restore "wallet.dat" from a backup or create a new wallet
+and re-import your addresses.
+""".strip() % (acct,g.proj_name_cap))
+		sys.exit(3)
+
+	return alist[0],w2
+
+
+def mmgen_addr_to_addr_data(m,addr_data):
 
 
-def mmgen_addr_to_btc_addr(m,addr_data):
+	no_data_msg = """
+No data found for MMgen address '%s'. Please import this address into
+your tracking wallet, or supply an address file for it on the command line.
+""".strip() % m
+	warn_msg = """
+Warning: no data for address '%s' exists in the wallet, so it was
+taken from the user-supplied address file.  You're strongly advised to
+import this address into your tracking wallet before proceeding with
+this transaction.  The address will not be tracked until you do so.
+""".strip() % m
+	fail_msg = """
+No data found for MMgen address '%s' in either wallet or supplied
+address file.  Please import this address into your tracking wallet, or
+supply an address file for it on the command line.
+""".strip() % m
 
 
 	ID,num = m.split(":")
 	ID,num = m.split(":")
 	from binascii import unhexlify
 	from binascii import unhexlify
@@ -442,68 +545,66 @@ def mmgen_addr_to_btc_addr(m,addr_data):
 		except: pass
 		except: pass
 		else:
 		else:
 			if not addr_data:
 			if not addr_data:
-				msg("Address data must be supplied for MMgen address '%s'" % m)
+				msg(no_data_msg)
 				sys.exit(2)
 				sys.exit(2)
 			for i in addr_data:
 			for i in addr_data:
 				if ID == i[0]:
 				if ID == i[0]:
 					for j in i[1]:
 					for j in i[1]:
 						if j[0] == num:
 						if j[0] == num:
-							return j[1]
-			msg("MMgen address '%s' not found in supplied address data" % m)
+							msg(warn_msg)
+							if not user_confirm("Continue anyway?"):
+								sys.exit(1)
+							return j[1],(j[2] if len(j) == 3 else "")
+			msg(fail_msg)
 			sys.exit(2)
 			sys.exit(2)
 
 
 	msg("Invalid format: %s" % m)
 	msg("Invalid format: %s" % m)
 	sys.exit(3)
 	sys.exit(3)
 
 
 
 
+def check_mmgen_to_btc_addr_mappings(inputs_data,b2m_map,infiles,seeds,opts):
+	in_maplist = [(i['account'].split()[0],i['address'])
+		for i in inputs_data if i['account']
+			and is_mmgen_addr(i['account'].split()[0])]
+	out_maplist = [(i[1][0],i[0]) for i in b2m_map.items()]
+
+	for maplist,label in (in_maplist,"inputs"), (out_maplist,"outputs"):
+		if not maplist: continue
+		qmsg("Checking MMGen -> BTC address mappings for %s" % label)
+		mmaddrs = [i[0] for i in maplist]
+		from copy import deepcopy
+		pairs = get_keys_for_mmgen_addrs(mmaddrs,
+				deepcopy(infiles),seeds,opts,gen_pairs=True)
+		for a,b in zip(sorted(pairs),sorted(maplist)):
+			if a != b:
+				msg("""
+MMGen -> BTC address mappings differ!
+In transaction:      %s
+Generated from seed: %s
+	""".strip() % (" ".join(a)," ".join(b)))
+				sys.exit(3)
 
 
-def make_tx_out(tx_arg,addr_data):
-
-	tx = {}
-	for i in tx_arg:
-		addr,amt = i.split(",")
-
-		if ":" in addr:
-			addr = mmgen_addr_to_btc_addr(addr,addr_data)
-		else:
-			check_address(addr)
-
-		try: tx[addr] = amt
-		except:
-			msg("Invalid format: %s: %s" % (addr,amt))
-			sys.exit(3)
-
-	if g.debug:
-		print "TX (cl):   ", repr(tx_arg)
-		print "TX (proc): ", repr(tx)
-
-	import decimal
-	try:
-		for i in tx.keys():
-			tx[i] = trim_exponent(Decimal(tx[i]))
-	except decimal.InvalidOperation:
-		msg("Decimal conversion error in suboption '%s:%s'" % (i,tx[i]))
-		sys.exit(3)
-
-	return tx
+	qmsg("Address mappings OK\n")
 
 
 
 
-def check_addr_comment(label):
+def check_addr_label(label):
 
 
 	if len(label) > g.max_addr_label_len:
 	if len(label) > g.max_addr_label_len:
 		msg("'%s': overlong label (length must be <=%s)" %
 		msg("'%s': overlong label (length must be <=%s)" %
 				(label,g.max_addr_label_len))
 				(label,g.max_addr_label_len))
 		sys.exit(3)
 		sys.exit(3)
 
 
-	for ch in list(label):
+	for ch in label:
 		if ch not in g.addr_label_symbols:
 		if ch not in g.addr_label_symbols:
-			msg("'%s': illegal character in label '%s'" % (ch,label))
-			msg("Permitted characters: A-Za-z0-9, plus '%s'" %
-					"', '".join(g.addr_label_punc))
+			msg("""
+"%s": illegal character in label "%s".
+Only ASCII printable characters are permitted.
+""".strip() % (ch,label))
 			sys.exit(3)
 			sys.exit(3)
 
 
 
 
 def parse_addrs_file(f):
 def parse_addrs_file(f):
+
 	lines = get_lines_from_file(f,"address data",remove_comments=True)
 	lines = get_lines_from_file(f,"address data",remove_comments=True)
 
 
 	try:
 	try:
@@ -518,30 +619,23 @@ def parse_addrs_file(f):
 		msg("'%s': invalid first line" % lines[0])
 		msg("'%s': invalid first line" % lines[0])
 	elif cbrace != '}':
 	elif cbrace != '}':
 		msg("'%s': invalid last line" % cbrace)
 		msg("'%s': invalid last line" % cbrace)
-	elif len(seed_id) != 8:
+	elif not is_mmgen_seed(seed_id):
 		msg("'%s': invalid Seed ID" % seed_id)
 		msg("'%s': invalid Seed ID" % seed_id)
 	else:
 	else:
-		try:
-			unhexlify(seed_id)
-		except:
-			msg("'%s': invalid Seed ID" % seed_id)
-			sys.exit(3)
-
 		ret = []
 		ret = []
 		for i in lines[1:-1]:
 		for i in lines[1:-1]:
 			d = i.split(None,2)
 			d = i.split(None,2)
 
 
-			try: d[0] = int(d[0])
-			except:
+			if not is_mmgen_num(d[0]):
 				msg("'%s': invalid address num. in line: %s" % (d[0],d))
 				msg("'%s': invalid address num. in line: %s" % (d[0],d))
 				sys.exit(3)
 				sys.exit(3)
 
 
-			from mmgen.bitcoin import verify_addr
-			if not verify_addr(d[1]):
-				msg("'%s': invalid address" % d[1])
+			if not is_btc_addr(d[1]):
+				msg("'%s': invalid Bitcoin address" % d[1])
 				sys.exit(3)
 				sys.exit(3)
 
 
-			if len(d) == 3: check_addr_comment(d[2])
+			if len(d) == 3:
+				check_addr_label(d[2])
 
 
 			ret.append(tuple(d))
 			ret.append(tuple(d))
 
 
@@ -553,52 +647,59 @@ def parse_addrs_file(f):
 def sign_transaction(c,tx_hex,sig_data,keys=None):
 def sign_transaction(c,tx_hex,sig_data,keys=None):
 
 
 	if keys:
 	if keys:
-		msg("%s keys total" % len(keys))
+		qmsg("%s keys total" % len(keys))
 		if g.debug: print "Keys:\n  %s" % "\n  ".join(keys)
 		if g.debug: print "Keys:\n  %s" % "\n  ".join(keys)
 
 
+	msg_r("Signing transaction...")
 	from mmgen.rpc import exceptions
 	from mmgen.rpc import exceptions
-
 	try:
 	try:
 		sig_tx = c.signrawtransaction(tx_hex,sig_data,keys)
 		sig_tx = c.signrawtransaction(tx_hex,sig_data,keys)
 	except exceptions.InvalidAddressOrKey:
 	except exceptions.InvalidAddressOrKey:
-		msg("Invalid address or key")
+		msg("failed\nInvalid address or key")
 		sys.exit(3)
 		sys.exit(3)
-# 	except:
-# 		msg("Failed to sign transaction")
-# 		sys.exit(3)
 
 
 	return sig_tx
 	return sig_tx
 
 
 
 
-def get_keys_for_mmgen_addrs(mmgen_addrs,infiles,opts):
+def get_keys_for_mmgen_addrs(mmgen_addrs,infiles,seeds,opts,gen_pairs=False):
+
+	seed_ids = list(set([i[:8] for i in mmgen_addrs]))
+	seed_ids_save = seed_ids[0:]  # deep copy
+	ret = []
 
 
-	seed_ids = list(set([i['account'][:8] for i in mmgen_addrs]))
-	seed_ids_save = seed_ids[0:]
-	keys = []
+	seeds_keys = [i for i in seed_ids if i in seeds]
 
 
 	while seed_ids:
 	while seed_ids:
-		infile = False
-		if infiles:
-			infile = infiles.pop()
-			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)
+		if seeds_keys:
+			seed = seeds[seeds_keys.pop()]
 		else:
 		else:
-			b,p,v = ("A seed","","is") if len(seed_ids) == 1 else ("Seed","s","are")
-			msg("ERROR: %s source%s %s required for the following seed ID%s: %s" %
-					(b,p,v,p," ".join(seed_ids)))
-			sys.exit(2)
+			infile = False
+			if infiles:
+				infile = infiles.pop()
+				seed = get_seed_retry(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)
+			else:
+				b,p,v = ("A seed","","is") if len(seed_ids) == 1 \
+						else ("Seed","s","are")
+				msg("ERROR: %s source%s %s required for the following seed ID%s: %s"%
+						(b,p,v,p," ".join(seed_ids)))
+				sys.exit(2)
 
 
 		seed_id = make_chksum_8(seed)
 		seed_id = make_chksum_8(seed)
 		if seed_id in seed_ids:
 		if seed_id in seed_ids:
 			seed_ids.remove(seed_id)
 			seed_ids.remove(seed_id)
-			seed_id_addrs = [
-				int(i['account'].split()[0][9:]) for i in mmgen_addrs
-					if i['account'][:8] == seed_id]
-
-			from mmgen.addr import generate_keys
-			keys += [i['wif'] for i in generate_keys(seed, seed_id_addrs)]
+			addr_ids = [int(i[9:]) for i in mmgen_addrs if i[:8] == seed_id]
+			seeds[seed_id] = seed
+			from mmgen.addr import generate_keys,generate_addrs
+			if gen_pairs:
+				o = {"gen_what":"addresses"}
+				ret += [("%s:%s" % (seed_id,i['num']),i['addr'])
+					for i in generate_addrs(seed, addr_ids, o)]
+			else:
+				ret += [i['wif'] for i in generate_keys(seed, addr_ids)]
 		else:
 		else:
 			if seed_id in seed_ids_save:
 			if seed_id in seed_ids_save:
 				msg_r("Ignoring duplicate seed source")
 				msg_r("Ignoring duplicate seed source")
@@ -610,7 +711,7 @@ def get_keys_for_mmgen_addrs(mmgen_addrs,infiles,opts):
 					msg("Invalid input file: %s" % infile)
 					msg("Invalid input file: %s" % infile)
 					sys.exit(2)
 					sys.exit(2)
 
 
-	return keys
+	return ret
 
 
 
 
 def sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys,opts):
 def sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys,opts):
@@ -642,15 +743,64 @@ def sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys,opts):
 	return sig_tx
 	return sig_tx
 
 
 
 
+def preverify_keys(addrs_orig, keys_orig):
+
+	addrs,keys,wrong_keys = set(addrs_orig[0:]),set(keys_orig[0:]),[]
+
+	if len(keys) < len(addrs):
+		msg("ERROR: not enough keys (%s) for number of non-%s addresses (%s)" %
+				(len(keys),g.proj_name_cap,len(addrs)))
+		sys.exit(2)
+
+	import mmgen.bitcoin as b
+
+	qmsg_r('Checking that user-supplied key list contains valid keys...')
+
+	invalid_keys = []
+
+	for n,k in enumerate(keys,1):
+		c = False if k[0] == '5' else True
+		if b.wiftohex(k,compressed=c) == False:
+			invalid_keys.append(k)
+
+	if invalid_keys:
+		s = "" if len(invalid_keys) == 1 else "s"
+		msg("\n%s/%s invalid key%s in keylist!\n" % (len(invalid_keys),len(keys),s))
+		sys.exit(2)
+	else: qmsg("OK")
+
+	msg('Pre-verifying keys in user-supplied key list (Ctrl-C to skip)')
+
+	try:
+		for n,k in enumerate(keys,1):
+			msg_r("\rkey %s of %s" % (n,len(keys)))
+			c = False if k[0] == '5' else True
+			hexkey = b.wiftohex(k,compressed=c)
+			addr = b.privnum2addr(int(hexkey,16),compressed=c)
+			if addr in addrs:
+				addrs.remove(addr)
+				if not addrs: break
+			else:
+				wrong_keys.append(k)
+	except KeyboardInterrupt:
+		msg("\nSkipping")
+	else:
+		msg("")
+		if wrong_keys:
+			s = "" if len(wrong_keys) == 1 else "s"
+			msg("%s extra key%s found" % (len(wrong_keys),s))
+
+		if addrs:
+			s = "" if len(addrs) == 1 else "es"
+			msg("No keys found for the following non-%s address%s:" %
+					(g.proj_name_cap,s))
+			print "  %s" % "\n  ".join(addrs)
+			sys.exit(2)
+
+
 def missing_keys_errormsg(other_addrs):
 def missing_keys_errormsg(other_addrs):
 	msg("""
 	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])
-	  ))
-
-def get_addr_data(cmd_args):
-	for f in cmd_args:
-		data = parse_addrs_file(f)
-		print repr(data); sys.exit() # DEBUG
+A key file must be supplied (or use the "-w" option) for the following
+non-mmgen address%s:
+""".strip() % ("" if len(other_addrs) == 1 else "es"))
+	print "  %s" % "\n  ".join([i['address'] for i in other_addrs])

+ 63 - 163
mmgen/util.py

@@ -23,124 +23,21 @@ import sys
 import mmgen.config as g
 import mmgen.config as g
 from binascii import hexlify,unhexlify
 from binascii import hexlify,unhexlify
 from mmgen.bitcoin import b58decode_pad
 from mmgen.bitcoin import b58decode_pad
+from mmgen.term import *
+
+def msg(s):    sys.stderr.write(s + "\n")
+def msg_r(s):  sys.stderr.write(s)
+def qmsg(s):
+	if not g.quiet: sys.stderr.write(s + "\n")
+def qmsg_r(s):
+	if not g.quiet: sys.stderr.write(s)
+def vmsg(s):
+	if g.verbose: sys.stderr.write(s + "\n")
+def vmsg_r(s):
+	if g.verbose: sys.stderr.write(s)
 
 
-def msg(s):   sys.stderr.write(s + "\n")
-def msg_r(s): sys.stderr.write(s)
 def bail(): sys.exit(9)
 def bail(): sys.exit(9)
 
 
-def kb_hold_protect_unix():
-
-	fd = sys.stdin.fileno()
-	old = termios.tcgetattr(fd)
-	tty.setcbreak(fd)
-
-	timeout = float(0.3)
-
-	try:
-		while True:
-			key = select([sys.stdin], [], [], timeout)[0]
-			if key: sys.stdin.read(1)
-			else: break
-	except:
-		print "\nUser interrupt"
-		sys.exit(1)
-	finally:
-		termios.tcsetattr(fd, termios.TCSADRAIN, old)
-
-
-def get_keypress_unix(prompt="",immed_chars=""):
-
-	msg_r(prompt)
-	timeout = float(0.3)
-
-	fd = sys.stdin.fileno()
-	old = termios.tcgetattr(fd)
-	tty.setcbreak(fd)
-
-	try:
-		while True:
-			select([sys.stdin], [], [], False)
-			ch = sys.stdin.read(1)
-			if immed_chars == "ALL" or ch in immed_chars:
-				return ch
-			if immed_chars == "ALL_EXCEPT_ENTER" and not ch in "\n\r":
-				return ch
-			second_key = select([sys.stdin], [], [], timeout)[0]
-			if second_key: continue
-			else: return ch
-	except:
-		print "\nUser interrupt"
-		sys.exit(1)
-	finally:
-		termios.tcsetattr(fd, termios.TCSADRAIN, old)
-
-
-def kb_hold_protect_mswin():
-
-	timeout = float(0.5)
-
-	try:
-		while True:
-			hit_time = time.time()
-			while True:
-				if msvcrt.kbhit():
-					msvcrt.getch()
-					break
-				if float(time.time() - hit_time) > timeout:
-					return
-	except:
-		msg("\nUser interrupt")
-		sys.exit(1)
-
-
-def get_keypress_mswin(prompt="",immed_chars=""):
-
-	msg_r(prompt)
-	timeout = float(0.5)
-
-	try:
-		while True:
-			if msvcrt.kbhit():
-				ch = msvcrt.getch()
-
-				if ord(ch) == 3: raise KeyboardInterrupt
-
-				if immed_chars == "ALL" or ch in immed_chars:
-					return ch
-				if immed_chars == "ALL_EXCEPT_ENTER" and not ch in "\n\r":
-					return ch
-
-				hit_time = time.time()
-
-				while True:
-					if msvcrt.kbhit(): break
-					if float(time.time() - hit_time) > timeout:
-						return ch
-	except:
-		msg("\nUser interrupt")
-		sys.exit(1)
-
-
-try:
-	import tty, termios
-	from select import select
-	get_char = get_keypress_unix
-	kb_hold_protect = kb_hold_protect_unix
-except:
-	try:
-		import msvcrt, time
-		get_char = get_keypress_mswin
-		kb_hold_protect = kb_hold_protect_mswin
-	except:
-		if not sys.platform.startswith("linux") \
-				and not sys.platform.startswith("win"):
-			msg("Unsupported platform: %s" % sys.platform)
-			msg("This program currently runs only on Linux and Windows")
-		else:
-			msg("Unable to set terminal mode")
-		sys.exit(2)
-
-
 def my_raw_input(prompt,echo=True,allowed_chars=""):
 def my_raw_input(prompt,echo=True,allowed_chars=""):
 	try:
 	try:
 		if echo:
 		if echo:
@@ -148,8 +45,8 @@ def my_raw_input(prompt,echo=True,allowed_chars=""):
 		else:
 		else:
 			from getpass import getpass
 			from getpass import getpass
 			reply = getpass(prompt)
 			reply = getpass(prompt)
-	except:
-		print "\nUser interrupt"
+	except KeyboardInterrupt:
+		msg("\nUser interrupt")
 		sys.exit(1)
 		sys.exit(1)
 
 
 	kb_hold_protect()
 	kb_hold_protect()
@@ -202,7 +99,7 @@ cmessages = {
 	'unencrypted_secret_keys': """
 	'unencrypted_secret_keys': """
 This program generates secret keys from your {} seed, outputting them in
 This program generates secret keys from your {} seed, outputting them in
 UNENCRYPTED form.  Generate only the key(s) you need and guard them carefully.
 UNENCRYPTED form.  Generate only the key(s) you need and guard them carefully.
-""".format(g.proj_name),
+""".format(g.proj_name_cap),
 	'brain_warning': """
 	'brain_warning': """
 ############################## EXPERTS ONLY! ##############################
 ############################## EXPERTS ONLY! ##############################
 
 
@@ -223,7 +120,7 @@ future, you must continue using these same parameters
 
 
 def confirm_or_exit(message, question, expect="YES"):
 def confirm_or_exit(message, question, expect="YES"):
 
 
-	msg("")
+	vmsg("")
 
 
 	m = message.strip()
 	m = message.strip()
 	if m: msg(m)
 	if m: msg(m)
@@ -353,7 +250,7 @@ def get_new_passphrase(what, opts):
 			pw2 = " ".join(_get_words_from_user(("Repeat %s: " % what),opts))
 			pw2 = " ".join(_get_words_from_user(("Repeat %s: " % what),opts))
 			if g.debug: print "Passphrases: [%s] [%s]" % (pw,pw2)
 			if g.debug: print "Passphrases: [%s] [%s]" % (pw,pw2)
 			if pw == pw2:
 			if pw == pw2:
-				msg("%ss match" % what.capitalize())
+				vmsg("%ss match" % what.capitalize())
 				break
 				break
 			else:
 			else:
 				msg("%ss do not match" % what.capitalize())
 				msg("%ss do not match" % what.capitalize())
@@ -362,7 +259,7 @@ def get_new_passphrase(what, opts):
 					g.passwd_max_tries)
 					g.passwd_max_tries)
 			sys.exit(2)
 			sys.exit(2)
 
 
-	if pw == "": msg("WARNING: Empty passphrase")
+	if pw == "": qmsg("WARNING: Empty passphrase")
 	return pw
 	return pw
 
 
 
 
@@ -384,17 +281,17 @@ def _get_seed_from_brain_passphrase(words,opts):
 	if g.debug: print "Sanitized brain passphrase: %s" % bp
 	if g.debug: print "Sanitized brain passphrase: %s" % bp
 	seed_len,hash_preset = _get_from_brain_opt_params(opts)
 	seed_len,hash_preset = _get_from_brain_opt_params(opts)
 	if g.debug: print "Brainwallet l = %s, p = %s" % (seed_len,hash_preset)
 	if g.debug: print "Brainwallet l = %s, p = %s" % (seed_len,hash_preset)
-	msg_r("Hashing brainwallet data.  Please wait...")
+	vmsg_r("Hashing brainwallet data.  Please wait...")
 	# Use buflen arg of scrypt.hash() to get seed of desired length
 	# Use buflen arg of scrypt.hash() to get seed of desired length
 	seed = _scrypt_hash_passphrase(bp, "", hash_preset, buflen=seed_len/8)
 	seed = _scrypt_hash_passphrase(bp, "", hash_preset, buflen=seed_len/8)
-	msg("Done")
+	vmsg("Done")
 	return seed
 	return seed
 
 
 
 
-def encrypt_seed(seed, key, opts):
+def encrypt_seed(seed, key):
 	"""
 	"""
-	Encrypt a seed for a {} deterministic wallet
-	""".format(g.proj_name)
+	Encrypt a seed for an {} deterministic wallet
+	""".format(g.proj_name_cap)
 
 
 	# 192-bit seed is 24 bytes -> not multiple of 16.  Must use MODE_CTR
 	# 192-bit seed is 24 bytes -> not multiple of 16.  Must use MODE_CTR
 	from Crypto.Cipher import AES
 	from Crypto.Cipher import AES
@@ -403,14 +300,14 @@ def encrypt_seed(seed, key, opts):
 	c = AES.new(key, AES.MODE_CTR,counter=Counter.new(128))
 	c = AES.new(key, AES.MODE_CTR,counter=Counter.new(128))
 	enc_seed = c.encrypt(seed)
 	enc_seed = c.encrypt(seed)
 
 
-	msg_r("Performing a test decryption of the seed...")
+	vmsg_r("Performing a test decryption of the seed...")
 
 
 	c = AES.new(key, AES.MODE_CTR,counter=Counter.new(128))
 	c = AES.new(key, AES.MODE_CTR,counter=Counter.new(128))
 	dec_seed = c.decrypt(enc_seed)
 	dec_seed = c.decrypt(enc_seed)
 
 
-	if dec_seed == seed: msg("done")
+	if dec_seed == seed: vmsg("done")
 	else:
 	else:
-		msg("FAILED.\nDecrypted seed doesn't match original seed")
+		msg("ERROR.\nDecrypted seed doesn't match original seed")
 		sys.exit(2)
 		sys.exit(2)
 
 
 	return enc_seed
 	return enc_seed
@@ -576,11 +473,11 @@ def write_wallet_to_file(seed, passwd, key_id, salt, enc_seed, opts):
 
 
 	chk = make_chksum_6(" ".join(lines))
 	chk = make_chksum_6(" ".join(lines))
 
 
-	confirm = False if 'quiet' in opts else True
+	confirm = False if g.quiet else True
 	write_to_file(outfile, "\n".join((chk,)+lines)+"\n", confirm)
 	write_to_file(outfile, "\n".join((chk,)+lines)+"\n", confirm)
 
 
 	msg("Wallet saved to file '%s'" % outfile)
 	msg("Wallet saved to file '%s'" % outfile)
-	if 'verbose' in opts:
+	if g.verbose:
 		_display_control_data(label,metadata,hash_preset,salt,enc_seed)
 		_display_control_data(label,metadata,hash_preset,salt,enc_seed)
 
 
 
 
@@ -592,10 +489,10 @@ def write_walletdat_dump_to_file(wallet_id,data,num_keys,ext,what,opts):
 	msg("wallet.dat %s saved to file '%s'" % (what,outfile))
 	msg("wallet.dat %s saved to file '%s'" % (what,outfile))
 
 
 
 
-def compare_checksums(chksum1, desc1, chksum2, desc2):
+def _compare_checksums(chksum1, desc1, chksum2, desc2):
 
 
 	if chksum1.lower() == chksum2.lower():
 	if chksum1.lower() == chksum2.lower():
-		msg("OK (%s)" % chksum1.upper())
+		vmsg("OK (%s)" % chksum1.upper())
 		return True
 		return True
 	else:
 	else:
 		if g.debug:
 		if g.debug:
@@ -608,6 +505,8 @@ def _is_hex(s):
 	except: return False
 	except: return False
 	else: return True
 	else: return True
 
 
+def match_ext(addr,ext):
+	return addr.split(".")[-1] == ext
 
 
 def _check_mmseed_format(words):
 def _check_mmseed_format(words):
 
 
@@ -629,10 +528,7 @@ def _check_mmseed_format(words):
 	return valid
 	return valid
 
 
 
 
-def check_wallet_format(infile, lines, opts):
-
-	def vmsg(s):
-		if 'verbose' in opts: msg(s)
+def _check_wallet_format(infile, lines):
 
 
 	what = "wallet file '%s'" % infile
 	what = "wallet file '%s'" % infile
 	valid = False
 	valid = False
@@ -660,17 +556,19 @@ def _check_chksum_6(chk,val,desc,infile):
 		msg("%s checksum passed: %s" % (desc.capitalize(),chk))
 		msg("%s checksum passed: %s" % (desc.capitalize(),chk))
 
 
 
 
-def get_data_from_wallet(infile,opts,silent=False):
+def get_data_from_wallet(infile,silent=False):
 
 
+	# Don't make this a qmsg: User will be prompted for passphrase and must see
+	# the filename.
 	if not silent:
 	if not silent:
-		msg("Getting {} wallet data from file '{}'".format(g.proj_name,infile))
+		msg("Getting {} wallet data from file '{}'".format(g.proj_name_cap,infile))
 
 
 	f = open_file_or_exit(infile, 'r')
 	f = open_file_or_exit(infile, 'r')
 
 
 	lines = [i.strip() for i in f.readlines()]
 	lines = [i.strip() for i in f.readlines()]
 	f.close()
 	f.close()
 
 
-	check_wallet_format(infile, lines, opts)
+	_check_wallet_format(infile, lines)
 
 
 	label = lines[1]
 	label = lines[1]
 
 
@@ -711,7 +609,7 @@ def _get_words_from_user(prompt, opts):
 
 
 
 
 def _get_words_from_file(infile,what):
 def _get_words_from_file(infile,what):
-	msg("Getting %s from file '%s'" % (what,infile))
+	qmsg("Getting %s from file '%s'" % (what,infile))
 	f = open_file_or_exit(infile, 'r')
 	f = open_file_or_exit(infile, 'r')
 	# split() also strips
 	# split() also strips
 	words = f.read().split()
 	words = f.read().split()
@@ -721,7 +619,8 @@ def _get_words_from_file(infile,what):
 
 
 
 
 def get_lines_from_file(infile,what="",remove_comments=False):
 def get_lines_from_file(infile,what="",remove_comments=False):
-	if what != "": msg("Getting %s from file '%s'" % (what,infile))
+	if what != "":
+		qmsg("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()
 	if remove_comments:
 	if remove_comments:
@@ -738,7 +637,7 @@ def get_lines_from_file(infile,what="",remove_comments=False):
 
 
 
 
 def get_data_from_file(infile,what="data"):
 def get_data_from_file(infile,what="data"):
-	msg("Getting %s from file '%s'" % (what,infile))
+	qmsg("Getting %s from file '%s'" % (what,infile))
 	f = open_file_or_exit(infile,'r')
 	f = open_file_or_exit(infile,'r')
 	data = f.read()
 	data = f.read()
 	f.close()
 	f.close()
@@ -755,18 +654,18 @@ def _get_seed_from_seed_data(words):
 	seed_b58 = "".join(words[1:])
 	seed_b58 = "".join(words[1:])
 
 
 	chk = make_chksum_6(seed_b58)
 	chk = make_chksum_6(seed_b58)
-	msg_r("Validating %s checksum..." % g.seed_ext)
+	vmsg_r("Validating %s checksum..." % g.seed_ext)
 
 
-	if compare_checksums(chk, "from seed", stored_chk, "from input"):
+	if _compare_checksums(chk, "from seed", stored_chk, "from input"):
 		seed = b58decode_pad(seed_b58)
 		seed = b58decode_pad(seed_b58)
 		if seed == False:
 		if seed == False:
 			msg("Invalid b58 number: %s" % val)
 			msg("Invalid b58 number: %s" % val)
 			return False
 			return False
 
 
-		msg("%s data produces seed ID: %s" % (g.seed_ext,make_chksum_8(seed)))
+		vmsg("%s data produces seed ID: %s" % (g.seed_ext,make_chksum_8(seed)))
 		return seed
 		return seed
 	else:
 	else:
-		msg("Invalid checksum for {} seed".format(g.proj_name))
+		msg("Invalid checksum for {} seed".format(g.proj_name_cap))
 		return False
 		return False
 
 
 
 
@@ -791,7 +690,8 @@ def get_mmgen_passphrase(prompt,opts):
 def get_bitcoind_passphrase(prompt,opts):
 def get_bitcoind_passphrase(prompt,opts):
 	if 'passwd_file' in opts:
 	if 'passwd_file' in opts:
 		mark_passwd_file_as_used(opts)
 		mark_passwd_file_as_used(opts)
-		return get_data_from_file(opts['passwd_file'],"passphrase").strip("\r\n")
+		return get_data_from_file(opts['passwd_file'],
+				"passphrase").strip("\r\n")
 	else:
 	else:
 		return my_raw_input(prompt,
 		return my_raw_input(prompt,
 					echo=True if 'echo_passphrase' in opts else False)
 					echo=True if 'echo_passphrase' in opts else False)
@@ -800,14 +700,14 @@ def get_bitcoind_passphrase(prompt,opts):
 def get_seed_from_wallet(
 def get_seed_from_wallet(
 		infile,
 		infile,
 		opts,
 		opts,
-		prompt="Enter {} wallet passphrase: ".format(g.proj_name),
+		prompt="Enter {} wallet passphrase: ".format(g.proj_name_cap),
 		silent=False
 		silent=False
 		):
 		):
 
 
-	wdata = get_data_from_wallet(infile,opts,silent=silent)
+	wdata = get_data_from_wallet(infile,silent=silent)
 	label,metadata,hash_preset,salt,enc_seed = wdata
 	label,metadata,hash_preset,salt,enc_seed = wdata
 
 
-	if 'verbose' in opts: _display_control_data(*wdata)
+	if g.verbose: _display_control_data(*wdata)
 
 
 	passwd = get_mmgen_passphrase(prompt,opts)
 	passwd = get_mmgen_passphrase(prompt,opts)
 
 
@@ -818,21 +718,21 @@ def get_seed_from_wallet(
 
 
 def make_key(passwd, salt, hash_preset):
 def make_key(passwd, salt, hash_preset):
 
 
-	msg_r("Hashing passphrase.  Please wait...")
+	vmsg_r("Hashing passphrase.  Please wait...")
 	key = _scrypt_hash_passphrase(passwd, salt, hash_preset)
 	key = _scrypt_hash_passphrase(passwd, salt, hash_preset)
-	msg("done")
+	vmsg("done")
 	return key
 	return key
 
 
 
 
 def decrypt_seed(enc_seed, key, seed_id, key_id):
 def decrypt_seed(enc_seed, key, seed_id, key_id):
 
 
-	msg_r("Checking key...")
+	vmsg_r("Checking key...")
 	chk = make_chksum_8(key)
 	chk = make_chksum_8(key)
-	if not compare_checksums(chk, "of key", key_id, "in header"):
+	if not _compare_checksums(chk, "of key", key_id, "in header"):
 		msg("Incorrect passphrase")
 		msg("Incorrect passphrase")
 		return False
 		return False
 
 
-	msg_r("Decrypting seed with key...")
+	vmsg_r("Decrypting seed with key...")
 
 
 	from Crypto.Cipher import AES
 	from Crypto.Cipher import AES
 	from Crypto.Util import Counter
 	from Crypto.Util import Counter
@@ -841,13 +741,13 @@ def decrypt_seed(enc_seed, key, seed_id, key_id):
 	dec_seed = c.decrypt(enc_seed)
 	dec_seed = c.decrypt(enc_seed)
 
 
 	chk = make_chksum_8(dec_seed)
 	chk = make_chksum_8(dec_seed)
-	if compare_checksums(chk,"of decrypted seed",seed_id,"in header"):
-		msg("Passphrase is OK")
+	if _compare_checksums(chk,"of decrypted seed",seed_id,"in header"):
+		qmsg("Passphrase is OK")
 	else:
 	else:
 		if not g.debug:
 		if not g.debug:
 			msg_r("Checking key ID...")
 			msg_r("Checking key ID...")
 			chk = make_chksum_8(key)
 			chk = make_chksum_8(key)
-			if compare_checksums(chk, "of key", key_id, "in header"):
+			if _compare_checksums(chk, "of key", key_id, "in header"):
 				msg("Key ID is correct but decryption of seed failed")
 				msg("Key ID is correct but decryption of seed failed")
 			else:
 			else:
 				msg("Incorrect passphrase")
 				msg("Incorrect passphrase")
@@ -894,10 +794,10 @@ def get_seed(infile,opts,silent=False):
 		if 'from_brain' not in opts:
 		if 'from_brain' not in opts:
 			msg("'--from-brain' parameters must be specified for brainwallet file")
 			msg("'--from-brain' parameters must be specified for brainwallet file")
 			sys.exit(2)
 			sys.exit(2)
-		if 'quiet' not in opts:
+		if not g.quiet:
 			confirm_or_exit(
 			confirm_or_exit(
 				cmessages['brain_warning'].format(
 				cmessages['brain_warning'].format(
-					g.proj_name.capitalize(), *_get_from_brain_opt_params(opts)),
+					g.proj_name_cap, *_get_from_brain_opt_params(opts)),
 				"continue")
 				"continue")
 		prompt = "Enter brainwallet passphrase: "
 		prompt = "Enter brainwallet passphrase: "
 		words = _get_words(infile,"brainwallet data",prompt,opts)
 		words = _get_words(infile,"brainwallet data",prompt,opts)
@@ -954,10 +854,10 @@ def do_pager(text):
 		else:
 		else:
 			try:
 			try:
 				p.communicate(text+end+"\n")
 				p.communicate(text+end+"\n")
-			except:
+			except KeyboardInterrupt:
 				# Has no effect.  Why?
 				# Has no effect.  Why?
 				if pager != "less":
 				if pager != "less":
-					msg("\n(Interrupted by user)\n")
+					msg("\n(User interrupt)\n")
 			finally:
 			finally:
 				msg_r("\r")
 				msg_r("\r")
 				break
 				break

+ 4 - 3
mmgen/walletgen.py

@@ -20,14 +20,15 @@ walletgen.py:  Routines used for seed generation and wallet creation
 """
 """
 
 
 import sys
 import sys
-from mmgen.util import msg, msg_r, get_char, prompt_and_get_char
+import mmgen.config as g
+from mmgen.util import msg, msg_r, qmsg, qmsg_r, get_char, prompt_and_get_char
 from binascii import hexlify
 from binascii import hexlify
 
 
 def get_random_data_from_user(opts):
 def get_random_data_from_user(opts):
 
 
 	ulen = opts['usr_randlen']
 	ulen = opts['usr_randlen']
 
 
-	if 'quiet' in opts:
+	if g.quiet:
 		msg("Enter %s random symbols" % ulen)
 		msg("Enter %s random symbols" % ulen)
 	else:
 	else:
 		msg("""
 		msg("""
@@ -54,7 +55,7 @@ displayed on the screen.
 		intervals.append(now - saved_time)
 		intervals.append(now - saved_time)
 		saved_time = now
 		saved_time = now
 
 
-	if 'quiet' in opts:
+	if g.quiet:
 		msg_r("\r")
 		msg_r("\r")
 	else:
 	else:
 		msg_r("\rThank you.  That's enough." + " "*15 + "\n\n")
 		msg_r("\rThank you.  That's enough." + " "*15 + "\n\n")

+ 2 - 1
setup.py

@@ -3,7 +3,7 @@ from distutils.core import setup
 
 
 setup(
 setup(
 		name         = 'mmgen',
 		name         = 'mmgen',
-		version      = '0.6.9',
+		version      = '0.7.0',
 		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',
@@ -19,6 +19,7 @@ setup(
 			'mmgen.mnemonic',
 			'mmgen.mnemonic',
 			'mmgen.mn_tirosh',
 			'mmgen.mn_tirosh',
 			'mmgen.Opts',
 			'mmgen.Opts',
+			'mmgen.term',
 			'mmgen.tx',
 			'mmgen.tx',
 			'mmgen.util',
 			'mmgen.util',
 			'mmgen.walletgen',
 			'mmgen.walletgen',