Online signing, new commands for 'mmgen-tool', code cleanups, bugfixes

This commit is contained in:
philemon 2014-07-30 00:48:45 +04:00
commit 490879f968
18 changed files with 467 additions and 323 deletions

View file

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

View file

@ -38,36 +38,40 @@ help_data = {
mnemonic, seed or password""".format(gen_what,g=g),
'usage':"[opts] [infile] <address list>",
'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)

View file

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

View file

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

View file

@ -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] <command> <args>",
'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 '{} <command> --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) + ")")

View file

@ -36,13 +36,13 @@ help_data = {
'desc': "Create a BTC transaction with outputs to specified addresses",
'usage': "[opts] <addr,amt> ... [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 <seed ID>:<number>.
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)

View file

@ -34,9 +34,9 @@ help_data = {
'desc': "Send a Bitcoin transaction signed by mmgen-txsign",
'usage': "[opts] <signed transaction file>",
'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
"""
}

View file

@ -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] <transaction file>,.. [mmgen wallet/seed/words/brainwallet file]...",
'usage': "[opts] <transaction file> .. [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-<what> 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:

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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": ['<string> [str]'],
"hextob58": ['<hex number> [str]'],
@ -66,6 +68,8 @@ commands = {
"pubkey2addr": ['<public key in hex format> [str]'],
"pubkey2hexaddr": ['<public key in hex format> [str]'],
"privhex2addr": ['<private key in hex format> [str]','compressed [bool=False]'],
"encrypt": ['<infile> [str]','outfile [str=""]','hash_preset [str="3"]'],
"decrypt": ['<infile> [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")

View file

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

View file

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

View file

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

View file

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