mmgen-passgen: generate passwords from your MMGen seed
- frees you from having to back up or remember passwords
- generate passwords in either base32 or base58 format
- user-configurable password length
- password generation is keyed to an ID string, allowing you to generate
separate sets of passwords for each online account
EXAMPLE:
mmgen-passgen yourname@nowhere.com 1-10
(generates ten passwords for your email account using your default wallet)
For more examples and detailed usage information, type 'mmgen-passgen --help'
This is the new implementation using hmac. It supersedes commit 85cf5b3
Mnemonic entry mode:
- Word-by-word mnemonic entry at the prompt
- Each word is checked individually, user is re-prompted in case of error
- Activated automatically if stdin is a terminal
This commit is contained in:
parent
85cf5b3cbb
commit
fde885f55b
13 changed files with 617 additions and 282 deletions
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
Install required Debian/Ubuntu packages:
|
||||
|
||||
$ sudo apt-get install python-pip python-dev python-pexpect python-ecdsa python-scrypt libssl-dev git autoconf libtool wipe
|
||||
$ sudo apt-get install python-pip python-dev python-pexpect python-ecdsa python-scrypt libssl-dev git autoconf libtool wipe python-setuptools
|
||||
|
||||
Install the Python Cryptography Toolkit:
|
||||
|
||||
$ sudo pip install pycrypto
|
||||
$ sudo -H pip install pycrypto
|
||||
|
||||
Install the secp256k1 library:
|
||||
|
||||
|
|
@ -16,6 +16,8 @@ Install the secp256k1 library:
|
|||
$ ./configure
|
||||
$ make
|
||||
$ sudo make install
|
||||
$ sudo ldconfig
|
||||
$ cd ..
|
||||
|
||||
Install MMGen:
|
||||
|
||||
|
|
@ -23,6 +25,7 @@ Install MMGen:
|
|||
$ cd mmgen
|
||||
$ git checkout -b stable stable_linux
|
||||
$ sudo ./setup.py install
|
||||
$ cd ..
|
||||
|
||||
Install vanitygen (optional):
|
||||
|
||||
|
|
@ -30,6 +33,7 @@ Install vanitygen (optional):
|
|||
$ git clone https://github.com/samr7/vanitygen.git
|
||||
$ cd vanitygen; make
|
||||
(copy the "keyconv" executable to your execution path)
|
||||
$ cd ..
|
||||
|
||||
Install bitcoind:
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,10 @@ it still requires patience, and the user experience is less than optimal.
|
|||
You're urged to use the prebuilt [MMGenLive][20] USB image instead. It's now
|
||||
the preferred way for all non-Linux users to run MMGen.***
|
||||
|
||||
***Windows XP warning: MMGen is no longer officially supported on Windows XP due
|
||||
to incompatibilities with the Crypto.Random module. The scripts run, but the
|
||||
security of your random numbers cannot be guaranteed. Use at your own risk.***
|
||||
|
||||
Install MMGen on Windows by completing the following three steps:
|
||||
|
||||
>> 1. Install MinGW and MSYS ([WinXP][03]|[>=Win7][01]), if you haven't already;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
## Table of Contents
|
||||
|
||||
#### <a href='#a_i'>Preliminaries</a>
|
||||
* <a href='#a_iv'>Invocation</a>
|
||||
* <a href='#a_cf'>Configuration file</a>
|
||||
* <a href='#a_ts'>Test setup and testnet</a>
|
||||
* <a href='#a_bb'>Before you begin</a>
|
||||
|
||||
#### <a href='#a_bo'>Basic Operations</a>
|
||||
* <a href='#a_gw'>Generate a wallet</a>
|
||||
* <a href='#a_ga'>Generate addresses</a>
|
||||
|
|
@ -14,47 +20,104 @@
|
|||
* <a href='#a_ic'>Incognito wallets</a>
|
||||
* <a href='#a_hi'>Hidden incognito wallets</a>
|
||||
|
||||
The following primer presupposes you have MMGen installed on two computers, one
|
||||
offline and one online. However, if you have an online computer and a few
|
||||
Bitcoin addresses with small balances, it’s perfectly possible to perform the
|
||||
operations described below on a single online machine.
|
||||
#### <a href='#a_at'>Advanced Topics</a>
|
||||
* <a href='#a_hw'>Hot wallets and key-address files</a>
|
||||
* <a href='#a_fee'>Transaction Fees</a>
|
||||
* <a href='#a_rbf'>BIP 125 replace-by-fee (RBF) transactions</a>
|
||||
* <a href='#a_rbf_onl'>With an online (hot) wallet</a>
|
||||
* <a href='#a_rbf_onf'>With an offline (cold storage) wallet</a>
|
||||
|
||||
For those who just want to experiment with MMGen: all wallet generation, wallet
|
||||
format conversion, address and key generation, and address import operations can
|
||||
be performed on either an online or offline computer with an empty blockchain
|
||||
and no Bitcoin balance.
|
||||
### <a name='a_i'>Preliminaries</a>
|
||||
|
||||
*NOTE: Beginning with v0.8.7a, MMGen supports testnet, allowing you to perform
|
||||
the entire set of MMGen operations without risking real funds (free testnet
|
||||
coins may be obtained at [https://tpfaucet.appspot.com/][02]). To use this
|
||||
feature, start bitcoind with the -testnet option and sync the testnet blockchain
|
||||
(about 9GB at this time of writing). To make MMGen use testnet instead of
|
||||
mainnet, supply the `--testnet=1` option as the first argument to all MMGen
|
||||
commands you run. To save typing, the option may also be set in the MMGen
|
||||
configuration file.*
|
||||
#### <a name='a_iv'>Invocation</a>
|
||||
|
||||
Note that all the filenames, seed IDs, Bitcoin addresses and so forth used in
|
||||
this primer are fake and for purposes of illustration only. The up arrow (for
|
||||
repeating commands) and tab key (or Ctrl-I) (for completing commands and
|
||||
filenames) will speed up your work at the command line greatly.
|
||||
The MMGen wallet system is not a single program but a suite of lightweight
|
||||
commands run from the command line. MMGen's commands all begin, not
|
||||
surprisingly, with 'mmgen'. To see a list of available commands, type 'mmgen'
|
||||
followed by the TAB key. Every mmgen command has a help screen displaying
|
||||
detailed usage and options information. To view it, type the command name
|
||||
followed by `--help`. Note that most command options have long and short
|
||||
versions. For example, the `--help` option may be abbreviated to `-h`.
|
||||
Exceptions are the options listed by `--longhelp`, which have no short versions.
|
||||
|
||||
MMGen commands are generally interactive, informing you at every step and
|
||||
prompting you for input. The `--verbose` or `-v` option requests commands to be
|
||||
more wordy, while the `--quiet` or `-q` option suppresses all but the most
|
||||
essential information. These options are available for all MMGen commands. The
|
||||
`--yes` option (available only for certain commands) suppresses even more
|
||||
information and can be used to make some commands non-interactive or scriptable.
|
||||
|
||||
Certain options require parameters, such as the `--seed-len` option, for
|
||||
instance, which takes a parameter of '128', '192' or '256'. Commands may also
|
||||
take optional or required arguments. For example, `mmgen-addrgen` requires an
|
||||
address or range of addresses as an argument. Arguments must always follow
|
||||
options on the command line.
|
||||
|
||||
Sample MMGen command invocations:
|
||||
|
||||
$ mmgen-txcreate --help
|
||||
$ mmgen-addrgen --verbose 1-10
|
||||
$ mmgen-walletgen
|
||||
$ mmgen-walletgen --quiet --seed-len 128
|
||||
|
||||
#### <a name='a_cf'>Configuration file</a>
|
||||
|
||||
Just like Bitcoin Core, MMGen has its own data directory and configuration file.
|
||||
The data directory is '.mmgen' in the user's home directory and the config
|
||||
file is 'mmgen.cfg'. The config file contains global settings which you may
|
||||
wish to edit at some point to customize MMGen to your needs. These settings
|
||||
include the maximum transaction fee; the user name, password and hostname
|
||||
used for communicating with bitcoind; and a number of others.
|
||||
|
||||
#### <a name='a_ts'>Test setup and testnet</a>
|
||||
|
||||
If you just want to quickly try out MMGen, it's possible to perform all wallet
|
||||
generation, wallet format conversion, address and key generation, and address
|
||||
import operations on an offline computer with no blockchain and no bitcoin
|
||||
balance.
|
||||
|
||||
If you want to practice creating, signing and sending transactions, however,
|
||||
as well as tracking balances, you'll need a fully synced blockchain and some
|
||||
actual coins to play with. To avoid risking real funds, it's *highly
|
||||
recommended* to practice transaction operations on testnet until you feel
|
||||
confident you know what you're doing. Testnet is just like the real Bitcoin
|
||||
network, but testnet coins have no monetary value. Free testnet coins may be
|
||||
obtained at [https://tpfaucet.appspot.com][02].
|
||||
|
||||
To use MMGen with testnet, you must first start bitcoind with the `-testnet`
|
||||
option and sync the testnet blockchain (about 12GB at the time of writing). To
|
||||
force any MMGen command to use testnet just add the `--testnet=1` option after
|
||||
the command name. Or just set the `testnet` option to `true` in 'mmgen.cfg' to
|
||||
make *all* commands use testnet.
|
||||
With testnet you can safely practice all the operations below,
|
||||
including the offline ones, on an online computer.
|
||||
|
||||
#### <a name='a_bb'>Before you begin</a>
|
||||
|
||||
Before you begin, note that the filenames, seed IDs and Bitcoin addresses used
|
||||
in this primer are intentionally invalid and are for purposes of illustration
|
||||
only. As you perform the exercises, you'll naturally substitute real ones in
|
||||
their place.
|
||||
|
||||
The up arrow (for repeating commands) and tab key (or Ctrl-I) (for completing
|
||||
commands and filenames) will speed up your work at the command line greatly.
|
||||
|
||||
### <a name='a_bo'>Basic Operations</a>
|
||||
|
||||
#### <a name='a_gw'>Generate a wallet (offline computer):</a>
|
||||
#### <a name='a_gw'>Generate a wallet (offline computer)</a>
|
||||
|
||||
*NOTE: Beginning with v0.8.8, MMGen supports a “default wallet” feature. After
|
||||
creating your wallet, MMGen will prompt you to make it your default. If you
|
||||
answer 'y', the wallet will be stored in your MMGen data directory and used for
|
||||
all future commands that require a wallet or other seed source.*
|
||||
*NOTE: MMGen supports a “default wallet” feature. After generating your wallet,
|
||||
you'll be prompted to make it your default. If you answer 'y', the wallet will
|
||||
be stored in your MMGen data directory and used for all future commands that
|
||||
require a wallet or other seed source.*
|
||||
|
||||
*You may not want this feature if you plan to store your MMGen wallet in another
|
||||
location than your MMGen data directory. Otherwise, it's recommended, as it
|
||||
frees you from having to type your wallet on the command line.*
|
||||
*You may not want this feature if you plan to store your MMGen wallet in a
|
||||
location other than your MMGen data directory. Otherwise, it’s recommended,
|
||||
as it frees you from having to type your wallet file on the command line.*
|
||||
|
||||
*The following examples suppose that you've chosen to use a default wallet.
|
||||
Bear in mind that in the absence of a default wallet, the path to a wallet file
|
||||
or other seed source needs to be included in all commands where it's
|
||||
applicable.*
|
||||
*The following examples suppose that you’ve chosen to use a default wallet.
|
||||
If you haven't, then you must include the path to a wallet file or other seed
|
||||
source in all commands where a seed source is required.*
|
||||
|
||||
On your offline computer, generate a wallet:
|
||||
|
||||
|
|
@ -73,18 +136,21 @@ wallet’s password or hash preset are changed and is less important.
|
|||
configurable: type `mmgen-walletgen --help` for details.
|
||||
|
||||
Before moving any funds into your MMGen wallet, you should back it up in several
|
||||
places and preferably on several media such as paper, flash memory or a CD-ROM.
|
||||
You’re advised to use a passphrase with your wallet. Otherwise, anyone who
|
||||
gains physical access to one of your backups can easily steal your coins. Don’t
|
||||
forget your passphrase. If you do, the coins in your MMGen wallet are gone
|
||||
forever.
|
||||
places and preferably on several media such as paper, flash memory or optical
|
||||
disk. You’re advised to use a passphrase with your wallet. Otherwise, anyone
|
||||
who gains physical access to one of your backups can easily steal your coins.
|
||||
Don’t forget your passphrase. If you do, the coins in your MMGen wallet are
|
||||
gone forever.
|
||||
|
||||
Since the wallet is a small, humanly readable ASCII file, it can easily be
|
||||
printed out on paper. It can also be exported to more compact forms, the seed
|
||||
file and mnemonic (discussed below). These formats are short enough to be
|
||||
written out by hand or memorized.
|
||||
printed out on paper.
|
||||
|
||||
#### <a name='a_ga'>Generate addresses (offline computer):</a>
|
||||
Another highly recommended way to back up your wallet is to generate a mnemonic
|
||||
or seed file <a href='#a_ms'>as described below </a> and memorize it. If you
|
||||
have an average or better memory, you'll find memorizing mnemonics to be
|
||||
surprisingly easy.
|
||||
|
||||
#### <a name='a_ga'>Generate addresses (offline computer)</a>
|
||||
|
||||
Now generate ten addresses with your just-created wallet:
|
||||
|
||||
|
|
@ -111,15 +177,16 @@ the resulting filename. MMGen addresses consist of the Seed ID followed by ‘:
|
|||
and an index. In this example, ‘89ABCDEF:1’ represents the Bitcoin address
|
||||
‘16bNmy...’, ‘89ABCDEF:2’ represents ‘1AmkUx...’ and so forth.
|
||||
|
||||
To begin moving your Bitcoin holdings into your MMGen wallet, just spend into
|
||||
any of these addresses. If you run out of addresses, generate more. To
|
||||
generate a hundred addresses, for example, you’d specify an address range of ‘1-100’.
|
||||
To fund your MMGen wallet, first import the addresses into your tracking wallet
|
||||
and then spend some bitcoin into any of them. If you run out of addresses,
|
||||
generate more. To generate a hundred addresses, for example, specify an address
|
||||
range of ‘1-100’.
|
||||
|
||||
Let’s say you’ve decided to spend some BTC into the first four addresses above.
|
||||
Before doing so, you must import these addresses into the tracking wallet on
|
||||
your online machine so their balances will be visible. For convenience
|
||||
of reference, provide the addresses with labels. We’ll use the labels
|
||||
‘Donations’, ‘Storage 1’, ‘Storage 2’ and ‘Storage 3’.
|
||||
Begin by importing these addresses into the tracking wallet on your online
|
||||
machine so their balances will be visible. For convenience of reference,
|
||||
provide the addresses with labels. We’ll use the labels ‘Donations’, ‘Storage
|
||||
1’, ‘Storage 2’ and ‘Storage 3’.
|
||||
|
||||
Make a copy of the address file
|
||||
|
||||
|
|
@ -150,7 +217,7 @@ of lines as well.
|
|||
|
||||
Save the file, copy it onto a USB stick and transfer it to your online computer.
|
||||
|
||||
#### <a name='a_ia'>Import addresses (online computer):</a>
|
||||
#### <a name='a_ia'>Import addresses (online computer)</a>
|
||||
|
||||
On your online computer, go to your bitcoind data directory and move any
|
||||
existing 'wallet.dat' file out of harm’s way. Start bitcoind and let it
|
||||
|
|
@ -177,9 +244,9 @@ also).
|
|||
|
||||
*While not covered in this introduction, note that it’s also possible to [import
|
||||
ordinary Bitcoin addresses into your tracking wallet][01]. This allows you to
|
||||
move funds from another wallet directly to MMGen without having to go through
|
||||
the network. To use it, you must save the keys corresponding to the addresses
|
||||
where the funds are stored in a separate file for use during signing.*
|
||||
track and spend funds from another wallet using MMGen without having to go
|
||||
through the network. To use it, you must save the keys corresponding to the
|
||||
addresses where the funds are stored in a separate file to use during signing.*
|
||||
|
||||
Now that your addresses are being tracked, you may go ahead and send some BTC to
|
||||
them over the Bitcoin network. If you send 0.1, 0.2, 0.3 and 0.4 BTC
|
||||
|
|
@ -194,14 +261,13 @@ after the transactions have been confirmed:
|
|||
89ABCDEF:4 Storage 3 0.4
|
||||
TOTAL: 1 BTC
|
||||
|
||||
#### <a name='a_ct'>Create a transaction (online computer):</a>
|
||||
#### <a name='a_ct'>Create a transaction (online computer)</a>
|
||||
|
||||
Now that you have some BTC under MMGen’s control, you’re ready to create a
|
||||
transaction. Note that transactions are harmless until they’re signed and
|
||||
broadcast to the network, so feel free to experiment and create transactions
|
||||
with different combinations of inputs and outputs. If you're using testnet,
|
||||
then even broadcast transactions are harmless, so it's highly recommended you
|
||||
do so if you want to practice sending transactions.
|
||||
then you risk nothing even when broadcasting transactions, of course.
|
||||
|
||||
To send 0.1 BTC to the a third-party address 1AmkUxrfy5dMrfmeYwTxLxfIswUCcpeysc,
|
||||
for example, and send the change back to yourself at address 89ABCDEF:5, you’d
|
||||
|
|
@ -247,23 +313,20 @@ After quitting the menu with 'q', you’ll see the following prompt:
|
|||
Enter a range or space-separated list of outputs to spend:
|
||||
|
||||
Here you must choose unspent outputs of sufficient value to cover the send
|
||||
amount of 0.1 BTC, plus the transaction fee. By the way, MMGen calculates fees
|
||||
automatically using bitcoind’s 'estimatefee' RPC call, which makes things very
|
||||
convenient. If you want to increase the fee a bit for speedier confirmation,
|
||||
use the `--tx-fee-adj` option. Type `mmgen-txcreate --help` for details.
|
||||
|
||||
Output #2 is worth 0.2 BTC, which is sufficient, so let’s choose it. After
|
||||
several more prompts and confirmations, your transaction will be saved:
|
||||
amount of 0.1 BTC, plus the transaction fee (for more on fees, see 'Transaction
|
||||
Fees' under 'Advanced Topics' below). Output #2 is worth 0.2 BTC, which is
|
||||
sufficient, so we’ll choose that. After several more prompts and confirmations,
|
||||
your transaction will be saved:
|
||||
|
||||
Transaction written to file 'FEDCBA[0.1].rawtx'
|
||||
|
||||
Note that the transaction filename consists of a unique ID plus the spend
|
||||
amount.
|
||||
Note that the transaction filename consists of a unique MMGen Transaction ID
|
||||
plus the non-change spend amount.
|
||||
|
||||
As you can see, MMGen gives you complete control over your transaction inputs
|
||||
and change addresses. This feature will be appreciated by privacy-conscious users.
|
||||
|
||||
#### <a name='a_sg'>Sign a transaction (offline computer):</a>
|
||||
#### <a name='a_sg'>Sign a transaction (offline computer)</a>
|
||||
|
||||
Now transfer the the raw transaction file to your offline computer and sign it
|
||||
using your default wallet:
|
||||
|
|
@ -274,16 +337,21 @@ using your default wallet:
|
|||
|
||||
Note that the signed transaction file has a new extension, '.sigtx'.
|
||||
|
||||
#### <a name='a_st'>Send a transaction (online computer):</a>
|
||||
#### <a name='a_st'>Send a transaction (online computer)</a>
|
||||
|
||||
Now you’re ready for the final step: broadcasting the transaction to the
|
||||
network. Copy the signed transaction file to your online computer, start
|
||||
bitcoind if necessary, and issue the command:
|
||||
bitcoind if necessary, make sure your blockchain is up-to-date and issue the
|
||||
command:
|
||||
|
||||
$ mmgen-txsend FEDCBA[0.1].sigtx
|
||||
...
|
||||
Transaction sent: abcd1234....
|
||||
|
||||
Like all MMGen commands, 'mmgen-txsend' is interactive, so you’ll be prompted
|
||||
before the transaction is actually sent.
|
||||
before the transaction is actually broadcast. If the send was successful, a
|
||||
64-character hexadecimal Bitcoin Transaction ID will be displayed ('abcd1234...'
|
||||
in our case).
|
||||
|
||||
Once the transaction is broadcast to the network and confirmed, your address
|
||||
listing should look something like this:
|
||||
|
|
@ -300,15 +368,12 @@ Since you’ve sent 0.1 BTC to a third party, your balance has declined by 0.1 B
|
|||
plus the tx fee of 0.0001 BTC. To verify that your transaction’s received its
|
||||
second, third and so on confirmations, increase `minconf` accordingly.
|
||||
|
||||
Congratulations! You’ve now mastered the basics of MMGen!
|
||||
|
||||
Some of MMGen’s more advanced features are discussed below. Others are
|
||||
documented in the help screens of the individual MMGen commands: display these
|
||||
by invoking the desired command with the `-h` or `--help` switch.
|
||||
Congratulations! You’ve now mastered the basics of MMGen! To learn about some
|
||||
of MMGen’s more advanced features, continue reading.
|
||||
|
||||
### <a name='a_af'>Additional Features</a>
|
||||
|
||||
#### <a name='a_ms'>Using the mnemonic, seed and hexseed formats:</a>
|
||||
#### <a name='a_ms'>Using the mnemonic, seed and hexseed formats</a>
|
||||
|
||||
Continuing our example above, generate a mnemonic from the default wallet:
|
||||
|
||||
|
|
@ -321,29 +386,33 @@ Continuing our example above, generate a mnemonic from the default wallet:
|
|||
float absent path strong curtain savior worst suspend bright touch away
|
||||
dirty measure thorn
|
||||
|
||||
Note: a 128- or 192-bit seed will generate a shorter mnemonic of 12 or 18
|
||||
words. You may generate a wallet with these seed lengths using the `-l`
|
||||
option to 'mmgen-walletgen'.
|
||||
Since our seed is 256 bits long, the mnemonic contains 24 words. 128-bit and
|
||||
192-bit seeds generate shorter mnemonics of 12 and 18 words, respectively.
|
||||
Wallets with these seed lengths can be generated using the `--seed-len` option
|
||||
to 'mmgen-walletgen'.
|
||||
|
||||
Though some consider 128 bits of entropy to provide adequate security for the
|
||||
foreseeable future, it’s advisable to 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.
|
||||
You'll find that even a 24-word mnemonic is not difficult to memorize.
|
||||
|
||||
NOTE: MMGen mnemonics are generated from the Electrum wordlist, but using
|
||||
ordinary base conversion instead of Electrum’s more complicated algorithm.
|
||||
|
||||
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:
|
||||
The mnemonic is a complete representation of your seed and may be used anywhere
|
||||
where you’d use an MMGen wallet. You can generate addresses with it just as you
|
||||
do with a wallet:
|
||||
|
||||
$ mmgen-addrgen 89ABCDEF.mmwords 1-10
|
||||
...
|
||||
Address data written to file '89ABCDEF[1-10].addrs'
|
||||
|
||||
The resulting address file will be identical to one generated by any wallet with
|
||||
Seed ID '89ABCDEF'.
|
||||
You can use it to sign transactions:
|
||||
|
||||
The mnemonic can be used to regenerate a lost wallet:
|
||||
$ mmgen-txsign FEDCBA[0.1].rawtx 89ABCDEF.mmwords
|
||||
...
|
||||
Signed transaction written to file 'FEDCBA[0.1].sigtx'
|
||||
|
||||
The mnemonic can also be used to regenerate a lost wallet:
|
||||
|
||||
$ mmgen-walletconv 89ABCDEF.mmwords
|
||||
...
|
||||
|
|
@ -352,32 +421,33 @@ The mnemonic can be used to regenerate a lost wallet:
|
|||
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 generated and used exactly
|
||||
the same way as mnemonic files:
|
||||
An alternative to mnemonics, seed files provide yet another way of representing
|
||||
your seed. They bear the extension '.mmseed' and are generated exactly the same
|
||||
way as mnemonic files:
|
||||
|
||||
$ mmgen-walletconv -o seed
|
||||
...
|
||||
Seed data written to file '89ABCDEF.mmseed'
|
||||
|
||||
And they can also be used to regenerate a wallet:
|
||||
They can be used just like mnemonics 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:
|
||||
Here’s a sample seed file for a 256-bit seed:
|
||||
|
||||
$ cat 8B7392ED.mmseed
|
||||
f4c84b C5ZT wWpT Jsoi wRVw 2dm9 Aftd WLb8 FggQ eC8h Szjd da9L
|
||||
|
||||
And for a 128-bit wallet:
|
||||
And for a 128-bit seed:
|
||||
|
||||
$ cat 8E0DFB78.mmseed
|
||||
0fe02f XnyC NfPH piuW dQ2d nM47 VU
|
||||
|
||||
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:
|
||||
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
|
||||
0fe02f
|
||||
|
|
@ -387,10 +457,9 @@ Or you can do the same thing with 'mmgen-tool':
|
|||
$ mmgen-tool str2id6 'XnyC NfPH piuW dQ2d nM47 VU'
|
||||
0fe02f
|
||||
|
||||
Beginning with version 0.9.0, export to and generation from hexadecimal
|
||||
(hexseed) format is also supported. Hexseed files are identical to seed files
|
||||
but encoded in hexadecimal rather than base 58. They bear the extension
|
||||
'.mmhex':
|
||||
Beginning with version 0.9.0, MMGen also supports seed files in hexadecimal
|
||||
(hexseed) format. Hexseed files are identical to seed files but encoded in
|
||||
hexadecimal rather than base 58. They bear the extension '.mmhex':
|
||||
|
||||
$ cat FE3C6545.mmhex
|
||||
afc3fe 456d 7f5f 1c4b fe3b c916 b875 60ae 6a3e
|
||||
|
|
@ -401,19 +470,22 @@ standard command-line tools:
|
|||
$ echo 456d 7f5f 1c4b fe3b c916 b875 60ae 6a3e | tr -d ' ' | xxd -r -p | sha256sum -b | xxd -r -p | sha256sum -b | cut -c 1-8
|
||||
fe3c6545
|
||||
|
||||
A hexseed can be used to easily generate keys even without the MMGen software,
|
||||
as explained in [this tutorial][03].
|
||||
A hexseed can be used to generate keys even without the MMGen software,
|
||||
using basic command-line utilities, as explained in [this tutorial][03].
|
||||
|
||||
#### <a name='a_ai'>Mnemonics, seeds and hexseeds: additional information</a>
|
||||
|
||||
MMGen commands that take mnemonic, seed or hexseed data may receive the data
|
||||
All MMGen commands that take mnemonic, seed or hexseed data may receive the data
|
||||
from a prompt instead of a file. Just omit the file name and specify the input
|
||||
format:
|
||||
|
||||
$ mmgen-walletconv -i words
|
||||
$ mmgen-addrgen -i words 1-10
|
||||
...
|
||||
Enter mnemonic data: <type or paste your mnemonic here>
|
||||
|
||||
This means that you may keep your seed entirely in your head, as either a
|
||||
mnemonic, seed or hexseed, and never record it anywhere on any medium.
|
||||
|
||||
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
|
||||
|
|
@ -549,6 +621,192 @@ Transaction signing uses the same syntax:
|
|||
...
|
||||
Signed transaction written to file 'ABCDEF[0.1].sigtx'
|
||||
|
||||
### <a name='a_at'>Advanced Topics</a>
|
||||
|
||||
#### <a name='a_hw'>Hot wallets and key-address files</a>
|
||||
|
||||
Chances are you'll want to use MMGen not only for cold storage but for
|
||||
day-to-day transactions too. For this you'll need to place a portion of your
|
||||
funds in a “hot wallet” on your online computer. With hot wallet funds you
|
||||
can use the command `mmgen-txdo` to quickly create, sign and send transactions
|
||||
in one operation.
|
||||
|
||||
There are two hot wallet strategies you can use. The first is to generate a
|
||||
separate MMGen wallet on your online computer for use as the hot wallet. The
|
||||
advantage of this is convenience: you won't have to specify a wallet or seed
|
||||
source on the command line. In addition, your hot wallet and cold wallet funds
|
||||
will be easily distinguishable in your tracking wallet by their different Seed
|
||||
IDs. The drawback of this strategy is that you now have two seeds that need
|
||||
backing up or memorizing.
|
||||
|
||||
The other strategy, which avoids this drawback, is to partition your cold wallet
|
||||
by mentally setting aside “hot” and “cold” address ranges. For example, you
|
||||
might choose to reserve all addresses in the range 1-1000 for cold storage and
|
||||
everything above that for your hot wallet.
|
||||
|
||||
The next step is to create a key-address file for a sufficient number of “hot”
|
||||
addresses to cover your day-to-day transaction needs for the foreseeable future.
|
||||
A key-address file is just like an address file except that it contains keys as
|
||||
well as addresses, thus functioning as a hot wallet for a range of addresses.
|
||||
Assuming your hot address range begins at 1001, you could start by creating a
|
||||
key-address file for a hundred hot addresses like this:
|
||||
|
||||
$ mmgen-keygen 1001-1100
|
||||
...
|
||||
Secret keys written to file '89ABCDEF[1001-1100].akeys.mmenc'
|
||||
|
||||
`mmgen-keygen` prompts you for a password to encrypt the key-address file with.
|
||||
This is a wise precaution, as it provides at least some security for keys that
|
||||
will be stored on an online machine.
|
||||
|
||||
Now copy the key-address file to your online machine and import the addresses
|
||||
into your tracking wallet:
|
||||
|
||||
$ mmgen-addrimport --batch --keyaddr-file '89ABCDEF[1001-1100].akeys.mmenc'
|
||||
|
||||
After funding your hot wallet by spending into some addresses in this range you
|
||||
can do quickie transactions with these funds using the `mmgen-txdo` command:
|
||||
|
||||
$ mmgen-txdo -M '89ABCDEF[1001-1100].akeys.mmenc' 1AmkUxrfy5dMrfmeYwTxLxfIswUCcpeysc,0.1 89ABCDEF:1010
|
||||
...
|
||||
Transaction sent: dcea1357....
|
||||
|
||||
The `--mmgen-keys-from-file` or `-M` option is required when using a key-address
|
||||
file in place of a default wallet. Note that your change address 89ABCDEF:1010
|
||||
is within the range covered by the key-address file, so your change funds will
|
||||
remain “hot spendable”.
|
||||
|
||||
Using `mmgen-txdo` with a default online hot wallet is even simpler. For a hot
|
||||
wallet with Seed ID 0FDE89AB, for instance, creating and sending a transaction
|
||||
looks like this:
|
||||
|
||||
$ mmgen-txdo 1AmkUxrfy5dMrfmeYwTxLxfIswUCcpeysc,0.1 0FDE89AB:10
|
||||
|
||||
|
||||
#### <a name='a_fee'>Transaction Fees</a>
|
||||
|
||||
MMGen gives you several options for dealing with transaction fees.
|
||||
|
||||
Firstly, and most simply, you may do nothing, in which case MMGen will calculate
|
||||
the fee automatically using bitcoind’s 'estimatefee' RPC call. You can adjust
|
||||
the estimated fee by any factor using the `--tx-fee-adj` option, a handy feature
|
||||
when you need transactions to confirm a bit more quickly. MMGen has no default
|
||||
fee, so if network fee estimation fails for any reason, you'll be prompted to
|
||||
enter the fee manually.
|
||||
|
||||
Secondly, you may specify the fee as an absolute BTC amount (a decimal number).
|
||||
This can be done either on the command line or at the interactive prompt when
|
||||
creating transactions with `mmgen-txcreate`, `mmgen-txdo` or `mmgen-txbump`.
|
||||
|
||||
Thirdly, instead of using an absolute BTC amount, you may specify the fee in
|
||||
satoshis per byte and let MMGen calculate the fee based on the transaction size.
|
||||
This also works both on the command line and at the interactive prompt. The
|
||||
satoshis-per-byte specification is an integer followed by the letter 's'. A fee
|
||||
of 90 satoshis per byte is thus represented as '90s'.
|
||||
|
||||
MMGen has a hard maximum fee (currently 0.01 BTC) which is alterable only in the
|
||||
config file. Thus MMGen will never create or broadcast any transaction with a
|
||||
mistakenly or dangerously high fee unless you expressly permit it to.
|
||||
|
||||
#### <a name='a_rbf'>BIP 125 replace-by-fee (RBF) transactions</a>
|
||||
|
||||
As of version 0.9.1, MMGen supports creating replaceable and replacement
|
||||
transactions in accordance with the BIP 125 replace-by-fee (RBF) specification.
|
||||
|
||||
To make your transactions replaceable, just specify the `--rbf` option when
|
||||
creating them with `mmgen-txcreate` or `mmgen-txdo`.
|
||||
|
||||
Version 0.9.1 also introduces `mmgen-txbump`, a convenient command for quickly
|
||||
creating replacement transactions from existing replaceable ones.
|
||||
`mmgen-txbump` can create, sign and send transactions in a single operation if
|
||||
desired.
|
||||
|
||||
Continuing the examples from our primer above, we'll examine two RBF scenarios,
|
||||
one for a hot wallet and one for a cold storage wallet. In the first scenario,
|
||||
initial and replacement transactions will be created, signed and sent in one
|
||||
operation. In the second, a batch of replacement transactions with
|
||||
incrementally increasing fees will created online and then signed offline.
|
||||
|
||||
#### <a name='a_rbf_onl'>With an online (hot) wallet</a>
|
||||
|
||||
Create, sign and send a BIP 125 replaceable transaction with a fee of 50
|
||||
satoshis per byte:
|
||||
|
||||
$ mmgen-txdo --rbf --tx-fee 50s 1AmkUxrfy5dMrfmeYwTxLxfIswUCcpeysc,0.1 0FDE89AB:5
|
||||
...
|
||||
Signed transaction written to file 'FEDCBB[0.1,50].sigtx'
|
||||
...
|
||||
Transaction sent: dcba4321....
|
||||
|
||||
Here you've sent 0.1 BTC to a third-party address and the change back to
|
||||
yourself at address #5 of your default hot wallet with Seed ID 0FDE89AB.
|
||||
|
||||
Note that the fee is shown in the filename after the send amount. The presence
|
||||
of the fee in the filename identifies the transaction as replaceable.
|
||||
|
||||
If the transaction fails to confirm in your desired timeframe, then create, sign
|
||||
and send a replacement transaction with a higher fee, say 100 satoshis per byte:
|
||||
|
||||
$ mmgen-txbump --send --tx-fee 100s --output-to-reduce c 'FEDCBB[0.1,50].sigtx'
|
||||
...
|
||||
Signed transaction written to file 'DAE123[0.1,100].sigtx'
|
||||
...
|
||||
Transaction sent: eef01357....
|
||||
|
||||
The `--send` switch instructs `mmgen-txbump` to sign and send the transaction
|
||||
after creating it. The `--output-to-reduce` switch with an argument of 'c'
|
||||
requests that the increased fee be deducted from the change ('c') output, which
|
||||
is usually what is desired. If you want it taken from some other output,
|
||||
identify the output by number. Note that the resulting replacement transaction
|
||||
has a different identifier, since it's a new transaction.
|
||||
|
||||
If this transaction also fails to confirm, then repeat the above step as many
|
||||
times as necessary to get a confirmation, increasing the fee each time. The
|
||||
only thing you have to modify with each iteration is the argument to `--tx-fee`.
|
||||
To reduce your typing even further, use the `--yes` switch to skip all
|
||||
non-essential prompts.
|
||||
|
||||
Note that if you're using a key-address file instead of a default hot wallet,
|
||||
you'll need to supply it on the command line as a parameter to the `-M` option.
|
||||
|
||||
#### <a name='a_rbf_onf'>With an offline (cold storage) wallet</a>
|
||||
|
||||
To achieve the same result as in the above example using a cold wallet, just
|
||||
create the initial transaction with `mmgen-txcreate` instead of `mmgen-txdo`:
|
||||
|
||||
$ mmgen-txcreate --rbf --tx-fee 50s 1AmkUxrfy5dMrfmeYwTxLxfIswUCcpeysc,0.1 89ABCDEF:5
|
||||
...
|
||||
Transaction written to file 'FEDCBC[0.1,50].rawtx'
|
||||
|
||||
Now create a series of transactions with incrementally increasing fees for
|
||||
offline signing:
|
||||
|
||||
$ mmgen-txbump --tx-fee 100s --output-to-reduce c 'FEDCBC[0.1,50].rawtx'
|
||||
$ mmgen-txbump --tx-fee 150s --output-to-reduce c 'FEDCBC[0.1,50].rawtx'
|
||||
$ mmgen-txbump --tx-fee 200s --output-to-reduce c 'FEDCBC[0.1,50].rawtx'
|
||||
|
||||
To speed things up, add the `--yes` switch to make `mmgen-txbump` completely
|
||||
non-interactive.
|
||||
|
||||
The result will be four raw transaction files with increasing fees, like this:
|
||||
|
||||
FEDCBC[0.1,50].rawtx
|
||||
3EBB00[0.1,100].rawtx
|
||||
124FFF[0.1,150].rawtx
|
||||
73DABB[0.1,200].rawtx
|
||||
|
||||
Copy the files to an empty folder, transfer the folder to your offline machine and batch sign them:
|
||||
|
||||
$ mmgen-txsign -d my_folder --yes my_folder/*.rawtx
|
||||
|
||||
Then copy the signed transaction files back to your online machine and broadcast
|
||||
them in turn until you get a confirmation:
|
||||
|
||||
$ mmgen-txsend FEDCBC[0.1,50].sigtx # ...if this doesn't confirm, then
|
||||
$ mmgen-txsend 3EBB00[0.1,100].sigtx # ...if this doesn't confirm, then
|
||||
$ mmgen-txsend 124FFF[0.1,150].sigtx # ...if this doesn't confirm, then
|
||||
$ mmgen-txsend 73DABB[0.1,200].sigtx
|
||||
|
||||
[01]: https://github.com/mmgen/mmgen/wiki/Tracking-and-spending-ordinary-Bitcoin-addresses
|
||||
[02]: https://tpfaucet.appspot.com
|
||||
[03]: Recovering-Keys-Without-MMGen
|
||||
|
|
|
|||
|
|
@ -20,7 +20,8 @@
|
|||
addr.py: Address generation/display routines for the MMGen suite
|
||||
"""
|
||||
|
||||
from hashlib import sha256, sha512
|
||||
from hashlib import sha256,sha512
|
||||
from binascii import hexlify,unhexlify
|
||||
from mmgen.common import *
|
||||
from mmgen.bitcoin import privnum2addr,hex2wif,wif2hex
|
||||
from mmgen.obj import *
|
||||
|
|
@ -83,7 +84,6 @@ def _privhex2addr_keyconv(privhex,compressed=False):
|
|||
def _privhex2addr_secp256k1(privhex,compressed=False):
|
||||
from mmgen.secp256k1 import priv2pub
|
||||
from mmgen.bitcoin import hexaddr2addr,pubhex2hexaddr
|
||||
from binascii import hexlify,unhexlify
|
||||
pubkey = priv2pub(unhexlify(privhex),int(compressed))
|
||||
return hexaddr2addr(pubhex2hexaddr(hexlify(pubkey)))
|
||||
|
||||
|
|
@ -275,10 +275,10 @@ Removed %s duplicate wif key%s from keylist (also in {pnm} key-address file
|
|||
self.fmt_data = mmgen_encrypt(self.fmt_data.encode('utf8'),desc,'')
|
||||
self.ext += '.'+g.mmenc_ext
|
||||
|
||||
def write_to_file(self,ask_tty=True,ask_write_default_yes=False,binary=False):
|
||||
def write_to_file(self,ask_tty=True,ask_write_default_yes=False,binary=False,desc=None):
|
||||
fn = u'{}.{}'.format(self.id_str,self.ext)
|
||||
ask_tty = self.has_keys and not opt.quiet
|
||||
write_data_to_file(fn,self.fmt_data,self.file_desc,ask_tty=ask_tty,binary=binary)
|
||||
write_data_to_file(fn,self.fmt_data,desc or self.file_desc,ask_tty=ask_tty,binary=binary)
|
||||
|
||||
def idxs(self):
|
||||
return [e.idx for e in self.data]
|
||||
|
|
@ -543,35 +543,27 @@ Record this checksum: it will be used to verify the password file in the future
|
|||
pw_len = None
|
||||
pw_fmt = None
|
||||
pw_info = {
|
||||
'base58': { 'min_len': 8 , 'max_len': 36 ,'dfl_len': 20, 'desc': 'base-58 password' },
|
||||
'base32': { 'min_len': 10 ,'max_len': 42 ,'dfl_len': 24, 'desc': 'base-32 password' }
|
||||
'b58': { 'min_len': 8 , 'max_len': 36 ,'dfl_len': 20, 'desc': 'base-58 password' },
|
||||
'b32': { 'min_len': 10 ,'max_len': 42 ,'dfl_len': 24, 'desc': 'base-32 password' }
|
||||
}
|
||||
cook_hash_rounds = 10 # not too many rounds, so hand decoding can still be feasible
|
||||
|
||||
def __init__(self,
|
||||
seed=None,
|
||||
addr_idxs=None,
|
||||
pw_id_str=None,
|
||||
pw_len=None,
|
||||
infile=None,
|
||||
chksum_only=False,
|
||||
pw_fmt=None,
|
||||
chk_params_only=False
|
||||
):
|
||||
def __init__(self,infile=None,seed=None,pw_idxs=None,pw_id_str=None,pw_len=None,pw_fmt=None,
|
||||
chksum_only=False,chk_params_only=False):
|
||||
|
||||
self.update_msgs()
|
||||
|
||||
if infile:
|
||||
(self.seed_id,self.data) = self.parse_file(infile) # sets self.pw_id_str,self.pw_fmt,self.pw_len
|
||||
else:
|
||||
for k in seed,addr_idxs: assert chk_params_only or k
|
||||
for k in seed,pw_idxs: assert chk_params_only or k
|
||||
for k in pw_id_str,pw_fmt: assert k
|
||||
self.pw_id_str = MMGenPWIDString(pw_id_str)
|
||||
self.set_pw_fmt(pw_fmt)
|
||||
self.set_pw_len(pw_len)
|
||||
if chk_params_only: return
|
||||
self.seed_id = seed.sid
|
||||
self.data = self.generate(seed,addr_idxs)
|
||||
self.data = self.generate(seed,pw_idxs)
|
||||
|
||||
self.num_addrs = len(self.data)
|
||||
self.fmt_data = ''
|
||||
|
|
@ -618,13 +610,11 @@ Record this checksum: it will be used to verify the password file in the future
|
|||
|
||||
def make_passwd(self,hex_sec):
|
||||
assert self.pw_fmt in self.pw_info
|
||||
from mmgen.bitcoin import b58a
|
||||
alpha,base = ((b58a,58),(b32a,32))[self.pw_fmt=='base32']
|
||||
# we take least significant part
|
||||
return ''.join(baseconv.fromhex(base,hex_sec,alpha,pad=self.pw_len))[-self.pw_len:]
|
||||
return ''.join(baseconv.fromhex(hex_sec,self.pw_fmt,pad=self.pw_len))[-self.pw_len:]
|
||||
|
||||
def chk_addr_or_pw(self,pw):
|
||||
if not (is_b58_str,is_b32_str)[self.pw_fmt=='base32'](pw):
|
||||
if not (is_b58_str,is_b32_str)[self.pw_fmt=='b32'](pw):
|
||||
msg('Password is not a valid {} string'.format(self.pw_fmt))
|
||||
return False
|
||||
if len(pw) != self.pw_len:
|
||||
|
|
@ -634,10 +624,14 @@ Record this checksum: it will be used to verify the password file in the future
|
|||
|
||||
def cook_seed(self,seed):
|
||||
from mmgen.crypto import sha256_rounds
|
||||
# Changing either pw_fmt or pw_len will cause a different, unrelated set of passwords to
|
||||
# be generated: this is what we want
|
||||
cseed = '{}{}:{}:{}'.format(seed,self.pw_fmt,self.pw_len,self.pw_id_str.encode('utf8'))
|
||||
dmsg('Cooked seed: {}\nSeed len: {}'.format(repr(cseed),len(cseed)))
|
||||
# Changing either pw_fmt, pw_len or id_str will cause a different, unrelated set of
|
||||
# passwords to be generated: this is what we want
|
||||
fid_str = '{}:{}:{}'.format(self.pw_fmt,self.pw_len,self.pw_id_str.encode('utf8'))
|
||||
dmsg(u'Full ID string: {}'.format(fid_str.decode('utf8')))
|
||||
# Original implementation was 'cseed = seed + fid_str'; hmac was not used
|
||||
import hmac
|
||||
cseed = hmac.new(seed,fid_str,sha256).digest()
|
||||
dmsg('Seed: {}\nCooked seed: {}\nCooked seed len: {}'.format(hexlify(seed),hexlify(cseed),len(cseed)))
|
||||
return sha256_rounds(cseed,self.cook_hash_rounds)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ class g(object):
|
|||
user_entropy = ''
|
||||
hash_preset = '3'
|
||||
usr_randchars = 30
|
||||
stdin_tty = bool(sys.stdin.isatty() or os.getenv('MMGEN_TEST_SUITE'))
|
||||
|
||||
max_tx_fee = BTCAmt('0.01')
|
||||
tx_fee_adj = 1.0
|
||||
|
|
@ -144,7 +145,6 @@ class g(object):
|
|||
min_urandchars = 10
|
||||
|
||||
seed_lens = 128,192,256
|
||||
mn_lens = [i / 32 * 3 for i in seed_lens]
|
||||
|
||||
mmenc_ext = 'mmenc'
|
||||
salt_len = 16
|
||||
|
|
|
|||
|
|
@ -125,6 +125,6 @@ if al.gen_addrs and opt.print_checksum:
|
|||
|
||||
if al.gen_keys and keypress_confirm('Encrypt key list?'):
|
||||
al.encrypt()
|
||||
al.write_to_file(binary=True)
|
||||
al.write_to_file(binary=True,desc='encrypted '+al.file_desc)
|
||||
else:
|
||||
al.write_to_file()
|
||||
|
|
|
|||
|
|
@ -27,6 +27,11 @@ from mmgen.addr import PasswordList,AddrIdxList
|
|||
from mmgen.seed import SeedSource
|
||||
from mmgen.obj import MMGenPWIDString
|
||||
|
||||
dfl_len = {
|
||||
'b58': PasswordList.pw_info['b58']['dfl_len'],
|
||||
'b32': PasswordList.pw_info['b32']['dfl_len']
|
||||
}
|
||||
|
||||
opts_data = {
|
||||
'sets': [('print_checksum',True,'quiet',True)],
|
||||
'desc': """Generate a range or list of passwords from an {pnm} wallet,
|
||||
|
|
@ -43,7 +48,9 @@ opts_data = {
|
|||
'f' at offset 'o' (comma-separated)
|
||||
-O, --old-incog-fmt Specify old-format incognito input
|
||||
-L, --passwd-len= l Specify length of generated passwords
|
||||
(default: {p} chars [base58], {q} chars [base32])
|
||||
(default: {d58} chars [base58], {d32} chars [base32]).
|
||||
An argument of 'h' will generate passwords of half
|
||||
the default length.
|
||||
-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
|
||||
|
|
@ -58,11 +65,8 @@ opts_data = {
|
|||
-v, --verbose Produce more verbose output
|
||||
""".format(
|
||||
seed_lens=', '.join([str(i) for i in g.seed_lens]),
|
||||
pnm=g.proj_name,
|
||||
kgs=' '.join(['{}:{}'.format(n,k) for n,k in enumerate(g.key_generators,1)]),
|
||||
g=g,
|
||||
p=PasswordList.pw_info['base58']['dfl_len'],
|
||||
q=PasswordList.pw_info['base32']['dfl_len']
|
||||
g=g,pnm=g.proj_name,d58=dfl_len['b58'],d32=dfl_len['b32'],
|
||||
kgs=' '.join(['{}:{}'.format(n,k) for n,k in enumerate(g.key_generators,1)])
|
||||
),
|
||||
'notes': """
|
||||
|
||||
|
|
@ -79,13 +83,13 @@ Changing either the password format (base32,base58) or length alters the seed
|
|||
and thus generates a completely new set of passwords.
|
||||
|
||||
EXAMPLE:
|
||||
Generate ten base58 passwords of length {dfl58} for Alice's email account:
|
||||
Generate ten base58 passwords of length {d58} for Alice's email account:
|
||||
{g.prog_name} alice@nowhere.com 1-10
|
||||
|
||||
Generate ten base58 passwords of length 16 for Alice's email account:
|
||||
{g.prog_name} -L16 alice@nowhere.com 1-10
|
||||
|
||||
Generate ten base32 passwords of length {dfl32} for Alice's email account:
|
||||
Generate ten base32 passwords of length {d32} for Alice's email account:
|
||||
{g.prog_name} -b alice@nowhere.com 1-10
|
||||
|
||||
The three sets of passwords are completely unrelated to each other, so
|
||||
|
|
@ -102,10 +106,8 @@ FMT CODES:
|
|||
{f}
|
||||
""".format(
|
||||
f='\n '.join(SeedSource.format_fmt_codes().splitlines()),
|
||||
o=opts,g=g,
|
||||
o=opts,g=g,d58=dfl_len['b58'],d32=dfl_len['b32'],
|
||||
ml=MMGenPWIDString.max_len,
|
||||
dfl58=PasswordList.pw_info['base58']['dfl_len'],
|
||||
dfl32=PasswordList.pw_info['base32']['dfl_len'],
|
||||
fs="', '".join(MMGenPWIDString.forbidden)
|
||||
)
|
||||
}
|
||||
|
|
@ -114,25 +116,27 @@ cmd_args = opts.init(opts_data,add_opts=['b16'])
|
|||
|
||||
if len(cmd_args) < 2: opts.usage()
|
||||
|
||||
idxs = AddrIdxList(fmt_str=cmd_args.pop())
|
||||
pw_idxs = AddrIdxList(fmt_str=cmd_args.pop())
|
||||
|
||||
pw_id_str = cmd_args.pop()
|
||||
|
||||
sf = get_seed_file(cmd_args,1)
|
||||
|
||||
pw_fmt = ('base58','base32')[bool(opt.base32)]
|
||||
pw_fmt = ('b58','b32')[bool(opt.base32)]
|
||||
|
||||
PasswordList(pw_id_str=pw_id_str,pw_len=opt.passwd_len,pw_fmt=pw_fmt,chk_params_only=True)
|
||||
pw_len = (opt.passwd_len,dfl_len[pw_fmt]/2)[opt.passwd_len in ('h','H')]
|
||||
|
||||
PasswordList(pw_id_str=pw_id_str,pw_len=pw_len,pw_fmt=pw_fmt,chk_params_only=True)
|
||||
do_license_msg()
|
||||
|
||||
ss = SeedSource(sf)
|
||||
|
||||
al = PasswordList(seed=ss.seed,addr_idxs=idxs,pw_id_str=pw_id_str,pw_len=opt.passwd_len,pw_fmt=pw_fmt)
|
||||
al = PasswordList(seed=ss.seed,pw_idxs=pw_idxs,pw_id_str=pw_id_str,pw_len=pw_len,pw_fmt=pw_fmt)
|
||||
|
||||
al.format()
|
||||
|
||||
if keypress_confirm('Encrypt password list?'):
|
||||
al.encrypt(desc='password list')
|
||||
al.write_to_file(binary=True)
|
||||
al.write_to_file(binary=True,desc='encrypted password list')
|
||||
else:
|
||||
al.write_to_file()
|
||||
al.write_to_file(desc='password list')
|
||||
|
|
|
|||
141
mmgen/seed.py
141
mmgen/seed.py
|
|
@ -117,7 +117,7 @@ class SeedSource(MMGenObject):
|
|||
if hasattr(self,'seed'):
|
||||
self._encrypt()
|
||||
return
|
||||
elif hasattr(self,'infile'):
|
||||
elif hasattr(self,'infile') or not g.stdin_tty:
|
||||
self._deformat_once()
|
||||
self._decrypt_retry()
|
||||
else:
|
||||
|
|
@ -135,7 +135,10 @@ class SeedSource(MMGenObject):
|
|||
self.fmt_data = get_data_from_file(self.infile.name,self.desc,
|
||||
binary=self.file_mode=='binary')
|
||||
else:
|
||||
self.fmt_data = get_data_from_user(self.desc)
|
||||
self.fmt_data = self._get_data_from_user(self.desc)
|
||||
|
||||
def _get_data_from_user(self,desc):
|
||||
return get_data_from_user(desc)
|
||||
|
||||
def _deformat_once(self):
|
||||
self._get_data()
|
||||
|
|
@ -355,14 +358,64 @@ class Mnemonic (SeedSourceUnenc):
|
|||
fmt_codes = 'mmwords','words','mnemonic','mnem','mn','m'
|
||||
desc = 'mnemonic data'
|
||||
ext = 'mmwords'
|
||||
wl_checksums = {
|
||||
'electrum': '5ca31424',
|
||||
'tirosh': '1a5faeff'
|
||||
}
|
||||
mn_base = 1626
|
||||
wordlists = sorted(wl_checksums)
|
||||
dfl_wordlist = 'electrum'
|
||||
# dfl_wordlist = 'tirosh'
|
||||
mn_lens = [i / 32 * 3 for i in g.seed_lens]
|
||||
wl_id = 'electrum' # or 'tirosh'
|
||||
|
||||
def _get_data_from_user(self,desc):
|
||||
|
||||
if not g.stdin_tty:
|
||||
return get_data_from_user(desc)
|
||||
|
||||
from mmgen.term import get_char_raw,get_char
|
||||
|
||||
def choose_mn_len():
|
||||
prompt = 'Choose a mnemonic length: 1) 12 words, 2) 18 words, 3) 24 words: '
|
||||
urange = [str(i+1) for i in range(len(self.mn_lens))]
|
||||
while True:
|
||||
r = get_char('\r'+prompt)
|
||||
if r in urange: break
|
||||
msg_r('\r' + ' '*len(prompt) + '\r')
|
||||
return self.mn_lens[int(r)-1]
|
||||
|
||||
while True:
|
||||
mn_len = choose_mn_len()
|
||||
prompt = 'Mnemonic length of {} words chosen. OK?'.format(mn_len)
|
||||
if keypress_confirm(prompt,default_yes=True,no_nl=True): break
|
||||
|
||||
m = 'Enter your {}-word mnemonic, hitting RETURN or SPACE after each word:'
|
||||
msg(m.format(mn_len))
|
||||
|
||||
def get_word():
|
||||
s = ''
|
||||
while True:
|
||||
ch = get_char_raw('')
|
||||
if ch in '\b\x7f':
|
||||
if s: s = s[:-1]
|
||||
elif ch in '\n ':
|
||||
if s: break
|
||||
else: s += ch
|
||||
return s
|
||||
|
||||
wl = baseconv.digits[self.wl_id]
|
||||
|
||||
def in_list(w):
|
||||
from bisect import bisect_left
|
||||
idx = bisect_left(wl,w)
|
||||
return(True,False)[idx == len(wl) or w != wl[idx]]
|
||||
|
||||
words,i,p = [],0,('Enter word #{}: ','Incorrect entry. Repeat word #{}: ')
|
||||
while len(words) < mn_len:
|
||||
msg_r('{r}{s}{r}'.format(r='\r',s=' '*40))
|
||||
if i == 1: time.sleep(0.1)
|
||||
msg_r(p[i].format(len(words)+1))
|
||||
s = get_word()
|
||||
if in_list(s):
|
||||
words.append(s); i = 0
|
||||
else:
|
||||
i = 1
|
||||
msg('')
|
||||
qmsg('Mnemonic successfully entered')
|
||||
return ' '.join(words)
|
||||
|
||||
@staticmethod
|
||||
def _mn2hex_pad(mn): return len(mn) * 8 / 3
|
||||
|
|
@ -370,52 +423,15 @@ class Mnemonic (SeedSourceUnenc):
|
|||
@staticmethod
|
||||
def _hex2mn_pad(hexnum): return len(hexnum) * 3 / 8
|
||||
|
||||
@classmethod
|
||||
def hex2mn(cls,hexnum,wordlist):
|
||||
wl = cls.get_wordlist(wordlist)
|
||||
return baseconv.fromhex(cls.mn_base,hexnum,wl,cls._hex2mn_pad(hexnum))
|
||||
|
||||
@classmethod
|
||||
def mn2hex(cls,mn,wordlist):
|
||||
wl = cls.get_wordlist(wordlist)
|
||||
return baseconv.tohex(cls.mn_base,mn,wl,cls._mn2hex_pad(mn))
|
||||
|
||||
@classmethod
|
||||
def get_wordlist(cls,wordlist=None):
|
||||
wordlist = wordlist or cls.dfl_wordlist
|
||||
if wordlist not in cls.wordlists:
|
||||
die(1,"'%s': invalid wordlist. Valid choices: '%s'" %
|
||||
(wordlist,"' '".join(cls.wordlists)))
|
||||
|
||||
return __import__('mmgen.mn_'+wordlist,fromlist=['words']).words.split()
|
||||
|
||||
@classmethod
|
||||
def check_wordlist(cls,wlname):
|
||||
|
||||
wl = cls.get_wordlist(wlname)
|
||||
Msg('Wordlist: %s\nLength: %i words' % (capfirst(wlname),len(wl)))
|
||||
new_chksum = sha256(' '.join(wl)).hexdigest()[:8]
|
||||
|
||||
if (sorted(wl) == wl):
|
||||
Msg('List is sorted')
|
||||
else:
|
||||
die(3,'ERROR: List is not sorted!')
|
||||
|
||||
compare_chksums(
|
||||
new_chksum,'generated checksum',
|
||||
cls.wl_checksums[wlname],'saved checksum',
|
||||
die_on_fail=True)
|
||||
Msg('Checksum %s matches' % new_chksum)
|
||||
|
||||
def _format(self):
|
||||
wl = self.get_wordlist()
|
||||
seed_hex = self.seed.hexdata
|
||||
mn = baseconv.fromhex(self.mn_base,seed_hex,wl,self._hex2mn_pad(seed_hex))
|
||||
|
||||
ret = baseconv.tohex(self.mn_base,mn,wl,self._mn2hex_pad(mn))
|
||||
hexseed = self.seed.hexdata
|
||||
|
||||
mn = baseconv.fromhex(hexseed,self.wl_id,self._hex2mn_pad(hexseed))
|
||||
ret = baseconv.tohex(mn,self.wl_id,self._mn2hex_pad(mn))
|
||||
|
||||
# Internal error, so just die on fail
|
||||
compare_or_die(ret,'recomputed seed',
|
||||
seed_hex,'original',e='Internal error')
|
||||
compare_or_die(ret,'recomputed seed',hexseed,'original',e='Internal error')
|
||||
|
||||
self.ssdata.mnemonic = mn
|
||||
self.fmt_data = ' '.join(mn) + '\n'
|
||||
|
|
@ -423,27 +439,28 @@ class Mnemonic (SeedSourceUnenc):
|
|||
def _deformat(self):
|
||||
|
||||
mn = self.fmt_data.split()
|
||||
wl = self.get_wordlist()
|
||||
|
||||
if len(mn) not in g.mn_lens:
|
||||
if len(mn) not in self.mn_lens:
|
||||
msg('Invalid mnemonic (%i words). Allowed numbers of words: %s' %
|
||||
(len(mn),', '.join([str(i) for i in g.mn_lens])))
|
||||
(len(mn),', '.join([str(i) for i in self.mn_lens])))
|
||||
return False
|
||||
|
||||
for n,w in enumerate(mn,1):
|
||||
if w not in wl:
|
||||
if w not in baseconv.digits[self.wl_id]:
|
||||
msg('Invalid mnemonic: word #%s is not in the wordlist' % n)
|
||||
return False
|
||||
|
||||
seed_hex = baseconv.tohex(self.mn_base,mn,wl,self._mn2hex_pad(mn))
|
||||
hexseed = baseconv.tohex(mn,self.wl_id,self._mn2hex_pad(mn))
|
||||
ret = baseconv.fromhex(hexseed,self.wl_id,self._hex2mn_pad(hexseed))
|
||||
|
||||
ret = baseconv.fromhex(self.mn_base,seed_hex,wl,self._hex2mn_pad(seed_hex))
|
||||
if len(hexseed) * 4 not in g.seed_lens:
|
||||
msg('Invalid mnemonic (produces too large a number)')
|
||||
return False
|
||||
|
||||
# Internal error, so just die
|
||||
compare_or_die(' '.join(ret),'recomputed mnemonic',
|
||||
' '.join(mn),'original',e='Internal error')
|
||||
compare_or_die(' '.join(ret),'recomputed mnemonic',' '.join(mn),'original',e='Internal error')
|
||||
|
||||
self.seed = Seed(unhexlify(seed_hex))
|
||||
self.seed = Seed(unhexlify(hexseed))
|
||||
self.ssdata.mnemonic = mn
|
||||
|
||||
check_usr_seed_len(self.seed.length)
|
||||
|
|
|
|||
|
|
@ -196,14 +196,16 @@ def _get_terminal_size_mswin():
|
|||
return 80,25
|
||||
|
||||
def set_terminal_vars():
|
||||
global get_char,kb_hold_protect,get_terminal_size
|
||||
global get_char,get_char_raw,kb_hold_protect,get_terminal_size
|
||||
if _platform == 'linux':
|
||||
get_char_raw = _get_keypress_unix_raw
|
||||
get_char = (_get_keypress_unix_raw,_get_keypress_unix)[g.hold_protect]
|
||||
kb_hold_protect = (_kb_hold_protect_unix_raw,_kb_hold_protect_unix)[g.hold_protect]
|
||||
if not sys.stdin.isatty():
|
||||
get_char,kb_hold_protect = _get_keypress_unix_stub,_kb_hold_protect_unix_raw
|
||||
get_terminal_size = _get_terminal_size_linux
|
||||
else:
|
||||
get_char_raw = _get_keypress_mswin_raw
|
||||
get_char = (_get_keypress_mswin_raw,_get_keypress_mswin)[g.hold_protect]
|
||||
kb_hold_protect = (_kb_hold_protect_mswin_raw,_kb_hold_protect_mswin)[g.hold_protect]
|
||||
if not sys.stdin.isatty():
|
||||
|
|
|
|||
|
|
@ -300,52 +300,38 @@ def wif2addr(wif,compressed=False):
|
|||
Vmsg_r('Addr: '); Msg(addr)
|
||||
|
||||
wordlists = 'electrum','tirosh'
|
||||
dfl_wordlist = 'electrum'
|
||||
dfl_wl_id = 'electrum'
|
||||
|
||||
from mmgen.seed import Mnemonic
|
||||
def do_random_mn(nbytes,wordlist):
|
||||
hexrand = ba.hexlify(get_random(nbytes))
|
||||
Vmsg('Seed: %s' % hexrand)
|
||||
for wlname in ([wordlist],wordlists)[wordlist=='all']:
|
||||
for wl_id in ([wordlist],wordlists)[wordlist=='all']:
|
||||
if wordlist == 'all':
|
||||
Msg('%s mnemonic:' % (capfirst(wlname)))
|
||||
mn = Mnemonic.hex2mn(hexrand,wordlist=wlname)
|
||||
Msg('%s mnemonic:' % (capfirst(wl_id)))
|
||||
mn = baseconv.fromhex(hexrand,wl_id)
|
||||
Msg(' '.join(mn))
|
||||
|
||||
def mn_rand128(wordlist=dfl_wordlist): do_random_mn(16,wordlist)
|
||||
def mn_rand192(wordlist=dfl_wordlist): do_random_mn(24,wordlist)
|
||||
def mn_rand256(wordlist=dfl_wordlist): do_random_mn(32,wordlist)
|
||||
def mn_rand128(wordlist=dfl_wl_id): do_random_mn(16,wordlist)
|
||||
def mn_rand192(wordlist=dfl_wl_id): do_random_mn(24,wordlist)
|
||||
def mn_rand256(wordlist=dfl_wl_id): do_random_mn(32,wordlist)
|
||||
|
||||
def hex2mn(s,wordlist=dfl_wordlist):
|
||||
Msg(' '.join(Mnemonic.hex2mn(s,wordlist)))
|
||||
def hex2mn(s,wordlist=dfl_wl_id): Msg(' '.join(baseconv.fromhex(s,wordlist)))
|
||||
def mn2hex(s,wordlist=dfl_wl_id): Msg(baseconv.tohex(s.split(),wordlist))
|
||||
|
||||
def mn2hex(s,wordlist=dfl_wordlist):
|
||||
Msg(Mnemonic.mn2hex(s.split(),wordlist))
|
||||
def strtob58(s,pad=None): Msg(''.join(baseconv.fromhex(ba.hexlify(s),'b58',pad)))
|
||||
def hextob58(s,pad=None): Msg(''.join(baseconv.fromhex(s,'b58',pad)))
|
||||
def hextob32(s,pad=None): Msg(''.join(baseconv.fromhex(s,'b32',pad)))
|
||||
def b58tostr(s): Msg(ba.unhexlify(baseconv.tohex(s,'b58')))
|
||||
def b58tohex(s,pad=None): Msg(baseconv.tohex(s,'b58',pad))
|
||||
def b32tohex(s,pad=None): Msg(baseconv.tohex(s.upper(),'b32',pad))
|
||||
|
||||
def strtob58(s,pad=None):
|
||||
Msg(''.join(baseconv.fromhex(58,ba.hexlify(s),mmb.b58a,pad)))
|
||||
|
||||
def b58tostr(s):
|
||||
Msg(ba.unhexlify(baseconv.tohex(58,s,mmb.b58a)))
|
||||
|
||||
def b58tohex(s,pad=None):
|
||||
Msg(baseconv.tohex(58,s,mmb.b58a,pad))
|
||||
|
||||
def hextob58(s,pad=None):
|
||||
Msg(''.join(baseconv.fromhex(58,s,mmb.b58a,pad)))
|
||||
|
||||
def b32tohex(s,pad=None):
|
||||
Msg(baseconv.tohex(32,s.upper(),b32a,pad))
|
||||
|
||||
def hextob32(s,pad=None):
|
||||
Msg(''.join(baseconv.fromhex(32,s,b32a,pad)))
|
||||
|
||||
def mn_stats(wordlist=dfl_wordlist):
|
||||
Mnemonic.check_wordlist(wordlist)
|
||||
|
||||
def mn_printlist(wordlist=dfl_wordlist):
|
||||
wl = Mnemonic.get_wordlist(wordlist)
|
||||
Msg('\n'.join(wl))
|
||||
from mmgen.seed import Mnemonic
|
||||
def mn_stats(wordlist=dfl_wl_id):
|
||||
wordlist in baseconv.digits or die(1,"'{}': not a valid wordlist".format(wordlist))
|
||||
baseconv.check_wordlist(wordlist)
|
||||
def mn_printlist(wordlist=dfl_wl_id):
|
||||
wordlist in baseconv.digits or die(1,"'{}': not a valid wordlist".format(wordlist))
|
||||
Msg('\n'.join(baseconv.digits[wordlist]))
|
||||
|
||||
def id8(infile):
|
||||
Msg(make_chksum_8(
|
||||
|
|
|
|||
|
|
@ -213,14 +213,11 @@ def is_int(s):
|
|||
|
||||
# https://en.wikipedia.org/wiki/Base32#RFC_4648_Base32_alphabet
|
||||
# https://tools.ietf.org/html/rfc4648
|
||||
b32a = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
|
||||
def is_b32_str(s): return set(list(s)) <= set(list(b32a))
|
||||
def is_hex_str(s): return set(list(s.lower())) <= set(list(hexdigits.lower()))
|
||||
def is_hex_str_lc(s): return set(list(s)) <= set(list(hexdigits.lower()))
|
||||
def is_hex_str_uc(s): return set(list(s)) <= set(list(hexdigits.upper()))
|
||||
def is_b58_str(s):
|
||||
from mmgen.bitcoin import b58a
|
||||
return set(list(s)) <= set(b58a)
|
||||
def is_b58_str(s): return set(list(s)) <= set(baseconv.digits['b58'])
|
||||
def is_b32_str(s): return set(list(s)) <= set(baseconv.digits['b32'])
|
||||
|
||||
def is_ascii(s,enc='ascii'):
|
||||
try: s.decode(enc)
|
||||
|
|
@ -231,29 +228,76 @@ def is_utf8(s): return is_ascii(s,enc='utf8')
|
|||
|
||||
class baseconv(object):
|
||||
|
||||
@staticmethod
|
||||
def tohex(base,words,wl,pad=None): # accepts both string and list input
|
||||
if type(words) not in (list,tuple):
|
||||
words = tuple(words.strip())
|
||||
if not set(words).issubset(set(wl)):
|
||||
die(2,'{} is not in base-{} format'.format(repr(words_arg),base))
|
||||
deconv = [wl.index(words[::-1][i])*(base**i)
|
||||
for i in range(len(words))]
|
||||
mn_base = 1626 # tirosh list is 1633 words long!
|
||||
digits = {
|
||||
'electrum': tuple(__import__('mmgen.mn_electrum',fromlist=['words']).words.split()),
|
||||
'tirosh': tuple(__import__('mmgen.mn_tirosh',fromlist=['words']).words.split()[:mn_base]),
|
||||
'b58': tuple('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'),
|
||||
'b32': tuple('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'),
|
||||
'b16': tuple('0123456789abcdef'),
|
||||
'b10': tuple('0123456789'),
|
||||
'b8': tuple('01234567'),
|
||||
}
|
||||
wl_chksums = {
|
||||
'electrum': '5ca31424',
|
||||
'tirosh': '48f05e1f', # tirosh truncated to mn_base (1626)
|
||||
# 'tirosh1633': '1a5faeff'
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_wordlist_chksum(cls,wl_id):
|
||||
return sha256(' '.join(cls.digits[wl_id])).hexdigest()[:8]
|
||||
|
||||
@classmethod
|
||||
def check_wordlists(cls):
|
||||
for k,v in cls.wl_chksums.items(): assert cls.get_wordlist_chksum(k) == v
|
||||
|
||||
@classmethod
|
||||
def check_wordlist(cls,wl_id):
|
||||
|
||||
wl = baseconv.digits[wl_id]
|
||||
Msg('Wordlist: %s\nLength: %i words' % (capfirst(wl_id),len(wl)))
|
||||
new_chksum = cls.get_wordlist_chksum(wl_id)
|
||||
|
||||
a,b = 'generated checksum','saved checksum'
|
||||
compare_chksums(new_chksum,a,cls.wl_chksums[wl_id],b,die_on_fail=True)
|
||||
|
||||
Msg('Checksum %s matches' % new_chksum)
|
||||
Msg('List is sorted') if tuple(sorted(wl)) == wl else die(3,'ERROR: List is not sorted!')
|
||||
|
||||
|
||||
@classmethod
|
||||
def tohex(cls,words_arg,wl_id,pad=None):
|
||||
|
||||
words = words_arg if type(words_arg) in (list,tuple) else tuple(words_arg.strip())
|
||||
|
||||
wl = cls.digits[wl_id]
|
||||
base = len(wl)
|
||||
|
||||
if not set(words) <= set(wl):
|
||||
die(2,'{} is not in {} (base{}) format'.format(repr(words_arg),wl_id,base))
|
||||
|
||||
deconv = [wl.index(words[::-1][i])*(base**i) for i in range(len(words))]
|
||||
ret = ('{:0{w}x}'.format(sum(deconv),w=pad or 0))
|
||||
return ('','0')[len(ret) % 2] + ret
|
||||
|
||||
@staticmethod
|
||||
def fromhex(base,hexnum,wl,pad=None):
|
||||
assert len(wl) == base
|
||||
@classmethod
|
||||
def fromhex(cls,hexnum,wl_id,pad=None):
|
||||
|
||||
hexnum = hexnum.strip()
|
||||
if not is_hex_str(hexnum):
|
||||
die(2,"'%s': not a hexadecimal number" % hexnum)
|
||||
|
||||
wl = cls.digits[wl_id]
|
||||
base = len(wl)
|
||||
num,ret = int(hexnum,16),[]
|
||||
while num:
|
||||
ret.append(num % base)
|
||||
num /= base
|
||||
return [wl[n] for n in [0] * ((pad or 0)-len(ret)) + ret[::-1]]
|
||||
|
||||
baseconv.check_wordlists()
|
||||
|
||||
def match_ext(addr,ext):
|
||||
return addr.split('.')[-1] == ext
|
||||
|
||||
|
|
@ -464,7 +508,7 @@ def write_data_to_file(
|
|||
if sys.stdout.isatty():
|
||||
if no_tty:
|
||||
die(2,'Printing %s to screen is not allowed' % desc)
|
||||
if ask_tty and not opt.quiet:
|
||||
if (ask_tty and not opt.quiet) or binary:
|
||||
confirm_or_exit('','output %s to screen' % desc)
|
||||
else:
|
||||
try: of = os.readlink('/proc/%d/fd/1' % os.getpid()) # Linux
|
||||
|
|
@ -567,7 +611,8 @@ def get_lines_from_file(fn,desc='',trim_comments=False):
|
|||
return ret
|
||||
|
||||
def get_data_from_user(desc='data',silent=False):
|
||||
data = my_raw_input('Enter %s: ' % desc, echo=opt.echo_passphrase)
|
||||
p = ('','Enter {}: '.format(desc))[g.stdin_tty]
|
||||
data = my_raw_input(p,echo=opt.echo_passphrase)
|
||||
dmsg('User input: [%s]' % data)
|
||||
return data
|
||||
|
||||
|
|
@ -618,20 +663,21 @@ def my_raw_input(prompt,echo=True,insert_txt='',use_readline=True):
|
|||
|
||||
return reply.strip()
|
||||
|
||||
def keypress_confirm(prompt,default_yes=False,verbose=False):
|
||||
def keypress_confirm(prompt,default_yes=False,verbose=False,no_nl=False):
|
||||
|
||||
from mmgen.term import get_char
|
||||
|
||||
q = ('(y/N)','(Y/n)')[bool(default_yes)]
|
||||
p = '{} {}: '.format(prompt,q)
|
||||
nl = ('\n','\r{}\r'.format(' '*len(p)))[no_nl]
|
||||
|
||||
while True:
|
||||
reply = get_char('%s %s: ' % (prompt, q)).strip('\n\r')
|
||||
|
||||
reply = get_char(p).strip('\n\r')
|
||||
if not reply:
|
||||
if default_yes: msg(''); return True
|
||||
else: msg(''); return False
|
||||
elif reply in 'yY': msg(''); return True
|
||||
elif reply in 'nN': msg(''); return False
|
||||
if default_yes: msg_r(nl); return True
|
||||
else: msg_r(nl); return False
|
||||
elif reply in 'yY': msg_r(nl); return True
|
||||
elif reply in 'nN': msg_r(nl); return False
|
||||
else:
|
||||
if verbose: msg('\nInvalid reply')
|
||||
else: msg_r('\r')
|
||||
|
|
@ -674,7 +720,7 @@ def do_pager(text):
|
|||
|
||||
def do_license_msg(immed=False):
|
||||
|
||||
if opt.quiet or g.no_license or opt.yes: return
|
||||
if opt.quiet or g.no_license or opt.yes or not g.stdin_tty: return
|
||||
|
||||
import mmgen.license as gpl
|
||||
|
||||
|
|
|
|||
17
test/ref/98831F3A-фубар@crypto.org-b58-20[1,4,9-11,1100].pws
Normal file
17
test/ref/98831F3A-фубар@crypto.org-b58-20[1,4,9-11,1100].pws
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# MMGen password file
|
||||
#
|
||||
# This file is editable.
|
||||
# Everything following a hash symbol '#' is a comment and ignored by MMGen.
|
||||
# A text label of 32 characters or less may be added to the right of each
|
||||
# password. The label may contain any printable ASCII symbol.
|
||||
#
|
||||
# Password data checksum for 98831F3A-фубар@crypto.org-b58-20[1,4,9-11,1100]: A983 DAB9 5514 27FB
|
||||
# Record this value to a secure location.
|
||||
98831F3A фубар@crypto.org b58:20 {
|
||||
1 MZ5eTt6xjpK1h6YeFuzJ
|
||||
4 E5gXkqxwpLPRzCVSHNfv
|
||||
9 qaoShe24QgvDXA19Ue5r
|
||||
10 kSMEeWLRH29Fthd6pDE7
|
||||
11 5JXuV3oRdG2KQcejAYhE
|
||||
1100 n3VQMkfGutW49yZDUny1
|
||||
}
|
||||
21
test/test.py
21
test/test.py
|
|
@ -280,8 +280,8 @@ cfgs = {
|
|||
'ref_bw_seed_id': '33F10310',
|
||||
'addrfile_chk': ('B230 7526 638F 38CB','B64D 7327 EF2A 60FE')[g.testnet],
|
||||
'keyaddrfile_chk': ('CF83 32FB 8A8B 08E2','FEBF 7878 97BB CC35')[g.testnet],
|
||||
'passfile_chk': '3EA0 A3C9 DA28 5126',
|
||||
'passfile32_chk': 'EF67 D0BE 4B24 9B4F',
|
||||
'passfile_chk': 'EB29 DC4F 924B 289F',
|
||||
'passfile32_chk': '37B6 C218 2ABC 7508',
|
||||
'wpasswd': 'reference password',
|
||||
'ref_wallet': 'FE3C6545-D782B529[128,1].mmdat',
|
||||
'ic_wallet': 'FE3C6545-E29303EA-5E229E30[128,1].mmincog',
|
||||
|
|
@ -309,8 +309,8 @@ cfgs = {
|
|||
'ref_bw_seed_id': 'CE918388',
|
||||
'addrfile_chk': ('8C17 A5FA 0470 6E89','0A59 C8CD 9439 8B81')[g.testnet],
|
||||
'keyaddrfile_chk': ('9648 5132 B98E 3AD9','2F72 C83F 44C5 0FAC')[g.testnet],
|
||||
'passfile_chk': '000C 7711 CD45 C5BE',
|
||||
'passfile32_chk': 'AFEC 54A1 7D79 1866',
|
||||
'passfile_chk': 'ADEA 0083 094D 489A',
|
||||
'passfile32_chk': '2A28 C5C7 36EC 217A',
|
||||
'wpasswd': 'reference password',
|
||||
'ref_wallet': '1378FC64-6F0F9BB4[192,1].mmdat',
|
||||
'ic_wallet': '1378FC64-2907DE97-F980D21F[192,1].mmincog',
|
||||
|
|
@ -338,16 +338,16 @@ cfgs = {
|
|||
'ref_bw_seed_id': 'B48CD7FC',
|
||||
'addrfile_chk': ('6FEF 6FB9 7B13 5D91','3C2C 8558 BB54 079E')[g.testnet],
|
||||
'keyaddrfile_chk': ('9F2D D781 1812 8BAD','7410 8F95 4B33 B4B2')[g.testnet],
|
||||
'passfile_chk': '54B1 A5BE 9F07 1FDD',
|
||||
'passfile32_chk': '072A 4A13 FB64 B64B',
|
||||
'passfile_chk': '2D6D 8FBA 422E 1315',
|
||||
'passfile32_chk': 'F6C1 CDFB 97D9 FCAE',
|
||||
'wpasswd': 'reference password',
|
||||
'ref_wallet': '98831F3A-{}[256,1].mmdat'.format(('27F2BF93','E2687906')[g.testnet]),
|
||||
'ref_addrfile': '98831F3A[1,31-33,500-501,1010-1011]{}.addrs'.format(tn_desc),
|
||||
'ref_keyaddrfile': '98831F3A[1,31-33,500-501,1010-1011]{}.akeys.mmenc'.format(tn_desc),
|
||||
'ref_passwdfile': '98831F3A-фубар@crypto.org-base58-20[1,4,9-11,1100].pws',
|
||||
'ref_passwdfile': '98831F3A-фубар@crypto.org-b58-20[1,4,9-11,1100].pws',
|
||||
'ref_addrfile_chksum': ('6FEF 6FB9 7B13 5D91','3C2C 8558 BB54 079E')[g.testnet],
|
||||
'ref_keyaddrfile_chksum': ('9F2D D781 1812 8BAD','7410 8F95 4B33 B4B2')[g.testnet],
|
||||
'ref_passwdfile_chksum': '7723 735B 2CBB 2571',
|
||||
'ref_passwdfile_chksum': 'A983 DAB9 5514 27FB',
|
||||
# 'ref_fake_unspent_data':'98831F3A_unspent.json',
|
||||
'ref_tx_file': 'FFB367[1.234]{}.rawtx'.format(tn_desc),
|
||||
'ic_wallet': '98831F3A-5482381C-18460FB1[256,1].mmincog',
|
||||
|
|
@ -631,6 +631,9 @@ os.environ['MMGEN_NO_LICENSE'] = '1'
|
|||
os.environ['MMGEN_MIN_URANDCHARS'] = '3'
|
||||
os.environ['MMGEN_BOGUS_SEND'] = '1'
|
||||
|
||||
# Tell spawned programs they're running in the test suite
|
||||
os.environ['MMGEN_TEST_SUITE'] = '1'
|
||||
|
||||
if opt.debug_scripts: os.environ['MMGEN_DEBUG'] = '1'
|
||||
|
||||
if opt.buf_keypress:
|
||||
|
|
@ -1713,7 +1716,7 @@ class MMGenTestSuite(object):
|
|||
t.hash_preset('new key list','1')
|
||||
# t.passphrase_new('new key list','kafile password')
|
||||
t.passphrase_new('new key list',cfg['kapasswd'])
|
||||
t.written_to_file('Secret keys',oo=True)
|
||||
t.written_to_file('Encrypted secret keys',oo=True)
|
||||
t.ok()
|
||||
|
||||
def refkeyaddrgen(self,name,wf,pf):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue