Old non-oo wallet code and files removed

test/test.py updated
This commit is contained in:
philemon 2015-05-01 10:39:45 +03:00
commit acd8eb26c5
21 changed files with 516 additions and 2680 deletions

View file

@ -12,6 +12,7 @@
* <a href=#11>Using the mnemonic and seed features</a>
* <a href=#12>Mnemonics and seeds — additional information</a>
* <a href=#13>Incognito wallets</a>
* <a href=#13a>Hidden incognito wallets</a>
### <a name=01>Basic Operations</a>
@ -22,7 +23,7 @@ On your offline computer, generate a wallet with a random seed:
$ 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
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
...
Address data saved to file '89ABCDEF[1-10].addrs'
Addresses written to file '89ABCDEF[1-10].addrs'
$ cat '89ABCDEF[1-10].addrs'
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.
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
16bNmyYISiptuvJG3X7MPwiiS4HYvD7ksE, "89ABCDEF:2" the equivalent of
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>
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
a new 'wallet.dat', which you'll use as your **tracking wallet**. Import your
four addresses into the new tracking wallet with the command:
existing 'wallet.dat' file out of harm's way. Start bitcoind and let it
generate a new 'wallet.dat', which you'll use as your **tracking wallet**.
Import your four addresses into the new tracking wallet with the command:
$ 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
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,
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
sort the outputs by four criteria: transaction ID, address, amount and
transaction age. Your overall balance in BTC appears at the top of the screen.
The list may be viewed in a pager or printed to file. For a wallet with ten
unspent outputs, the display might look something like this:
The list may optionally be viewed in a pager or printed to file. For a wallet
with ten unspent outputs, the display might look something like this:
UNSPENT OUTPUTS (sort order: reverse amount) Total BTC: 39.72
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):
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.
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
example. Of course the wallet should have a passphrase. Otherwise, anyone who
gains physical access to one of your backups can easily steal your coins.
example. You're advised to use a passphrase on your wallet. Otherwise, anyone
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
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
address with the largest balance, 10 BTC, into three roughly equal parts,
sending it to the addresses labeled "Storage 1", "Storage 2" and "Storage 3"
(89ABCDEF:2, 89ABCDEF:3 and 89ABCDEF:4).
small balances is a Good Idea. So you've decided to begin by breaking up your
address with the largest balance, 10 BTC, address 1F93Znz..., into three roughly
equal parts and send them to the addresses labeled "Storage 1", "Storage 2" and
"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
89ABCDEF {
1 16bNmyYISiptuvJG3X7MPwiiS4HYvD7ksE Donations
2 1AmkUxrfy5dMrfmeYwTxLxfIswUCcpeysc Storage 1
3 1HgYCsfqYzIg7LVVfDTp7gYJocJEiDAy6N Storage 2
4 14Tu3z1tiexXDonNsFIkvzqutE5E3pTK8s Storage 3
}
The following command will send 3.3 BTC to the first two addresses and the
remainder of the transaction's 10 BTC input to the third, subtracting a default
transaction fee of 0.001 BTC:
The following command does just this, sending 6.6 BTC of the transaction's 10
BTC input to the first two addresses and the remainder to the third address,
for which no amount has been specified:
$ mmgen-txcreate 1AmkUxrfy5dMrfmeYwTxLxfIswUCcpeysc,3.3 1HgYCsfqYzIg7LVVfDTp7gYJocJEiDAy6N,3.3 14Tu3z1tiexXDonNsFIkvzqutE5E3pTK8s
The bare address with no amount is the **change address**. MMGen will compute
the change amount (3.399 BTC in this case) automatically.
The address with no amount is the **change address**; MMGen will calculate
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
using MMGen addresses in place of their Bitcoin equivalents:
Note that the above transaction can be expressed much more concisely by
replacing the Bitcoin addresses with their MMGen equivalents:
$ 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
'-i' option above, plus an interactive menu. After quitting with 'q', you'll
be prompted to choose the transaction's inputs.
For this to work, the addresses must be imported into your tracking wallet,
which they should be in any case.
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:
Find the input with the 10 BTC balance in the list. This is input 1), so type
'1' and ENTER. After several more prompts and confirmations 'mmgen-txcreate'
will exit with the message:
Type your remembered '1' here and hit ENTER. After several more prompts and
confirmations, your transaction will be saved:
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
BTC, is conveniently included in the filename.
Note that the transaction has a unique ID, and the non-change spend amount of
6.6 BTC is included in the filename.
#### <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
to your offline computer for signing. You need to find the key for your
transaction's one input address, 1F9495H8EJL.... If the key in question is in a
bitcoin 'wallet.dat', there's an included command (a modified version of the
well-known pywallet utility) that will conveniently extract it for you:
to your offline computer for signing. For this you'll need the key corresponding
to the transaction's input address, 1F93Znz.... If the key in question is in a
bitcoind 'wallet.dat', copy 'wallet.dat' to your offline machine as well and use
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
...
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
not a problem, since the unused keys will be ignored (you can extract only the
keys you need using the '--keys-for-addrs' option). Now go ahead and sign the
transaction using this list of keys.
not a problem, since the unused keys will be ignored (if you wish, you can
extract only the one key you need using the '--keys-for-addrs' option). Now go
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
several Bitcoin wallets with balances, you can just dump all their keys and
merge them into a single file which you can use to sign all future transactions
with wallet.dat inputs:
several Bitcoin wallets with balances, you can just concatenate these lists into
a single file which you can use to sign all future transactions with
'wallet.dat' inputs:
$ mmgen-pywallet -k wallet1.dat
$ mmgen-pywallet -k wallet2.dat
$ mmgen-pywallet -k wallet3.dat
$ cat wd_*.keys > all_keys
For your future transactions with MMGen address inputs, you'll list the MMGen
seed source (wallet, mnemonic or seed file) on the command line after the
transaction file, and the required keys will be generated automatically, as in
this example:
Once you've migrated your funds to MMGen, such key files will no longer be
needed. Instead, you'll sign transactions by listing an MMGen seed source
(wallet, mnemonic or seed file) on the command line after the transaction,
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
...
Signed transaction saved to file tx_9D2C3A[1.23].sig
$ mmgen-txsign tx_ABCDE[1.2345].raw 1234567A-BCDEF123[256,3].mmdat
Transactions may contain a mixture of MMGen and non-MMGen inputs as well as
inputs with more than one MMGen seed ID. Just provide a seed source for each
seed ID on the command line.
Transactions may contain a mixture of MMGen and non-MMGen inputs, as well as
inputs with more than one MMGen Seed ID. Just list a seed source for each
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
have deal with keys directly again, because MMGen generates all keys on the fly
using the seed.
$ mmgen-txsign -k key_list my_tx.raw a.mmdat b.mmwords c.mmseed
#### <a name=07>Send a transaction (online computer):</a>
Now you're ready for the final step: broadcasting the transaction to the network.
Copy the 'tx_*.sig' file to your online computer, start bitcoind, if it's not
running, and execute the command:
Now you're ready for the final step: broadcasting the transaction to the
network. Copy just-created signed transaction file to your online computer,
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
confirmation before the transaction is actually sent.
Like all MMGen commands, 'mmgen-txsend' is interactive, so you'll be prompted
before the transaction is actually sent.
Once the transaction's confirmed by the network, your three new MMGen addresses
will appear on the listing of 'mmgen-txcreate -i'. Type 'm' at the menu to
see them displayed in MMGen format.
Once the transaction is broadcast to the network, you can view your three new
MMGen addresses and their balances:
$ 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
first funds under MMGen's control.
first funds under MMGen control.
### <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:
$ 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
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'.
Though some consider 128 bits of entropy to provide adequate security for the
foreseeable future, you should stick to the default 256-bit seed length if
you're not planning to use the mnemonic feature.
foreseeable future, it's advisable to stick to the default 256-bit seed length
if you're not planning to use the mnemonic feature.
NOTE: MMGen mnemonics are generated from the Electrum wordlist, but using
ordinary base conversion instead of Electrum's more complicated algorithm.
Generate addresses 1-11 of seed 89ABCDEF using the mnemonic instead of the
wallet:
The mnemonic file may be used any place you'd use a MMGen wallet with the same
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.
You'll see they're the same.
The resulting address file will be identical to one generated by any wallet with
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
Seed ID.
Seed files bear the extension '.mmseed' and are listed on the command line the
same way mnemonic files are.
Seed files bear the extension '.mmseed' and are generated and used exactly
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
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
0fe02f XnyC NfPH piuW dQ2d nM47 VU
As you can see, the latter file is short enough to be memorized or written down
on a scrap of paper. From the unix command line, you can test your memory using
the seed's checksum ("0fe02f" in this example) as follows:
As you can see, seed files are short enough to be easily written out by hand or
even memorized. And their built-in checksum makes it easy to test your memory
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
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'
0fe02f
#### <a name=12>Mnemonics and seeds — additional information:</a>
With the '-m' or '-s' option, MMGen commands that take mnemonic and seed
data may receive the data from a prompt instead of a file.
MMGen commands that take mnemonic and seed data may receive the data from a
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
standard output instead of file with the '-S' option. This feature has
intentionally been made optional to safeguard against looking-over-the-shoulder,
Van Eyck phreaking and other side-channel attacks. MMGen commands never print
private data to the screen unless explicitly asked to.
$ mmgen-walletconv -i words
...
Enter mnemonic data: <type or paste your mnemonic here>
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
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.
This also has obvious security benefits, ensuring that no sensitive data
remains on disk after your computer's been powered down.
'-d /dev/shm' to write keys and seeds to volatile memory instead of disk,
ensuring that no trace of this sensitive data remains once your computer's been
powered down.
#### <a name=13><a name=incog>Incognito wallets</a>
A wallet exported to incognito format is indistinguishable from random data,
allowing you to hide your wallet at an offset within a random-filled file or
partition. Thus both the location and nature of the data are unknown to a
potential attacker, who in addition cannot be sure that the file or partition
contains anything useful at all, barring any inside knowledge.
An incognito format wallet is indistinguishable from random data, allowing you
to hide your wallet at an offset within a random-data-filled file or partition.
Barring any inside knowledge, a potential attacker has no way of knowing where
the wallet is hidden, or whether the file or partition contains anything of
interest at all for that matter.
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
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
there.
again that any potential attacker even knows or suspects you have an MMGen
wallet hidden there.
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.
These can be changed using the 'mmgen-passchg' utility:
storage, you're advised to use a strong scrypt (hash) preset and a strong
password. These can be changed using the 'mmgen-passchg' utility:
$ mmgen-passchg -p 5 89ABCDEF-01234567[256,3].mmdat
...
Hash preset has changed (3 -> 5)
Enter new passphrase: <my new strong passphrase>
Hash preset of wallet: '3'
Enter old passphrase for MMGen wallet: <old weak passphrase>
...
Wallet saved to file '89ABCDEF-87654321[256,5].mmdat'
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
Hash preset changed to '5'
Enter new passphrase for MMGen wallet: <new strong passphrase>
...
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
search through a file or partition and locate your wallet if you've forgotten
where you hid it (see below).
The scrypt preset is the numeral in the wallet filename following the seed
length. As you can see, it's now changed to '5'. Now export your new toughened
wallet to incognito format, using the '-k' option to leave the passphrase
unchanged:
Repeat the same export operation, but output to hexadecimal:
$ mmgen-walletchk -X 89ABCDEF-87654321[256,5].mmdat
$ mmgen-walletconv -k -o incog 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
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
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
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
out on a printer and you're ready to go.
Indistinguishable from any random hex dump, this data is ideally suited for a
paper wallet that could potentially fall into the wrong hands.
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
Addresses written to file '89ABCDEF[100-110].addrs'
Or sign a transaction like this:
Addresses written to file '89ABCDEF[101-110].addrs'
$ 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
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:
##### <a name=13a><a name=incog>Hidden incognito wallets</a>
$ 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
entropy and a progress meter:
$ mmgen-tool -r40 rand2file random.dat 1G
Now export your wallet to hidden incognito format, hiding it in this 1GB random
file at offset 123456789:
$ mmgen-walletchk -G random.dat,123456789 89ABCDEF-87654321[256,5].mmdat
$ mmgen-walletconv -k -o hincog -J random.dat,123456789 89ABCDEF-87654321[256,5].mmdat
...
New Incog Wallet ID: ED1F2ACB
...
Requested file 'random.dat' does not exist. Create? (Y/n): Y
Enter file size: 1G
...
Incog ID: ED1F2ACB
Data written to file 'random.dat' at offset 123456789
The altered random file can now be uploaded to a cloud storage service, for
example, or some other, preferably non-public, location on the Net (in a
real-life situation you'll choose a less obvious offset than '123456789'
though, won't you?).
Your "random" file can now be uploaded to a cloud storage service, for example,
or some other, preferably non-public, location on the Net (in a real-life
situation you will choose a less obvious offset than '123456789' though, won't
you?).
If at some point in the future you download this file to recover your wallet
data but find you've forgotten the offset, you can recover it using the saved
Incog ID as follows:
Now let's say at some point in the future you download this file to recover
your wallet and realize you've forgotten the offset where the data is hidden.
If you've saved your Incog ID, you're in luck:
$ mmgen-tool find_incog_data random.dat ED1F2ACB
...
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:
$ 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
Note that the seed length parameter here will always be '256' unless you're
using a non-default seed length.
Signed transaction written to file 'tx_ABCDEF[0.1].sig'

View file

@ -26,7 +26,6 @@ from hashlib import new as hashlib_new
from binascii import hexlify, unhexlify
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.tx import *
from mmgen.obj import *
@ -367,10 +366,9 @@ class AddrInfo(MMGenObject):
def make_addrdata_chksum(self):
nchars = 24
lines=[" ".join([str(e.idx),e.addr]+([e.wif] if self.has_keys else []))
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):

View file

@ -30,15 +30,6 @@ from mmgen.util import *
from mmgen.term import get_char
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': """
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.
@ -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
keystrokes will also be used as a source of randomness.
""",
'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(),
# '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.
# """,
# '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):
@ -211,222 +211,6 @@ def get_random(length):
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
salt_len,sha256_len,nonce_len = 32,32,32

View file

@ -25,7 +25,7 @@ from mmgen.util import die,get_extension,check_infile
class Filename(MMGenObject):
def __init__(self,fn,ftype=None):
def __init__(self,fn,ftype=None,write=False):
self.name = fn
self.dirname = os.path.dirname(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))
# TODO: Check for Windows
mode = (os.O_RDONLY,os.O_RDWR)[int(write)]
import stat
if stat.S_ISBLK(os.stat(fn).st_mode):
fd = os.open(fn, os.O_RDONLY)
self.size = os.lseek(fd, 0, os.SEEK_END)
os.close(fd)
try:
fd = os.open(fn, mode)
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:
self.size = os.stat(fn).st_size

View file

@ -49,7 +49,7 @@ prog_name = os.path.basename(sys.argv[0])
author = "Philemon"
email = "<mmgen-py@yandex.com>"
Cdates = '2013-2015'
version = '0.8.0'
version = '0.8.1rc1'
required_opts = [
"quiet","verbose","debug","outdir","echo_passphrase","passwd_file",

View file

@ -22,14 +22,9 @@ main.py - Script launcher for the MMGen suite
def launch(what):
import os
t = "MMGEN_USE_OLD_SCRIPTS"
if not (t in os.environ and os.environ[t]):
if what in ("walletgen","walletchk","passchg"):
what = "wallet"
if what == "walletconv": what = "wallet"
if what == "keygen": what = "addrgen"
if what in ("walletgen","walletchk","walletconv","passchg"):
what = "wallet"
if what == "keygen": what = "addrgen"
try: import termios
except: __import__("mmgen.main_" + what) # Windows

View file

@ -28,48 +28,50 @@ import mmgen.opt as opt
from mmgen.util import *
from mmgen.crypto import *
from mmgen.addr import *
from mmgen.seed import SeedSource
if sys.argv[0].split("-")[-1] == "keygen":
gen_what = "keys"
opt_filter = None
note1 = """
By default, both addresses and secret keys are generated.
""".strip()
else:
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 = {
'sets': [('print_checksum',True,'quiet',True)],
'desc': """Generate a range or list of {what} from an {pnm} wallet,
mnemonic, seed or password""".format(what=gen_what,pnm=g.proj_name),
'usage':"[opts] [infile] <address range or list>",
'options': """
-h, --help Print this help message
-A, --no-addresses Print only secret keys, no addresses
-d, --outdir= d Specify an alternate directory 'd' for output
-c, --save-checksum Save address list checksum to file
-e, --echo-passphrase Echo passphrase or mnemonic to screen upon entry
-H, --show-hash-presets Show information on available hash presets
-K, --no-keyconv Force use of internal libraries for address gener-
ation, even if 'keyconv' is available
-l, --seed-len= N Length of seed. Options: {seed_lens}
(default: {g.seed_len})
-p, --hash-preset= p Use scrypt.hash() parameters from preset 'p' when
hashing password (default: '{g.hash_preset}')
-P, --passwd-file= f Get {pnm} wallet passphrase from file 'f'
-q, --quiet Suppress warnings; overwrite files without
prompting
-S, --stdout Print {what} to stdout
-v, --verbose Produce more verbose output
-x, --b16 Print secret keys in hexadecimal too
-b, --from-brain= l,p Generate {what} from a user-created password,
i.e. a "brainwallet", using seed length 'l' and
hash preset 'p' (comma-separated)
-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
-h, --help Print this help message.
-A, --no-addresses Print only secret keys, no addresses.
-c, --print-checksum Print address list checksum and exit.
-d, --outdir= d Output files to directory 'd' instead of working dir.
-e, --echo-passphrase Echo passphrase or mnemonic to screen upon entry.
-i, --in-fmt= f Input is from wallet format 'f' (see FMT CODES below).
-H, --hidden-incog-input-params=f,o Read hidden incognito data from file
'f' at offset 'o' (comma-separated).
-O, --old-incog-fmt Specify old-format incognito input.
-K, --no-keyconv Force use of internal libraries for address genera-
tion, even if 'keyconv' is available.
-l, --seed-len= l Specify wallet seed length of 'l' bits. This option
is required only for brainwallet and incognito inputs
with non-standard (< {g.seed_len}-bit) seed lengths.
-p, --hash-preset= p Use the scrypt hash parameters defined by preset 'p'
for password hashing (default: '{g.hash_preset}').
-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.
-S, --stdout Print {what} to stdout.
-v, --verbose Produce more verbose output.
-x, --b16 Print secret keys in hexadecimal too.
""".format(
seed_lens=", ".join([str(i) for i in g.seed_lens]),
pnm=g.proj_name,
@ -78,96 +80,63 @@ opts_data = {
'notes': """
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
generation.
{n}
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.
{o.pw_note}
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.
{o.bw_note}
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
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 "")
FMT CODES:
{f}
""".format(
n=note1,
f="\n ".join(SeedSource.format_fmt_codes().split("\n")),
o=opt.opts
)
}
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)
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([
opt.from_mnemonic,opt.from_brain,opt.from_seed,opt.from_incog_hidden]):
infile,addr_idx_arg = "",cmd_args[0]
elif len(cmd_args) == 2:
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)
addrlist_arg = cmd_args.pop()
addr_idxs = parse_addr_idxs(addrlist_arg)
if not addr_idxs:
die(1,"'%s': invalid address list argument" % addrlist_arg)
do_license_msg()
# Interact with user:
if gen_what == "keys" and not opt.quiet:
confirm_or_exit(wmsg['unencrypted_secret_keys'], 'continue')
opt.gen_what = "a" if gen_what == "addresses" \
else "k" if opt.no_addresses else "ka"
# Generate data:
ss = SeedSource(*cmd_args)
seed = get_seed_retry(infile)
opt.gen_what = "a" if gen_what == "addresses" else (
"k" if opt.no_addresses else "ka")
ainfo = generate_addrs(seed,addr_idxs)
ainfo = generate_addrs(ss.seed.data,addr_idxs)
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:
w = "key-address" if 'k' in opt.gen_what else "address"
write_to_file(outfile_base+"."+g.addrfile_chksum_ext,
ainfo.checksum+"\n","%s data checksum" % w,True,True,False)
if 'a' in opt.gen_what and opt.print_checksum:
Die(0,ainfo.checksum)
if 'k' in opt.gen_what and keypress_confirm("Encrypt key list?"):
addrdata_str = mmgen_encrypt(addrdata_str,"new key list","")
enc_ext = "." + g.mmenc_ext
else: enc_ext = ""
# Output data:
if opt.stdout or not sys.stdout.isatty():
if enc_ext and sys.stdout.isatty():
msg("Cannot write encrypted data to screen. Exiting")
sys.exit(2)
write_to_stdout(addrdata_str,gen_what,ask_terminal=(gen_what=="keys"
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)
ext = (g.keyfile_ext,g.keyaddrfile_ext)[int("ka" in opt.gen_what)]
ext = (g.addrfile_ext,ext)[int("k" in opt.gen_what)]
outfile = "%s.%s%s" % (outfile_base, ext, enc_ext)
ask_tty = "k" in opt.gen_what and not opt.quiet
if gen_what == "keys": gen_what = "secret keys"
write_data_to_file(outfile,addrdata_str,gen_what,ask_tty=ask_tty)

View file

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

View file

@ -26,44 +26,50 @@ import mmgen.globalvars as g
import mmgen.opt as opt
from mmgen.tx import *
from mmgen.util import do_license_msg,dmsg
from mmgen.seed import SeedSource
pnm = g.proj_name
pnl = pnm.lower()
opts_data = {
'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': """
-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
-i, --info Display information about the transaction and exit
-t, --terse-info Like '--info', but produce more concise output
-I, --tx-id Display transaction ID and exit
-k, --keys-from-file= f Provide additional keys for non-{pnm} addresses
-K, --no-keyconv Force use of internal libraries for address gener-
ation, even if 'keyconv' is available
-M, --mmgen-keys-from-file=f Provide keys for {pnm} addresses in a key-
address file (output of '{pnl}-keygen'). Permits
online signing without an {pnm} seed source.
The key-address file is also used to verify
{pnm}-to-BTC mappings, so its checksum should
be recorded by the user.
-P, --passwd-file= f Get {pnm} wallet or bitcoind passphrase from file 'f'
-q, --quiet Suppress warnings; overwrite files without
prompting
-v, --verbose Produce more verbose output
-b, --from-brain= l,p Generate keys from a user-created password,
i.e. a "brainwallet", using seed length 'l' and
hash preset 'p'
-w, --use-wallet-dat Get keys from a running bitcoind
-g, --from-incog Generate keys from an incognito wallet
-X, --from-incog-hex Generate keys from an incognito hexadecimal 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 keys from an electrum-like mnemonic
-s, --from-seed Generate keys from a seed in .{g.seed_ext} format
-h, --help Print this help message.
-b, --brain-params=l,p Use seed length 'l' and hash preset 'p' for brain-
wallet input. Required only if these parameters dif-
fer from those of an incognito wallet also being used
as a seed source.
-d, --outdir= d Specify an alternate directory 'd' for output.
-D, --tx-id Display transaction ID and exit.
-e, --echo-passphrase Print passphrase to screen when typing it.
-i, --in-fmt= f Input is from wallet format 'f' (see FMT CODES below).
-H, --hidden-incog-input-params=f,o Read hidden incognito data from file
'f' at offset 'o' (comma-separated).
-O, --old-incog-fmt Specify old-format incognito input.
-l, --seed-len= l Specify wallet seed length of 'l' bits. This option
is required only for brainwallet and incognito inputs
with non-standard (< {g.seed_len}-bit) seed lengths.
-p, --hash-preset=p Use the scrypt hash parameters defined by preset 'p'
for password hashing (default: '{g.hash_preset}').
-z, --show-hash-presets Show information on available hash presets.
-k, --keys-from-file=f Provide additional keys for non-{pnm} addresses
-K, --no-keyconv Force use of internal libraries for address gener-
ation, even if 'keyconv' is available.
-M, --mmgen-keys-from-file=f Provide keys for {pnm} addresses in a key-
address file (output of '{pnl}-keygen'). Permits
online signing without an {pnm} seed source.
The key-address file is also used to verify
{pnm}-to-BTC mappings, so its checksum should
be recorded by the user.
-P, --passwd-file= f Get {pnm} wallet or bitcoind passphrase from file 'f'
-q, --quiet Suppress warnings; overwrite files without
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),
'notes': """
@ -91,7 +97,13 @@ Seed data supplied in files must have the following extensions:
seed: '.{g.seed_ext}'
mnemonic: '.{g.mn_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 = {
@ -110,23 +122,20 @@ def get_seed_for_seed_id(seed_id,infiles,saved_seeds):
if seed_id in saved_seeds.keys():
return saved_seeds[seed_id]
from mmgen.crypto import get_seed_retry
while True:
if infiles:
seed = get_seed_retry(infiles.pop(0))
elif any([opt.from_brain,opt.from_mnemonic,opt.from_seed,opt.from_incog]):
ss = SeedSource(infiles.pop(0),ignore_in_fmt=True)
elif opt.in_fmt:
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))
else:
msg("ERROR: No seed source found for seed ID: %s" % seed_id)
sys.exit(2)
sid = make_chksum_8(seed)
saved_seeds[sid] = seed
saved_seeds[ss.seed.sid] = ss.seed.data
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):
@ -285,7 +294,7 @@ def get_keys_from_keylist(kldata,other_addrs):
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()
for i in infiles: check_infile(i)

View file

@ -33,10 +33,12 @@ usage = "[opts] [infile]"
nargs = 1
iaction = "convert"
oaction = "convert"
bw_note = opt.opts.bw_note
pw_note = opt.opts.pw_note
if invoked_as == "gen":
desc = "Generate an {pnm} wallet from a random seed"
opt_filter = "hdoJlLpPqrSv"
opt_filter = "ehdoJlLpPqrSvz"
usage = "[opts]"
oaction = "output"
nargs = 0
@ -45,21 +47,25 @@ elif invoked_as == "conv":
opt_filter = None
elif invoked_as == "chk":
desc = "Check validity of an {pnm} wallet"
opt_filter = "hiHOlpPqrv"
opt_filter = "ehiHOlpPqrvz"
iaction = "input"
elif invoked_as == "passchg":
desc = "Change the password, hash preset or label of an {pnm} wallet"
opt_filter = "hdiHkKOlLmpPqrSv"
opt_filter = "ehdiHkKOlLmpPqrSvz"
iaction = "input"
bw_note = ""
else:
die(1,"'%s': unrecognized invocation" % bn)
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),
'usage': usage,
'options': """
-h, --help Print this help message.
-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).
-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
@ -78,20 +84,28 @@ opts_data = {
-m, --keep-label Reuse label of input wallet for output wallet.
-p, --hash-preset= p Use the scrypt hash parameters defined by preset 'p'
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.
-r, --usr-randchars=n Get 'n' characters of additional randomness from user
(min={g.min_urandchars}, max={g.max_urandchars}, default={g.usr_randchars}).
-S, --stdout Write wallet data to stdout instead of file.
-v, --verbose Produce more verbose output.
FMT CODES:
{f}
""".format(
g=g,
iaction=capfirst(iaction),
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))]
)
}

View file

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

View file

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

View file

@ -26,6 +26,20 @@ import mmgen.share.Opts
import opt
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():
Msg("USAGE: %s %s" % (g.prog_name, usage_txt))
sys.exit(2)
@ -175,9 +189,10 @@ def check_opts(usr_opts): # Returns false if any check fails
(val,desc,n,sepword))
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)):
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 True
@ -212,15 +227,14 @@ def check_opts(usr_opts): # Returns false if any check fails
desc = "parameter for '%s' option" % fmt_opt(key)
from mmgen.util import check_infile,check_outfile,check_outdir
# Check for file existence and readability
from mmgen.util import check_infile
if key in ('keys_from_file','mmgen_keys_from_file',
'passwd_file','keysforaddrs','comment_file'):
check_infile(val) # exits on error
continue
if key == 'outdir':
from mmgen.util import check_outdir
check_outdir(val) # exits on error
elif key == 'label':
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):
die(1,"Output to brainwallet format unsupported")
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':
check_infile(val.split(",")[0])
check_infile(a[0],blkdev_ok=True)
key2 = 'in_fmt'
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'
if hasattr(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" % (
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':
if not opt_is_int(val,desc): return False
if not opt_is_in_list(int(val),g.seed_lens,desc): return False
elif key == 'hash_preset':
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':
if val == 0: continue
if not opt_is_int(val,desc): return False

View file

@ -64,7 +64,7 @@ class SeedSource(MMGenObject):
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):
opt_sstype = cls.fmt_code_to_sstype(opt)
@ -91,7 +91,7 @@ class SeedSource(MMGenObject):
f = Filename(fn,ftype="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)
me = super(cls,cls).__new__(sstype)
@ -109,7 +109,7 @@ class SeedSource(MMGenObject):
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.msg = {}
@ -692,16 +692,26 @@ class Brainwallet (SeedSourceEnc):
ext = "mmbrain"
# 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):
self.brainpasswd = " ".join(self.fmt_data.split())
return True
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...")
# Use buflen arg of scrypt.hash() to get seed of desired length
seed = scrypt_hash_passphrase(self.brainpasswd, "",
self.ssdata.hash_preset, buflen=opt.seed_len/8)
d.hash_preset, buflen=seed_len/8)
vmsg("Done")
self.seed = Seed(seed)
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")]
if fn.size < d.hincog_offset + d.target_data_len:
die(1,
"%s file 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))
"%s file '%s' has length %s, too short to %s %s bytes of data at offset %s"
% (m,fn.name,fn.size,action,d.target_data_len,d.hincog_offset))
def _get_data(self):
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" %
(self.infile.name,d.hincog_offset), "Data read from file")
# overrides method in SeedSource
def write_to_file(self):
d = self.ssdata
@ -972,9 +981,10 @@ harder to find, you're advised to choose a much larger file size than this.
else:
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:
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.close(fh)
msg("%s written to file '%s' at offset %s" % (
capfirst(self.desc),
f.name,d.hincog_offset))
capfirst(self.desc),f.name,d.hincog_offset))

View file

@ -39,7 +39,7 @@ def process_opts(argv,opts_data,short_opts,long_opts):
opts_data['prog_name'] = os.path.basename(sys.argv[0])
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:
print str(err); sys.exit(2)

View file

@ -132,11 +132,11 @@ def suf(arg,suf_type):
def get_extension(f):
return os.path.splitext(f)[1][1:]
def make_chksum_N(s,n,sep=False):
if n%4 or not (4 <= n <= 64): return False
def make_chksum_N(s,nchars,sep=False):
if nchars%4 or not (4 <= nchars <= 64): return False
s = sha256(sha256(s).digest()).hexdigest().upper()
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):
s = sha256(sha256(s).digest()).hexdigest()[:8].upper()
@ -273,36 +273,43 @@ def open_file_or_exit(filename,mode):
return f
def check_file_type_and_access(fname,ftype):
def check_file_type_and_access(fname,ftype,blkdev_ok=False):
import os, stat
typ2,tdesc2,access,action = (stat.S_ISLNK,"symbolic link",os.R_OK,"read")\
if ftype == "input file" else (stat.S_ISBLK,"block device",os.W_OK,"writ")
a = ((os.R_OK,"read"),(os.W_OK,"writ"))
access,m = a[int(ftype in ("output file","output directory"))]
if ftype == "directory":
typ1,typ2,tdesc = stat.S_ISDIR,stat.S_ISDIR,"directory"
else:
typ1,tdesc = stat.S_ISREG,"regular file or "+tdesc2
ok_types = [
(stat.S_ISREG,"regular file"),
(stat.S_ISLNK,"symbolic link")
]
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
except:
msg("Unable to stat requested %s '%s'" % (ftype,fname))
sys.exit(1)
if not (typ1(mode) or typ2(mode)):
msg("Requested %s '%s' is not a %s" % (ftype,fname,tdesc))
for t in ok_types:
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)
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)
return True
def check_infile(f): return check_file_type_and_access(f,"input file")
def check_outfile(f): return check_file_type_and_access(f,"output file")
def check_outdir(f): return check_file_type_and_access(f,"directory")
def check_infile(f,blkdev_ok=False):
return check_file_type_and_access(f,"input file",blkdev_ok=blkdev_ok)
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):

