Improved handling of user input; code cleanups

This commit is contained in:
The MMGen Project 2014-03-25 12:18:26 +04:00
commit 9a64bf42e2
18 changed files with 617 additions and 502 deletions

287
README.md
View file

@ -1,9 +1,9 @@
# mmgen = Multi-Mode GENerator
# MMGen = Multi-Mode GENerator
## a Bitcoin cold storage solution for the command line
NOTE: Parts of this README are now **out of date**. In particular, the
new transaction scripts automate the process of offline signing, so that
your private keys never touch the online machine. An updated README is
your private keys never touch your online machine. An updated README is
on the way. For the time being, consult the `--help` option of the
`mmgen-tx*` scripts.
@ -14,143 +14,218 @@ addresses is done at your own risk.
### Features:
> As with all deterministic wallets, mmgen can generate an unlimited number
> of address/key pairs from a single seed. You back up your wallet only once.
> Like all deterministic wallets, MMGen can generate a virtually
> unlimited number of address/key pairs from a single seed, allowing you
> to maintain and track a large number of addresses with balances. Your
> wallet never changes (unless you change the password), so you need
> back it up only once.
> With MMGen you can choose from four different ways to access your Bitcoins:
> The "master key", the seed providing access to all your Bitcoins, can
> be stored in four different ways:
>> 1) an encrypted wallet (the AES 256 key is generated from your
>> 1) in an encrypted wallet (the AES 256 key is generated from your
>> password using the crack-resistant scrypt hash function. The
>> wallet's password and hash strength can be changed);
>> 2) a short, human-readable seed file (unencrypted);
>> 2) in a one-line, human-readable seed file (unencrypted);
>> 3) an Electrum-like mnemonic of 12, 18 or 24 words; or
>> 3) as an Electrum-like mnemonic of 12, 18 or 24 words; or
>> 4) a brainwallet password (recommended for expert users only).
>> 4) as a brainwallet password (this option is recommended for expert
>> users only).
> Furthermore, these methods can all be combined. If you forget your
> mnemonic, for example, you can regenerate it and your keys from a
> stored wallet or seed. Correspondingly, a lost wallet or seed can be
> recovered from the mnemonic.
> The wallet and seed are short, simple text files suitable for printing
> or even writing out by hand. Built-in checksums are used to verify
> they've been correctly copied. The base-58-encoded seed is short
> enough to memorize, providing another brain storage alternative.
> The wallet and seed files are in a simple, ASCII-based format suitable
> for printing or even writing out by hand. Built-in checksums are used
> to verify they've been correctly copied. The base-58-encoded seed
> file is short enough to memorize, providing another brain storage
> alternative.
> Implemented as a suite of lightweight Python scripts with a
> command-line interface, MMGen demands practically no system resources.
> Yet in tandem with a bitcoind enabled for watch-only addresses
> (see below), it provides a complete solution for securely
> storing Bitcoins offline and tracking and spending them online.
> Transactions are signed offline: your private keys never touch an
> online computer.
> Implemented as a suite of lightweight Python scripts for the console,
> MMGen requires only a bare minimum of system resources. Yet in tandem
> with a watch-only enabled bitcoind (see below), it provides a robust
> solution for securely storing, tracking, spending and receiving
> Bitcoins.
> MMGen is currently supported on Windows and Linux.
### Download/Install
#### Debian/Ubuntu Linux:
> **Perform the following steps on both your online and offline
> computers:**
> Install the pip Python installer:
sudo apt-get install python-pip
> Install required Python modules:
sudo pip install ecdsa scrypt pycrypto bitcoin-python
> Install MMGen:
git clone https://github.com/mmgen/mmgen.git
cd mmgen; sudo ./setup.py install
> Install vanitygen (optional but recommended):
git clone https://github.com/samr7/vanitygen.git
(build and put the 'keyconv' executable in your path)
> At this point you may begin using MMgen to create wallets, generate
> keys and create raw transactions as described in **Using MMGen**
> below. But since you'd like to be able to track addresses and sign
> transactions too, you'll now need to install the bitcoin daemon,
> `bitcoind`, on both your online and offline machines.
> **Bitcoind installation**
> On the **offline machine**, the bitcoin daemon is used solely for
> signing transactions and can therefore be run without a blockchain.
> The version bundled with the prebuilt Bitcoin-QT is just fine for this
> purpose. It can be obtained here:
https://bitcoin.org/en/download
> After installation, locate the bitcoind executable, optionally place
> it in your path and start it with the arguments `-daemon -maxconnections=0`.
> Note that in the absence of a blockchain the daemon starts very quickly
> and uses practically no CPU power once running. Thus you'll have no
> problem using a low-powered computer such as a netbook for your
> offline machine.
> On the **online machine**, the bitcoin daemon is used for tracking
> addresses and must be run with the full blockchain. Thus a more
> powerful computer is required here. In addition, the precompiled
> bitcoin package we installed above lacks (for now) the watch-only
> address support we need, so we must get and compile Sipa's
> watch-only enabled version from github:
git clone https://github.com/sipa/bitcoin
cd bitcoin
git branch wo origin/watchonly
git checkout wo
./configure
make
(You may have to install the libboost-all-dev package for the build to succeed)
> With your online machine connected to the Internet, start your freshly
> compiled daemon and let it synchronize, making sure to move your old
> `wallet.dat` out of harm's way beforehand if you have an existing
> bitcoin installation. You'll use the new wallet created by the daemon
> on startup as your **tracking wallet**.
> Your setup is now complete.
### Instructions for Linux/Unix:
### Download/Install:
> Install required Python modules:
sudo pip install ecdsa scrypt pycrypto bitcoin-python
> Install mmgen:
git clone https://github.com/mmgen/mmgen.git
cd mmgen; sudo ./setup.py install
> Install vanitygen (optional but recommended):
git clone https://github.com/samr7/vanitygen.git
(build and put the 'keyconv' executable in your path)
### Getting Started:
### Using MMGen:
> On your offline computer:
> Generate a wallet with a random seed:
$ mmgen-walletgen
...
Wallet saved to file '89ABCDEF-76543210[256,3].dat'
$ mmgen-walletgen
...
Wallet saved to file '89ABCDEF-76543210[256,3].mmdat'
> "89ABCDEF" is the Seed ID; "76543210" is the Key ID. These are
> randomly generated, so your IDs will naturally be different than the
> fictitious ones used in this example.
> "89ABCDEF" is the Seed ID; "76543210" is the Key ID. These are
> randomly generated, so your IDs will of course be different than the
> fictitious ones used here.
> The Seed ID never changes and will be used to identify all
> keys/addresses generated by this wallet. The Key ID changes when the
> wallet's password or hash preset are changed.
> The Seed ID never changes and is used to identify all keys/addresses
> generated by this wallet. The Key ID changes when the wallet's
> password or hash preset are changed.
> "256" is the seed length; "3" is the scrypt hash preset. These are
> "256" is the seed length; "3" is the scrypt hash preset. These are
> configurable.
> Generate ten addresses with the wallet:
$ mmgen-addrgen 89ABCDEF-76543210[256,3].dat 1-10
...
Address data saved to file '89ABCDEF[1-10].addrs'
$ mmgen-addrgen 89ABCDEF-76543210[256,3].mmdat 1-10
...
Address data saved to file '89ABCDEF[1-10].addrs'
> Note that the address range, "1-10", is included in the resulting filename.
> Note that the address range, "1-10", is reflected in the resulting filename.
$ cat '89ABCDEF[1-10].addrs'
89ABCDEF {
1 16bNmyYISiptuvJG3X7MPwiiS4HYvD7ksE
2 1AmkUxrfy5dMrfmeYwTxLxfIswUCcpeysc
3 1HgYCsfqYzIg7LVVfDTp7gYJocJEiDAy6N
4 14Tu3z1tiexXDonNsFIkvzqutE5E3pTK8s
5 1PeI55vtp2bX2uKDkAAR2c6ekHNYe4Hcq7
6 1FEqfEsSILwXPfMvVvVuUovzTaaST62Mnf
7 1LTTzuhMqPLwQ4IGCwwugny6ZMtUQJSJ1
8 1F9495H8EJLb54wirgZkVgI47SP7M2RQWv
9 1JbrCyt7BdxRE9GX1N7GiEct8UnIjPmpYd
10 1H7vVTk4ejUbQXw45I6g5qvPBSe9bsjDqh
$ cat '89ABCDEF[1-10].addrs'
89ABCDEF {
1 16bNmyYISiptuvJG3X7MPwiiS4HYvD7ksE
2 1AmkUxrfy5dMrfmeYwTxLxfIswUCcpeysc
3 1HgYCsfqYzIg7LVVfDTp7gYJocJEiDAy6N
4 14Tu3z1tiexXDonNsFIkvzqutE5E3pTK8s
5 1PeI55vtp2bX2uKDkAAR2c6ekHNYe4Hcq7
6 1FEqfEsSILwXPfMvVvVuUovzTaaST62Mnf
7 1LTTzuhMqPLwQ4IGCwwugny6ZMtUQJSJ1
8 1F9495H8EJLb54wirgZkVgI47SP7M2RQWv
9 1JbrCyt7BdxRE9GX1N7GiEct8UnIjPmpYd
10 1H7vVTk4ejUbQXw45I6g5qvPBSe9bsjDqh
}
> Let's label the first two addresses "Donations" and "Client 1"
> and import them into our tracking wallet. To do this,
> copy and edit the above file in a text editor such as vim:
$ cat my.addrs
89ABCDEF {
1 16bNmyYISiptuvJG3X7MPwiiS4HYvD7ksE Donations
2 1AmkUxrfy5dMrfmeYwTxLxfIswUCcpeysc Client 1
}
> With the online bitcoind running, import the two addresses into the
> wallet:
> To store your Bitcoins, spend them into these addresses from whatever
> wallets/software you're currently using. If you have lots of BTC,
> generate many addresses so that each address will have only a
> relatively small balance.
$ mmgen-addrimport my.addrs
### END rewrite
### Spending your stored coins:
> Take address 1 out of cold storage by generating a key for it:
$ mmgen-keygen 89ABCDEF-76543210[256,3].dat 1
...
Key data saved to file '89ABCDEF[1].akeys'
$ mmgen-keygen 89ABCDEF-76543210[256,3].mmdat 1
...
Key data saved to file '89ABCDEF[1].akeys'
$ cat 89ABCDEF[1].akeys
89ABCDEF {
1 sec: 5JCAfK1pjRoJgmpmd2HEMNwHxAzprGIXeQt8dz5qt3iLvU2KCbS
addr: 16bNmyYISiptuvJG3X7MPwiiS4HYvD7ksE
}
$ cat 89ABCDEF[1].akeys
89ABCDEF {
1 sec: 5JCAfK1pjRoJgmpmd2HEMNwHxAzprGIXeQt8dz5qt3iLvU2KCbS
addr: 16bNmyYISiptuvJG3X7MPwiiS4HYvD7ksE
}
> Save the .akeys file to a USB stick and transfer it to your online computer.
> On your online computer, import the secret key into a running bitcoind
> or bitcoin-qt:
$ bitcoind importprivkey 5JCAfK1pjRoJgmpmd2HEMNwHxAzprGIXeQt8dz5qt3iLvU2KCbS
$ bitcoind importprivkey 5JCAfK1pjRoJgmpmd2HEMNwHxAzprGIXeQt8dz5qt3iLvU2KCbS
> You're done! This address' balance can now be spent.
> You're done! This address' balance can now be spent.
> OPTIONAL: To track balances without exposing secret keys on your
> online computer, download and compile sipa's bitcoind patched for
> watch-only addresses:
$ git clone https://github.com/sipa/bitcoin
$ git branch mywatchonly remotes/origin/watchonly
$ git checkout mywatchonly
(build, install)
(You may have to install libboost-all-dev for the build to succeed)
$ git clone https://github.com/sipa/bitcoin
$ git branch mywatchonly remotes/origin/watchonly
$ git checkout mywatchonly
(build, install)
(You may have to install libboost-all-dev for the build to succeed)
> With your newly-compiled bitcoind running, import the addresses from
> '89ABCDEF[1-10].addrs' to track their balances:
$ bitcoind importaddress 16bNmyYISiptuvJG3X7MPwiiS4HYvD7ksE
$ bitcoind importaddress 1AmkUxrfy5dMrfmeYwTxLxfIswUCcpeysc
$ ...
$ bitcoind importaddress 16bNmyYISiptuvJG3X7MPwiiS4HYvD7ksE
$ bitcoind importaddress 1AmkUxrfy5dMrfmeYwTxLxfIswUCcpeysc
$ ...
### Using the mnemonic and seed features:
@ -158,51 +233,51 @@ addresses is done at your own risk.
> Generate a mnemonic from the wallet:
$ mmgen-walletchk -m '89ABCDEF-76543210[256,3].dat'
...
Mnemonic data saved to file '89ABCDEF.words'
$ mmgen-walletchk -m '89ABCDEF-76543210[256,3].mmdat'
...
Mnemonic data saved to file '89ABCDEF.mmwords'
$ cat 89ABCDEF.words
pleasure tumble spider laughter many stumble secret bother
after search float absent path strong curtain savior
worst suspend bright touch away dirty measure thorn
$ cat 89ABCDEF.mmwords
pleasure tumble spider laughter many stumble secret bother
after search float absent path strong curtain savior
worst suspend bright touch away dirty measure thorn
> Note: a 128- or 192-bit seed will generate a shorter mnemonic of 12 or
> 18 words. You may generate a wallet with a these seed lengths by
> using the `-l` option of `mmgen-walletgen`. Whether you consider
> 128 bits of entropy enough is your call. It's probably adequate for
> 128 bits of entropy enough is your call. It's probably adequate for
> the foreseeable future.
> Generate addresses 1-11 using the mnemonic instead of the wallet:
$ mmgen-addrgen -m 89ABCDEF.words 1-11
...
Address data saved to file '89ABCDEF[1-11].addrs'
$ mmgen-addrgen -m 89ABCDEF.mmwords 1-11
...
Address data saved to file '89ABCDEF[1-11].addrs'
> Compare the first ten addresses with those earlier generated from the
> wallet. You'll see they're the same.
> Recover a lost wallet using the mnemonic:
$ mmgen-walletgen -m 89ABCDEF.words
...
Wallet saved to file '89ABCDEF-01234567[256,3].dat'
$ mmgen-walletgen -m 89ABCDEF.mmwords
...
Wallet saved to file '89ABCDEF-01234567[256,3].mmdat'
> Note that the regenerated wallet has a different Key ID but
> of course the same Seed ID.
> Seeds are generated and input the same way as mnemonics. Just change
> Seeds are generated and input the same way as mnemonics. Just change
> the `-m` option to `-s` in the preceding commands.
> A seed file for a 256-bit seed looks like this:
$ cat 8B7392ED.mmseed
f4c84b C5ZT wWpT Jsoi wRVw 2dm9 Aftd WLb8 FggQ eC8h Szjd da9L
$ cat 8B7392ED.mmseed
f4c84b C5ZT wWpT Jsoi wRVw 2dm9 Aftd WLb8 FggQ eC8h Szjd da9L
> And for a 128-bit seed:
$ cat 8E0DFB78.mmseed
0fe02f XnyC NfPH piuW dQ2d nM47 VU
$ cat 8E0DFB78.mmseed
0fe02f XnyC NfPH piuW dQ2d nM47 VU
> The latter is short enough to be memorized or written down.
@ -221,8 +296,8 @@ addresses is done at your own risk.
> Mnemonic and seed files may be output to a directory of your choice
> using the `-d` option of `mmgen-walletchk`.
> Bear in mind that mnemonic and seed data is unencrypted. If it's
> compromised, your Bitcoins can easily be stolen. Make sure no one's
> Bear in mind that mnemonic and seed data is unencrypted. If it's
> compromised, your Bitcoins can easily be stolen. Make sure no one's
> looking over your shoulder when you print mnemonic or seed data to
> screen. Securely delete your mnemonic and seed files. In Linux, you
> can achieve additional security by writing the files to volatile
@ -235,7 +310,7 @@ addresses is done at your own risk.
### Test suite:
> To see what tests are available, run the scripts in the 'tests'
> directory with no arguments. Among others, you might find the
> directory with no arguments. Among others, you might find the
> following tests to be of interest:
>> Compare 10 addresses generated by 'keyconv' with mmgen's

View file

@ -122,23 +122,18 @@ opts,cmd_args = process_opts(sys.argv,help_data,"".join(short_opts),long_opts)
if 'show_hash_presets' in opts: show_hash_presets()
# Sanity checking and processing of command-line arguments:
opts['gen_what'] = gen_what
set_if_unset_and_typeconvert(opts,(
('hash_preset',hash_preset,'str'),
('seed_len',seed_len,'int')
))
check_opts(opts,long_opts)
# Exits on invalid input
check_opts(opts,('hash_preset','seed_len','outdir','from_brain'))
if debug: show_opts_and_cmd_args(opts,cmd_args)
if len(cmd_args) == 1 and (
if len(cmd_args) == 1 and (
'from_mnemonic' in opts or
'from_brain' in opts or
'from_seed' in opts
): infile,addr_list_arg = "",cmd_args[0]
):
infile,addr_list_arg = "",cmd_args[0]
elif len(cmd_args) == 2:
infile,addr_list_arg = cmd_args
check_infile(infile)

View file

@ -48,6 +48,8 @@ if len(cmd_args) != 1 and not 'addrlist' in opts:
msg("You must specify an mmgen address list (and/or non-mmgen addresses with the '--addrlist' option)")
sys.exit(1)
check_opts(opts,long_opts)
if cmd_args:
check_infile(cmd_args[0])
seed_id,addr_data = parse_addrs_file(cmd_args[0])
@ -55,15 +57,15 @@ else:
seed_id,addr_data = "",[]
if 'addrlist' in opts:
check_infile(opts['addrlist'])
l = get_lines_from_file(opts['addrlist'],"non-mmgen addresses")
addr_data += [(None,i) for i in l]
msg_r("Validating addresses...")
for i in [i[1] for i in addr_data]:
if not verify_addr(i):
for i in addr_data:
if not verify_addr(i[1]):
msg("%s: invalid address" % i)
sys.exit(2)
msg("OK")
import mmgen.config

View file

@ -1 +0,0 @@
mmgen-addrgen

View file

@ -21,8 +21,7 @@ mmgen-passchg: Change a mmgen deterministic wallet's passphrase, label or
"""
import sys
import mmgen.Opts as Opts
from mmgen.Opts import *
from mmgen.utils import *
from mmgen.config import *
@ -51,12 +50,11 @@ short_opts = "hd:HkL:p:P:v"
long_opts = "help","outdir=","show_hash_presets","keep_old_passphrase",\
"label=","hash_preset=","passwd_file=","verbose"
opts,cmd_args = Opts.process_opts(sys.argv,help_data,short_opts,long_opts)
opts,cmd_args = process_opts(sys.argv,help_data,short_opts,long_opts)
if 'show_hash_presets' in opts: show_hash_presets()
set_if_unset_and_typeconvert(opts,(('hash_preset',hash_preset,'str'),))
check_opts(opts,('hash_preset','outdir','label'))
check_opts(opts,long_opts)
if len(cmd_args) != 1:
msg("One input file must be specified")

View file

@ -92,15 +92,15 @@ long_opts = "help","outdir=","echo_passphrase","json","keys","addrs",\
opts,cmd_args = process_opts(sys.argv,help_data,short_opts,long_opts)
from mmgen.utils import check_infile,check_opts
from mmgen.utils import check_infile
check_opts(opts,long_opts)
if len(cmd_args) == 1:
check_infile(cmd_args[0])
else:
usage(help_data)
check_opts(opts,('outdir',))
if ('json' not in opts and 'keys' not in opts
and 'addrs' not in opts and 'keysforaddrs' not in opts):
usage(help_data)

View file

@ -26,7 +26,7 @@ from mmgen.Opts import *
from mmgen.license import *
from mmgen.config import *
from mmgen.tx import *
from mmgen.utils import check_opts, msg, msg_r, user_confirm
from mmgen.utils import msg, msg_r, user_confirm
from decimal import Decimal
prog_name = sys.argv[0].split("/")[-1]
@ -55,12 +55,9 @@ long_opts = "help","outdir=","echo_passphrase","info","quiet"
opts,cmd_args = process_opts(sys.argv,help_data,short_opts,long_opts)
# Exits on invalid input
check_opts(opts, ('outdir',))
check_opts(opts,long_opts)
if debug:
print "Processed options: %s" % repr(opts)
print "Cmd args: %s" % repr(cmd_args)
if debug: show_opts_and_cmd_args(opts,cmd_args)
if len(cmd_args) == 3:
rcpt_arg,tx_fee,change_addr = cmd_args
@ -81,7 +78,7 @@ if not 'info' in opts:
# Begin execution
c = connect_to_bitcoind()
if not 'quiet' in opts and not 'info' in opts: do_license_msg()
if not 'quiet' in opts and not 'info' in opts: do_license_msg(immed=True)
# Begin test
# import mmgen.rpc
@ -145,10 +142,15 @@ for i in tx_out.keys(): tx_out[i] = float(tx_out[i])
if change: tx_out[change_addr] = float(change)
tx_hex = c.createrawtransaction(tx_in,tx_out)
prompt = "Transaction successfully created\nView decoded transaction?"
if user_confirm(prompt,default_yes=False):
view_tx_data(c,[i.__dict__ for i in sel_unspent],tx_hex)
msg("Transaction successfully created")
prompt = "View decoded transaction? (y)es, (N)o, (v)iew in pager"
reply = prompt_and_get_char(prompt,"YyNnVv",enter_ok=True)
if reply and reply in "YyVv":
pager = True if reply in "Vv" else False
view_tx_data(c,[i.__dict__ for i in sel_unspent],tx_hex,pager=pager)
prompt = "Save transaction?"
if user_confirm(prompt,default_yes=True):
print_tx_to_file(tx_hex,sel_unspent,send_amt,opts)
else:
msg("Transaction not saved")

View file

@ -25,7 +25,7 @@ from mmgen.Opts import *
from mmgen.license import *
from mmgen.config import *
from mmgen.tx import *
from mmgen.utils import msg,check_opts,check_infile,get_lines_from_file,confirm_or_exit
from mmgen.utils import msg,check_infile,get_lines_from_file,confirm_or_exit
prog_name = sys.argv[0].split("/")[-1]
@ -45,8 +45,7 @@ long_opts = "help","outdir=","quiet"
opts,cmd_args = process_opts(sys.argv,help_data,short_opts,long_opts)
# Exits on invalid input
check_opts(opts, ('outdir',))
check_opts(opts,long_opts)
if len(cmd_args) == 1:
infile = cmd_args[0]

View file

@ -20,13 +20,12 @@ mmgen-txsign: Sign a Bitcoin transaction generated by mmgen-txcreate
"""
import sys
#from hashlib import sha256
from mmgen.Opts import *
from mmgen.license import *
from mmgen.config import *
from mmgen.tx import *
from mmgen.utils import *
from mmgen.utils import msg
help_data = {
'prog_name': sys.argv[0].split("/")[-1],
@ -80,96 +79,12 @@ long_opts = "help","outdir=","echo_passphrase","info","tx_id",\
opts,infiles = process_opts(sys.argv,help_data,short_opts,long_opts)
# Exits on invalid input
check_opts(opts, ('outdir','from_brain'))
if 'keys_from_file' in opts: check_infile(opts['keys_from_file'])
check_opts(opts,long_opts)
if not infiles: usage(help_data)
for i in infiles: check_infile(i)
def get_keys_for_mmgen_addrs(mmgen_addrs,infiles):
seed_ids = list(set([i['account'][:8] for i in mmgen_addrs]))
seed_ids_save = seed_ids[0:]
keys = []
while seed_ids:
infile = False
if infiles:
infile = infiles.pop()
seed = get_seed(infile,opts)
elif "from_brain" in opts or "from_mnemonic" in opts or "from_seed" in opts:
msg("Need data for seed ID %s" % seed_ids[0])
seed = get_seed_retry("",opts)
else:
b,p,v = ("A seed","","is") if len(seed_ids) == 1 else ("Seed","s","are")
msg("ERROR: %s source%s %s required for the following seed ID%s: %s" %
(b,p,v,p," ".join(seed_ids)))
sys.exit(2)
seed_id = make_chksum_8(seed)
if seed_id in seed_ids:
seed_ids.remove(seed_id)
seed_id_addrs = [
int(i['account'].split()[0][9:]) for i in mmgen_addrs
if i['account'][:8] == seed_id]
from mmgen.addr import generate_keys
keys += [i['wif'] for i in generate_keys(seed, seed_id_addrs)]
else:
if seed_id in seed_ids_save:
msg_r("Ignoring duplicate seed source")
if infile: msg(" '%s'" % infile)
else: msg(" for ID %s" % seed_id)
else:
msg("Seed source produced an invalid seed ID (%s)" % seed_id)
if infile:
msg("Invalid input file: %s" % infile)
sys.exit(2)
return keys
def sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys):
try:
sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
except:
from mmgen.rpc import exceptions
msg("Using keys in wallet.dat as per user request")
prompt = "Enter passphrase for bitcoind wallet: "
while True:
passwd = get_bitcoind_passphrase(prompt,opts)
try:
c.walletpassphrase(passwd, 9999)
except exceptions.WalletPassphraseIncorrect:
msg("Passphrase incorrect")
else:
msg("Passphrase OK"); break
sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
msg("Locking wallet")
try:
c.walletlock()
except:
msg("Failed to lock wallet")
return sig_tx
def missing_keys_errormsg(other_addrs):
msg("""
A key file (option '-f') or wallet.dat (option '-w') must be supplied
for the following non-mmgen address%s: %s""" %
("" if len(other_addrs) == 1 else "es",
" ".join([i['address'] for i in other_addrs])
))
# Begin execution
c = connect_to_bitcoind()
tx_file = infiles.pop(0)
@ -186,43 +101,37 @@ if 'info' in opts:
view_tx_data(c,inputs_data,tx_hex,metadata)
sys.exit(0)
if not 'quiet' in opts: do_license_msg()
if not 'quiet' in opts: do_license_msg(immed=True)
msg("Successfully opened transaction file '%s'" % tx_file)
if user_confirm("View transaction data? ",default_yes=False):
view_tx_data(c,inputs_data,tx_hex,metadata)
prompt = "View transaction data? (y)es, (N)o, (v)iew in pager"
reply = prompt_and_get_char(prompt,"YyNnVv",enter_ok=True)
if reply and reply in "YyVv":
p = True if reply in "Vv" else False
view_tx_data(c,inputs_data,tx_hex,metadata,pager=p)
# Are inputs mmgen addresses?
mmgen_addrs,other_addrs = [],[]
mmgen_addrs = [i for i in inputs_data if verify_mmgen_label(i['account'])]
other_addrs = [i for i in inputs_data if not verify_mmgen_label(i['account'])]
for i in inputs_data:
if verify_mmgen_label(i['account']):
mmgen_addrs.append(i)
else:
other_addrs.append(i)
if 'keys_from_file' in opts:
keys = get_lines_from_file(opts['keys_from_file'],"key data")
else:
keys = []
keys = get_lines_from_file(opts['keys_from_file'],"key data") \
if 'keys_from_file' in opts else []
if mmgen_addrs:
if other_addrs and not keys and not 'use_wallet_dat' in opts:
missing_keys_errormsg(other_addrs)
sys.exit(2)
keys += get_keys_for_mmgen_addrs(mmgen_addrs,infiles)
keys += get_keys_for_mmgen_addrs(mmgen_addrs,infiles,opts)
if 'use_wallet_dat' in opts:
sig_tx = sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys)
sig_tx = sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys,opts)
else:
sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
elif other_addrs:
if 'use_wallet_dat' in opts:
sig_tx = sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys)
sig_tx = sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys,opts)
else:
if keys:
sig_tx = sign_transaction(c,tx_hex,sig_data,keys)

View file

@ -49,7 +49,8 @@ long_opts = "help","outdir=","echo_passphrase","export_mnemonic",\
opts,cmd_args = process_opts(sys.argv,help_data,short_opts,long_opts)
check_opts(opts, ('outdir',))
# Argument sanity checks and processing:
check_opts(opts,long_opts)
if len(cmd_args) != 1: usage(help_data)
@ -61,7 +62,7 @@ if 'export_seed' in opts:
msg("Exporting seed data to file by user request")
seed = get_seed_from_wallet(cmd_args[0], opts)
msg("Wallet is OK")
if seed: msg("Wallet is OK")
if 'export_mnemonic' in opts:
wl = get_default_wordlist()

View file

@ -101,30 +101,15 @@ opts,cmd_args = process_opts(sys.argv,help_data,short_opts,long_opts)
if 'show_hash_presets' in opts: show_hash_presets()
# Argument sanity checks and processing:
check_opts(opts,long_opts)
set_if_unset_and_typeconvert(opts,(
('usr_randlen',usr_randlen,'int'),
('hash_preset',hash_preset,'str'),
('seed_len',seed_len,'int')
))
if debug: show_opts_and_cmd_args(opts,cmd_args)
# Exits on invalid input
check_opts(opts,
('usr_randlen','hash_preset','seed_len','outdir','label','from_brain')
)
if debug:
print "Processed options: %s" % repr(opts)
print "Cmd args: %s" % repr(cmd_args)
if len(cmd_args) == 1 and (
'from_brain' in opts or
'from_mnemonic' in opts or
'from_seed' in opts):
if len(cmd_args) == 1:
infile = cmd_args[0]
check_infile(infile)
elif len(cmd_args) == 0: infile = ""
elif len(cmd_args) == 0:
infile = ""
else: usage(help_data)
# Begin execution
@ -134,7 +119,7 @@ if not 'quiet' in opts: do_license_msg()
msg_r("Acquiring random data from your computer...")
from time import sleep
sleep(1)
sleep(0.25)
try:
from Crypto import Random
@ -157,7 +142,7 @@ usr_rand_data = sha256(usr_keys).digest() + \
sha256("".join(key_timings)).digest()
for i in 'from_mnemonic','from_brain','from_seed':
if i in opts:
if infile or (i in opts):
seed = get_seed_retry(infile,opts); break
else:
# Truncate random data for smaller seed lengths

View file

@ -18,11 +18,13 @@
import sys, getopt
from mmgen.config import *
from mmgen.utils import msg
def usage(hd):
print "USAGE: %s %s" % (hd['prog_name'], hd['usage'])
sys.exit(2)
def print_help(progname,help_data):
pn_len = str(len(progname)+2)
print (" %-"+pn_len+"s %s") % (progname.upper()+":", help_data['desc'])
@ -66,3 +68,118 @@ def process_opts(argv,help_data,short_opts,long_opts):
if debug: print "User-selected options: %s" % repr(opts)
return opts,args
def check_opts(opts,long_opts):
# These must be set to the default values in mmgen.config:
for i in cl_override_vars:
if i+"=" in long_opts:
set_if_unset_and_typeconvert(opts,i)
for opt in opts.keys():
val = opts[opt]
what = "parameter for '--%s' option" % opt.replace("_","-")
# Check for file existence and readability
for i in 'keys_from_file','addrlist','passwd_file','keysforaddrs':
if opt == i:
check_infile(val)
return
if opt == 'outdir':
what = "output directory"
import re, os, stat
d = re.sub(r'/*$','', val)
opts[opt] = d
try: mode = os.stat(d).st_mode
except:
msg("Unable to stat requested %s '%s'" % (what,d))
sys.exit(1)
if not stat.S_ISDIR(mode):
msg("Requested %s '%s' is not a directory" % (what,d))
sys.exit(1)
if not os.access(d, os.W_OK|os.X_OK):
msg("Requested %s '%s' is unwritable by you" % (what,d))
sys.exit(1)
elif opt == 'label':
label = val.strip()
opts[opt] = label
if len(label) > 32:
msg("Label must be 32 characters or less")
sys.exit(1)
from string import ascii_letters, digits
label_chrs = list(ascii_letters + digits) + [".", "_", " "]
for ch in list(label):
if ch not in label_chrs:
msg("'%s': illegal character in label" % ch)
sys.exit(1)
elif opt == 'from_brain':
try:
l,p = val.split(",")
except:
msg("'%s': invalid %s" % (val,what))
sys.exit(1)
try:
int(l)
except:
msg("'%s': invalid 'l' %s (not an integer)" % (l,what))
sys.exit(1)
if int(l) not in seed_lens:
msg("'%s': invalid 'l' %s. Options: %s" %
(l, what, ", ".join([str(i) for i in seed_lens])))
sys.exit(1)
if p not in hash_presets:
hps = ", ".join([i for i in sorted(hash_presets.keys())])
msg("'%s': invalid 'p' %s. Options: %s" % (p, what, hps))
sys.exit(1)
elif opt == 'seed_len':
if val not in seed_lens:
msg("'%s': invalid %s. Options: %s"
% (val,what,", ".join([str(i) for i in seed_lens])))
sys.exit(2)
elif opt == 'hash_preset':
if val not in hash_presets:
msg("'%s': invalid %s. Options: %s"
% (val,what,", ".join(sorted(hash_presets.keys()))))
sys.exit(2)
elif opt == 'usr_randlen':
if val > max_randlen or val < min_randlen:
msg("'%s': invalid %s (must be >= %s and <= %s)"
% (val,what,min_randlen,max_randlen))
sys.exit(2)
else:
if debug: print "check_opts(): No test for opt '%s'" % opt
def show_opts_and_cmd_args(opts,cmd_args):
print "Processed options: %s" % repr(opts)
print "Cmd args: %s" % repr(cmd_args)
def set_if_unset_and_typeconvert(opts,opt):
if opt in cl_override_vars:
if opt not in opts:
# Set to similarly named default value in mmgen.config
opts[opt] = eval(opt)
else:
vtype = type(eval(opt))
if vtype == int: f,t = int,"an integer"
elif vtype == str: f,t = str,"a string"
try:
opts[opt] = f(opts[opt])
except:
msg("'%s': invalid parameter for '--%s' option (not %s)" %
(opts[opt],opt.replace("_","-"),t))
sys.exit(1)

View file

@ -157,9 +157,9 @@ def format_addr_data(addrlist, seed_chksum, opts):
# address, and it will be appended to the bitcoind wallet label upon import.
# The label may contain ASCII letters, numerals, and the symbols
# '{}' and '{}'.
""".format(proj_name.capitalize(),max_wallet_addr_label_len,
"', '".join(wallet_addr_label_symbols[0:-1]),
wallet_addr_label_symbols[-1]).strip()
""".format(proj_name.capitalize(),max_addr_label_len,
"', '".join(addr_label_symbols[0:-1]),
addr_label_symbols[-1]).strip()
data = []
if not 'stdout' in opts: data.append(header + "\n")
data.append("%s {" % seed_chksum.upper())

View file

@ -30,6 +30,8 @@ seed_exts = wallet_ext, seed_ext, mn_ext, brain_ext
default_wl = "electrum"
#default_wl = "tirosh"
cl_override_vars = 'seed_len','hash_preset','usr_randlen'
seed_lens = 128,192,256
seed_len = 256
@ -56,6 +58,7 @@ hash_presets = {
'3': [14, 8, 8],
'4': [15, 8, 12],
'5': [16, 8, 16],
'6': [17, 8, 20],
}
wallet_addr_label_symbols = ".","_",",","-"," "
max_wallet_addr_label_len = 16
addr_label_symbols = ".","_",",","-"," "
max_addr_label_len = 16

View file

@ -585,15 +585,15 @@ copy of the Program in return for a fee.
"""
}
def do_license_msg():
def do_license_msg(immed=False):
msg(gpl['warning'])
prompt = "%s " % gpl['prompt'].strip()
while True:
reply = get_char(prompt)
reply = get_char(prompt, immed_chars="wc" if immed else "")
if reply == 'w':
from mmgen.utils import do_pager
do_pager(gpl['conditions'],"END OF CONDITIONS AND WARRANTY")
do_pager(gpl['conditions'])
elif reply == 'c':
msg(""); break
else:

View file

@ -206,7 +206,7 @@ def sort_and_view(unspent):
for n,i in enumerate(out):
if i.skip == "d":
addr = " |" + "-"*32
addr = "|" + "." * 33
else:
if show_mmaddr:
if verify_mmgen_label(i.account):
@ -215,21 +215,26 @@ def sort_and_view(unspent):
addr = i.address
else:
addr = i.address
txid = " |---" if i.skip == "t" else i.txid[:8]+"..."
txid = " |..." if i.skip == "t" else i.txid[:8]+"..."
output.append(fs % (str(n+1)+")",txid,i.vout,addr,i.amt,i.days))
skip_body = False
while True:
if skip_body: skip_body = False
if skip_body:
skip_body = False
immed_chars = "qpP"
else:
msg("\n".join(output))
msg("""
Sort options: [t]xid, [a]mount, a[d]dress, [A]ge, [r]everse, [M]mgen addr
View options: [g]roup, show [m]mgen addr""")
immed_chars = "qpPtadArMgm"
reply = get_char(
"(Type 'q' to quit sorting, 'p' to print to file, 'P' to view in pager): ")
"(Type 'q' to quit sorting, 'p' to print to file, 'v' to view in pager): ",
immed_chars=immed_chars)
if reply == 'a': unspent.sort(s_amt); sort = "amount"; break
elif reply == 't': unspent.sort(s_txid); sort = "txid"; break
elif reply == 'd': unspent.sort(s_addr); sort = "address"; break
@ -268,7 +273,7 @@ View options: [g]roup, show [m]mgen addr""")
write_to_file(outfile, outdata)
skip_body = True
msg("\nData written to '%s'" % outfile)
elif reply == 'P': do_pager("\n".join(output))
elif reply == 'v': do_pager("\n".join(output))
elif reply == 'q': break
else: msg("Invalid input")
@ -297,28 +302,28 @@ def verify_mmgen_label(s,return_str=False,check_label_len=False):
if not i in "0123456789": return fail
if check_label_len and comment:
check_wallet_addr_comment(comment)
check_addr_comment(comment)
return success
def view_tx_data(c,inputs_data,tx_hex,metadata=[]):
def view_tx_data(c,inputs_data,tx_hex,metadata=[],pager=False):
td = c.decoderawtransaction(tx_hex)
msg("TRANSACTION DATA:\n")
out = "TRANSACTION DATA\n\n"
if metadata: msg(
"Header: [Tx ID: {}] [Amount: {} BTC] [Time: {}]\n".format(*metadata))
if metadata:
out += "Header: [Tx ID: {}] [Amount: {} BTC] [Time: {}]\n\n".format(*metadata)
msg("Inputs:")
out += "Inputs:\n\n"
total_in = 0
for n,i in enumerate(td['vin']):
for j in inputs_data:
if j['txid'] == i['txid'] and j['vout'] == i['vout']:
days = int(j['confirmations'] * mins_per_block / (60*24))
total_in += j['amount']
msg(" " + """
out += (" " + """
%-2s tx,vout: %s,%s
address: %s
ID/label: %s
@ -326,25 +331,29 @@ def view_tx_data(c,inputs_data,tx_hex,metadata=[]):
confirmations: %s (around %s days)
""".strip() %
(n+1,i['txid'],i['vout'],j['address'],verify_mmgen_label(j['account'],True),
trim_exponent(j['amount']),j['confirmations'],days)+"\n")
trim_exponent(j['amount']),j['confirmations'],days)+"\n\n")
break
msg("Total input: %s BTC\n" % trim_exponent(total_in))
out += "Total input: %s BTC\n\n" % trim_exponent(total_in)
total_out = 0
msg("Outputs:")
out += "Outputs:\n\n"
for n,i in enumerate(td['vout']):
total_out += i['value']
msg(" " + """
out += (" " + """
%-2s address: %s
amount: %s BTC
""".strip() % (
n,
i['scriptPubKey']['addresses'][0],
trim_exponent(i['value']))
+ "\n")
msg("Total output: %s BTC" % trim_exponent(total_out))
msg("TX fee: %s BTC\n" % trim_exponent(total_in-total_out))
+ "\n\n")
out += "Total output: %s BTC\n" % trim_exponent(total_out)
out += "TX fee: %s BTC\n" % trim_exponent(total_in-total_out)
if pager: do_pager(out+"\n")
else: msg("\n"+out)
def parse_tx_data(tx_data,infile):
@ -418,20 +427,20 @@ def make_tx_out(rcpt_arg):
return tx_out
def check_wallet_addr_comment(label):
def check_addr_comment(label):
if len(label) > max_wallet_addr_label_len:
if len(label) > max_addr_label_len:
msg("'%s': overlong label (length must be <=%s)" %
(label,max_wallet_addr_label_len))
(label,max_addr_label_len))
sys.exit(3)
from string import ascii_letters, digits
chrs = tuple(ascii_letters + digits) + wallet_addr_label_symbols
chrs = tuple(ascii_letters + digits) + addr_label_symbols
for ch in list(label):
if ch not in chrs:
msg("'%s': illegal character in label '%s'" % (ch,label))
msg("Permitted characters: A-Za-z0-9, plus '%s'" %
"', '".join(wallet_addr_label_symbols))
"', '".join(addr_label_symbols))
sys.exit(3)
@ -474,7 +483,7 @@ def parse_addrs_file(f):
msg("'%s': invalid address" % d[1])
sys.exit(3)
if len(d) == 3: check_wallet_addr_comment(d[2])
if len(d) == 3: check_addr_comment(d[2])
ret.append(tuple(d))
@ -501,3 +510,84 @@ def sign_transaction(c,tx_hex,sig_data,keys=None):
# sys.exit(3)
return sig_tx
def get_keys_for_mmgen_addrs(mmgen_addrs,infiles,opts):
seed_ids = list(set([i['account'][:8] for i in mmgen_addrs]))
seed_ids_save = seed_ids[0:]
keys = []
while seed_ids:
infile = False
if infiles:
infile = infiles.pop()
seed = get_seed(infile,opts)
elif "from_brain" in opts or "from_mnemonic" in opts or "from_seed" in opts:
msg("Need data for seed ID %s" % seed_ids[0])
seed = get_seed_retry("",opts)
else:
b,p,v = ("A seed","","is") if len(seed_ids) == 1 else ("Seed","s","are")
msg("ERROR: %s source%s %s required for the following seed ID%s: %s" %
(b,p,v,p," ".join(seed_ids)))
sys.exit(2)
seed_id = make_chksum_8(seed)
if seed_id in seed_ids:
seed_ids.remove(seed_id)
seed_id_addrs = [
int(i['account'].split()[0][9:]) for i in mmgen_addrs
if i['account'][:8] == seed_id]
from mmgen.addr import generate_keys
keys += [i['wif'] for i in generate_keys(seed, seed_id_addrs)]
else:
if seed_id in seed_ids_save:
msg_r("Ignoring duplicate seed source")
if infile: msg(" '%s'" % infile)
else: msg(" for ID %s" % seed_id)
else:
msg("Seed source produced an invalid seed ID (%s)" % seed_id)
if infile:
msg("Invalid input file: %s" % infile)
sys.exit(2)
return keys
def sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys,opts):
try:
sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
except:
from mmgen.rpc import exceptions
msg("Using keys in wallet.dat as per user request")
prompt = "Enter passphrase for bitcoind wallet: "
while True:
passwd = get_bitcoind_passphrase(prompt,opts)
try:
c.walletpassphrase(passwd, 9999)
except exceptions.WalletPassphraseIncorrect:
msg("Passphrase incorrect")
else:
msg("Passphrase OK"); break
sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
msg("Locking wallet")
try:
c.walletlock()
except:
msg("Failed to lock wallet")
return sig_tx
def missing_keys_errormsg(other_addrs):
msg("""
A key file (option '-f') or wallet.dat (option '-w') must be supplied
for the following non-mmgen address%s: %s""" %
("" if len(other_addrs) == 1 else "es",
" ".join([i['address'] for i in other_addrs])
))

View file

@ -26,70 +26,95 @@ from mmgen.bitcoin import b58decode_pad
def msg(s): sys.stderr.write(s + "\n")
def msg_r(s): sys.stderr.write(s)
def bail(): sys.exit(9)
def my_getpass(prompt):
from getpass import getpass
# getpass prompts to stderr, so no trickery required as with raw_input()
try: pw = getpass(prompt)
except:
msg("\nUser interrupt")
sys.exit(1)
return pw
term = False
def get_char(prompt=""):
def get_keypress_unix(prompt="",immed_chars=""):
msg_r(prompt)
timeout = float(0.3)
global term
if not term:
try:
import tty, termios
term = "unix"
except:
try:
import msvcrt
term = "mswin"
except:
msg("Unable to set terminal mode")
sys.exit(2)
fd = sys.stdin.fileno()
old = termios.tcgetattr(fd)
tty.setcbreak(fd)
try:
if term == "unix":
import tty, termios
fd = sys.stdin.fileno()
old = termios.tcgetattr(fd)
tty.setcbreak(fd)
while True:
select([sys.stdin], [], [], False)
ch = sys.stdin.read(1)
elif term == "mswin":
import msvcrt
ch = msvcrt.getch()
if ord(ch) == 3:
raise KeyboardInterrupt
if immed_chars == "ALL" or ch in immed_chars:
return ch
if immed_chars == "ALL_EXCEPT_ENTER" and not ch in "\n\r":
return ch
second_key = select([sys.stdin], [], [], timeout)[0]
if second_key: continue
else: return ch
except:
msg("\nUser interrupt")
print "\nUser interrupt"
sys.exit(1)
finally:
if term == "unix":
termios.tcsetattr(fd, termios.TCSADRAIN, old)
return ch
termios.tcsetattr(fd, termios.TCSADRAIN, old)
def my_raw_input(prompt):
def get_keypress_mswin(prompt="",immed_chars=""):
msg_r(prompt)
try: reply = raw_input()
timeout = float(0.5)
try:
while True:
if msvcrt.kbhit():
ch = msvcrt.getch()
if ord(ch) == 3: raise KeyboardInterrupt
if immed_chars == "ALL" or ch in immed_chars:
return ch
if immed_chars == "ALL_EXCEPT_ENTER" and not ch in "\n\r":
return ch
hit_time = time.time()
while True:
if msvcrt.kbhit(): break
if float(time.time() - hit_time) > timeout:
return ch
except:
msg("\nUser interrupt")
sys.exit(1)
try:
import tty, termios
from select import select
get_char = get_keypress_unix
except:
try:
import msvcrt, time
get_char = get_keypress_mswin
except:
if not sys.platform.startswith("linux") \
and not sys.platform.startswith("win"):
msg("Unsupported platform: %s" % sys.platform)
msg("This program currently runs only on Linux and Windows")
else:
msg("Unable to set terminal mode")
sys.exit(2)
def my_raw_input(prompt,echo=True):
msg_r(prompt)
reply = ""
while True:
ch = get_char(immed_chars="ALL_EXCEPT_ENTER")
if echo: msg_r(ch)
if ch in "\n\r":
if not echo: msg("")
break
reply += ch
return reply
@ -101,6 +126,7 @@ def _get_hash_params(hash_preset):
msg("%s: invalid 'hash_preset' value" % hash_preset)
sys.exit(3)
def show_hash_presets():
fs = " {:<7} {:<6} {:<3} {}"
msg("Available parameters for scrypt.hash():")
@ -111,87 +137,6 @@ def show_hash_presets():
sys.exit(0)
def check_opts(opts,keys):
for key in keys:
if key not in opts: continue
val = opts[key]
what = "parameter for '--%s' option" % key.replace("_","-")
if key == 'outdir':
what = "output directory"
import re, os, stat
d = re.sub(r'/*$','', val)
opts[key] = d
try: mode = os.stat(d).st_mode
except:
msg("Unable to stat requested %s '%s'" % (what,d))
sys.exit(1)
if not stat.S_ISDIR(mode):
msg("Requested %s '%s' is not a directory" % (what,d))
sys.exit(1)
if not os.access(d, os.W_OK|os.X_OK):
msg("Requested %s '%s' is unwritable by you" % (what,d))
sys.exit(1)
elif key == 'label':
label = val.strip()
opts[key] = label
if len(label) > 32:
msg("Label must be 32 characters or less")
sys.exit(1)
from string import ascii_letters, digits
label_chrs = list(ascii_letters + digits) + [".", "_", " "]
for ch in list(label):
if ch not in label_chrs:
msg("'%s': illegal character in label" % ch)
sys.exit(1)
elif key == 'from_brain':
try:
l,p = val.split(",")
except:
msg("'%s': invalid %s" % (val,what))
sys.exit(1)
try:
int(l)
except:
msg("'%s': invalid 'l' %s (not an integer)" % (l,what))
sys.exit(1)
if int(l) not in seed_lens:
msg("'%s': invalid 'l' %s. Options: %s" %
(l, what, ", ".join([str(i) for i in seed_lens])))
sys.exit(1)
if p not in hash_presets:
hps = ", ".join([i for i in sorted(hash_presets.keys())])
msg("'%s': invalid 'p' %s. Options: %s" % (p, what, hps))
sys.exit(1)
elif key == 'seed_len':
if val not in seed_lens:
msg("'%s': invalid %s. Options: %s"
% (val,what,", ".join([str(i) for i in seed_lens])))
sys.exit(2)
elif key == 'hash_preset':
if val not in hash_presets:
msg("'%s': invalid %s. Options: %s"
% (val,what,", ".join(sorted(hash_presets.keys()))))
sys.exit(2)
elif key == 'usr_randlen':
if val > max_randlen or val < min_randlen:
msg("'%s': invalid %s (must be >= %s and <= %s)"
% (val,what,min_randlen,max_randlen))
sys.exit(2)
cmessages = {
'null': "",
'unencrypted_secret_keys': """
@ -215,6 +160,7 @@ future, you must continue using these same parameters
"""
}
def confirm_or_exit(message, question, expect="YES"):
msg("")
@ -236,38 +182,34 @@ def confirm_or_exit(message, question, expect="YES"):
msg("")
def user_confirm(prompt,default_yes=False):
def user_confirm(prompt,default_yes=False,verbose=False):
q = "(Y/n)" if default_yes else "(y/N)"
while True:
reply = get_char("%s %s: " % (prompt, q)).strip()
msg("")
reply = get_char("%s %s: " % (prompt, q)).strip("\n\r")
if not reply:
return True if default_yes else False
elif reply in 'yY': return True
elif reply in 'nN': return False
else: msg("Invalid reply")
def set_if_unset_and_typeconvert(opts,item):
for opt,var,dtype in item:
if dtype == 'int': f,s = int,"an integer"
elif dtype == 'str': f,s = str,"a string"
if opt in opts:
val = opts[opt]
what = "invalid parameter for '--%s' option" % opt.replace("_","-")
try:
f(val)
except:
msg("'%s': %s (not %s)" % (val,what,s))
sys.exit(1)
opts[opt] = f(val)
if default_yes: msg(""); return True
else: msg(""); return False
elif reply in 'yY': msg(""); return True
elif reply in 'nN': msg(""); return False
else:
opts[opt] = var
if verbose: msg("\nInvalid reply")
else: msg_r("\r")
def prompt_and_get_char(prompt,chars,enter_ok=False,verbose=False):
while True:
reply = get_char("%s: " % prompt).strip("\n\r")
if reply in chars or (enter_ok and not reply):
msg("")
return reply
if verbose: msg("\nInvalid reply")
else: msg_r("\r")
def make_chksum_8(s):
@ -279,11 +221,6 @@ def make_chksum_6(s):
return sha256(s).hexdigest()[:6]
def _get_from_brain_opt_params(opts):
l,p = opts['from_brain'].split(",")
return(int(l),p)
def check_infile(f):
import os, stat
@ -377,6 +314,11 @@ def _scrypt_hash_passphrase(passwd, salt, hash_preset, buflen=32):
return scrypt.hash(passwd, salt, 2**N, r, p, buflen=buflen)
def _get_from_brain_opt_params(opts):
l,p = opts['from_brain'].split(",")
return(int(l),p)
def _get_seed_from_brain_passphrase(words,opts):
bp = " ".join(words)
if debug: print "Sanitized brain passphrase: %s" % bp
@ -541,6 +483,7 @@ def make_timestr():
def secs_to_hms(secs):
return "{:02d}:{:02d}:{:02d}".format(secs/3600, (secs/60) % 60, secs % 60)
def write_wallet_to_file(seed, passwd, key_id, salt, enc_seed, opts):
seed_id = make_chksum_8(seed)
@ -701,10 +644,8 @@ def get_data_from_wallet(infile,opts,silent=False):
def _get_words_from_user(prompt, opts):
# split() also strips
if 'echo_passphrase' in opts:
words = my_raw_input(prompt).split()
else:
words = my_getpass(prompt).split()
words = my_raw_input(prompt,
echo=True if 'echo_passphrase' in opts else False).split()
if debug: print "Sanitized input: [%s]" % " ".join(words)
return words
@ -758,6 +699,7 @@ def _get_seed_from_seed_data(words):
msg("Invalid checksum for {} seed".format(proj_name))
return False
passwd_file_used = False
def mark_passwd_file_as_used(opts):
@ -781,10 +723,8 @@ def get_bitcoind_passphrase(prompt,opts):
mark_passwd_file_as_used(opts)
return get_data_from_file(opts['passwd_file'],"passphrase").strip("\r\n")
else:
if 'echo_passphrase' in opts:
return my_raw_input(prompt)
else:
return my_getpass(prompt)
return my_raw_input(prompt,
echo=True if 'echo_passphrase' in opts else False)
def get_seed_from_wallet(
@ -925,47 +865,45 @@ def remove_blanks_comments(lines):
return ret
def do_pager(text,endmsg=""):
import os
if sys.platform.startswith("linux"):
if 'PAGER' in os.environ and os.environ['PAGER']:
try:
p = os.popen(os.environ['PAGER'], 'w')
except:
print text
else:
try:
p.write(text)
p.close()
except:
p.close()
msg_r("\r")
else:
print text
elif sys.platform.startswith("win"):
try:
import msvcrt
except:
print text
else:
try:
from subprocess import Popen, PIPE, STDOUT
p = Popen(["more","/C"], stdin=PIPE, shell=True)
if endmsg:
p.stdin.write("%s\n%s\n\n" % (text,endmsg))
else:
p.stdin.write(text)
except:
msg("\nUser exit")
from time import sleep
# Flush stdin
while msvcrt.kbhit(): msvcrt.getch()
sleep(1)
while msvcrt.kbhit(): msvcrt.getch()
msg("")
else:
print text
def do_pager(text):
pagers = ["less","more"]
shell = False
from os import environ
# Hack for MS Windows command line (i.e. non CygWin) environment
# When 'shell' is true, Windows aborts the calling program if executable
# not found.
# When 'shell' is false, an exception is raised, invoking the fallback
# 'print' instead of the pager.
# We risk assuming that "more" will always be available on a stock
# Windows installation.
if sys.platform.startswith("win") and 'HOME' not in environ:
shell = True
pagers = ["more"]
if 'PAGER' in environ and environ['PAGER'] != pagers[0]:
pagers = [environ['PAGER']] + pagers
for pager in pagers:
end = "" if pager == "less" else "\n(end of text)\n"
try:
from subprocess import Popen, PIPE, STDOUT
p = Popen([pager], stdin=PIPE, shell=shell)
except: pass
else:
try:
p.communicate(text+end+"\n")
except:
# Has no effect. Why?
if pager != "less":
msg("\n(Interrupted by user)\n")
finally:
msg_r("\r")
break
else: print text+end
if __name__ == "__main__":

View file

@ -20,7 +20,7 @@ walletgen.py: Routines used for seed generation and wallet creation
"""
import sys
from mmgen.utils import msg, msg_r, get_char
from mmgen.utils import msg, msg_r, get_char, prompt_and_get_char
from binascii import hexlify
def get_random_data_from_user(opts):
@ -48,17 +48,19 @@ displayed on the screen.
user_rand_data,intervals = "",[]
for i in range(ulen):
user_rand_data += get_char()
user_rand_data += get_char(immed_chars="ALL")
msg_r("\r" + prompt % (ulen - i - 1))
now = time.time()
intervals.append(now - saved_time)
saved_time = now
if 'quiet' in opts:
msg_r("\r")
else:
msg_r("\rThank you. That's enough." + " "*15 + "\n\n")
time.sleep(0.5)
get_char("User random data successfully acquired. Press ENTER to continue: ")
prompt = "User random data successfully acquired. Press ENTER to continue"
prompt_and_get_char(prompt,"",enter_ok=True)
return user_rand_data, ["{:.22f}".format(i) for i in intervals]