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:
The MMGen Project 2017-07-07 16:28:07 +03:00
commit fde885f55b
Signed by: mmgen
GPG key ID: 62DBE9E5212F05BE
13 changed files with 617 additions and 282 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View 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')

View file

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

View file

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

View file

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

View file

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

View 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
}

View file

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