Browse Source

Improved handling of user input; code cleanups

philemon 11 years ago
parent
commit
9a64bf42e2
18 changed files with 617 additions and 502 deletions
  1. 177 102
      README.md
  2. 5 10
      mmgen-addrgen
  3. 5 3
      mmgen-addrimport
  4. 0 1
      mmgen-keygen
  5. 3 5
      mmgen-passchg
  6. 3 3
      mmgen-pywallet
  7. 12 10
      mmgen-txcreate
  8. 2 3
      mmgen-txsend
  9. 15 106
      mmgen-txsign
  10. 3 2
      mmgen-walletchk
  11. 9 24
      mmgen-walletgen
  12. 117 0
      mmgen/Opts.py
  13. 3 3
      mmgen/addr.py
  14. 5 2
      mmgen/config.py
  15. 3 3
      mmgen/license.py
  16. 115 25
      mmgen/tx.py
  17. 134 196
      mmgen/utils.py
  18. 6 4
      mmgen/walletgen.py

+ 177 - 102
README.md

@@ -1,9 +1,9 @@
-#  mmgen = Multi-Mode GENerator
+#  MMGen = Multi-Mode GENerator
 ## a Bitcoin cold storage solution for the command line
 ## a Bitcoin cold storage solution for the command line
 
 
 NOTE: Parts of this README are now **out of date**.  In particular, the
 NOTE: Parts of this README are now **out of date**.  In particular, the
 new transaction scripts automate the process of offline signing, so that
 new transaction scripts automate the process of offline signing, so that
-your private keys never touch the online machine.  An updated README is
+your private keys never touch your online machine.	An updated README is
 on the way.  For the time being, consult the `--help` option of the
 on the way.  For the time being, consult the `--help` option of the
 `mmgen-tx*` scripts.
 `mmgen-tx*` scripts.
 
 
@@ -14,143 +14,218 @@ addresses is done at your own risk.
 
 
 ### Features:
 ### Features:
 
 
-> As with all deterministic wallets, mmgen can generate an unlimited number
-> of address/key pairs from a single seed.  You back up your wallet only once.
+> Like all deterministic wallets, MMGen can generate a virtually
+> unlimited number of address/key pairs from a single seed, allowing you
+> to maintain and track a large number of addresses with balances.  Your
+> wallet never changes (unless you change the password), so you need
+> back it up only once.
 
 
-> With MMGen you can choose from four different ways to access your Bitcoins:
+> The "master key", the seed providing access to all your Bitcoins, can
+> be stored in four different ways:
 
 
->> 1) an encrypted wallet (the AES 256 key is generated from your
+>> 1) in an encrypted wallet (the AES 256 key is generated from your
 >> password using the crack-resistant scrypt hash function.  The
 >> password using the crack-resistant scrypt hash function.  The
 >> wallet's password and hash strength can be changed);
 >> wallet's password and hash strength can be changed);
 
 
->> 2) a short, human-readable seed file (unencrypted);
+>> 2) in a one-line, human-readable seed file (unencrypted);
 
 
->> 3) an Electrum-like mnemonic of 12, 18 or 24 words; or
+>> 3) as an Electrum-like mnemonic of 12, 18 or 24 words; or
 
 
->> 4) a brainwallet password (recommended for expert users only).
+>> 4) as a brainwallet password (this option is recommended for expert
+>> users only).
 
 
 > Furthermore, these methods can all be combined.  If you forget your
 > Furthermore, these methods can all be combined.  If you forget your
 > mnemonic, for example, you can regenerate it and your keys from a
 > mnemonic, for example, you can regenerate it and your keys from a
 > stored wallet or seed.  Correspondingly, a lost wallet or seed can be
 > stored wallet or seed.  Correspondingly, a lost wallet or seed can be
 > recovered from the mnemonic.
 > recovered from the mnemonic.
 
 
-> The wallet and seed are short, simple text files suitable for printing
-> or even writing out by hand.  Built-in checksums are used to verify
-> they've been correctly copied.  The base-58-encoded seed is short
-> enough to memorize, providing another brain storage alternative.
+> The wallet and seed files are in a simple, ASCII-based format suitable
+> for printing or even writing out by hand.	Built-in checksums are used
+> to verify they've been correctly copied.  The base-58-encoded seed
+> file is short enough to memorize, providing another brain storage
+> alternative.
 
 
-> Implemented as a suite of lightweight Python scripts with a
-> command-line interface, MMGen demands practically no system resources.
-> Yet in tandem with a bitcoind enabled for watch-only addresses
-> (see below), it provides a complete solution for securely
-> storing Bitcoins offline and tracking and spending them online.
+> Transactions are signed offline: your private keys never touch an
+> online computer.
 
 
+> Implemented as a suite of lightweight Python scripts for the console,
+> MMGen requires only a bare minimum of system resources.  Yet in tandem
+> with a watch-only enabled bitcoind (see below), it provides a robust
+> solution for securely storing, tracking, spending and receiving
+> Bitcoins.
 
 
-### Instructions for Linux/Unix:
+> MMGen is currently supported on Windows and Linux.
 
 
-### Download/Install:
->  Install required Python modules:
+### Download/Install
 
 
-            sudo pip install ecdsa scrypt pycrypto bitcoin-python
+#### Debian/Ubuntu Linux:
 
 
->  Install mmgen:
+> **Perform the following steps on both your online and offline
+> computers:**
 
 
-            git clone https://github.com/mmgen/mmgen.git
-            cd mmgen; sudo ./setup.py install
+> Install the pip Python installer:
 
 
->  Install vanitygen (optional but recommended):
+			sudo apt-get install python-pip
 
 
-            git clone https://github.com/samr7/vanitygen.git
-            (build and put the 'keyconv' executable in your path)
+> Install required Python modules:
 
 
-### Getting Started:
+			sudo pip install ecdsa scrypt pycrypto bitcoin-python
+
+> Install MMGen:
+
+			git clone https://github.com/mmgen/mmgen.git
+			cd mmgen; sudo ./setup.py install
+
+> Install vanitygen (optional but recommended):
+
+			git clone https://github.com/samr7/vanitygen.git
+			(build and put the 'keyconv' executable in your path)
+
+> At this point you may begin using MMgen to create wallets, generate
+> keys and create raw transactions as described in **Using MMGen**
+> below.  But since you'd like to be able to track addresses and sign
+> transactions too, you'll now need to install the bitcoin daemon,
+> `bitcoind`, on both your online and offline machines.
+
+> **Bitcoind installation**
+
+> On the **offline machine**, the bitcoin daemon is used solely for
+> signing transactions and can therefore be run without a blockchain.
+> The version bundled with the prebuilt Bitcoin-QT is just fine for this
+> purpose.  It can be obtained here:
+
+			https://bitcoin.org/en/download
+
+> After installation, locate the bitcoind executable, optionally place
+> it in your path and start it with the arguments `-daemon -maxconnections=0`.
+
+> Note that in the absence of a blockchain the daemon starts very quickly
+> and uses practically no CPU power once running.  Thus you'll have no
+> problem using a low-powered computer such as a netbook for your
+> offline machine.
+
+> On the **online machine**, the bitcoin daemon is used for tracking
+> addresses and must be run with the full blockchain.  Thus a more
+> powerful computer is required here.  In addition, the precompiled
+> bitcoin package we installed above lacks (for now) the watch-only
+> address support we need, so we must get and compile Sipa's
+> watch-only enabled version from github:
+
+            git clone https://github.com/sipa/bitcoin
+            cd bitcoin
+            git branch wo origin/watchonly
+            git checkout wo
+			./configure
+            make
+            (You may have to install the libboost-all-dev package for the build to succeed)
+> With your online machine connected to the Internet, start your freshly
+> compiled daemon and let it synchronize, making sure to move your old
+> `wallet.dat` out of harm's way beforehand if you have an existing
+> bitcoin installation.  You'll use the new wallet created by the daemon
+> on startup as your **tracking wallet**.
+
+> Your setup is now complete.
+
+
+### Using MMGen:
 > On your offline computer:
 > On your offline computer:
 
 
 > Generate a wallet with a random seed:
 > Generate a wallet with a random seed:
 
 
-            $ mmgen-walletgen
-            ...
-            Wallet saved to file '89ABCDEF-76543210[256,3].dat'
+			$ mmgen-walletgen
+			...
+			Wallet saved to file '89ABCDEF-76543210[256,3].mmdat'
 
 
-> "89ABCDEF" is the Seed ID; "76543210" is the Key ID.  These are
-> randomly generated, so your IDs will naturally be different than the
-> fictitious ones used in this example.
+> "89ABCDEF" is the Seed ID; "76543210" is the Key ID. These are
+> randomly generated, so your IDs will of course be different than the
+> fictitious ones used here.
 
 
-> The Seed ID never changes and will be used to identify all
-> keys/addresses generated by this wallet.  The Key ID changes when the
-> wallet's password or hash preset are changed.
+> The Seed ID never changes and is used to identify all keys/addresses
+> generated by this wallet.	The Key ID changes when the wallet's
+> password or hash preset are changed.
 
 
-> "256" is the seed length; "3" is the scrypt hash preset.  These are
+> "256" is the seed length; "3" is the scrypt hash preset.	These are
 > configurable.
 > configurable.
 
 
 
 
 > Generate ten addresses with the wallet:
 > Generate ten addresses with the wallet:
 
 
-            $ mmgen-addrgen 89ABCDEF-76543210[256,3].dat 1-10
-            ...
-            Address data saved to file '89ABCDEF[1-10].addrs'
-
-
-> Note that the address range, "1-10", is included in the resulting filename.
-
-            $ cat '89ABCDEF[1-10].addrs'
-            89ABCDEF {
-              1     16bNmyYISiptuvJG3X7MPwiiS4HYvD7ksE
-              2     1AmkUxrfy5dMrfmeYwTxLxfIswUCcpeysc
-              3     1HgYCsfqYzIg7LVVfDTp7gYJocJEiDAy6N
-              4     14Tu3z1tiexXDonNsFIkvzqutE5E3pTK8s
-              5     1PeI55vtp2bX2uKDkAAR2c6ekHNYe4Hcq7
-              6     1FEqfEsSILwXPfMvVvVuUovzTaaST62Mnf
-              7     1LTTzuhMqPLwQ4IGCwwugny6ZMtUQJSJ1
-              8     1F9495H8EJLb54wirgZkVgI47SP7M2RQWv
-              9     1JbrCyt7BdxRE9GX1N7GiEct8UnIjPmpYd
-              10    1H7vVTk4ejUbQXw45I6g5qvPBSe9bsjDqh
+			$ mmgen-addrgen 89ABCDEF-76543210[256,3].mmdat 1-10
+			...
+			Address data saved to file '89ABCDEF[1-10].addrs'
+
+
+> Note that the address range, "1-10", is reflected in the resulting filename.
+
+			$ cat '89ABCDEF[1-10].addrs'
+			89ABCDEF {
+			  1		16bNmyYISiptuvJG3X7MPwiiS4HYvD7ksE
+			  2		1AmkUxrfy5dMrfmeYwTxLxfIswUCcpeysc
+			  3		1HgYCsfqYzIg7LVVfDTp7gYJocJEiDAy6N
+			  4		14Tu3z1tiexXDonNsFIkvzqutE5E3pTK8s
+			  5		1PeI55vtp2bX2uKDkAAR2c6ekHNYe4Hcq7
+			  6		1FEqfEsSILwXPfMvVvVuUovzTaaST62Mnf
+			  7		1LTTzuhMqPLwQ4IGCwwugny6ZMtUQJSJ1
+			  8		1F9495H8EJLb54wirgZkVgI47SP7M2RQWv
+			  9		1JbrCyt7BdxRE9GX1N7GiEct8UnIjPmpYd
+			  10	1H7vVTk4ejUbQXw45I6g5qvPBSe9bsjDqh
+			}
+
+> Let's label the first two addresses "Donations" and "Client 1"
+> and import them into our tracking wallet.  To do this,
+> copy and edit the above file in a text editor such as vim:
+
+			$ cat my.addrs
+			89ABCDEF {
+			  1		16bNmyYISiptuvJG3X7MPwiiS4HYvD7ksE  Donations
+			  2		1AmkUxrfy5dMrfmeYwTxLxfIswUCcpeysc  Client 1
             }
             }
 
 
+> With the online bitcoind running, import the two addresses into the
+> wallet:
 
 
-> To store your Bitcoins, spend them into these addresses from whatever
-> wallets/software you're currently using.  If you have lots of BTC,
-> generate many addresses so that each address will have only a
-> relatively small balance.
+			$ mmgen-addrimport my.addrs
+
+### END rewrite
 
 
 ### Spending your stored coins:
 ### Spending your stored coins:
 > Take address 1 out of cold storage by generating a key for it:
 > Take address 1 out of cold storage by generating a key for it:
 
 
-            $ mmgen-keygen 89ABCDEF-76543210[256,3].dat 1
-            ...
-            Key data saved to file '89ABCDEF[1].akeys'
+			$ mmgen-keygen 89ABCDEF-76543210[256,3].mmdat 1
+			...
+			Key data saved to file '89ABCDEF[1].akeys'
 
 
-            $ cat 89ABCDEF[1].akeys
-            89ABCDEF {
-              1  sec:  5JCAfK1pjRoJgmpmd2HEMNwHxAzprGIXeQt8dz5qt3iLvU2KCbS
-                 addr: 16bNmyYISiptuvJG3X7MPwiiS4HYvD7ksE
-            }
+			$ cat 89ABCDEF[1].akeys
+			89ABCDEF {
+			  1  sec:  5JCAfK1pjRoJgmpmd2HEMNwHxAzprGIXeQt8dz5qt3iLvU2KCbS
+				 addr: 16bNmyYISiptuvJG3X7MPwiiS4HYvD7ksE
+			}
 
 
 > Save the .akeys file to a USB stick and transfer it to your online computer.
 > Save the .akeys file to a USB stick and transfer it to your online computer.
 
 
 > On your online computer, import the secret key into a running bitcoind
 > On your online computer, import the secret key into a running bitcoind
 > or bitcoin-qt:
 > or bitcoin-qt:
 
 
-            $ bitcoind importprivkey 5JCAfK1pjRoJgmpmd2HEMNwHxAzprGIXeQt8dz5qt3iLvU2KCbS
+			$ bitcoind importprivkey 5JCAfK1pjRoJgmpmd2HEMNwHxAzprGIXeQt8dz5qt3iLvU2KCbS
 
 
-> You're done!  This address' balance can now be spent.
+> You're done!	This address' balance can now be spent.
 
 
 > OPTIONAL: To track balances without exposing secret keys on your
 > OPTIONAL: To track balances without exposing secret keys on your
 > online computer, download and compile sipa's bitcoind patched for
 > online computer, download and compile sipa's bitcoind patched for
 > watch-only addresses:
 > watch-only addresses:
 
 
-            $ git clone https://github.com/sipa/bitcoin
-            $ git branch mywatchonly remotes/origin/watchonly
-            $ git checkout mywatchonly
-            (build, install)
-            (You may have to install libboost-all-dev for the build to succeed)
+			$ git clone https://github.com/sipa/bitcoin
+			$ git branch mywatchonly remotes/origin/watchonly
+			$ git checkout mywatchonly
+			(build, install)
+			(You may have to install libboost-all-dev for the build to succeed)
 
 
 > With your newly-compiled bitcoind running, import the addresses from
 > With your newly-compiled bitcoind running, import the addresses from
 > '89ABCDEF[1-10].addrs' to track their balances:
 > '89ABCDEF[1-10].addrs' to track their balances:
 
 
-            $ bitcoind importaddress 16bNmyYISiptuvJG3X7MPwiiS4HYvD7ksE
-            $ bitcoind importaddress 1AmkUxrfy5dMrfmeYwTxLxfIswUCcpeysc
-            $ ...
+			$ bitcoind importaddress 16bNmyYISiptuvJG3X7MPwiiS4HYvD7ksE
+			$ bitcoind importaddress 1AmkUxrfy5dMrfmeYwTxLxfIswUCcpeysc
+			$ ...
 
 
 ### Using the mnemonic and seed features:
 ### Using the mnemonic and seed features:
 
 
@@ -158,51 +233,51 @@ addresses is done at your own risk.
 
 
 > Generate a mnemonic from the wallet:
 > Generate a mnemonic from the wallet:
 
 
-            $ mmgen-walletchk -m '89ABCDEF-76543210[256,3].dat'
-            ...
-            Mnemonic data saved to file '89ABCDEF.words'
+			$ mmgen-walletchk -m '89ABCDEF-76543210[256,3].mmdat'
+			...
+			Mnemonic data saved to file '89ABCDEF.mmwords'
 
 
-            $ cat 89ABCDEF.words
-            pleasure tumble spider laughter many stumble secret bother
-            after search float absent path strong curtain savior
-            worst suspend bright touch away dirty measure thorn
+			$ cat 89ABCDEF.mmwords
+			pleasure tumble spider laughter many stumble secret bother
+			after search float absent path strong curtain savior
+			worst suspend bright touch away dirty measure thorn
 
 
 > Note: a 128- or 192-bit seed will generate a shorter mnemonic of 12 or
 > Note: a 128- or 192-bit seed will generate a shorter mnemonic of 12 or
 > 18 words.  You may generate a wallet with a these seed lengths by
 > 18 words.  You may generate a wallet with a these seed lengths by
 > using the `-l` option of `mmgen-walletgen`.  Whether you consider
 > using the `-l` option of `mmgen-walletgen`.  Whether you consider
-> 128 bits of entropy enough is your call.  It's probably adequate for
+> 128 bits of entropy enough is your call.	It's probably adequate for
 > the foreseeable future.
 > the foreseeable future.
 
 
 > Generate addresses 1-11 using the mnemonic instead of the wallet:
 > Generate addresses 1-11 using the mnemonic instead of the wallet:
 
 
-            $ mmgen-addrgen -m 89ABCDEF.words 1-11
-            ...
-            Address data saved to file '89ABCDEF[1-11].addrs'
+			$ mmgen-addrgen -m 89ABCDEF.mmwords 1-11
+			...
+			Address data saved to file '89ABCDEF[1-11].addrs'
 
 
 > Compare the first ten addresses with those earlier generated from the
 > Compare the first ten addresses with those earlier generated from the
 > wallet.  You'll see they're the same.
 > wallet.  You'll see they're the same.
 
 
 > Recover a lost wallet using the mnemonic:
 > Recover a lost wallet using the mnemonic:
 
 
-            $ mmgen-walletgen -m 89ABCDEF.words
-            ...
-            Wallet saved to file '89ABCDEF-01234567[256,3].dat'
+			$ mmgen-walletgen -m 89ABCDEF.mmwords
+			...
+			Wallet saved to file '89ABCDEF-01234567[256,3].mmdat'
 
 
 > Note that the regenerated wallet has a different Key ID but
 > Note that the regenerated wallet has a different Key ID but
 > of course the same Seed ID.
 > of course the same Seed ID.
 
 
-> Seeds are generated and input the same way as mnemonics.  Just change
+> Seeds are generated and input the same way as mnemonics.	Just change
 > the `-m` option to `-s` in the preceding commands.
 > the `-m` option to `-s` in the preceding commands.
 
 
 > A seed file for a 256-bit seed looks like this:
 > A seed file for a 256-bit seed looks like this:
 
 
-            $ cat 8B7392ED.mmseed
-            f4c84b C5ZT wWpT Jsoi wRVw 2dm9 Aftd WLb8 FggQ eC8h Szjd da9L
+			$ cat 8B7392ED.mmseed
+			f4c84b C5ZT wWpT Jsoi wRVw 2dm9 Aftd WLb8 FggQ eC8h Szjd da9L
 
 
 > And for a 128-bit seed:
 > And for a 128-bit seed:
 
 
-            $ cat 8E0DFB78.mmseed
-            0fe02f XnyC NfPH piuW dQ2d nM47 VU
+			$ cat 8E0DFB78.mmseed
+			0fe02f XnyC NfPH piuW dQ2d nM47 VU
 
 
 > The latter is short enough to be memorized or written down.
 > The latter is short enough to be memorized or written down.
 
 
@@ -221,8 +296,8 @@ addresses is done at your own risk.
 > Mnemonic and seed files may be output to a directory of your choice
 > Mnemonic and seed files may be output to a directory of your choice
 > using the `-d` option of `mmgen-walletchk`.
 > using the `-d` option of `mmgen-walletchk`.
 
 
-> Bear in mind that mnemonic and seed data is unencrypted.  If it's
-> compromised, your Bitcoins can easily be stolen.  Make sure no one's
+> Bear in mind that mnemonic and seed data is unencrypted.	If it's
+> compromised, your Bitcoins can easily be stolen.	Make sure no one's
 > looking over your shoulder when you print mnemonic or seed data to
 > looking over your shoulder when you print mnemonic or seed data to
 > screen.  Securely delete your mnemonic and seed files.  In Linux, you
 > screen.  Securely delete your mnemonic and seed files.  In Linux, you
 > can achieve additional security by writing the files to volatile
 > can achieve additional security by writing the files to volatile
@@ -235,7 +310,7 @@ addresses is done at your own risk.
 
 
 ### Test suite:
 ### Test suite:
 > To see what tests are available, run the scripts in the 'tests'
 > To see what tests are available, run the scripts in the 'tests'
-> directory with no arguments.  Among others, you might find the
+> directory with no arguments.	Among others, you might find the
 > following tests to be of interest:
 > following tests to be of interest:
 
 
 >> Compare 10 addresses generated by 'keyconv' with mmgen's
 >> Compare 10 addresses generated by 'keyconv' with mmgen's

+ 5 - 10
mmgen-addrgen

@@ -122,23 +122,18 @@ 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()
 
 
-# Sanity checking and processing of command-line arguments:
-
 opts['gen_what'] = gen_what
 opts['gen_what'] = gen_what
 
 
-set_if_unset_and_typeconvert(opts,(
-	('hash_preset',hash_preset,'str'),
-	('seed_len',seed_len,'int')
-))
+check_opts(opts,long_opts)
 
 
-# Exits on invalid input
-check_opts(opts,('hash_preset','seed_len','outdir','from_brain'))
+if debug: show_opts_and_cmd_args(opts,cmd_args)
 
 
-if   len(cmd_args) == 1 and (
+if len(cmd_args) == 1 and (
 			'from_mnemonic' in opts or
 			'from_mnemonic' in opts or
 			'from_brain' in opts or
 			'from_brain' in opts or
 			'from_seed' in opts
 			'from_seed' in opts
-		): infile,addr_list_arg = "",cmd_args[0]
+		):
+	infile,addr_list_arg = "",cmd_args[0]
 elif len(cmd_args) == 2:
 elif len(cmd_args) == 2:
 	infile,addr_list_arg = cmd_args
 	infile,addr_list_arg = cmd_args
 	check_infile(infile)
 	check_infile(infile)

+ 5 - 3
mmgen-addrimport

@@ -48,6 +48,8 @@ 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)")
 	sys.exit(1)
 	sys.exit(1)
 
 
+check_opts(opts,long_opts)
+
 if cmd_args:
 if cmd_args:
 	check_infile(cmd_args[0])
 	check_infile(cmd_args[0])
 	seed_id,addr_data = parse_addrs_file(cmd_args[0])
 	seed_id,addr_data = parse_addrs_file(cmd_args[0])
@@ -55,15 +57,15 @@ else:
 	seed_id,addr_data = "",[]
 	seed_id,addr_data = "",[]
 
 
 if 'addrlist' in opts:
 if 'addrlist' in opts:
-	check_infile(opts['addrlist'])
 	l = get_lines_from_file(opts['addrlist'],"non-mmgen addresses")
 	l = get_lines_from_file(opts['addrlist'],"non-mmgen addresses")
 	addr_data += [(None,i) for i in l]
 	addr_data += [(None,i) for i in l]
 
 
 msg_r("Validating addresses...")
 msg_r("Validating addresses...")
-for i in [i[1] for i in addr_data]:
-	if not verify_addr(i):
+for i in addr_data:
+	if not verify_addr(i[1]):
 		msg("%s: invalid address" % i)
 		msg("%s: invalid address" % i)
 		sys.exit(2)
 		sys.exit(2)
+
 msg("OK")
 msg("OK")
 
 
 import mmgen.config
 import mmgen.config

+ 0 - 1
mmgen-keygen

@@ -1 +0,0 @@
-mmgen-addrgen

+ 3 - 5
mmgen-passchg

@@ -21,8 +21,7 @@ mmgen-passchg: Change a mmgen deterministic wallet's passphrase, label or
 """
 """
 
 
 import sys
 import sys
-import mmgen.Opts as Opts
-
+from mmgen.Opts import *
 from mmgen.utils import *
 from mmgen.utils import *
 from mmgen.config import *
 from mmgen.config import *
 
 
@@ -51,12 +50,11 @@ short_opts = "hd:HkL:p:P:v"
 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=","verbose"
 
 
-opts,cmd_args = Opts.process_opts(sys.argv,help_data,short_opts,long_opts)
+opts,cmd_args = process_opts(sys.argv,help_data,short_opts,long_opts)
 
 
 if 'show_hash_presets' in opts: show_hash_presets()
 if 'show_hash_presets' in opts: show_hash_presets()
 
 
-set_if_unset_and_typeconvert(opts,(('hash_preset',hash_preset,'str'),))
-check_opts(opts,('hash_preset','outdir','label'))
+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")

+ 3 - 3
mmgen-pywallet

@@ -92,15 +92,15 @@ long_opts  = "help","outdir=","echo_passphrase","json","keys","addrs",\
 
 
 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.utils import check_infile,check_opts
+from mmgen.utils import check_infile
+
+check_opts(opts,long_opts)
 
 
 if len(cmd_args) == 1:
 if len(cmd_args) == 1:
 	check_infile(cmd_args[0])
 	check_infile(cmd_args[0])
 else:
 else:
 	usage(help_data)
 	usage(help_data)
 
 
-check_opts(opts,('outdir',))
-
 if ('json' not in opts and 'keys' not in opts
 if ('json' not in opts and 'keys' not in opts
 		and 'addrs' not in opts and 'keysforaddrs' not in opts):
 		and 'addrs' not in opts and 'keysforaddrs' not in opts):
 			usage(help_data)
 			usage(help_data)

+ 12 - 10
mmgen-txcreate

@@ -26,7 +26,7 @@ from mmgen.Opts import *
 from mmgen.license import *
 from mmgen.license import *
 from mmgen.config import *
 from mmgen.config import *
 from mmgen.tx import *
 from mmgen.tx import *
-from mmgen.utils import check_opts, msg, msg_r, user_confirm
+from mmgen.utils import msg, msg_r, user_confirm
 from decimal import Decimal
 from decimal import Decimal
 
 
 prog_name = sys.argv[0].split("/")[-1]
 prog_name = sys.argv[0].split("/")[-1]
@@ -55,12 +55,9 @@ long_opts  = "help","outdir=","echo_passphrase","info","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)
 
 
-# Exits on invalid input
-check_opts(opts, ('outdir',))
+check_opts(opts,long_opts)
 
 
-if debug:
-	print "Processed options:     %s" % repr(opts)
-	print "Cmd args:              %s" % repr(cmd_args)
+if debug: show_opts_and_cmd_args(opts,cmd_args)
 
 
 if len(cmd_args) == 3:
 if len(cmd_args) == 3:
 	rcpt_arg,tx_fee,change_addr = cmd_args
 	rcpt_arg,tx_fee,change_addr = cmd_args
@@ -81,7 +78,7 @@ if not 'info' in opts:
 # Begin execution
 # Begin execution
 c = connect_to_bitcoind()
 c = connect_to_bitcoind()
 
 
-if not 'quiet' in opts and not 'info' in opts: do_license_msg()
+if not 'quiet' in opts and not 'info' in opts: do_license_msg(immed=True)
 
 
 # Begin test
 # Begin test
 # import mmgen.rpc
 # import mmgen.rpc
@@ -145,10 +142,15 @@ for i in tx_out.keys(): tx_out[i] = float(tx_out[i])
 if change: tx_out[change_addr] = float(change)
 if change: tx_out[change_addr] = float(change)
 tx_hex = c.createrawtransaction(tx_in,tx_out)
 tx_hex = c.createrawtransaction(tx_in,tx_out)
 
 
-prompt = "Transaction successfully created\nView decoded transaction?"
-if user_confirm(prompt,default_yes=False):
-	view_tx_data(c,[i.__dict__ for i in sel_unspent],tx_hex)
+msg("Transaction successfully created")
+prompt = "View decoded transaction? (y)es, (N)o, (v)iew in pager"
+reply = prompt_and_get_char(prompt,"YyNnVv",enter_ok=True)
+if reply and reply in "YyVv":
+	pager = True if reply in "Vv" else False
+	view_tx_data(c,[i.__dict__ for i in sel_unspent],tx_hex,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,opts)
+else:
+	msg("Transaction not saved")

+ 2 - 3
mmgen-txsend

@@ -25,7 +25,7 @@ from mmgen.Opts import *
 from mmgen.license import *
 from mmgen.license import *
 from mmgen.config import *
 from mmgen.config import *
 from mmgen.tx import *
 from mmgen.tx import *
-from mmgen.utils import msg,check_opts,check_infile,get_lines_from_file,confirm_or_exit
+from mmgen.utils import msg,check_infile,get_lines_from_file,confirm_or_exit
 
 
 prog_name = sys.argv[0].split("/")[-1]
 prog_name = sys.argv[0].split("/")[-1]
 
 
@@ -45,8 +45,7 @@ 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)
 
 
-# Exits on invalid input
-check_opts(opts, ('outdir',))
+check_opts(opts,long_opts)
 
 
 if len(cmd_args) == 1:
 if len(cmd_args) == 1:
 	infile = cmd_args[0]
 	infile = cmd_args[0]

+ 15 - 106
mmgen-txsign

@@ -20,13 +20,12 @@ mmgen-txsign: Sign a Bitcoin transaction generated by mmgen-txcreate
 """
 """
 
 
 import sys
 import sys
-#from hashlib import sha256
 
 
 from mmgen.Opts import *
 from mmgen.Opts import *
 from mmgen.license import *
 from mmgen.license import *
 from mmgen.config import *
 from mmgen.config import *
 from mmgen.tx import *
 from mmgen.tx import *
-from mmgen.utils import *
+from mmgen.utils import msg
 
 
 help_data = {
 help_data = {
 	'prog_name': sys.argv[0].split("/")[-1],
 	'prog_name': sys.argv[0].split("/")[-1],
@@ -80,96 +79,12 @@ long_opts  = "help","outdir=","echo_passphrase","info","tx_id",\
 
 
 opts,infiles = process_opts(sys.argv,help_data,short_opts,long_opts)
 opts,infiles = process_opts(sys.argv,help_data,short_opts,long_opts)
 
 
-# Exits on invalid input
-check_opts(opts, ('outdir','from_brain'))
-if 'keys_from_file' in opts: check_infile(opts['keys_from_file'])
+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)
 
 
-
-def get_keys_for_mmgen_addrs(mmgen_addrs,infiles):
-
-	seed_ids = list(set([i['account'][:8] for i in mmgen_addrs]))
-	seed_ids_save = seed_ids[0:]
-	keys = []
-
-	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)
-		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)
-		if seed_id in seed_ids:
-			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)]
-		else:
-			if seed_id in seed_ids_save:
-				msg_r("Ignoring duplicate seed source")
-				if infile: msg(" '%s'" % infile)
-				else:      msg(" for ID %s" % seed_id)
-			else:
-				msg("Seed source produced an invalid seed ID (%s)" % seed_id)
-				if infile:
-					msg("Invalid input file: %s" % infile)
-					sys.exit(2)
-
-	return keys
-
-
-def sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys):
-
-	try:
-		sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
-	except:
-		from mmgen.rpc import exceptions
-		msg("Using keys in wallet.dat as per user request")
-		prompt = "Enter passphrase for bitcoind wallet: "
-		while True:
-			passwd = get_bitcoind_passphrase(prompt,opts)
-
-			try:
-				c.walletpassphrase(passwd, 9999)
-			except exceptions.WalletPassphraseIncorrect:
-				msg("Passphrase incorrect")
-			else:
-				msg("Passphrase OK"); break
-
-		sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
-
-		msg("Locking wallet")
-		try:
-			c.walletlock()
-		except:
-			msg("Failed to lock wallet")
-
-	return sig_tx
-
-
-def missing_keys_errormsg(other_addrs):
-	msg("""
-A key file (option '-f') or wallet.dat (option '-w') must be supplied
-for the following non-mmgen address%s: %s""" %
-	("" if len(other_addrs) == 1 else "es",
-	" ".join([i['address'] for i in other_addrs])
-	  ))
-
 # Begin execution
 # Begin execution
-
 c = connect_to_bitcoind()
 c = connect_to_bitcoind()
 
 
 tx_file = infiles.pop(0)
 tx_file = infiles.pop(0)
@@ -186,43 +101,37 @@ if 'info' in opts:
 	view_tx_data(c,inputs_data,tx_hex,metadata)
 	view_tx_data(c,inputs_data,tx_hex,metadata)
 	sys.exit(0)
 	sys.exit(0)
 
 
-if not 'quiet' in opts: do_license_msg()
+if not 'quiet' in opts: do_license_msg(immed=True)
 
 
 msg("Successfully opened transaction file '%s'" % tx_file)
 msg("Successfully opened transaction file '%s'" % tx_file)
 
 
-if user_confirm("View transaction data? ",default_yes=False):
-	view_tx_data(c,inputs_data,tx_hex,metadata)
-
+prompt = "View transaction data? (y)es, (N)o, (v)iew in pager"
+reply = prompt_and_get_char(prompt,"YyNnVv",enter_ok=True)
+if reply and reply in "YyVv":
+	p = True if reply in "Vv" else False
+	view_tx_data(c,inputs_data,tx_hex,metadata,pager=p)
 
 
 # Are inputs mmgen addresses?
 # Are inputs mmgen addresses?
-mmgen_addrs,other_addrs = [],[]
-
-for i in inputs_data:
-	if verify_mmgen_label(i['account']):
-		mmgen_addrs.append(i)
-	else:
-		other_addrs.append(i)
-
+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'])]
 
 
-if 'keys_from_file' in opts:
-	keys = get_lines_from_file(opts['keys_from_file'],"key data")
-else:
-	keys = []
+keys = get_lines_from_file(opts['keys_from_file'],"key data") \
+	if 'keys_from_file' in opts else []
 
 
 if mmgen_addrs:
 if mmgen_addrs:
 	if other_addrs and not keys and not 'use_wallet_dat' in opts:
 	if other_addrs and not keys and not 'use_wallet_dat' in opts:
 		missing_keys_errormsg(other_addrs)
 		missing_keys_errormsg(other_addrs)
 		sys.exit(2)
 		sys.exit(2)
 
 
-	keys += get_keys_for_mmgen_addrs(mmgen_addrs,infiles)
+	keys += get_keys_for_mmgen_addrs(mmgen_addrs,infiles,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)
+		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:
 	if 'use_wallet_dat' in opts:
-		sig_tx = sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys)
+		sig_tx = sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys,opts)
 	else:
 	else:
 		if keys:
 		if keys:
 			sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
 			sig_tx = sign_transaction(c,tx_hex,sig_data,keys)

+ 3 - 2
mmgen-walletchk

@@ -49,7 +49,8 @@ long_opts  = "help","outdir=","echo_passphrase","export_mnemonic",\
 
 
 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)
 
 
-check_opts(opts, ('outdir',))
+# Argument sanity checks and processing:
+check_opts(opts,long_opts)
 
 
 if len(cmd_args) != 1: usage(help_data)
 if len(cmd_args) != 1: usage(help_data)
 
 
@@ -61,7 +62,7 @@ if 'export_seed' in opts:
 	msg("Exporting seed data to file by user request")
 	msg("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)
-msg("Wallet is OK")
+if seed: msg("Wallet is OK")
 
 
 if 'export_mnemonic' in opts:
 if 'export_mnemonic' in opts:
 	wl = get_default_wordlist()
 	wl = get_default_wordlist()

+ 9 - 24
mmgen-walletgen

@@ -101,30 +101,15 @@ opts,cmd_args = process_opts(sys.argv,help_data,short_opts,long_opts)
 
 
 if 'show_hash_presets' in opts: show_hash_presets()
 if 'show_hash_presets' in opts: show_hash_presets()
 
 
-# Argument sanity checks and processing:
-
-set_if_unset_and_typeconvert(opts,(
-	('usr_randlen',usr_randlen,'int'),
-	('hash_preset',hash_preset,'str'),
-	('seed_len',seed_len,'int')
-))
-
-# Exits on invalid input
-check_opts(opts,
-	('usr_randlen','hash_preset','seed_len','outdir','label','from_brain')
-)
-
-if debug:
-	print "Processed options:     %s" % repr(opts)
-	print "Cmd args:              %s" % repr(cmd_args)
-
-if len(cmd_args) == 1 and (
-		'from_brain' in opts or
-		'from_mnemonic' in opts or
-		'from_seed' in opts):
+check_opts(opts,long_opts)
+
+if debug: show_opts_and_cmd_args(opts,cmd_args)
+
+if len(cmd_args) == 1:
 	infile = cmd_args[0]
 	infile = cmd_args[0]
 	check_infile(infile)
 	check_infile(infile)
-elif len(cmd_args) == 0: infile = ""
+elif len(cmd_args) == 0:
+	infile = ""
 else: usage(help_data)
 else: usage(help_data)
 
 
 # Begin execution
 # Begin execution
@@ -134,7 +119,7 @@ if not 'quiet' in opts: do_license_msg()
 msg_r("Acquiring random data from your computer...")
 msg_r("Acquiring random data from your computer...")
 
 
 from time import sleep
 from time import sleep
-sleep(1)
+sleep(0.25)
 
 
 try:
 try:
 	from Crypto import Random
 	from Crypto import Random
@@ -157,7 +142,7 @@ usr_rand_data = sha256(usr_keys).digest() + \
 				sha256("".join(key_timings)).digest()
 				sha256("".join(key_timings)).digest()
 
 
 for i in 'from_mnemonic','from_brain','from_seed':
 for i in 'from_mnemonic','from_brain','from_seed':
-	if i in opts:
+	if infile or (i in opts):
 		seed = get_seed_retry(infile,opts); break
 		seed = get_seed_retry(infile,opts); break
 else:
 else:
 	# Truncate random data for smaller seed lengths
 	# Truncate random data for smaller seed lengths

+ 117 - 0
mmgen/Opts.py

@@ -18,11 +18,13 @@
 
 
 import sys, getopt
 import sys, getopt
 from mmgen.config import *
 from mmgen.config import *
+from mmgen.utils import msg
 
 
 def usage(hd):
 def usage(hd):
 	print "USAGE: %s %s" % (hd['prog_name'], hd['usage'])
 	print "USAGE: %s %s" % (hd['prog_name'], hd['usage'])
 	sys.exit(2)
 	sys.exit(2)
 
 
+
 def print_help(progname,help_data):
 def print_help(progname,help_data):
 	pn_len = str(len(progname)+2)
 	pn_len = str(len(progname)+2)
 	print ("  %-"+pn_len+"s %s")    % (progname.upper()+":", help_data['desc'])
 	print ("  %-"+pn_len+"s %s")    % (progname.upper()+":", help_data['desc'])
@@ -66,3 +68,118 @@ def process_opts(argv,help_data,short_opts,long_opts):
 	if debug: print "User-selected options: %s" % repr(opts)
 	if debug: print "User-selected options: %s" % repr(opts)
 
 
 	return opts,args
 	return opts,args
+
+
+def check_opts(opts,long_opts):
+
+	# These must be set to the default values in mmgen.config:
+	for i in cl_override_vars:
+		if i+"=" in long_opts:
+			set_if_unset_and_typeconvert(opts,i)
+
+	for opt in opts.keys():
+
+		val = opts[opt]
+		what = "parameter for '--%s' option" % opt.replace("_","-")
+
+		# Check for file existence and readability
+		for i in 'keys_from_file','addrlist','passwd_file','keysforaddrs':
+			if opt == i:
+				check_infile(val)
+				return
+
+		if opt == 'outdir':
+			what = "output directory"
+			import re, os, stat
+			d = re.sub(r'/*$','', val)
+			opts[opt] = d
+
+			try: mode = os.stat(d).st_mode
+			except:
+				msg("Unable to stat requested %s '%s'" % (what,d))
+				sys.exit(1)
+
+			if not stat.S_ISDIR(mode):
+				msg("Requested %s '%s' is not a directory" % (what,d))
+				sys.exit(1)
+
+			if not os.access(d, os.W_OK|os.X_OK):
+				msg("Requested %s '%s' is unwritable by you" % (what,d))
+				sys.exit(1)
+		elif opt == 'label':
+			label = val.strip()
+			opts[opt] = label
+
+			if len(label) > 32:
+				msg("Label must be 32 characters or less")
+				sys.exit(1)
+
+			from string import ascii_letters, digits
+			label_chrs = list(ascii_letters + digits) + [".", "_", " "]
+			for ch in list(label):
+				if ch not in label_chrs:
+					msg("'%s': illegal character in label" % ch)
+					sys.exit(1)
+		elif opt == 'from_brain':
+			try:
+				l,p = val.split(",")
+			except:
+				msg("'%s': invalid %s" % (val,what))
+				sys.exit(1)
+
+			try:
+				int(l)
+			except:
+				msg("'%s': invalid 'l' %s (not an integer)" % (l,what))
+				sys.exit(1)
+
+			if int(l) not in seed_lens:
+				msg("'%s': invalid 'l' %s.  Options: %s" %
+						(l, what, ", ".join([str(i) for i in seed_lens])))
+				sys.exit(1)
+
+			if p not in hash_presets:
+				hps = ", ".join([i for i in sorted(hash_presets.keys())])
+				msg("'%s': invalid 'p' %s.  Options: %s" % (p, what, hps))
+				sys.exit(1)
+		elif opt == 'seed_len':
+			if val not in seed_lens:
+				msg("'%s': invalid %s.  Options: %s"
+				% (val,what,", ".join([str(i) for i in seed_lens])))
+				sys.exit(2)
+		elif opt == 'hash_preset':
+			if val not in hash_presets:
+				msg("'%s': invalid %s.  Options: %s"
+				% (val,what,", ".join(sorted(hash_presets.keys()))))
+				sys.exit(2)
+		elif opt == 'usr_randlen':
+			if val > max_randlen or val < min_randlen:
+				msg("'%s': invalid %s (must be >= %s and <= %s)"
+				% (val,what,min_randlen,max_randlen))
+				sys.exit(2)
+		else:
+			if debug: print "check_opts(): No test for opt '%s'" % opt
+
+
+def show_opts_and_cmd_args(opts,cmd_args):
+	print "Processed options:     %s" % repr(opts)
+	print "Cmd args:              %s" % repr(cmd_args)
+
+
+def set_if_unset_and_typeconvert(opts,opt):
+
+	if opt in cl_override_vars:
+		if opt not in opts:
+			# Set to similarly named default value in mmgen.config
+			opts[opt] = eval(opt)
+		else:
+			vtype = type(eval(opt))
+			if   vtype == int: f,t = int,"an integer"
+			elif vtype == str: f,t = str,"a string"
+
+			try:
+				opts[opt] = f(opts[opt])
+			except:
+				msg("'%s': invalid parameter for '--%s' option (not %s)" %
+						(opts[opt],opt.replace("_","-"),t))
+				sys.exit(1)

+ 3 - 3
mmgen/addr.py

@@ -157,9 +157,9 @@ def format_addr_data(addrlist, seed_chksum, opts):
 # 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
 # The label may contain ASCII letters, numerals, and the symbols
 # '{}' and '{}'.
 # '{}' and '{}'.
-""".format(proj_name.capitalize(),max_wallet_addr_label_len,
-		"', '".join(wallet_addr_label_symbols[0:-1]),
-		wallet_addr_label_symbols[-1]).strip()
+""".format(proj_name.capitalize(),max_addr_label_len,
+		"', '".join(addr_label_symbols[0:-1]),
+		addr_label_symbols[-1]).strip()
 	data = []
 	data = []
 	if not 'stdout' in opts: data.append(header + "\n")
 	if not 'stdout' in opts: data.append(header + "\n")
 	data.append("%s {" % seed_chksum.upper())
 	data.append("%s {" % seed_chksum.upper())

+ 5 - 2
mmgen/config.py

@@ -30,6 +30,8 @@ seed_exts = wallet_ext, seed_ext, mn_ext, brain_ext
 default_wl    = "electrum"
 default_wl    = "electrum"
 #default_wl    = "tirosh"
 #default_wl    = "tirosh"
 
 
+cl_override_vars = 'seed_len','hash_preset','usr_randlen'
+
 seed_lens = 128,192,256
 seed_lens = 128,192,256
 seed_len  = 256
 seed_len  = 256
 
 
@@ -56,6 +58,7 @@ hash_presets = {
 	'3': [14, 8, 8],
 	'3': [14, 8, 8],
 	'4': [15, 8, 12],
 	'4': [15, 8, 12],
 	'5': [16, 8, 16],
 	'5': [16, 8, 16],
+	'6': [17, 8, 20],
 }
 }
-wallet_addr_label_symbols = ".","_",",","-"," "
-max_wallet_addr_label_len = 16
+addr_label_symbols = ".","_",",","-"," "
+max_addr_label_len = 16

+ 3 - 3
mmgen/license.py

@@ -585,15 +585,15 @@ copy of the Program in return for a fee.
 """
 """
 }
 }
 
 
-def do_license_msg():
+def do_license_msg(immed=False):
 	msg(gpl['warning'])
 	msg(gpl['warning'])
 	prompt = "%s " % gpl['prompt'].strip()
 	prompt = "%s " % gpl['prompt'].strip()
 
 
 	while True:
 	while True:
-		reply = get_char(prompt)
+		reply = get_char(prompt, immed_chars="wc" if immed else "")
 		if reply == 'w':
 		if reply == 'w':
 			from mmgen.utils import do_pager
 			from mmgen.utils import do_pager
-			do_pager(gpl['conditions'],"END OF CONDITIONS AND WARRANTY")
+			do_pager(gpl['conditions'])
 		elif reply == 'c':
 		elif reply == 'c':
 			msg(""); break
 			msg(""); break
 		else:
 		else:

+ 115 - 25
mmgen/tx.py

@@ -206,7 +206,7 @@ def sort_and_view(unspent):
 
 
 		for n,i in enumerate(out):
 		for n,i in enumerate(out):
 			if i.skip == "d":
 			if i.skip == "d":
-				addr = " |" + "-"*32
+				addr = "|" + "." * 33
 			else:
 			else:
 				if show_mmaddr:
 				if show_mmaddr:
 					if verify_mmgen_label(i.account):
 					if verify_mmgen_label(i.account):
@@ -215,21 +215,26 @@ def sort_and_view(unspent):
 						addr = i.address
 						addr = i.address
 				else:
 				else:
 					addr = i.address
 					addr = i.address
-			txid = "       |---" if i.skip == "t" else i.txid[:8]+"..."
+			txid = "       |..." if i.skip == "t" else i.txid[:8]+"..."
 
 
 			output.append(fs % (str(n+1)+")",txid,i.vout,addr,i.amt,i.days))
 			output.append(fs % (str(n+1)+")",txid,i.vout,addr,i.amt,i.days))
 
 
 		skip_body = False
 		skip_body = False
 		while True:
 		while True:
-			if skip_body: skip_body = False
+			if skip_body:
+				skip_body = False
+				immed_chars = "qpP"
 			else:
 			else:
 				msg("\n".join(output))
 				msg("\n".join(output))
 				msg("""
 				msg("""
 Sort options: [t]xid, [a]mount, a[d]dress, [A]ge, [r]everse, [M]mgen addr
 Sort options: [t]xid, [a]mount, a[d]dress, [A]ge, [r]everse, [M]mgen addr
 View options: [g]roup, show [m]mgen addr""")
 View options: [g]roup, show [m]mgen addr""")
+				immed_chars = "qpPtadArMgm"
 
 
 			reply = get_char(
 			reply = get_char(
-"(Type 'q' to quit sorting, 'p' to print to file, 'P' to view in pager): ")
+"(Type 'q' to quit sorting, 'p' to print to file, 'v' to view in pager): ",
+				immed_chars=immed_chars)
+
 			if   reply == 'a': unspent.sort(s_amt);  sort = "amount"; break
 			if   reply == 'a': unspent.sort(s_amt);  sort = "amount"; break
 			elif reply == 't': unspent.sort(s_txid); sort = "txid"; break
 			elif reply == 't': unspent.sort(s_txid); sort = "txid"; break
 			elif reply == 'd': unspent.sort(s_addr); sort = "address"; break
 			elif reply == 'd': unspent.sort(s_addr); sort = "address"; break
