Modified README.md. Various fixes, improvements.
This commit is contained in:
parent
0f11e8971a
commit
536bfb5221
21 changed files with 839 additions and 385 deletions
561
README.md
561
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`
|
||||
|
|
|
|||
78
doc/MMGenLinuxInstall.md
Normal file
78
doc/MMGenLinuxInstall.md
Normal 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.
|
||||
22
doc/MMGenWindowsInstall.md
Normal file
22
doc/MMGenWindowsInstall.md
Normal 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...)
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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'])
|
||||
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
121
mmgen/tx.py
121
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
|
||||
|
|
|
|||
167
mmgen/utils.py
167
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"
|
||||
|
|
|
|||
2
setup.py
2
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',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue