Improved handling of user input; code cleanups
This commit is contained in:
parent
dcab2602b6
commit
9a64bf42e2
18 changed files with 617 additions and 502 deletions
287
README.md
287
README.md
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
mmgen-addrgen
|
||||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
121
mmgen-txsign
121
mmgen-txsign
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
117
mmgen/Opts.py
117
mmgen/Opts.py
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
140
mmgen/tx.py
140
mmgen/tx.py
|
|
@ -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])
|
||||
))
|
||||
|
|
|
|||
344
mmgen/utils.py
344
mmgen/utils.py
|
|
@ -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__":
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue