Browse Source

Modified README.md. Various fixes, improvements.

philemon 11 years ago
parent
commit
536bfb5221
21 changed files with 839 additions and 385 deletions
  1. 358 183
      README.md
  2. 78 0
      doc/MMGenLinuxInstall.md
  3. 22 0
      doc/MMGenWindowsInstall.md
  4. 7 7
      mmgen-addrgen
  5. 25 9
      mmgen-addrimport
  6. 4 4
      mmgen-passchg
  7. 68 22
      mmgen-txcreate
  8. 1 1
      mmgen-txsend
  9. 2 2
      mmgen-txsign
  10. 3 3
      mmgen-walletchk
  11. 12 12
      mmgen-walletgen
  12. 26 25
      mmgen/Opts.py
  13. 7 10
      mmgen/addr.py
  14. 20 2
      mmgen/config.py
  15. 0 1
      mmgen/license.py
  16. 3 3
      mmgen/mnemonic.py
  17. 5 5
      mmgen/rpc/connection.py
  18. 2 2
      mmgen/rpc/proxy.py
  19. 83 38
      mmgen/tx.py
  20. 112 55
      mmgen/utils.py
  21. 1 1
      setup.py

+ 358 - 183
README.md

@@ -1,261 +1,436 @@
-#  mmgen = Multi-Mode GENerator
-## a Bitcoin cold storage solution for the command line
+MMGen = Multi-Mode GENerator
+============================
 
-NOTE: Parts of this README are now **out of date**.  In particular, the
-new transaction scripts automate the process of offline signing, so that
-your private keys never touch the online machine.  An updated README is
-on the way.  For the time being, consult the `--help` option of the
-`mmgen-tx*` scripts.
+a Bitcoin cold storage solution for the command line
+----------------------------------------------------
 
-NOTE: For the time being, MMGen should be considered experimental software.
-Downloading and testing it out is easy, risk-free and encouraged.
-However, spending significant amounts of BTC into your mmgen-generated
-addresses is done at your own risk.
+### Description
 
-### Features:
+MMGen is implemented as a suite of lightweight Python command-line
+scripts that require only a bare minimum of system resources.
+The scripts function in tandem with a modified
+bitcoind running on an online computer and a standard
+bitcoind running offline to provide a robust solution for securely
+storing, tracking, spending and receiving your Bitcoins.  "Non-MMGen"
+addresses can be tracked and spent as well, creating an easy migration
+path from other wallets.
 
-> 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.
+The online bitcoin daemon is modified to support watch-only addresses,
+a feature soon to be included in the mainline Satoshi build.
+In the meantime, instructions are provided below for compiling the
+watch-only bitcoind, which under Linux is surprisingly easy.
 
-> With MMGen you can choose from four different ways to access your Bitcoins:
+MMGen uses the reference Satoshi daemon, rather than less-reliable
+third-party software, to do all the "heavy lifting" of tracking and
+signing transactions.
+And unlike other online/offline wallet solutions, the MMGen system is
+completely self-contained, relying on no external server to do its
+work; no third party will know which addresses you're tracking.
 
->> 1) an encrypted wallet (the AES 256 key is generated from your
->> password using the crack-resistant scrypt hash function.  The
->> wallet's password and hash strength can be changed);
+Like all deterministic wallets, MMGen can generate a virtually
+unlimited number of address/key pairs from a single seed.  Your wallet
+never changes, so you need back it up only once.
+Transactions are signed offline: your private keys never touch an
+online computer.
 
->> 2) a short, human-readable seed file (unencrypted);
+At the heart of the MMGen system is the seed, the "master key"
+providing access to all your Bitcoins.  The seed can be stored in four
+different ways:
 
->> 3) an Electrum-like mnemonic of 12, 18 or 24 words; or
+> 1) as a wallet with a password
+> encrypted using the crack-resistant scrypt hash function.
+> Scrypt's parameters can be adjusted on the command line, making your
+> wallet's password virtually impossible to crack should it fall into the wrong
+> hands.  The wallet is a tiny text file
+> suitable for printing or even writing out by hand;
 
->> 4) a brainwallet password (recommended for expert users only).
+> 2) as a seed file: a one-line base-58 representation of your
+> unencrypted seed plus a checksum;
 
-> Furthermore, these methods can all be combined.  If you forget your
-> mnemonic, for example, you can regenerate it and your keys from a
-> stored wallet or seed.  Correspondingly, a lost wallet or seed can be
-> recovered from the mnemonic.
+> 3) as an Electrum-like mnemonic of 12, 18 or 24 words; or
 
-> 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.
+> 4) as a brainwallet password (this option is recommended for expert
+> users only).
 
-> 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.
+The best part is that all these methods can be combined.  If you
+forget your mnemonic, for example, you can regenerate it and your keys
+from the stored wallet or seed file.  Correspondingly, a lost wallet can
+be regenerated from the mnemonic or seed or a lost seed from the wallet or
+mnemonic.
 
 
-### Instructions for Linux/Unix:
+### Download/Install
 
-### Download/Install:
->  Install required Python modules:
+> #### [Install on Microsoft Windows](doc/MMGenWindowsInstall.md)
 
-            sudo pip install ecdsa scrypt pycrypto bitcoin-python
+> #### [Install on Debian/Ubuntu Linux](doc/MMGenLinuxInstall.md)
 
->  Install mmgen:
 
-            git clone https://github.com/mmgen/mmgen.git
-            cd mmgen; sudo ./setup.py install
+### Using MMGen
 
->  Install vanitygen (optional but recommended):
+#### Generate a wallet (offline computer):
 
-            git clone https://github.com/samr7/vanitygen.git
-            (build and put the 'keyconv' executable in your path)
+On your offline computer, generate a wallet with a random seed:
 
-### Getting Started:
-> On your offline computer:
+		$ mmgen-walletgen
+		...
+		Wallet saved to file '89ABCDEF-76543210[256,3].mmdat'
 
-> Generate a wallet with a random seed:
+"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.
 
-            $ mmgen-walletgen
-            ...
-            Wallet saved to file '89ABCDEF-76543210[256,3].dat'
+The Seed ID never changes and will be used to identify all keys/addresses
+generated by this seed.  The Key ID changes when the wallet's password
+or hash preset are changed.
 
-> "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.
+"256" is the seed length; "3" is the scrypt hash preset.  These values
+are configurable: type `mmgen-walletgen --help` for details.
 
-> 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.
+#### Generate addresses (offline computer):
 
-> "256" is the seed length; "3" is the scrypt hash preset.  These are
-> configurable.
+Now generate ten addresses with your just-created wallet:
 
+		$ mmgen-addrgen 89ABCDEF-76543210[256,3].mmdat 1-10
+		...
+		Address data saved to file '89ABCDEF[1-10].addrs'
+		$ 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
+		}
+
+Note that the address range, "1-10", is reflected in the resulting filename.
+MMGen addresses are identified by their seed ID plus number, separated
+by a colon.  In this example, "89ABCDEF:1" is the MMGen equivalent
+of Bitcoin address 16bNmyYISiptuvJG3X7MPwiiS4HYvD7ksE, "89ABCDEF:2"
+the equivalent of 1AmkUxrfy5dMrfmeYwTxLxfIswUCcpeysc, and so forth.
 
-> Generate ten addresses with the wallet:
+Let's say you've decided to transfer some BTC into the first four
+addresses above.  Your first step, then, will be to import these
+addresses into the tracking wallet on your online machine.
+You've chosen to provide the addresses with the labels "Donations",
+"Storage 1", "Storage 2" and "Storage 3" for convenient identification.
 
-            $ mmgen-addrgen 89ABCDEF-76543210[256,3].dat 1-10
-            ...
-            Address data saved to file '89ABCDEF[1-10].addrs'
+Make a copy of the file:
 
+		$ cp '89ABCDEF[1-10].addrs' my_addrs_for_import
 
-> Note that the address range, "1-10", is included in the resulting filename.
+and edit the copy using your favorite text editor.  The result will
+look something like this:
 
-            $ 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
-            }
+		$ cat my_addrs_for_import
+		89ABCDEF {
+		  1    16bNmyYISiptuvJG3X7MPwiiS4HYvD7ksE  Donations
+		  2    1AmkUxrfy5dMrfmeYwTxLxfIswUCcpeysc  Storage 1
+		  3    1HgYCsfqYzIg7LVVfDTp7gYJocJEiDAy6N  Storage 2
+		  4    14Tu3z1tiexXDonNsFIkvzqutE5E3pTK8s  Storage 3
+		}
 
+Note that rows in the list may be arranged in any order; addresses
+need not be consecutive.
 
-> 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.
+Copy this file onto a USB stick and transfer it to your online
+computer.
 
-### Spending your stored coins:
-> Take address 1 out of cold storage by generating a key for it:
+#### Import addresses (online computer):
 
-            $ mmgen-keygen 89ABCDEF-76543210[256,3].dat 1
-            ...
-            Key data saved to file '89ABCDEF[1].akeys'
+On your online computer, start bitcoind and import the addresses into
+the tracking wallet:
 
-            $ cat 89ABCDEF[1].akeys
-            89ABCDEF {
-              1  sec:  5JCAfK1pjRoJgmpmd2HEMNwHxAzprGIXeQt8dz5qt3iLvU2KCbS
-                 addr: 16bNmyYISiptuvJG3X7MPwiiS4HYvD7ksE
-            }
+		$ mmgen-addrimport my_addrs_for_import
 
-> Save the .akeys file to a USB stick and transfer it to your online computer.
+These addresses are now being "tracked".  Any BTC transferred
+to them will show up in our listing of unspent outputs.
 
-> On your online computer, import the secret key into a running bitcoind
-> or bitcoin-qt:
+You'll want to track your existing addresses with balances too.
+Make a plain list of these addresses, one address per line, and import
+the list into the tracking wallet using the same command
+with the `'-l'` option:
 
-            $ bitcoind importprivkey 5JCAfK1pjRoJgmpmd2HEMNwHxAzprGIXeQt8dz5qt3iLvU2KCbS
+		$ mmgen-addrimport -l my_existing_addrs_with_balances
 
-> You're done!  This address' balance can now be spent.
+Since the importing process is slow, you may want to do it in stages,
+a few addresses at a time.
 
-> OPTIONAL: To track balances without exposing secret keys on your
-> online computer, download and compile sipa's bitcoind patched for
-> watch-only addresses:
+For your convenience, comments beginning with a '#' symbol may be
+included in address lists.
+Continue in this fashion until you've imported all addresses with
+balances into your tracking wallet.
 
-            $ 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)
+#### Create a transaction (online computer):
+
+Now that your existing addresses are imported, you're ready to create
+a test transaction using the `mmgen-txcreate` command.  Note that
+transactions are harmless until they're signed and broadcast to the
+network, so feel free to experiment with different transactions
+using different combinations of inputs and outputs.
+
+First of all, you'll probably want to examine your balances:
+
+		$ mmgen-txcreate -i
+
+A list of all your unspent outputs will appear,
+along with a menu allowing you to sort the outputs by four
+criteria: transaction ID, address, amount and transaction age.
+Your overall balance in BTC appears at the top of the screen.  The
+list may be viewed in a pager or printed to file.  If you have
+ten unspent outputs, your display will look something like this:
+
+		UNSPENT OUTPUTS (sort order: reverse amount)  Total BTC: 39.72
+		 Num  TX id  Vout    Address                            Amount (BTC)  Age(days)
+		 1)   04f97185... 2  1F93Znz8PI5Pnvv8ZAJsb74EzKpmRMLFbk  10           320
+		 2)   dd900544... 1  194Fceqx86jqIWumphUmfVyFMjAAbMLcSE   9.9287435   7
+		 3)   7ec81a8f... 0  1FhIkRabPSZhhUsA6qvukmfK4T4PZLbC4M   7.26        17
+		 4)   64094b55... 0  16JSUJdGMbxUBEQatAR5sGE89tbSIsLHqg   3.15        140
+		 5)   fd687c65... 1  1QKAtU66aUntCBx9m6TfEIf3gQuCNWCVDY   3.15        140
+		 6)   9a8f20e2... 1  1FMNDFz1yUywjJSprjvYY9t1yxkE8GGIwT   3.15        140
+		 7)   03a7c51a... 3  1svxnSdKVIcMs6qWYA7qLzA29orXbzXUm    1.6382466   54
+		 8)   9955f06c... 2  18nWPLQGUzI7X1Rcm4zmVV6Z3xhokdYx9G   1.2         27
+		 9)   8a4ab4f5... 0  13S9HNu7PQn1aJ4qILfhqRSakXwvSTnbwJ   0.23033     3
+		 10)  5bfe5621... 1  1FV1Lhs6Dnc9gMxjJTo6h4nTeIjJbQ1PgV   0.01        42
+
+		Sort options: [t]xid, [a]mount, a[d]dress, [A]ge, [r]everse, [M]mgen addr
+		View options: [g]roup, show [m]mgen addr
+		(Type 'q' to quit sorting, 'p' to print to file, 'v' to view in pager):
+
+Now let's actually create a transaction.  Let's say you've decided to
+gradually begin spending your 39.72 BTC balance into your shiny new
+MMGen wallet with seed ID 89ABCDEF.
 
-> With your newly-compiled bitcoind running, import the addresses from
-> '89ABCDEF[1-10].addrs' to track their balances:
+Before doing anything else, you should back up your MMGen wallet in
+several places and possibly on several media too: paper, flash memory
+or CD-ROM, for example.  Of course the wallet should have a
+passphrase.  Otherwise, anyone who gains physical control of one of
+your backups can easily steal your coins.
 
-            $ bitcoind importaddress 16bNmyYISiptuvJG3X7MPwiiS4HYvD7ksE
-            $ bitcoind importaddress 1AmkUxrfy5dMrfmeYwTxLxfIswUCcpeysc
-            $ ...
+Recall that there's no limit to the number of addresses you can
+generate with your seed.  You've wisely determined that having many
+addresses with relatively small balances is a Good Idea.
+You've decided to begin by breaking up your
+address with the largest balance, 10 BTC, into three roughly equal parts,
+sending it to the addresses labeled "Storage 1", "Storage 2" and
+"Storage 3" (89ABCDEF:1, 89ABCDEF:2 and 89ABCDEF:3).
 
-### Using the mnemonic and seed features:
+To refresh our memory, here are the three addresses in question:
 
-> Continuing our example above,
+		89ABCDEF {
+		  2    1AmkUxrfy5dMrfmeYwTxLxfIswUCcpeysc  Storage 1
+		  3    1HgYCsfqYzIg7LVVfDTp7gYJocJEiDAy6N  Storage 2
+		  4    14Tu3z1tiexXDonNsFIkvzqutE5E3pTK8s  Storage 3
+		}
 
-> Generate a mnemonic from the wallet:
+The following command will send 3.3 BTC to the first two addresses and
+the remainder of the transaction's inputs to the third, subtracting a
+default transaction fee of 0.001 BTC:
 
-            $ mmgen-walletchk -m '89ABCDEF-76543210[256,3].dat'
-            ...
-            Mnemonic data saved to file '89ABCDEF.words'
+		$ mmgen-txcreate 1AmkUxrfy5dMrfmeYwTxLxfIswUCcpeysc,3.3 1HgYCsfqYzIg7LVVfDTp7gYJocJEiDAy6N,3.3 14Tu3z1tiexXDonNsFIkvzqutE5E3pTK8s
 
-            $ 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
+The third address has the amount left out, making it a **change
+address**.
+MMGen will compute the change amount (3.399 BTC in this case) automatically.
 
-> 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
-> using the `-l` option of `mmgen-walletgen`.  Whether you consider
-> 128 bits of entropy enough is your call.  It's probably adequate for
-> the foreseeable future.
+Alternatively, and more conveniently, you can write the
+addresses in MMGen format:
 
-> Generate addresses 1-11 using the mnemonic instead of the wallet:
+		$ mmgen-txcreate -a addrs 89ABCDEF:2,3.3 89ABCDEF:3,3.3 89ABCDEF:4
 
-            $ mmgen-addrgen -m 89ABCDEF.words 1-11
-            ...
-            Address data saved to file '89ABCDEF[1-11].addrs'
+'addrs' is an MMGen address file containing the requested output
+addresses.  Any unneeded addresses in the file will be ignored.
 
-> Compare the first ten addresses with those earlier generated from the
-> wallet.  You'll see they're the same.
+Now hit ENTER, choose the
+transaction's input from the list (10 BTC, address
+1F9495H8EJLb54wirgZkVgI47SP7M2RQWv, txid 04f97185...,2),
+and confirm.
+If all goes well, the command will exit with a message like this:
 
-> Recover a lost wallet using the mnemonic:
+		Transaction data saved to file 'tx_1EDCBA[6.6].raw'
 
-            $ mmgen-walletgen -m 89ABCDEF.words
-            ...
-            Wallet saved to file '89ABCDEF-01234567[256,3].dat'
+Note that the transaction has a unique ID, and the non-change output
+amount, 6.6 BTC, is conveniently included in the filename.
 
-> Note that the regenerated wallet has a different Key ID but
-> of course the same Seed ID.
+#### Sign the transaction (offline computer):
 
-> Seeds are generated and input the same way as mnemonics.  Just change
-> the `-m` option to `-s` in the preceding commands.
+Now copy the raw transaction you've just created to a USB stick and
+transfer it to your offline computer for signing.  You need to find
+the key for your transaction's one input address,
+1F9495H8EJLb54wirgZkVgI47SP7M2RQWv.  If the key in question is in a
+bitcoin 'wallet.dat', there's an included command that will
+conveniently extract the key for you:
 
-> A seed file for a 256-bit seed looks like this:
+		$ mmgen-pywallet -k wallet.dat
+		...
+		wallet.dat secret keys saved to file wd_EDBC983A[102].keys
 
-            $ cat 8B7392ED.mmseed
-            f4c84b C5ZT wWpT Jsoi wRVw 2dm9 Aftd WLb8 FggQ eC8h Szjd da9L
+You've in fact extracted a list of all of the wallet's 102 keys here,
+but that's not a problem, as the unused keys will be ignored.
+Now go ahead and sign the transaction using this list of keys.
 
-> And for a 128-bit seed:
+		$ mmgen-txsign -k wd_EDBC983A[102].keys tx_1EDCBA[6.6].raw
+		...
+		Signed transaction saved to file tx_1EDCBA[6.6].sig
 
-            $ cat 8E0DFB78.mmseed
-            0fe02f XnyC NfPH piuW dQ2d nM47 VU
+Note that mmgen-pywallet's output is just a flat list of keys.
+So if you have several Bitcoin wallets with balances, you can just
+dump all their keys and concatenate them into a single file which you
+can use to sign all your future transactions involving wallet.dat
+inputs:
 
-> The latter is short enough to be memorized or written down.
+		$ mmgen-pywallet -k wallet1.dat
+		$ mmgen-pywallet -k wallet2.dat
+		$ mmgen-pywallet -k wallet3.dat
+		$ cat wd_*.keys > all_keys
 
-> The first word in the seed file is a checksum.
-> To check that you've written or memorized the seed correctly (should
-> you choose to do so), compare it with the first 6 characters of a
-> sha256 hash of the remainder of the line (with spaces removed).
+For transactions whose inputs are MMGen addresses, an MMGen
+seed source (i.e. wallet, mnemonic or seed file) is listed on the
+command line after the transaction file, and the required keys are
+automatically generated:
 
-#### Mnemonics and seeds — additional information:
-> Mnemonic and seed data may be entered at a prompt instead of from a
-> file.  Just omit the filename on the command line.
+		$ mmgen-txsign tx_9D2C3A[1.23].raw B73B58EA-125FB230[256,3].mmdat
+
+Transactions may contain a mixture of MMGen and non-MMGen inputs as
+well as inputs with more than one MMGen seed ID.  Just provide a seed
+source for each seed ID on the command line.
+
+Eventually, when you've placed all your BTC under MMGen control,
+you'll never have to deal with keys again.  MMGen just generates them
+on the fly as the need arises.
+
+#### Send the transaction (online computer):
+
+Now we're ready for the final step: broadcasting the transaction to
+the network.  Copy the \*.sig file to your online computer, start
+bitcoind, if it's not running, and execute the command
+
+		$ mmgen-txsend tx_1EDCBA[6.6].sig
+
+Like all mmgen commands, `mmgen-txsend` is interactive, so you'll be
+asked for confirmation before the transaction is actually sent.
+
+Once the transaction's confirmed by the network, your three new MMGen
+addresses will appear on the listing of `mmgen-txcreate -i`.  Type
+'m' at the menu and they'll be displayed in MMGen format.
+
+Congratulations!  You've performed your first MMGen transaction.
+
+### Additional Features
+
+#### Using the mnemonic and seed features:
+
+Continuing our example above, generate a mnemonic from the wallet:
+
+		$ mmgen-walletchk -m '89ABCDEF-76543210[256,3].mmdat'
+		...
+		Mnemonic data saved to file '89ABCDEF.mmwords'
+
+		$ 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
+18 words.  You may generate a wallet with a these seed lengths
+using the `'-l'` option to `mmgen-walletgen`.
+
+Though some consider 128 bits of entropy to provide adequate security
+for the foreseeable future, you should stick to the default 256-bit
+seed length if you're not planning to use the mnemonic feature.
 
-> Mnemonic and seed data may be printed to standard output instead of a
-> file using the `-S` option of `mmgen-walletchk`.
+NOTE: MMGen mnemonics are generated from the Electrum wordlist, only
+using ordinary base conversion instead of Electrum's more complicated
+algorithm.
 
-> Mnemonic and seed files may be output to a directory of your choice
-> using the `-d` option of `mmgen-walletchk`.
+Generate addresses 1-11 using the mnemonic instead of the wallet:
 
-> 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
-> screen.  Securely delete your mnemonic and seed files.  In Linux, you
-> can achieve additional security by writing the files to volatile
-> memory in '/dev/shm' instead of disk.
+		$ mmgen-addrgen 89ABCDEF.mmwords 1-11
+		...
+		Address data saved to file '89ABCDEF[1-11].addrs'
 
-### Vanitygen note:
-> When available, the 'keyconv' utility from the vanitygen package is
-> used to generate addresses as it's much faster than the Python ecdsa
-> library.
+Compare the first ten addresses with those earlier generated by the
+wallet.  You'll see they're the same.
+
+Recover a lost wallet using the mnemonic:
+
+		$ mmgen-walletgen 89ABCDEF.mmwords
+		...
+		Wallet saved to file '89ABCDEF-01234567[256,3].mmdat'
+
+Note that the regenerated wallet has a different Key ID but
+of course the same Seed ID.
+
+Seed files bear the extension '\*.mmseed' and are listed on the command
+line the same way as mnemonic files are.
+
+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
+
+And for a 128-bit seed:
+
+		$ cat 8E0DFB78.mmseed
+		0fe02f XnyC NfPH piuW dQ2d nM47 VU
+
+As you can see, the latter is short enough to practically be memorized.
+From the unix command line,
+you can test your memory using the seed's checksum ('0fe02f' in this example)
+as follows:
+
+		$ echo -n XnyCNfPHpiuWdQ2dnM47VU | sha256sum | cut -c 1-6
+		0fe02f
+
+#### Mnemonics and seeds — additional information:
+
+With the `'-m'` or `'-s'` option,
+MMGen commands that take mnemonic and seed data may receive the data
+from a prompt instead of a file.
+
+MMGen commands that produce mnemonic and seed data may be forced to
+print it to standard output instead of file with the `'-S'` option.
+This feature was intentionally made optional to safeguard against
+looking-over-the-shoulder, Van Eyck phreaking and other side-channel
+attacks.  MMGen commands never print private data to the screen
+unless explicitly asked to.
+
+The output file of any MMGen command may be written to a directory of
+your choice using the `'-d'` option.  For example, on a Linux system you
+could use `'-d /dev/shm'` to write key and seed data to volatile memory
+instead of disk.
+This also has obvious security benefits, ensuring that no sensitive data
+remains on disk after your computer's been powered down.
 
 ### Test suite:
-> To see what tests are available, run the scripts in the 'tests'
-> directory with no arguments.  Among others, you might find the
-> following tests to be of interest:
 
->> Compare 10 addresses generated by 'keyconv' with mmgen's
->> internally-generated ones:
->>> `tests/bitcoin.py keyconv_compare_randloop 10`
+To see what tests are available, run the scripts in the 'tests'
+directory with no arguments.  You might find the following tests to be
+of interest:
+
+> Compare 10 addresses generated by 'keyconv' with mmgen's
+> internally-generated ones:
+>> `tests/bitcoin.py keyconv_compare_randloop 10`
 
->> Convert a string to base 58 and back:
->>> `tests/bitcoin.py strtob58 'a string'`
+> Convert a string to base 58 and back:
+>> `tests/bitcoin.py strtob58 'a string'`
 
->> Convert a hex number to base 58 and back:
->>> `tests/bitcoin.py hextob58 deadbeef`
+> Convert a hex number to base 58 and back:
+>> `tests/bitcoin.py hextob58 deadbeef`
 
->> Perform 1000 hex -> base58 -> hex conversions, comparing results stringwise:
->>> `tests/bitcoin.py hextob58_pad_randloop 1000`
+> Perform 1000 hex -> base58 -> hex conversions, comparing results stringwise:
+>> `tests/bitcoin.py hextob58_pad_randloop 1000`
 
->> Generate a 12-word mnemonic from a random 128-bit seed:
->>> `tests/mnemonic.py random128`
+> Generate a 12-word mnemonic from a random 128-bit seed:
+>> `tests/mnemonic.py random128`
 
->> or an 18-word mnemonic from a random 192-bit seed:
->>> `tests/mnemonic.py random192`
+> or an 18-word mnemonic from a random 192-bit seed:
+>> `tests/mnemonic.py random192`
 
->> or a 24-word mnemonic from a random 256-bit seed:
->>> `tests/mnemonic.py random256`
+> or a 24-word mnemonic from a random 256-bit seed:
+>> `tests/mnemonic.py random256`

+ 78 - 0
doc/MMGenLinuxInstall.md

@@ -0,0 +1,78 @@
+MMGen = Multi-Mode GENerator
+----------------------------
+##### a Bitcoin cold storage solution for the command line
+
+### Download/Install on Debian/Ubuntu Linux:
+
+**Perform the following steps on both your online and offline
+computers:**
+
+Install the pip Python installer:
+
+		$ sudo apt-get install python-pip
+
+Install required Python modules:
+
+		$ 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 can begin trying out MMgen, creating a test wallet
+and generating keys as described in **Using MMGen** below.  To be
+able to track addresses and create and sign transactions, however,
+you'll need to have bitcoin daemons installed on your online and
+offline machines.
+
+**Bitcoind installation**
+
+The bitcoin daemon on the **offline machine** is used solely for
+signing transactions and is therefore 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 and start it up:
+
+		$ bitcoind -daemon -maxconnections=0
+
+Note that in the absence of a blockchain the daemon starts very quickly
+and uses practically no CPU once running.
+Thus a low-powered computer such as a netbook can serve quite nicely
+as an offline signing machine.
+
+On the **online machine**, the bitcoin daemon is used for tracking
+addresses and requires a full, updated blockchain.  For
+this a more powerful computer is needed, especially when importing
+addresses.  Plenty of free disk space is also required to accomodate
+the rapidly-growing blockchain (20GB in size at the time of writing).
+
+The standard bitcoin daemon at present lacks the watch-only address
+support we need, so we must get and compile the special watch-only
+enabled version created by Bitcoin core developer Pieter Wuille, aka
+sipa.  If you have the necessary dependencies installed, this process
+is surprisingly painless.
+
+		$ curl -O https://codeload.github.com/sipa/bitcoin/zip/watchonly
+		$ unzip watchonly
+		$ cd bitcoin-watchonly
+		$ ./autogen.sh
+		$ ./configure
+		$ make -j4
+		(You may have to install the libboost-all-dev package for the build to succeed)
+
+With your online machine connected to the Internet, start the freshly
+compiled daemon and let it synchronize the blockchain, **taking care
+to move any existing wallet.dat out of harm's way** beforehand.
+The daemon will create a new wallet upon startup, which you'll use
+as your **tracking wallet**.
+
+Congratulations!  Your MMGen installation is now complete.

+ 22 - 0
doc/MMGenWindowsInstall.md

@@ -0,0 +1,22 @@
+MMGen = Multi-Mode GENerator
+----------------------------
+##### a Bitcoin cold storage solution for the command line
+
+### Download/Install on Microsoft Windows
+
+  Compiling the watch-only bitcoind and the python scrypt module are the
+  main challenges for Windows installation.
+  To compile bitcoind, MinGW must first be installed
+  and the Berkeley DB and Boost libraries built from source.
+  The Python interpeter is required for building the python scrypt
+  module and installing the other python modules.
+
+  The build process involves some editing of source code and configuration
+  files, so it's useful to have a good text editor installed on the system.
+
+  MMGen's remaining components either require no compilation (the ecdsa and
+  bitcoin-python modules and MMGen itself) or can be found online in
+  precompiled form (the Python interpeter, pycrypto Python module and
+  vanitygen)
+
+##### (to be continued...)

+ 7 - 7
mmgen-addrgen

@@ -25,7 +25,7 @@ mmgen-addrgen: Generate a list or range of addresses from a mmgen
 import sys
 
 from mmgen.Opts   import *
-from mmgen.config import *
+import mmgen.config as g
 from mmgen.license import *
 from mmgen.utils  import *
 from mmgen.addr   import *
@@ -44,7 +44,7 @@ else: extra_help_data = ("","","")
 help_data = {
 	'prog_name': sys.argv[0].split("/")[-1],
 	'desc': """Generate a list or range of {} from a {} wallet,
-                  mnemonic, seed or password""".format(gen_what,proj_name),
+                  mnemonic, seed or password""".format(gen_what,g.proj_name),
 	'usage':"[opts] [infile] <address list>",
 	'options': """
 -h, --help               Print this help message{}
@@ -97,13 +97,13 @@ addresses, the same 'l' and 'p' parameters to '--from-brain' must be used
 in all future invocations with that passphrase
 """.format(
 		extra_help_data[0],
-		", ".join([str(i) for i in seed_lens]),
-		seed_len,
-		hash_preset,
+		", ".join([str(i) for i in g.seed_lens]),
+		g.seed_len,
+		g.hash_preset,
 		extra_help_data[1],
 		extra_help_data[2],
 		W=gen_what,
-		S=seed_ext
+		S=g.seed_ext
 	)
 }
 
@@ -126,7 +126,7 @@ opts['gen_what'] = gen_what
 
 check_opts(opts,long_opts)
 
-if debug: show_opts_and_cmd_args(opts,cmd_args)
+if g.debug: show_opts_and_cmd_args(opts,cmd_args)
 
 if len(cmd_args) == 1 and (
 			'from_mnemonic' in opts or

+ 25 - 9
mmgen-addrimport

@@ -57,7 +57,8 @@ else:
 	seed_id,addr_data = "",[]
 
 if 'addrlist' in opts:
-	l = get_lines_from_file(opts['addrlist'],"non-mmgen addresses")
+	l = get_lines_from_file(
+			opts['addrlist'],"non-mmgen addresses",remove_comments=True)
 	addr_data += [(None,i) for i in l]
 
 msg_r("Validating addresses...")
@@ -68,8 +69,8 @@ for i in addr_data:
 
 msg("OK")
 
-import mmgen.config
-mmgen.config.http_timeout = 3600
+import mmgen.config as g
+g.http_timeout = 3600
 
 c = connect_to_bitcoind()
 
@@ -82,17 +83,28 @@ Importing addresses can take a long time (>30 min.) on a low-powered computer.
 import threading
 import time
 
+err_flag = False
+
+def import_address(addr,label):
+	try:
+		c.importaddress(addr,label)
+	except:
+		global err_flag
+		err_flag = True
+
+
 w1 = len(str(len(addr_data))) * 2 + 2
 w2 = len(str(max([i[0] for i in addr_data if i[0]]))) + 12
-msg_fmt = "\rImporting %-" + str(w1) + "s %-34s %-" + str(w2) + "s %s"
+msg_fmt = "\r%s %-" + str(w1) + "s %-34s %-" + str(w2) + "s"
 
+msg("Importing addresses")
 for n,i in enumerate(addr_data):
 	if i[0]:
 		comment = " " + i[2] if len(i) == 3 else ""
 		label = "%s:%s%s" % (seed_id,i[0],comment)
 	else: label = "non-mmgen"
 
-	t = threading.Thread(target=c.importaddress, args = (i[1],label))
+	t = threading.Thread(target=import_address, args = (i[1],label))
 	t.daemon = True
 	t.start()
 
@@ -102,11 +114,15 @@ for n,i in enumerate(addr_data):
 		if t.is_alive():
 			elapsed = int(time.time() - start)
 			msg_r(msg_fmt % (
+				secs_to_hms(elapsed),
 				("%s/%s:" % (n+1,len(addr_data))),
-				i[1], "(" + label + ")", secs_to_hms(elapsed))
+				i[1], "(" + label + ")"
+				)
 			)
+			time.sleep(1)
 		else:
-			msg("")
+			if err_flag:
+				msg("\nImport failed")
+				sys.exit(2)
+			msg("\nOK")
 			break
-
-		time.sleep(1)

+ 4 - 4
mmgen-passchg

@@ -23,11 +23,11 @@ mmgen-passchg: Change a mmgen deterministic wallet's passphrase, label or
 import sys
 from mmgen.Opts import *
 from mmgen.utils import *
-from mmgen.config import *
+import mmgen.config as g
 
 help_data = {
 	'desc':  """Change the passphrase, hash preset or label of a {}
-                  deterministic wallet""".format(proj_name),
+                  deterministic wallet""".format(g.proj_name),
 	'usage':   "[opts] [filename]",
 	'options': """
 -h, --help                  Print this help message
@@ -43,7 +43,7 @@ help_data = {
 
 NOTE: The key ID will change if either the passphrase or hash preset
       are changed
-""".format(hash_preset)
+""".format(g.hash_preset)
 }
 
 short_opts = "hd:HkL:p:P:v"
@@ -112,7 +112,7 @@ if 'preset' in changed or 'passwd' in changed: # Update key ID, salt
 	from hashlib import sha256
 	from Crypto import Random
 
-	salt = sha256(salt + Random.new().read(128)).digest()[:salt_len]
+	salt = sha256(salt + Random.new().read(128)).digest()[:g.salt_len]
 	key = make_key(passwd, salt, opts['hash_preset'])
 	new_key_id = make_chksum_8(key)
 	msg("Key ID changed: %s -> %s" % (key_id,new_key_id))

+ 68 - 22
mmgen-txcreate

@@ -24,7 +24,7 @@ import sys
 
 from mmgen.Opts import *
 from mmgen.license import *
-from mmgen.config import *
+import mmgen.config as g
 from mmgen.tx import *
 from mmgen.utils import msg, msg_r, user_confirm
 from decimal import Decimal
@@ -33,8 +33,8 @@ prog_name = sys.argv[0].split("/")[-1]
 
 help_data = {
 	'prog_name': prog_name,
-	'desc':    "Create a BTC transaction, sending to specified addresses",
-	'usage':   "[opts] <address:amount>[,...] <transaction fee> <change address>",
+	'desc':    "Create a BTC transaction with outputs to specified addresses",
+	'usage':   "[opts]  <addr,amt> ... [change addr] [tx fee] [addr file] ...",
 	'options': """
 -h, --help                Print this help message
 -d, --outdir           d  Specify an alternate directory 'd' for output
@@ -43,42 +43,85 @@ help_data = {
 -q, --quiet               Suppress warnings; overwrite files without
                           prompting
 
-Outputs to spend are chosen by the user via a menu.
+-f, --tx-fee           f  Transaction fee (default: %s BTC)
 
-Ages of transactions are approximate based on an average block discovery
+Transaction inputs are chosen from a list of the user's unpent outputs
+via an interactive menu.
+
+Ages of transactions are approximate based on an average block creation
 interval of %s minutes.
-""" % mins_per_block
+
+Addresses on the command line can be Bitcoin addresses or MMGen
+addresses in the form <seed ID>:<number>
+""" % (Decimal(g.tx_fee),g.mins_per_block)
 }
 
-short_opts = "hd:eiq"
-long_opts  = "help","outdir=","echo_passphrase","info","quiet"
+short_opts = "ha:d:eiqf:"
+long_opts  = "help","addr_file","outdir=","echo_passphrase","info","quiet",\
+				"tx_fee="
 
 opts,cmd_args = process_opts(sys.argv,help_data,short_opts,long_opts)
 
 check_opts(opts,long_opts)
 
-if debug: show_opts_and_cmd_args(opts,cmd_args)
-
-if len(cmd_args) == 3:
-	rcpt_arg,tx_fee,change_addr = cmd_args
-	check_address(change_addr)
-elif len(cmd_args) == 2:
-	rcpt_arg,tx_fee = cmd_args
-	change_addr = ""
-elif len(cmd_args) == 0 and 'info' in opts:
-	pass
-else: usage(help_data)
+if g.debug: show_opts_and_cmd_args(opts,cmd_args)
 
 if not 'info' in opts:
-	tx_out = make_tx_out(rcpt_arg)
+
+	outputs,addr_files,change_addr = [],[],""
+
+	for a in cmd_args:
+		if a.split(".")[-1] == g.addrfile_ext:
+			check_infile(a)
+			addr_files.append(a)
+		elif "," in a:
+			outputs.append(a)
+		else:
+			if change_addr:
+				msg("More than one change address specified: %s, %s" %
+						(change_addr, a))
+				sys.exit(2)
+			change_addr = a
+
+	if not outputs:
+		msg("At least one output must be specified on the command line")
+		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
+
+	try:
+		tx_fee = Decimal(tx_fee)
+	except:
+		msg("Invalid transaction fee format: %s" % tx_fee)
+		sys.exit(2)
+
+	if tx_fee > g.max_tx_fee:
+		msg("Transaction fee too large: %s > %s" % (tx_fee,g.max_tx_fee))
+		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)
+
+	tx_fee = check_btc_amt(tx_fee)
+
+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 'quiet' in opts and not 'info' in opts:
+	do_license_msg(immed=True)
 
 # Begin test
 # import mmgen.rpc
@@ -140,6 +183,9 @@ if change > 0 and not change_addr:
 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:
+	print "tx_in:", repr(tx_in)
+	print "tx_out:", repr(tx_out)
 tx_hex = c.createrawtransaction(tx_in,tx_out)
 
 msg("Transaction successfully created")

+ 1 - 1
mmgen-txsend

@@ -23,7 +23,7 @@ import sys
 
 from mmgen.Opts import *
 from mmgen.license import *
-from mmgen.config import *
+import mmgen.config as g
 from mmgen.tx import *
 from mmgen.utils import msg,check_infile,get_lines_from_file,confirm_or_exit
 

+ 2 - 2
mmgen-txsign

@@ -23,7 +23,7 @@ import sys
 
 from mmgen.Opts import *
 from mmgen.license import *
-from mmgen.config import *
+import mmgen.config as g
 from mmgen.tx import *
 from mmgen.utils import msg
 
@@ -69,7 +69,7 @@ Seed data supplied in files must have the following extensions:
    seed:        '.{}'
    mnemonic:    '.{}'
    brainwallet: '.{}'
-""".format(seed_ext,wallet_ext,seed_ext,mn_ext,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"

+ 3 - 3
mmgen-walletchk

@@ -29,7 +29,7 @@ help_data = {
 	'prog_name': sys.argv[0].split("/")[-1],
 	'desc':  """Check integrity of a %s deterministic wallet, display
                     its information and export seed and mnemonic data."""\
-					% proj_name,
+					% g.proj_name,
 	'usage':   "[opts] [filename]",
 	'options': """
 -h, --help             Print this help message
@@ -68,8 +68,8 @@ if 'export_mnemonic' in opts:
 	wl = get_default_wordlist()
 
 	from mmgen.mnemonic import get_mnemonic_from_seed
-	p = True if debug else False
-	mn = get_mnemonic_from_seed(seed, wl, default_wl, print_info=p)
+	p = True if g.debug else False
+	mn = get_mnemonic_from_seed(seed, wl, g.default_wl, print_info=p)
 
 	write_mnemonic(mn, seed, opts)
 

+ 12 - 12
mmgen-walletgen

@@ -24,14 +24,14 @@ from hashlib import sha256
 
 from mmgen.Opts import *
 from mmgen.license import *
-from mmgen.config import *
+import mmgen.config as g
 from mmgen.walletgen import *
 from mmgen.utils import *
 prog_name = sys.argv[0].split("/")[-1]
 
 help_data = {
 	'prog_name': prog_name,
-	'desc':    "Generate a {} deterministic wallet".format(proj_name),
+	'desc':    "Generate a {} deterministic wallet".format(g.proj_name),
 	'usage':   "[opts] [infile]",
 	'options': """
 -h, --help                 Print this help message
@@ -83,12 +83,12 @@ For a brainwallet passphrase to always generate the same keys and
 addresses, the same 'l' and 'p' parameters to '--from-brain' must be used
 in all future invocations with that passphrase.
 """.format(
-		",".join([str(i) for i in seed_lens]),
-		seed_len,
-		hash_preset,
-		usr_randlen,
+		",".join([str(i) for i in g.seed_lens]),
+		g.seed_len,
+		g.hash_preset,
+		g.usr_randlen,
 		prog_name,
-		S=seed_ext,
+		S=g.seed_ext,
 	)
 }
 
@@ -103,7 +103,7 @@ if 'show_hash_presets' in opts: show_hash_presets()
 
 check_opts(opts,long_opts)
 
-if debug: show_opts_and_cmd_args(opts,cmd_args)
+if g.debug: show_opts_and_cmd_args(opts,cmd_args)
 
 if len(cmd_args) == 1:
 	infile = cmd_args[0]
@@ -131,12 +131,12 @@ except:
 
 msg("OK")
 
-if 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)
 msg("")
 
-if debug: display_user_random_data(usr_keys,key_timings)
+if g.debug: display_user_random_data(usr_keys,key_timings)
 
 usr_rand_data = sha256(usr_keys).digest() + \
 				sha256("".join(key_timings)).digest()
@@ -150,7 +150,7 @@ else:
 	seed = sha256(seed).digest()[:opts['seed_len']/8]
 
 salt = os_rand_data[1] + usr_rand_data
-salt = sha256(salt).digest()[:salt_len]
+salt = sha256(salt).digest()[:g.salt_len]
 
 if not 'quiet' in opts:
 	msg("""
@@ -160,7 +160,7 @@ no strength checking of passphrases is performed.  For an empty passphrase,
 just hit ENTER twice.
 """ % opts['hash_preset'])
 
-passwd = get_new_passphrase("{} wallet passphrase".format(proj_name), opts)
+passwd = get_new_passphrase("{} wallet passphrase".format(g.proj_name), opts)
 
 key = make_key(passwd, salt, opts['hash_preset'])
 

+ 26 - 25
mmgen/Opts.py

@@ -17,7 +17,7 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import sys, getopt
-from mmgen.config import *
+import mmgen.config as g
 from mmgen.utils import msg
 
 def usage(hd):
@@ -37,7 +37,7 @@ def process_opts(argv,help_data,short_opts,long_opts):
 
 	progname = argv[0].split("/")[-1]
 
-	if debug:
+	if g.debug:
 		print "Short opts: %s" % repr(short_opts)
 		print "Long opts:  %s" % repr(long_opts)
 
@@ -65,7 +65,7 @@ def process_opts(argv,help_data,short_opts,long_opts):
 			opts[long_opts[short_opts_l.index(opt[1:]+":")][:-1].replace("-","_")] = arg
 		else: assert False, "Invalid option"
 
-	if debug: print "User-selected options: %s" % repr(opts)
+	if g.debug: print "User-selected options: %s" % repr(opts)
 
 	return opts,args
 
@@ -73,7 +73,7 @@ def process_opts(argv,help_data,short_opts,long_opts):
 def check_opts(opts,long_opts):
 
 	# These must be set to the default values in mmgen.config:
-	for i in cl_override_vars:
+	for i in g.cl_override_vars:
 		if i+"=" in long_opts:
 			set_if_unset_and_typeconvert(opts,i)
 
@@ -110,14 +110,13 @@ def check_opts(opts,long_opts):
 			label = val.strip()
 			opts[opt] = label
 
-			if len(label) > 32:
-				msg("Label must be 32 characters or less")
+			if len(label) > g.max_wallet_label_len:
+				msg("Label must be %s characters or less" %
+					g.max_wallet_label_len)
 				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:
+				if ch not in g.wallet_label_symbols:
 					msg("'%s': illegal character in label" % ch)
 					sys.exit(1)
 		elif opt == 'from_brain':
@@ -133,32 +132,32 @@ def check_opts(opts,long_opts):
 				msg("'%s': invalid 'l' %s (not an integer)" % (l,what))
 				sys.exit(1)
 
-			if int(l) not in seed_lens:
+			if int(l) not in g.seed_lens:
 				msg("'%s': invalid 'l' %s.  Options: %s" %
-						(l, what, ", ".join([str(i) for i in seed_lens])))
+						(l, what, ", ".join([str(i) for i in g.seed_lens])))
 				sys.exit(1)
 
-			if p not in hash_presets:
-				hps = ", ".join([i for i in sorted(hash_presets.keys())])
+			if p not in g.hash_presets:
+				hps = ", ".join([i for i in sorted(g.hash_presets.keys())])
 				msg("'%s': invalid 'p' %s.  Options: %s" % (p, what, hps))
 				sys.exit(1)
 		elif opt == 'seed_len':
-			if val not in seed_lens:
+			if val not in g.seed_lens:
 				msg("'%s': invalid %s.  Options: %s"
-				% (val,what,", ".join([str(i) for i in seed_lens])))
+				% (val,what,", ".join([str(i) for i in g.seed_lens])))
 				sys.exit(2)
 		elif opt == 'hash_preset':
-			if val not in hash_presets:
+			if val not in g.hash_presets:
 				msg("'%s': invalid %s.  Options: %s"
-				% (val,what,", ".join(sorted(hash_presets.keys()))))
+				% (val,what,", ".join(sorted(g.hash_presets.keys()))))
 				sys.exit(2)
 		elif opt == 'usr_randlen':
-			if val > max_randlen or val < min_randlen:
+			if val > g.max_randlen or val < g.min_randlen:
 				msg("'%s': invalid %s (must be >= %s and <= %s)"
-				% (val,what,min_randlen,max_randlen))
+				% (val,what,g.min_randlen,g.max_randlen))
 				sys.exit(2)
 		else:
-			if debug: print "check_opts(): No test for opt '%s'" % opt
+			if g.debug: print "check_opts(): No test for opt '%s'" % opt
 
 
 def show_opts_and_cmd_args(opts,cmd_args):
@@ -168,14 +167,16 @@ def show_opts_and_cmd_args(opts,cmd_args):
 
 def set_if_unset_and_typeconvert(opts,opt):
 
-	if opt in cl_override_vars:
+	if opt in g.cl_override_vars:
 		if opt not in opts:
 			# Set to similarly named default value in mmgen.config
-			opts[opt] = eval(opt)
+			opts[opt] = eval("g."+opt)
 		else:
-			vtype = type(eval(opt))
-			if   vtype == int: f,t = int,"an integer"
-			elif vtype == str: f,t = str,"a string"
+			vtype = type(eval("g."+opt))
+			if g.debug: print "Opt: %s, Type: %s" % (opt,vtype)
+			if   vtype == int:   f,t = int,"an integer"
+			elif vtype == str:   f,t = str,"a string"
+			elif vtype == float: f,t = float,"a float"
 
 			try:
 				opts[opt] = f(opts[opt])

+ 7 - 10
mmgen/addr.py

@@ -24,25 +24,23 @@ from hashlib import sha256, sha512
 from binascii import hexlify, unhexlify
 
 from mmgen.bitcoin import numtowif
-from mmgen.config import *
+import mmgen.config as g
 
 def test_for_keyconv():
 	"""
 	Test for the presence of 'keyconv' utility on system
 	"""
 
-	keyconv_exec = "keyconv"
-
 	from subprocess import Popen, PIPE
 	try:
-		p = Popen([keyconv_exec, '-h'], stdout=PIPE, stderr=PIPE)
+		p = Popen([g.keyconv_exec, '-h'], stdout=PIPE, stderr=PIPE)
 	except:
 		sys.stderr.write("""
 Executable '%s' unavailable. Falling back on (slow) internal ECDSA library.
 Please install '%s' from the %s package on your system for much
 faster address generation.
 
-""" % (keyconv_exec, keyconv_exec, "vanitygen"))
+""" % (g.keyconv_exec, g.keyconv_exec, "vanitygen"))
 		return False
 	else:
 		return True
@@ -157,9 +155,8 @@ def format_addr_data(addrlist, seed_chksum, opts):
 # address, and it will be appended to the bitcoind wallet label upon import.
 # The label may contain ASCII letters, numerals, and the symbols
 # '{}' and '{}'.
-""".format(proj_name.capitalize(),max_addr_label_len,
-		"', '".join(addr_label_symbols[0:-1]),
-		addr_label_symbols[-1]).strip()
+""".format(g.proj_name.capitalize(),g.max_addr_label_len,
+		"', '".join(g.addr_label_punc[0:-1]), g.addr_label_punc[-1]).strip()
 	data = []
 	if not 'stdout' in opts: data.append(header + "\n")
 	data.append("%s {" % seed_chksum.upper())
@@ -208,8 +205,8 @@ def fmt_addr_list(addr_list):
 
 def write_addr_data_to_file(seed, data, addr_list, opts):
 
-	if 'print_addresses_only' in opts: ext = "addrs"
-	elif 'no_addresses' in opts:       ext = "keys"
+	if 'print_addresses_only' in opts: ext = g.addrfile_ext
+	elif 'no_addresses' in opts:       ext = g.keyfile_ext
 	else:                              ext = "akeys"
 
 	if 'b16' in opts: ext = ext.replace("keys","xkeys")

+ 20 - 2
mmgen/config.py

@@ -18,6 +18,11 @@
 """
 config.py:  Constants and configuration options for the mmgen suite
 """
+
+from decimal import Decimal
+tx_fee        = Decimal("0.001")
+max_tx_fee    = Decimal("0.1")
+
 proj_name     = "mmgen"
 
 wallet_ext    = "mmdat"
@@ -25,7 +30,10 @@ seed_ext      = "mmseed"
 mn_ext        = "mmwords"
 brain_ext     = "mmbrain"
 
-seed_exts = wallet_ext, seed_ext, mn_ext, brain_ext
+seedfile_exts = wallet_ext, seed_ext, mn_ext, brain_ext
+
+addrfile_ext = "addrs"
+keyfile_ext  = "keys"
 
 default_wl    = "electrum"
 #default_wl    = "tirosh"
@@ -39,6 +47,8 @@ mnemonic_lens = [i / 32 * 3 for i in seed_lens]
 
 http_timeout = 30
 
+keyconv_exec = "keyconv"
+
 from os import getenv
 debug = True if getenv("MMGEN_DEBUG") else False
 
@@ -60,5 +70,13 @@ hash_presets = {
 	'5': [16, 8, 16],
 	'6': [17, 8, 20],
 }
-addr_label_symbols = ".","_",",","-"," "
+
+from string import ascii_letters, digits
+
+addr_label_punc = ".","_",",","-"," "
+addr_label_symbols = tuple(ascii_letters + digits) + addr_label_punc
 max_addr_label_len = 16
+
+wallet_label_punc = ".", "_", " "
+wallet_label_symbols = tuple(ascii_letters + digits) + wallet_label_punc
+max_wallet_label_len = 32

+ 0 - 1
mmgen/license.py

@@ -20,7 +20,6 @@ license.py:  Show the license
 """
 
 import sys
-from mmgen.config import proj_name
 from mmgen.utils import msg, msg_r, get_char
 
 gpl = {

+ 3 - 3
mmgen/mnemonic.py

@@ -32,7 +32,7 @@ def mn_len(hexnum): return len(hexnum) * 3 / 8
 import sys
 
 from mmgen.utils import msg,make_chksum_8
-from mmgen.config import *
+import mmgen.config as g
 
 # These universal base-conversion routines work for any base
 
@@ -47,9 +47,9 @@ def hextobaseN(base,hexnum,wordlist,mn_len):
 
 def get_seed_from_mnemonic(mn,wl):
 
-	if len(mn) not in mnemonic_lens:
+	if len(mn) not in g.mnemonic_lens:
 		msg("Bad mnemonic (%i words).  Allowed numbers of words: %s" %
-				(len(mn),", ".join([str(i) for i in mnemonic_lens])))
+				(len(mn),", ".join([str(i) for i in g.mnemonic_lens])))
 		return False
 
 	for n,w in enumerate(mn,1):

+ 5 - 5
mmgen/rpc/connection.py

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

+ 2 - 2
mmgen/rpc/proxy.py

@@ -58,7 +58,7 @@ class JSONRPCException(Exception):
 		self.error = rpcError
 
 
-import mmgen.config
+import mmgen.config as g
 
 class AuthServiceProxy(object):
 	def __init__(self, serviceURL, serviceName = None):
@@ -75,7 +75,7 @@ class AuthServiceProxy(object):
 		authpair = authpair.encode('utf8')
 		self.__authhdr = "Basic ".encode('utf8') + base64.b64encode(authpair)
 
-		http_timeout = mmgen.config.http_timeout
+		http_timeout = g.http_timeout
 
 		if self.__url.scheme == 'https':
 			self.__conn = httplib.HTTPSConnection(self.__url.hostname, port,

+ 83 - 38
mmgen/tx.py

@@ -23,7 +23,7 @@ from binascii import unhexlify
 from mmgen.utils import *
 import sys, os
 from decimal import Decimal
-from mmgen.config import *
+import mmgen.config as g
 
 txmsg = {
 'not_enough_btc': "Not enough BTC in the inputs for this transaction (%s BTC)",
@@ -37,21 +37,22 @@ specified recipient address.
 NOTE: This transaction uses a mixture of both mmgen and non-mmgen inputs,
 which makes the signing process more complicated.  When signing the
 transaction, keys for the non-mmgen inputs must be supplied in a separate
-file using the '-k' option of mmgen-txsign.
-
-Alternatively, you may import the mmgen keys into the wallet.dat of your
-offline bitcoind, first generating the required keys with mmgen-keygen and
-then running mmgen-txsign with the '-f' option to force the use of
-wallet.dat as the key source.
+file using the '-k' option to mmgen-txsign.
 
 Selected mmgen inputs: %s"""
 }
 
+# Deleted text:
+# Alternatively, you may import the mmgen keys into the wallet.dat of your
+# offline bitcoind, first generating the required keys with mmgen-keygen and
+# then running mmgen-txsign with the '-f' option to force the use of
+# wallet.dat as the key source.
+
 
 def connect_to_bitcoind():
 
 	host,port,user,passwd = "localhost",8332,"rpcuser","rpcpassword"
-	cfg = get_cfg_options((user,passwd))
+	cfg = get_bitcoind_cfg_options((user,passwd))
 
 	import mmgen.rpc.connection
 	f = mmgen.rpc.connection.BitcoinConnection
@@ -72,7 +73,7 @@ def trim_exponent(n):
 	return d.quantize(Decimal(1)) if d == d.to_integral() else d.normalize()
 
 
-def	check_address(rcpt_address):
+def check_address(rcpt_address):
 	from mmgen.bitcoin import verify_addr
 	if not verify_addr(rcpt_address):
 		sys.exit(3)
@@ -87,6 +88,9 @@ def check_btc_amt(send_amt):
 		msg("%s: Invalid amount" % send_amt)
 		sys.exit(3)
 
+	if g.debug:
+		print "Decimal(amt): %s\nAs tuple: %s" % (send_amt,repr(retval.as_tuple()))
+
 	if retval.as_tuple()[-1] < -8:
 		msg("%s: Too many decimal places in amount" % send_amt)
 		sys.exit(3)
@@ -94,16 +98,18 @@ def check_btc_amt(send_amt):
 	return trim_exponent(retval)
 
 
-def get_cfg_options(cfg_keys):
+def get_bitcoind_cfg_options(cfg_keys):
 
 	if "HOME" in os.environ:
-		cfg_file = "%s/%s" % (os.environ["HOME"], ".bitcoin/bitcoin.conf")
+		data_dir = ".bitcoin"
+		cfg_file = "%s/%s/%s" % (os.environ["HOME"], data_dir, "bitcoin.conf")
 	elif "HOMEPATH" in os.environ:
 	# Windows:
-		cfg_file = "%s%s" % (os.environ["HOMEPATH"],
-						r"\Application Data\Bitcoin\bitcoin.conf")
+		data_dir = r"Application Data\Bitcoin"
+		cfg_file = "%s\%s\%s" % (os.environ["HOMEPATH"],data_dir,"bitcoin.conf")
 	else:
-		msg("Unable to find bitcoin configuration file")
+		msg("Neither $HOME nor %HOMEPATH% is set")
+		msg("Don't know where to look for 'bitcoin.conf'")
 		sys.exit(3)
 
 	try:
@@ -202,7 +208,7 @@ def sort_and_view(unspent):
 			amt = str(trim_exponent(i.amount))
 			lfill = 3 - len(amt.split(".")[0]) if "." in amt else 3 - len(amt)
 			i.amt = " "*lfill + amt
-			i.days = int(i.confirmations * mins_per_block / (60*24))
+			i.days = int(i.confirmations * g.mins_per_block / (60*24))
 
 		for n,i in enumerate(out):
 			if i.skip == "d":
@@ -321,7 +327,7 @@ def view_tx_data(c,inputs_data,tx_hex,metadata=[],pager=False):
 	for n,i in enumerate(td['vin']):
 		for j in inputs_data:
 			if j['txid'] == i['txid'] and j['vout'] == i['vout']:
-				days = int(j['confirmations'] * mins_per_block / (60*24))
+				days = int(j['confirmations'] * g.mins_per_block / (60*24))
 				total_in += j['amount']
 				out += (" " + """
 %-2s tx,vout: %s,%s
@@ -391,7 +397,7 @@ def parse_tx_data(tx_data,infile):
 def select_outputs(unspent,prompt):
 
 	while True:
-		reply = my_raw_input(prompt).strip()
+		reply = my_raw_input(prompt,allowed_chars="0123456789 -").strip()
 
 		if not reply: continue
 
@@ -407,46 +413,80 @@ def select_outputs(unspent,prompt):
 		return selected
 
 
+def mmgen_addr_to_btc_addr(m,addr_data):
+
+	ID,num = m.split(":")
+	from binascii import unhexlify
+	try: unhexlify(ID)
+	except: pass
+	else:
+		try: num = int(num)
+		except: pass
+		else:
+			if not addr_data:
+				msg("Address data must be supplied for MMgen address '%s'" % m)
+				sys.exit(2)
+			for i in addr_data:
+				if ID == i[0]:
+					for j in i[1]:
+						if j[0] == num:
+							return j[1]
+			msg("MMgen address '%s' not found in supplied address data" % m)
+			sys.exit(2)
 
-def make_tx_out(rcpt_arg):
+	msg("Invalid format: %s" % m)
+	sys.exit(3)
 
-	import decimal
-	try:
-		tx_out = dict([(i.split(":")[0],i.split(":")[1])
-							for i in rcpt_arg.split(",")])
-	except:
-		msg("Invalid format: %s" % rcpt_arg)
-		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_out.keys():
-			tx_out[i] = trim_exponent(Decimal(tx_out[i]))
+		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_out[i]))
+		msg("Decimal conversion error in suboption '%s:%s'" % (i,tx[i]))
 		sys.exit(3)
 
-	return tx_out
+	return tx
+
 
 def check_addr_comment(label):
 
-	if len(label) > max_addr_label_len:
+	if len(label) > g.max_addr_label_len:
 		msg("'%s': overlong label (length must be <=%s)" %
-				(label,max_addr_label_len))
+				(label,g.max_addr_label_len))
 		sys.exit(3)
 
-	from string import ascii_letters, digits
-	chrs = tuple(ascii_letters + digits) + addr_label_symbols
 	for ch in list(label):
-		if ch not in chrs:
+		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(addr_label_symbols))
+					"', '".join(g.addr_label_punc))
 			sys.exit(3)
 
 
 def parse_addrs_file(f):
-	lines = get_lines_from_file(f,"address data")
-	lines = remove_blanks_comments(lines)
+	lines = get_lines_from_file(f,"address data",remove_comments=True)
 
 	try:
 		seed_id,obrace = lines[0].split()
@@ -496,7 +536,7 @@ def sign_transaction(c,tx_hex,sig_data,keys=None):
 
 	if keys:
 		msg("%s keys total" % len(keys))
-		if debug: print "Keys:\n  %s" % "\n  ".join(keys)
+		if g.debug: print "Keys:\n  %s" % "\n  ".join(keys)
 
 	from mmgen.rpc import exceptions
 
@@ -591,3 +631,8 @@ 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

+ 112 - 55
mmgen/utils.py

@@ -20,7 +20,7 @@ utils.py:  Shared routines for the mmgen suite
 """
 
 import sys
-from mmgen.config import *
+import mmgen.config as g
 from binascii import hexlify,unhexlify
 from mmgen.bitcoin import b58decode_pad
 
@@ -28,6 +28,25 @@ def msg(s):   sys.stderr.write(s + "\n")
 def msg_r(s): sys.stderr.write(s)
 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=""):
 
@@ -56,6 +75,24 @@ def get_keypress_unix(prompt="",immed_chars=""):
 		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)
@@ -88,10 +125,12 @@ 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"):
@@ -102,14 +141,35 @@ except:
 		sys.exit(2)
 
 
-def my_raw_input(prompt,echo=True):
+def my_raw_input(prompt,echo=True,allowed_chars=""):
+	try:
+		if echo:
+			reply = raw_input(prompt)
+		else:
+			from getpass import getpass
+			reply = getpass(prompt)
+	except:
+		print "\nUser interrupt"
+		sys.exit(1)
+
+	kb_hold_protect()
+	return reply
+
+
+def my_raw_input_old(prompt,echo=True,allowed_chars=""):
 
 	msg_r(prompt)
 	reply = ""
 
 	while True:
 		ch = get_char(immed_chars="ALL_EXCEPT_ENTER")
-		if echo: msg_r(ch)
+		if allowed_chars and ch not in allowed_chars+"\n\r\b"+chr(0x7f):
+			continue
+		if echo:
+			if ch in "\b"+chr(0x7f): # WIP
+				pass
+				# reply.pop(0)
+			else: msg_r(ch)
 		if ch in "\n\r":
 			if not echo: msg("")
 			break
@@ -119,8 +179,8 @@ def my_raw_input(prompt,echo=True):
 
 
 def _get_hash_params(hash_preset):
-	if hash_preset in hash_presets:
-		return hash_presets[hash_preset] # N,p,r,buflen
+	if hash_preset in g.hash_presets:
+		return g.hash_presets[hash_preset] # N,p,r,buflen
 	else:
 		# Shouldn't be here
 		msg("%s: invalid 'hash_preset' value" % hash_preset)
@@ -131,8 +191,8 @@ def show_hash_presets():
 	fs = "  {:<7} {:<6} {:<3}  {}"
 	msg("Available parameters for scrypt.hash():")
 	msg(fs.format("Preset","N","r","p"))
-	for i in sorted(hash_presets.keys()):
-		msg(fs.format("'%s'" % i, *hash_presets[i]))
+	for i in sorted(g.hash_presets.keys()):
+		msg(fs.format("'%s'" % i, *g.hash_presets[i]))
 	msg("N = memory usage (power of two), p = iterations (rounds)")
 	sys.exit(0)
 
@@ -142,7 +202,7 @@ cmessages = {
 	'unencrypted_secret_keys': """
 This program generates secret keys from your {} seed, outputting them in
 UNENCRYPTED form.  Generate only the key(s) you need and guard them carefully.
-""".format(proj_name),
+""".format(g.proj_name),
 	'brain_warning': """
 ############################## EXPERTS ONLY! ##############################
 
@@ -275,7 +335,7 @@ def parse_address_list(arg,sep=","):
 				return False
 			for k in range(beg,end+1): ret.append(k)
 		else:
-			msg("'%s': invalid argument for address range" % j)
+			msg("'%s': invalid argument for address range" % i)
 			return False
 
 	return sorted(set(ret))
@@ -288,10 +348,10 @@ def get_new_passphrase(what, opts):
 	elif 'echo_passphrase' in opts:
 		pw = " ".join(_get_words_from_user(("Enter %s: " % what), opts))
 	else:
-		for i in range(passwd_max_tries):
+		for i in range(g.passwd_max_tries):
 			pw = " ".join(_get_words_from_user(("Enter %s: " % what),opts))
 			pw2 = " ".join(_get_words_from_user(("Repeat %s: " % what),opts))
-			if debug: print "Passphrases: [%s] [%s]" % (pw,pw2)
+			if g.debug: print "Passphrases: [%s] [%s]" % (pw,pw2)
 			if pw == pw2:
 				msg("%ss match" % what.capitalize())
 				break
@@ -299,7 +359,7 @@ def get_new_passphrase(what, opts):
 				msg("%ss do not match" % what.capitalize())
 		else:
 			msg("User failed to duplicate passphrase in %s attempts" %
-					passwd_max_tries)
+					g.passwd_max_tries)
 			sys.exit(2)
 
 	if pw == "": msg("WARNING: Empty passphrase")
@@ -321,9 +381,9 @@ def _get_from_brain_opt_params(opts):
 
 def _get_seed_from_brain_passphrase(words,opts):
 	bp = " ".join(words)
-	if 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)
-	if 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...")
 	# Use buflen arg of scrypt.hash() to get seed of desired length
 	seed = _scrypt_hash_passphrase(bp, "", hash_preset, buflen=seed_len/8)
@@ -334,7 +394,7 @@ def _get_seed_from_brain_passphrase(words,opts):
 def encrypt_seed(seed, key, opts):
 	"""
 	Encrypt a seed for a {} deterministic wallet
-	""".format(proj_name)
+	""".format(g.proj_name)
 
 	# 192-bit seed is 24 bytes -> not multiple of 16.  Must use MODE_CTR
 	from Crypto.Cipher import AES
@@ -371,7 +431,7 @@ def write_to_stdout(data, what, confirm=True):
 
 def get_default_wordlist():
 
