Old non-oo wallet code and files removed
test/test.py updated
This commit is contained in:
parent
d3f07f3c9f
commit
acd8eb26c5
21 changed files with 516 additions and 2680 deletions
|
|
@ -12,6 +12,7 @@
|
||||||
* <a href=#11>Using the mnemonic and seed features</a>
|
* <a href=#11>Using the mnemonic and seed features</a>
|
||||||
* <a href=#12>Mnemonics and seeds — additional information</a>
|
* <a href=#12>Mnemonics and seeds — additional information</a>
|
||||||
* <a href=#13>Incognito wallets</a>
|
* <a href=#13>Incognito wallets</a>
|
||||||
|
* <a href=#13a>Hidden incognito wallets</a>
|
||||||
|
|
||||||
|
|
||||||
### <a name=01>Basic Operations</a>
|
### <a name=01>Basic Operations</a>
|
||||||
|
|
@ -22,7 +23,7 @@ On your offline computer, generate a wallet with a random seed:
|
||||||
|
|
||||||
$ mmgen-walletgen
|
$ mmgen-walletgen
|
||||||
...
|
...
|
||||||
Wallet saved to file '89ABCDEF-76543210[256,3].mmdat'
|
MMGen wallet written to file '89ABCDEF-76543210[256,3].mmdat'
|
||||||
|
|
||||||
"89ABCDEF" is the Seed ID; "76543210" is the Key ID. These are randomly
|
"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
|
generated, so your IDs will of course be different than the fictitious ones used
|
||||||
|
|
@ -41,7 +42,7 @@ Now generate ten addresses with your just-created wallet:
|
||||||
|
|
||||||
$ mmgen-addrgen 89ABCDEF-76543210[256,3].mmdat 1-10
|
$ mmgen-addrgen 89ABCDEF-76543210[256,3].mmdat 1-10
|
||||||
...
|
...
|
||||||
Address data saved to file '89ABCDEF[1-10].addrs'
|
Addresses written to file '89ABCDEF[1-10].addrs'
|
||||||
|
|
||||||
$ cat '89ABCDEF[1-10].addrs'
|
$ cat '89ABCDEF[1-10].addrs'
|
||||||
89ABCDEF {
|
89ABCDEF {
|
||||||
|
|
@ -58,7 +59,7 @@ Now generate ten addresses with your just-created wallet:
|
||||||
}
|
}
|
||||||
|
|
||||||
Note that the address range, "1-10", is reflected in the resulting filename.
|
Note that the address range, "1-10", is reflected in the resulting filename.
|
||||||
MMGen addresses are identified by their seed ID and index number, separated by a
|
MMGen addresses are identified by their Seed ID and index number, separated by a
|
||||||
colon. In this example, "89ABCDEF:1" is the MMGen equivalent of Bitcoin address
|
colon. In this example, "89ABCDEF:1" is the MMGen equivalent of Bitcoin address
|
||||||
16bNmyYISiptuvJG3X7MPwiiS4HYvD7ksE, "89ABCDEF:2" the equivalent of
|
16bNmyYISiptuvJG3X7MPwiiS4HYvD7ksE, "89ABCDEF:2" the equivalent of
|
||||||
1AmkUxrfy5dMrfmeYwTxLxfIswUCcpeysc, and so forth.
|
1AmkUxrfy5dMrfmeYwTxLxfIswUCcpeysc, and so forth.
|
||||||
|
|
@ -93,9 +94,9 @@ Copy this file onto a USB stick and transfer it to your online computer.
|
||||||
#### <a name=04>Import addresses (online computer):</a>
|
#### <a name=04>Import addresses (online computer):</a>
|
||||||
|
|
||||||
On your online computer, go to your bitcoind data directory and move any
|
On your online computer, go to your bitcoind data directory and move any
|
||||||
existing 'wallet.dat' file out of the way. Start bitcoind and let it generate
|
existing 'wallet.dat' file out of harm's way. Start bitcoind and let it
|
||||||
a new 'wallet.dat', which you'll use as your **tracking wallet**. Import your
|
generate a new 'wallet.dat', which you'll use as your **tracking wallet**.
|
||||||
four addresses into the new tracking wallet with the command:
|
Import your four addresses into the new tracking wallet with the command:
|
||||||
|
|
||||||
$ mmgen-addrimport my.addrs
|
$ mmgen-addrimport my.addrs
|
||||||
|
|
||||||
|
|
@ -138,7 +139,7 @@ harmless until they're signed and broadcast to the network, so feel free to
|
||||||
experiment with different transactions using different combinations of inputs
|
experiment with different transactions using different combinations of inputs
|
||||||
and outputs.
|
and outputs.
|
||||||
|
|
||||||
First of all you'll want to examine your balances. Note that 'mmgen-tool
|
First of all, you'll want to examine your balances. Note that 'mmgen-tool
|
||||||
listaddresses' shows only MMGen address balances; to view **all** balances,
|
listaddresses' shows only MMGen address balances; to view **all** balances,
|
||||||
including your non-MMGen ones, use the 'mmgen-txcreate' command:
|
including your non-MMGen ones, use the 'mmgen-txcreate' command:
|
||||||
|
|
||||||
|
|
@ -147,8 +148,8 @@ including your non-MMGen ones, use the 'mmgen-txcreate' command:
|
||||||
A list of all unspent outputs will appear, along with a menu allowing you to
|
A list of all unspent outputs will appear, along with a menu allowing you to
|
||||||
sort the outputs by four criteria: transaction ID, address, amount and
|
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.
|
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. For a wallet with ten
|
The list may optionally be viewed in a pager or printed to file. For a wallet
|
||||||
unspent outputs, the display might look something like this:
|
with ten unspent outputs, the display might look something like this:
|
||||||
|
|
||||||
UNSPENT OUTPUTS (sort order: reverse amount) Total BTC: 39.72
|
UNSPENT OUTPUTS (sort order: reverse amount) Total BTC: 39.72
|
||||||
Num TX id Vout Address Amount (BTC) Age(days)
|
Num TX id Vout Address Amount (BTC) Age(days)
|
||||||
|
|
@ -168,126 +169,143 @@ unspent outputs, the display might look something like this:
|
||||||
(Type 'q' to quit sorting, 'p' to print to file, 'v' to view in pager):
|
(Type 'q' to quit sorting, 'p' to print to file, 'v' to view in pager):
|
||||||
|
|
||||||
Now let's actually create a transaction. Let's say you've decided to gradually
|
Now let's actually create a transaction. Let's say you've decided to gradually
|
||||||
begin moving your 39.72 BTC balance into your shiny new MMGen wallet with seed
|
begin moving your 39.72 BTC balance into your shiny new MMGen wallet with Seed
|
||||||
ID 89ABCDEF.
|
ID 89ABCDEF.
|
||||||
|
|
||||||
Before moving any funds into your MMGen wallet, you should back it up in several
|
Before moving any funds into your MMGen wallet, you should back it up in several
|
||||||
places and possibly on several media too: paper, flash memory or CD-ROM, for
|
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
|
example. You're advised to use a passphrase on your wallet. Otherwise, anyone
|
||||||
gains physical access to one of your backups can easily steal your coins.
|
who gains physical access to one of your backups can easily steal your coins.
|
||||||
|
|
||||||
Recall that there's no limit to the number of addresses you can generate with
|
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
|
your seed. You've wisely determined that having many addresses with relatively
|
||||||
small balances is a Good Idea. So you've decided to begin by breaking up the
|
small balances is a Good Idea. So you've decided to begin by breaking up your
|
||||||
address with the largest balance, 10 BTC, into three roughly equal parts,
|
address with the largest balance, 10 BTC, address 1F93Znz..., into three roughly
|
||||||
sending it to the addresses labeled "Storage 1", "Storage 2" and "Storage 3"
|
equal parts and send them to the addresses labeled "Storage 1", "Storage 2" and
|
||||||
(89ABCDEF:2, 89ABCDEF:3 and 89ABCDEF:4).
|
"Storage 3" (89ABCDEF:2, 89ABCDEF:3 and 89ABCDEF:4).
|
||||||
|
|
||||||
To refresh your memory, here are the three addresses in question:
|
To refresh your memory, here are the three destination addresses in question:
|
||||||
|
|
||||||
$ cat my.addrs
|
$ cat my.addrs | grep -v Donations
|
||||||
# My first MMGen addresses
|
# My first MMGen addresses
|
||||||
89ABCDEF {
|
89ABCDEF {
|
||||||
1 16bNmyYISiptuvJG3X7MPwiiS4HYvD7ksE Donations
|
|
||||||
2 1AmkUxrfy5dMrfmeYwTxLxfIswUCcpeysc Storage 1
|
2 1AmkUxrfy5dMrfmeYwTxLxfIswUCcpeysc Storage 1
|
||||||
3 1HgYCsfqYzIg7LVVfDTp7gYJocJEiDAy6N Storage 2
|
3 1HgYCsfqYzIg7LVVfDTp7gYJocJEiDAy6N Storage 2
|
||||||
4 14Tu3z1tiexXDonNsFIkvzqutE5E3pTK8s Storage 3
|
4 14Tu3z1tiexXDonNsFIkvzqutE5E3pTK8s Storage 3
|
||||||
}
|
}
|
||||||
|
|
||||||
The following command will send 3.3 BTC to the first two addresses and the
|
The following command does just this, sending 6.6 BTC of the transaction's 10
|
||||||
remainder of the transaction's 10 BTC input to the third, subtracting a default
|
BTC input to the first two addresses and the remainder to the third address,
|
||||||
transaction fee of 0.001 BTC:
|
for which no amount has been specified:
|
||||||
|
|
||||||
$ mmgen-txcreate 1AmkUxrfy5dMrfmeYwTxLxfIswUCcpeysc,3.3 1HgYCsfqYzIg7LVVfDTp7gYJocJEiDAy6N,3.3 14Tu3z1tiexXDonNsFIkvzqutE5E3pTK8s
|
$ mmgen-txcreate 1AmkUxrfy5dMrfmeYwTxLxfIswUCcpeysc,3.3 1HgYCsfqYzIg7LVVfDTp7gYJocJEiDAy6N,3.3 14Tu3z1tiexXDonNsFIkvzqutE5E3pTK8s
|
||||||
|
|
||||||
The bare address with no amount is the **change address**. MMGen will compute
|
The address with no amount is the **change address**; MMGen will calculate
|
||||||
the change amount (3.399 BTC in this case) automatically.
|
the amount sent to it automatically by subtracting the sum of the outputs
|
||||||
|
plus transaction fee, if any, from the inputs. In our example, 3.39995 BTC (10
|
||||||
|
BTC - (3.3 + 3.3 BTC) - .00005 BTC default transaction fee) will go to this
|
||||||
|
address.
|
||||||
|
|
||||||
Note that the above transaction can be written much more elegantly and concisely
|
Note that the above transaction can be expressed much more concisely by
|
||||||
using MMGen addresses in place of their Bitcoin equivalents:
|
replacing the Bitcoin addresses with their MMGen equivalents:
|
||||||
|
|
||||||
$ mmgen-txcreate 89ABCDEF:2,3.3 89ABCDEF:3,3.3 89ABCDEF:4
|
$ mmgen-txcreate 89ABCDEF:2,3.3 89ABCDEF:3,3.3 89ABCDEF:4
|
||||||
|
|
||||||
After hitting ENTER you'll be presented with the same display produced by the
|
For this to work, the addresses must be imported into your tracking wallet,
|
||||||
'-i' option above, plus an interactive menu. After quitting with 'q', you'll
|
which they should be in any case.
|
||||||
be prompted to choose the transaction's inputs.
|
|
||||||
|
After hitting ENTER you'll be presented with the same UNSPENT OUTPUTS display as
|
||||||
|
with the '-i' option above. In our example, note that the output with 10 BTC
|
||||||
|
which you wish to spend, 1F93Znz..., is listed as number '1'. Remember this.
|
||||||
|
After quitting the menu with 'q' you'll see the following prompt:
|
||||||
|
|
||||||
Enter a range or space-separated list of outputs to spend:
|
Enter a range or space-separated list of outputs to spend:
|
||||||
|
|
||||||
Find the input with the 10 BTC balance in the list. This is input 1), so type
|
Type your remembered '1' here and hit ENTER. After several more prompts and
|
||||||
'1' and ENTER. After several more prompts and confirmations 'mmgen-txcreate'
|
confirmations, your transaction will be saved:
|
||||||
will exit with the message:
|
|
||||||
|
|
||||||
Transaction data saved to file 'tx_1EDCBA[6.6].raw'
|
Transaction written to file 'tx_FEDCBA[6.6].raw'
|
||||||
|
|
||||||
Note that the transaction has a unique ID, and the non-change spend amount, 6.6
|
Note that the transaction has a unique ID, and the non-change spend amount of
|
||||||
BTC, is conveniently included in the filename.
|
6.6 BTC is included in the filename.
|
||||||
|
|
||||||
#### <a name=06>Sign a transaction (offline computer):</a>
|
#### <a name=06>Sign a transaction (offline computer):</a>
|
||||||
|
|
||||||
Now copy the raw transaction you've just created to a USB stick and transfer it
|
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
|
to your offline computer for signing. For this you'll need the key corresponding
|
||||||
transaction's one input address, 1F9495H8EJL.... If the key in question is in a
|
to the transaction's input address, 1F93Znz.... If the key in question is in a
|
||||||
bitcoin 'wallet.dat', there's an included command (a modified version of the
|
bitcoind 'wallet.dat', copy 'wallet.dat' to your offline machine as well and use
|
||||||
well-known pywallet utility) that will conveniently extract it for you:
|
the included command 'mmgen-pywallet' (a modified version of the well-known
|
||||||
|
pywallet utility) to extract the required key from it.
|
||||||
|
|
||||||
$ mmgen-pywallet -k wallet.dat
|
$ mmgen-pywallet -k wallet.dat
|
||||||
...
|
...
|
||||||
wallet.dat secret keys saved to file wd_EDBC983A[102].keys
|
Private keys written 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
|
You've in fact extracted a list of all of the wallet's 102 keys here, but that's
|
||||||
not a problem, since the unused keys will be ignored (you can extract only the
|
not a problem, since the unused keys will be ignored (if you wish, you can
|
||||||
keys you need using the '--keys-for-addrs' option). Now go ahead and sign the
|
extract only the one key you need using the '--keys-for-addrs' option). Now go
|
||||||
transaction using this list of keys.
|
ahead and sign the transaction using this key list:
|
||||||
|
|
||||||
$ mmgen-txsign -k wd_EDBC983A[102].keys tx_1EDCBA[6.6].raw
|
$ mmgen-txsign -k wd_EDBC983A[102].keys tx_FEDCBA[6.6].raw
|
||||||
...
|
...
|
||||||
Signed transaction saved to file tx_ABCDEF[0.1].sig
|
Signed transaction written to file 'tx_FEDCBA[6.6].sig'
|
||||||
|
|
||||||
Note that 'mmgen-pywallet's output is just a flat list of keys. So if you have
|
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
|
several Bitcoin wallets with balances, you can just concatenate these lists into
|
||||||
merge them into a single file which you can use to sign all future transactions
|
a single file which you can use to sign all future transactions with
|
||||||
with wallet.dat inputs:
|
'wallet.dat' inputs:
|
||||||
|
|
||||||
$ mmgen-pywallet -k wallet1.dat
|
$ mmgen-pywallet -k wallet1.dat
|
||||||
$ mmgen-pywallet -k wallet2.dat
|
$ mmgen-pywallet -k wallet2.dat
|
||||||
$ mmgen-pywallet -k wallet3.dat
|
$ mmgen-pywallet -k wallet3.dat
|
||||||
$ cat wd_*.keys > all_keys
|
$ cat wd_*.keys > all_keys
|
||||||
|
|
||||||
For your future transactions with MMGen address inputs, you'll list the MMGen
|
Once you've migrated your funds to MMGen, such key files will no longer be
|
||||||
seed source (wallet, mnemonic or seed file) on the command line after the
|
needed. Instead, you'll sign transactions by listing an MMGen seed source
|
||||||
transaction file, and the required keys will be generated automatically, as in
|
(wallet, mnemonic or seed file) on the command line after the transaction,
|
||||||
this example:
|
and the required keys will be generated on the fly, as in this example:
|
||||||
|
|
||||||
$ mmgen-txsign tx_9D2C3A[1.23].raw B73B58EA-125FB230[256,3].mmdat
|
$ mmgen-txsign tx_ABCDE[1.2345].raw 1234567A-BCDEF123[256,3].mmdat
|
||||||
...
|
|
||||||
Signed transaction saved to file tx_9D2C3A[1.23].sig
|
|
||||||
|
|
||||||
Transactions may contain a mixture of MMGen and non-MMGen inputs as well as
|
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
|
inputs with more than one MMGen Seed ID. Just list a seed source for each
|
||||||
seed ID on the command line.
|
MMGen input on the command line after the transaction, as in this example:
|
||||||
|
|
||||||
Eventually, when you've placed all your BTC under MMGen control, you'll never
|
$ mmgen-txsign -k key_list my_tx.raw a.mmdat b.mmwords c.mmseed
|
||||||
have deal with keys directly again, because MMGen generates all keys on the fly
|
|
||||||
using the seed.
|
|
||||||
|
|
||||||
#### <a name=07>Send a transaction (online computer):</a>
|
#### <a name=07>Send a transaction (online computer):</a>
|
||||||
|
|
||||||
Now you're ready for the final step: broadcasting the transaction to the network.
|
Now you're ready for the final step: broadcasting the transaction to the
|
||||||
Copy the 'tx_*.sig' file to your online computer, start bitcoind, if it's not
|
network. Copy just-created signed transaction file to your online computer,
|
||||||
running, and execute the command:
|
start bitcoind and issue the command:
|
||||||
|
|
||||||
$ mmgen-txsend tx_1EDCBA[6.6].sig
|
$ mmgen-txsend tx_FEDCBA[6.6].sig
|
||||||
|
|
||||||
Like all MMGen commands, 'mmgen-txsend' is interactive, so you'll be asked for
|
Like all MMGen commands, 'mmgen-txsend' is interactive, so you'll be prompted
|
||||||
confirmation before the transaction is actually sent.
|
before the transaction is actually sent.
|
||||||
|
|
||||||
Once the transaction's confirmed by the network, your three new MMGen addresses
|
Once the transaction is broadcast to the network, you can view your three new
|
||||||
will appear on the listing of 'mmgen-txcreate -i'. Type 'm' at the menu to
|
MMGen addresses and their balances:
|
||||||
see them displayed in MMGen format.
|
|
||||||
|
$ mmgen-tool listaddresses minconf=0
|
||||||
|
ADDRESS COMMENT BALANCE
|
||||||
|
89ABCDEF:2 Storage 1 3.3
|
||||||
|
89ABCDEF:3 Storage 2 3.3
|
||||||
|
89ABCDEF:4 Storage 3 3.39995
|
||||||
|
|
||||||
|
Your total MMGen balance will also now be visible:
|
||||||
|
|
||||||
|
$ mmgen-tool getbalance minconf=0
|
||||||
|
Wallet Unconfirmed <0 confirms >=0 confirms
|
||||||
|
89ABCDEF: 0 BTC 0 BTC 9.99995 BTC
|
||||||
|
TOTAL: 0 BTC 0 BTC 9.99995 BTC
|
||||||
|
|
||||||
|
To verify that your transaction's received its first, second, third and so forth
|
||||||
|
confirmation, increase the 'minconf' value to 1, 2, 3 and so forth.
|
||||||
|
|
||||||
Congratulations! You've performed your first MMGen transaction and placed your
|
Congratulations! You've performed your first MMGen transaction and placed your
|
||||||
first funds under MMGen's control.
|
first funds under MMGen control.
|
||||||
|
|
||||||
### <a name=10>Additional Features</a>
|
### <a name=10>Additional Features</a>
|
||||||
|
|
||||||
|
|
@ -295,9 +313,9 @@ first funds under MMGen's control.
|
||||||
|
|
||||||
Continuing our example above, generate a mnemonic from the wallet:
|
Continuing our example above, generate a mnemonic from the wallet:
|
||||||
|
|
||||||
$ mmgen-walletchk -m '89ABCDEF-76543210[256,3].mmdat'
|
$ mmgen-walletconv -o words '89ABCDEF-76543210[256,3].mmdat'
|
||||||
...
|
...
|
||||||
Mnemonic data saved to file '89ABCDEF.mmwords'
|
Mnemonic data written to file '89ABCDEF.mmwords'
|
||||||
|
|
||||||
$ cat 89ABCDEF.mmwords
|
$ cat 89ABCDEF.mmwords
|
||||||
pleasure tumble spider laughter many stumble secret bother after search
|
pleasure tumble spider laughter many stumble secret bother after search
|
||||||
|
|
@ -309,114 +327,139 @@ words. You may generate a wallet with these seed lengths using the '-l'
|
||||||
option to 'mmgen-walletgen'.
|
option to 'mmgen-walletgen'.
|
||||||
|
|
||||||
Though some consider 128 bits of entropy to provide adequate security for the
|
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
|
foreseeable future, it's advisable to stick to the default 256-bit seed length
|
||||||
you're not planning to use the mnemonic feature.
|
if you're not planning to use the mnemonic feature.
|
||||||
|
|
||||||
NOTE: MMGen mnemonics are generated from the Electrum wordlist, but using
|
NOTE: MMGen mnemonics are generated from the Electrum wordlist, but using
|
||||||
ordinary base conversion instead of Electrum's more complicated algorithm.
|
ordinary base conversion instead of Electrum's more complicated algorithm.
|
||||||
|
|
||||||
Generate addresses 1-11 of seed 89ABCDEF using the mnemonic instead of the
|
The mnemonic file may be used any place you'd use a MMGen wallet with the same
|
||||||
wallet:
|
Seed ID. You can generate ten addresses with it just as you did with the
|
||||||
|
wallet, for example:
|
||||||
|
|
||||||
$ mmgen-addrgen 89ABCDEF.mmwords 1-11
|
$ mmgen-addrgen 89ABCDEF.mmwords 1-10
|
||||||
...
|
...
|
||||||
Address data saved to file '89ABCDEF[1-11].addrs'
|
Address data written to file '89ABCDEF[1-10].addrs'
|
||||||
|
|
||||||
Compare the first ten addresses with those earlier generated by the wallet.
|
The resulting address file will be identical to one generated by any wallet with
|
||||||
You'll see they're the same.
|
Seed ID '89ABCDEF'.
|
||||||
|
|
||||||
Regenerate a lost wallet using the mnemonic:
|
The mnemonic can be used to regenerate a lost wallet:
|
||||||
|
|
||||||
$ mmgen-walletgen 89ABCDEF.mmwords
|
$ mmgen-walletconv 89ABCDEF.mmwords
|
||||||
...
|
...
|
||||||
Wallet saved to file '89ABCDEF-01234567[256,3].mmdat'
|
MMGen wallet written to file '89ABCDEF-01234567[256,3].mmdat'
|
||||||
|
|
||||||
Note that the regenerated wallet has a different Key ID but of course the same
|
Note that the regenerated wallet has a different Key ID but of course the same
|
||||||
Seed ID.
|
Seed ID.
|
||||||
|
|
||||||
Seed files bear the extension '.mmseed' and are listed on the command line the
|
Seed files bear the extension '.mmseed' and are generated and used exactly
|
||||||
same way mnemonic files are.
|
the same way as mnemonic files:
|
||||||
|
|
||||||
A seed file for a 256-bit seed looks like this:
|
$ mmgen-walletconv -o seed '89ABCDEF-76543210[256,3].mmdat'
|
||||||
|
...
|
||||||
|
Seed data written to file '89ABCDEF.mmseed'
|
||||||
|
|
||||||
|
And they can also be used to regenerate a wallet:
|
||||||
|
|
||||||
|
$ mmgen-walletconv 89ABCDEF.mmseed
|
||||||
|
...
|
||||||
|
MMGen wallet written to file '89ABCDEF-23456701[256,3].mmdat'
|
||||||
|
|
||||||
|
Here's a sample seed file for a 256-bit wallet:
|
||||||
|
|
||||||
$ cat 8B7392ED.mmseed
|
$ cat 8B7392ED.mmseed
|
||||||
f4c84b C5ZT wWpT Jsoi wRVw 2dm9 Aftd WLb8 FggQ eC8h Szjd da9L
|
f4c84b C5ZT wWpT Jsoi wRVw 2dm9 Aftd WLb8 FggQ eC8h Szjd da9L
|
||||||
|
|
||||||
And for a 128-bit seed:
|
And for a 128-bit wallet:
|
||||||
|
|
||||||
$ cat 8E0DFB78.mmseed
|
$ cat 8E0DFB78.mmseed
|
||||||
0fe02f XnyC NfPH piuW dQ2d nM47 VU
|
0fe02f XnyC NfPH piuW dQ2d nM47 VU
|
||||||
|
|
||||||
As you can see, the latter file is short enough to be memorized or written down
|
As you can see, seed files are short enough to be easily written out by hand or
|
||||||
on a scrap of paper. From the unix command line, you can test your memory using
|
even memorized. And their built-in checksum makes it easy to test your memory
|
||||||
the seed's checksum ("0fe02f" in this example) as follows:
|
using a simple Unix shell command:
|
||||||
|
|
||||||
$ echo -n XnyC NfPH piuW dQ2d nM47 VU | tr -d ' ' |sha256sum |cut -c 1-6
|
$ echo -n XnyC NfPH piuW dQ2d nM47 VU | tr -d ' '| sha256sum | cut -c 1-6
|
||||||
0fe02f
|
0fe02f
|
||||||
|
|
||||||
Or better yet, use 'mmgen-tool' to do the same thing:
|
Or you can do the same thing with 'mmgen-tool':
|
||||||
|
|
||||||
$ mmgen-tool str2id6 'XnyC NfPH piuW dQ2d nM47 VU'
|
$ mmgen-tool str2id6 'XnyC NfPH piuW dQ2d nM47 VU'
|
||||||
0fe02f
|
0fe02f
|
||||||
|
|
||||||
#### <a name=12>Mnemonics and seeds — additional information:</a>
|
#### <a name=12>Mnemonics and seeds — additional information:</a>
|
||||||
|
|
||||||
With the '-m' or '-s' option, MMGen commands that take mnemonic and seed
|
MMGen commands that take mnemonic and seed data may receive the data from a
|
||||||
data may receive the data from a prompt instead of a file.
|
prompt instead of a file. Just omit the file name and specify the input format:
|
||||||
|
|
||||||
MMGen commands that produce mnemonic and seed data may be forced to print it to
|
$ mmgen-walletconv -i words
|
||||||
standard output instead of file with the '-S' option. This feature has
|
...
|
||||||
intentionally been made optional to safeguard against looking-over-the-shoulder,
|
Enter mnemonic data: <type or paste your mnemonic here>
|
||||||
Van Eyck phreaking and other side-channel attacks. MMGen commands never print
|
|
||||||
private data to the screen unless explicitly asked to.
|
With the '-S' option, MMGen commands may be requested to print wallet data to
|
||||||
|
screen instead of a file. To safeguard against over-the-shoulder, Van Eck
|
||||||
|
phreaking and other side-channel attacks, you'll be prompted before this
|
||||||
|
sensitive data is actually displayed. MMGen never prints unencrypted private
|
||||||
|
data to screen by default.
|
||||||
|
|
||||||
The output of any MMGen command may be written to a directory of your choice
|
The output of any MMGen command may be written to a directory of your choice
|
||||||
using the '-d' option. For example, on a Linux system you can use
|
using the '-d' option. For example, on a Linux system you can use
|
||||||
'-d /dev/shm' to write key and seed data to volatile memory instead of disk.
|
'-d /dev/shm' to write keys and seeds to volatile memory instead of disk,
|
||||||
This also has obvious security benefits, ensuring that no sensitive data
|
ensuring that no trace of this sensitive data remains once your computer's been
|
||||||
remains on disk after your computer's been powered down.
|
powered down.
|
||||||
|
|
||||||
#### <a name=13><a name=incog>Incognito wallets</a>
|
#### <a name=13><a name=incog>Incognito wallets</a>
|
||||||
|
|
||||||
A wallet exported to incognito format is indistinguishable from random data,
|
An incognito format wallet is indistinguishable from random data, allowing you
|
||||||
allowing you to hide your wallet at an offset within a random-filled file or
|
to hide your wallet at an offset within a random-data-filled file or partition.
|
||||||
partition. Thus both the location and nature of the data are unknown to a
|
Barring any inside knowledge, a potential attacker has no way of knowing where
|
||||||
potential attacker, who in addition cannot be sure that the file or partition
|
the wallet is hidden, or whether the file or partition contains anything of
|
||||||
contains anything useful at all, barring any inside knowledge.
|
interest at all for that matter.
|
||||||
|
|
||||||
An incognito wallet with a reasonably secure password could even be hidden on
|
An incognito wallet with a reasonably secure password could even be hidden on
|
||||||
unencrypted cloud storage. Hiding your wallet at some offset in a 1 GB file
|
unencrypted cloud storage. Hiding your wallet at some offset in a 1 GB file
|
||||||
increases the difficulty of any attack by a factor of one billion, assuming
|
increases the difficulty of any attack by a factor of one billion, assuming
|
||||||
a potential attacker even knows or suspects you have an MMGen wallet hidden
|
again that any potential attacker even knows or suspects you have an MMGen
|
||||||
there.
|
wallet hidden there.
|
||||||
|
|
||||||
If you plan to store your incognito wallet in an insecure location such as cloud
|
If you plan to store your incognito wallet in an insecure location such as cloud
|
||||||
storage, you're advised to use a strong scrypt preset and a strong password.
|
storage, you're advised to use a strong scrypt (hash) preset and a strong
|
||||||
These can be changed using the 'mmgen-passchg' utility:
|
password. These can be changed using the 'mmgen-passchg' utility:
|
||||||
|
|
||||||
$ mmgen-passchg -p 5 89ABCDEF-01234567[256,3].mmdat
|
$ mmgen-passchg -p 5 89ABCDEF-01234567[256,3].mmdat
|
||||||
...
|
...
|
||||||
Hash preset has changed (3 -> 5)
|
Hash preset of wallet: '3'
|
||||||
Enter new passphrase: <my new strong passphrase>
|
Enter old passphrase for MMGen wallet: <old weak passphrase>
|
||||||
...
|
...
|
||||||
Wallet saved to file '89ABCDEF-87654321[256,5].mmdat'
|
Hash preset changed to '5'
|
||||||
|
Enter new passphrase for MMGen wallet: <new strong passphrase>
|
||||||
The new scrypt preset is indicated by the numeral '5' after the comma in the new
|
|
||||||
wallet filename. Now export your new strengthened wallet to incognito format:
|
|
||||||
|
|
||||||
$ mmgen-walletchk -g 89ABCDEF-87654321[256,5].mmdat
|
|
||||||
...
|
...
|
||||||
Incognito wallet data saved to file '89ABCDEF-87654321-ECA86420[256,5].mmincog'
|
MMGen wallet written to file '89ABCDEF-87654321[256,5].mmdat'
|
||||||
|
|
||||||
'ECA86420' is the Incog ID. This can be used by the 'mmgen-tool' utility to
|
The scrypt preset is the numeral in the wallet filename following the seed
|
||||||
search through a file or partition and locate your wallet if you've forgotten
|
length. As you can see, it's now changed to '5'. Now export your new toughened
|
||||||
where you hid it (see below).
|
wallet to incognito format, using the '-k' option to leave the passphrase
|
||||||
|
unchanged:
|
||||||
|
|
||||||
Repeat the same export operation, but output to hexadecimal:
|
$ mmgen-walletconv -k -o incog 89ABCDEF-87654321[256,5].mmdat
|
||||||
|
|
||||||
$ mmgen-walletchk -X 89ABCDEF-87654321[256,5].mmdat
|
|
||||||
...
|
...
|
||||||
Incognito wallet data saved to file '89ABCDEF-87654321-CA86420E[256,5].mmincox'
|
Reusing passphrase at user request
|
||||||
|
...
|
||||||
|
New Incog Wallet ID: ECA86420
|
||||||
|
...
|
||||||
|
Incognito data written to file '89ABCDEF-87654321-ECA86420[256,5].mmincog'
|
||||||
|
|
||||||
|
Incog wallets have a special identifier, the Incog ID, which can be used to
|
||||||
|
locate the wallet data if you've forgotten where you hid it (see the example
|
||||||
|
below). Naturally, an attacker could use this ID to find the data too, so it
|
||||||
|
should be kept secret.
|
||||||
|
|
||||||
|
Incog wallets can also be output to hexadecimal format:
|
||||||
|
|
||||||
|
$ mmgen-walletconv -k -o incox 89ABCDEF-87654321[256,5].mmdat
|
||||||
|
...
|
||||||
|
Hex incognito data written to file '89ABCDEF-87654321-CA86420E[256,5].mmincox'
|
||||||
|
|
||||||
$ cat 89ABCDEF-87654321-1EE402F4[256,5].mmincox
|
$ cat 89ABCDEF-87654321-1EE402F4[256,5].mmincox
|
||||||
6772 edb2 10cf ad0d c7dd 484b cc7e 42e9
|
6772 edb2 10cf ad0d c7dd 484b cc7e 42e9
|
||||||
|
|
@ -424,72 +467,67 @@ Repeat the same export operation, but output to hexadecimal:
|
||||||
3706 c5ce 56e0 7590 e677 6c6e 750a d057
|
3706 c5ce 56e0 7590 e677 6c6e 750a d057
|
||||||
b43a 21f9 82c7 6bd1 fe96 bad9 2d54 c4c0
|
b43a 21f9 82c7 6bd1 fe96 bad9 2d54 c4c0
|
||||||
|
|
||||||
Note that the Incog ID is different here: it's generated from the init vector,
|
Note that the Incog ID is different here: it's generated from an init vector,
|
||||||
which is a different random number each time, making the incog data as a whole
|
which is a different random number each time, making the incog data as a whole
|
||||||
different as well. This allows you to store your incog data in multiple
|
different as well. This allows you to store your incog data in multiple
|
||||||
insecure locations without having repeated "random" wallet data give you away.
|
public locations without having repeated "random" wallet data give you away.
|
||||||
|
|
||||||
As you can see, this data is ideally suited for a paper wallet. Just print it
|
Indistinguishable from any random hex dump, this data is ideally suited for a
|
||||||
out on a printer and you're ready to go.
|
paper wallet that could potentially fall into the wrong hands.
|
||||||
|
|
||||||
Your incognito wallet (whether hex or binary) can be used just like any other
|
Your incognito wallet (whether hex or binary) can be used just like any other
|
||||||
MMGen wallet, mnemonic or seed file. Generate addresses with it like this:
|
MMGen wallet, mnemonic or seed file to generate addresses and sign transactions:
|
||||||
|
|
||||||
$ mmgen-addrgen 89ABCDEF-87654321-CA86420E[256,5].mmincox 100-110
|
$ mmgen-addrgen 89ABCDEF-87654321-CA86420E[256,5].mmincox 101-110
|
||||||
...
|
...
|
||||||
Generated 10 addresses
|
Generated 10 addresses
|
||||||
Addresses written to file '89ABCDEF[100-110].addrs'
|
Addresses written to file '89ABCDEF[101-110].addrs'
|
||||||
|
|
||||||
|
|
||||||
Or sign a transaction like this:
|
|
||||||
|
|
||||||
$ mmgen-txsign tx_FABCDE[0.3].raw 89ABCDEF-87654321-CA86420E[256,5].mmincox
|
$ mmgen-txsign tx_FABCDE[0.3].raw 89ABCDEF-87654321-CA86420E[256,5].mmincox
|
||||||
...
|
...
|
||||||
Signed transaction saved to file tx_FABCDE[0.3].sig
|
Signed transaction written to file tx_FABCDE[0.3].sig
|
||||||
|
|
||||||
You can create an incognito wallet and hide it at a specified offset in a file or
|
##### <a name=13a><a name=incog>Hidden incognito wallets</a>
|
||||||
partition in one convenient operation using the '-G' ('--export-incog-hidden')
|
|
||||||
option. Here's how you'd hide a wallet in a 1GB file filled with random data.
|
|
||||||
First create the random file:
|
|
||||||
|
|
||||||
$ dd if=/dev/urandom of=random.dat bs=1K count=1M
|
With the '-o hincog' option, incognito wallet data can be created and hidden at
|
||||||
|
a specified offset in a file or partition in a single convenient operation, with
|
||||||
|
the random file being created automatically if required. Here's how you'd
|
||||||
|
create a 1GB file 'random.dat' and hide a wallet in it at offset 123456789:
|
||||||
|
|
||||||
Or better yet, use 'mmgen-tool' to do the same job but with some additional user
|
$ mmgen-walletconv -k -o hincog -J random.dat,123456789 89ABCDEF-87654321[256,5].mmdat
|
||||||
entropy and a progress meter:
|
...
|
||||||
|
New Incog Wallet ID: ED1F2ACB
|
||||||
$ mmgen-tool -r40 rand2file random.dat 1G
|
...
|
||||||
|
Requested file 'random.dat' does not exist. Create? (Y/n): Y
|
||||||
Now export your wallet to hidden incognito format, hiding it in this 1GB random
|
Enter file size: 1G
|
||||||
file at offset 123456789:
|
|
||||||
|
|
||||||
$ mmgen-walletchk -G random.dat,123456789 89ABCDEF-87654321[256,5].mmdat
|
|
||||||
...
|
...
|
||||||
Incog ID: ED1F2ACB
|
|
||||||
Data written to file 'random.dat' at offset 123456789
|
Data written to file 'random.dat' at offset 123456789
|
||||||
|
|
||||||
The altered random file can now be uploaded to a cloud storage service, for
|
Your "random" file can now be uploaded to a cloud storage service, for example,
|
||||||
example, or some other, preferably non-public, location on the Net (in a
|
or some other, preferably non-public, location on the Net (in a real-life
|
||||||
real-life situation you'll choose a less obvious offset than '123456789'
|
situation you will choose a less obvious offset than '123456789' though, won't
|
||||||
though, won't you?).
|
you?).
|
||||||
|
|
||||||
If at some point in the future you download this file to recover your wallet
|
Now let's say at some point in the future you download this file to recover
|
||||||
data but find you've forgotten the offset, you can recover it using the saved
|
your wallet and realize you've forgotten the offset where the data is hidden.
|
||||||
Incog ID as follows:
|
If you've saved your Incog ID, you're in luck:
|
||||||
|
|
||||||
$ mmgen-tool find_incog_data random.dat ED1F2ACB
|
$ mmgen-tool find_incog_data random.dat ED1F2ACB
|
||||||
...
|
...
|
||||||
Incog data for ID ED1F2ACB found at offset 123456789
|
Incog data for ID ED1F2ACB found at offset 123456789
|
||||||
|
|
||||||
Hidden incog wallets are almost as convenient to use as ordinary ones.
|
The search process can be slow, so patience is required. In addition, on
|
||||||
|
large files 'false positives' are a distinct possibility, in which case you'll
|
||||||
|
need to use the 'keep_searching=1' parameter to keep going until you find the
|
||||||
|
real offset.
|
||||||
|
|
||||||
|
Hidden incog wallets are nearly as convenient to use as ordinary ones.
|
||||||
Generating ten addresses with your hidden incog data is as easy as this:
|
Generating ten addresses with your hidden incog data is as easy as this:
|
||||||
|
|
||||||
$ mmgen-addrgen -G random.dat,123456789,256 32-42
|
$ mmgen-addrgen -H random.dat,123456789 101-110
|
||||||
|
|
||||||
Use the same syntax to sign a transaction:
|
Transaction signing uses the same syntax:
|
||||||
|
|
||||||
$ mmgen-txsign -G random.dat,123456789,256 tx_ABCDEE[0.1].raw
|
$ mmgen-txsign -H random.dat,123456789 tx_ABCDEF[0.1].raw
|
||||||
...
|
...
|
||||||
Signed transaction saved to file tx_ABCDEE[0.1].sig
|
Signed transaction written to file 'tx_ABCDEF[0.1].sig'
|
||||||
|
|
||||||
Note that the seed length parameter here will always be '256' unless you're
|
|
||||||
using a non-default seed length.
|
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ from hashlib import new as hashlib_new
|
||||||
from binascii import hexlify, unhexlify
|
from binascii import hexlify, unhexlify
|
||||||
|
|
||||||
from mmgen.bitcoin import numtowif
|
from mmgen.bitcoin import numtowif
|
||||||
# from mmgen.util import msg,qmsg,qmsg_r,make_chksum_N,get_lines_from_file,get_data_from_file,get_extension
|
|
||||||
from mmgen.util import *
|
from mmgen.util import *
|
||||||
from mmgen.tx import *
|
from mmgen.tx import *
|
||||||
from mmgen.obj import *
|
from mmgen.obj import *
|
||||||
|
|
@ -367,10 +366,9 @@ class AddrInfo(MMGenObject):
|
||||||
|
|
||||||
|
|
||||||
def make_addrdata_chksum(self):
|
def make_addrdata_chksum(self):
|
||||||
nchars = 24
|
|
||||||
lines=[" ".join([str(e.idx),e.addr]+([e.wif] if self.has_keys else []))
|
lines=[" ".join([str(e.idx),e.addr]+([e.wif] if self.has_keys else []))
|
||||||
for e in self.addrdata]
|
for e in self.addrdata]
|
||||||
self.checksum = make_chksum_N(" ".join(lines), nchars, sep=True)
|
self.checksum = make_chksum_N(" ".join(lines), nchars=24, sep=True)
|
||||||
|
|
||||||
|
|
||||||
def fmt_data(self,enable_comments=False):
|
def fmt_data(self,enable_comments=False):
|
||||||
|
|
|
||||||
250
mmgen/crypto.py
250
mmgen/crypto.py
|
|
@ -30,15 +30,6 @@ from mmgen.util import *
|
||||||
from mmgen.term import get_char
|
from mmgen.term import get_char
|
||||||
|
|
||||||
crmsg = {
|
crmsg = {
|
||||||
'incog_iv_id': """
|
|
||||||
Check that the generated Incog ID above is correct.
|
|
||||||
If it's not, then your incognito data is incorrect or corrupted.
|
|
||||||
""",
|
|
||||||
'incog_iv_id_hidden': """
|
|
||||||
Check that the generated Incog ID above is correct.
|
|
||||||
If it's not, then your incognito data is incorrect or corrupted,
|
|
||||||
or you've supplied an incorrect offset.
|
|
||||||
""",
|
|
||||||
'usr_rand_notice': """
|
'usr_rand_notice': """
|
||||||
You've chosen to not fully trust your OS's random number generator and provide
|
You've chosen to not fully trust your OS's random number generator and provide
|
||||||
some additional entropy of your own. Please type %s symbols on your keyboard.
|
some additional entropy of your own. Please type %s symbols on your keyboard.
|
||||||
|
|
@ -47,14 +38,23 @@ use both upper and lowercase as well as punctuation and numerals. What you
|
||||||
type will not be displayed on the screen. Note that the timings between your
|
type will not be displayed on the screen. Note that the timings between your
|
||||||
keystrokes will also be used as a source of randomness.
|
keystrokes will also be used as a source of randomness.
|
||||||
""",
|
""",
|
||||||
'incorrect_incog_passphrase_try_again': """
|
# 'incog_iv_id': """
|
||||||
Incorrect passphrase, hash preset, or maybe old-format incog wallet.
|
# Check that the generated Incog ID above is correct.
|
||||||
Try again? (Y)es, (n)o, (m)ore information:
|
# If it's not, then your incognito data is incorrect or corrupted.
|
||||||
""".strip(),
|
# """,
|
||||||
'confirm_seed_id': """
|
# 'incog_iv_id_hidden': """
|
||||||
If the seed ID above is correct but you're seeing this message, then you need
|
# Check that the generated Incog ID above is correct.
|
||||||
to exit and re-run the program with the '--old-incog-fmt' option.
|
# If it's not, then your incognito data is incorrect or corrupted,
|
||||||
""".strip(),
|
# or you've supplied an incorrect offset.
|
||||||
|
# """,
|
||||||
|
# 'incorrect_incog_passphrase_try_again': """
|
||||||
|
# Incorrect passphrase, hash preset, or maybe old-format incog wallet.
|
||||||
|
# Try again? (Y)es, (n)o, (m)ore information:
|
||||||
|
# """.strip(),
|
||||||
|
# 'confirm_seed_id': """
|
||||||
|
# If the seed ID above is correct but you're seeing this message, then you need
|
||||||
|
# to exit and re-run the program with the '--old-incog-fmt' option.
|
||||||
|
# """.strip(),
|
||||||
}
|
}
|
||||||
|
|
||||||
def encrypt_seed(seed, key):
|
def encrypt_seed(seed, key):
|
||||||
|
|
@ -211,222 +211,6 @@ def get_random(length):
|
||||||
return os_rand
|
return os_rand
|
||||||
|
|
||||||
|
|
||||||
def get_seed_from_wallet(
|
|
||||||
infile,
|
|
||||||
desc="{pnm} wallet".format(pnm=g.proj_name),
|
|
||||||
silent=False
|
|
||||||
):
|
|
||||||
|
|
||||||
wdata = get_data_from_wallet(infile,silent=silent)
|
|
||||||
label,metadata,hash_preset,salt,enc_seed = wdata
|
|
||||||
|
|
||||||
if opt.debug: display_control_data(*wdata)
|
|
||||||
|
|
||||||
padd = " "+infile if opt.quiet else ""
|
|
||||||
passwd = get_mmgen_passphrase(desc+padd)
|
|
||||||
|
|
||||||
key = make_key(passwd, salt, hash_preset)
|
|
||||||
|
|
||||||
return decrypt_seed(enc_seed, key, metadata[0], metadata[1])
|
|
||||||
|
|
||||||
|
|
||||||
def get_hidden_incog_data():
|
|
||||||
# Already sanity-checked:
|
|
||||||
fname,offset,seed_len = opt.from_incog_hidden.split(",")
|
|
||||||
qmsg("Getting hidden incog data from file '%s'" % fname)
|
|
||||||
|
|
||||||
z = 0 if opt.old_incog_fmt else 8
|
|
||||||
dlen = g.aesctr_iv_len + g.salt_len + (int(seed_len)/8) + z
|
|
||||||
|
|
||||||
fsize = check_data_fits_file_at_offset(fname,int(offset),dlen,"read")
|
|
||||||
|
|
||||||
import os
|
|
||||||
f = os.open(fname,os.O_RDONLY)
|
|
||||||
os.lseek(f, int(offset), os.SEEK_SET)
|
|
||||||
data = os.read(f, dlen)
|
|
||||||
os.close(f)
|
|
||||||
qmsg("Data read from file '%s' at offset %s" % (fname,offset),
|
|
||||||
"Data read from file")
|
|
||||||
return data
|
|
||||||
|
|
||||||
def confirm_old_format():
|
|
||||||
|
|
||||||
while True:
|
|
||||||
reply = get_char(
|
|
||||||
crmsg['incorrect_incog_passphrase_try_again']+" ").strip("\n\r")
|
|
||||||
if not reply: msg(""); return False
|
|
||||||
elif reply in 'yY': msg(""); return False
|
|
||||||
elif reply in 'nN': msg("\nExiting at user request"); sys.exit(1)
|
|
||||||
elif reply in 'mM': msg(""); return True
|
|
||||||
else:
|
|
||||||
if opt.verbose: msg("\nInvalid reply")
|
|
||||||
else: msg_r("\r")
|
|
||||||
|
|
||||||
|
|
||||||
def get_seed_from_incog_wallet(
|
|
||||||
infile,
|
|
||||||
desc="{pnm} incognito wallet".format(pnm=g.proj_name),
|
|
||||||
silent=False,
|
|
||||||
hex_input=False
|
|
||||||
):
|
|
||||||
|
|
||||||
desc = "incognito wallet data"
|
|
||||||
|
|
||||||
if opt.from_incog_hidden:
|
|
||||||
d = get_hidden_incog_data()
|
|
||||||
else:
|
|
||||||
d = get_data_from_file(infile,desc)
|
|
||||||
if hex_input:
|
|
||||||
try:
|
|
||||||
d = unhexlify("".join(d.split()).strip())
|
|
||||||
except:
|
|
||||||
msg("Data in file '%s' is not in hexadecimal format" % infile)
|
|
||||||
sys.exit(2)
|
|
||||||
# File could be of invalid length, so check:
|
|
||||||
z = 0 if opt.old_incog_fmt else 8
|
|
||||||
valid_dlens = [i/8 + g.aesctr_iv_len + g.salt_len + z for i in g.seed_lens]
|
|
||||||
# New fmt: [56, 64, 72]. Old fmt: [48, 56, 64].
|
|
||||||
if len(d) not in valid_dlens:
|
|
||||||
vn = [i/8 + g.aesctr_iv_len + g.salt_len + 8 for i in g.seed_lens]
|
|
||||||
if len(d) in vn:
|
|
||||||
msg("Re-run the program without the '--old-incog-fmt' option")
|
|
||||||
sys.exit()
|
|
||||||
else: qmsg(
|
|
||||||
"Invalid incognito file size: %s. Valid sizes (in bytes): %s" %
|
|
||||||
(len(d), " ".join([str(i) for i in valid_dlens])))
|
|
||||||
return False
|
|
||||||
|
|
||||||
iv, enc_incog_data = d[0:g.aesctr_iv_len], d[g.aesctr_iv_len:]
|
|
||||||
|
|
||||||
incog_id = make_iv_chksum(iv)
|
|
||||||
msg("Incog ID: %s (IV ID: %s)" % (incog_id,make_chksum_8(iv)))
|
|
||||||
qmsg("Check the applicable value against your records")
|
|
||||||
vmsg(crmsg['incog_iv_id_hidden' if opt.from_incog_hidden
|
|
||||||
else 'incog_iv_id'])
|
|
||||||
|
|
||||||
while True:
|
|
||||||
passwd = get_mmgen_passphrase(desc+" "+incog_id)
|
|
||||||
|
|
||||||
qmsg("Configured hash presets: %s" % " ".join(sorted(g.hash_presets)))
|
|
||||||
if 'hash_preset' in opt.set_by_user:
|
|
||||||
hp = opt.hash_preset
|
|
||||||
else:
|
|
||||||
hp = get_hash_preset_from_user(desc="incog wallet")
|
|
||||||
|
|
||||||
# IV is used BOTH to initialize counter and to salt password!
|
|
||||||
key = make_key(passwd, iv, hp, "wrapper key")
|
|
||||||
d = decrypt_data(enc_incog_data, key, int(hexlify(iv),16), "incog data")
|
|
||||||
|
|
||||||
salt,enc_seed = d[0:g.salt_len], d[g.salt_len:]
|
|
||||||
|
|
||||||
key = make_key(passwd, salt, hp, "main key")
|
|
||||||
vmsg("Key ID: %s" % make_chksum_8(key))
|
|
||||||
|
|
||||||
seed = decrypt_seed(enc_seed, key, "", "")
|
|
||||||
old_fmt_sid = make_chksum_8(seed)
|
|
||||||
|
|
||||||
def confirm_correct_seed_id(sid):
|
|
||||||
m = "Seed ID: %s. Is the seed ID correct?" % sid
|
|
||||||
return keypress_confirm(m, True)
|
|
||||||
|
|
||||||
if opt.old_incog_fmt:
|
|
||||||
if confirm_correct_seed_id(old_fmt_sid):
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
chk,seed_maybe = seed[:8],seed[8:]
|
|
||||||
if sha256(seed_maybe).digest()[:8] == chk:
|
|
||||||
msg("Passphrase and hash preset are correct")
|
|
||||||
seed = seed_maybe
|
|
||||||
break
|
|
||||||
elif confirm_old_format():
|
|
||||||
if confirm_correct_seed_id(old_fmt_sid):
|
|
||||||
break
|
|
||||||
|
|
||||||
msg("Valid incog data for seed ID %s" % make_chksum_8(seed))
|
|
||||||
return seed
|
|
||||||
|
|
||||||
|
|
||||||
def _get_seed(infile,silent=False,seed_id=""):
|
|
||||||
|
|
||||||
ext = get_extension(infile)
|
|
||||||
|
|
||||||
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 ext == g.incog_ext: source = "incognito wallet"
|
|
||||||
elif ext == g.incog_hex_ext: source = "incognito wallet"
|
|
||||||
elif opt.from_mnemonic : source = "mnemonic"
|
|
||||||
elif opt.from_brain : source = "brainwallet"
|
|
||||||
elif opt.from_seed : source = "seed"
|
|
||||||
elif opt.from_incog : source = "incognito wallet"
|
|
||||||
else:
|
|
||||||
if infile: msg(
|
|
||||||
"Invalid file extension for file: %s\nValid extensions: '.%s'" %
|
|
||||||
(infile, "', '.".join(g.seedfile_exts)))
|
|
||||||
else: msg("No seed source type specified and no file supplied")
|
|
||||||
sys.exit(2)
|
|
||||||
|
|
||||||
seed_id_str = " for seed ID "+seed_id if seed_id else ""
|
|
||||||
if source == "mnemonic":
|
|
||||||
prompt = "Enter mnemonic%s: " % seed_id_str
|
|
||||||
words = get_words(infile,"mnemonic data",prompt)
|
|
||||||
wl = get_default_wordlist()
|
|
||||||
from mmgen.mnemonic import get_seed_from_mnemonic
|
|
||||||
seed = get_seed_from_mnemonic(words,wl)
|
|
||||||
elif source == "brainwallet":
|
|
||||||
if not opt.from_brain:
|
|
||||||
msg("'--from-brain' parameters must be specified for brainwallet file")
|
|
||||||
sys.exit(2)
|
|
||||||
prompt = "Enter brainwallet passphrase%s: " % seed_id_str
|
|
||||||
words = get_words(infile,"brainwallet data",prompt)
|
|
||||||
seed = _get_seed_from_brain_passphrase(words)
|
|
||||||
elif source == "seed":
|
|
||||||
prompt = "Enter seed%s in %s format: " % (seed_id_str,g.seed_ext)
|
|
||||||
words = get_words(infile,"seed data",prompt)
|
|
||||||
seed = get_seed_from_seed_data(words)
|
|
||||||
elif source == "wallet":
|
|
||||||
seed = get_seed_from_wallet(infile, silent=silent)
|
|
||||||
elif source == "incognito wallet":
|
|
||||||
h = (ext == g.incog_hex_ext) or opt.from_incog_hex
|
|
||||||
seed = get_seed_from_incog_wallet(infile, silent=silent, hex_input=h)
|
|
||||||
|
|
||||||
|
|
||||||
if infile and not seed and (
|
|
||||||
source == "seed" or source == "mnemonic" or source == "incognito wallet"):
|
|
||||||
msg("Invalid %s file '%s'" % (source,infile))
|
|
||||||
sys.exit(2)
|
|
||||||
|
|
||||||
dmsg("Seed: %s" % hexlify(seed))
|
|
||||||
|
|
||||||
return seed
|
|
||||||
|
|
||||||
|
|
||||||
# Repeat if entered data is invalid
|
|
||||||
def get_seed_retry(infile,seed_id=""):
|
|
||||||
silent = False
|
|
||||||
while True:
|
|
||||||
seed = _get_seed(infile,silent=silent,seed_id=seed_id)
|
|
||||||
silent = True
|
|
||||||
if seed: return seed
|
|
||||||
|
|
||||||
|
|
||||||
def _get_seed_from_brain_passphrase(words,silent=False):
|
|
||||||
bp = " ".join(words)
|
|
||||||
dmsg("Sanitized brain passphrase: %s" % bp)
|
|
||||||
seed_len,hash_preset = get_from_brain_opt_params()
|
|
||||||
dmsg("Brainwallet l = %s, p = %s" % (seed_len,hash_preset))
|
|
||||||
vmsg_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)
|
|
||||||
vmsg("Done")
|
|
||||||
|
|
||||||
if not silent:
|
|
||||||
msg("Valid brainwallet for seed ID %s" % make_chksum_8(seed))
|
|
||||||
|
|
||||||
return seed
|
|
||||||
|
|
||||||
|
|
||||||
# Vars for mmgen_*crypt functions only
|
# Vars for mmgen_*crypt functions only
|
||||||
salt_len,sha256_len,nonce_len = 32,32,32
|
salt_len,sha256_len,nonce_len = 32,32,32
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ from mmgen.util import die,get_extension,check_infile
|
||||||
|
|
||||||
class Filename(MMGenObject):
|
class Filename(MMGenObject):
|
||||||
|
|
||||||
def __init__(self,fn,ftype=None):
|
def __init__(self,fn,ftype=None,write=False):
|
||||||
self.name = fn
|
self.name = fn
|
||||||
self.dirname = os.path.dirname(fn)
|
self.dirname = os.path.dirname(fn)
|
||||||
self.basename = os.path.basename(fn)
|
self.basename = os.path.basename(fn)
|
||||||
|
|
@ -41,10 +41,17 @@ class Filename(MMGenObject):
|
||||||
die(2,"Unrecognized extension '.%s' for file '%s'" % (self.ext,fn))
|
die(2,"Unrecognized extension '.%s' for file '%s'" % (self.ext,fn))
|
||||||
|
|
||||||
# TODO: Check for Windows
|
# TODO: Check for Windows
|
||||||
|
mode = (os.O_RDONLY,os.O_RDWR)[int(write)]
|
||||||
import stat
|
import stat
|
||||||
if stat.S_ISBLK(os.stat(fn).st_mode):
|
if stat.S_ISBLK(os.stat(fn).st_mode):
|
||||||
fd = os.open(fn, os.O_RDONLY)
|
try:
|
||||||
self.size = os.lseek(fd, 0, os.SEEK_END)
|
fd = os.open(fn, mode)
|
||||||
os.close(fd)
|
except OSError as e:
|
||||||
|
if e.errno == 13:
|
||||||
|
die(2,"'%s': permission denied" % fn)
|
||||||
|
# if e.errno != 17: raise
|
||||||
|
else:
|
||||||
|
self.size = os.lseek(fd, 0, os.SEEK_END)
|
||||||
|
os.close(fd)
|
||||||
else:
|
else:
|
||||||
self.size = os.stat(fn).st_size
|
self.size = os.stat(fn).st_size
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ prog_name = os.path.basename(sys.argv[0])
|
||||||
author = "Philemon"
|
author = "Philemon"
|
||||||
email = "<mmgen-py@yandex.com>"
|
email = "<mmgen-py@yandex.com>"
|
||||||
Cdates = '2013-2015'
|
Cdates = '2013-2015'
|
||||||
version = '0.8.0'
|
version = '0.8.1rc1'
|
||||||
|
|
||||||
required_opts = [
|
required_opts = [
|
||||||
"quiet","verbose","debug","outdir","echo_passphrase","passwd_file",
|
"quiet","verbose","debug","outdir","echo_passphrase","passwd_file",
|
||||||
|
|
|
||||||
|
|
@ -22,14 +22,9 @@ main.py - Script launcher for the MMGen suite
|
||||||
|
|
||||||
def launch(what):
|
def launch(what):
|
||||||
|
|
||||||
import os
|
if what in ("walletgen","walletchk","walletconv","passchg"):
|
||||||
t = "MMGEN_USE_OLD_SCRIPTS"
|
what = "wallet"
|
||||||
if not (t in os.environ and os.environ[t]):
|
if what == "keygen": what = "addrgen"
|
||||||
if what in ("walletgen","walletchk","passchg"):
|
|
||||||
what = "wallet"
|
|
||||||
|
|
||||||
if what == "walletconv": what = "wallet"
|
|
||||||
if what == "keygen": what = "addrgen"
|
|
||||||
|
|
||||||
try: import termios
|
try: import termios
|
||||||
except: __import__("mmgen.main_" + what) # Windows
|
except: __import__("mmgen.main_" + what) # Windows
|
||||||
|
|
|
||||||
|
|
@ -28,48 +28,50 @@ import mmgen.opt as opt
|
||||||
from mmgen.util import *
|
from mmgen.util import *
|
||||||
from mmgen.crypto import *
|
from mmgen.crypto import *
|
||||||
from mmgen.addr import *
|
from mmgen.addr import *
|
||||||
|
from mmgen.seed import SeedSource
|
||||||
|
|
||||||
if sys.argv[0].split("-")[-1] == "keygen":
|
if sys.argv[0].split("-")[-1] == "keygen":
|
||||||
gen_what = "keys"
|
gen_what = "keys"
|
||||||
opt_filter = None
|
opt_filter = None
|
||||||
|
note1 = """
|
||||||
|
By default, both addresses and secret keys are generated.
|
||||||
|
""".strip()
|
||||||
else:
|
else:
|
||||||
gen_what = "addresses"
|
gen_what = "addresses"
|
||||||
opt_filter = "hdceHKlpPqSvbgXGoms"
|
opt_filter = "hcdeiHOKlpzPqSv"
|
||||||
|
note1 = """
|
||||||
|
If available, the external 'keyconv' program will be used for address
|
||||||
|
generation.
|
||||||
|
""".strip()
|
||||||
|
|
||||||
opts_data = {
|
opts_data = {
|
||||||
|
'sets': [('print_checksum',True,'quiet',True)],
|
||||||
'desc': """Generate a range or list of {what} from an {pnm} wallet,
|
'desc': """Generate a range or list of {what} from an {pnm} wallet,
|
||||||
mnemonic, seed or password""".format(what=gen_what,pnm=g.proj_name),
|
mnemonic, seed or password""".format(what=gen_what,pnm=g.proj_name),
|
||||||
'usage':"[opts] [infile] <address range or list>",
|
'usage':"[opts] [infile] <address range or list>",
|
||||||
'options': """
|
'options': """
|
||||||
-h, --help Print this help message
|
-h, --help Print this help message.
|
||||||
-A, --no-addresses Print only secret keys, no addresses
|
-A, --no-addresses Print only secret keys, no addresses.
|
||||||
-d, --outdir= d Specify an alternate directory 'd' for output
|
-c, --print-checksum Print address list checksum and exit.
|
||||||
-c, --save-checksum Save address list checksum to file
|
-d, --outdir= d Output files to directory 'd' instead of working dir.
|
||||||
-e, --echo-passphrase Echo passphrase or mnemonic to screen upon entry
|
-e, --echo-passphrase Echo passphrase or mnemonic to screen upon entry.
|
||||||
-H, --show-hash-presets Show information on available hash presets
|
-i, --in-fmt= f Input is from wallet format 'f' (see FMT CODES below).
|
||||||
-K, --no-keyconv Force use of internal libraries for address gener-
|
-H, --hidden-incog-input-params=f,o Read hidden incognito data from file
|
||||||
ation, even if 'keyconv' is available
|
'f' at offset 'o' (comma-separated).
|
||||||
-l, --seed-len= N Length of seed. Options: {seed_lens}
|
-O, --old-incog-fmt Specify old-format incognito input.
|
||||||
(default: {g.seed_len})
|
-K, --no-keyconv Force use of internal libraries for address genera-
|
||||||
-p, --hash-preset= p Use scrypt.hash() parameters from preset 'p' when
|
tion, even if 'keyconv' is available.
|
||||||
hashing password (default: '{g.hash_preset}')
|
-l, --seed-len= l Specify wallet seed length of 'l' bits. This option
|
||||||
-P, --passwd-file= f Get {pnm} wallet passphrase from file 'f'
|
is required only for brainwallet and incognito inputs
|
||||||
-q, --quiet Suppress warnings; overwrite files without
|
with non-standard (< {g.seed_len}-bit) seed lengths.
|
||||||
prompting
|
-p, --hash-preset= p Use the scrypt hash parameters defined by preset 'p'
|
||||||
-S, --stdout Print {what} to stdout
|
for password hashing (default: '{g.hash_preset}').
|
||||||
-v, --verbose Produce more verbose output
|
-z, --show-hash-presets Show information on available hash presets.
|
||||||
-x, --b16 Print secret keys in hexadecimal too
|
-P, --passwd-file= f Get wallet passphrase from file 'f'.
|
||||||
|
-q, --quiet Produce quieter output; suppress some warnings.
|
||||||
-b, --from-brain= l,p Generate {what} from a user-created password,
|
-S, --stdout Print {what} to stdout.
|
||||||
i.e. a "brainwallet", using seed length 'l' and
|
-v, --verbose Produce more verbose output.
|
||||||
hash preset 'p' (comma-separated)
|
-x, --b16 Print secret keys in hexadecimal too.
|
||||||
-g, --from-incog Generate {what} from an incognito wallet
|
|
||||||
-X, --from-incog-hex Generate {what} from incognito hexadecimal wallet
|
|
||||||
-G, --from-incog-hidden=f,o,l Generate {what} from incognito data in file
|
|
||||||
'f' at offset 'o', with seed length of 'l'
|
|
||||||
-o, --old-incog-fmt Use old (pre-0.7.8) incog format
|
|
||||||
-m, --from-mnemonic Generate {what} from an electrum-like mnemonic
|
|
||||||
-s, --from-seed Generate {what} from a seed in .{g.seed_ext} format
|
|
||||||
""".format(
|
""".format(
|
||||||
seed_lens=", ".join([str(i) for i in g.seed_lens]),
|
seed_lens=", ".join([str(i) for i in g.seed_lens]),
|
||||||
pnm=g.proj_name,
|
pnm=g.proj_name,
|
||||||
|
|
@ -78,96 +80,63 @@ opts_data = {
|
||||||
'notes': """
|
'notes': """
|
||||||
|
|
||||||
Addresses are given in a comma-separated list. Hyphen-separated ranges are
|
Addresses are given in a comma-separated list. Hyphen-separated ranges are
|
||||||
also allowed.{a}
|
also allowed.
|
||||||
|
|
||||||
If available, the external 'keyconv' program will be used for address
|
{n}
|
||||||
generation.
|
|
||||||
|
|
||||||
Data for the --from-<what> options will be taken from <infile> if <infile>
|
{o.pw_note}
|
||||||
is specified. Otherwise, the user will be prompted to enter the data.
|
|
||||||
|
|
||||||
For passphrases all combinations of whitespace are equal, and leading and
|
{o.bw_note}
|
||||||
trailing space are ignored. This permits reading passphrase data from a
|
|
||||||
multi-line file with free spacing and indentation. This is particularly
|
|
||||||
convenient for long brainwallet passphrases, for example.
|
|
||||||
|
|
||||||
BRAINWALLET NOTE:
|
FMT CODES:
|
||||||
|
{f}
|
||||||
As brainwallets require especially strong hashing to thwart dictionary
|
""".format(
|
||||||
attacks, the brainwallet hash preset must be specified by the user, using
|
n=note1,
|
||||||
the 'p' parameter of the '--from-brain' option
|
f="\n ".join(SeedSource.format_fmt_codes().split("\n")),
|
||||||
|
o=opt.opts
|
||||||
The '--from-brain' option also requires the user to specify a seed length
|
)
|
||||||
(the 'l' parameter)
|
|
||||||
|
|
||||||
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(a="\n\nBy default, both addresses and secret keys are generated."
|
|
||||||
if gen_what == "keys" else "")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
wmsg = {
|
|
||||||
'unencrypted_secret_keys': """
|
|
||||||
This program generates secret keys from your {pnm} seed, outputting them in
|
|
||||||
UNENCRYPTED form. Generate only the key(s) you need and guard them carefully.
|
|
||||||
""".format(pnm=g.proj_name),
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd_args = opt.opts.init(opts_data,add_opts=["b16"],opt_filter=opt_filter)
|
cmd_args = opt.opts.init(opts_data,add_opts=["b16"],opt_filter=opt_filter)
|
||||||
|
|
||||||
if opt.from_incog_hex or opt.from_incog_hidden: opt.from_incog = True
|
nargs = 2
|
||||||
|
if len(cmd_args) < nargs \
|
||||||
|
and not opt.hidden_incog_input_params and not opt.in_fmt:
|
||||||
|
opt.opts.usage()
|
||||||
|
elif len(cmd_args) > nargs \
|
||||||
|
or (len(cmd_args) == nargs and opt.hidden_incog_input_params):
|
||||||
|
opt.opts.usage()
|
||||||
|
|
||||||
if len(cmd_args) == 1 and any([
|
addrlist_arg = cmd_args.pop()
|
||||||
opt.from_mnemonic,opt.from_brain,opt.from_seed,opt.from_incog_hidden]):
|
addr_idxs = parse_addr_idxs(addrlist_arg)
|
||||||
infile,addr_idx_arg = "",cmd_args[0]
|
if not addr_idxs:
|
||||||
elif len(cmd_args) == 2:
|
die(1,"'%s': invalid address list argument" % addrlist_arg)
|
||||||
infile,addr_idx_arg = cmd_args
|
|
||||||
check_infile(infile)
|
|
||||||
else: opt.opts.usage()
|
|
||||||
|
|
||||||
addr_idxs = parse_addr_idxs(addr_idx_arg)
|
|
||||||
|
|
||||||
if not addr_idxs: sys.exit(2)
|
|
||||||
|
|
||||||
do_license_msg()
|
do_license_msg()
|
||||||
|
|
||||||
# Interact with user:
|
opt.gen_what = "a" if gen_what == "addresses" \
|
||||||
if gen_what == "keys" and not opt.quiet:
|
else "k" if opt.no_addresses else "ka"
|
||||||
confirm_or_exit(wmsg['unencrypted_secret_keys'], 'continue')
|
|
||||||
|
|
||||||
# Generate data:
|
# Generate data:
|
||||||
|
ss = SeedSource(*cmd_args)
|
||||||
|
|
||||||
seed = get_seed_retry(infile)
|
ainfo = generate_addrs(ss.seed.data,addr_idxs)
|
||||||
|
|
||||||
opt.gen_what = "a" if gen_what == "addresses" else (
|
|
||||||
"k" if opt.no_addresses else "ka")
|
|
||||||
|
|
||||||
ainfo = generate_addrs(seed,addr_idxs)
|
|
||||||
|
|
||||||
addrdata_str = ainfo.fmt_data()
|
addrdata_str = ainfo.fmt_data()
|
||||||
outfile_base = "{}[{}]".format(make_chksum_8(seed), ainfo.idxs_fmt)
|
outfile_base = "{}[{}]".format(ss.seed.sid, ainfo.idxs_fmt)
|
||||||
|
|
||||||
if 'a' in opt.gen_what and opt.save_checksum:
|
if 'a' in opt.gen_what and opt.print_checksum:
|
||||||
w = "key-address" if 'k' in opt.gen_what else "address"
|
Die(0,ainfo.checksum)
|
||||||
write_to_file(outfile_base+"."+g.addrfile_chksum_ext,
|
|
||||||
ainfo.checksum+"\n","%s data checksum" % w,True,True,False)
|
|
||||||
|
|
||||||
if 'k' in opt.gen_what and keypress_confirm("Encrypt key list?"):
|
if 'k' in opt.gen_what and keypress_confirm("Encrypt key list?"):
|
||||||
addrdata_str = mmgen_encrypt(addrdata_str,"new key list","")
|
addrdata_str = mmgen_encrypt(addrdata_str,"new key list","")
|
||||||
enc_ext = "." + g.mmenc_ext
|
enc_ext = "." + g.mmenc_ext
|
||||||
else: enc_ext = ""
|
else: enc_ext = ""
|
||||||
|
|
||||||
# Output data:
|
ext = (g.keyfile_ext,g.keyaddrfile_ext)[int("ka" in opt.gen_what)]
|
||||||
if opt.stdout or not sys.stdout.isatty():
|
ext = (g.addrfile_ext,ext)[int("k" in opt.gen_what)]
|
||||||
if enc_ext and sys.stdout.isatty():
|
outfile = "%s.%s%s" % (outfile_base, ext, enc_ext)
|
||||||
msg("Cannot write encrypted data to screen. Exiting")
|
ask_tty = "k" in opt.gen_what and not opt.quiet
|
||||||
sys.exit(2)
|
if gen_what == "keys": gen_what = "secret keys"
|
||||||
write_to_stdout(addrdata_str,gen_what,ask_terminal=(gen_what=="keys"
|
write_data_to_file(outfile,addrdata_str,gen_what,ask_tty=ask_tty)
|
||||||
and not opt.quiet and sys.stdout.isatty()))
|
|
||||||
else:
|
|
||||||
outfile = "%s.%s%s" % (outfile_base, (
|
|
||||||
g.keyaddrfile_ext if "ka" in opt.gen_what else (
|
|
||||||
g.keyfile_ext if "k" in opt.gen_what else
|
|
||||||
g.addrfile_ext)), enc_ext)
|
|
||||||
write_to_file(outfile,addrdata_str,gen_what,not opt.quiet,True)
|
|
||||||
|
|
|
||||||
|
|
@ -1,124 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
#
|
|
||||||
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
|
|
||||||
# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
"""
|
|
||||||
mmgen-passchg: Change an MMGen deterministic wallet's passphrase, label or
|
|
||||||
hash preset
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
from mmgen.util import *
|
|
||||||
from mmgen.crypto import *
|
|
||||||
import mmgen.globalvars as g
|
|
||||||
import mmgen.opt as opt
|
|
||||||
|
|
||||||
opts_data = {
|
|
||||||
'desc': """Change the passphrase, hash preset or label of an {pnm}
|
|
||||||
deterministic wallet""".format(pnm=g.proj_name),
|
|
||||||
'usage': "[opts] [filename]",
|
|
||||||
'options': """
|
|
||||||
-h, --help Print this help message
|
|
||||||
-d, --outdir= d Specify an alternate directory 'd' for output
|
|
||||||
-H, --show-hash-presets Show information on available hash presets
|
|
||||||
-k, --keep-old-passphrase Keep old passphrase (use when changing hash
|
|
||||||
strength or label only)
|
|
||||||
-L, --label= l Change the wallet's label to 'l'
|
|
||||||
-p, --hash-preset= p Change scrypt.hash() parameters to preset 'p'
|
|
||||||
(default: '{g.hash_preset}')
|
|
||||||
-P, --passwd-file= f Get new {pnm} wallet passphrase from file 'f'
|
|
||||||
-r, --usr-randchars= n Get 'n' characters of additional randomness from
|
|
||||||
user (min={g.min_urandchars}, max={g.max_urandchars})
|
|
||||||
-q, --quiet Suppress warnings; overwrite files without
|
|
||||||
prompting
|
|
||||||
-v, --verbose Produce more verbose output
|
|
||||||
""".format(g=g,pnm=g.proj_name),
|
|
||||||
'notes': """
|
|
||||||
|
|
||||||
NOTE: The key ID will change if either the passphrase or hash preset are
|
|
||||||
changed
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd_args = opt.opts.init(opts_data)
|
|
||||||
|
|
||||||
if len(cmd_args) != 1:
|
|
||||||
msg("One input file must be specified")
|
|
||||||
sys.exit(2)
|
|
||||||
infile = cmd_args[0]
|
|
||||||
|
|
||||||
# Old key:
|
|
||||||
label,metadata,hash_preset,salt,enc_seed = get_data_from_wallet(infile)
|
|
||||||
seed_id,key_id = metadata[:2]
|
|
||||||
|
|
||||||
# Repeat on incorrect pw entry
|
|
||||||
while True:
|
|
||||||
desc = "{pnm} wallet".format(pnm=g.proj_name)
|
|
||||||
passwd = get_mmgen_passphrase(desc,not opt.keep_old_passphrase)
|
|
||||||
key = make_key(passwd, salt, hash_preset)
|
|
||||||
seed = decrypt_seed(enc_seed, key, seed_id, key_id)
|
|
||||||
if seed: break
|
|
||||||
|
|
||||||
changed = {}
|
|
||||||
|
|
||||||
if opt.label:
|
|
||||||
if opt.label != label:
|
|
||||||
msg("Label changed: '%s' -> '%s'" % (label, opt.label))
|
|
||||||
changed['label'] = True
|
|
||||||
else:
|
|
||||||
msg("Label is unchanged: '%s'" % (label))
|
|
||||||
else: opt.label = label # Copy the old label
|
|
||||||
|
|
||||||
if opt.hash_preset:
|
|
||||||
if hash_preset != opt.hash_preset:
|
|
||||||
qmsg("Hash preset changed: '%s' -> '%s'" %
|
|
||||||
(hash_preset, opt.hash_preset))
|
|
||||||
changed['preset'] = True
|
|
||||||
else:
|
|
||||||
msg("Hash preset is unchanged")
|
|
||||||
else:
|
|
||||||
opt.hash_preset = hash_preset
|
|
||||||
|
|
||||||
if opt.keep_old_passphrase:
|
|
||||||
msg("Keeping old passphrase by user request")
|
|
||||||
else:
|
|
||||||
new_passwd = get_new_passphrase(
|
|
||||||
"{pnm} wallet".format(pnm=g.proj_name), True)
|
|
||||||
|
|
||||||
if new_passwd == passwd:
|
|
||||||
qmsg("Passphrase is unchanged")
|
|
||||||
else:
|
|
||||||
qmsg("Passphrase has changed")
|
|
||||||
passwd = new_passwd
|
|
||||||
changed['passwd'] = True
|
|
||||||
|
|
||||||
if 'preset' in changed or 'passwd' in changed: # Update key ID, salt
|
|
||||||
qmsg("Will update salt and key ID")
|
|
||||||
|
|
||||||
from hashlib import sha256
|
|
||||||
|
|
||||||
salt = sha256(salt + get_random(128)).digest()[:g.salt_len]
|
|
||||||
key = make_key(passwd, salt, opt.hash_preset)
|
|
||||||
new_key_id = make_chksum_8(key)
|
|
||||||
qmsg("Key ID changed: %s -> %s" % (key_id,new_key_id))
|
|
||||||
key_id = new_key_id
|
|
||||||
enc_seed = encrypt_seed(seed, key)
|
|
||||||
elif not 'label' in changed:
|
|
||||||
msg("Data unchanged. No file will be written")
|
|
||||||
sys.exit(2)
|
|
||||||
|
|
||||||
write_wallet_to_file(seed, passwd, key_id, salt, enc_seed)
|
|
||||||
|
|
@ -26,44 +26,50 @@ import mmgen.globalvars as g
|
||||||
import mmgen.opt as opt
|
import mmgen.opt as opt
|
||||||
from mmgen.tx import *
|
from mmgen.tx import *
|
||||||
from mmgen.util import do_license_msg,dmsg
|
from mmgen.util import do_license_msg,dmsg
|
||||||
|
from mmgen.seed import SeedSource
|
||||||
|
|
||||||
|
|
||||||
pnm = g.proj_name
|
pnm = g.proj_name
|
||||||
pnl = pnm.lower()
|
pnl = pnm.lower()
|
||||||
|
|
||||||
opts_data = {
|
opts_data = {
|
||||||
'desc': "Sign Bitcoin transactions generated by {pnl}-txcreate".format(pnl=pnl),
|
'desc': "Sign Bitcoin transactions generated by {pnl}-txcreate".format(pnl=pnl),
|
||||||
'usage': "[opts] <transaction file> .. [mmgen wallet/seed/words/brainwallet file] ..",
|
'usage': "[opts] <transaction file>... [seed source]...",
|
||||||
'options': """
|
'options': """
|
||||||
-h, --help Print this help message
|
-h, --help Print this help message.
|
||||||
-d, --outdir= d Specify an alternate directory 'd' for output
|
-b, --brain-params=l,p Use seed length 'l' and hash preset 'p' for brain-
|
||||||
-e, --echo-passphrase Print passphrase to screen when typing it
|
wallet input. Required only if these parameters dif-
|
||||||
-i, --info Display information about the transaction and exit
|
fer from those of an incognito wallet also being used
|
||||||
-t, --terse-info Like '--info', but produce more concise output
|
as a seed source.
|
||||||
-I, --tx-id Display transaction ID and exit
|
-d, --outdir= d Specify an alternate directory 'd' for output.
|
||||||
-k, --keys-from-file= f Provide additional keys for non-{pnm} addresses
|
-D, --tx-id Display transaction ID and exit.
|
||||||
-K, --no-keyconv Force use of internal libraries for address gener-
|
-e, --echo-passphrase Print passphrase to screen when typing it.
|
||||||
ation, even if 'keyconv' is available
|
-i, --in-fmt= f Input is from wallet format 'f' (see FMT CODES below).
|
||||||
-M, --mmgen-keys-from-file=f Provide keys for {pnm} addresses in a key-
|
-H, --hidden-incog-input-params=f,o Read hidden incognito data from file
|
||||||
address file (output of '{pnl}-keygen'). Permits
|
'f' at offset 'o' (comma-separated).
|
||||||
online signing without an {pnm} seed source.
|
-O, --old-incog-fmt Specify old-format incognito input.
|
||||||
The key-address file is also used to verify
|
-l, --seed-len= l Specify wallet seed length of 'l' bits. This option
|
||||||
{pnm}-to-BTC mappings, so its checksum should
|
is required only for brainwallet and incognito inputs
|
||||||
be recorded by the user.
|
with non-standard (< {g.seed_len}-bit) seed lengths.
|
||||||
-P, --passwd-file= f Get {pnm} wallet or bitcoind passphrase from file 'f'
|
-p, --hash-preset=p Use the scrypt hash parameters defined by preset 'p'
|
||||||
-q, --quiet Suppress warnings; overwrite files without
|
for password hashing (default: '{g.hash_preset}').
|
||||||
prompting
|
-z, --show-hash-presets Show information on available hash presets.
|
||||||
-v, --verbose Produce more verbose output
|
-k, --keys-from-file=f Provide additional keys for non-{pnm} addresses
|
||||||
-b, --from-brain= l,p Generate keys from a user-created password,
|
-K, --no-keyconv Force use of internal libraries for address gener-
|
||||||
i.e. a "brainwallet", using seed length 'l' and
|
ation, even if 'keyconv' is available.
|
||||||
hash preset 'p'
|
-M, --mmgen-keys-from-file=f Provide keys for {pnm} addresses in a key-
|
||||||
-w, --use-wallet-dat Get keys from a running bitcoind
|
address file (output of '{pnl}-keygen'). Permits
|
||||||
-g, --from-incog Generate keys from an incognito wallet
|
online signing without an {pnm} seed source.
|
||||||
-X, --from-incog-hex Generate keys from an incognito hexadecimal wallet
|
The key-address file is also used to verify
|
||||||
-G, --from-incog-hidden= f,o,l Generate keys from incognito data in file
|
{pnm}-to-BTC mappings, so its checksum should
|
||||||
'f' at offset 'o', with seed length of 'l'
|
be recorded by the user.
|
||||||
-o, --old-incog-fmt Use old (pre-0.7.8) incog format
|
-P, --passwd-file= f Get {pnm} wallet or bitcoind passphrase from file 'f'
|
||||||
-m, --from-mnemonic Generate keys from an electrum-like mnemonic
|
-q, --quiet Suppress warnings; overwrite files without
|
||||||
-s, --from-seed Generate keys from a seed in .{g.seed_ext} format
|
prompting
|
||||||
|
-I, --info Display information about the transaction and exit.
|
||||||
|
-t, --terse-info Like '--info', but produce more concise output.
|
||||||
|
-v, --verbose Produce more verbose output
|
||||||
|
-w, --use-wallet-dat Get keys from a running bitcoind
|
||||||
""".format(g=g,pnm=pnm,pnl=pnl),
|
""".format(g=g,pnm=pnm,pnl=pnl),
|
||||||
'notes': """
|
'notes': """
|
||||||
|
|
||||||
|
|
@ -91,7 +97,13 @@ Seed data supplied in files must have the following extensions:
|
||||||
seed: '.{g.seed_ext}'
|
seed: '.{g.seed_ext}'
|
||||||
mnemonic: '.{g.mn_ext}'
|
mnemonic: '.{g.mn_ext}'
|
||||||
brainwallet: '.{g.brain_ext}'
|
brainwallet: '.{g.brain_ext}'
|
||||||
""".format(g=g,pnm=pnm,pnl=pnl)
|
|
||||||
|
FMT CODES:
|
||||||
|
{f}
|
||||||
|
""".format(
|
||||||
|
f="\n ".join(SeedSource.format_fmt_codes().split("\n")),
|
||||||
|
g=g,pnm=pnm,pnl=pnl
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
wmsg = {
|
wmsg = {
|
||||||
|
|
@ -110,23 +122,20 @@ def get_seed_for_seed_id(seed_id,infiles,saved_seeds):
|
||||||
if seed_id in saved_seeds.keys():
|
if seed_id in saved_seeds.keys():
|
||||||
return saved_seeds[seed_id]
|
return saved_seeds[seed_id]
|
||||||
|
|
||||||
from mmgen.crypto import get_seed_retry
|
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
if infiles:
|
if infiles:
|
||||||
seed = get_seed_retry(infiles.pop(0))
|
ss = SeedSource(infiles.pop(0),ignore_in_fmt=True)
|
||||||
elif any([opt.from_brain,opt.from_mnemonic,opt.from_seed,opt.from_incog]):
|
elif opt.in_fmt:
|
||||||
qmsg("Need seed data for seed ID %s" % seed_id)
|
qmsg("Need seed data for seed ID %s" % seed_id)
|
||||||
seed = get_seed_retry("",seed_id)
|
ss = SeedSource()
|
||||||
msg("User input produced seed ID %s" % make_chksum_8(seed))
|
msg("User input produced seed ID %s" % make_chksum_8(seed))
|
||||||
else:
|
else:
|
||||||
msg("ERROR: No seed source found for seed ID: %s" % seed_id)
|
msg("ERROR: No seed source found for seed ID: %s" % seed_id)
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
sid = make_chksum_8(seed)
|
saved_seeds[ss.seed.sid] = ss.seed.data
|
||||||
saved_seeds[sid] = seed
|
|
||||||
|
|
||||||
if sid == seed_id: return seed
|
if ss.seed.sid == seed_id: return ss.seed.data
|
||||||
|
|
||||||
|
|
||||||
def get_keys_for_mmgen_addrs(mmgen_addrs,infiles,saved_seeds):
|
def get_keys_for_mmgen_addrs(mmgen_addrs,infiles,saved_seeds):
|
||||||
|
|
@ -285,7 +294,7 @@ def get_keys_from_keylist(kldata,other_addrs):
|
||||||
|
|
||||||
infiles = opt.opts.init(opts_data,add_opts=["b16"])
|
infiles = opt.opts.init(opts_data,add_opts=["b16"])
|
||||||
|
|
||||||
if opt.from_incog_hex or opt.from_incog_hidden: opt.from_incog = True
|
#if opt.from_incog_hex or opt.from_incog_hidden: opt.from_incog = True
|
||||||
|
|
||||||
if not infiles: opt.opts.usage()
|
if not infiles: opt.opts.usage()
|
||||||
for i in infiles: check_infile(i)
|
for i in infiles: check_infile(i)
|
||||||
|
|
|
||||||
|
|
@ -33,10 +33,12 @@ usage = "[opts] [infile]"
|
||||||
nargs = 1
|
nargs = 1
|
||||||
iaction = "convert"
|
iaction = "convert"
|
||||||
oaction = "convert"
|
oaction = "convert"
|
||||||
|
bw_note = opt.opts.bw_note
|
||||||
|
pw_note = opt.opts.pw_note
|
||||||
|
|
||||||
if invoked_as == "gen":
|
if invoked_as == "gen":
|
||||||
desc = "Generate an {pnm} wallet from a random seed"
|
desc = "Generate an {pnm} wallet from a random seed"
|
||||||
opt_filter = "hdoJlLpPqrSv"
|
opt_filter = "ehdoJlLpPqrSvz"
|
||||||
usage = "[opts]"
|
usage = "[opts]"
|
||||||
oaction = "output"
|
oaction = "output"
|
||||||
nargs = 0
|
nargs = 0
|
||||||
|
|
@ -45,21 +47,25 @@ elif invoked_as == "conv":
|
||||||
opt_filter = None
|
opt_filter = None
|
||||||
elif invoked_as == "chk":
|
elif invoked_as == "chk":
|
||||||
desc = "Check validity of an {pnm} wallet"
|
desc = "Check validity of an {pnm} wallet"
|
||||||
opt_filter = "hiHOlpPqrv"
|
opt_filter = "ehiHOlpPqrvz"
|
||||||
iaction = "input"
|
iaction = "input"
|
||||||
elif invoked_as == "passchg":
|
elif invoked_as == "passchg":
|
||||||
desc = "Change the password, hash preset or label of an {pnm} wallet"
|
desc = "Change the password, hash preset or label of an {pnm} wallet"
|
||||||
opt_filter = "hdiHkKOlLmpPqrSv"
|
opt_filter = "ehdiHkKOlLmpPqrSvz"
|
||||||
iaction = "input"
|
iaction = "input"
|
||||||
|
bw_note = ""
|
||||||
else:
|
else:
|
||||||
die(1,"'%s': unrecognized invocation" % bn)
|
die(1,"'%s': unrecognized invocation" % bn)
|
||||||
|
|
||||||
opts_data = {
|
opts_data = {
|
||||||
|
# Can't use: share/Opts doesn't know anything about fmt codes
|
||||||
|
# 'sets': [('hidden_incog_output_params',bool,'out_fmt','hi')],
|
||||||
'desc': desc.format(pnm=g.proj_name),
|
'desc': desc.format(pnm=g.proj_name),
|
||||||
'usage': usage,
|
'usage': usage,
|
||||||
'options': """
|
'options': """
|
||||||
-h, --help Print this help message.
|
-h, --help Print this help message.
|
||||||
-d, --outdir= d Output files to directory 'd' instead of working dir.
|
-d, --outdir= d Output files to directory 'd' instead of working dir.
|
||||||
|
-e, --echo-passphrase Echo passphrases and other user input to screen.
|
||||||
-i, --in-fmt= f {iaction} from wallet format 'f' (see FMT CODES below).
|
-i, --in-fmt= f {iaction} from wallet format 'f' (see FMT CODES below).
|
||||||
-o, --out-fmt= f {oaction} to wallet format 'f' (see FMT CODES below).
|
-o, --out-fmt= f {oaction} to wallet format 'f' (see FMT CODES below).
|
||||||
-H, --hidden-incog-input-params=f,o Read hidden incognito data from file
|
-H, --hidden-incog-input-params=f,o Read hidden incognito data from file
|
||||||
|
|
@ -78,20 +84,28 @@ opts_data = {
|
||||||
-m, --keep-label Reuse label of input wallet for output wallet.
|
-m, --keep-label Reuse label of input wallet for output wallet.
|
||||||
-p, --hash-preset= p Use the scrypt hash parameters defined by preset 'p'
|
-p, --hash-preset= p Use the scrypt hash parameters defined by preset 'p'
|
||||||
for password hashing (default: '{g.hash_preset}').
|
for password hashing (default: '{g.hash_preset}').
|
||||||
-P, --passwd-file= f Get wallet passphrase from file 'f'
|
-z, --show-hash-presets Show information on available hash presets.
|
||||||
|
-P, --passwd-file= f Get wallet passphrase from file 'f'.
|
||||||
-q, --quiet Produce quieter output; suppress some warnings.
|
-q, --quiet Produce quieter output; suppress some warnings.
|
||||||
-r, --usr-randchars=n Get 'n' characters of additional randomness from user
|
-r, --usr-randchars=n Get 'n' characters of additional randomness from user
|
||||||
(min={g.min_urandchars}, max={g.max_urandchars}, default={g.usr_randchars}).
|
(min={g.min_urandchars}, max={g.max_urandchars}, default={g.usr_randchars}).
|
||||||
-S, --stdout Write wallet data to stdout instead of file.
|
-S, --stdout Write wallet data to stdout instead of file.
|
||||||
-v, --verbose Produce more verbose output.
|
-v, --verbose Produce more verbose output.
|
||||||
|
|
||||||
FMT CODES:
|
|
||||||
{f}
|
|
||||||
""".format(
|
""".format(
|
||||||
g=g,
|
g=g,
|
||||||
iaction=capfirst(iaction),
|
iaction=capfirst(iaction),
|
||||||
oaction=capfirst(oaction),
|
oaction=capfirst(oaction),
|
||||||
f="\n ".join(SeedSource.format_fmt_codes().split("\n"))
|
),
|
||||||
|
'notes': """
|
||||||
|
|
||||||
|
{pw_note}{bw_note}
|
||||||
|
|
||||||
|
FMT CODES:
|
||||||
|
{f}
|
||||||
|
""".format(
|
||||||
|
f="\n ".join(SeedSource.format_fmt_codes().split("\n")),
|
||||||
|
pw_note=pw_note,
|
||||||
|
bw_note=("","\n\n" + bw_note)[int(bool(bw_note))]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,167 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
#
|
|
||||||
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
|
|
||||||
# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
"""
|
|
||||||
mmgen-walletchk: Check integrity of an MMGen deterministic wallet, display
|
|
||||||
information about it and export it to various formats
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import mmgen.globalvars as g
|
|
||||||
import mmgen.opt as opt
|
|
||||||
from mmgen.util import *
|
|
||||||
from mmgen.crypto import *
|
|
||||||
|
|
||||||
opts_data = {
|
|
||||||
'desc': """Check integrity of an {pnm} deterministic wallet, display
|
|
||||||
its information, and export seed and mnemonic data.
|
|
||||||
""".format(pnm=g.proj_name),
|
|
||||||
'usage': "[opts] [filename]",
|
|
||||||
'options': """
|
|
||||||
-h, --help Print this help message
|
|
||||||
-d, --outdir= d Specify an alternate directory 'd' for output
|
|
||||||
-e, --echo-passphrase Print passphrase to screen when typing it
|
|
||||||
-P, --passwd-file= f Get {pnm} wallet passphrase from file 'f'
|
|
||||||
-q, --quiet Suppress warnings; overwrite files without prompting
|
|
||||||
-r, --usr-randchars= n Get 'n' characters of additional randomness from
|
|
||||||
user (min={g.min_urandchars}, max={g.max_urandchars})
|
|
||||||
-S, --stdout Print seed or mnemonic data to standard output
|
|
||||||
-v, --verbose Produce more verbose output
|
|
||||||
-g, --export-incog Export wallet to incognito format
|
|
||||||
-X, --export-incog-hex Export wallet to incognito hexadecimal format
|
|
||||||
-G, --export-incog-hidden=f,o Hide incognito data in existing file 'f'
|
|
||||||
at offset 'o' (comma-separated)
|
|
||||||
-o, --old-incog-fmt Use old (pre-0.7.8) incog format
|
|
||||||
-m, --export-mnemonic Export the wallet's mnemonic to file
|
|
||||||
-s, --export-seed Export the wallet's seed to file
|
|
||||||
""".format(g=g,pnm=g.proj_name),
|
|
||||||
'notes': """
|
|
||||||
|
|
||||||
Since good randomness is particularly important for incognito wallets,
|
|
||||||
the '--usr-randchars' option is turned on by default to gather additional
|
|
||||||
entropy from the user when one of the '--export-incog*' options is
|
|
||||||
selected. If you fully trust your OS's random number generator and wish
|
|
||||||
to disable this option, then specify '-r0' on the command line.
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
|
|
||||||
def wallet_to_incog_data(infile):
|
|
||||||
|
|
||||||
d = get_data_from_wallet(infile,silent=True)
|
|
||||||
seed_id,key_id,preset,salt,enc_seed = \
|
|
||||||
d[1][0], d[1][1], d[2].split(":")[0], d[3], d[4]
|
|
||||||
|
|
||||||
while True:
|
|
||||||
passwd = get_mmgen_passphrase("{pnm} wallet".format(pnm=g.proj_name))
|
|
||||||
key = make_key(passwd, salt, preset, "main key")
|
|
||||||
seed = decrypt_seed(enc_seed, key, seed_id, key_id)
|
|
||||||
if seed: break
|
|
||||||
|
|
||||||
iv = get_random(g.aesctr_iv_len)
|
|
||||||
iv_id = make_iv_chksum(iv)
|
|
||||||
msg("Incog ID: %s" % iv_id)
|
|
||||||
|
|
||||||
if not opt.old_incog_fmt:
|
|
||||||
salt = get_random(g.salt_len)
|
|
||||||
key = make_key(passwd, salt, preset, "incog wallet key")
|
|
||||||
key_id = make_chksum_8(key)
|
|
||||||
from hashlib import sha256
|
|
||||||
chk = sha256(seed).digest()[:8]
|
|
||||||
enc_seed = encrypt_data(chk+seed, key, 1, "seed")
|
|
||||||
|
|
||||||
# IV is used BOTH to initialize counter and to salt password!
|
|
||||||
key = make_key(passwd, iv, preset, "incog wrapper key")
|
|
||||||
wrap_enc = encrypt_data(salt+enc_seed,key,int(hexlify(iv),16),"incog data")
|
|
||||||
|
|
||||||
return iv+wrap_enc,seed_id,key_id,iv_id,preset
|
|
||||||
|
|
||||||
|
|
||||||
def export_to_hidden_incog(incog_enc):
|
|
||||||
outfile,offset = opt.export_incog_hidden.split(",") #Already sanity-checked
|
|
||||||
if opt.outdir: outfile = make_full_path(opt.outdir,outfile)
|
|
||||||
|
|
||||||
dmsg("Incog data len %s, offset %s" % (len(incog_enc),offset))
|
|
||||||
check_data_fits_file_at_offset(outfile,int(offset),len(incog_enc),"write")
|
|
||||||
|
|
||||||
if not opt.quiet: confirm_or_exit("","alter file '%s'" % outfile)
|
|
||||||
import os
|
|
||||||
f = os.open(outfile,os.O_RDWR)
|
|
||||||
os.lseek(f, int(offset), os.SEEK_SET)
|
|
||||||
os.write(f, incog_enc)
|
|
||||||
os.close(f)
|
|
||||||
msg("Data written to file '%s' at offset %s" %
|
|
||||||
(os.path.relpath(outfile),offset))
|
|
||||||
|
|
||||||
|
|
||||||
cmd_args = opt.opts.init(opts_data)
|
|
||||||
|
|
||||||
if opt.export_incog_hidden or opt.export_incog_hex:
|
|
||||||
opt.export_incog = True
|
|
||||||
|
|
||||||
if len(cmd_args) != 1: opt.opts.usage()
|
|
||||||
|
|
||||||
check_infile(cmd_args[0])
|
|
||||||
|
|
||||||
if opt.outdir and opt.export_incog_hidden:
|
|
||||||
msg("Warning: '--outdir' option is ignored when exporting hidden incog data")
|
|
||||||
|
|
||||||
g.use_urandchars = True
|
|
||||||
|
|
||||||
if opt.export_mnemonic:
|
|
||||||
qmsg("Exporting mnemonic data to file by user request")
|
|
||||||
elif opt.export_seed:
|
|
||||||
qmsg("Exporting seed data to file by user request")
|
|
||||||
elif opt.export_incog:
|
|
||||||
qmsg("Exporting wallet to incognito format by user request")
|
|
||||||
incog_enc,seed_id,key_id,iv_id,preset = \
|
|
||||||
wallet_to_incog_data(cmd_args[0])
|
|
||||||
|
|
||||||
if opt.export_incog_hidden:
|
|
||||||
export_to_hidden_incog(incog_enc)
|
|
||||||
else:
|
|
||||||
z = 0 if opt.old_incog_fmt else 8
|
|
||||||
seed_len = (len(incog_enc)-g.salt_len-g.aesctr_iv_len-z)*8
|
|
||||||
fn = "%s-%s-%s[%s,%s].%s" % (
|
|
||||||
seed_id, key_id, iv_id, seed_len, preset,
|
|
||||||
g.incog_hex_ext if opt.export_incog_hex else g.incog_ext
|
|
||||||
)
|
|
||||||
data = pretty_hexdump(incog_enc,2,8,line_nums=False) \
|
|
||||||
if opt.export_incog_hex else incog_enc
|
|
||||||
write_to_file_or_stdout(fn, data, "incognito wallet data")
|
|
||||||
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
seed = get_seed_retry(cmd_args[0])
|
|
||||||
if seed: msg("Wallet is OK")
|
|
||||||
else:
|
|
||||||
msg("Error opening wallet")
|
|
||||||
sys.exit(2)
|
|
||||||
|
|
||||||
if opt.export_mnemonic:
|
|
||||||
wl = get_default_wordlist()
|
|
||||||
from mmgen.mnemonic import get_mnemonic_from_seed
|
|
||||||
mn = get_mnemonic_from_seed(seed, wl, g.default_wordlist, opt.debug)
|
|
||||||
fn = "%s.%s" % (make_chksum_8(seed).upper(), g.mn_ext)
|
|
||||||
write_to_file_or_stdout(fn, " ".join(mn)+"\n", "mnemonic data")
|
|
||||||
|
|
||||||
elif opt.export_seed:
|
|
||||||
from mmgen.bitcoin import b58encode_pad
|
|
||||||
data = split_into_cols(4,b58encode_pad(seed))
|
|
||||||
chk = make_chksum_6(b58encode_pad(seed))
|
|
||||||
fn = "%s.%s" % (make_chksum_8(seed).upper(), g.seed_ext)
|
|
||||||
write_to_file_or_stdout(fn, "%s %s\n" % (chk,data), "seed data")
|
|
||||||
|
|
@ -1,172 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
#
|
|
||||||
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
|
|
||||||
# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
"""
|
|
||||||
mmgen-walletgen: Generate an MMGen deterministic wallet
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys, os
|
|
||||||
from hashlib import sha256
|
|
||||||
|
|
||||||
import mmgen.globalvars as g
|
|
||||||
import mmgen.opt as opt
|
|
||||||
from mmgen.util import *
|
|
||||||
from mmgen.crypto import *
|
|
||||||
|
|
||||||
pnm = g.proj_name
|
|
||||||
|
|
||||||
opts_data = {
|
|
||||||
'desc': "Generate an {pnm} deterministic wallet".format(pnm=pnm),
|
|
||||||
'usage': "[opts] [infile]",
|
|
||||||
'options': """
|
|
||||||
-h, --help Print this help message
|
|
||||||
-d, --outdir= d Specify an alternate directory 'd' for output
|
|
||||||
-e, --echo-passphrase Print passphrase to screen when typing it
|
|
||||||
-H, --show-hash-presets Show information on available hash presets
|
|
||||||
-l, --seed-len= n Create seed of length 'n'. Options: {seed_lens}
|
|
||||||
(default: {g.seed_len})
|
|
||||||
-L, --label= l Label to identify this wallet (32 chars max.
|
|
||||||
Allowed symbols: A-Z, a-z, 0-9, " ", "_", ".")
|
|
||||||
-p, --hash-preset= p Use scrypt.hash() parameters from preset 'p'
|
|
||||||
(default: '{g.hash_preset}')
|
|
||||||
-P, --passwd-file= f Get {pnm} wallet passphrase from file 'f'
|
|
||||||
-q, --quiet Produce quieter output; overwrite files without
|
|
||||||
prompting
|
|
||||||
-r, --usr-randchars= n Get 'n' characters of additional randomness from
|
|
||||||
user (min={g.min_urandchars}, max={g.max_urandchars})
|
|
||||||
-v, --verbose Produce more verbose output
|
|
||||||
|
|
||||||
-b, --from-brain= l,p Generate wallet from a user-created passphrase,
|
|
||||||
i.e. a "brainwallet", using seed length 'l' and
|
|
||||||
hash preset 'p' (comma-separated)
|
|
||||||
-g, --from-incog Generate wallet from an incognito-format wallet
|
|
||||||
-G, --from-incog-hidden= f,o,l Generate keys from incognito data in file
|
|
||||||
'f' at offset 'o', with seed length of 'l'
|
|
||||||
-o, --old-incog-fmt Use old (pre-0.7.8) incog format
|
|
||||||
-m, --from-mnemonic Generate wallet from an Electrum-like mnemonic
|
|
||||||
-s, --from-seed Generate wallet from a seed in .{g.seed_ext} format
|
|
||||||
""".format(seed_lens=",".join([str(i) for i in g.seed_lens]),g=g,pnm=pnm),
|
|
||||||
'notes': """
|
|
||||||
|
|
||||||
By default (i.e. when invoked without any of the '--from-<what>' options),
|
|
||||||
{g.prog_name} generates a wallet based on a random seed.
|
|
||||||
|
|
||||||
Data for the --from-<what> options will be taken from <infile> if <infile>
|
|
||||||
is specified. Otherwise, the user will be prompted to enter the data.
|
|
||||||
|
|
||||||
For passphrases all combinations of whitespace are equal, and leading and
|
|
||||||
trailing space are ignored. This permits reading passphrase data from a
|
|
||||||
multi-line file with free spacing and indentation. This is particularly
|
|
||||||
convenient for long brainwallet passphrases, for example.
|
|
||||||
|
|
||||||
Since good randomness is particularly important when generating wallets,
|
|
||||||
the '--usr-randchars' option is turned on by default to gather additional
|
|
||||||
entropy from the user. If you fully trust your OS's random number gener-
|
|
||||||
ator and wish to disable this option, specify '-r0' on the command line.
|
|
||||||
|
|
||||||
BRAINWALLET NOTE:
|
|
||||||
|
|
||||||
As brainwallets require especially strong hashing to thwart dictionary
|
|
||||||
attacks, the brainwallet hash preset must be specified by the user, using
|
|
||||||
the 'p' parameter of the '--from-brain' option. This preset should be
|
|
||||||
stronger than the one used for hashing the seed (i.e. the default value or
|
|
||||||
the one specified in the '--hash-preset' option).
|
|
||||||
|
|
||||||
The '--from-brain' option also requires the user to specify a seed length
|
|
||||||
(the 'l' parameter), which overrides both the default and any one given in
|
|
||||||
the '--seed-len' option.
|
|
||||||
|
|
||||||
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(g=g)
|
|
||||||
}
|
|
||||||
|
|
||||||
wmsg = {
|
|
||||||
'choose_wallet_passphrase': """
|
|
||||||
You must choose a passphrase to encrypt the wallet with. A key will be
|
|
||||||
generated from your passphrase using a hash preset of '%s'. Please note that
|
|
||||||
no strength checking of passphrases is performed. For an empty passphrase,
|
|
||||||
just hit ENTER twice.
|
|
||||||
""".strip(),
|
|
||||||
'brain_warning': """
|
|
||||||
############################## EXPERTS ONLY! ##############################
|
|
||||||
|
|
||||||
A brainwallet will be secure only if you really know what you're doing and
|
|
||||||
have put much care into its creation. The creators of {pnm} assume no
|
|
||||||
responsibility for coins stolen as a result of a poorly crafted brainwallet
|
|
||||||
passphrase.
|
|
||||||
|
|
||||||
A key will be generated from your passphrase using the parameters requested
|
|
||||||
by you: seed length {}, hash preset '{}'. For brainwallets it's highly
|
|
||||||
recommended to use one of the higher-numbered presets.
|
|
||||||
|
|
||||||
Remember the seed length and hash preset parameters you've specified. To
|
|
||||||
generate the correct keys/addresses associated with this passphrase in the
|
|
||||||
future, you must continue using these same parameters.
|
|
||||||
""",
|
|
||||||
}
|
|
||||||
|
|
||||||
import mmgen.opt as opt
|
|
||||||
cmd_args = opt.opts.init(opts_data)
|
|
||||||
|
|
||||||
if len(cmd_args) == 1:
|
|
||||||
infile = cmd_args[0]
|
|
||||||
check_infile(infile)
|
|
||||||
ext = infile.split(".")[-1]
|
|
||||||
ok_exts = g.seedfile_exts
|
|
||||||
for e in ok_exts:
|
|
||||||
if e == ext: break
|
|
||||||
else:
|
|
||||||
msg(
|
|
||||||
"Input file must have one of the following extensions: .%s" % ", .".join(ok_exts))
|
|
||||||
sys.exit(1)
|
|
||||||
elif len(cmd_args) == 0:
|
|
||||||
infile = ""
|
|
||||||
else: opt.opts.usage()
|
|
||||||
|
|
||||||
g.use_urandchars = True
|
|
||||||
|
|
||||||
# Begin execution
|
|
||||||
|
|
||||||
do_license_msg()
|
|
||||||
|
|
||||||
if opt.from_brain and not opt.quiet:
|
|
||||||
confirm_or_exit(wmsg['brain_warning'].format(
|
|
||||||
pnm=pnm, *get_from_brain_opt_params()),
|
|
||||||
"continue")
|
|
||||||
|
|
||||||
if infile or any([
|
|
||||||
opt.from_mnemonic,opt.from_brain,opt.from_seed,opt.from_incog]):
|
|
||||||
seed = get_seed_retry(infile)
|
|
||||||
qmsg("")
|
|
||||||
else:
|
|
||||||
# Truncate random data for smaller seed lengths
|
|
||||||
seed = sha256(get_random(128)).digest()[:opt.seed_len/8]
|
|
||||||
|
|
||||||
salt = sha256(get_random(128)).digest()[:g.salt_len]
|
|
||||||
|
|
||||||
qmsg(wmsg['choose_wallet_passphrase'] % opt.hash_preset)
|
|
||||||
|
|
||||||
passwd = get_new_passphrase("new {pnm} wallet".format(pnm=pnm))
|
|
||||||
|
|
||||||
key = make_key(passwd, salt, opt.hash_preset)
|
|
||||||
|
|
||||||
enc_seed = encrypt_seed(seed, key)
|
|
||||||
|
|
||||||
write_wallet_to_file(seed,passwd,make_chksum_8(key),salt,enc_seed)
|
|
||||||
|
|
@ -26,6 +26,20 @@ import mmgen.share.Opts
|
||||||
import opt
|
import opt
|
||||||
from mmgen.util import msg,msg_r,mdie,mmsg,Msg,die,is_mmgen_wallet_label
|
from mmgen.util import msg,msg_r,mdie,mmsg,Msg,die,is_mmgen_wallet_label
|
||||||
|
|
||||||
|
pw_note = """
|
||||||
|
For passphrases all combinations of whitespace are equal and leading and
|
||||||
|
trailing space is ignored. This permits reading passphrase or brainwallet
|
||||||
|
data from a multi-line file with free spacing and indentation.
|
||||||
|
""".strip()
|
||||||
|
|
||||||
|
bw_note = """
|
||||||
|
BRAINWALLET NOTE:
|
||||||
|
|
||||||
|
To thwart dictionary attacks, it's recommended to use a strong hash preset
|
||||||
|
with brainwallets. For a brainwallet passphrase to generate the correct
|
||||||
|
seed, the same seed length and hash preset parameters must always be used.
|
||||||
|
""".strip()
|
||||||
|
|
||||||
def usage():
|
def usage():
|
||||||
Msg("USAGE: %s %s" % (g.prog_name, usage_txt))
|
Msg("USAGE: %s %s" % (g.prog_name, usage_txt))
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
@ -175,9 +189,10 @@ def check_opts(usr_opts): # Returns false if any check fails
|
||||||
(val,desc,n,sepword))
|
(val,desc,n,sepword))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def opt_compares(val,op,target,desc):
|
def opt_compares(val,op,target,desc,what=""):
|
||||||
|
if what: what += " "
|
||||||
if not eval("%s %s %s" % (val, op, target)):
|
if not eval("%s %s %s" % (val, op, target)):
|
||||||
msg("%s: invalid %s (not %s %s)" % (val,desc,op,target))
|
msg("%s: invalid %s (%snot %s %s)" % (val,desc,what,op,target))
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
@ -212,15 +227,14 @@ def check_opts(usr_opts): # Returns false if any check fails
|
||||||
|
|
||||||
desc = "parameter for '%s' option" % fmt_opt(key)
|
desc = "parameter for '%s' option" % fmt_opt(key)
|
||||||
|
|
||||||
|
from mmgen.util import check_infile,check_outfile,check_outdir
|
||||||
# Check for file existence and readability
|
# Check for file existence and readability
|
||||||
from mmgen.util import check_infile
|
|
||||||
if key in ('keys_from_file','mmgen_keys_from_file',
|
if key in ('keys_from_file','mmgen_keys_from_file',
|
||||||
'passwd_file','keysforaddrs','comment_file'):
|
'passwd_file','keysforaddrs','comment_file'):
|
||||||
check_infile(val) # exits on error
|
check_infile(val) # exits on error
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if key == 'outdir':
|
if key == 'outdir':
|
||||||
from mmgen.util import check_outdir
|
|
||||||
check_outdir(val) # exits on error
|
check_outdir(val) # exits on error
|
||||||
elif key == 'label':
|
elif key == 'label':
|
||||||
if not is_mmgen_wallet_label(val):
|
if not is_mmgen_wallet_label(val):
|
||||||
|
|
@ -244,10 +258,22 @@ def check_opts(usr_opts): # Returns false if any check fails
|
||||||
elif issubclass(sstype,Brainwallet):
|
elif issubclass(sstype,Brainwallet):
|
||||||
die(1,"Output to brainwallet format unsupported")
|
die(1,"Output to brainwallet format unsupported")
|
||||||
elif key in ('hidden_incog_input_params','hidden_incog_output_params'):
|
elif key in ('hidden_incog_input_params','hidden_incog_output_params'):
|
||||||
|
a = val.split(",")
|
||||||
|
if len(a) != 2:
|
||||||
|
opt_display(key,val)
|
||||||
|
msg("Option requires two comma-separated arguments")
|
||||||
|
return False
|
||||||
|
if not opt_is_int(a[1],desc): return False
|
||||||
if key == 'hidden_incog_input_params':
|
if key == 'hidden_incog_input_params':
|
||||||
check_infile(val.split(",")[0])
|
check_infile(a[0],blkdev_ok=True)
|
||||||
key2 = 'in_fmt'
|
key2 = 'in_fmt'
|
||||||
else:
|
else:
|
||||||
|
import os
|
||||||
|
try: os.stat(a[0])
|
||||||
|
except:
|
||||||
|
b = os.path.dirname(a[0])
|
||||||
|
if b: check_outdir(b)
|
||||||
|
else: check_outfile(a[0],blkdev_ok=True)
|
||||||
key2 = 'out_fmt'
|
key2 = 'out_fmt'
|
||||||
if hasattr(opt,key2):
|
if hasattr(opt,key2):
|
||||||
val2 = getattr(opt,key2)
|
val2 = getattr(opt,key2)
|
||||||
|
|
@ -257,47 +283,22 @@ def check_opts(usr_opts): # Returns false if any check fails
|
||||||
"Option conflict:\n %s, with\n %s=%s" % (
|
"Option conflict:\n %s, with\n %s=%s" % (
|
||||||
fmt_opt(key),fmt_opt(key2),val2
|
fmt_opt(key),fmt_opt(key2),val2
|
||||||
))
|
))
|
||||||
|
|
||||||
# begin OLD, deprecated
|
|
||||||
elif key == 'hidden_incog_params':
|
|
||||||
from mmgen.util import check_outfile
|
|
||||||
if not opt_splits(val,",",2,desc): return False
|
|
||||||
outfile,offset = val.split(",")
|
|
||||||
check_outfile(outfile)
|
|
||||||
w = "offset " + desc
|
|
||||||
if not opt_is_int(offset,w): return False
|
|
||||||
if not opt_compares(offset,">=",0,desc): return False
|
|
||||||
elif key == 'export_incog_hidden' or key == 'from_incog_hidden':
|
|
||||||
if key == 'from_incog_hidden':
|
|
||||||
if not opt_splits(val,",",3,desc): return False
|
|
||||||
infile,offset,seed_len = val.split(",")
|
|
||||||
from mmgen.util import check_infile
|
|
||||||
check_infile(infile)
|
|
||||||
w = "seed length " + desc
|
|
||||||
if not opt_is_int(seed_len,w): return False
|
|
||||||
if not opt_is_in_list(int(seed_len),g.seed_lens,w): return False
|
|
||||||
else:
|
|
||||||
from mmgen.util import check_outfile
|
|
||||||
if not opt_splits(val,",",2,desc): return False
|
|
||||||
outfile,offset = val.split(",")
|
|
||||||
check_outfile(outfile)
|
|
||||||
w = "offset " + desc
|
|
||||||
if not opt_is_int(offset,w): return False
|
|
||||||
if not opt_compares(offset,">=",0,desc): return False
|
|
||||||
elif key == 'from_brain':
|
|
||||||
if not opt_splits(val,",",2,desc): return False
|
|
||||||
l,p = val.split(",")
|
|
||||||
w = "seed length " + desc
|
|
||||||
if not opt_is_int(l,w): return False
|
|
||||||
if not opt_is_in_list(int(l),g.seed_lens,w): return False
|
|
||||||
w = "hash preset " + desc
|
|
||||||
if not opt_is_in_list(p,g.hash_presets.keys(),w): return False
|
|
||||||
# end OLD
|
|
||||||
elif key == 'seed_len':
|
elif key == 'seed_len':
|
||||||
if not opt_is_int(val,desc): return False
|
if not opt_is_int(val,desc): return False
|
||||||
if not opt_is_in_list(int(val),g.seed_lens,desc): return False
|
if not opt_is_in_list(int(val),g.seed_lens,desc): return False
|
||||||
elif key == 'hash_preset':
|
elif key == 'hash_preset':
|
||||||
if not opt_is_in_list(val,g.hash_presets.keys(),desc): return False
|
if not opt_is_in_list(val,g.hash_presets.keys(),desc): return False
|
||||||
|
elif key == 'brain_params':
|
||||||
|
a = val.split(",")
|
||||||
|
if len(a) != 2:
|
||||||
|
opt_display(key,val)
|
||||||
|
msg("Option requires two comma-separated arguments")
|
||||||
|
return False
|
||||||
|
d = "seed length " + desc
|
||||||
|
if not opt_is_int(a[0],d): return False
|
||||||
|
if not opt_is_in_list(int(a[0]),g.seed_lens,d): return False
|
||||||
|
d = "hash preset " + desc
|
||||||
|
if not opt_is_in_list(a[1],g.hash_presets.keys(),d): return False
|
||||||
elif key == 'usr_randchars':
|
elif key == 'usr_randchars':
|
||||||
if val == 0: continue
|
if val == 0: continue
|
||||||
if not opt_is_int(val,desc): return False
|
if not opt_is_int(val,desc): return False
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ class SeedSource(MMGenObject):
|
||||||
|
|
||||||
class SeedSourceData(MMGenObject): pass
|
class SeedSourceData(MMGenObject): pass
|
||||||
|
|
||||||
def __new__(cls,fn=None,ss=None,ignore_in_fmt_opt=False,passchg=False):
|
def __new__(cls,fn=None,ss=None,ignore_in_fmt=False,passchg=False):
|
||||||
|
|
||||||
def die_on_opt_mismatch(opt,sstype):
|
def die_on_opt_mismatch(opt,sstype):
|
||||||
opt_sstype = cls.fmt_code_to_sstype(opt)
|
opt_sstype = cls.fmt_code_to_sstype(opt)
|
||||||
|
|
@ -91,7 +91,7 @@ class SeedSource(MMGenObject):
|
||||||
f = Filename(fn,ftype="hincog")
|
f = Filename(fn,ftype="hincog")
|
||||||
sstype = cls.fmt_code_to_sstype("hincog")
|
sstype = cls.fmt_code_to_sstype("hincog")
|
||||||
|
|
||||||
if opt.in_fmt and not ignore_in_fmt_opt:
|
if opt.in_fmt and not ignore_in_fmt:
|
||||||
die_on_opt_mismatch(opt.in_fmt,sstype)
|
die_on_opt_mismatch(opt.in_fmt,sstype)
|
||||||
|
|
||||||
me = super(cls,cls).__new__(sstype)
|
me = super(cls,cls).__new__(sstype)
|
||||||
|
|
@ -109,7 +109,7 @@ class SeedSource(MMGenObject):
|
||||||
|
|
||||||
return me
|
return me
|
||||||
|
|
||||||
def __init__(self,fn=None,ss=None,ignore_in_fmt_opt=False,passchg=False):
|
def __init__(self,fn=None,ss=None,ignore_in_fmt=False,passchg=False):
|
||||||
|
|
||||||
self.ssdata = self.SeedSourceData()
|
self.ssdata = self.SeedSourceData()
|
||||||
self.msg = {}
|
self.msg = {}
|
||||||
|
|
@ -692,16 +692,26 @@ class Brainwallet (SeedSourceEnc):
|
||||||
ext = "mmbrain"
|
ext = "mmbrain"
|
||||||
# brainwallet warning message? TODO
|
# brainwallet warning message? TODO
|
||||||
|
|
||||||
|
def get_bw_params(self):
|
||||||
|
# already checked
|
||||||
|
a = opt.brain_params.split(",")
|
||||||
|
return int(a[0]),a[1]
|
||||||
|
|
||||||
def _deformat(self):
|
def _deformat(self):
|
||||||
self.brainpasswd = " ".join(self.fmt_data.split())
|
self.brainpasswd = " ".join(self.fmt_data.split())
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _decrypt(self):
|
def _decrypt(self):
|
||||||
self._get_hash_preset()
|
d = self.ssdata
|
||||||
|
if hasattr(opt,"brain_params"):
|
||||||
|
seed_len,d.hash_preset = self.get_bw_params()
|
||||||
|
else:
|
||||||
|
self._get_hash_preset()
|
||||||
|
seed_len = opt.seed_len
|
||||||
vmsg_r("Hashing brainwallet data. Please wait...")
|
vmsg_r("Hashing brainwallet data. Please wait...")
|
||||||
# Use buflen arg of scrypt.hash() to get seed of desired length
|
# Use buflen arg of scrypt.hash() to get seed of desired length
|
||||||
seed = scrypt_hash_passphrase(self.brainpasswd, "",
|
seed = scrypt_hash_passphrase(self.brainpasswd, "",
|
||||||
self.ssdata.hash_preset, buflen=opt.seed_len/8)
|
d.hash_preset, buflen=seed_len/8)
|
||||||
vmsg("Done")
|
vmsg("Done")
|
||||||
self.seed = Seed(seed)
|
self.seed = Seed(seed)
|
||||||
msg("Seed ID: %s" % self.seed.sid)
|
msg("Seed ID: %s" % self.seed.sid)
|
||||||
|
|
@ -921,8 +931,8 @@ harder to find, you're advised to choose a much larger file size than this.
|
||||||
m = ("Input","Destination")[int(action=="write")]
|
m = ("Input","Destination")[int(action=="write")]
|
||||||
if fn.size < d.hincog_offset + d.target_data_len:
|
if fn.size < d.hincog_offset + d.target_data_len:
|
||||||
die(1,
|
die(1,
|
||||||
"%s file has length %s, too short to %s %s bytes of data at offset %s"
|
"%s file '%s' has length %s, too short to %s %s bytes of data at offset %s"
|
||||||
% (m,fn.size,action,d.target_data_len,d.hincog_offset))
|
% (m,fn.name,fn.size,action,d.target_data_len,d.hincog_offset))
|
||||||
|
|
||||||
def _get_data(self):
|
def _get_data(self):
|
||||||
d = self.ssdata
|
d = self.ssdata
|
||||||
|
|
@ -941,7 +951,6 @@ harder to find, you're advised to choose a much larger file size than this.
|
||||||
qmsg("Data read from file '%s' at offset %s" %
|
qmsg("Data read from file '%s' at offset %s" %
|
||||||
(self.infile.name,d.hincog_offset), "Data read from file")
|
(self.infile.name,d.hincog_offset), "Data read from file")
|
||||||
|
|
||||||
|
|
||||||
# overrides method in SeedSource
|
# overrides method in SeedSource
|
||||||
def write_to_file(self):
|
def write_to_file(self):
|
||||||
d = self.ssdata
|
d = self.ssdata
|
||||||
|
|
@ -972,9 +981,10 @@ harder to find, you're advised to choose a much larger file size than this.
|
||||||
else:
|
else:
|
||||||
die(1,"Exiting at user request")
|
die(1,"Exiting at user request")
|
||||||
|
|
||||||
self.outfile = f = Filename(fn,ftype="hincog")
|
self.outfile = f = Filename(fn,ftype=self.fmt_codes[0],write=True)
|
||||||
|
|
||||||
dmsg("Incog data len %s, offset %s" % (d.target_data_len,d.hincog_offset))
|
dmsg("%s data len %s, offset %s" % (
|
||||||
|
capfirst(self.desc),d.target_data_len,d.hincog_offset))
|
||||||
|
|
||||||
if check_offset:
|
if check_offset:
|
||||||
self._check_valid_offset(f,"write")
|
self._check_valid_offset(f,"write")
|
||||||
|
|
@ -985,5 +995,4 @@ harder to find, you're advised to choose a much larger file size than this.
|
||||||
os.write(fh, self.fmt_data)
|
os.write(fh, self.fmt_data)
|
||||||
os.close(fh)
|
os.close(fh)
|
||||||
msg("%s written to file '%s' at offset %s" % (
|
msg("%s written to file '%s' at offset %s" % (
|
||||||
capfirst(self.desc),
|
capfirst(self.desc),f.name,d.hincog_offset))
|
||||||
f.name,d.hincog_offset))
|
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ def process_opts(argv,opts_data,short_opts,long_opts):
|
||||||
opts_data['prog_name'] = os.path.basename(sys.argv[0])
|
opts_data['prog_name'] = os.path.basename(sys.argv[0])
|
||||||
long_opts = [i.replace("_","-") for i in long_opts]
|
long_opts = [i.replace("_","-") for i in long_opts]
|
||||||
|
|
||||||
try: cl_opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
|
try: cl_opts,args = getopt.getopt(argv[1:], short_opts, long_opts)
|
||||||
except getopt.GetoptError as err:
|
except getopt.GetoptError as err:
|
||||||
print str(err); sys.exit(2)
|
print str(err); sys.exit(2)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -132,11 +132,11 @@ def suf(arg,suf_type):
|
||||||
def get_extension(f):
|
def get_extension(f):
|
||||||
return os.path.splitext(f)[1][1:]
|
return os.path.splitext(f)[1][1:]
|
||||||
|
|
||||||
def make_chksum_N(s,n,sep=False):
|
def make_chksum_N(s,nchars,sep=False):
|
||||||
if n%4 or not (4 <= n <= 64): return False
|
if nchars%4 or not (4 <= nchars <= 64): return False
|
||||||
s = sha256(sha256(s).digest()).hexdigest().upper()
|
s = sha256(sha256(s).digest()).hexdigest().upper()
|
||||||
sep = " " if sep else ""
|
sep = " " if sep else ""
|
||||||
return sep.join([s[i*4:i*4+4] for i in range(n/4)])
|
return sep.join([s[i*4:i*4+4] for i in range(nchars/4)])
|
||||||
|
|
||||||
def make_chksum_8(s,sep=False):
|
def make_chksum_8(s,sep=False):
|
||||||
s = sha256(sha256(s).digest()).hexdigest()[:8].upper()
|
s = sha256(sha256(s).digest()).hexdigest()[:8].upper()
|
||||||
|
|
@ -273,36 +273,43 @@ def open_file_or_exit(filename,mode):
|
||||||
return f
|
return f
|
||||||
|
|
||||||
|
|
||||||
def check_file_type_and_access(fname,ftype):
|
def check_file_type_and_access(fname,ftype,blkdev_ok=False):
|
||||||
|
|
||||||
import os, stat
|
import os, stat
|
||||||
|
|
||||||
typ2,tdesc2,access,action = (stat.S_ISLNK,"symbolic link",os.R_OK,"read")\
|
a = ((os.R_OK,"read"),(os.W_OK,"writ"))
|
||||||
if ftype == "input file" else (stat.S_ISBLK,"block device",os.W_OK,"writ")
|
access,m = a[int(ftype in ("output file","output directory"))]
|
||||||
|
|
||||||
if ftype == "directory":
|
ok_types = [
|
||||||
typ1,typ2,tdesc = stat.S_ISDIR,stat.S_ISDIR,"directory"
|
(stat.S_ISREG,"regular file"),
|
||||||
else:
|
(stat.S_ISLNK,"symbolic link")
|
||||||
typ1,tdesc = stat.S_ISREG,"regular file or "+tdesc2
|
]
|
||||||
|
if blkdev_ok: ok_types.append((stat.S_ISBLK,"block device"))
|
||||||
|
if ftype == "output directory": ok_types = [(stat.S_ISDIR, "output directory")]
|
||||||
|
|
||||||
try: mode = os.stat(fname).st_mode
|
try: mode = os.stat(fname).st_mode
|
||||||
except:
|
except:
|
||||||
msg("Unable to stat requested %s '%s'" % (ftype,fname))
|
msg("Unable to stat requested %s '%s'" % (ftype,fname))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if not (typ1(mode) or typ2(mode)):
|
for t in ok_types:
|
||||||
msg("Requested %s '%s' is not a %s" % (ftype,fname,tdesc))
|
if t[0](mode): break
|
||||||
|
else:
|
||||||
|
msg("Requested %s '%s' is not a %s" % (ftype,fname,
|
||||||
|
" or ".join([t[1] for t in ok_types])))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if not os.access(fname, access):
|
if not os.access(fname, access):
|
||||||
msg("Requested %s '%s' is un%sable by you" % (ftype,fname,action))
|
msg("Requested %s '%s' is not %sable by you" % (ftype,fname,m))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def check_infile(f): return check_file_type_and_access(f,"input file")
|
def check_infile(f,blkdev_ok=False):
|
||||||
def check_outfile(f): return check_file_type_and_access(f,"output file")
|
return check_file_type_and_access(f,"input file",blkdev_ok=blkdev_ok)
|
||||||
def check_outdir(f): return check_file_type_and_access(f,"directory")
|
def check_outfile(f,blkdev_ok=False):
|
||||||
|
return check_file_type_and_access(f,"output file",blkdev_ok=blkdev_ok)
|
||||||
|
def check_outdir(f): return check_file_type_and_access(f,"output directory")
|
||||||
|
|
||||||
def _validate_addr_num(n):
|
def _validate_addr_num(n):
|
||||||
|
|
||||||
|
|
|
||||||
5
setup.py
5
setup.py
|
|
@ -21,7 +21,7 @@ from distutils.core import setup
|
||||||
setup(
|
setup(
|
||||||
name = 'mmgen',
|
name = 'mmgen',
|
||||||
description = 'A complete Bitcoin cold-storage solution for the command line',
|
description = 'A complete Bitcoin cold-storage solution for the command line',
|
||||||
version = '0.8.0',
|
version = '0.8.1rc1',
|
||||||
author = 'Philemon',
|
author = 'Philemon',
|
||||||
author_email = 'mmgen-py@yandex.com',
|
author_email = 'mmgen-py@yandex.com',
|
||||||
url = 'https://github.com/mmgen/mmgen',
|
url = 'https://github.com/mmgen/mmgen',
|
||||||
|
|
@ -52,15 +52,12 @@ setup(
|
||||||
'mmgen.main',
|
'mmgen.main',
|
||||||
'mmgen.main_addrgen',
|
'mmgen.main_addrgen',
|
||||||
'mmgen.main_addrimport',
|
'mmgen.main_addrimport',
|
||||||
'mmgen.main_passchg',
|
|
||||||
'mmgen.main_pywallet',
|
'mmgen.main_pywallet',
|
||||||
'mmgen.main_tool',
|
'mmgen.main_tool',
|
||||||
'mmgen.main_txcreate',
|
'mmgen.main_txcreate',
|
||||||
'mmgen.main_txsend',
|
'mmgen.main_txsend',
|
||||||
'mmgen.main_txsign',
|
'mmgen.main_txsign',
|
||||||
'mmgen.main_wallet',
|
'mmgen.main_wallet',
|
||||||
'mmgen.main_walletchk',
|
|
||||||
'mmgen.main_walletgen',
|
|
||||||
|
|
||||||
'mmgen.share.__init__',
|
'mmgen.share.__init__',
|
||||||
'mmgen.share.Opts',
|
'mmgen.share.Opts',
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
98
test/test.py
98
test/test.py
|
|
@ -13,6 +13,7 @@ import mmgen.opt as opt
|
||||||
from mmgen.util import mmsg,mdie,Msg,die,capfirst
|
from mmgen.util import mmsg,mdie,Msg,die,capfirst
|
||||||
from mmgen.test import *
|
from mmgen.test import *
|
||||||
|
|
||||||
|
tb_cmd = "scripts/traceback.py"
|
||||||
hincog_fn = "rand_data"
|
hincog_fn = "rand_data"
|
||||||
hincog_bytes = 1024*1024
|
hincog_bytes = 1024*1024
|
||||||
hincog_offset = 98765
|
hincog_offset = 98765
|
||||||
|
|
@ -28,8 +29,8 @@ ref_wallet_hash_preset = "1"
|
||||||
ref_wallet_incog_offset = 123
|
ref_wallet_incog_offset = 123
|
||||||
|
|
||||||
ref_bw_hash_preset = "1"
|
ref_bw_hash_preset = "1"
|
||||||
ref_bw_file = "brainwallet"
|
ref_bw_file = "wallet.mmbrain"
|
||||||
ref_bw_file_spc = "brainwallet-spaced"
|
ref_bw_file_spc = "wallet-spaced.mmbrain"
|
||||||
|
|
||||||
ref_kafile_pass = "kafile password"
|
ref_kafile_pass = "kafile password"
|
||||||
ref_kafile_hash_preset = "1"
|
ref_kafile_hash_preset = "1"
|
||||||
|
|
@ -381,8 +382,9 @@ opts_data = {
|
||||||
-q, --quiet Produce minimal output. Suppress dependency info
|
-q, --quiet Produce minimal output. Suppress dependency info
|
||||||
-s, --system Test scripts and modules installed on system rather than
|
-s, --system Test scripts and modules installed on system rather than
|
||||||
those in the repo root
|
those in the repo root
|
||||||
|
-t, --traceback Run the command inside the '{tb_cmd}' script
|
||||||
-v, --verbose Produce more verbose output
|
-v, --verbose Produce more verbose output
|
||||||
""",
|
""".format(tb_cmd=tb_cmd),
|
||||||
'notes': """
|
'notes': """
|
||||||
|
|
||||||
If no command is given, the whole suite of tests is run.
|
If no command is given, the whole suite of tests is run.
|
||||||
|
|
@ -542,6 +544,9 @@ class MMGenExpect(object):
|
||||||
os.system(" ".join([mmgen_cmd] + cmd_args))
|
os.system(" ".join([mmgen_cmd] + cmd_args))
|
||||||
sys.exit()
|
sys.exit()
|
||||||
else:
|
else:
|
||||||
|
if opt.traceback:
|
||||||
|
cmd_args = [mmgen_cmd] + cmd_args
|
||||||
|
mmgen_cmd = tb_cmd
|
||||||
self.p = pexpect.spawn(mmgen_cmd,cmd_args)
|
self.p = pexpect.spawn(mmgen_cmd,cmd_args)
|
||||||
if opt.exact_output: self.p.logfile = sys.stdout
|
if opt.exact_output: self.p.logfile = sys.stdout
|
||||||
|
|
||||||
|
|
@ -580,7 +585,7 @@ class MMGenExpect(object):
|
||||||
passphrase+"\n",regex=True)
|
passphrase+"\n",regex=True)
|
||||||
|
|
||||||
def hash_preset(self,desc,preset=''):
|
def hash_preset(self,desc,preset=''):
|
||||||
my_expect(self.p,("Enter hash preset for %s," % desc))
|
my_expect(self.p,("Enter hash preset for %s" % desc))
|
||||||
my_expect(self.p,("or hit ENTER .*?:"), str(preset)+"\n",regex=True)
|
my_expect(self.p,("or hit ENTER .*?:"), str(preset)+"\n",regex=True)
|
||||||
|
|
||||||
def written_to_file(self,desc,overwrite_unlikely=False,query="Overwrite? ",oo=False):
|
def written_to_file(self,desc,overwrite_unlikely=False,query="Overwrite? ",oo=False):
|
||||||
|
|
@ -897,12 +902,11 @@ class MMGenTestSuite(object):
|
||||||
t.license()
|
t.license()
|
||||||
t.passphrase("MMGen wallet",cfg['wpasswd'])
|
t.passphrase("MMGen wallet",cfg['wpasswd'])
|
||||||
t.expect("Passphrase is OK")
|
t.expect("Passphrase is OK")
|
||||||
t.expect("[0-9]+ addresses generated",regex=True)
|
|
||||||
chk = t.expect_getend(r"Checksum for address data .*?: ",regex=True)
|
chk = t.expect_getend(r"Checksum for address data .*?: ",regex=True)
|
||||||
if check_ref:
|
if check_ref:
|
||||||
refcheck("address data checksum",chk,cfg['addrfile_chk'])
|
refcheck("address data checksum",chk,cfg['addrfile_chk'])
|
||||||
return
|
return
|
||||||
t.written_to_file("Addresses")
|
t.written_to_file("Addresses",oo=True)
|
||||||
ok()
|
ok()
|
||||||
|
|
||||||
def refaddrgen(self,name,walletfile):
|
def refaddrgen(self,name,walletfile):
|
||||||
|
|
@ -1084,11 +1088,11 @@ class MMGenTestSuite(object):
|
||||||
self.export_incog(
|
self.export_incog(
|
||||||
name,wf,desc="hidden incognito data",out_fmt="hi",add_args=add_args)
|
name,wf,desc="hidden incognito data",out_fmt="hi",add_args=add_args)
|
||||||
|
|
||||||
def addrgen_seed(self,name,walletfile,foo,desc="seed data",arg="-s"):
|
def addrgen_seed(self,name,walletfile,foo,desc="seed data",in_fmt="seed"):
|
||||||
stdout = (False,True)[int(desc=="seed data")] #capture output to screen once
|
stdout = (False,True)[int(desc=="seed data")] #capture output to screen once
|
||||||
add_arg = ([],["-S"])[int(stdout)]
|
add_arg = ([],["-S"])[int(stdout)]
|
||||||
t = MMGenExpect(name,"mmgen-addrgen", add_arg +
|
t = MMGenExpect(name,"mmgen-addrgen", add_arg +
|
||||||
[arg,"-d",cfg['tmpdir'],walletfile,cfg['addr_idx_list']])
|
["-i"+in_fmt,"-d",cfg['tmpdir'],walletfile,cfg['addr_idx_list']])
|
||||||
t.license()
|
t.license()
|
||||||
t.expect_getend("Valid %s for seed ID " % desc)
|
t.expect_getend("Valid %s for seed ID " % desc)
|
||||||
vmsg("Comparing generated checksum with checksum from previous address file")
|
vmsg("Comparing generated checksum with checksum from previous address file")
|
||||||
|
|
@ -1098,16 +1102,19 @@ class MMGenTestSuite(object):
|
||||||
# t.no_overwrite()
|
# t.no_overwrite()
|
||||||
ok()
|
ok()
|
||||||
|
|
||||||
def addrgen_mnemonic(self,name,walletfile,foo):
|
def addrgen_mnemonic(self,name,wf,foo):
|
||||||
self.addrgen_seed(name,walletfile,foo,desc="mnemonic",arg="-m")
|
self.addrgen_seed(name,wf,foo,desc="mnemonic data",in_fmt="words")
|
||||||
|
|
||||||
def addrgen_incog(self,name,walletfile,foo,args=["-g"]):
|
def addrgen_incog(self,name,wf=[],foo="",in_fmt="i",
|
||||||
t = MMGenExpect(name,"mmgen-addrgen",args+["-d",
|
desc="incognito data",args=[]):
|
||||||
cfg['tmpdir'],walletfile,cfg['addr_idx_list']])
|
t = MMGenExpect(name,"mmgen-addrgen",
|
||||||
|
args+["-i"+in_fmt,"-d",cfg['tmpdir']]+
|
||||||
|
([wf] if wf else [])+
|
||||||
|
[cfg['addr_idx_list']])
|
||||||
t.license()
|
t.license()
|
||||||
t.expect_getend("Incog ID: ")
|
t.expect_getend("Incog Wallet ID: ")
|
||||||
t.passphrase("incognito wallet data \w{8}", cfg['wpasswd'])
|
t.hash_preset(desc,'1')
|
||||||
t.hash_preset("incog wallet",'1')
|
t.passphrase("%s \w{8}" % desc, cfg['wpasswd'])
|
||||||
vmsg("Comparing generated checksum with checksum from address file")
|
vmsg("Comparing generated checksum with checksum from address file")
|
||||||
chk = t.expect_getend(r"Checksum for address data .*?: ",regex=True)
|
chk = t.expect_getend(r"Checksum for address data .*?: ",regex=True)
|
||||||
t.close()
|
t.close()
|
||||||
|
|
@ -1115,19 +1122,18 @@ class MMGenTestSuite(object):
|
||||||
# t.no_overwrite()
|
# t.no_overwrite()
|
||||||
ok()
|
ok()
|
||||||
|
|
||||||
def addrgen_incog_hex(self,name,walletfile,foo):
|
def addrgen_incog_hex(self,name,wf,foo):
|
||||||
self.addrgen_incog(name,walletfile,foo,args=["-X"])
|
self.addrgen_incog(name,wf,"",in_fmt="xi",desc="hex incognito data")
|
||||||
|
|
||||||
def addrgen_incog_hidden(self,name,walletfile,foo):
|
def addrgen_incog_hidden(self,name,wf,foo):
|
||||||
rf = os.path.join(cfg['tmpdir'],hincog_fn)
|
rf = os.path.join(cfg['tmpdir'],hincog_fn)
|
||||||
self.addrgen_incog(name,walletfile,foo,
|
self.addrgen_incog(name,[],"",in_fmt="hi",desc="hidden incognito data",
|
||||||
args=["-G","%s,%s,%s"%(rf,hincog_offset,hincog_seedlen)])
|
args=["-H","%s,%s"%(rf,hincog_offset),"-l",str(hincog_seedlen)])
|
||||||
|
|
||||||
def keyaddrgen(self,name,walletfile,check_ref=False):
|
def keyaddrgen(self,name,walletfile,check_ref=False):
|
||||||
t = MMGenExpect(name,"mmgen-keygen",
|
t = MMGenExpect(name,"mmgen-keygen",
|
||||||
["-d",cfg['tmpdir'],walletfile,cfg['addr_idx_list']])
|
["-d",cfg['tmpdir'],walletfile,cfg['addr_idx_list']])
|
||||||
t.license()
|
t.license()
|
||||||
t.expect("Type uppercase 'YES' to confirm: ","YES\n")
|
|
||||||
t.passphrase("MMGen wallet",cfg['wpasswd'])
|
t.passphrase("MMGen wallet",cfg['wpasswd'])
|
||||||
chk = t.expect_getend(r"Checksum for key-address data .*?: ",regex=True)
|
chk = t.expect_getend(r"Checksum for key-address data .*?: ",regex=True)
|
||||||
if check_ref:
|
if check_ref:
|
||||||
|
|
@ -1136,7 +1142,7 @@ class MMGenTestSuite(object):
|
||||||
t.expect("Encrypt key list? (y/N): ","y")
|
t.expect("Encrypt key list? (y/N): ","y")
|
||||||
t.hash_preset("new key list",'1')
|
t.hash_preset("new key list",'1')
|
||||||
t.passphrase_new("new key list",cfg['kapasswd'])
|
t.passphrase_new("new key list",cfg['kapasswd'])
|
||||||
t.written_to_file("Keys")
|
t.written_to_file("Secret keys",oo=True)
|
||||||
ok()
|
ok()
|
||||||
|
|
||||||
def refkeyaddrgen(self,name,walletfile):
|
def refkeyaddrgen(self,name,walletfile):
|
||||||
|
|
@ -1189,7 +1195,7 @@ class MMGenTestSuite(object):
|
||||||
t.license()
|
t.license()
|
||||||
t.tx_view()
|
t.tx_view()
|
||||||
for cnum in ('1','3'):
|
for cnum in ('1','3'):
|
||||||
t.expect_getend("Getting MMGen wallet data from file ")
|
# t.expect_getend("Getting MMGen wallet data from file ")
|
||||||
t.passphrase("MMGen wallet",cfgs[cnum]['wpasswd'])
|
t.passphrase("MMGen wallet",cfgs[cnum]['wpasswd'])
|
||||||
self.txsign_end(t)
|
self.txsign_end(t)
|
||||||
ok()
|
ok()
|
||||||
|
|
@ -1218,15 +1224,12 @@ class MMGenTestSuite(object):
|
||||||
def txsign4(self,name,f1,f2,f3,f4,f5):
|
def txsign4(self,name,f1,f2,f3,f4,f5):
|
||||||
non_mm_fn = os.path.join(cfg['tmpdir'],non_mmgen_fn)
|
non_mm_fn = os.path.join(cfg['tmpdir'],non_mmgen_fn)
|
||||||
t = MMGenExpect(name,"mmgen-txsign",
|
t = MMGenExpect(name,"mmgen-txsign",
|
||||||
["-d",cfg['tmpdir'],"-b",cfg['bw_params'],"-k",non_mm_fn,f1,f2,f3,f4,f5])
|
["-d",cfg['tmpdir'],"-i","brain","-b"+cfg['bw_params'],"-p1","-k",non_mm_fn,f1,f2,f3,f4,f5])
|
||||||
t.license()
|
t.license()
|
||||||
t.tx_view()
|
t.tx_view()
|
||||||
|
|
||||||
for cnum,desc,app in ('1',"incognito","incognito"),('3',"MMGen","MMGen"):
|
for cnum,desc in ('1',"incognito data"),('3',"MMGen wallet"):
|
||||||
t.expect_getend("Getting %s wallet data from file " % desc)
|
t.passphrase(("%s" % desc),cfgs[cnum]['wpasswd'])
|
||||||
t.passphrase("%s wallet"%app,cfgs[cnum]['wpasswd'])
|
|
||||||
if cnum == '1':
|
|
||||||
t.hash_preset("incog wallet",'1')
|
|
||||||
|
|
||||||
self.txsign_end(t)
|
self.txsign_end(t)
|
||||||
ok()
|
ok()
|
||||||
|
|
@ -1368,11 +1371,11 @@ class MMGenTestSuite(object):
|
||||||
def ref_hincog_conv_out(self,name,extra_uopts=[]):
|
def ref_hincog_conv_out(self,name,extra_uopts=[]):
|
||||||
ic_f = os.path.join(cfg['tmpdir'],"rand.data")
|
ic_f = os.path.join(cfg['tmpdir'],"rand.data")
|
||||||
hi_parms = "%s,%s" % (ic_f,ref_wallet_incog_offset)
|
hi_parms = "%s,%s" % (ic_f,ref_wallet_incog_offset)
|
||||||
hi_parms_legacy = "%s,%s,%s"%(ic_f,ref_wallet_incog_offset,cfg['seed_len'])
|
sl_parm = "-l" + str(cfg['seed_len'])
|
||||||
self.walletconv_out(name,
|
self.walletconv_out(name,
|
||||||
"hidden incognito data", "hi",
|
"hidden incognito data", "hi",
|
||||||
uopts=["-J",hi_parms] + extra_uopts,
|
uopts=["-J",hi_parms,sl_parm] + extra_uopts,
|
||||||
uopts_chk=["-G",hi_parms_legacy],
|
uopts_chk=["-H",hi_parms,sl_parm],
|
||||||
pw=True
|
pw=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -1411,8 +1414,8 @@ class MMGenTestSuite(object):
|
||||||
|
|
||||||
def ref_brain_chk(self,name,bw_file=ref_bw_file):
|
def ref_brain_chk(self,name,bw_file=ref_bw_file):
|
||||||
wf = os.path.join(ref_dir,bw_file)
|
wf = os.path.join(ref_dir,bw_file)
|
||||||
arg = "-b%s,%s" % (cfg['seed_len'],ref_bw_hash_preset)
|
args = ["-l%s" % cfg['seed_len'], "-p"+ref_bw_hash_preset]
|
||||||
self.keygen_chksum_chk(name,wf,cfg['ref_bw_seed_id'],[arg])
|
self.keygen_chksum_chk(name,wf,cfg['ref_bw_seed_id'],args)
|
||||||
|
|
||||||
def keygen_chksum_chk_hincog(self,name,seed_id,hincog_parm):
|
def keygen_chksum_chk_hincog(self,name,seed_id,hincog_parm):
|
||||||
t = MMGenExpect(name,"mmgen-keygen", ["-p1","-q","-S","-A"]+hincog_parm+["1"],extra_desc="(check)")
|
t = MMGenExpect(name,"mmgen-keygen", ["-p1","-q","-S","-A"]+hincog_parm+["1"],extra_desc="(check)")
|
||||||
|
|
@ -1425,7 +1428,9 @@ class MMGenTestSuite(object):
|
||||||
|
|
||||||
def keygen_chksum_chk(self,name,wf,seed_id,args=[],pw=False):
|
def keygen_chksum_chk(self,name,wf,seed_id,args=[],pw=False):
|
||||||
hp_arg = ["-p1"] if pw else []
|
hp_arg = ["-p1"] if pw else []
|
||||||
t = MMGenExpect(name,"mmgen-keygen", ["-q","-S","-A"]+args+hp_arg+[wf,"1"],extra_desc="(check)")
|
t = MMGenExpect(name,"mmgen-keygen",
|
||||||
|
["-l",str(cfg['seed_len']),"-q","-S","-A"]+args+hp_arg+[wf,"1"],
|
||||||
|
extra_desc="(check)")
|
||||||
if pw:
|
if pw:
|
||||||
t.passphrase("",cfg['wpasswd'])
|
t.passphrase("",cfg['wpasswd'])
|
||||||
t.expect("Encrypt key list? (y/N): ","\n")
|
t.expect("Encrypt key list? (y/N): ","\n")
|
||||||
|
|
@ -1448,20 +1453,21 @@ class MMGenTestSuite(object):
|
||||||
def ref_brain_chk_spc3(self,name):
|
def ref_brain_chk_spc3(self,name):
|
||||||
self.ref_brain_chk(name,bw_file=ref_bw_file_spc)
|
self.ref_brain_chk(name,bw_file=ref_bw_file_spc)
|
||||||
|
|
||||||
def ref_hincog_chk(self,name):
|
def ref_hincog_chk(self,name,desc="hidden incognito data"):
|
||||||
for wtype,desc,earg in ('hic_wallet','',[]), \
|
for wtype,edesc,earg in ('hic_wallet','',[]), \
|
||||||
('hic_wallet_old','(old format)',["-o"]):
|
('hic_wallet_old','(old format)',["-O"]):
|
||||||
ic_arg = "%s,%s,%s" % (
|
ic_arg = "%s,%s" % (
|
||||||
os.path.join(ref_dir,cfg[wtype]),
|
os.path.join(ref_dir,cfg[wtype]),
|
||||||
ref_wallet_incog_offset,cfg['seed_len']
|
ref_wallet_incog_offset
|
||||||
)
|
)
|
||||||
t = MMGenExpect(name,"mmgen-keygen",
|
t = MMGenExpect(name,"mmgen-keygen",["-l",str(cfg['seed_len']),
|
||||||
["-q","-A"]+earg+["-G"]+[ic_arg]+['1'],extra_desc=desc)
|
"-q","-A"]+earg+["-H"]+[ic_arg]+['1'],extra_desc=edesc)
|
||||||
t.passphrase("incognito wallet",cfg['wpasswd'])
|
t.hash_preset(desc,"1")
|
||||||
t.hash_preset("incog wallet","1")
|
t.passphrase(desc,cfg['wpasswd'])
|
||||||
if wtype == 'hic_wallet_old':
|
if wtype == 'hic_wallet_old':
|
||||||
t.expect("Is the seed ID correct? (Y/n): ","\n")
|
t.expect("Is the seed ID correct? (Y/n): ","\n")
|
||||||
chk = t.expect_getend("Valid incog data for seed ID ")
|
chk = t.expect_getend("Seed ID: ")
|
||||||
|
t.expect("Encrypt key list? (y/N): ","\n")
|
||||||
t.close()
|
t.close()
|
||||||
cmp_or_die(cfg['seed_id'],chk)
|
cmp_or_die(cfg['seed_id'],chk)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue