diff --git a/README.md b/README.md index eb15c3ce..72086597 100644 --- a/README.md +++ b/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 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) - -### Getting Started: -> On your offline computer: - -> Generate a wallet with a random seed: - - $ mmgen-walletgen - ... - Wallet saved to file '89ABCDEF-76543210[256,3].dat' - -> "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. - -> 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. - -> "256" is the seed length; "3" is the scrypt hash preset. These are -> configurable. +> #### [Install on Debian/Ubuntu Linux](doc/MMGenLinuxInstall.md) -> Generate ten addresses with the wallet: +### Using MMGen - $ mmgen-addrgen 89ABCDEF-76543210[256,3].dat 1-10 - ... - Address data saved to file '89ABCDEF[1-10].addrs' +#### Generate a wallet (offline computer): +On your offline computer, generate a wallet with a random seed: -> Note that the address range, "1-10", is included in the resulting filename. + $ mmgen-walletgen + ... + Wallet saved to file '89ABCDEF-76543210[256,3].mmdat' - $ 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 - } +"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 seed. The Key ID changes when the wallet's password +or hash preset are changed. -> 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. +"256" is the seed length; "3" is the scrypt hash preset. These values +are configurable: type `mmgen-walletgen --help` for details. -### Spending your stored coins: -> Take address 1 out of cold storage by generating a key for it: +#### Generate addresses (offline computer): - $ mmgen-keygen 89ABCDEF-76543210[256,3].dat 1 - ... - Key data saved to file '89ABCDEF[1].akeys' +Now generate ten addresses with your just-created wallet: - $ cat 89ABCDEF[1].akeys - 89ABCDEF { - 1 sec: 5JCAfK1pjRoJgmpmd2HEMNwHxAzprGIXeQt8dz5qt3iLvU2KCbS - addr: 16bNmyYISiptuvJG3X7MPwiiS4HYvD7ksE - } + $ 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 + } -> Save the .akeys file to a USB stick and transfer it to your online computer. +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. -> On your online computer, import the secret key into a running bitcoind -> or bitcoin-qt: +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. - $ bitcoind importprivkey 5JCAfK1pjRoJgmpmd2HEMNwHxAzprGIXeQt8dz5qt3iLvU2KCbS +Make a copy of the file: -> You're done! This address' balance can now be spent. + $ cp '89ABCDEF[1-10].addrs' my_addrs_for_import -> OPTIONAL: To track balances without exposing secret keys on your -> online computer, download and compile sipa's bitcoind patched for -> watch-only addresses: +and edit the copy using your favorite text editor. The result will +look something like this: - $ 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) + $ cat my_addrs_for_import + 89ABCDEF { + 1 16bNmyYISiptuvJG3X7MPwiiS4HYvD7ksE Donations + 2 1AmkUxrfy5dMrfmeYwTxLxfIswUCcpeysc Storage 1 + 3 1HgYCsfqYzIg7LVVfDTp7gYJocJEiDAy6N Storage 2 + 4 14Tu3z1tiexXDonNsFIkvzqutE5E3pTK8s Storage 3 + } -> With your newly-compiled bitcoind running, import the addresses from -> '89ABCDEF[1-10].addrs' to track their balances: +Note that rows in the list may be arranged in any order; addresses +need not be consecutive. - $ bitcoind importaddress 16bNmyYISiptuvJG3X7MPwiiS4HYvD7ksE - $ bitcoind importaddress 1AmkUxrfy5dMrfmeYwTxLxfIswUCcpeysc - $ ... +Copy this file onto a USB stick and transfer it to your online +computer. -### Using the mnemonic and seed features: +#### Import addresses (online computer): -> Continuing our example above, +On your online computer, start bitcoind and import the addresses into +the tracking wallet: -> Generate a mnemonic from the wallet: + $ mmgen-addrimport my_addrs_for_import - $ mmgen-walletchk -m '89ABCDEF-76543210[256,3].dat' - ... - Mnemonic data saved to file '89ABCDEF.words' +These addresses are now being "tracked". Any BTC transferred +to them will show up in our listing of unspent outputs. - $ 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 +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: -> 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. + $ mmgen-addrimport -l my_existing_addrs_with_balances -> Generate addresses 1-11 using the mnemonic instead of the wallet: +Since the importing process is slow, you may want to do it in stages, +a few addresses at a time. - $ mmgen-addrgen -m 89ABCDEF.words 1-11 - ... - Address data saved to file '89ABCDEF[1-11].addrs' +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. -> Compare the first ten addresses with those earlier generated from the -> wallet. You'll see they're the same. +#### Create a transaction (online computer): -> Recover a lost wallet using the mnemonic: +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. - $ mmgen-walletgen -m 89ABCDEF.words - ... - Wallet saved to file '89ABCDEF-01234567[256,3].dat' +First of all, you'll probably want to examine your balances: -> Note that the regenerated wallet has a different Key ID but -> of course the same Seed ID. + $ mmgen-txcreate -i -> Seeds are generated and input the same way as mnemonics. Just change -> the `-m` option to `-s` in the preceding commands. +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: -> A seed file for a 256-bit seed looks 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 - $ cat 8B7392ED.mmseed - f4c84b C5ZT wWpT Jsoi wRVw 2dm9 Aftd WLb8 FggQ eC8h Szjd da9L + 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): -> And for a 128-bit seed: +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. - $ cat 8E0DFB78.mmseed - 0fe02f XnyC NfPH piuW dQ2d nM47 VU +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. -> The latter is short enough to be memorized or written down. +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). -> 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). +To refresh our memory, here are the three addresses in question: + + 89ABCDEF { + 2 1AmkUxrfy5dMrfmeYwTxLxfIswUCcpeysc Storage 1 + 3 1HgYCsfqYzIg7LVVfDTp7gYJocJEiDAy6N Storage 2 + 4 14Tu3z1tiexXDonNsFIkvzqutE5E3pTK8s Storage 3 + } + +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-txcreate 1AmkUxrfy5dMrfmeYwTxLxfIswUCcpeysc,3.3 1HgYCsfqYzIg7LVVfDTp7gYJocJEiDAy6N,3.3 14Tu3z1tiexXDonNsFIkvzqutE5E3pTK8s + +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. + +Alternatively, and more conveniently, you can write the +addresses in MMGen format: + + $ mmgen-txcreate -a addrs 89ABCDEF:2,3.3 89ABCDEF:3,3.3 89ABCDEF:4 + +'addrs' is an MMGen address file containing the requested output +addresses. Any unneeded addresses in the file will be ignored. + +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: + + Transaction data saved to file 'tx_1EDCBA[6.6].raw' + +Note that the transaction has a unique ID, and the non-change output +amount, 6.6 BTC, is conveniently included in the filename. + +#### Sign the transaction (offline computer): + +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: + + $ mmgen-pywallet -k wallet.dat + ... + wallet.dat secret keys saved to file wd_EDBC983A[102].keys + +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. + + $ mmgen-txsign -k wd_EDBC983A[102].keys tx_1EDCBA[6.6].raw + ... + Signed transaction saved to file tx_1EDCBA[6.6].sig + +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: + + $ mmgen-pywallet -k wallet1.dat + $ mmgen-pywallet -k wallet2.dat + $ mmgen-pywallet -k wallet3.dat + $ cat wd_*.keys > all_keys + +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: + + $ 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. + +NOTE: MMGen mnemonics are generated from the Electrum wordlist, only +using ordinary base conversion instead of Electrum's more complicated +algorithm. + +Generate addresses 1-11 using the mnemonic instead of the wallet: + + $ mmgen-addrgen 89ABCDEF.mmwords 1-11 + ... + Address data saved to file '89ABCDEF[1-11].addrs' + +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: -> Mnemonic and seed data may be entered at a prompt instead of from a -> file. Just omit the filename on the command line. -> Mnemonic and seed data may be printed to standard output instead of a -> file using the `-S` option of `mmgen-walletchk`. +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. -> Mnemonic and seed files may be output to a directory of your choice -> using the `-d` option of `mmgen-walletchk`. +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. -> 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. - -### 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. +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: ->> Convert a string to base 58 and back: ->>> `tests/bitcoin.py strtob58 'a string'` +> Compare 10 addresses generated by 'keyconv' with mmgen's +> internally-generated ones: +>> `tests/bitcoin.py keyconv_compare_randloop 10` ->> Convert a hex number to base 58 and back: ->>> `tests/bitcoin.py hextob58 deadbeef` +> Convert a string to base 58 and back: +>> `tests/bitcoin.py strtob58 'a string'` ->> Perform 1000 hex -> base58 -> hex conversions, comparing results stringwise: ->>> `tests/bitcoin.py hextob58_pad_randloop 1000` +> Convert a hex number to base 58 and back: +>> `tests/bitcoin.py hextob58 deadbeef` ->> Generate a 12-word mnemonic from a random 128-bit seed: ->>> `tests/mnemonic.py random128` +> Perform 1000 hex -> base58 -> hex conversions, comparing results stringwise: +>> `tests/bitcoin.py hextob58_pad_randloop 1000` ->> or an 18-word mnemonic from a random 192-bit seed: ->>> `tests/mnemonic.py random192` +> Generate a 12-word mnemonic from a random 128-bit seed: +>> `tests/mnemonic.py random128` ->> or a 24-word mnemonic from a random 256-bit seed: ->>> `tests/mnemonic.py random256` +> 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` diff --git a/doc/MMGenLinuxInstall.md b/doc/MMGenLinuxInstall.md new file mode 100644 index 00000000..67489afb --- /dev/null +++ b/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. diff --git a/doc/MMGenWindowsInstall.md b/doc/MMGenWindowsInstall.md new file mode 100644 index 00000000..cd1cb8aa --- /dev/null +++ b/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...) diff --git a/mmgen-addrgen b/mmgen-addrgen index 050baea1..33c55fb8 100755 --- a/mmgen-addrgen +++ b/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]
", '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 diff --git a/mmgen-addrimport b/mmgen-addrimport index a1cfbf47..68f17012 100755 --- a/mmgen-addrimport +++ b/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) diff --git a/mmgen-passchg b/mmgen-passchg index f16e4ac9..7d9ae728 100755 --- a/mmgen-passchg +++ b/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)) diff --git a/mmgen-txcreate b/mmgen-txcreate index 066dbba6..0166642b 100755 --- a/mmgen-txcreate +++ b/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] [,...] ", + 'desc': "Create a BTC transaction with outputs to specified addresses", + 'usage': "[opts] ... [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 : +""" % (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") diff --git a/mmgen-txsend b/mmgen-txsend index 7b9f534a..93592980 100755 --- a/mmgen-txsend +++ b/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 diff --git a/mmgen-txsign b/mmgen-txsign index 2a540e78..abe3fb21 100755 --- a/mmgen-txsign +++ b/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" diff --git a/mmgen-walletchk b/mmgen-walletchk index 2565a5a2..01688adb 100755 --- a/mmgen-walletchk +++ b/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) diff --git a/mmgen-walletgen b/mmgen-walletgen index fb6154c9..99a65672 100755 --- a/mmgen-walletgen +++ b/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']) diff --git a/mmgen/Opts.py b/mmgen/Opts.py index 6c44d945..17f5b0a5 100755 --- a/mmgen/Opts.py +++ b/mmgen/Opts.py @@ -17,7 +17,7 @@ # along with this program. If not, see . 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]) diff --git a/mmgen/addr.py b/mmgen/addr.py index 3085cd4c..a5df8cb7 100755 --- a/mmgen/addr.py +++ b/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") diff --git a/mmgen/config.py b/mmgen/config.py index 5a93e99e..ee6bfa9f 100755 --- a/mmgen/config.py +++ b/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 diff --git a/mmgen/license.py b/mmgen/license.py index 2d2fab2a..cea8e8a2 100755 --- a/mmgen/license.py +++ b/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 = { diff --git a/mmgen/mnemonic.py b/mmgen/mnemonic.py index 34a5c8e5..b9e06cf5 100755 --- a/mmgen/mnemonic.py +++ b/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): diff --git a/mmgen/rpc/connection.py b/mmgen/rpc/connection.py index 6623ddda..0fe6fc82 100755 --- a/mmgen/rpc/connection.py +++ b/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 [allowhighfees=false] def sendrawtransaction(self,tx): diff --git a/mmgen/rpc/proxy.py b/mmgen/rpc/proxy.py index a22decfe..6e13fdda 100755 --- a/mmgen/rpc/proxy.py +++ b/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, diff --git a/mmgen/tx.py b/mmgen/tx.py index c69c687b..18f5d3f1 100755 --- a/mmgen/tx.py +++ b/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): -def make_tx_out(rcpt_arg): + 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) + + msg("Invalid format: %s" % m) + 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: - 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) - - 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 diff --git a/mmgen/utils.py b/mmgen/utils.py index 55079a94..bf030b69 100755 --- a/mmgen/utils.py +++ b/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" diff --git a/setup.py b/setup.py index ed22dd2f..48c26fe6 100755 --- a/setup.py +++ b/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',