-	wl_id = default_wl
+	wl_id = g.default_wl
 	if wl_id == "electrum": from mmgen.mn_electrum import electrum_words as wl
 	elif wl_id == "tirosh": from mmgen.mn_tirosh   import tirosh_words as wl
 	return wl.strip().split("\n")
@@ -411,7 +471,7 @@ def write_to_file(outfile,data,confirm=False):
 
 def write_seed(seed, opts):
 
-	outfile = "%s.%s" % (make_chksum_8(seed).upper(),seed_ext)
+	outfile = "%s.%s" % (make_chksum_8(seed).upper(),g.seed_ext)
 	if 'outdir' in opts:
 		outfile = "%s/%s" % (opts['outdir'], outfile)
 
@@ -432,7 +492,7 @@ def write_seed(seed, opts):
 
 def write_mnemonic(mn, seed, opts):
 
-	outfile = "%s.%s" % (make_chksum_8(seed).upper(),mn_ext)
+	outfile = "%s.%s" % (make_chksum_8(seed).upper(),g.mn_ext)
 	if 'outdir' in opts:
 		outfile = "%s/%s" % (opts['outdir'], outfile)
 
@@ -492,7 +552,7 @@ def write_wallet_to_file(seed, passwd, key_id, salt, enc_seed, opts):
 
 	hash_preset = opts['hash_preset']
 
-	outfile="{}-{}[{},{}].{}".format(seed_id,key_id,seed_len,hash_preset,wallet_ext)
+	outfile="{}-{}[{},{}].{}".format(seed_id,key_id,seed_len,hash_preset,g.wallet_ext)
 	if 'outdir' in opts:
 		outfile = "%s/%s" % (opts['outdir'], outfile)
 
@@ -538,7 +598,7 @@ def compare_checksums(chksum1, desc1, chksum2, desc2):
 		msg("OK (%s)" % chksum1.upper())
 		return True
 	else:
-		if debug:
+		if g.debug:
 			msg("ERROR!\nComputed checksum %s (%s) doesn't match checksum %s (%s)" \
 				% (desc1,chksum1,desc2,chksum2))
 		return False
@@ -552,7 +612,7 @@ def _is_hex(s):
 def _check_mmseed_format(words):
 
 	valid = False
-	what = "%s data" % seed_ext
+	what = "%s data" % g.seed_ext
 	try:
 		chklen = len(words[0])
 	except:
@@ -596,14 +656,14 @@ def _check_chksum_6(chk,val,desc,infile):
 		msg("%s checksum incorrect in file '%s'!" % (desc,infile))
 		msg("Checksum: %s. Computed value: %s" % (chk,comp_chk))
 		sys.exit(2)
-	elif debug:
+	elif g.debug:
 		msg("%s checksum passed: %s" % (desc.capitalize(),chk))
 
 
 def get_data_from_wallet(infile,opts,silent=False):
 
 	if not silent:
-		msg("Getting {} wallet data from file '{}'".format(proj_name,infile))
+		msg("Getting {} wallet data from file '{}'".format(g.proj_name,infile))
 
 	f = open_file_or_exit(infile, 'r')
 
@@ -646,7 +706,7 @@ def _get_words_from_user(prompt, opts):
 	# split() also strips
 	words = my_raw_input(prompt,
 				echo=True if 'echo_passphrase' in opts else False).split()
-	if debug: print "Sanitized input: [%s]" % " ".join(words)
+	if g.debug: print "Sanitized input: [%s]" % " ".join(words)
 	return words
 
 
@@ -656,15 +716,25 @@ def _get_words_from_file(infile,what):
 	# split() also strips
 	words = f.read().split()
 	f.close()
-	if debug: print "Sanitized input: [%s]" % " ".join(words)
+	if g.debug: print "Sanitized input: [%s]" % " ".join(words)
 	return words
 
 
-def get_lines_from_file(infile,what=""):
+def get_lines_from_file(infile,what="",remove_comments=False):
 	if what != "": msg("Getting %s from file '%s'" % (what,infile))
 	f = open_file_or_exit(infile,'r')
 	lines = f.read().splitlines(); f.close()
-	return lines
+	if remove_comments:
+		import re
+		# re.sub(pattern, repl, string, count=0, flags=0)
+		ret = []
+		for i in lines:
+			i = re.sub('#.*','',i,1)
+			i = re.sub('\s+$','',i)
+			if i: ret.append(i)
+		return ret
+	else:
+		return lines
 
 
 def get_data_from_file(infile,what="data"):
@@ -678,14 +748,14 @@ def get_data_from_file(infile,what="data"):
 def _get_seed_from_seed_data(words):
 
 	if not _check_mmseed_format(words):
-		msg("Invalid %s data" % seed_ext)
+		msg("Invalid %s data" % g.seed_ext)
 		return False
 
 	stored_chk = words[0]
 	seed_b58 = "".join(words[1:])
 
 	chk = make_chksum_6(seed_b58)
-	msg_r("Validating %s checksum..." % seed_ext)
+	msg_r("Validating %s checksum..." % g.seed_ext)
 
 	if compare_checksums(chk, "from seed", stored_chk, "from input"):
 		seed = b58decode_pad(seed_b58)
@@ -693,10 +763,10 @@ def _get_seed_from_seed_data(words):
 			msg("Invalid b58 number: %s" % val)
 			return False
 
-		msg("%s data produces seed ID: %s" % (seed_ext,make_chksum_8(seed)))
+		msg("%s data produces seed ID: %s" % (g.seed_ext,make_chksum_8(seed)))
 		return seed
 	else:
-		msg("Invalid checksum for {} seed".format(proj_name))
+		msg("Invalid checksum for {} seed".format(g.proj_name))
 		return False
 
 
@@ -730,7 +800,7 @@ def get_bitcoind_passphrase(prompt,opts):
 def get_seed_from_wallet(
 		infile,
 		opts,
-		prompt="Enter {} wallet passphrase: ".format(proj_name),
+		prompt="Enter {} wallet passphrase: ".format(g.proj_name),
 		silent=False
 		):
 
@@ -774,7 +844,7 @@ def decrypt_seed(enc_seed, key, seed_id, key_id):
 	if compare_checksums(chk,"of decrypted seed",seed_id,"in header"):
 		msg("Passphrase is OK")
 	else:
-		if not debug:
+		if not g.debug:
 			msg_r("Checking key ID...")
 			chk = make_chksum_8(key)
 			if compare_checksums(chk, "of key", key_id, "in header"):
@@ -784,7 +854,7 @@ def decrypt_seed(enc_seed, key, seed_id, key_id):
 
 		return False
 
-	if debug: msg("key: %s" % hexlify(key))
+	if g.debug: msg("key: %s" % hexlify(key))
 
 	return dec_seed
 
@@ -800,17 +870,17 @@ def get_seed(infile,opts,silent=False):
 
 	ext = infile.split(".")[-1]
 
-	if   ext == mn_ext:           source = "mnemonic"
-	elif ext == brain_ext:        source = "brainwallet"
-	elif ext == seed_ext:         source = "seed"
-	elif ext == wallet_ext:       source = "wallet"
+	if   ext == g.mn_ext:           source = "mnemonic"
+	elif ext == g.brain_ext:        source = "brainwallet"
+	elif ext == g.seed_ext:         source = "seed"
+	elif ext == g.wallet_ext:       source = "wallet"
 	elif 'from_mnemonic' in opts: source = "mnemonic"
 	elif 'from_brain'    in opts: source = "brainwallet"
 	elif 'from_seed'     in opts: source = "seed"
 	else:
 		if infile: msg(
 			"Invalid file extension for file: %s\nValid extensions: '.%s'" %
-			(infile, "', '.".join(seed_exts)))
+			(infile, "', '.".join(g.seedfile_exts)))
 		else: msg("No seed source type specified and no file supplied")
 		sys.exit(2)
 
@@ -827,19 +897,19 @@ def get_seed(infile,opts,silent=False):
 		if 'quiet' not in opts:
 			confirm_or_exit(
 				cmessages['brain_warning'].format(
-					proj_name.capitalize(), *_get_from_brain_opt_params(opts)),
+					g.proj_name.capitalize(), *_get_from_brain_opt_params(opts)),
 				"continue")
 		prompt = "Enter brainwallet passphrase: "
 		words = _get_words(infile,"brainwallet data",prompt,opts)
 		seed = _get_seed_from_brain_passphrase(words,opts)
 	elif source == "seed":
-		prompt = "Enter seed in %s format: " % seed_ext
+		prompt = "Enter seed in %s format: " % g.seed_ext
 		words = _get_words(infile,"seed data",prompt,opts)
 		seed = _get_seed_from_seed_data(words)
 	elif source == "wallet":
 		seed = get_seed_from_wallet(infile, opts, silent=silent)
 
-	if infile and not seed:
+	if infile and not seed and (source == "seed" or source == "mnemonic"):
 		msg("Invalid %s file: %s" % (source,infile))
 		sys.exit(2)
 
@@ -854,18 +924,6 @@ def get_seed_retry(infile,opts):
 		if seed: return seed
 
 
-def remove_blanks_comments(lines):
-	import re
-#	re.sub(pattern, repl, string, count=0, flags=0)
-	ret = []
-	for i in lines:
-		i = re.sub('#.*','',i,1)
-		i = re.sub('\s+$','',i)
-		if i: ret.append(i)
-
-	return ret
-
-
 def do_pager(text):
 
 	pagers = ["less","more"]
@@ -905,6 +963,5 @@ def do_pager(text):
 				break
 	else: print text+end
 
-
 if __name__ == "__main__":
 	print "utils.py"

+ 1 - 1
setup.py

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