View file

@ -21,7 +21,7 @@ from distutils.core import setup
setup(
name = 'mmgen',
description = 'A complete Bitcoin cold-storage solution for the command line',
version = '0.8.0',
version = '0.8.1rc1',
author = 'Philemon',
author_email = 'mmgen-py@yandex.com',
url = 'https://github.com/mmgen/mmgen',
@ -52,15 +52,12 @@ setup(
'mmgen.main',
'mmgen.main_addrgen',
'mmgen.main_addrimport',
'mmgen.main_passchg',
'mmgen.main_pywallet',
'mmgen.main_tool',
'mmgen.main_txcreate',
'mmgen.main_txsend',
'mmgen.main_txsign',
'mmgen.main_wallet',
'mmgen.main_walletchk',
'mmgen.main_walletgen',
'mmgen.share.__init__',
'mmgen.share.Opts',

File diff suppressed because it is too large Load diff

View file

@ -13,6 +13,7 @@ import mmgen.opt as opt
from mmgen.util import mmsg,mdie,Msg,die,capfirst
from mmgen.test import *
tb_cmd = "scripts/traceback.py"
hincog_fn = "rand_data"
hincog_bytes = 1024*1024
hincog_offset = 98765
@ -28,8 +29,8 @@ ref_wallet_hash_preset = "1"
ref_wallet_incog_offset = 123
ref_bw_hash_preset = "1"
ref_bw_file = "brainwallet"
ref_bw_file_spc = "brainwallet-spaced"
ref_bw_file = "wallet.mmbrain"
ref_bw_file_spc = "wallet-spaced.mmbrain"
ref_kafile_pass = "kafile password"
ref_kafile_hash_preset = "1"
@ -381,8 +382,9 @@ opts_data = {
-q, --quiet Produce minimal output. Suppress dependency info
-s, --system Test scripts and modules installed on system rather than
those in the repo root
-t, --traceback Run the command inside the '{tb_cmd}' script
-v, --verbose Produce more verbose output
""",
""".format(tb_cmd=tb_cmd),
'notes': """
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))
sys.exit()
else:
if opt.traceback:
cmd_args = [mmgen_cmd] + cmd_args
mmgen_cmd = tb_cmd
self.p = pexpect.spawn(mmgen_cmd,cmd_args)
if opt.exact_output: self.p.logfile = sys.stdout
@ -580,7 +585,7 @@ class MMGenExpect(object):
passphrase+"\n",regex=True)
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)
def written_to_file(self,desc,overwrite_unlikely=False,query="Overwrite? ",oo=False):
@ -897,12 +902,11 @@ class MMGenTestSuite(object):
t.license()
t.passphrase("MMGen wallet",cfg['wpasswd'])
t.expect("Passphrase is OK")
t.expect("[0-9]+ addresses generated",regex=True)
chk = t.expect_getend(r"Checksum for address data .*?: ",regex=True)
if check_ref:
refcheck("address data checksum",chk,cfg['addrfile_chk'])
return
t.written_to_file("Addresses")
t.written_to_file("Addresses",oo=True)
ok()
def refaddrgen(self,name,walletfile):
@ -1084,11 +1088,11 @@ class MMGenTestSuite(object):
self.export_incog(
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
add_arg = ([],["-S"])[int(stdout)]
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.expect_getend("Valid %s for seed ID " % desc)
vmsg("Comparing generated checksum with checksum from previous address file")
@ -1098,16 +1102,19 @@ class MMGenTestSuite(object):
# t.no_overwrite()
ok()
def addrgen_mnemonic(self,name,walletfile,foo):
self.addrgen_seed(name,walletfile,foo,desc="mnemonic",arg="-m")
def addrgen_mnemonic(self,name,wf,foo):
self.addrgen_seed(name,wf,foo,desc="mnemonic data",in_fmt="words")
def addrgen_incog(self,name,walletfile,foo,args=["-g"]):
t = MMGenExpect(name,"mmgen-addrgen",args+["-d",
cfg['tmpdir'],walletfile,cfg['addr_idx_list']])
def addrgen_incog(self,name,wf=[],foo="",in_fmt="i",
desc="incognito data",args=[]):
t = MMGenExpect(name,"mmgen-addrgen",
args+["-i"+in_fmt,"-d",cfg['tmpdir']]+
([wf] if wf else [])+
[cfg['addr_idx_list']])
t.license()
t.expect_getend("Incog ID: ")
t.passphrase("incognito wallet data \w{8}", cfg['wpasswd'])
t.hash_preset("incog wallet",'1')
t.expect_getend("Incog Wallet ID: ")
t.hash_preset(desc,'1')
t.passphrase("%s \w{8}" % desc, cfg['wpasswd'])
vmsg("Comparing generated checksum with checksum from address file")
chk = t.expect_getend(r"Checksum for address data .*?: ",regex=True)
t.close()
@ -1115,19 +1122,18 @@ class MMGenTestSuite(object):
# t.no_overwrite()
ok()
def addrgen_incog_hex(self,name,walletfile,foo):
self.addrgen_incog(name,walletfile,foo,args=["-X"])
def addrgen_incog_hex(self,name,wf,foo):
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)
self.addrgen_incog(name,walletfile,foo,
args=["-G","%s,%s,%s"%(rf,hincog_offset,hincog_seedlen)])
self.addrgen_incog(name,[],"",in_fmt="hi",desc="hidden incognito data",
args=["-H","%s,%s"%(rf,hincog_offset),"-l",str(hincog_seedlen)])
def keyaddrgen(self,name,walletfile,check_ref=False):
t = MMGenExpect(name,"mmgen-keygen",
["-d",cfg['tmpdir'],walletfile,cfg['addr_idx_list']])
t.license()
t.expect("Type uppercase 'YES' to confirm: ","YES\n")
t.passphrase("MMGen wallet",cfg['wpasswd'])
chk = t.expect_getend(r"Checksum for key-address data .*?: ",regex=True)
if check_ref:
@ -1136,7 +1142,7 @@ class MMGenTestSuite(object):
t.expect("Encrypt key list? (y/N): ","y")
t.hash_preset("new key list",'1')
t.passphrase_new("new key list",cfg['kapasswd'])
t.written_to_file("Keys")
t.written_to_file("Secret keys",oo=True)
ok()
def refkeyaddrgen(self,name,walletfile):
@ -1189,7 +1195,7 @@ class MMGenTestSuite(object):
t.license()
t.tx_view()
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'])
self.txsign_end(t)
ok()
@ -1218,15 +1224,12 @@ class MMGenTestSuite(object):
def txsign4(self,name,f1,f2,f3,f4,f5):
non_mm_fn = os.path.join(cfg['tmpdir'],non_mmgen_fn)
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.tx_view()
for cnum,desc,app in ('1',"incognito","incognito"),('3',"MMGen","MMGen"):
t.expect_getend("Getting %s wallet data from file " % desc)
t.passphrase("%s wallet"%app,cfgs[cnum]['wpasswd'])
if cnum == '1':
t.hash_preset("incog wallet",'1')
for cnum,desc in ('1',"incognito data"),('3',"MMGen wallet"):
t.passphrase(("%s" % desc),cfgs[cnum]['wpasswd'])
self.txsign_end(t)
ok()
@ -1368,11 +1371,11 @@ class MMGenTestSuite(object):
def ref_hincog_conv_out(self,name,extra_uopts=[]):
ic_f = os.path.join(cfg['tmpdir'],"rand.data")
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,
"hidden incognito data", "hi",
uopts=["-J",hi_parms] + extra_uopts,
uopts_chk=["-G",hi_parms_legacy],
uopts=["-J",hi_parms,sl_parm] + extra_uopts,
uopts_chk=["-H",hi_parms,sl_parm],
pw=True
)
@ -1411,8 +1414,8 @@ class MMGenTestSuite(object):
def ref_brain_chk(self,name,bw_file=ref_bw_file):
wf = os.path.join(ref_dir,bw_file)
arg = "-b%s,%s" % (cfg['seed_len'],ref_bw_hash_preset)
self.keygen_chksum_chk(name,wf,cfg['ref_bw_seed_id'],[arg])
args = ["-l%s" % cfg['seed_len'], "-p"+ref_bw_hash_preset]
self.keygen_chksum_chk(name,wf,cfg['ref_bw_seed_id'],args)
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)")
@ -1425,7 +1428,9 @@ class MMGenTestSuite(object):
def keygen_chksum_chk(self,name,wf,seed_id,args=[],pw=False):
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:
t.passphrase("",cfg['wpasswd'])
t.expect("Encrypt key list? (y/N): ","\n")
@ -1448,20 +1453,21 @@ class MMGenTestSuite(object):
def ref_brain_chk_spc3(self,name):
self.ref_brain_chk(name,bw_file=ref_bw_file_spc)
def ref_hincog_chk(self,name):
for wtype,desc,earg in ('hic_wallet','',[]), \
('hic_wallet_old','(old format)',["-o"]):
ic_arg = "%s,%s,%s" % (
def ref_hincog_chk(self,name,desc="hidden incognito data"):
for wtype,edesc,earg in ('hic_wallet','',[]), \
('hic_wallet_old','(old format)',["-O"]):
ic_arg = "%s,%s" % (
os.path.join(ref_dir,cfg[wtype]),
ref_wallet_incog_offset,cfg['seed_len']
ref_wallet_incog_offset
)
t = MMGenExpect(name,"mmgen-keygen",
["-q","-A"]+earg+["-G"]+[ic_arg]+['1'],extra_desc=desc)
t.passphrase("incognito wallet",cfg['wpasswd'])
t.hash_preset("incog wallet","1")
t = MMGenExpect(name,"mmgen-keygen",["-l",str(cfg['seed_len']),
"-q","-A"]+earg+["-H"]+[ic_arg]+['1'],extra_desc=edesc)
t.hash_preset(desc,"1")
t.passphrase(desc,cfg['wpasswd'])
if wtype == 'hic_wallet_old':
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()
cmp_or_die(cfg['seed_id'],chk)