Browse Source

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

philemon 11 years ago
parent
commit
490879f968
18 changed files with 467 additions and 323 deletions
  1. 3 0
      README.md
  2. 34 30
      mmgen-addrgen
  3. 2 2
      mmgen-addrimport
  4. 15 14
      mmgen-passchg
  5. 16 11
      mmgen-tool
  6. 9 9
      mmgen-txcreate
  7. 3 3
      mmgen-txsend
  8. 46 26
      mmgen-txsign
  9. 13 2
      mmgen-walletchk
  10. 13 39
      mmgen-walletgen
  11. 44 37
      mmgen/Opts.py
  12. 28 23
      mmgen/addr.py
  13. 6 3
      mmgen/config.py
  14. 63 18
      mmgen/tool.py
  15. 56 20
      mmgen/tx.py
  16. 114 34
      mmgen/util.py
  17. 0 51
      mmgen/walletgen.py
  18. 2 1
      setup.py

+ 3 - 0
README.md

@@ -67,6 +67,8 @@ mnemonic or seed or a lost seed from the wallet or mnemonic.
 
 
 > #### See [Getting Started with MMGen][3]
 > #### See [Getting Started with MMGen][3]
 
 
+> #### [MMGen command help][6]
+
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
 
 [**Forum**][4] |
 [**Forum**][4] |
@@ -78,3 +80,4 @@ Donate: 15TLdmi5NYLdqmtCqczUs5pBPkJDXRs83w
 [3]: https://github.com/mmgen/mmgen/wiki/Getting-Started-with-MMGen
 [3]: https://github.com/mmgen/mmgen/wiki/Getting-Started-with-MMGen
 [4]: https://bitcointalk.org/index.php?topic=567069.0
 [4]: https://bitcointalk.org/index.php?topic=567069.0
 [5]: https://github.com/mmgen/mmgen/wiki/MMGen-Signing-Key
 [5]: https://github.com/mmgen/mmgen/wiki/MMGen-Signing-Key
+[6]: https://github.com/mmgen/mmgen/wiki/cmdhelp/Command-Help

+ 34 - 30
mmgen-addrgen

@@ -38,36 +38,40 @@ help_data = {
                   mnemonic, seed or password""".format(gen_what,g=g),
                   mnemonic, seed or password""".format(gen_what,g=g),
 	'usage':"[opts] [infile] <address list>",
 	'usage':"[opts] [infile] <address list>",
 	'options': """
 	'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{}
-
--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
+-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
 """.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]),
 		seed_lens=", ".join([str(i) for i in g.seed_lens]),
 		what=gen_what, g=g
 		what=gen_what, g=g
 ),
 ),
@@ -160,9 +164,9 @@ if 'stdout' in opts:
 	if gen_what == "keys" and not g.quiet:
 	if gen_what == "keys" and not g.quiet:
 		confirm = True
 		confirm = True
 	else: confirm = False
 	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():
 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:
 else:
 	write_addr_data_to_file(seed, addr_data_str, addr_idxs, opts)
 	write_addr_data_to_file(seed, addr_data_str, addr_idxs, opts)
 
 

+ 2 - 2
mmgen-addrimport

@@ -28,8 +28,8 @@ from mmgen.tx import connect_to_bitcoind,parse_addrs_file
 
 
 help_data = {
 help_data = {
 	'prog_name': sys.argv[0].split("/")[-1],
 	'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]",
 	'usage':"[opts] [mmgen address file]",
 	'options': """
 	'options': """
 -h, --help        Print this help message
 -h, --help        Print this help message

+ 15 - 14
mmgen-passchg

@@ -30,18 +30,20 @@ help_data = {
                   deterministic wallet""".format(g.proj_name),
                   deterministic wallet""".format(g.proj_name),
 	'usage':   "[opts] [filename]",
 	'usage':   "[opts] [filename]",
 	'options': """
 	'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),
 """.format(g=g),
 	'notes': """
 	'notes': """
 
 
@@ -110,9 +112,8 @@ if 'preset' in changed or 'passwd' in changed: # Update key ID, salt
 	qmsg("Will update salt and key ID")
 	qmsg("Will update salt and key ID")
 
 
 	from hashlib import sha256
 	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'])
 	key = make_key(passwd, salt, opts['hash_preset'])
 	new_key_id = make_chksum_8(key)
 	new_key_id = make_chksum_8(key)
 	qmsg("Key ID changed: %s -> %s" % (key_id,new_key_id))
 	qmsg("Key ID changed: %s -> %s" % (key_id,new_key_id))

+ 16 - 11
mmgen-tool

@@ -25,7 +25,7 @@ from hashlib import sha256
 from mmgen.Opts import *
 from mmgen.Opts import *
 import mmgen.config as g
 import mmgen.config as g
 from mmgen.util import pretty_hexdump
 from mmgen.util import pretty_hexdump
-from mmgen.tool import *
+import mmgen.tool as tool
 prog_name = sys.argv[0].split("/")[-1]
 prog_name = sys.argv[0].split("/")[-1]
 
 
 help_data = {
 help_data = {
@@ -33,16 +33,18 @@ help_data = {
 	'desc':    "Perform various BTC-related operations",
 	'desc':    "Perform various BTC-related operations",
 	'usage':   "[opts] <command> <args>",
 	'usage':   "[opts] <command> <args>",
 	'options': """
 	'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': """
 	'notes': """
 
 
 COMMANDS:{}
 COMMANDS:{}
 Type '{} <command> --help for usage information on a particular
 Type '{} <command> --help for usage information on a particular
 command
 command
-""".format(command_help,prog_name)
+""".format(tool.command_help,prog_name)
 }
 }
 
 
 opts,cmd_args = parse_opts(sys.argv,help_data)
 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:
 if len(cmd_args) < 1:
 	usage(help_data)
 	usage(help_data)
 	sys.exit(1)
 	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)
 	msg("'%s': No such command" % command)
 	sys.exit(1)
 	sys.exit(1)
 
 
 if cmd_args and cmd_args[0] == '--help':
 if cmd_args and cmd_args[0] == '--help':
-	tool_usage(prog_name, command)
+	tool.tool_usage(prog_name, command)
 	sys.exit(0)
 	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) + ")"
 #print command + "(" + ", ".join(args) + ")"
-eval(command + "(" + ", ".join(args) + ")")
+eval("tool." + command + "(" + ", ".join(args) + ")")

+ 9 - 9
mmgen-txcreate

@@ -36,13 +36,13 @@ help_data = {
 	'desc':    "Create a BTC transaction with outputs to specified addresses",
 	'desc':    "Create a BTC transaction with outputs to specified addresses",
 	'usage':   "[opts]  <addr,amt> ... [change addr] [addr file] ...",
 	'usage':   "[opts]  <addr,amt> ... [change addr] [addr file] ...",
 	'options': """
 	'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),
 """.format(g=g),
 	'notes': """
 	'notes': """
 
 
@@ -52,12 +52,12 @@ via an interactive menu.
 Ages of transactions are approximate based on an average block creation
 Ages of transactions are approximate based on an average block creation
 interval of {g.mins_per_block} minutes.
 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>.
 of the form <seed ID>:<number>.
 
 
 To send all inputs (minus TX fee) to a single output, specify one address
 To send all inputs (minus TX fee) to a single output, specify one address
 with no amount on the command line.
 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)
 opts,cmd_args = parse_opts(sys.argv,help_data)

+ 3 - 3
mmgen-txsend

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

+ 46 - 26
mmgen-txsign

@@ -30,14 +30,19 @@ from mmgen.util import msg,qmsg
 help_data = {
 help_data = {
 	'prog_name': sys.argv[0].split("/")[-1],
 	'prog_name': sys.argv[0].split("/")[-1],
 	'desc':    "Sign Bitcoin transactions generated by mmgen-txcreate",
 	'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': """
 	'options': """
 -h, --help               Print this help message
 -h, --help               Print this help message
 -d, --outdir=         d  Specify an alternate directory 'd' for output
 -d, --outdir=         d  Specify an alternate directory 'd' for output
 -e, --echo-passphrase    Print passphrase to screen when typing it
 -e, --echo-passphrase    Print passphrase to screen when typing it
 -i, --info               Display information about the transaction and exit
 -i, --info               Display information about the transaction and exit
 -I, --tx-id              Display transaction ID 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'
 -P, --passwd-file=    f  Get passphrase from file 'f'
 -q, --quiet              Suppress warnings; overwrite files without
 -q, --quiet              Suppress warnings; overwrite files without
                          prompting
                          prompting
@@ -49,37 +54,37 @@ help_data = {
 -g, --from-incog         Generate keys from an incognito wallet
 -g, --from-incog         Generate keys from an incognito wallet
 -X, --from-incog-hex     Generate keys from an incognito hexadecimal 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
 -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
 -m, --from-mnemonic      Generate keys from an electrum-like mnemonic
 -s, --from-seed          Generate keys from a seed in .{g.seed_ext} format
 -s, --from-seed          Generate keys from a seed in .{g.seed_ext} format
-""".format(g=g),
+""".format(g=g,pnm=g.proj_name),
 	'notes': """
 	'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.
 mmgen-addrgen and mmgen-keygen utilities.
 
 
 Data for the --from-<what> options will be taken from a file if a second
 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
 file is specified on the command line.  Otherwise, the user will be
 prompted to enter the data.
 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)
 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
 using the '--keys-from-file' option.  Alternatively, one may get keys from
 a running bitcoind using the '--force-wallet-dat' option.  First import the
 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
 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:
 Seed data supplied in files must have the following extensions:
    wallet:      '.{g.wallet_ext}'
    wallet:      '.{g.wallet_ext}'
    seed:        '.{g.seed_ext}'
    seed:        '.{g.seed_ext}'
    mnemonic:    '.{g.mn_ext}'
    mnemonic:    '.{g.mn_ext}'
    brainwallet: '.{g.brain_ext}'
    brainwallet: '.{g.brain_ext}'
-""".format(g=g)
+""".format(g=g,pnm=g.proj_name)
 }
 }
 
 
 opts,infiles = parse_opts(sys.argv,help_data)
 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 "quiet" in opts: g.quiet = True
 if 'from_incog_hex' in opts or 'from_incog_hidden' in opts:
 if 'from_incog_hex' in opts or 'from_incog_hidden' in opts:
 	opts['from_incog'] = True
 	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)
 if not infiles: usage(help_data)
 for i in infiles: check_infile(i)
 for i in infiles: check_infile(i)
@@ -94,13 +102,16 @@ for i in infiles: check_infile(i)
 c = connect_to_bitcoind()
 c = connect_to_bitcoind()
 
 
 saved_seeds = {}
 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)
 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:
 for tx_file in tx_files:
 	m = "" if 'tx_id' in opts else "transaction data"
 	m = "" if 'tx_id' in opts else "transaction data"
@@ -118,21 +129,30 @@ for tx_file in tx_files:
 		sys.exit(0)
 		sys.exit(0)
 
 
 # Are inputs mmgen addresses?
 # 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
 	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)
 		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)
 		preverify_keys(a, keys)
 		opts['skip_key_preverify'] = True
 		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:
 	if len(tx_files) > 1:
 		msg("\nTransaction %s/%s:" % (tx_files.index(tx_file)+1,len(tx_files)))
 		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']}
 		{"txid":i['txid'],"vout":i['vout'],"scriptPubKey":i['scriptPubKey']}
 			for i in inputs_data]
 			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)
 		keys += get_keys_for_mmgen_addrs(ml,infiles,saved_seeds,opts)
 
 
 		if 'use_wallet_dat' in opts:
 		if 'use_wallet_dat' in opts:
 			sig_tx = sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys,opts)
 			sig_tx = sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys,opts)
 		else:
 		else:
 			sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
 			sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
-	elif other_addrs:
+	elif other_inputs:
 		if keys:
 		if keys:
 			sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
 			sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
 		else:
 		else:

+ 13 - 2
mmgen-walletchk

@@ -37,14 +37,24 @@ help_data = {
 -e, --echo-passphrase  Print passphrase to screen when typing it
 -e, --echo-passphrase  Print passphrase to screen when typing it
 -P, --passwd-file=  f  Get passphrase from file 'f'
 -P, --passwd-file=  f  Get passphrase from file 'f'
 -q, --quiet            Suppress warnings; overwrite files without prompting
 -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
 -S, --stdout           Print seed or mnemonic data to standard output
 -v, --verbose          Produce more verbose output
 -v, --verbose          Produce more verbose output
 -g, --export-incog     Export wallet to incognito format
 -g, --export-incog     Export wallet to incognito format
 -X, --export-incog-hex Export wallet to incognito hexadecimal 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
 -m, --export-mnemonic  Export the wallet's mnemonic to file
 -s, --export-seed      Export the wallet's seed 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:
 elif 'export_seed' in opts:
 	qmsg("Exporting seed data to file by user request")
 	qmsg("Exporting seed data to file by user request")
 elif 'export_incog' in opts:
 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")
 	qmsg("Exporting wallet to incognito format by user request")
 	incog_enc,seed_id,key_id,iv_id,preset = \
 	incog_enc,seed_id,key_id,iv_id,preset = \
 		wallet_to_incog_data(cmd_args[0],opts)
 		wallet_to_incog_data(cmd_args[0],opts)

+ 13 - 39
mmgen-walletgen

@@ -47,8 +47,8 @@ help_data = {
 -P, --passwd-file=      f  Get passphrase from file 'f'
 -P, --passwd-file=      f  Get passphrase from file 'f'
 -q, --quiet                Produce quieter output; overwrite files without
 -q, --quiet                Produce quieter output; overwrite files without
                            prompting
                            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
 -v, --verbose              Produce more verbose output
 
 
 -b, --from-brain=      l,p Generate wallet from a user-created passphrase,
 -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
 multi-line file with free spacing and indentation.  This is particularly
 convenient for long brainwallet passphrases, for example.
 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:
 BRAINWALLET NOTE:
 
 
 As brainwallets require especially strong hashing to thwart dictionary
 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 'quiet' in opts: g.quiet = True
 if 'verbose' in opts: g.verbose = True
 if 'verbose' in opts: g.verbose = True
 if 'show_hash_presets' in opts: show_hash_presets()
 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)
 if g.debug: show_opts_and_cmd_args(opts,cmd_args)
 
 
@@ -116,31 +122,6 @@ else: usage(help_data)
 
 
 do_license_msg()
 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:
 if 'from_brain' in opts and not g.quiet:
 	confirm_or_exit(cmessages['brain_warning'].format(
 	confirm_or_exit(cmessages['brain_warning'].format(
 			g.proj_name, *get_from_brain_opt_params(opts)),
 			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
 		break
 else:
 else:
 	# Truncate random data for smaller seed lengths
 	# Truncate random data for smaller seed lengths
-	seed = os_rand_data[0] + usr_rand_data
-	seed = sha256(seed).digest()[:opts['seed_len']/8]
-
-salt = os_rand_data[1] + usr_rand_data
-salt = sha256(salt).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'])
+	seed = sha256(get_random(128,opts)).digest()[:opts['seed_len']/8]
+
+salt = sha256(get_random(128,opts)).digest()[:g.salt_len]
+
+qmsg(cmessages['choose_wallet_passphrase'] % opts['hash_preset'])
 
 
 passwd = get_new_passphrase("{} wallet passphrase".format(g.proj_name), opts)
 passwd = get_new_passphrase("{} wallet passphrase".format(g.proj_name), opts)
 
 

+ 44 - 37
mmgen/Opts.py

@@ -83,27 +83,38 @@ def parse_opts(argv,help_data):
 
 
 	lines = help_data['options'].strip().split("\n")
 	lines = help_data['options'].strip().split("\n")
 	import re
 	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]
-
-	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]
+	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()
+
+	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(
 	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]]
 			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)
 	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 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 not check_opts(opts,long_opts): sys.exit(1) # MMGen only!
+	if g.debug: print "processed opts: %s" % opts
 
 
 	return opts,infiles
 	return opts,infiles
 
 
 
 
 def show_opts_and_cmd_args(opts,cmd_args):
 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:
 # Everything below here is MMGen-specific:
@@ -112,17 +123,13 @@ from mmgen.util import msg,check_infile
 
 
 def check_opts(opts,long_opts):
 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():
 	for opt,val in opts.items():
 
 
 		what = "parameter for '--%s' option" % opt.replace("_","-")
 		what = "parameter for '--%s' option" % opt.replace("_","-")
 
 
 		# Check for file existence and readability
 		# 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
 			check_infile(val)  # exits on error
 			continue
 			continue
 
 
@@ -233,10 +240,14 @@ def check_opts(opts,long_opts):
 				msg("'%s': invalid %s.  Options: %s"
 				msg("'%s': invalid %s.  Options: %s"
 				% (val,what,", ".join(sorted(g.hash_presets.keys()))))
 				% (val,what,", ".join(sorted(g.hash_presets.keys()))))
 				return False
 				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
 				return False
 		else:
 		else:
 			if g.debug: print "check_opts(): No test for opt '%s'" % opt
 			if g.debug: print "check_opts(): No test for opt '%s'" % opt
@@ -244,22 +255,18 @@ def check_opts(opts,long_opts):
 	return True
 	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)

+ 28 - 23
mmgen/addr.py

@@ -93,11 +93,11 @@ def generate_addrs(seed, addrnums, opts):
 		if g.debug:
 		if g.debug:
 			print "Privkey round %s:\n  hex: %s\n  wif: %s" % (i, sec, wif)
 			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:
 		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 not 'no_addresses' in opts:
 			if keyconv:
 			if keyconv:
@@ -107,9 +107,9 @@ def generate_addrs(seed, addrnums, opts):
 			else:
 			else:
 				addr = privnum2addr(int(sec,16))
 				addr = privnum2addr(int(sec,16))
 
 
-			el['addr'] = addr
+			d['addr'] = addr
 
 
-		out.append(el)
+		out.append(d)
 
 
 	w = opts['gen_what']
 	w = opts['gen_what']
 	if t_addrs == 1:
 	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):
 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']
 	start = addr_data[0]['num']
 	end   = addr_data[-1]['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)
 			(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))
 				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 'no_addresses' in opts:
 			if 'b16' in opts:
 			if 'b16' in opts:
-				data.append(fa % (col1, " (hex):", el['sec']))
+				out.append(fa % (col1, " (hex):", d['sec']))
 				col1 = ""
 				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:
 		elif 'print_secret' in opts:
 			if 'b16' in opts:
 			if 'b16' in opts:
-				data.append(fa % (col1, "sec (hex):", el['sec']))
+				out.append(fa % (col1, "sec (hex):", d['sec']))
 				col1 = ""
 				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:
 		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):
 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
 	if 'print_addresses_only' in opts: ext = g.addrfile_ext
 	elif 'no_addresses' in opts:       ext = g.keyfile_ext
 	elif 'no_addresses' in opts:       ext = g.keyfile_ext
+	elif 'flat_list' in opts:          ext = g.keylist_ext
 	else:                              ext = "akeys"
 	else:                              ext = "akeys"
 
 
 	if 'b16' in opts: ext = ext.replace("keys","xkeys")
 	if 'b16' in opts: ext = ext.replace("keys","xkeys")

+ 6 - 3
mmgen/config.py

@@ -46,11 +46,13 @@ rawtx_ext    = "raw"
 sigtx_ext    = "sig"
 sigtx_ext    = "sig"
 addrfile_ext = "addrs"
 addrfile_ext = "addrs"
 keyfile_ext  = "keys"
 keyfile_ext  = "keys"
+keylist_ext  = "keylist"
+mmenc_ext    = "mmenc"
 
 
 default_wl    = "electrum"
 default_wl    = "electrum"
 #default_wl    = "tirosh"
 #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_lens = 128,192,256
 seed_len  = 256
 seed_len  = 256
@@ -68,8 +70,8 @@ bogus_wallet_data = getenv("MMGEN_BOGUS_WALLET_DATA")
 
 
 mins_per_block = 8.5
 mins_per_block = 8.5
 passwd_max_tries = 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
 salt_len    = 16
 aesctr_iv_len  = 16
 aesctr_iv_len  = 16
 
 
@@ -97,6 +99,7 @@ max_addr_label_len = 32
 wallet_label_symbols = addr_label_symbols
 wallet_label_symbols = addr_label_symbols
 max_wallet_label_len = 32
 max_wallet_label_len = 32
 
 
+user_entropy = ""
 #addr_label_punc = ".","_",",","-"," ","(",")"
 #addr_label_punc = ".","_",",","-"," ","(",")"
 #addr_label_symbols = tuple(ascii_letters + digits) + addr_label_punc
 #addr_label_symbols = tuple(ascii_letters + digits) + addr_label_punc
 #wallet_label_punc = addr_label_punc
 #wallet_label_punc = addr_label_punc

+ 63 - 18
mmgen/tool.py

@@ -23,6 +23,7 @@ import sys
 import mmgen.bitcoin as bitcoin
 import mmgen.bitcoin as bitcoin
 import binascii as ba
 import binascii as ba
 
 
+import mmgen.config as g
 from mmgen.util import *
 from mmgen.util import *
 from mmgen.tx import *
 from mmgen.tx import *
 
 
@@ -33,6 +34,7 @@ def Vmsg(s):
 def Vmsg_r(s):
 def Vmsg_r(s):
 	if g.verbose: sys.stdout.write(s)
 	if g.verbose: sys.stdout.write(s)
 
 
+opts = {}
 commands = {
 commands = {
 	"strtob58":     ['<string> [str]'],
 	"strtob58":     ['<string> [str]'],
 	"hextob58":     ['<hex number> [str]'],
 	"hextob58":     ['<hex number> [str]'],
@@ -66,6 +68,8 @@ commands = {
 	"pubkey2addr":  ['<public key in hex format> [str]'],
 	"pubkey2addr":  ['<public key in hex format> [str]'],
 	"pubkey2hexaddr": ['<public key in hex format> [str]'],
 	"pubkey2hexaddr": ['<public key in hex format> [str]'],
 	"privhex2addr": ['<private key in hex format> [str]','compressed [bool=False]'],
 	"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 = """
 command_help = """
@@ -73,10 +77,10 @@ command_help = """
   hexdump      - encode data into formatted hexadecimal form (file or stdin)
   hexdump      - encode data into formatted hexadecimal form (file or stdin)
   unhexdump    - decode formatted hexadecimal data (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:
   Bitcoin operations:
   strtob58     - convert a string to base 58
   strtob58     - convert a string to base 58
@@ -89,7 +93,7 @@ command_help = """
   hex2wif      - convert a private key from hex to WIF format
   hex2wif      - convert a private key from hex to WIF format
   wif2addr     - generate a Bitcoin address from a key in WIF format
   wif2addr     - generate a Bitcoin address from a key in WIF format
   pubkey2addr  - convert Bitcoin public key to address
   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
   hexaddr2addr - convert Bitcoin address from hex to base58 format
   addr2hexaddr - convert Bitcoin address from base58 to hex format
   addr2hexaddr - convert Bitcoin address from base58 to hex format
   privhex2addr - generate Bitcoin address from private key in 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
   sha256x2     - compute a double sha256 hash of data
   getrand      - print 'n' bytes (default 32) of random data in hex format
   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"
   Mnemonic operations (choose "electrum" (default), "tirosh" or "all"
   wordlists):
   wordlists):
   mn_rand128   - generate random 128-bit mnemonic
   mn_rand128   - generate random 128-bit mnemonic
@@ -109,14 +121,14 @@ command_help = """
   mn_printlist - print mnemonic wordlist
   mn_printlist - print mnemonic wordlist
 
 
   Bitcoind operations (bitcoind must be running):
   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,
   getbalance    - like 'bitcoind getbalance' but shows confirmed/unconfirmed,
                   spendable/unspendable
                   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!
   computed using a different algorithm and are NOT Electrum-compatible!
-"""
+""".format(pnm=g.proj_name)
 
 
 def tool_usage(prog_name, command):
 def tool_usage(prog_name, command):
 	print "USAGE: '%s %s%s'" % (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))
 	dec = f_dec(ba.unhexlify(enc))
 	print_convert_results(s,enc,dec)
 	print_convert_results(s,enc,dec)
 
 
-def get_random(length):
-	from Crypto import Random
-	return Random.new().read(length)
-
 def b58randenc():
 def b58randenc():
-	r = get_random(32)
+	r = get_random(32,opts)
 	enc = bitcoin.b58encode(r)
 	enc = bitcoin.b58encode(r)
 	dec = bitcoin.b58decode(enc)
 	dec = bitcoin.b58decode(enc)
 	print_convert_results(ba.hexlify(r),enc,ba.hexlify(dec))
 	print_convert_results(ba.hexlify(r),enc,ba.hexlify(dec))
 
 
 def getrand(bytes='32'):
 def getrand(bytes='32'):
-	print ba.hexlify(get_random(int(bytes)))
+	print ba.hexlify(get_random(int(bytes),opts))
 
 
 def randwif(compressed=False):
 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)
 	enc = bitcoin.hextowif(r_hex,compressed)
 	print_convert_results(r_hex,enc,"",no_recode=True)
 	print_convert_results(r_hex,enc,"",no_recode=True)
 
 
 def randpair(compressed=False):
 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)
 	wif = bitcoin.hextowif(r_hex,compressed)
 	addr = bitcoin.privnum2addr(int(r_hex,16),compressed)
 	addr = bitcoin.privnum2addr(int(r_hex,16),compressed)
 	Vmsg("Key (hex):  %s" % r_hex)
 	Vmsg("Key (hex):  %s" % r_hex)
@@ -265,7 +273,7 @@ def get_wordlist(wordlist):
 	return el if wordlist == "electrum" else tl
 	return el if wordlist == "electrum" else tl
 
 
 def do_random_mn(nbytes,wordlist):
 def do_random_mn(nbytes,wordlist):
-	r = get_random(nbytes)
+	r = get_random(nbytes,opts)
 	wlists = wordlists if wordlist == "all" else [wordlist]
 	wlists = wordlists if wordlist == "all" else [wordlist]
 	for wl in wlists:
 	for wl in wlists:
 		l = get_wordlist(wl)
 		l = get_wordlist(wl)
@@ -397,3 +405,40 @@ def wif2hex(wif,compressed=False):
 
 
 def hex2wif(hexpriv,compressed=False):
 def hex2wif(hexpriv,compressed=False):
 	print bitcoin.hextowif(hexpriv,compressed)
 	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")

+ 56 - 20
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': """
 No spendable outputs found!  Import addresses with balances into your
 No spendable outputs found!  Import addresses with balances into your
 watch-only wallet using 'mmgen-addrimport' and then re-run this program.
 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:
 # Deleted text:
@@ -504,7 +522,7 @@ def mmaddr2btcaddr_bitcoind(c,mmaddr,acct_data):
 	return "",""
 	return "",""
 
 
 
 
-def mmaddr2btcaddr_addrfile(mmaddr,addr_data):
+def mmaddr2btcaddr_addrfile(mmaddr,addr_data,silent=False):
 
 
 	mmid,mmidx = mmaddr.split(":")
 	mmid,mmidx = mmaddr.split(":")
 
 
@@ -512,35 +530,28 @@ def mmaddr2btcaddr_addrfile(mmaddr,addr_data):
 		if mmid == ad[0]:
 		if mmid == ad[0]:
 			for j in ad[1]:
 			for j in ad[1]:
 				if j[0] == mmidx:
 				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],"")
 					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()]
 	out_maplist = [(i[1][0],i[0]) for i in b2m_map.items()]
 
 
 	for maplist,label in (in_maplist,"inputs"), (out_maplist,"outputs"):
 	for maplist,label in (in_maplist,"inputs"), (out_maplist,"outputs"):
 		if not maplist: continue
 		if not maplist: continue
 		qmsg("Checking MMGen -> BTC address mappings for %s" % label)
 		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)
 				infiles,saved_seeds,opts,gen_pairs=True)
 		for a,b in zip(sorted(pairs),sorted(maplist)):
 		for a,b in zip(sorted(pairs),sorted(maplist)):
 			if a != b:
 			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)
 				sys.exit(3)
 
 
 	qmsg("Address mappings OK")
 	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):
 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 = list(set([i[:8] for i in mmgen_addrs]))
-	seed_ids_save = seed_ids[0:]  # deep copy
 	ret = []
 	ret = []
 
 
 	for seed_id in seed_ids:
 	for seed_id in seed_ids:
@@ -733,6 +743,7 @@ def preverify_keys(addrs_orig, keys_orig):
 		sys.exit(2)
 		sys.exit(2)
 	else: qmsg("OK")
 	else: qmsg("OK")
 
 
+	# Check that keys match addresses:
 	msg('Pre-verifying keys in user-supplied key list (Ctrl-C to skip)')
 	msg('Pre-verifying keys in user-supplied key list (Ctrl-C to skip)')
 
 
 	try:
 	try:
@@ -768,3 +779,28 @@ A key file must be supplied (or use the "-w" option) for the following
 non-mmgen address%s:
 non-mmgen address%s:
 """.strip() % ("" if len(other_addrs) == 1 else "es"))
 """.strip() % ("" if len(other_addrs) == 1 else "es"))
 	print "  %s" % "\n  ".join([i['address'] for i in other_addrs])
 	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")

+ 114 - 34
mmgen/util.py

@@ -20,8 +20,10 @@ util.py:  Shared routines for the mmgen suite
 """
 """
 
 
 import sys
 import sys
-import mmgen.config as g
+from hashlib import sha256
 from binascii import hexlify,unhexlify
 from binascii import hexlify,unhexlify
+
+import mmgen.config as g
 from mmgen.bitcoin import b58decode_pad
 from mmgen.bitcoin import b58decode_pad
 from mmgen.term import *
 from mmgen.term import *
 
 
@@ -46,6 +48,60 @@ def get_extension(f):
 	import os
 	import os
 	return os.path.splitext(f)[1][1:]
 	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):
 def my_raw_input(prompt,echo=True):
 	try:
 	try:
 		if echo:
 		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
 Remember the seed length and hash preset parameters you've specified.  To
 generate the correct keys/addresses associated with this passphrase in the
 generate the correct keys/addresses associated with this passphrase in the
 future, you must continue using these same parameters
 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):
 def make_chksum_8(s,sep=False):
-	from hashlib import sha256
 	s = sha256(sha256(s).digest()).hexdigest()[:8].upper()
 	s = sha256(sha256(s).digest()).hexdigest()[:8].upper()
 	return "{} {}".format(s[:4],s[4:]) if sep else s
 	return "{} {}".format(s[:4],s[4:]) if sep else s
 
 
 def make_chksum_6(s):
 def make_chksum_6(s):
-	from hashlib import sha256
 	return sha256(s).hexdigest()[:6]
 	return sha256(s).hexdigest()[:6]
 
 
 
 
@@ -296,31 +364,36 @@ def _get_seed_from_brain_passphrase(words,opts):
 	return seed
 	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
 	# 192-bit seed is 24 bytes -> not multiple of 16.  Must use MODE_CTR
 	from Crypto.Cipher import AES
 	from Crypto.Cipher import AES
 	from Crypto.Util import Counter
 	from Crypto.Util import Counter
 
 
+	vmsg("Encrypting %s" % what)
+
 	c = AES.new(key, AES.MODE_CTR,
 	c = AES.new(key, AES.MODE_CTR,
 			counter=Counter.new(g.aesctr_iv_len*8,initial_value=iv))
 			counter=Counter.new(g.aesctr_iv_len*8,initial_value=iv))
-	enc_seed = c.encrypt(seed)
+	enc_data = c.encrypt(data)
 
 
-	vmsg_r("Performing a test decryption of the seed...")
+	vmsg_r("Performing a test decryption of the %s..." % what)
 
 
 	c = AES.new(key, AES.MODE_CTR,
 	c = AES.new(key, AES.MODE_CTR,
 			counter=Counter.new(g.aesctr_iv_len*8,initial_value=iv))
 			counter=Counter.new(g.aesctr_iv_len*8,initial_value=iv))
-	dec_seed = c.decrypt(enc_seed)
+	dec_data = c.decrypt(enc_data)
 
 
-	if dec_seed == seed: vmsg("done")
+	if dec_data == data: vmsg("done\n")
 	else:
 	else:
-		msg("ERROR.\nDecrypted seed doesn't match original seed")
+		msg("ERROR.\nDecrypted %s doesn't match original %s" % (what,what))
 		sys.exit(2)
 		sys.exit(2)
 
 
-	return enc_seed
+	return enc_data
 
 
 
 
 def write_to_stdout(data, what, confirm=True):
 def write_to_stdout(data, what, confirm=True):
