diff --git a/README.md b/README.md
index b57a0429..0c02f32e 100644
--- a/README.md
+++ b/README.md
@@ -67,6 +67,8 @@ mnemonic or seed or a lost seed from the wallet or mnemonic.
> #### See [Getting Started with MMGen][3]
+> #### [MMGen command help][6]
+
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
[**Forum**][4] |
@@ -78,3 +80,4 @@ Donate: 15TLdmi5NYLdqmtCqczUs5pBPkJDXRs83w
[3]: https://github.com/mmgen/mmgen/wiki/Getting-Started-with-MMGen
[4]: https://bitcointalk.org/index.php?topic=567069.0
[5]: https://github.com/mmgen/mmgen/wiki/MMGen-Signing-Key
+[6]: https://github.com/mmgen/mmgen/wiki/cmdhelp/Command-Help
diff --git a/mmgen-addrgen b/mmgen-addrgen
index 82409b60..23ac14e0 100755
--- a/mmgen-addrgen
+++ b/mmgen-addrgen
@@ -38,36 +38,40 @@ help_data = {
mnemonic, seed or password""".format(gen_what,g=g),
'usage':"[opts] [infile]
",
'options': """
--h, --help Print this help message{}
--d, --outdir= d Specify an alternate directory 'd' for output
--e, --echo-passphrase Echo passphrase or mnemonic to screen upon entry
--H, --show-hash-presets Show information on available hash presets
--K, --no-keyconv Use internal libraries for address generation
- instead of 'keyconv'
--l, --seed-len= N Length of seed. Options: {seed_lens}
- (default: {g.seed_len})
--p, --hash-preset= p Use scrypt.hash() parameters from preset 'p' when
- hashing password (default: '{g.hash_preset}')
--P, --passwd-file= f Get passphrase from file 'f'
--q, --quiet Suppress warnings; overwrite files without
- prompting
--S, --stdout Print {what} to stdout
--v, --verbose Produce more verbose output{}
+-h, --help Print this help message{}
+-d, --outdir= d Specify an alternate directory 'd' for output
+-e, --echo-passphrase Echo passphrase or mnemonic to screen upon entry{}
+-H, --show-hash-presets Show information on available hash presets
+-K, --no-keyconv Use internal libraries for address generation
+ instead of 'keyconv'
+-l, --seed-len= N Length of seed. Options: {seed_lens}
+ (default: {g.seed_len})
+-p, --hash-preset= p Use scrypt.hash() parameters from preset 'p' when
+ hashing password (default: '{g.hash_preset}')
+-P, --passwd-file= f Get passphrase from file 'f'
+-q, --quiet Suppress warnings; overwrite files without
+ prompting
+-S, --stdout Print {what} to stdout
+-v, --verbose Produce more verbose output{}
--b, --from-brain= l,p Generate {what} from a user-created password,
- i.e. a "brainwallet", using seed length 'l' and
- hash preset 'p' (comma-separated)
--g, --from-incog Generate {what} from an incognito wallet
--X, --from-incog-hex Generate {what} from incognito hexadecimal wallet
--G, --from-incog-hidden= f,o,l Generate {what} from incognito data in file
- 'f' at offset 'o', with seed length of 'l'
--m, --from-mnemonic Generate {what} from an electrum-like mnemonic
--s, --from-seed Generate {what} from a seed in .{g.seed_ext} format
+-b, --from-brain= l,p Generate {what} from a user-created password,
+ i.e. a "brainwallet", using seed length 'l' and
+ hash preset 'p' (comma-separated)
+-g, --from-incog Generate {what} from an incognito wallet
+-X, --from-incog-hex Generate {what} from incognito hexadecimal wallet
+-G, --from-incog-hidden=f,o,l Generate {what} from incognito data in file
+ 'f' at offset 'o', with seed length of 'l'
+-m, --from-mnemonic Generate {what} from an electrum-like mnemonic
+-s, --from-seed Generate {what} from a seed in .{g.seed_ext} format
""".format(
*(
- ("\n-A, --no-addresses Print only secret keys, no addresses",
- "\n-x, --b16 Print secret keys in hexadecimal too")
- if gen_what == "keys" else ("","")),
+ (
+"\n-A, --no-addresses Print only secret keys, no addresses",
+"\n-f, --flat-list Produce a flat list of keys suitable for use with" +
+"\n 'mmgen-txsign'",
+"\n-x, --b16 Print secret keys in hexadecimal too"
+ )
+ if gen_what == "keys" else ("","","")),
seed_lens=", ".join([str(i) for i in g.seed_lens]),
what=gen_what, g=g
),
@@ -160,9 +164,9 @@ if 'stdout' in opts:
if gen_what == "keys" and not g.quiet:
confirm = True
else: confirm = False
- write_to_stdout(addr_data_str,"secret keys",confirm)
+ write_to_stdout(addr_data_str,gen_what,confirm)
elif not sys.stdout.isatty():
- write_to_stdout(addr_data_str,"secret keys",confirm=False)
+ write_to_stdout(addr_data_str,gen_what,confirm=False)
else:
write_addr_data_to_file(seed, addr_data_str, addr_idxs, opts)
diff --git a/mmgen-addrimport b/mmgen-addrimport
index 1757315d..10b0ac23 100755
--- a/mmgen-addrimport
+++ b/mmgen-addrimport
@@ -28,8 +28,8 @@ from mmgen.tx import connect_to_bitcoind,parse_addrs_file
help_data = {
'prog_name': sys.argv[0].split("/")[-1],
- 'desc': """Import addresses (both mmgen and non-mmgen) into a bitcoind
- watching wallet""",
+ 'desc': """Import addresses (both {pnm} and non-{pnm}) into a bitcoind
+ watching wallet""".format(pnm=g.proj_name),
'usage':"[opts] [mmgen address file]",
'options': """
-h, --help Print this help message
diff --git a/mmgen-passchg b/mmgen-passchg
index 2b64cbf5..7165532e 100755
--- a/mmgen-passchg
+++ b/mmgen-passchg
@@ -30,18 +30,20 @@ help_data = {
deterministic wallet""".format(g.proj_name),
'usage': "[opts] [filename]",
'options': """
--h, --help Print this help message
--d, --outdir= d Specify an alternate directory 'd' for output
--H, --show-hash-presets Show information on available hash presets
--k, --keep-old-passphrase Keep old passphrase (use when changing hash
- strength or label only)
--L, --label= l Change the wallet's label to 'l'
--p, --hash-preset= p Change scrypt.hash() parameters to preset 'p'
- (default: '{g.hash_preset}')
--P, --passwd-file= f Get new passphrase from file 'f'
--q, --quiet Suppress warnings; overwrite files without
- prompting
--v, --verbose Produce more verbose output
+-h, --help Print this help message
+-d, --outdir= d Specify an alternate directory 'd' for output
+-H, --show-hash-presets Show information on available hash presets
+-k, --keep-old-passphrase Keep old passphrase (use when changing hash
+ strength or label only)
+-L, --label= l Change the wallet's label to 'l'
+-p, --hash-preset= p Change scrypt.hash() parameters to preset 'p'
+ (default: '{g.hash_preset}')
+-P, --passwd-file= f Get new passphrase from file 'f'
+-r, --usr-randchars= n Get 'n' characters of additional randomness from
+ user (min={g.min_urandchars}, max={g.max_urandchars})
+-q, --quiet Suppress warnings; overwrite files without
+ prompting
+-v, --verbose Produce more verbose output
""".format(g=g),
'notes': """
@@ -110,9 +112,8 @@ if 'preset' in changed or 'passwd' in changed: # Update key ID, salt
qmsg("Will update salt and key ID")
from hashlib import sha256
- from Crypto import Random
- salt = sha256(salt + Random.new().read(128)).digest()[:g.salt_len]
+ salt = sha256(salt + get_random(128,opts)).digest()[:g.salt_len]
key = make_key(passwd, salt, opts['hash_preset'])
new_key_id = make_chksum_8(key)
qmsg("Key ID changed: %s -> %s" % (key_id,new_key_id))
diff --git a/mmgen-tool b/mmgen-tool
index da66e9b8..f902cdbe 100755
--- a/mmgen-tool
+++ b/mmgen-tool
@@ -25,7 +25,7 @@ from hashlib import sha256
from mmgen.Opts import *
import mmgen.config as g
from mmgen.util import pretty_hexdump
-from mmgen.tool import *
+import mmgen.tool as tool
prog_name = sys.argv[0].split("/")[-1]
help_data = {
@@ -33,16 +33,18 @@ help_data = {
'desc': "Perform various BTC-related operations",
'usage': "[opts] ",
'options': """
--h, --help Print this help message
--q, --quiet Produce quieter output
--v, --verbose Produce more verbose output
-""",
+-h, --help Print this help message
+-q, --quiet Produce quieter output
+-r, --usr-randchars=n Get 'n' characters of additional randomness from
+ user (min={g.min_urandchars}, max={g.max_urandchars})
+-v, --verbose Produce more verbose output
+""".format(g=g),
'notes': """
COMMANDS:{}
Type '{} --help for usage information on a particular
command
-""".format(command_help,prog_name)
+""".format(tool.command_help,prog_name)
}
opts,cmd_args = parse_opts(sys.argv,help_data)
@@ -53,17 +55,20 @@ if 'verbose' in opts: g.verbose = True
if len(cmd_args) < 1:
usage(help_data)
sys.exit(1)
-else: command = cmd_args.pop(0)
-if command not in commands.keys():
+command = cmd_args.pop(0)
+
+if command not in tool.commands.keys():
msg("'%s': No such command" % command)
sys.exit(1)
if cmd_args and cmd_args[0] == '--help':
- tool_usage(prog_name, command)
+ tool.tool_usage(prog_name, command)
sys.exit(0)
-args = process_args(prog_name, command, cmd_args)
+args = tool.process_args(prog_name, command, cmd_args)
+
+tool.opts = opts
#print command + "(" + ", ".join(args) + ")"
-eval(command + "(" + ", ".join(args) + ")")
+eval("tool." + command + "(" + ", ".join(args) + ")")
diff --git a/mmgen-txcreate b/mmgen-txcreate
index 6d5f7e47..e81bd0c7 100755
--- a/mmgen-txcreate
+++ b/mmgen-txcreate
@@ -36,13 +36,13 @@ help_data = {
'desc': "Create a BTC transaction with outputs to specified addresses",
'usage': "[opts] ... [change addr] [addr file] ...",
'options': """
--h, --help Print this help message
--d, --outdir= d Specify an alternate directory 'd' for output
--e, --echo-passphrase Print passphrase to screen when typing it
--f, --tx-fee= f Transaction fee (default: {g.tx_fee} BTC)
--i, --info Display unspent outputs and exit
--q, --quiet Suppress warnings; overwrite files without
- prompting
+-h, --help Print this help message
+-d, --outdir= d Specify an alternate directory 'd' for output
+-e, --echo-passphrase Print passphrase to screen when typing it
+-f, --tx-fee= f Transaction fee (default: {g.tx_fee} BTC)
+-i, --info Display unspent outputs and exit
+-q, --quiet Suppress warnings; overwrite files without
+ prompting
""".format(g=g),
'notes': """
@@ -52,12 +52,12 @@ via an interactive menu.
Ages of transactions are approximate based on an average block creation
interval of {g.mins_per_block} minutes.
-Addresses on the command line can be Bitcoin addresses or MMGen addresses
+Addresses on the command line can be Bitcoin addresses or {pnm} addresses
of the form :.
To send all inputs (minus TX fee) to a single output, specify one address
with no amount on the command line.
-""".format(g=g)
+""".format(g=g,pnm=g.proj_name)
}
opts,cmd_args = parse_opts(sys.argv,help_data)
diff --git a/mmgen-txsend b/mmgen-txsend
index 1f159d50..75c0018e 100755
--- a/mmgen-txsend
+++ b/mmgen-txsend
@@ -34,9 +34,9 @@ help_data = {
'desc': "Send a Bitcoin transaction signed by mmgen-txsign",
'usage': "[opts] ",
'options': """
--h, --help Print this help message
--d, --outdir= d Specify an alternate directory 'd' for output
--q, --quiet Suppress warnings; overwrite files without prompting
+-h, --help Print this help message
+-d, --outdir= d Specify an alternate directory 'd' for output
+-q, --quiet Suppress warnings; overwrite files without prompting
"""
}
diff --git a/mmgen-txsign b/mmgen-txsign
index 98025c21..7bd71485 100755
--- a/mmgen-txsign
+++ b/mmgen-txsign
@@ -30,14 +30,19 @@ from mmgen.util import msg,qmsg
help_data = {
'prog_name': sys.argv[0].split("/")[-1],
'desc': "Sign Bitcoin transactions generated by mmgen-txcreate",
- 'usage': "[opts] ,.. [mmgen wallet/seed/words/brainwallet file]...",
+ 'usage': "[opts] .. [mmgen wallet/seed/words/brainwallet file] .. [addrfile] ..",
'options': """
-h, --help Print this help message
-d, --outdir= d Specify an alternate directory 'd' for output
-e, --echo-passphrase Print passphrase to screen when typing it
-i, --info Display information about the transaction and exit
-I, --tx-id Display transaction ID and exit
--k, --keys-from-file= k Provide additional key data from file 'k'
+-k, --keys-from-file= f Provide additional keys for non-{pnm} addresses
+-K, --all-keys-from-file=f Like '-k', only use the keyfile as key source
+ for ALL inputs, including {pnm} ones. Can be used
+ for online signing without an {pnm} seed source.
+ {pnm}-to-BTC mappings can optionally be verified
+ using address file(s) listed on the command line
-P, --passwd-file= f Get passphrase from file 'f'
-q, --quiet Suppress warnings; overwrite files without
prompting
@@ -49,37 +54,37 @@ help_data = {
-g, --from-incog Generate keys from an incognito wallet
-X, --from-incog-hex Generate keys from an incognito hexadecimal wallet
-G, --from-incog-hidden= f,o,l Generate keys from incognito data in file
- 'f' at offset 'o', with seed length of 'l'
+ 'f' at offset 'o', with seed length of 'l'
-m, --from-mnemonic Generate keys from an electrum-like mnemonic
-s, --from-seed Generate keys from a seed in .{g.seed_ext} format
-""".format(g=g),
+""".format(g=g,pnm=g.proj_name),
'notes': """
-Transactions with either mmgen or non-mmgen input addresses may be signed.
-For non-mmgen inputs, the bitcoind wallet.dat is used as the key source.
-For mmgen inputs, key data is generated from your seed as with the
+Transactions with either {pnm} or non-{pnm} input addresses may be signed.
+For non-{pnm} inputs, the bitcoind wallet.dat is used as the key source.
+For {pnm} inputs, key data is generated from your seed as with the
mmgen-addrgen and mmgen-keygen utilities.
Data for the --from- options will be taken from a file if a second
file is specified on the command line. Otherwise, the user will be
prompted to enter the data.
-In cases of transactions with mixed mmgen and non-mmgen inputs, non-mmgen
+In cases of transactions with mixed {pnm} and non-{pnm} inputs, non-{pnm}
keys must be supplied in a separate file (WIF format, one key per line)
using the '--keys-from-file' option. Alternatively, one may get keys from
a running bitcoind using the '--force-wallet-dat' option. First import the
-required mmgen keys using 'bitcoind importprivkey'.
+required {pnm} keys using 'bitcoind importprivkey'.
-For transaction outputs that are MMGen addresses, MMGen-to-Bitcoin address
+For transaction outputs that are {pnm} addresses, {pnm}-to-Bitcoin address
mappings are verified. Therefore, seed material for these addresses must
-be supplied on the command line.
+be supplied on the command line (but see '--all-keys-from-file').
Seed data supplied in files must have the following extensions:
wallet: '.{g.wallet_ext}'
seed: '.{g.seed_ext}'
mnemonic: '.{g.mn_ext}'
brainwallet: '.{g.brain_ext}'
-""".format(g=g)
+""".format(g=g,pnm=g.proj_name)
}
opts,infiles = parse_opts(sys.argv,help_data)
@@ -87,6 +92,9 @@ opts,infiles = parse_opts(sys.argv,help_data)
if "quiet" in opts: g.quiet = True
if 'from_incog_hex' in opts or 'from_incog_hidden' in opts:
opts['from_incog'] = True
+if 'all_keys_from_file' in opts:
+ opts['keys_from_file'] = opts['all_keys_from_file']
+ opts['skip_key_preverify'] = True
if not infiles: usage(help_data)
for i in infiles: check_infile(i)
@@ -94,13 +102,16 @@ for i in infiles: check_infile(i)
c = connect_to_bitcoind()
saved_seeds = {}
-tx_files = [i for i in set(infiles) if get_extension(i) == g.rawtx_ext]
-infiles = list(set(infiles) - set(tx_files))
+tx_files = [i for i in set(infiles) if get_extension(i) == g.rawtx_ext]
+addrfiles = [a for a in set(infiles) if get_extension(a) == g.addrfile_ext]
+infiles = list(set(infiles) - set(tx_files) - set(addrfiles))
if not "info" in opts: do_license_msg(immed=True)
-keys_from_file = get_lines_from_file(opts['keys_from_file'],"key data",
- remove_comments=True) if 'keys_from_file' in opts else []
+if 'keys_from_file' in opts:
+ keys_from_file = get_lines_from_file(opts['keys_from_file'],"key data",
+ remove_comments=True)
+else: keys_from_file = []
for tx_file in tx_files:
m = "" if 'tx_id' in opts else "transaction data"
@@ -118,21 +129,30 @@ for tx_file in tx_files:
sys.exit(0)
# Are inputs mmgen addresses?
- mmgen_addrs = [i for i in inputs_data if parse_mmgen_label(i['account'])[0]]
- other_addrs = [i for i in inputs_data if not parse_mmgen_label(i['account'])[0]]
+ mmgen_inputs = [i for i in inputs_data if parse_mmgen_label(i['account'])[0]]
+ other_inputs = [i for i in inputs_data if not parse_mmgen_label(i['account'])[0]]
+
+ if 'all_keys_from_file' in opts: other_inputs = inputs_data
keys = keys_from_file
- if other_addrs and not keys and not 'use_wallet_dat' in opts:
- missing_keys_errormsg(other_addrs)
+ if other_inputs and not keys and not 'use_wallet_dat' in opts:
+ missing_keys_errormsg(other_inputs)
sys.exit(2)
- if other_addrs and keys and not 'skip_key_preverify' in opts:
- a = [i['address'] for i in other_addrs]
+ if other_inputs and keys and not 'skip_key_preverify' in opts:
+ a = [i['address'] for i in other_inputs]
preverify_keys(a, keys)
opts['skip_key_preverify'] = True
- check_mmgen_to_btc_addr_mappings(inputs_data,b2m_map,infiles,saved_seeds,opts)
+ if 'all_keys_from_file' in opts:
+ if addrfiles:
+ check_mmgen_to_btc_addr_mappings_addrfile(mmgen_inputs,b2m_map,addrfiles)
+ else:
+ confirm_or_exit(txmsg['skip_mapping_checks_warning'],"continue")
+ else:
+ check_mmgen_to_btc_addr_mappings(
+ mmgen_inputs,b2m_map,infiles,saved_seeds,opts)
if len(tx_files) > 1:
msg("\nTransaction %s/%s:" % (tx_files.index(tx_file)+1,len(tx_files)))
@@ -147,15 +167,15 @@ for tx_file in tx_files:
{"txid":i['txid'],"vout":i['vout'],"scriptPubKey":i['scriptPubKey']}
for i in inputs_data]
- if mmgen_addrs:
- ml = [i['account'].split()[0] for i in mmgen_addrs]
+ if mmgen_inputs and not 'all_keys_from_file' in opts:
+ ml = [i['account'].split()[0] for i in mmgen_inputs]
keys += get_keys_for_mmgen_addrs(ml,infiles,saved_seeds,opts)
if 'use_wallet_dat' in opts:
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:
+ elif other_inputs:
if keys:
sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
else:
diff --git a/mmgen-walletchk b/mmgen-walletchk
index 815bbb19..ac938d00 100755
--- a/mmgen-walletchk
+++ b/mmgen-walletchk
@@ -37,14 +37,24 @@ help_data = {
-e, --echo-passphrase Print passphrase to screen when typing it
-P, --passwd-file= f Get passphrase from file 'f'
-q, --quiet Suppress warnings; overwrite files without prompting
+-r, --usr-randchars= n Get 'n' characters of additional randomness from
+ user (min={g.min_urandchars}, max={g.max_urandchars})
-S, --stdout Print seed or mnemonic data to standard output
-v, --verbose Produce more verbose output
-g, --export-incog Export wallet to incognito format
-X, --export-incog-hex Export wallet to incognito hexadecimal format
--G, --export-incog-hidden= f,o Hide incognito data in existing file 'f'
- at offset 'o' (comma-separated)
+-G, --export-incog-hidden=f,o Hide incognito data in existing file 'f'
+ at offset 'o' (comma-separated)
-m, --export-mnemonic Export the wallet's mnemonic to file
-s, --export-seed Export the wallet's seed to file
+""".format(g=g),
+ 'notes': """
+
+Since good randomness is particularly important for incognito wallets,
+the '--usr-randchars' option is turned on by default to gather additional
+entropy from the user when one of the '--export-incog*' options is
+selected. If you fully trust your OS's random number generator and wish
+to disable this option, then specify '-r0' on the command line.
"""
}
@@ -64,6 +74,7 @@ if 'export_mnemonic' in opts:
elif 'export_seed' in opts:
qmsg("Exporting seed data to file by user request")
elif 'export_incog' in opts:
+ if opts['usr_randchars'] == -1: opts['usr_randchars'] = g.usr_randchars_dfl
qmsg("Exporting wallet to incognito format by user request")
incog_enc,seed_id,key_id,iv_id,preset = \
wallet_to_incog_data(cmd_args[0],opts)
diff --git a/mmgen-walletgen b/mmgen-walletgen
index 38380ce0..91d0a15f 100755
--- a/mmgen-walletgen
+++ b/mmgen-walletgen
@@ -47,8 +47,8 @@ help_data = {
-P, --passwd-file= f Get passphrase from file 'f'
-q, --quiet Produce quieter output; overwrite files without
prompting
--u, --usr-randlen= n Get 'n' characters of randomness from the user
- (default: {g.usr_randlen})
+-r, --usr-randchars= n Get 'n' characters of additional randomness from
+ user (min={g.min_urandchars}, max={g.max_urandchars})
-v, --verbose Produce more verbose output
-b, --from-brain= l,p Generate wallet from a user-created passphrase,
@@ -71,6 +71,11 @@ trailing space are ignored. This permits reading passphrase data from a
multi-line file with free spacing and indentation. This is particularly
convenient for long brainwallet passphrases, for example.
+Since good randomness is particularly important when generating wallets,
+the '--usr-randchars' option is turned on by default to gather additional
+entropy from the user. If you fully trust your OS's random number gener-
+ator and wish to disable this option, specify '-r0' on the command line.
+
BRAINWALLET NOTE:
As brainwallets require especially strong hashing to thwart dictionary
@@ -94,6 +99,7 @@ opts,cmd_args = parse_opts(sys.argv,help_data)
if 'quiet' in opts: g.quiet = True
if 'verbose' in opts: g.verbose = True
if 'show_hash_presets' in opts: show_hash_presets()
+if opts['usr_randchars'] == -1: opts['usr_randchars'] = g.usr_randchars_dfl
if g.debug: show_opts_and_cmd_args(opts,cmd_args)
@@ -116,31 +122,6 @@ else: usage(help_data)
do_license_msg()
-qmsg_r("Acquiring random data from your computer...")
-
-from time import sleep
-sleep(0.25)
-
-try:
- from Crypto import Random
- r = Random.new()
- os_rand_data = (r.read(256),r.read(128))
-except:
- msg("FAILED\nUnable to generate random numbers. Exiting")
- sys.exit(2)
-
-qmsg("OK")
-
-if g.debug: display_os_random_data(os_rand_data)
-
-usr_keys,key_timings = get_random_data_from_user(opts)
-qmsg("")
-
-if g.debug: display_user_random_data(usr_keys,key_timings)
-
-usr_rand_data = sha256(usr_keys).digest() + \
- sha256("".join(key_timings)).digest()
-
if 'from_brain' in opts and not g.quiet:
confirm_or_exit(cmessages['brain_warning'].format(
g.proj_name, *get_from_brain_opt_params(opts)),
@@ -155,18 +136,11 @@ for i in 'from_mnemonic','from_brain','from_seed','from_incog':
break
else:
# Truncate random data for smaller seed lengths
- seed = os_rand_data[0] + usr_rand_data
- seed = sha256(seed).digest()[:opts['seed_len']/8]
+ seed = sha256(get_random(128,opts)).digest()[:opts['seed_len']/8]
-salt = os_rand_data[1] + usr_rand_data
-salt = sha256(salt).digest()[:g.salt_len]
+salt = sha256(get_random(128,opts)).digest()[:g.salt_len]
-qmsg("""
-Now you must choose a passphrase to encrypt the wallet with. A key will be
-generated from your passphrase using a hash preset of '%s'. Please note that
-no strength checking of passphrases is performed. For an empty passphrase,
-just hit ENTER twice.
-""".strip() % opts['hash_preset'])
+qmsg(cmessages['choose_wallet_passphrase'] % opts['hash_preset'])
passwd = get_new_passphrase("{} wallet passphrase".format(g.proj_name), opts)
diff --git a/mmgen/Opts.py b/mmgen/Opts.py
index aaf74f8a..b940c2d4 100755
--- a/mmgen/Opts.py
+++ b/mmgen/Opts.py
@@ -83,27 +83,38 @@ def parse_opts(argv,help_data):
lines = help_data['options'].strip().split("\n")
import re
- pat = r"^-([a-zA-Z0-9]), --([a-zA-Z0-9-]{1,64})(=*) (.*)"
- opt_data = [m.groups() for m in [re.match(pat,l) for l in lines] if m]
+ pat = r"^-([a-zA-Z0-9]), --([a-zA-Z0-9-]{1,64})(=| )(.+)"
+ rep = r"-{0}, --{1}{w}{3}"
+ opt_data = [list(m.groups()) for m in [re.match(pat,l) for l in lines] if m]
+# for o in opt_data: print o
+# sys.exit()
- short_opts = "".join([d[0]+(":" if d[2] else "") for d in opt_data if d])
- long_opts = [d[1].replace("-","_")+d[2] for d in opt_data if d]
+ for d in opt_data:
+ if d[2] == " ": d[2] = ""
+ short_opts = "".join([d[0]+d[2].replace("=",":") for d in opt_data])
+ long_opts = [d[1].replace("-","_")+d[2] for d in opt_data]
help_data['options'] = "\n".join(
- ["-{0}, --{1}{w} {3}".format(w=" " if m.group(3) else "", *m.groups())
+ [rep.format(w=" ", *m.groups())
if m else k for m,k in [(re.match(pat,l),l) for l in lines]]
)
opts,infiles = process_opts(argv,help_data,short_opts,long_opts)
- if g.debug: print "processed user opts: %s" % opts
+ # check_opts() doesn't touch opts[]
+ if not check_opts(opts,long_opts): sys.exit(1)
- if not check_opts(opts,long_opts): sys.exit(1) # MMGen only!
+ # If unset, set these to the default values in mmgen.config:
+ for v in g.cl_override_vars:
+ if v in opts: typeconvert_override_var(opts,v)
+ else: opts[v] = eval("g."+v)
+
+ if g.debug: print "processed opts: %s" % opts
return opts,infiles
def show_opts_and_cmd_args(opts,cmd_args):
- print "Processed options: %s" % repr(opts)
- print "Cmd args: %s" % repr(cmd_args)
+ print "Processed options: %s" % repr(opts)
+ print "Cmd args: %s" % repr(cmd_args)
# Everything below here is MMGen-specific:
@@ -112,17 +123,13 @@ from mmgen.util import msg,check_infile
def check_opts(opts,long_opts):
- # These must be set to the default values in mmgen.config:
- for i in g.cl_override_vars:
- if i+"=" in long_opts:
- set_if_unset_and_typeconvert(opts,i)
-
for opt,val in opts.items():
what = "parameter for '--%s' option" % opt.replace("_","-")
# Check for file existence and readability
- if opt in ('keys_from_file','addrlist','passwd_file','keysforaddrs'):
+ if opt in ('keys_from_file','all_keys_from_file','addrlist',
+ 'passwd_file','keysforaddrs'):
check_infile(val) # exits on error
continue
@@ -233,10 +240,14 @@ def check_opts(opts,long_opts):
msg("'%s': invalid %s. Options: %s"
% (val,what,", ".join(sorted(g.hash_presets.keys()))))
return False
- elif opt == 'usr_randlen':
- if val > g.max_randlen or val < g.min_randlen:
- msg("'%s': invalid %s (must be >= %s and <= %s)"
- % (val,what,g.min_randlen,g.max_randlen))
+ elif opt == 'usr_randchars':
+ try: v = int(val)
+ except:
+ msg("'%s': invalid value for %s (not an integer)" % (val,what))
+ return False
+ if v != 0 and not (g.min_urandchars <= v <= g.max_urandchars):
+ msg("'%s': invalid %s (must be >= %s and <= %s (or zero))"
+ % (v,what,g.min_urandchars,g.max_urandchars))
return False
else:
if g.debug: print "check_opts(): No test for opt '%s'" % opt
@@ -244,22 +255,18 @@ def check_opts(opts,long_opts):
return True
-def set_if_unset_and_typeconvert(opts,opt):
+def typeconvert_override_var(opts,opt):
- if opt in g.cl_override_vars:
- if opt not in opts:
- # Set to similarly named default value in mmgen.config
- opts[opt] = eval("g."+opt)
- else:
- vtype = type(eval("g."+opt))
- if g.debug: print "Opt: %s, Type: %s" % (opt,vtype)
- if vtype == int: f,t = int,"an integer"
- elif vtype == str: f,t = str,"a string"
- elif vtype == float: f,t = float,"a float"
+ vtype = type(eval("g."+opt))
+ if g.debug: print "Override opt: %-15s [%s]" % (opt,vtype)
- try:
- opts[opt] = f(opts[opt])
- except:
- msg("'%s': invalid parameter for '--%s' option (not %s)" %
- (opts[opt],opt.replace("_","-"),t))
- sys.exit(1)
+ if vtype == int: f,t = int,"an integer"
+ elif vtype == str: f,t = str,"a string"
+ elif vtype == float: f,t = float,"a float"
+
+ try:
+ opts[opt] = f(opts[opt])
+ except:
+ msg("'%s': invalid parameter for '--%s' option (not %s)" %
+ (opts[opt],opt.replace("_","-"),t))
+ sys.exit(1)
diff --git a/mmgen/addr.py b/mmgen/addr.py
index 2dd92524..e9298507 100755
--- a/mmgen/addr.py
+++ b/mmgen/addr.py
@@ -93,11 +93,11 @@ def generate_addrs(seed, addrnums, opts):
if g.debug:
print "Privkey round %s:\n hex: %s\n wif: %s" % (i, sec, wif)
- el = { 'num': i }
+ d = { 'num': i }
if not 'print_addresses_only' in opts:
- el['sec'] = sec
- el['wif'] = wif
+ d['sec'] = sec
+ d['wif'] = wif
if not 'no_addresses' in opts:
if keyconv:
@@ -107,9 +107,9 @@ def generate_addrs(seed, addrnums, opts):
else:
addr = privnum2addr(int(sec,16))
- el['addr'] = addr
+ d['addr'] = addr
- out.append(el)
+ out.append(d)
w = opts['gen_what']
if t_addrs == 1:
@@ -127,6 +127,10 @@ def generate_keys(seed, addrnums):
def format_addr_data(addr_data, addr_data_chksum, seed_id, addr_idxs, opts):
+ if 'flat_list' in opts:
+ return "\n\n".join(["# %s:%s %s\n%s" % (seed_id,d['num'],d['addr'],d['wif'])
+ for d in addr_data])+"\n\n"
+
start = addr_data[0]['num']
end = addr_data[-1]['num']
@@ -140,35 +144,35 @@ def format_addr_data(addr_data, addr_data_chksum, seed_id, addr_idxs, opts):
(5 if 'print_secret' in opts else 1) + len(wif_msg)
)
- data = []
- if not 'stdout' in opts: data.append(addrmsgs['addrfile_header'] + "\n")
- data.append("# Address data checksum for {}[{}]: {}".format(
+ out = []
+ if not 'stdout' in opts: out.append(addrmsgs['addrfile_header'] + "\n")
+ out.append("# Address data checksum for {}[{}]: {}".format(
seed_id, fmt_addr_idxs(addr_idxs), addr_data_chksum))
- data.append("# Record this value to a secure location\n")
- data.append("%s {" % seed_id.upper())
+ out.append("# Record this value to a secure location\n")
+ out.append("%s {" % seed_id.upper())
- for el in addr_data:
- col1 = el['num']
+ for d in addr_data:
+ col1 = d['num']
if 'no_addresses' in opts:
if 'b16' in opts:
- data.append(fa % (col1, " (hex):", el['sec']))
+ out.append(fa % (col1, " (hex):", d['sec']))
col1 = ""
- data.append(fa % (col1, " (wif):", el['wif']))
- if 'b16' in opts: data.append("")
+ out.append(fa % (col1, " (wif):", d['wif']))
+ if 'b16' in opts: out.append("")
elif 'print_secret' in opts:
if 'b16' in opts:
- data.append(fa % (col1, "sec (hex):", el['sec']))
+ out.append(fa % (col1, "sec (hex):", d['sec']))
col1 = ""
- data.append(fa % (col1, "sec"+wif_msg+":", el['wif']))
- data.append(fa % ("", "addr:", el['addr']))
- data.append("")
+ out.append(fa % (col1, "sec"+wif_msg+":", d['wif']))
+ out.append(fa % ("", "addr:", d['addr']))
+ out.append("")
else:
- data.append(fa % (col1, "", el['addr']))
+ out.append(fa % (col1, "", d['addr']))
- if not data[-1]: data.pop()
- data.append("}")
+ if not out[-1]: out.pop()
+ out.append("}")
- return "\n".join(data) + "\n"
+ return "\n".join(out) + "\n"
def fmt_addr_idxs(addr_idxs):
@@ -193,6 +197,7 @@ def write_addr_data_to_file(seed, addr_data_str, addr_idxs, opts):
if 'print_addresses_only' in opts: ext = g.addrfile_ext
elif 'no_addresses' in opts: ext = g.keyfile_ext
+ elif 'flat_list' in opts: ext = g.keylist_ext
else: ext = "akeys"
if 'b16' in opts: ext = ext.replace("keys","xkeys")
diff --git a/mmgen/config.py b/mmgen/config.py
index 0f98ba4a..5b87a31d 100755
--- a/mmgen/config.py
+++ b/mmgen/config.py
@@ -46,11 +46,13 @@ rawtx_ext = "raw"
sigtx_ext = "sig"
addrfile_ext = "addrs"
keyfile_ext = "keys"
+keylist_ext = "keylist"
+mmenc_ext = "mmenc"
default_wl = "electrum"
#default_wl = "tirosh"
-cl_override_vars = 'seed_len','hash_preset','usr_randlen'
+cl_override_vars = 'seed_len','hash_preset','usr_randchars'
seed_lens = 128,192,256
seed_len = 256
@@ -68,8 +70,8 @@ bogus_wallet_data = getenv("MMGEN_BOGUS_WALLET_DATA")
mins_per_block = 8.5
passwd_max_tries = 5
-max_randlen,min_randlen = 80,5
-usr_randlen = 20
+usr_randchars,usr_randchars_dfl = -1,30 # see get_random()
+max_urandchars,min_urandchars = 80,10
salt_len = 16
aesctr_iv_len = 16
@@ -97,6 +99,7 @@ max_addr_label_len = 32
wallet_label_symbols = addr_label_symbols
max_wallet_label_len = 32
+user_entropy = ""
#addr_label_punc = ".","_",",","-"," ","(",")"
#addr_label_symbols = tuple(ascii_letters + digits) + addr_label_punc
#wallet_label_punc = addr_label_punc
diff --git a/mmgen/tool.py b/mmgen/tool.py
index dfb976a5..2e2d7bb6 100755
--- a/mmgen/tool.py
+++ b/mmgen/tool.py
@@ -23,6 +23,7 @@ import sys
import mmgen.bitcoin as bitcoin
import binascii as ba
+import mmgen.config as g
from mmgen.util import *
from mmgen.tx import *
@@ -33,6 +34,7 @@ def Vmsg(s):
def Vmsg_r(s):
if g.verbose: sys.stdout.write(s)
+opts = {}
commands = {
"strtob58": [' [str]'],
"hextob58": [' [str]'],
@@ -66,6 +68,8 @@ commands = {
"pubkey2addr": [' [str]'],
"pubkey2hexaddr": [' [str]'],
"privhex2addr": [' [str]','compressed [bool=False]'],
+ "encrypt": [' [str]','outfile [str=""]','hash_preset [str="3"]'],
+ "decrypt": [' [str]','outfile [str=""]','hash_preset [str="3"]'],
}
command_help = """
@@ -73,10 +77,10 @@ command_help = """
hexdump - encode data into formatted hexadecimal form (file or stdin)
unhexdump - decode formatted hexadecimal data (file or stdin)
- MMGen-specific operations
- id8 - generate 8-character MMGen ID checksum for file (or stdin)
- id6 - generate 6-character MMGen ID checksum for file (or stdin)
- check_addrfile - compute checksum and address list for MMGen address file
+ {pnm}-specific operations
+ id8 - generate 8-character {pnm} ID checksum for file (or stdin)
+ id6 - generate 6-character {pnm} ID checksum for file (or stdin)
+ check_addrfile - compute checksum and address list for {pnm} address file
Bitcoin operations:
strtob58 - convert a string to base 58
@@ -89,7 +93,7 @@ command_help = """
hex2wif - convert a private key from hex to WIF format
wif2addr - generate a Bitcoin address from a key in WIF format
pubkey2addr - convert Bitcoin public key to address
- pubkey2hexaddr - convert Bitcoin public key to address in hex format
+ pubkey2hexaddr - convert Bitcoin public key to address in hex format
hexaddr2addr - convert Bitcoin address from hex to base58 format
addr2hexaddr - convert Bitcoin address from base58 to hex format
privhex2addr - generate Bitcoin address from private key in hex format
@@ -100,6 +104,14 @@ command_help = """
sha256x2 - compute a double sha256 hash of data
getrand - print 'n' bytes (default 32) of random data in hex format
+ Encryption operations:
+ encrypt - encrypt a file using {pnm}'s encryption suite
+ decrypt - decrypt an {pnm}-encrypted file
+ {pnm} encryption suite:
+ * Key: Scrypt (user-configurable hash parameters, 32-byte salt)
+ * Enc: AES256_CTR, 16-byte rand IV, sha256 hash + 32-byte nonce + data
+ * The encrypted file is indistinguishable from random data
+
Mnemonic operations (choose "electrum" (default), "tirosh" or "all"
wordlists):
mn_rand128 - generate random 128-bit mnemonic
@@ -109,14 +121,14 @@ command_help = """
mn_printlist - print mnemonic wordlist
Bitcoind operations (bitcoind must be running):
- listaddresses - show MMGen addresses and their balances
+ listaddresses - show {pnm} addresses and their balances
getbalance - like 'bitcoind getbalance' but shows confirmed/unconfirmed,
spendable/unspendable
- viewtx - show raw transaction in human-readable form
+ viewtx - show raw/signed {pnm} transaction in human-readable form
- IMPORTANT NOTE: Though MMGen mnemonics use the Electrum wordlist, they're
+ IMPORTANT NOTE: Though {pnm} mnemonics use the Electrum wordlist, they're
computed using a different algorithm and are NOT Electrum-compatible!
-"""
+""".format(pnm=g.proj_name)
def tool_usage(prog_name, command):
print "USAGE: '%s %s%s'" % (prog_name, command,
@@ -216,26 +228,22 @@ def b58tohex(s,f_enc=bitcoin.b58decode, f_dec=bitcoin.b58encode):
dec = f_dec(ba.unhexlify(enc))
print_convert_results(s,enc,dec)
-def get_random(length):
- from Crypto import Random
- return Random.new().read(length)
-
def b58randenc():
- r = get_random(32)
+ r = get_random(32,opts)
enc = bitcoin.b58encode(r)
dec = bitcoin.b58decode(enc)
print_convert_results(ba.hexlify(r),enc,ba.hexlify(dec))
def getrand(bytes='32'):
- print ba.hexlify(get_random(int(bytes)))
+ print ba.hexlify(get_random(int(bytes),opts))
def randwif(compressed=False):
- r_hex = ba.hexlify(get_random(32))
+ r_hex = ba.hexlify(get_random(32,opts))
enc = bitcoin.hextowif(r_hex,compressed)
print_convert_results(r_hex,enc,"",no_recode=True)
def randpair(compressed=False):
- r_hex = ba.hexlify(get_random(32))
+ r_hex = ba.hexlify(get_random(32,opts))
wif = bitcoin.hextowif(r_hex,compressed)
addr = bitcoin.privnum2addr(int(r_hex,16),compressed)
Vmsg("Key (hex): %s" % r_hex)
@@ -265,7 +273,7 @@ def get_wordlist(wordlist):
return el if wordlist == "electrum" else tl
def do_random_mn(nbytes,wordlist):
- r = get_random(nbytes)
+ r = get_random(nbytes,opts)
wlists = wordlists if wordlist == "all" else [wordlist]
for wl in wlists:
l = get_wordlist(wl)
@@ -397,3 +405,40 @@ def wif2hex(wif,compressed=False):
def hex2wif(hexpriv,compressed=False):
print bitcoin.hextowif(hexpriv,compressed)
+
+salt_len,sha256_len,nonce_len = 32,32,32
+
+def encrypt(infile,outfile="",hash_preset=''):
+ d = get_data_from_file(infile,"data for encryption")
+ salt,iv,nonce = get_random(salt_len,opts),\
+ get_random(g.aesctr_iv_len,opts), get_random(nonce_len,opts)
+ hp,m = (hash_preset,"user-requested") if hash_preset else ('3',"default")
+ qmsg("Using %s hash preset of '%s'" % (m,hp))
+ passwd = get_new_passphrase("passphrase",{})
+ key = make_key(passwd, salt, hp)
+ from hashlib import sha256
+ enc_d = encrypt_data(sha256(nonce+d).digest() + nonce + d, key,
+ int(ba.hexlify(iv),16))
+ if outfile == '-': sys.stdout.write(salt+iv+enc_d)
+ else: write_to_file((outfile or infile+"."+g.mmenc_ext),salt+iv+enc_d,True,True)
+
+def decrypt(infile,outfile="",hash_preset=''):
+ d = get_data_from_file(infile,"encrypted data")
+ dstart = salt_len + g.aesctr_iv_len
+ salt,iv,enc_d = d[:salt_len],d[salt_len:dstart],d[dstart:]
+ hp,m = (hash_preset,"user-requested") if hash_preset else ('3',"default")
+ qmsg("Using %s hash preset of '%s'" % (m,hp))
+ passwd = get_mmgen_passphrase("Enter passphrase: ",{})
+ key = make_key(passwd, salt, hp)
+ dec_d = decrypt_data(enc_d, key, int(ba.hexlify(iv),16))
+ from hashlib import sha256
+ if dec_d[:sha256_len] == sha256(dec_d[sha256_len:]).digest():
+ out = dec_d[sha256_len+nonce_len:]
+ if outfile == '-': sys.stdout.write(out)
+ else:
+ import re
+ of = re.sub(r'\.%s$'%g.mmenc_ext,r'',infile)
+ if of == infile: of = infile+".dec"
+ write_to_file((outfile or of), out, True,True)
+ else:
+ msg("Incorrect passphrase or hash preset")
diff --git a/mmgen/tx.py b/mmgen/tx.py
index fd25af0d..2c6dd8d9 100755
--- a/mmgen/tx.py
+++ b/mmgen/tx.py
@@ -61,7 +61,25 @@ tracking wallet, or supply an address file for it on the command line.
'no_spendable_outputs': """
No spendable outputs found! Import addresses with balances into your
watch-only wallet using 'mmgen-addrimport' and then re-run this program.
-""".strip()
+""".strip(),
+ 'mapping_error': """
+MMGen -> BTC address mappings differ!
+In transaction: %s
+Generated from seed: %s
+""".strip(),
+ 'skip_mapping_checks_warning': """
+You've chosen the '--all-keys-from-file' option. Since all signing keys will
+be taken from this file, no {pnm} seed source will be consulted and {pnm}-to-
+BTC mapping checks cannot not be performed. Were an attacker to compromise
+your tracking wallet or raw transaction file, he could thus cause you to spend
+coin to an unintended address. For greater security, supply a trusted {pnm}
+address file for your output addresses on the command line.
+""".strip().format(pnm=g.proj_name),
+ 'missing_mappings': """
+No information was found in the supplied address files for the following {pnm}
+addresses: %s
+The {pnm}-to-BTC mappings for these addresses cannot be verified!
+""".strip().format(pnm=g.proj_name),
}
# Deleted text:
@@ -504,7 +522,7 @@ def mmaddr2btcaddr_bitcoind(c,mmaddr,acct_data):
return "",""
-def mmaddr2btcaddr_addrfile(mmaddr,addr_data):
+def mmaddr2btcaddr_addrfile(mmaddr,addr_data,silent=False):
mmid,mmidx = mmaddr.split(":")
@@ -512,35 +530,28 @@ def mmaddr2btcaddr_addrfile(mmaddr,addr_data):
if mmid == ad[0]:
for j in ad[1]:
if j[0] == mmidx:
- msg(txmsg['addrfile_warn_msg'].format(mmaddr=mmaddr))
- if not user_confirm("Continue anyway?"):
- sys.exit(1)
+ if not silent:
+ msg(txmsg['addrfile_warn_msg'].format(mmaddr=mmaddr))
+ if not user_confirm("Continue anyway?"):
+ sys.exit(1)
return j[1:] if len(j) == 3 else (j[1],"")
- msg(txmsg['addrfile_fail_msg'].format(mmaddr=mmaddr))
- sys.exit(2)
+ if silent: return "",""
+ else: msg(txmsg['addrfile_fail_msg'].format(mmaddr=mmaddr)); sys.exit(2)
-def check_mmgen_to_btc_addr_mappings(inputs_data,b2m_map,infiles,saved_seeds,opts):
- in_maplist = [(i['account'].split()[0],i['address'])
- for i in inputs_data if i['account']
- and is_mmgen_addr(i['account'].split()[0])]
+def check_mmgen_to_btc_addr_mappings(mmgen_inputs,b2m_map,infiles,saved_seeds,opts):
+ in_maplist = [(i['account'].split()[0],i['address']) for i in mmgen_inputs]
out_maplist = [(i[1][0],i[0]) for i in b2m_map.items()]
for maplist,label in (in_maplist,"inputs"), (out_maplist,"outputs"):
if not maplist: continue
qmsg("Checking MMGen -> BTC address mappings for %s" % label)
- mmaddrs = [i[0] for i in maplist]
- from copy import deepcopy
- pairs = get_keys_for_mmgen_addrs(mmaddrs,
+ pairs = get_keys_for_mmgen_addrs([i[0] for i in maplist],
infiles,saved_seeds,opts,gen_pairs=True)
for a,b in zip(sorted(pairs),sorted(maplist)):
if a != b:
- msg("""
-MMGen -> BTC address mappings differ!
-In transaction: %s
-Generated from seed: %s
- """.strip() % (" ".join(a)," ".join(b)))
+ msg(txmsg['mapping_error'] % (" ".join(a)," ".join(b)))
sys.exit(3)
qmsg("Address mappings OK")
@@ -659,7 +670,6 @@ def get_seed_for_seed_id(seed_id,infiles,saved_seeds,opts):
def get_keys_for_mmgen_addrs(mmgen_addrs,infiles,saved_seeds,opts,gen_pairs=False):
seed_ids = list(set([i[:8] for i in mmgen_addrs]))
- seed_ids_save = seed_ids[0:] # deep copy
ret = []
for seed_id in seed_ids:
@@ -733,6 +743,7 @@ def preverify_keys(addrs_orig, keys_orig):
sys.exit(2)
else: qmsg("OK")
+ # Check that keys match addresses:
msg('Pre-verifying keys in user-supplied key list (Ctrl-C to skip)')
try:
@@ -768,3 +779,28 @@ A key file must be supplied (or use the "-w" option) for the following
non-mmgen address%s:
""".strip() % ("" if len(other_addrs) == 1 else "es"))
print " %s" % "\n ".join([i['address'] for i in other_addrs])
+
+
+def check_mmgen_to_btc_addr_mappings_addrfile(mmgen_inputs,b2m_map,addrfiles):
+ addr_data = [parse_addrs_file(a) for a in addrfiles]
+ in_maplist = [(i['account'].split()[0],i['address']) for i in mmgen_inputs]
+ out_maplist = [(i[1][0],i[0]) for i in b2m_map.items()]
+
+ missing,wrong = [],[]
+ for maplist,label in (in_maplist,"inputs"), (out_maplist,"outputs"):
+ qmsg("Checking MMGen -> BTC address mappings for %s" % label)
+ for i in maplist:
+ btaddr = mmaddr2btcaddr_addrfile(i[0],addr_data,silent=True)[0]
+ if not btaddr: missing.append(i[0])
+ elif btaddr != i[1]: wrong.append((i[0],i[1],btaddr))
+
+ if wrong:
+ fs = " {:11} {:35} {}"
+ msg("ERROR: The following address mappings did not match!")
+ msg(fs.format("MMGen addr","In TX file:","In address file:"))
+ for w in wrong: msg(fs.format(*w))
+ sys.exit(3)
+
+ if missing:
+ confirm_or_exit(txmsg['missing_mappings'] % " ".join(missing),"continue")
+ else: qmsg("Address mappings OK")
diff --git a/mmgen/util.py b/mmgen/util.py
index 7bc2a3d3..31446d37 100755
--- a/mmgen/util.py
+++ b/mmgen/util.py
@@ -20,8 +20,10 @@ util.py: Shared routines for the mmgen suite
"""
import sys
-import mmgen.config as g
+from hashlib import sha256
from binascii import hexlify,unhexlify
+
+import mmgen.config as g
from mmgen.bitcoin import b58decode_pad
from mmgen.term import *
@@ -46,6 +48,60 @@ def get_extension(f):
import os
return os.path.splitext(f)[1][1:]
+
+def get_random_data_from_user(uchars):
+
+ if g.quiet: msg("Enter %s random symbols" % uchars)
+ else: msg(cmessages['usr_rand_notice'] % uchars)
+
+ prompt = "You may begin typing. %s symbols left: "
+ msg_r(prompt % uchars)
+
+ import time
+ # time.clock() always returns zero, so we'll use time.time()
+ saved_time = time.time()
+
+ key_data,time_data = "",[]
+
+ for i in range(uchars):
+ key_data += get_char(immed_chars="ALL")
+ msg_r("\r" + prompt % (uchars - i - 1))
+ now = time.time()
+ time_data.append(now - saved_time)
+ saved_time = now
+
+ if g.quiet: msg_r("\r")
+ else: msg_r("\rThank you. That's enough.%s\n\n" % (" "*18))
+
+ fmt_time_data = ["{:.22f}".format(i) for i in time_data]
+
+ if g.debug:
+ msg("\nUser input:\n%s\nKeystroke time intervals:\n%s\n" %
+ (key_data,"\n".join(fmt_time_data)))
+
+ prompt = "User random data successfully acquired. Press ENTER to continue"
+ prompt_and_get_char(prompt,"",enter_ok=True)
+ msg("")
+
+ return key_data+"".join(fmt_time_data)
+
+
+def get_random(length,opts):
+ from Crypto import Random
+ os_rand = Random.new().read(length)
+ if 'usr_randchars' in opts and opts['usr_randchars'] not in (0,-1):
+ kwhat = "a key from random data with "
+ if not g.user_entropy:
+ g.user_entropy = sha256(
+ get_random_data_from_user(opts['usr_randchars'])).digest()
+ kwhat += "user entropy"
+ else:
+ kwhat += "saved user entropy"
+ key = make_key(g.user_entropy, "", '2', what=kwhat)
+ return encrypt_data(os_rand,key,what="random data")
+ else:
+ return os_rand
+
def my_raw_input(prompt,echo=True):
try:
if echo:
@@ -118,7 +174,21 @@ recommended to use one of the higher-numbered presets
Remember the seed length and hash preset parameters you've specified. To
generate the correct keys/addresses associated with this passphrase in the
future, you must continue using these same parameters
-"""
+""",
+ 'usr_rand_notice': """
+You've chosen to not fully trust your OS's random number generator and provide
+some additional entropy of your own. Please type %s symbols on your keyboard.
+Type slowly and choose your symbols carefully for maximum randomness. Try to
+use both upper and lowercase as well as punctuation and numerals. What you
+type will not be displayed on the screen. Note that the timings between your
+keystrokes will also be used as a source of randomness.
+""",
+ 'choose_wallet_passphrase': """
+Now you must choose a passphrase to encrypt the wallet with. A key will be
+generated from your passphrase using a hash preset of '%s'. Please note that
+no strength checking of passphrases is performed. For an empty passphrase,
+just hit ENTER twice.
+""".strip()
}
@@ -172,12 +242,10 @@ def prompt_and_get_char(prompt,chars,enter_ok=False,verbose=False):
def make_chksum_8(s,sep=False):
- from hashlib import sha256
s = sha256(sha256(s).digest()).hexdigest()[:8].upper()
return "{} {}".format(s[:4],s[4:]) if sep else s
def make_chksum_6(s):
- from hashlib import sha256
return sha256(s).hexdigest()[:6]
@@ -296,31 +364,36 @@ def _get_seed_from_brain_passphrase(words,opts):
return seed
-def encrypt_seed(seed, key, iv=1):
+def encrypt_seed(seed, key):
+ return encrypt_data(seed, key, iv=1, what="seed")
+
+def encrypt_data(data, key, iv=1, what="data"):
+ """
+ Encrypt arbitrary data using AES256 in counter mode
"""
- Encrypt a seed for an {} deterministic wallet
- """.format(g.proj_name)
# 192-bit seed is 24 bytes -> not multiple of 16. Must use MODE_CTR
from Crypto.Cipher import AES
from Crypto.Util import Counter
- c = AES.new(key, AES.MODE_CTR,
- counter=Counter.new(g.aesctr_iv_len*8,initial_value=iv))
- enc_seed = c.encrypt(seed)
-
- vmsg_r("Performing a test decryption of the seed...")
+ vmsg("Encrypting %s" % what)
c = AES.new(key, AES.MODE_CTR,
counter=Counter.new(g.aesctr_iv_len*8,initial_value=iv))
- dec_seed = c.decrypt(enc_seed)
+ enc_data = c.encrypt(data)
- if dec_seed == seed: vmsg("done")
+ vmsg_r("Performing a test decryption of the %s..." % what)
+
+ c = AES.new(key, AES.MODE_CTR,
+ counter=Counter.new(g.aesctr_iv_len*8,initial_value=iv))
+ dec_data = c.decrypt(enc_data)
+
+ if dec_data == data: vmsg("done\n")
else:
- msg("ERROR.\nDecrypted seed doesn't match original seed")
+ msg("ERROR.\nDecrypted %s doesn't match original %s" % (what,what))
sys.exit(2)
- return enc_seed
+ return enc_data
def write_to_stdout(data, what, confirm=True):
@@ -354,7 +427,9 @@ def open_file_or_exit(filename,mode):
return f
-def write_to_file(outfile,data,confirm=False):
+def write_to_file(outfile,data,confirm=False,verbose=False):
+
+ if verbose: qmsg("Writing data to file '%s'" % outfile)
if confirm:
from os import stat
@@ -396,8 +471,8 @@ def _display_control_data(label,metadata,hash_preset,salt,enc_seed):
from mmgen.bitcoin import b58encode_pad
for i in (
("Label:", label),
- ("Seed ID:", metadata[0]),
- ("Key ID:", metadata[1]),
+ ("Seed ID:", metadata[0].upper()),
+ ("Key ID:", metadata[1].upper()),
("Seed length:", "%s bits (%s bytes)" %
(metadata[2],int(metadata[2])/8)),
("Scrypt params:", "Preset '%s' (%s)" % (hash_preset,
@@ -799,10 +874,9 @@ def get_seed_from_incog_wallet(
break
msg("%s: Invalid hash preset" % hp)
- from hashlib import sha256
# IV is used BOTH to initialize counter and to salt password!
key = make_key(passwd, iv, hp, "wrapper key")
- d = decrypt_seed(enc_incog_data, key, "", "", iv=int(hexlify(iv),16))
+ d = decrypt_data(enc_incog_data, key, int(hexlify(iv),16), "incog data")
if d == False: sys.exit(2)
salt,enc_seed = d[0:g.salt_len], d[g.salt_len:]
@@ -820,14 +894,14 @@ def get_seed_from_incog_wallet(
def make_key(passwd, salt, hash_preset, what="key"):
- vmsg_r("Generating %s from passphrase. Please wait..." % what)
+ vmsg_r("Generating %s. Please wait..." % what)
key = _scrypt_hash_passphrase(passwd, salt, hash_preset)
vmsg("done")
if g.debug: print "Key: %s" % hexlify(key)
return key
-def decrypt_seed(enc_seed, key, seed_id, key_id, iv=1):
+def decrypt_seed(enc_seed, key, seed_id, key_id):
vmsg("Checking key...")
chk1 = make_chksum_8(key)
@@ -836,23 +910,16 @@ def decrypt_seed(enc_seed, key, seed_id, key_id, iv=1):
msg("Incorrect passphrase")
return False
- vmsg("Decrypting seed with key...")
-
- from Crypto.Cipher import AES
- from Crypto.Util import Counter
-
- c = AES.new(key, AES.MODE_CTR,
- counter=Counter.new(g.aesctr_iv_len*8,initial_value=iv))
- dec_seed = c.decrypt(enc_seed)
+ dec_seed = decrypt_data(enc_seed, key, iv=1, what="seed")
chk2 = make_chksum_8(dec_seed)
+
if seed_id:
if _compare_checksums(chk2,"of decrypted seed",seed_id,"in header"):
qmsg("Passphrase is OK")
else:
if not g.debug:
msg_r("Checking key ID...")
- chk1 = make_chksum_8(key)
if _compare_checksums(chk1, "of key", key_id, "in header"):
msg("Key ID is correct but decryption of seed failed")
else:
@@ -867,6 +934,20 @@ def decrypt_seed(enc_seed, key, seed_id, key_id, iv=1):
return dec_seed
+def decrypt_data(enc_data, key, iv=1, what="data"):
+
+ vmsg("Decrypting %s with key..." % what)
+
+ from Crypto.Cipher import AES
+ from Crypto.Util import Counter
+
+ c = AES.new(key, AES.MODE_CTR,
+ counter=Counter.new(g.aesctr_iv_len*8,initial_value=iv))
+
+ return c.decrypt(enc_data)
+
+
+
def _get_words(infile,what,prompt,opts):
if infile:
return _get_words_from_file(infile,what)
@@ -1007,6 +1088,7 @@ def decode_pretty_hexdump(data):
lines = [re.sub('^\d+:\s+','',l) for l in data.split("\n")]
return unhexlify("".join(("".join(lines).split())))
+
def wallet_to_incog_data(infile,opts):
d = get_data_from_wallet(infile,silent=True)
@@ -1019,16 +1101,14 @@ def wallet_to_incog_data(infile,opts):
if decrypt_seed(enc_seed, key, seed_id, key_id) == False:
sys.exit(2)
- from Crypto import Random
- iv = Random.new().read(g.aesctr_iv_len)
+ iv = get_random(g.aesctr_iv_len,opts)
iv_id = make_chksum_8(iv)
qmsg("IV ID: %s" % iv_id)
- from binascii import hexlify
- from hashlib import sha256
# IV is used BOTH to initialize counter and to salt password!
key = make_key(passwd, iv, preset, "wrapper key")
- wrap_enc = encrypt_seed(salt + enc_seed, key, iv=int(hexlify(iv),16))
+ m = "incog data"
+ wrap_enc = encrypt_data(salt + enc_seed, key, int(hexlify(iv),16), m)
return iv+wrap_enc,seed_id,key_id,iv_id,preset
diff --git a/mmgen/walletgen.py b/mmgen/walletgen.py
index f3b3a02e..b82051bf 100755
--- a/mmgen/walletgen.py
+++ b/mmgen/walletgen.py
@@ -24,55 +24,4 @@ import mmgen.config as g
from mmgen.util import msg, msg_r, qmsg, qmsg_r, get_char, prompt_and_get_char
from binascii import hexlify
-def get_random_data_from_user(opts):
- ulen = opts['usr_randlen']
-
- if g.quiet:
- msg("Enter %s random symbols" % ulen)
- else:
- msg("""
-We're going to be paranoid and not fully trust your OS's random number
-generator. Please type %s symbols on your keyboard. Type slowly and choose
-your symbols carefully for maximum randomness. Try to use both upper and
-lowercase as well as punctuation and numerals. What you type will not be
-displayed on the screen.
-""" % ulen)
-
- prompt = "You may begin typing. %s symbols left: "
- msg_r(prompt % ulen)
-
- import time
- # time.clock() always returns zero, so we'll use time.time()
- saved_time = time.time()
-
- user_rand_data,intervals = "",[]
-
- for i in range(ulen):
- 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 g.quiet:
- msg_r("\r")
- else:
- msg_r("\rThank you. That's enough." + " "*15 + "\n\n")
-
- 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]
-
-
-def display_os_random_data(os_rand_data):
- print "Rand1: {}\nRand2: {}".format(
- *[hexlify(i) for i in os_rand_data])
-
-
-def display_user_random_data(user_rand_data,intervals_fmt):
- msg("\nUser random data: " + user_rand_data)
- msg("Keystroke time intervals:")
- for i in range(0,len(intervals_fmt),3):
- msg(" " + " ".join(intervals_fmt[i:i+3]))
diff --git a/setup.py b/setup.py
index 3e571923..cf03e1a5 100755
--- a/setup.py
+++ b/setup.py
@@ -3,7 +3,7 @@ from distutils.core import setup
setup(
name = 'mmgen',
- version = '0.7.5',
+ version = '0.7.6',
author = 'Philemon',
author_email = 'mmgen-py@yandex.com',
url = 'https://github.com/mmgen/mmgen',
@@ -45,6 +45,7 @@ setup(
],
scripts=[
'mmgen-addrgen',
+ 'mmgen-keygen',
'mmgen-addrimport',
'mmgen-passchg',
'mmgen-walletchk',