@@ -268,7 +273,7 @@ View options: [g]roup, show [m]mgen addr""")
 				write_to_file(outfile, outdata)
 				write_to_file(outfile, outdata)
 				skip_body = True
 				skip_body = True
 				msg("\nData written to '%s'" % outfile)
 				msg("\nData written to '%s'" % outfile)
-			elif reply == 'P': do_pager("\n".join(output))
+			elif reply == 'v': do_pager("\n".join(output))
 			elif reply == 'q': break
 			elif reply == 'q': break
 			else: msg("Invalid input")
 			else: msg("Invalid input")
 
 
@@ -297,28 +302,28 @@ def verify_mmgen_label(s,return_str=False,check_label_len=False):
 		if not i in "0123456789": return fail
 		if not i in "0123456789": return fail
 
 
 	if check_label_len and comment:
 	if check_label_len and comment:
-		check_wallet_addr_comment(comment)
+		check_addr_comment(comment)
 
 
 	return success
 	return success
 
 
 
 
-def view_tx_data(c,inputs_data,tx_hex,metadata=[]):
+def view_tx_data(c,inputs_data,tx_hex,metadata=[],pager=False):
 
 
 	td = c.decoderawtransaction(tx_hex)
 	td = c.decoderawtransaction(tx_hex)
 
 
-	msg("TRANSACTION DATA:\n")
+	out = "TRANSACTION DATA\n\n"
 
 
-	if metadata: msg(
-		"Header: [Tx ID: {}] [Amount: {} BTC] [Time: {}]\n".format(*metadata))
+	if metadata:
+		out += "Header: [Tx ID: {}] [Amount: {} BTC] [Time: {}]\n\n".format(*metadata)
 
 
-	msg("Inputs:")
+	out += "Inputs:\n\n"
 	total_in = 0
 	total_in = 0
 	for n,i in enumerate(td['vin']):
 	for n,i in enumerate(td['vin']):
 		for j in inputs_data:
 		for j in inputs_data:
 			if j['txid'] == i['txid'] and j['vout'] == i['vout']:
 			if j['txid'] == i['txid'] and j['vout'] == i['vout']:
 				days = int(j['confirmations'] * mins_per_block / (60*24))
 				days = int(j['confirmations'] * mins_per_block / (60*24))
 				total_in += j['amount']
 				total_in += j['amount']
-				msg(" " + """
+				out += (" " + """
 %-2s tx,vout: %s,%s
 %-2s tx,vout: %s,%s
     address:        %s
     address:        %s
     ID/label:       %s
     ID/label:       %s
@@ -326,25 +331,29 @@ def view_tx_data(c,inputs_data,tx_hex,metadata=[]):
     confirmations:  %s (around %s days)
     confirmations:  %s (around %s days)
 """.strip() %
 """.strip() %
 	(n+1,i['txid'],i['vout'],j['address'],verify_mmgen_label(j['account'],True),
 	(n+1,i['txid'],i['vout'],j['address'],verify_mmgen_label(j['account'],True),
-		trim_exponent(j['amount']),j['confirmations'],days)+"\n")
+		trim_exponent(j['amount']),j['confirmations'],days)+"\n\n")
 				break
 				break
 
 
-	msg("Total input: %s BTC\n" % trim_exponent(total_in))
+	out += "Total input: %s BTC\n\n" % trim_exponent(total_in)
 
 
 	total_out = 0
 	total_out = 0
-	msg("Outputs:")
+	out += "Outputs:\n\n"
 	for n,i in enumerate(td['vout']):
 	for n,i in enumerate(td['vout']):
 		total_out += i['value']
 		total_out += i['value']
-		msg(" " + """
+		out += (" " + """
 %-2s address: %s
 %-2s address: %s
     amount:  %s BTC
     amount:  %s BTC
 """.strip() % (
 """.strip() % (
 		n,
 		n,
 		i['scriptPubKey']['addresses'][0],
 		i['scriptPubKey']['addresses'][0],
 		trim_exponent(i['value']))
 		trim_exponent(i['value']))
-	+ "\n")
-	msg("Total output: %s BTC" % trim_exponent(total_out))
-	msg("TX fee:       %s BTC\n" % trim_exponent(total_in-total_out))
+	+ "\n\n")
+	out += "Total output: %s BTC\n" % trim_exponent(total_out)
+	out += "TX fee:       %s BTC\n" % trim_exponent(total_in-total_out)
+
+	if pager: do_pager(out+"\n")
+	else:     msg("\n"+out)
+
 
 
 
 
 def parse_tx_data(tx_data,infile):
 def parse_tx_data(tx_data,infile):
@@ -418,20 +427,20 @@ def make_tx_out(rcpt_arg):
 
 
 	return tx_out
 	return tx_out
 
 
-def check_wallet_addr_comment(label):
+def check_addr_comment(label):
 
 
-	if len(label) > max_wallet_addr_label_len:
+	if len(label) > max_addr_label_len:
 		msg("'%s': overlong label (length must be <=%s)" %
 		msg("'%s': overlong label (length must be <=%s)" %
-				(label,max_wallet_addr_label_len))
+				(label,max_addr_label_len))
 		sys.exit(3)
 		sys.exit(3)
 
 
 	from string import ascii_letters, digits
 	from string import ascii_letters, digits
-	chrs = tuple(ascii_letters + digits) + wallet_addr_label_symbols
+	chrs = tuple(ascii_letters + digits) + addr_label_symbols
 	for ch in list(label):
 	for ch in list(label):
 		if ch not in chrs:
 		if ch not in chrs:
 			msg("'%s': illegal character in label '%s'" % (ch,label))
 			msg("'%s': illegal character in label '%s'" % (ch,label))
 			msg("Permitted characters: A-Za-z0-9, plus '%s'" %
 			msg("Permitted characters: A-Za-z0-9, plus '%s'" %
-					"', '".join(wallet_addr_label_symbols))
+					"', '".join(addr_label_symbols))
 			sys.exit(3)
 			sys.exit(3)
 
 
 
 
@@ -474,7 +483,7 @@ def parse_addrs_file(f):
 				msg("'%s': invalid address" % d[1])
 				msg("'%s': invalid address" % d[1])
 				sys.exit(3)
 				sys.exit(3)
 
 
-			if len(d) == 3: check_wallet_addr_comment(d[2])
+			if len(d) == 3: check_addr_comment(d[2])
 
 
 			ret.append(tuple(d))
 			ret.append(tuple(d))
 
 
@@ -501,3 +510,84 @@ def sign_transaction(c,tx_hex,sig_data,keys=None):
 # 		sys.exit(3)
 # 		sys.exit(3)
 
 
 	return sig_tx
 	return sig_tx
+
+
+def get_keys_for_mmgen_addrs(mmgen_addrs,infiles,opts):
+
+	seed_ids = list(set([i['account'][:8] for i in mmgen_addrs]))
+	seed_ids_save = seed_ids[0:]
+	keys = []
+
+	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)
+		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)
+		if seed_id in seed_ids:
+			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)]
+		else:
+			if seed_id in seed_ids_save:
+				msg_r("Ignoring duplicate seed source")
+				if infile: msg(" '%s'" % infile)
+				else:      msg(" for ID %s" % seed_id)
+			else:
+				msg("Seed source produced an invalid seed ID (%s)" % seed_id)
+				if infile:
+					msg("Invalid input file: %s" % infile)
+					sys.exit(2)
+
+	return keys
+
+
+def sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys,opts):
+
+	try:
+		sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
+	except:
+		from mmgen.rpc import exceptions
+		msg("Using keys in wallet.dat as per user request")
+		prompt = "Enter passphrase for bitcoind wallet: "
+		while True:
+			passwd = get_bitcoind_passphrase(prompt,opts)
+
+			try:
+				c.walletpassphrase(passwd, 9999)
+			except exceptions.WalletPassphraseIncorrect:
+				msg("Passphrase incorrect")
+			else:
+				msg("Passphrase OK"); break
+
+		sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
+
+		msg("Locking wallet")
+		try:
+			c.walletlock()
+		except:
+			msg("Failed to lock wallet")
+
+	return sig_tx
+
+
+def missing_keys_errormsg(other_addrs):
+	msg("""
+A key file (option '-f') or wallet.dat (option '-w') must be supplied
+for the following non-mmgen address%s: %s""" %
+	("" if len(other_addrs) == 1 else "es",
+	" ".join([i['address'] for i in other_addrs])
+	  ))

+ 134 - 196
mmgen/utils.py

@@ -26,69 +26,94 @@ from mmgen.bitcoin import b58decode_pad
 
 
 def msg(s):   sys.stderr.write(s + "\n")
 def msg(s):   sys.stderr.write(s + "\n")
 def msg_r(s): sys.stderr.write(s)
 def msg_r(s): sys.stderr.write(s)
-
 def bail(): sys.exit(9)
 def bail(): sys.exit(9)
 
 
-def my_getpass(prompt):
 
 
-	from getpass import getpass
-	# getpass prompts to stderr, so no trickery required as with raw_input()
-	try: pw = getpass(prompt)
+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:
 	except:
-		msg("\nUser interrupt")
+		print "\nUser interrupt"
 		sys.exit(1)
 		sys.exit(1)
+	finally:
+		termios.tcsetattr(fd, termios.TCSADRAIN, old)
 
 
-	return pw
-
-term = False
 
 
-def get_char(prompt=""):
+def get_keypress_mswin(prompt="",immed_chars=""):
 
 
 	msg_r(prompt)
 	msg_r(prompt)
+	timeout = float(0.5)
 
 
-	global term
+	try:
+		while True:
+			if msvcrt.kbhit():
+				ch = msvcrt.getch()
 
 
-	if not term:
-		try:
-			import tty, termios
-			term = "unix"
-		except:
-			try:
-				import msvcrt
-				term = "mswin"
-			except:
-				msg("Unable to set terminal mode")
-				sys.exit(2)
+				if ord(ch) == 3: raise KeyboardInterrupt
 
 
-	try:
-		if term == "unix":
-			import tty, termios
-			fd = sys.stdin.fileno()
-			old = termios.tcgetattr(fd)
-			tty.setcbreak(fd)
-			ch = sys.stdin.read(1)
-		elif term == "mswin":
-			import msvcrt
-			ch = msvcrt.getch()
-			if ord(ch) == 3:
-				raise KeyboardInterrupt
+				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:
 	except:
 		msg("\nUser interrupt")
 		msg("\nUser interrupt")
 		sys.exit(1)
 		sys.exit(1)
-	finally:
-		if term == "unix":
-			termios.tcsetattr(fd, termios.TCSADRAIN, old)
 
 
-	return ch
+
+try:
+	import tty, termios
+	from select import select
+	get_char = get_keypress_unix
+except:
+	try:
+		import msvcrt, time
+		get_char = get_keypress_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):
+def my_raw_input(prompt,echo=True):
 
 
 	msg_r(prompt)
 	msg_r(prompt)
-	try: reply = raw_input()
-	except:
-		msg("\nUser interrupt")
-		sys.exit(1)
+	reply = ""
+
+	while True:
+		ch = get_char(immed_chars="ALL_EXCEPT_ENTER")
+		if echo: msg_r(ch)
+		if ch in "\n\r":
+			if not echo: msg("")
+			break
+		reply += ch
 
 
 	return reply
 	return reply
 
 
@@ -101,6 +126,7 @@ def _get_hash_params(hash_preset):
 		msg("%s: invalid 'hash_preset' value" % hash_preset)
 		msg("%s: invalid 'hash_preset' value" % hash_preset)
 		sys.exit(3)
 		sys.exit(3)
 
 
+
 def show_hash_presets():
 def show_hash_presets():
 	fs = "  {:<7} {:<6} {:<3}  {}"
 	fs = "  {:<7} {:<6} {:<3}  {}"
 	msg("Available parameters for scrypt.hash():")
 	msg("Available parameters for scrypt.hash():")
@@ -111,87 +137,6 @@ def show_hash_presets():
 	sys.exit(0)
 	sys.exit(0)
 
 
 
 
-def check_opts(opts,keys):
-
-	for key in keys:
-		if key not in opts: continue
-
-		val = opts[key]
-		what = "parameter for '--%s' option" % key.replace("_","-")
-
-		if key == 'outdir':
-			what = "output directory"
-			import re, os, stat
-			d = re.sub(r'/*$','', val)
-			opts[key] = d
-
-			try: mode = os.stat(d).st_mode
-			except:
-				msg("Unable to stat requested %s '%s'" % (what,d))
-				sys.exit(1)
-
-			if not stat.S_ISDIR(mode):
-				msg("Requested %s '%s' is not a directory" % (what,d))
-				sys.exit(1)
-
-			if not os.access(d, os.W_OK|os.X_OK):
-				msg("Requested %s '%s' is unwritable by you" % (what,d))
-				sys.exit(1)
-
-		elif key == 'label':
-			label = val.strip()
-			opts[key] = label
-
-			if len(label) > 32:
-				msg("Label must be 32 characters or less")
-				sys.exit(1)
-
-			from string import ascii_letters, digits
-			label_chrs = list(ascii_letters + digits) + [".", "_", " "]
-			for ch in list(label):
-				if ch not in label_chrs:
-					msg("'%s': illegal character in label" % ch)
-					sys.exit(1)
-
-		elif key == 'from_brain':
-			try:
-				l,p = val.split(",")
-			except:
-				msg("'%s': invalid %s" % (val,what))
-				sys.exit(1)
-
-			try:
-				int(l)
-			except:
-				msg("'%s': invalid 'l' %s (not an integer)" % (l,what))
-				sys.exit(1)
-
-			if int(l) not in seed_lens:
-				msg("'%s': invalid 'l' %s.  Options: %s" %
-						(l, what, ", ".join([str(i) for i in seed_lens])))
-				sys.exit(1)
-
-			if p not in hash_presets:
-				hps = ", ".join([i for i in sorted(hash_presets.keys())])
-				msg("'%s': invalid 'p' %s.  Options: %s" % (p, what, hps))
-				sys.exit(1)
-		elif key == 'seed_len':
-			if val not in seed_lens:
-				msg("'%s': invalid %s.  Options: %s"
-				% (val,what,", ".join([str(i) for i in seed_lens])))
-				sys.exit(2)
-		elif key == 'hash_preset':
-			if val not in hash_presets:
-				msg("'%s': invalid %s.  Options: %s"
-				% (val,what,", ".join(sorted(hash_presets.keys()))))
-				sys.exit(2)
-		elif key == 'usr_randlen':
-			if val > max_randlen or val < min_randlen:
-				msg("'%s': invalid %s (must be >= %s and <= %s)"
-				% (val,what,min_randlen,max_randlen))
-				sys.exit(2)
-
-
 cmessages = {
 cmessages = {
 	'null': "",
 	'null': "",
 	'unencrypted_secret_keys': """
 	'unencrypted_secret_keys': """
@@ -215,6 +160,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("")
 	msg("")
@@ -236,38 +182,34 @@ def confirm_or_exit(message, question, expect="YES"):
 	msg("")
 	msg("")
 
 
 
 
-def user_confirm(prompt,default_yes=False):
+def user_confirm(prompt,default_yes=False,verbose=False):
 
 
 	q = "(Y/n)" if default_yes else "(y/N)"
 	q = "(Y/n)" if default_yes else "(y/N)"
 
 
 	while True:
 	while True:
-		reply = get_char("%s %s: " % (prompt, q)).strip()
-		msg("")
+		reply = get_char("%s %s: " % (prompt, q)).strip("\n\r")
 
 
 		if not reply:
 		if not reply:
-			return True if default_yes else False
-		elif reply in 'yY': return True
-		elif reply in 'nN': return False
-		else: msg("Invalid reply")
+			if default_yes: msg(""); return True
+			else:           msg(""); return False
+		elif reply in 'yY': msg(""); return True
+		elif reply in 'nN': msg(""); return False
+		else:
+			if verbose: msg("\nInvalid reply")
+			else: msg_r("\r")
 
 
 
 
-def set_if_unset_and_typeconvert(opts,item):
+def prompt_and_get_char(prompt,chars,enter_ok=False,verbose=False):
 
 
-	for opt,var,dtype in item:
-		if   dtype == 'int': f,s = int,"an integer"
-		elif dtype == 'str': f,s = str,"a string"
+	while True:
+		reply = get_char("%s: " % prompt).strip("\n\r")
 
 
-		if opt in opts:
-			val = opts[opt]
-			what = "invalid parameter for '--%s' option" % opt.replace("_","-")
-			try:
-				f(val)
-			except:
-				msg("'%s': %s (not %s)" % (val,what,s))
-				sys.exit(1)
-			opts[opt] = f(val)
-		else:
-			opts[opt] = var
+		if reply in chars or (enter_ok and not reply):
+			msg("")
+			return reply
+
+		if verbose: msg("\nInvalid reply")
+		else: msg_r("\r")
 
 
 
 
 def make_chksum_8(s):
 def make_chksum_8(s):
@@ -279,11 +221,6 @@ def make_chksum_6(s):
 	return sha256(s).hexdigest()[:6]
 	return sha256(s).hexdigest()[:6]
 
 
 
 
-def _get_from_brain_opt_params(opts):
-	l,p = opts['from_brain'].split(",")
-	return(int(l),p)
-
-
 def check_infile(f):
 def check_infile(f):
 
 
 	import os, stat
 	import os, stat
@@ -377,6 +314,11 @@ def _scrypt_hash_passphrase(passwd, salt, hash_preset, buflen=32):
 	return scrypt.hash(passwd, salt, 2**N, r, p, buflen=buflen)
 	return scrypt.hash(passwd, salt, 2**N, r, p, buflen=buflen)
 
 
 
 
+def _get_from_brain_opt_params(opts):
+	l,p = opts['from_brain'].split(",")
+	return(int(l),p)
+
+
 def _get_seed_from_brain_passphrase(words,opts):
 def _get_seed_from_brain_passphrase(words,opts):
 	bp = " ".join(words)
 	bp = " ".join(words)
 	if debug: print "Sanitized brain passphrase: %s" % bp
 	if debug: print "Sanitized brain passphrase: %s" % bp
@@ -541,6 +483,7 @@ def make_timestr():
 def secs_to_hms(secs):
 def secs_to_hms(secs):
 	return "{:02d}:{:02d}:{:02d}".format(secs/3600, (secs/60) % 60, secs % 60)
 	return "{:02d}:{:02d}:{:02d}".format(secs/3600, (secs/60) % 60, secs % 60)
 
 
+
 def write_wallet_to_file(seed, passwd, key_id, salt, enc_seed, opts):
 def write_wallet_to_file(seed, passwd, key_id, salt, enc_seed, opts):
 
 
 	seed_id = make_chksum_8(seed)
 	seed_id = make_chksum_8(seed)
@@ -701,10 +644,8 @@ def get_data_from_wallet(infile,opts,silent=False):
 
 
 def _get_words_from_user(prompt, opts):
 def _get_words_from_user(prompt, opts):
 	# split() also strips
 	# split() also strips
-	if 'echo_passphrase' in opts:
-		words = my_raw_input(prompt).split()
-	else:
-		words = my_getpass(prompt).split()
+	words = my_raw_input(prompt,
+				echo=True if 'echo_passphrase' in opts else False).split()
 	if debug: print "Sanitized input: [%s]" % " ".join(words)
 	if debug: print "Sanitized input: [%s]" % " ".join(words)
 	return words
 	return words
 
 
@@ -758,6 +699,7 @@ def _get_seed_from_seed_data(words):
 		msg("Invalid checksum for {} seed".format(proj_name))
 		msg("Invalid checksum for {} seed".format(proj_name))
 		return False
 		return False
 
 
+
 passwd_file_used = False
 passwd_file_used = False
 
 
 def mark_passwd_file_as_used(opts):
 def mark_passwd_file_as_used(opts):
@@ -781,10 +723,8 @@ def get_bitcoind_passphrase(prompt,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:
-		if 'echo_passphrase' in opts:
-			return my_raw_input(prompt)
-		else:
-			return my_getpass(prompt)
+		return my_raw_input(prompt,
+					echo=True if 'echo_passphrase' in opts else False)
 
 
 
 
 def get_seed_from_wallet(
 def get_seed_from_wallet(
@@ -925,47 +865,45 @@ def remove_blanks_comments(lines):
 
 
 	return ret
 	return ret
 
 
-def do_pager(text,endmsg=""):
-	import os
-	if sys.platform.startswith("linux"):
-		if 'PAGER' in os.environ and os.environ['PAGER']:
-			try:
-				p = os.popen(os.environ['PAGER'], 'w')
-			except:
-				print text
-			else:
-				try:
-					p.write(text)
-					p.close()
-				except:
-					p.close()
-				msg_r("\r")
-		else:
-			print text
-	elif sys.platform.startswith("win"):
+
+def do_pager(text):
+
+	pagers = ["less","more"]
+	shell = False
+
+	from os import environ
+
+# Hack for MS Windows command line (i.e. non CygWin) environment
+# When 'shell' is true, Windows aborts the calling program if executable
+# not found.
+# When 'shell' is false, an exception is raised, invoking the fallback
+# 'print' instead of the pager.
+# We risk assuming that "more" will always be available on a stock
+# Windows installation.
+	if sys.platform.startswith("win") and 'HOME' not in environ:
+		shell = True
+		pagers = ["more"]
+
+	if 'PAGER' in environ and environ['PAGER'] != pagers[0]:
+		pagers = [environ['PAGER']] + pagers
+
+	for pager in pagers:
+		end = "" if pager == "less" else "\n(end of text)\n"
 		try:
 		try:
-			import msvcrt
-		except:
-			print text
+			from subprocess import Popen, PIPE, STDOUT
+			p = Popen([pager], stdin=PIPE, shell=shell)
+		except: pass
 		else:
 		else:
 			try:
 			try:
-				from subprocess import Popen, PIPE, STDOUT
-				p = Popen(["more","/C"], stdin=PIPE, shell=True)
-				if endmsg:
-					p.stdin.write("%s\n%s\n\n" % (text,endmsg))
-				else:
-					p.stdin.write(text)
+				p.communicate(text+end+"\n")
 			except:
 			except:
-				msg("\nUser exit")
-
-			from time import sleep
-			# Flush stdin
-			while msvcrt.kbhit(): msvcrt.getch()
-			sleep(1)
-			while msvcrt.kbhit(): msvcrt.getch()
-			msg("")
-	else:
-		print text
+				# Has no effect.  Why?
+				if pager != "less":
+					msg("\n(Interrupted by user)\n")
+			finally:
+				msg_r("\r")
+				break
+	else: print text+end
 
 
 
 
 if __name__ == "__main__":
 if __name__ == "__main__":

+ 6 - 4
mmgen/walletgen.py

@@ -20,7 +20,7 @@ walletgen.py:  Routines used for seed generation and wallet creation
 """
 """
 
 
 import sys
 import sys
-from mmgen.utils import msg, msg_r, get_char
+from mmgen.utils import msg, msg_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):
@@ -48,17 +48,19 @@ displayed on the screen.
 	user_rand_data,intervals = "",[]
 	user_rand_data,intervals = "",[]
 
 
 	for i in range(ulen):
 	for i in range(ulen):
-		user_rand_data += get_char()
+		user_rand_data += get_char(immed_chars="ALL")
 		msg_r("\r" + prompt % (ulen - i - 1))
 		msg_r("\r" + prompt % (ulen - i - 1))
 		now = time.time()
 		now = time.time()
 		intervals.append(now - saved_time)
 		intervals.append(now - saved_time)
 		saved_time = now
 		saved_time = now
+
 	if 'quiet' in opts:
 	if 'quiet' in opts:
 		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")
-	time.sleep(0.5)
-	get_char("User random data successfully acquired.  Press ENTER to continue: ")
+
+	prompt = "User random data successfully acquired.  Press ENTER to continue"
+	prompt_and_get_char(prompt,"",enter_ok=True)
 
 
 	return user_rand_data, ["{:.22f}".format(i) for i in intervals]
 	return user_rand_data, ["{:.22f}".format(i) for i in intervals]