@@ -354,7 +427,9 @@ def open_file_or_exit(filename,mode):
 	return f
 	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:
 	if confirm:
 		from os import stat
 		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
 	from mmgen.bitcoin import b58encode_pad
 	for i in (
 	for i in (
 		("Label:",               label),
 		("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)" %
 		("Seed length:",         "%s bits (%s bytes)" %
 				(metadata[2],int(metadata[2])/8)),
 				(metadata[2],int(metadata[2])/8)),
 		("Scrypt params:",  "Preset '%s' (%s)" % (hash_preset,
 		("Scrypt params:",  "Preset '%s' (%s)" % (hash_preset,
@@ -799,10 +874,9 @@ def get_seed_from_incog_wallet(
 			break
 			break
 		msg("%s: Invalid hash preset" % hp)
 		msg("%s: Invalid hash preset" % hp)
 
 
-	from hashlib import sha256
 	# IV is used BOTH to initialize counter and to salt password!
 	# IV is used BOTH to initialize counter and to salt password!
 	key = make_key(passwd, iv, hp, "wrapper key")
 	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)
 	if d == False: sys.exit(2)
 
 
 	salt,enc_seed = d[0:g.salt_len], d[g.salt_len:]
 	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"):
 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)
 	key = _scrypt_hash_passphrase(passwd, salt, hash_preset)
 	vmsg("done")
 	vmsg("done")
 	if g.debug: print "Key: %s" % hexlify(key)
 	if g.debug: print "Key: %s" % hexlify(key)
 	return 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...")
 	vmsg("Checking key...")
 	chk1 = make_chksum_8(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")
 			msg("Incorrect passphrase")
 			return False
 			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)
 	chk2 = make_chksum_8(dec_seed)
+
 	if seed_id:
 	if seed_id:
 		if _compare_checksums(chk2,"of decrypted seed",seed_id,"in header"):
 		if _compare_checksums(chk2,"of decrypted seed",seed_id,"in header"):
 			qmsg("Passphrase is OK")
 			qmsg("Passphrase is OK")
 		else:
 		else:
 			if not g.debug:
 			if not g.debug:
 				msg_r("Checking key ID...")
 				msg_r("Checking key ID...")
-				chk1 = make_chksum_8(key)
 				if _compare_checksums(chk1, "of key", key_id, "in header"):
 				if _compare_checksums(chk1, "of key", key_id, "in header"):
 					msg("Key ID is correct but decryption of seed failed")
 					msg("Key ID is correct but decryption of seed failed")
 				else:
 				else:
@@ -867,6 +934,20 @@ def decrypt_seed(enc_seed, key, seed_id, key_id, iv=1):
 	return dec_seed
 	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):
 def _get_words(infile,what,prompt,opts):
 	if infile:
 	if infile:
 		return _get_words_from_file(infile,what)
 		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")]
 	lines = [re.sub('^\d+:\s+','',l) for l in data.split("\n")]
 	return unhexlify("".join(("".join(lines).split())))
 	return unhexlify("".join(("".join(lines).split())))
 
 
+
 def wallet_to_incog_data(infile,opts):
 def wallet_to_incog_data(infile,opts):
 
 
 	d = get_data_from_wallet(infile,silent=True)
 	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:
 	if decrypt_seed(enc_seed, key, seed_id, key_id) == False:
 		sys.exit(2)
 		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)
 	iv_id = make_chksum_8(iv)
 	qmsg("IV ID: %s" % iv_id)
 	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!
 	# IV is used BOTH to initialize counter and to salt password!
 	key = make_key(passwd, iv, preset, "wrapper key")
 	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
 	return iv+wrap_enc,seed_id,key_id,iv_id,preset
 
 

+ 0 - 51
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 mmgen.util import msg, msg_r, qmsg, qmsg_r, get_char, prompt_and_get_char
 from binascii import hexlify
 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]))

+ 2 - 1
setup.py

@@ -3,7 +3,7 @@ from distutils.core import setup
 
 
 setup(
 setup(
 		name         = 'mmgen',
 		name         = 'mmgen',
-		version      = '0.7.5',
+		version      = '0.7.6',
 		author       = 'Philemon',
 		author       = 'Philemon',
 		author_email = 'mmgen-py@yandex.com',
 		author_email = 'mmgen-py@yandex.com',
 		url          = 'https://github.com/mmgen/mmgen',
 		url          = 'https://github.com/mmgen/mmgen',
@@ -45,6 +45,7 @@ setup(
 		],
 		],
 		scripts=[
 		scripts=[
 			'mmgen-addrgen',
 			'mmgen-addrgen',
+			'mmgen-keygen',
 			'mmgen-addrimport',
 			'mmgen-addrimport',
 			'mmgen-passchg',
 			'mmgen-passchg',
 			'mmgen-walletchk',
 			'mmgen-walletchk',