Modified README.md. Various fixes, improvements.

This commit is contained in:
philemon 2014-03-30 12:07:19 +04:00
commit 536bfb5221
21 changed files with 839 additions and 385 deletions

561
README.md
View file

@ -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`

78
doc/MMGenLinuxInstall.md Normal file
View file

@ -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.

View file

@ -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...)

View file

@ -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

View file

@ -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)

View file

@ -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))

View file

@ -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")

View file

@ -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

View file

@ -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"

View file

@ -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)

View file

@ -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'])

View file

@ -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])

View file

@ -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")

View file

@ -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

View file

@ -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 = {

View file

@ -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):

View file

@ -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):

View file

@ -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,

View file

@ -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

View file

@ -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"

View file

@ -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',