Browse Source

New script launcher, better KeyboardInterrupt handling, bugfixes in term.py

philemon 10 years ago
parent
commit
2b183c18f3

+ 5 - 179
mmgen-addrgen

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C) 2013-2014 by philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -17,183 +17,9 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 """
-mmgen-addrgen: Generate a list or range of addresses from a mmgen
-               deterministic wallet.
-               Call as 'btc-keygen' to allow key generation.
+mmgen-addrgen: Generate a series or range of addresses from an MMGen
+               deterministic wallet
 """
 
-import sys
-
-import mmgen.config as g
-from mmgen.Opts import *
-from mmgen.license import *
-from mmgen.util import *
-from mmgen.crypto import *
-from mmgen.addr import *
-from mmgen.tx import make_addr_data_chksum
-
-what = "keys" if sys.argv[0].split("-")[-1] == "keygen" else "addresses"
-
-help_data = {
-	'prog_name': g.prog_name,
-	'desc': """Generate a list or range of {} from an {g.proj_name} wallet,
-                  mnemonic, seed or password""".format(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
--c, --save-checksum     Save address list checksum to file
--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(
-		*(
-		   (
-"\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                        '{}-txsign'".format(g.proj_name.lower()),
-"\n-x, --b16               Print secret keys in hexadecimal too"
-			)
-		if what == "keys" else ("","","")),
-		seed_lens=", ".join([str(i) for i in g.seed_lens]),
-		what=what, g=g
-),
-	'notes': """
-
-Addresses are given in a comma-separated list.  Hyphen-separated ranges are
-also allowed.{}
-
-If available, the external 'keyconv' program will be used for address
-generation.
-
-Data for the --from-<what> options will be taken from <infile> if <infile>
-is specified.  Otherwise, the user will be prompted to enter the data.
-
-For passphrases all combinations of whitespace are equal, and leading and
-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.
-
-BRAINWALLET NOTE:
-
-As brainwallets require especially strong hashing to thwart dictionary
-attacks, the brainwallet hash preset must be specified by the user, using
-the 'p' parameter of the '--from-brain' option
-
-The '--from-brain' option also requires the user to specify a seed length
-(the 'l' parameter)
-
-For a brainwallet passphrase to always generate the same keys and addresses,
-the same 'l' and 'p' parameters to '--from-brain' must be used in all future
-invocations with that passphrase
-""".format("\n\nBy default, both addresses and secret keys are generated."
-				if what == "keys" else "")
-}
-
-opts,cmd_args = parse_opts(sys.argv,help_data)
-
-if 'show_hash_presets' in opts: show_hash_presets()
-if 'verbose' in opts: g.verbose = True
-if 'quiet' in opts: g.quiet = True
-if 'from_incog_hex' in opts or 'from_incog_hidden' in opts:
-	opts['from_incog'] = True
-
-if g.debug: show_opts_and_cmd_args(opts,cmd_args)
-
-if len(cmd_args) == 1 and (
-			'from_mnemonic' in opts
-			or 'from_brain' in opts
-			or 'from_seed' in opts
-			or 'from_incog_hidden' in opts
-		):
-	infile,addr_idx_arg = "",cmd_args[0]
-elif len(cmd_args) == 2:
-	infile,addr_idx_arg = cmd_args
-	check_infile(infile)
-else: usage(help_data)
-
-addr_idxs = parse_address_list(addr_idx_arg)
-
-if not addr_idxs: sys.exit(2)
-
-do_license_msg()
-
-# Interact with user:
-if what == "keys" and not g.quiet:
-	confirm_or_exit(cmessages['unencrypted_secret_keys'], 'continue')
-
-# Generate data:
-
-seed    = get_seed_retry(infile,opts)
-seed_id = make_chksum_8(seed)
-
-for l in (
-	('flat_list', 'no_addresses'),
-	('flat_list', 'b16'),
-): warn_incompatible_opts(opts,l)
-
-opts['gen_what'] = \
-	("addrs") if what == "addresses" else (
-	("keys") if 'no_addresses' in opts else ("addrs","keys"))
-
-addr_data        = generate_addrs(seed, addr_idxs, opts)
-addr_data_chksum = make_addr_data_chksum([(a.num,a.addr)
-		for a in addr_data]) if 'addrs' in opts['gen_what'] else ""
-addr_data_str    = format_addr_data(
-		addr_data, addr_data_chksum, seed_id, addr_idxs, opts)
-
-outfile_base = "{}[{}]".format(seed_id, fmt_addr_idxs(addr_idxs))
-
-if 'flat_list' in opts:
-	confirm = False if g.quiet else True
-	outfile = "%s.%s" % (outfile_base,g.keylist_ext)
-	if (user_confirm("Encrypt key list?")):
-		enc_data = mmgen_encrypt(addr_data_str,"",opts)
-		outfile += "."+g.mmenc_ext
-		write_to_file(outfile,enc_data,opts,"encrypted key list",confirm,True)
-	else:
-		write_to_file(outfile,addr_data_str,opts,"key list",confirm,True)
-	sys.exit()
-
-# Output data:
-if 'stdout' in opts:
-	confirm = True if (what == "keys" and not g.quiet) else False
-	write_to_stdout(addr_data_str,what,confirm)
-elif not sys.stdout.isatty():
-	write_to_stdout(addr_data_str,what,confirm=False)
-else:
-	confirm = False if g.quiet else True
-	outfile = outfile_base + "." + (
-		g.keylist_ext if 'flat_list' in opts else (
-		g.keyfile_ext if opts['gen_what'] == ("keys") else (
-		g.addrfile_ext if opts['gen_what'] == ("addrs") else "akeys")))
-	write_to_file(outfile,addr_data_str,opts,what,confirm,True)
-
-if 'addrs' in opts['gen_what']:
-	msg("Checksum for address data {}: {}".format(outfile_base,addr_data_chksum))
-	if 'save_checksum' in opts:
-		a = "address data checksum"
-		write_to_file(outfile_base+".chk",addr_data_chksum,opts,a,confirm,True)
-	else:
-		qmsg("This checksum will be used to verify the address file in the future.")
-		qmsg("Record it to a safe location.")
+import mmgen.main
+mmgen.main.main("addrgen")

+ 4 - 122
mmgen-addrimport

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C) 2013-2014 by philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -17,126 +17,8 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 """
-mmgen-addrimport: Import addresses into a bitcoind watching wallet.
+mmgen-addrimport: Import addresses into a MMGen bitcoind watching wallet
 """
 
-import sys
-from mmgen.Opts   import *
-from mmgen.license import *
-from mmgen.util import *
-from mmgen.tx import connect_to_bitcoind,parse_addrs_file
-
-help_data = {
-	'prog_name': g.prog_name,
-	'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
--l, --addrlist= f Import the non-mmgen Bitcoin addresses listed in file 'f'
--q, --quiet       Suppress warnings
--r, --rescan      Rescan the blockchain.  Required if address to import is
-                  on the blockchain and has a balance.  Rescanning is slow.
-"""
-}
-
-opts,cmd_args = parse_opts(sys.argv,help_data)
-
-if 'quiet' in opts: g.quiet = True
-
-if len(cmd_args) != 1 and not 'addrlist' in opts:
-	msg("You must specify an mmgen address list (and/or non-mmgen addresses with the '--addrlist' option)")
-	sys.exit(1)
-
-if cmd_args:
-	check_infile(cmd_args[0])
-	seed_id,addr_data = parse_addrs_file(cmd_args[0])
-else:
-	seed_id,addr_data = "",[]
-
-if 'addrlist' in opts:
-	lines = get_lines_from_file(opts['addrlist'],"non-mmgen addresses",
-			trim_comments=True)
-	addr_data += [(None,l) for l in lines]
-
-from mmgen.bitcoin import verify_addr
-qmsg_r("Validating addresses...")
-for i in addr_data:
-	if not verify_addr(i[1],verbose=True):
-		msg("%s: invalid address" % i)
-		sys.exit(2)
-qmsg("OK")
-
-import mmgen.config as g
-g.http_timeout = 3600
-
-c = connect_to_bitcoind()
-
-m = """
-WARNING: You've chosen the '--rescan' option.  Rescanning the block chain is
-necessary only if an address you're importing is already on the block chain
-and has a balance.  Note that the rescanning process is very slow (>30 min.
-for each imported address on a low-powered computer).
-	""".strip() if "rescan" in opts else """
-WARNING: If any of the addresses you're importing is already on the block chain
-and has a balance, you must exit the program now and rerun it using the
-'--rescan' option.  Otherwise you may ignore this message and continue.
-""".strip()
-
-if g.quiet: m = ""
-confirm_or_exit(m, "continue", expect="YES")
-
-err_flag = False
-
-def import_address(addr,label,rescan):
-	try:
-		c.importaddress(addr,label,rescan)
-	except:
-		global err_flag
-		err_flag = True
-
-
-w1 = len(str(len(addr_data))) * 2 + 2
-w2 = len(str(max([i[0] for i in addr_data if i[0]]))) + 12
-
-if "rescan" in opts:
-	import threading
-	import time
-	msg_fmt = "\r%s %-" + str(w1) + "s %-34s %-" + str(w2) + "s"
-else:
-	msg_fmt = "\r%-" + str(w1) + "s %-34s %-" + str(w2) + "s"
-
-msg("Importing addresses")
-for n,i in enumerate(addr_data):
-	if i[0]:
-		comment = " " + i[2] if len(i) == 3 else ""
-		label = "%s:%s%s" % (seed_id,i[0],comment)
-	else: label = "non-mmgen"
-
-	if "rescan" in opts:
-		t = threading.Thread(target=import_address, args=(i[1],label,True))
-		t.daemon = True
-		t.start()
-
-		start = int(time.time())
-
-		while True:
-			if t.is_alive():
-				elapsed = int(time.time() - start)
-				msg_r(msg_fmt % (
-						secs_to_hms(elapsed),
-						("%s/%s:" % (n+1,len(addr_data))),
-						i[1], "(" + label + ")"
-					)
-				)
-				time.sleep(1)
-			else:
-				if err_flag: msg("\nImport failed"); sys.exit(2)
-				msg("\nOK")
-				break
-	else:
-		import_address(i[1],label,rescan=False)
-		msg_r(msg_fmt % (("%s/%s:" % (n+1,len(addr_data))),
-							i[1], "(" + label + ")"))
-		if err_flag: msg("\nImport failed"); sys.exit(2)
-		msg(" - OK")
+import mmgen.main
+mmgen.main.main("addrimport")

+ 5 - 108
mmgen-passchg

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C) 2013-2014 by philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -15,114 +15,11 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
 """
-mmgen-passchg: Change a mmgen deterministic wallet's passphrase, label or
+mmgen-passchg: Change an MMGen deterministic wallet's passphrase, label or
                hash preset
 """
 
-import sys
-from mmgen.Opts import *
-from mmgen.util import *
-from mmgen.crypto import *
-import mmgen.config as g
-
-help_data = {
-	'prog_name': g.prog_name,
-	'desc':  """Change the passphrase, hash preset or label of an {}
-                  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'
--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': """
-
-NOTE: The key ID will change if either the passphrase or hash preset are
-      changed
-"""
-}
-
-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 len(cmd_args) != 1:
-	msg("One input file must be specified")
-	sys.exit(2)
-infile = cmd_args[0]
-
-# Old key:
-label,metadata,hash_preset,salt,enc_seed = get_data_from_wallet(infile)
-seed_id,key_id = metadata[:2]
-
-# Repeat on incorrect pw entry
-prompt = "Enter %spassphrase: " % (""
-		if 'keep_old_passphrase' in opts else "old ")
-while True:
-	passwd = get_mmgen_passphrase(prompt,{})
-	key = make_key(passwd, salt, hash_preset)
-	seed = decrypt_seed(enc_seed, key, seed_id, key_id)
-	if seed: break
-
-changed = {}
-
-if 'label' in opts:
-	if opts['label'] != label:
-		msg("Label changed: '%s' -> '%s'" % (label, opts['label']))
-		changed['label'] = True
-	else:
-		msg("Label is unchanged: '%s'" % (label))
-else: opts['label'] = label  # Copy the old label
-
-if 'hash_preset' in opts:
-	if hash_preset != opts['hash_preset']:
-		qmsg("Hash preset has changed (%s -> %s)" %
-			(hash_preset, opts['hash_preset']))
-		changed['preset'] = True
-	else:
-		msg("Hash preset is unchanged")
-else:
-	opts['hash_preset'] = hash_preset
-
-if 'keep_old_passphrase' in opts:
-	msg("Keeping old passphrase by user request")
-else:
-	new_passwd = get_new_passphrase("new passphrase", opts)
-
-	if new_passwd == passwd:
-		qmsg("Passphrase is unchanged")
-	else:
-		qmsg("Passphrase has changed")
-		passwd = new_passwd
-		changed['passwd'] = True
-
-if 'preset' in changed or 'passwd' in changed: # Update key ID, salt
-	qmsg("Will update salt and key ID")
-
-	from hashlib import sha256
-
-	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))
-	key_id = new_key_id
-	enc_seed = encrypt_seed(seed, key)
-elif not 'label' in changed:
-	msg("Data unchanged.  No file will be written")
-	sys.exit(2)
-
-write_wallet_to_file(seed, passwd, key_id, salt, enc_seed, opts)
+import mmgen.main
+mmgen.main.main("passchg")

+ 4 - 1658
mmgen-pywallet

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C) 2013 by philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -15,1664 +15,10 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
 """
 mmgen-pywallet: Dump contents of a bitcoind wallet to file
 """
 
-# Changes by Philemon:
-#   password entry at prompt
-#   dump keys, addresses or keys for specified addresses (output in flat list)
-
-# PyWallet 1.2.1 (Public Domain)
-# http://github.com/joric/pywallet
-# Most of the actual PyWallet code placed in the public domain.
-# PyWallet includes portions of free software, listed below.
-
-# BitcoinTools (wallet.dat handling code, MIT License)
-# https://github.com/gavinandresen/bitcointools
-# Copyright (c) 2010 Gavin Andresen
-
-# python-ecdsa (EC_KEY implementation, MIT License)
-# http://github.com/warner/python-ecdsa
-# "python-ecdsa" Copyright (c) 2010 Brian Warner
-# Portions written in 2005 by Peter Pearson and placed in the public domain.
-
-# SlowAES (aes.py code, Apache 2 License)
-# http://code.google.com/p/slowaes/
-# Copyright (c) 2008, Josh Davis (http://www.josh-davis.org),
-# Alex Martelli (http://www.aleax.it)
-# Ported from C code written by Laurent Haan (http://www.progressive-coding.com)
-
-from bsddb.db import *
-import sys, time
-import json
-import logging
-import struct
-import StringIO
-import traceback
-import socket
-import types
-import string
-import exceptions
-import hashlib
-import random
-import math
-
-import mmgen.config as g
-from mmgen.Opts import *
-from mmgen.util import msg
-
-max_version = 60000
-addrtype = 0
-json_db = {}
-private_keys = []
-password = None
-
-help_data = {
-	'prog_name': g.prog_name,
-	'desc':    "Dump contents of a bitcoind wallet to file",
-	'usage':   "[opts] <bitcoind wallet file>",
-	'options': """
--h, --help             Print this help message
--d, --outdir=       d  Specify an alternate directory 'd' for output
--e, --echo-passphrase  Display passphrase on screen upon entry
--j, --json             Dump wallet in json format
--k, --keys             Dump all private keys (flat list)
--a, --addrs            Dump all addresses (flat list)
--K, --keysforaddrs= f  Dump private keys for addresses listed in file 'f'
--P, --passwd-file=  f  Get passphrase from file 'f'
--S, --stdout           Dump to stdout rather than file
-"""
-}
-
-opts,cmd_args = parse_opts(sys.argv,help_data)
-from mmgen.Opts import warn_incompatible_opts
-warn_incompatible_opts(opts,('json','keys','addrs','keysforaddrs'))
-
-if len(cmd_args) == 1:
-	from mmgen.util import check_infile
-	check_infile(cmd_args[0])
-else:
-	usage(help_data)
-
-if ('json' not in opts and 'keys' not in opts
-		and 'addrs' not in opts and 'keysforaddrs' not in opts):
-			usage(help_data)
-
-
-# from the SlowAES project, http://code.google.com/p/slowaes (aes.py)
-
-def append_PKCS7_padding(s):
-	"""return s padded to a multiple of 16-bytes by PKCS7 padding"""
-	numpads = 16 - (len(s)%16)
-	return s + numpads*chr(numpads)
-
-def strip_PKCS7_padding(s):
-	"""return s stripped of PKCS7 padding"""
-	if len(s)%16 or not s:
-		raise ValueError("String of len %d can't be PCKS7-padded" % len(s))
-	numpads = ord(s[-1])
-	if numpads > 16:
-		raise ValueError("String ending with %r can't be PCKS7-padded" % s[-1])
-	return s[:-numpads]
-
-class AES(object):
-	# valid key sizes
-	keySize = dict(SIZE_128=16, SIZE_192=24, SIZE_256=32)
-
-	# Rijndael S-box
-	sbox =  [0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67,
-			0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59,
-			0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7,
-			0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1,
-			0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05,
-			0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83,
-			0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29,
-			0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b,
-			0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa,
-			0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c,
-			0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc,
-			0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec,
-			0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19,
-			0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee,
-			0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49,
-			0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
-			0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4,
-			0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6,
-			0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70,
-			0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9,
-			0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e,
-			0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1,
-			0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0,
-			0x54, 0xbb, 0x16]
-
-	# Rijndael Inverted S-box
-	rsbox = [0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3,
-			0x9e, 0x81, 0xf3, 0xd7, 0xfb , 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f,
-			0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb , 0x54,
-			0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b,
-			0x42, 0xfa, 0xc3, 0x4e , 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24,
-			0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25 , 0x72, 0xf8,
-			0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d,
-			0x65, 0xb6, 0x92 , 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda,
-			0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84 , 0x90, 0xd8, 0xab,
-			0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3,
-			0x45, 0x06 , 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1,
-			0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b , 0x3a, 0x91, 0x11, 0x41,
-			0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6,
-			0x73 , 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9,
-			0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e , 0x47, 0xf1, 0x1a, 0x71, 0x1d,
-			0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b ,
-			0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0,
-			0xfe, 0x78, 0xcd, 0x5a, 0xf4 , 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07,
-			0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f , 0x60,
-			0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f,
-			0x93, 0xc9, 0x9c, 0xef , 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5,
-			0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61 , 0x17, 0x2b,
-			0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55,
-			0x21, 0x0c, 0x7d]
-
-	def getSBoxValue(self,num):
-		"""Retrieves a given S-Box Value"""
-		return self.sbox[num]
-
-	def getSBoxInvert(self,num):
-		"""Retrieves a given Inverted S-Box Value"""
-		return self.rsbox[num]
-
-	def rotate(self, word):
-		""" Rijndael's key schedule rotate operation.
-
-		Rotate a word eight bits to the left: eg, rotate(1d2c3a4f) == 2c3a4f1d
-		Word is an char list of size 4 (32 bits overall).
-		"""
-		return word[1:] + word[:1]
-
-	# Rijndael Rcon
-	Rcon = [0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36,
-			0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97,
-			0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72,
-			0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66,
-			0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04,
-			0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d,
-			0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3,
-			0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61,
-			0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a,
-			0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40,
-			0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc,
-			0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5,
-			0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a,
-			0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d,
-			0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c,
-			0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35,
-			0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4,
-			0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc,
-			0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08,
-			0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a,
-			0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d,
-			0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2,
-			0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74,
-			0xe8, 0xcb ]
-
-	def getRconValue(self, num):
-		"""Retrieves a given Rcon Value"""
-		return self.Rcon[num]
-
-	def core(self, word, iteration):
-		"""Key schedule core."""
-		# rotate the 32-bit word 8 bits to the left
-		word = self.rotate(word)
-		# apply S-Box substitution on all 4 parts of the 32-bit word
-		for i in range(4):
-			word[i] = self.getSBoxValue(word[i])
-		# XOR the output of the rcon operation with i to the first part
-		# (leftmost) only
-		word[0] = word[0] ^ self.getRconValue(iteration)
-		return word
-
-	def expandKey(self, key, size, expandedKeySize):
-		"""Rijndael's key expansion.
-
-		Expands an 128,192,256 key into an 176,208,240 bytes key
-
-		expandedKey is a char list of large enough size,
-		key is the non-expanded key.
-		"""
-		# current expanded keySize, in bytes
-		currentSize = 0
-		rconIteration = 1
-		expandedKey = [0] * expandedKeySize
-
-		# set the 16, 24, 32 bytes of the expanded key to the input key
-		for j in range(size):
-			expandedKey[j] = key[j]
-		currentSize += size
-
-		while currentSize < expandedKeySize:
-			# assign the previous 4 bytes to the temporary value t
-			t = expandedKey[currentSize-4:currentSize]
-
-			# every 16,24,32 bytes we apply the core schedule to t
-			# and increment rconIteration afterwards
-			if currentSize % size == 0:
-				t = self.core(t, rconIteration)
-				rconIteration += 1
-			# For 256-bit keys, we add an extra sbox to the calculation
-			if size == self.keySize["SIZE_256"] and ((currentSize % size) == 16):
-				for l in range(4): t[l] = self.getSBoxValue(t[l])
-
-			# We XOR t with the four-byte block 16,24,32 bytes before the new
-			# expanded key.  This becomes the next four bytes in the expanded
-			# key.
-			for m in range(4):
-				expandedKey[currentSize] = expandedKey[currentSize - size] ^ \
-						t[m]
-				currentSize += 1
-
-		return expandedKey
-
-	def addRoundKey(self, state, roundKey):
-		"""Adds (XORs) the round key to the state."""
-		for i in range(16):
-			state[i] ^= roundKey[i]
-		return state
-
-	def createRoundKey(self, expandedKey, roundKeyPointer):
-		"""Create a round key.
-		Creates a round key from the given expanded key and the
-		position within the expanded key.
-		"""
-		roundKey = [0] * 16
-		for i in range(4):
-			for j in range(4):
-				roundKey[j*4+i] = expandedKey[roundKeyPointer + i*4 + j]
-		return roundKey
-
-	def galois_multiplication(self, a, b):
-		"""Galois multiplication of 8 bit characters a and b."""
-		p = 0
-		for counter in range(8):
-			if b & 1: p ^= a
-			hi_bit_set = a & 0x80
-			a <<= 1
-			# keep a 8 bit
-			a &= 0xFF
-			if hi_bit_set:
-				a ^= 0x1b
-			b >>= 1
-		return p
-
-	#
-	# substitute all the values from the state with the value in the SBox
-	# using the state value as index for the SBox
-	#
-	def subBytes(self, state, isInv):
-		if isInv: getter = self.getSBoxInvert
-		else: getter = self.getSBoxValue
-		for i in range(16): state[i] = getter(state[i])
-		return state
-
-	# iterate over the 4 rows and call shiftRow() with that row
-	def shiftRows(self, state, isInv):
-		for i in range(4):
-			state = self.shiftRow(state, i*4, i, isInv)
-		return state
-
-	# each iteration shifts the row to the left by 1
-	def shiftRow(self, state, statePointer, nbr, isInv):
-		for i in range(nbr):
-			if isInv:
-				state[statePointer:statePointer+4] = \
-						state[statePointer+3:statePointer+4] + \
-						state[statePointer:statePointer+3]
-			else:
-				state[statePointer:statePointer+4] = \
-						state[statePointer+1:statePointer+4] + \
-						state[statePointer:statePointer+1]
-		return state
-
-	# galois multiplication of the 4x4 matrix
-	def mixColumns(self, state, isInv):
-		# iterate over the 4 columns
-		for i in range(4):
-			# construct one column by slicing over the 4 rows
-			column = state[i:i+16:4]
-			# apply the mixColumn on one column
-			column = self.mixColumn(column, isInv)
-			# put the values back into the state
-			state[i:i+16:4] = column
-
-		return state
-
-	# galois multiplication of 1 column of the 4x4 matrix
-	def mixColumn(self, column, isInv):
-		if isInv: mult = [14, 9, 13, 11]
-		else: mult = [2, 1, 1, 3]
-		cpy = list(column)
-		g = self.galois_multiplication
-
-		column[0] = g(cpy[0], mult[0]) ^ g(cpy[3], mult[1]) ^ \
-					g(cpy[2], mult[2]) ^ g(cpy[1], mult[3])
-		column[1] = g(cpy[1], mult[0]) ^ g(cpy[0], mult[1]) ^ \
-					g(cpy[3], mult[2]) ^ g(cpy[2], mult[3])
-		column[2] = g(cpy[2], mult[0]) ^ g(cpy[1], mult[1]) ^ \
-					g(cpy[0], mult[2]) ^ g(cpy[3], mult[3])
-		column[3] = g(cpy[3], mult[0]) ^ g(cpy[2], mult[1]) ^ \
-					g(cpy[1], mult[2]) ^ g(cpy[0], mult[3])
-		return column
-
-	# applies the 4 operations of the forward round in sequence
-	def aes_round(self, state, roundKey):
-		state = self.subBytes(state, False)
-		state = self.shiftRows(state, False)
-		state = self.mixColumns(state, False)
-		state = self.addRoundKey(state, roundKey)
-		return state
-
-	# applies the 4 operations of the inverse round in sequence
-	def aes_invRound(self, state, roundKey):
-		state = self.shiftRows(state, True)
-		state = self.subBytes(state, True)
-		state = self.addRoundKey(state, roundKey)
-		state = self.mixColumns(state, True)
-		return state
-
-	# Perform the initial operations, the standard round, and the final
-	# operations of the forward aes, creating a round key for each round
-	def aes_main(self, state, expandedKey, nbrRounds):
-		state = self.addRoundKey(state, self.createRoundKey(expandedKey, 0))
-		i = 1
-		while i < nbrRounds:
-			state = self.aes_round(state, self.createRoundKey(expandedKey, 16*i))
-			i += 1
-		state = self.subBytes(state, False)
-		state = self.shiftRows(state, False)
-		state = self.addRoundKey(state, self.createRoundKey(expandedKey, 16*nbrRounds))
-		return state
-
-	# Perform the initial operations, the standard round, and the final
-	# operations of the inverse aes, creating a round key for each round
-	def aes_invMain(self, state, expandedKey, nbrRounds):
-		state = self.addRoundKey(state, self.createRoundKey(expandedKey, 16*nbrRounds))
-		i = nbrRounds - 1
-		while i > 0:
-			state = self.aes_invRound(state, self.createRoundKey(expandedKey, 16*i))
-			i -= 1
-		state = self.shiftRows(state, True)
-		state = self.subBytes(state, True)
-		state = self.addRoundKey(state, self.createRoundKey(expandedKey, 0))
-		return state
-
-	# encrypts a 128 bit input block against the given key of size specified
-	def encrypt(self, iput, key, size):
-		output = [0] * 16
-		# the number of rounds
-		nbrRounds = 0
-		# the 128 bit block to encode
-		block = [0] * 16
-		# set the number of rounds
-		if size == self.keySize["SIZE_128"]: nbrRounds = 10
-		elif size == self.keySize["SIZE_192"]: nbrRounds = 12
-		elif size == self.keySize["SIZE_256"]: nbrRounds = 14
-		else: return None
-
-		# the expanded keySize
-		expandedKeySize = 16*(nbrRounds+1)
-
-		# Set the block values, for the block:
-		# a0,0 a0,1 a0,2 a0,3
-		# a1,0 a1,1 a1,2 a1,3
-		# a2,0 a2,1 a2,2 a2,3
-		# a3,0 a3,1 a3,2 a3,3
-		# the mapping order is a0,0 a1,0 a2,0 a3,0 a0,1 a1,1 ... a2,3 a3,3
-		#
-		# iterate over the columns
-		for i in range(4):
-			# iterate over the rows
-			for j in range(4):
-				block[(i+(j*4))] = iput[(i*4)+j]
-
-		# expand the key into an 176, 208, 240 bytes key
-		# the expanded key
-		expandedKey = self.expandKey(key, size, expandedKeySize)
-
-		# encrypt the block using the expandedKey
-		block = self.aes_main(block, expandedKey, nbrRounds)
-
-		# unmap the block again into the output
-		for k in range(4):
-			# iterate over the rows
-			for l in range(4):
-				output[(k*4)+l] = block[(k+(l*4))]
-		return output
-
-	# decrypts a 128 bit input block against the given key of size specified
-	def decrypt(self, iput, key, size):
-		output = [0] * 16
-		# the number of rounds
-		nbrRounds = 0
-		# the 128 bit block to decode
-		block = [0] * 16
-		# set the number of rounds
-		if size == self.keySize["SIZE_128"]: nbrRounds = 10
-		elif size == self.keySize["SIZE_192"]: nbrRounds = 12
-		elif size == self.keySize["SIZE_256"]: nbrRounds = 14
-		else: return None
-
-		# the expanded keySize
-		expandedKeySize = 16*(nbrRounds+1)
-
-		# Set the block values, for the block:
-		# a0,0 a0,1 a0,2 a0,3
-		# a1,0 a1,1 a1,2 a1,3
-		# a2,0 a2,1 a2,2 a2,3
-		# a3,0 a3,1 a3,2 a3,3
-		# the mapping order is a0,0 a1,0 a2,0 a3,0 a0,1 a1,1 ... a2,3 a3,3
-
-		# iterate over the columns
-		for i in range(4):
-			# iterate over the rows
-			for j in range(4):
-				block[(i+(j*4))] = iput[(i*4)+j]
-		# expand the key into an 176, 208, 240 bytes key
-		expandedKey = self.expandKey(key, size, expandedKeySize)
-		# decrypt the block using the expandedKey
-		block = self.aes_invMain(block, expandedKey, nbrRounds)
-		# unmap the block again into the output
-		for k in range(4):
-			# iterate over the rows
-			for l in range(4):
-				output[(k*4)+l] = block[(k+(l*4))]
-		return output
-
-class AESModeOfOperation(object):
-
-	aes = AES()
-
-	# structure of supported modes of operation
-	modeOfOperation = dict(OFB=0, CFB=1, CBC=2)
-
-	# converts a 16 character string into a number array
-	def convertString(self, string, start, end, mode):
-		if end - start > 16: end = start + 16
-		if mode == self.modeOfOperation["CBC"]: ar = [0] * 16
-		else: ar = []
-
-		i = start
-		j = 0
-		while len(ar) < end - start:
-			ar.append(0)
-		while i < end:
-			ar[j] = ord(string[i])
-			j += 1
-			i += 1
-		return ar
-
-	# Mode of Operation Encryption
-	# stringIn - Input String
-	# mode - mode of type modeOfOperation
-	# hexKey - a hex key of the bit length size
-	# size - the bit length of the key
-	# hexIV - the 128 bit hex Initilization Vector
-	def encrypt(self, stringIn, mode, key, size, IV):
-		if len(key) % size:
-			return None
-		if len(IV) % 16:
-			return None
-		# the AES input/output
-		plaintext = []
-		iput = [0] * 16
-		output = []
-		ciphertext = [0] * 16
-		# the output cipher string
-		cipherOut = []
-		# char firstRound
-		firstRound = True
-		if stringIn != None:
-			for j in range(int(math.ceil(float(len(stringIn))/16))):
-				start = j*16
-				end = j*16+16
-				if  end > len(stringIn):
-					end = len(stringIn)
-				plaintext = self.convertString(stringIn, start, end, mode)
-				# print 'PT@%s:%s' % (j, plaintext)
-				if mode == self.modeOfOperation["CFB"]:
-					if firstRound:
-						output = self.aes.encrypt(IV, key, size)
-						firstRound = False
-					else:
-						output = self.aes.encrypt(iput, key, size)
-					for i in range(16):
-						if len(plaintext)-1 < i:
-							ciphertext[i] = 0 ^ output[i]
-						elif len(output)-1 < i:
-							ciphertext[i] = plaintext[i] ^ 0
-						elif len(plaintext)-1 < i and len(output) < i:
-							ciphertext[i] = 0 ^ 0
-						else:
-							ciphertext[i] = plaintext[i] ^ output[i]
-					for k in range(end-start):
-						cipherOut.append(ciphertext[k])
-					iput = ciphertext
-				elif mode == self.modeOfOperation["OFB"]:
-					if firstRound:
-						output = self.aes.encrypt(IV, key, size)
-						firstRound = False
-					else:
-						output = self.aes.encrypt(iput, key, size)
-					for i in range(16):
-						if len(plaintext)-1 < i:
-							ciphertext[i] = 0 ^ output[i]
-						elif len(output)-1 < i:
-							ciphertext[i] = plaintext[i] ^ 0
-						elif len(plaintext)-1 < i and len(output) < i:
-							ciphertext[i] = 0 ^ 0
-						else:
-							ciphertext[i] = plaintext[i] ^ output[i]
-					for k in range(end-start):
-						cipherOut.append(ciphertext[k])
-					iput = output
-				elif mode == self.modeOfOperation["CBC"]:
-					for i in range(16):
-						if firstRound:
-							iput[i] =  plaintext[i] ^ IV[i]
-						else:
-							iput[i] =  plaintext[i] ^ ciphertext[i]
-					# print 'IP@%s:%s' % (j, iput)
-					firstRound = False
-					ciphertext = self.aes.encrypt(iput, key, size)
-					# always 16 bytes because of the padding for CBC
-					for k in range(16):
-						cipherOut.append(ciphertext[k])
-		return mode, len(stringIn), cipherOut
-
-	# Mode of Operation Decryption
-	# cipherIn - Encrypted String
-	# originalsize - The unencrypted string length - required for CBC
-	# mode - mode of type modeOfOperation
-	# key - a number array of the bit length size
-	# size - the bit length of the key
-	# IV - the 128 bit number array Initilization Vector
-	def decrypt(self, cipherIn, originalsize, mode, key, size, IV):
-		# cipherIn = unescCtrlChars(cipherIn)
-		if len(key) % size:
-			return None
-		if len(IV) % 16:
-			return None
-		# the AES input/output
-		ciphertext = []
-		iput = []
-		output = []
-		plaintext = [0] * 16
-		# the output plain text string
-		stringOut = ''
-		# char firstRound
-		firstRound = True
-		if cipherIn != None:
-			for j in range(int(math.ceil(float(len(cipherIn))/16))):
-				start = j*16
-				end = j*16+16
-				if j*16+16 > len(cipherIn):
-					end = len(cipherIn)
-				ciphertext = cipherIn[start:end]
-				if mode == self.modeOfOperation["CFB"]:
-					if firstRound:
-						output = self.aes.encrypt(IV, key, size)
-						firstRound = False
-					else:
-						output = self.aes.encrypt(iput, key, size)
-					for i in range(16):
-						if len(output)-1 < i:
-							plaintext[i] = 0 ^ ciphertext[i]
-						elif len(ciphertext)-1 < i:
-							plaintext[i] = output[i] ^ 0
-						elif len(output)-1 < i and len(ciphertext) < i:
-							plaintext[i] = 0 ^ 0
-						else:
-							plaintext[i] = output[i] ^ ciphertext[i]
-					for k in range(end-start):
-						stringOut += chr(plaintext[k])
-					iput = ciphertext
-				elif mode == self.modeOfOperation["OFB"]:
-					if firstRound:
-						output = self.aes.encrypt(IV, key, size)
-						firstRound = False
-					else:
-						output = self.aes.encrypt(iput, key, size)
-					for i in range(16):
-						if len(output)-1 < i:
-							plaintext[i] = 0 ^ ciphertext[i]
-						elif len(ciphertext)-1 < i:
-							plaintext[i] = output[i] ^ 0
-						elif len(output)-1 < i and len(ciphertext) < i:
-							plaintext[i] = 0 ^ 0
-						else:
-							plaintext[i] = output[i] ^ ciphertext[i]
-					for k in range(end-start):
-						stringOut += chr(plaintext[k])
-					iput = output
-				elif mode == self.modeOfOperation["CBC"]:
-					output = self.aes.decrypt(ciphertext, key, size)
-					for i in range(16):
-						if firstRound:
-							plaintext[i] = IV[i] ^ output[i]
-						else:
-							plaintext[i] = iput[i] ^ output[i]
-					firstRound = False
-					if originalsize is not None and originalsize < end:
-						for k in range(originalsize-start):
-							stringOut += chr(plaintext[k])
-					else:
-						for k in range(end-start):
-							stringOut += chr(plaintext[k])
-					iput = ciphertext
-		return stringOut
-
-# end of aes.py code
-
-# pywallet crypter implementation
-
-crypter = None
-
-try:
-	from Crypto.Cipher import AES
-	crypter = 'pycrypto'
-except:
-	pass
-
-class Crypter_pycrypto( object ):
-	def SetKeyFromPassphrase(self, vKeyData, vSalt, nDerivIterations, nDerivationMethod):
-		if nDerivationMethod != 0:
-			return 0
-		data = vKeyData + vSalt
-		for i in xrange(nDerivIterations):
-			data = hashlib.sha512(data).digest()
-		self.SetKey(data[0:32])
-		self.SetIV(data[32:32+16])
-		return len(data)
-
-	def SetKey(self, key):
-		self.chKey = key
-
-	def SetIV(self, iv):
-		self.chIV = iv[0:16]
-
-	def Encrypt(self, data):
-		return AES.new(self.chKey,AES.MODE_CBC,self.chIV).encrypt(data)[0:32]
-
-	def Decrypt(self, data):
-		return AES.new(self.chKey,AES.MODE_CBC,self.chIV).decrypt(data)[0:32]
-
-try:
-	if not crypter:
-		import ctypes
-		import ctypes.util
-		ssl = ctypes.cdll.LoadLibrary (ctypes.util.find_library ('ssl') or 'libeay32')
-		crypter = 'ssl'
-except:
-	pass
-
-class Crypter_ssl(object):
-	def __init__(self):
-		self.chKey = ctypes.create_string_buffer (32)
-		self.chIV = ctypes.create_string_buffer (16)
-
-	def SetKeyFromPassphrase(self, vKeyData, vSalt, nDerivIterations, nDerivationMethod):
-		if nDerivationMethod != 0:
-			return 0
-		strKeyData = ctypes.create_string_buffer (vKeyData)
-		chSalt = ctypes.create_string_buffer (vSalt)
-		return ssl.EVP_BytesToKey(ssl.EVP_aes_256_cbc(), ssl.EVP_sha512(), chSalt, strKeyData,
-			len(vKeyData), nDerivIterations, ctypes.byref(self.chKey), ctypes.byref(self.chIV))
-
-	def SetKey(self, key):
-		self.chKey = ctypes.create_string_buffer(key)
-
-	def SetIV(self, iv):
-		self.chIV = ctypes.create_string_buffer(iv)
-
-	def Encrypt(self, data):
-		buf = ctypes.create_string_buffer(len(data) + 16)
-		written = ctypes.c_int(0)
-		final = ctypes.c_int(0)
-		ctx = ssl.EVP_CIPHER_CTX_new()
-		ssl.EVP_CIPHER_CTX_init(ctx)
-		ssl.EVP_EncryptInit_ex(ctx, ssl.EVP_aes_256_cbc(), None, self.chKey, self.chIV)
-		ssl.EVP_EncryptUpdate(ctx, buf, ctypes.byref(written), data, len(data))
-		output = buf.raw[:written.value]
-		ssl.EVP_EncryptFinal_ex(ctx, buf, ctypes.byref(final))
-		output += buf.raw[:final.value]
-		return output
-
-	def Decrypt(self, data):
-		buf = ctypes.create_string_buffer(len(data) + 16)
-		written = ctypes.c_int(0)
-		final = ctypes.c_int(0)
-		ctx = ssl.EVP_CIPHER_CTX_new()
-		ssl.EVP_CIPHER_CTX_init(ctx)
-		ssl.EVP_DecryptInit_ex(ctx, ssl.EVP_aes_256_cbc(), None, self.chKey, self.chIV)
-		ssl.EVP_DecryptUpdate(ctx, buf, ctypes.byref(written), data, len(data))
-		output = buf.raw[:written.value]
-		ssl.EVP_DecryptFinal_ex(ctx, buf, ctypes.byref(final))
-		output += buf.raw[:final.value]
-		return output
-
-class Crypter_pure(object):
-	def __init__(self):
-		self.m = AESModeOfOperation()
-		self.cbc = self.m.modeOfOperation["CBC"]
-		self.sz = self.m.aes.keySize["SIZE_256"]
-
-	def SetKeyFromPassphrase(self, vKeyData, vSalt, nDerivIterations, nDerivationMethod):
-		if nDerivationMethod != 0:
-			return 0
-		data = vKeyData + vSalt
-		for i in xrange(nDerivIterations):
-			data = hashlib.sha512(data).digest()
-		self.SetKey(data[0:32])
-		self.SetIV(data[32:32+16])
-		return len(data)
-
-	def SetKey(self, key):
-		self.chKey = [ord(i) for i in key]
-
-	def SetIV(self, iv):
-		self.chIV = [ord(i) for i in iv]
-
-	def Encrypt(self, data):
-		mode, size, cypher = self.m.encrypt(data, self.cbc, self.chKey, self.sz, self.chIV)
-		return ''.join(map(chr, cypher))
-
-	def Decrypt(self, data):
-		chData = [ord(i) for i in data]
-		return self.m.decrypt(chData, self.sz, self.cbc, self.chKey, self.sz, self.chIV)
-
-# secp256k1
-
-_p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2FL
-_r = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141L
-_b = 0x0000000000000000000000000000000000000000000000000000000000000007L
-_a = 0x0000000000000000000000000000000000000000000000000000000000000000L
-_Gx = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798L
-_Gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8L
-
-# python-ecdsa code (EC_KEY implementation)
-
-class CurveFp( object ):
-	def __init__( self, p, a, b ):
-		self.__p = p
-		self.__a = a
-		self.__b = b
-
-	def p( self ):
-		return self.__p
-
-	def a( self ):
-		return self.__a
-
-	def b( self ):
-		return self.__b
-
-	def contains_point( self, x, y ):
-		return ( y * y - ( x * x * x + self.__a * x + self.__b ) ) % self.__p == 0
-
-class Point( object ):
-	def __init__( self, curve, x, y, order = None ):
-		self.__curve = curve
-		self.__x = x
-		self.__y = y
-		self.__order = order
-		if self.__curve: assert self.__curve.contains_point( x, y )
-		if order: assert self * order == INFINITY
-
-	def __add__( self, other ):
-		if other == INFINITY: return self
-		if self == INFINITY: return other
-		assert self.__curve == other.__curve
-		if self.__x == other.__x:
-			if ( self.__y + other.__y ) % self.__curve.p() == 0:
-				return INFINITY
-			else:
-				return self.double()
-
-		p = self.__curve.p()
-		l = ( ( other.__y - self.__y ) * \
-					inverse_mod( other.__x - self.__x, p ) ) % p
-		x3 = ( l * l - self.__x - other.__x ) % p
-		y3 = ( l * ( self.__x - x3 ) - self.__y ) % p
-		return Point( self.__curve, x3, y3 )
-
-	def __mul__( self, other ):
-		def leftmost_bit( x ):
-			assert x > 0
-			result = 1L
-			while result <= x: result = 2 * result
-			return result / 2
-
-		e = other
-		if self.__order: e = e % self.__order
-		if e == 0: return INFINITY
-		if self == INFINITY: return INFINITY
-		assert e > 0
-		e3 = 3 * e
-		negative_self = Point( self.__curve, self.__x, -self.__y, self.__order )
-		i = leftmost_bit( e3 ) / 2
-		result = self
-		while i > 1:
-			result = result.double()
-			if ( e3 & i ) != 0 and ( e & i ) == 0: result = result + self
-			if ( e3 & i ) == 0 and ( e & i ) != 0: result = result + negative_self
-			i = i / 2
-		return result
-
-	def __rmul__( self, other ):
-		return self * other
-
-	def __str__( self ):
-		if self == INFINITY: return "infinity"
-		return "(%d,%d)" % ( self.__x, self.__y )
-
-	def double( self ):
-		if self == INFINITY:
-			return INFINITY
-
-		p = self.__curve.p()
-		a = self.__curve.a()
-		l = ( ( 3 * self.__x * self.__x + a ) * \
-					inverse_mod( 2 * self.__y, p ) ) % p
-		x3 = ( l * l - 2 * self.__x ) % p
-		y3 = ( l * ( self.__x - x3 ) - self.__y ) % p
-		return Point( self.__curve, x3, y3 )
-
-	def x( self ):
-		return self.__x
-
-	def y( self ):
-		return self.__y
-
-	def curve( self ):
-		return self.__curve
-
-	def order( self ):
-		return self.__order
-
-INFINITY = Point( None, None, None )
-
-def inverse_mod( a, m ):
-	if a < 0 or m <= a: a = a % m
-	c, d = a, m
-	uc, vc, ud, vd = 1, 0, 0, 1
-	while c != 0:
-		q, c, d = divmod( d, c ) + ( c, )
-		uc, vc, ud, vd = ud - q*uc, vd - q*vc, uc, vc
-	assert d == 1
-	if ud > 0: return ud
-	else: return ud + m
-
-class Signature( object ):
-	def __init__( self, r, s ):
-		self.r = r
-		self.s = s
-
-class Public_key( object ):
-	def __init__( self, generator, point ):
-		self.curve = generator.curve()
-		self.generator = generator
-		self.point = point
-		n = generator.order()
-		if not n:
-			raise RuntimeError, "Generator point must have order."
-		if not n * point == INFINITY:
-			raise RuntimeError, "Generator point order is bad."
-		if point.x() < 0 or n <= point.x() or point.y() < 0 or n <= point.y():
-			raise RuntimeError, "Generator point has x or y out of range."
-
-	def verifies( self, hash, signature ):
-		G = self.generator
-		n = G.order()
-		r = signature.r
-		s = signature.s
-		if r < 1 or r > n-1: return False
-		if s < 1 or s > n-1: return False
-		c = inverse_mod( s, n )
-		u1 = ( hash * c ) % n
-		u2 = ( r * c ) % n
-		xy = u1 * G + u2 * self.point
-		v = xy.x() % n
-		return v == r
-
-class Private_key( object ):
-	def __init__( self, public_key, secret_multiplier ):
-		self.public_key = public_key
-		self.secret_multiplier = secret_multiplier
-
-	def der( self ):
-		hex_der_key = '06052b8104000a30740201010420' + \
-			'%064x' % self.secret_multiplier + \
-			'a00706052b8104000aa14403420004' + \
-			'%064x' % self.public_key.point.x() + \
-			'%064x' % self.public_key.point.y()
-		return hex_der_key.decode('hex')
-
-	def sign( self, hash, random_k ):
-		G = self.public_key.generator
-		n = G.order()
-		k = random_k % n
-		p1 = k * G
-		r = p1.x()
-		if r == 0: raise RuntimeError, "amazingly unlucky random number r"
-		s = ( inverse_mod( k, n ) * \
-					( hash + ( self.secret_multiplier * r ) % n ) ) % n
-		if s == 0: raise RuntimeError, "amazingly unlucky random number s"
-		return Signature( r, s )
-
-class EC_KEY(object):
-	def __init__( self, secret ):
-		curve = CurveFp( _p, _a, _b )
-		generator = Point( curve, _Gx, _Gy, _r )
-		self.pubkey = Public_key( generator, generator * secret )
-		self.privkey = Private_key( self.pubkey, secret )
-		self.secret = secret
-
-# end of python-ecdsa code
-
-# pywallet openssl private key implementation
-
-def i2d_ECPrivateKey(pkey, compressed=False):
-	if compressed:
-		key = '3081d30201010420' + \
-			'%064x' % pkey.secret + \
-			'a081a53081a2020101302c06072a8648ce3d0101022100' + \
-			'%064x' % _p + \
-			'3006040100040107042102' + \
-			'%064x' % _Gx + \
-			'022100' + \
-			'%064x' % _r + \
-			'020101a124032200'
-	else:
-		key = '308201130201010420' + \
-			'%064x' % pkey.secret + \
-			'a081a53081a2020101302c06072a8648ce3d0101022100' + \
-			'%064x' % _p + \
-			'3006040100040107044104' + \
-			'%064x' % _Gx + \
-			'%064x' % _Gy + \
-			'022100' + \
-			'%064x' % _r + \
-			'020101a144034200'
-
-	return key.decode('hex') + i2o_ECPublicKey(pkey, compressed)
-
-def i2o_ECPublicKey(pkey, compressed=False):
-	# public keys are 65 bytes long (520 bits)
-	# 0x04 + 32-byte X-coordinate + 32-byte Y-coordinate
-	# 0x00 = point at infinity, 0x02 and 0x03 = compressed, 0x04 = uncompressed
-	# compressed keys: <sign> <x> where <sign> is 0x02 if y is even and 0x03 if y is odd
-	if compressed:
-		if pkey.pubkey.point.y() & 1:
-			key = '03' + '%064x' % pkey.pubkey.point.x()
-		else:
-			key = '02' + '%064x' % pkey.pubkey.point.x()
-	else:
-		key = '04' + \
-			'%064x' % pkey.pubkey.point.x() + \
-			'%064x' % pkey.pubkey.point.y()
-
-	return key.decode('hex')
-
-# bitcointools hashes and base58 implementation
-
-def hash_160(public_key):
-	md = hashlib.new('ripemd160')
-	md.update(hashlib.sha256(public_key).digest())
-	return md.digest()
-
-def public_key_to_bc_address(public_key):
-	h160 = hash_160(public_key)
-	return hash_160_to_bc_address(h160)
-
-def hash_160_to_bc_address(h160):
-	vh160 = chr(addrtype) + h160
-	h = Hash(vh160)
-	addr = vh160 + h[0:4]
-	return b58encode(addr)
-
-def bc_address_to_hash_160(addr):
-	bytes = b58decode(addr, 25)
-	return bytes[1:21]
-
-__b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
-__b58base = len(__b58chars)
-
-def b58encode(v):
-	""" encode v, which is a string of bytes, to base58.
-	"""
-
-	long_value = 0L
-	for (i, c) in enumerate(v[::-1]):
-		long_value += (256**i) * ord(c)
-
-	result = ''
-	while long_value >= __b58base:
-		div, mod = divmod(long_value, __b58base)
-		result = __b58chars[mod] + result
-		long_value = div
-	result = __b58chars[long_value] + result
-
-	# Bitcoin does a little leading-zero-compression:
-	# leading 0-bytes in the input become leading-1s
-	nPad = 0
-	for c in v:
-		if c == '\0': nPad += 1
-		else: break
-
-	return (__b58chars[0]*nPad) + result
-
-def b58decode(v, length):
-	""" decode v into a string of len bytes
-	"""
-	long_value = 0L
-	for (i, c) in enumerate(v[::-1]):
-		long_value += __b58chars.find(c) * (__b58base**i)
-
-	result = ''
-	while long_value >= 256:
-		div, mod = divmod(long_value, 256)
-		result = chr(mod) + result
-		long_value = div
-	result = chr(long_value) + result
-
-	nPad = 0
-	for c in v:
-		if c == __b58chars[0]: nPad += 1
-		else: break
-
-	result = chr(0)*nPad + result
-	if length is not None and len(result) != length:
-		return None
-
-	return result
-
-# end of bitcointools base58 implementation
-
-
-# address handling code
-
-def Hash(data):
-	return hashlib.sha256(hashlib.sha256(data).digest()).digest()
-
-def EncodeBase58Check(secret):
-	hash = Hash(secret)
-	return b58encode(secret + hash[0:4])
-
-def DecodeBase58Check(sec):
-	vchRet = b58decode(sec, None)
-	secret = vchRet[0:-4]
-	csum = vchRet[-4:]
-	hash = Hash(secret)
-	cs32 = hash[0:4]
-	if cs32 != csum:
-		return None
-	else:
-		return secret
-
-def PrivKeyToSecret(privkey):
-	if len(privkey) == 279:
-		return privkey[9:9+32]
-	else:
-		return privkey[8:8+32]
-
-def SecretToASecret(secret, compressed=False):
-	vchIn = chr((addrtype+128)&255) + secret
-	if compressed: vchIn += '\01'
-	return EncodeBase58Check(vchIn)
-
-def ASecretToSecret(sec):
-	vch = DecodeBase58Check(sec)
-	if vch and vch[0] == chr((addrtype+128)&255):
-		return vch[1:]
-	else:
-		return False
-
-def regenerate_key(sec):
-	b = ASecretToSecret(sec)
-	if not b:
-		return False
-	b = b[0:32]
-	secret = int('0x' + b.encode('hex'), 16)
-	return EC_KEY(secret)
-
-def GetPubKey(pkey, compressed=False):
-	return i2o_ECPublicKey(pkey, compressed)
-
-def GetPrivKey(pkey, compressed=False):
-	return i2d_ECPrivateKey(pkey, compressed)
-
-def GetSecret(pkey):
-	return ('%064x' % pkey.secret).decode('hex')
-
-def is_compressed(sec):
-	b = ASecretToSecret(sec)
-	return len(b) == 33
-
-# bitcointools wallet.dat handling code
-
-def create_env(db_dir):
-	db_env = DBEnv(0)
-	r = db_env.open(db_dir, (DB_CREATE|DB_INIT_LOCK|DB_INIT_LOG|DB_INIT_MPOOL|DB_INIT_TXN|DB_THREAD|DB_RECOVER))
-	return db_env
-
-def parse_CAddress(vds):
-	d = {'ip':'0.0.0.0','port':0,'nTime': 0}
-	try:
-		d['nVersion'] = vds.read_int32()
-		d['nTime'] = vds.read_uint32()
-		d['nServices'] = vds.read_uint64()
-		d['pchReserved'] = vds.read_bytes(12)
-		d['ip'] = socket.inet_ntoa(vds.read_bytes(4))
-		d['port'] = vds.read_uint16()
-	except:
-		pass
-	return d
-
-def deserialize_CAddress(d):
-	return d['ip']+":"+str(d['port'])
-
-def parse_BlockLocator(vds):
-	d = { 'hashes' : [] }
-	nHashes = vds.read_compact_size()
-	for i in xrange(nHashes):
-		d['hashes'].append(vds.read_bytes(32))
-		return d
-
-def deserialize_BlockLocator(d):
-  result = "Block Locator top: "+d['hashes'][0][::-1].encode('hex_codec')
-  return result
-
-def parse_setting(setting, vds):
-	if setting[0] == "f":    # flag (boolean) settings
-		return str(vds.read_boolean())
-	elif setting[0:4] == "addr": # CAddress
-		d = parse_CAddress(vds)
-		return deserialize_CAddress(d)
-	elif setting == "nTransactionFee":
-		return vds.read_int64()
-	elif setting == "nLimitProcessors":
-		return vds.read_int32()
-	return 'unknown setting'
-
-class SerializationError(Exception):
-	""" Thrown when there's a problem deserializing or serializing """
-
-class BCDataStream(object):
-	def __init__(self):
-		self.input = None
-		self.read_cursor = 0
-
-	def clear(self):
-		self.input = None
-		self.read_cursor = 0
-
-	def write(self, bytes):    # Initialize with string of bytes
-		if self.input is None:
-			self.input = bytes
-		else:
-			self.input += bytes
-
-	def map_file(self, file, start):    # Initialize with bytes from file
-		self.input = mmap.mmap(file.fileno(), 0, access=mmap.ACCESS_READ)
-		self.read_cursor = start
-	def seek_file(self, position):
-		self.read_cursor = position
-	def close_file(self):
-		self.input.close()
-
-	def read_string(self):
-		# Strings are encoded depending on length:
-		# 0 to 252 :    1-byte-length followed by bytes (if any)
-		# 253 to 65,535 : byte'253' 2-byte-length followed by bytes
-		# 65,536 to 4,294,967,295 : byte '254' 4-byte-length followed by bytes
-		# ... and the Bitcoin client is coded to understand:
-		# greater than 4,294,967,295 : byte '255' 8-byte-length followed by bytes of string
-		# ... but I don't think it actually handles any strings that big.
-		if self.input is None:
-			raise SerializationError("call write(bytes) before trying to deserialize")
-
-		try:
-			length = self.read_compact_size()
-		except IndexError:
-			raise SerializationError("attempt to read past end of buffer")
-
-		return self.read_bytes(length)
-
-	def write_string(self, string):
-		# Length-encoded as with read-string
-		self.write_compact_size(len(string))
-		self.write(string)
-
-	def read_bytes(self, length):
-		try:
-			result = self.input[self.read_cursor:self.read_cursor+length]
-			self.read_cursor += length
-			return result
-		except IndexError:
-			raise SerializationError("attempt to read past end of buffer")
-
-		return ''
-
-	def read_boolean(self): return self.read_bytes(1)[0] != chr(0)
-	def read_int16(self): return self._read_num('<h')
-	def read_uint16(self): return self._read_num('<H')
-	def read_int32(self): return self._read_num('<i')
-	def read_uint32(self): return self._read_num('<I')
-	def read_int64(self): return self._read_num('<q')
-	def read_uint64(self): return self._read_num('<Q')
-
-	def write_boolean(self, val): return self.write(chr(1) if val else chr(0))
-	def write_int16(self, val): return self._write_num('<h', val)
-	def write_uint16(self, val): return self._write_num('<H', val)
-	def write_int32(self, val): return self._write_num('<i', val)
-	def write_uint32(self, val): return self._write_num('<I', val)
-	def write_int64(self, val): return self._write_num('<q', val)
-	def write_uint64(self, val): return self._write_num('<Q', val)
-
-	def read_compact_size(self):
-		size = ord(self.input[self.read_cursor])
-		self.read_cursor += 1
-		if size == 253:
-			size = self._read_num('<H')
-		elif size == 254:
-			size = self._read_num('<I')
-		elif size == 255:
-			size = self._read_num('<Q')
-		return size
-
-	def write_compact_size(self, size):
-		if size < 0:
-			raise SerializationError("attempt to write size < 0")
-		elif size < 253:
-			self.write(chr(size))
-		elif size < 2**16:
-			self.write('\xfd')
-			self._write_num('<H', size)
-		elif size < 2**32:
-			self.write('\xfe')
-			self._write_num('<I', size)
-		elif size < 2**64:
-			self.write('\xff')
-			self._write_num('<Q', size)
-
-	def _read_num(self, format):
-		(i,) = struct.unpack_from(format, self.input, self.read_cursor)
-		self.read_cursor += struct.calcsize(format)
-		return i
-
-	def _write_num(self, format, num):
-		s = struct.pack(format, num)
-		self.write(s)
-
-def open_wallet(db_env, db_file="wallet.dat", writable=False):
-	db = DB(db_env)
-	flags = DB_THREAD | (DB_CREATE if writable else DB_RDONLY)
-	try:
-		r = db.open(db_file, "main", DB_BTREE, flags)
-	except DBError:
-		r = True
-
-	if r is not None:
-		logging.error("Couldn't open " + db_file + "/main. Try quitting Bitcoin and running this again.")
-		sys.exit(1)
-
-	return db
-
-def parse_wallet(db, item_callback):
-	kds = BCDataStream()
-	vds = BCDataStream()
-
-	for (key, value) in db.items():
-		d = { }
-
-		kds.clear(); kds.write(key)
-		vds.clear(); vds.write(value)
-
-		type = kds.read_string()
-
-		d["__key__"] = key
-		d["__value__"] = value
-		d["__type__"] = type
-
-		try:
-			if type == "tx":
-				d["tx_id"] = kds.read_bytes(32)
-			elif type == "name":
-				d['hash'] = kds.read_string()
-				d['name'] = vds.read_string()
-			elif type == "version":
-				d['version'] = vds.read_uint32()
-			elif type == "minversion":
-				d['minversion'] = vds.read_uint32()
-			elif type == "setting":
-				d['setting'] = kds.read_string()
-				d['value'] = parse_setting(d['setting'], vds)
-			elif type == "key":
-				d['public_key'] = kds.read_bytes(kds.read_compact_size())
-				d['private_key'] = vds.read_bytes(vds.read_compact_size())
-			elif type == "wkey":
-				d['public_key'] = kds.read_bytes(kds.read_compact_size())
-				d['private_key'] = vds.read_bytes(vds.read_compact_size())
-				d['created'] = vds.read_int64()
-				d['expires'] = vds.read_int64()
-				d['comment'] = vds.read_string()
-			elif type == "ckey":
-				d['public_key'] = kds.read_bytes(kds.read_compact_size())
-				d['crypted_key'] = vds.read_bytes(vds.read_compact_size())
-			elif type == "mkey":
-				d['nID'] = kds.read_int32()
-				d['crypted_key'] = vds.read_bytes(vds.read_compact_size())
-				d['salt'] = vds.read_bytes(vds.read_compact_size())
-				d['nDerivationMethod'] = vds.read_int32()
-				d['nDeriveIterations'] = vds.read_int32()
-				d['vchOtherDerivationParameters'] = vds.read_bytes(vds.read_compact_size())
-			elif type == "defaultkey":
-				d['key'] = vds.read_bytes(vds.read_compact_size())
-			elif type == "pool":
-				d['n'] = kds.read_int64()
-				d['nVersion'] = vds.read_int32()
-				d['nTime'] = vds.read_int64()
-				d['public_key'] = vds.read_bytes(vds.read_compact_size())
-			elif type == "acc":
-				d['account'] = kds.read_string()
-				d['nVersion'] = vds.read_int32()
-				d['public_key'] = vds.read_bytes(vds.read_compact_size())
-			elif type == "acentry":
-				d['account'] = kds.read_string()
-				d['n'] = kds.read_uint64()
-				d['nVersion'] = vds.read_int32()
-				d['nCreditDebit'] = vds.read_int64()
-				d['nTime'] = vds.read_int64()
-				d['otherAccount'] = vds.read_string()
-				d['comment'] = vds.read_string()
-			elif type == "bestblock":
-				d['nVersion'] = vds.read_int32()
-				d.update(parse_BlockLocator(vds))
-
-			item_callback(type, d)
-
-		except Exception, e:
-			traceback.print_exc()
-			print("ERROR parsing wallet.dat, type %s" % type)
-			print("key data in hex: %s"%key.encode('hex_codec'))
-			print("value data in hex: %s"%value.encode('hex_codec'))
-			sys.exit(1)
-
-def update_wallet(db, type, data):
-	"""Write a single item to the wallet.
-	db must be open with writable=True.
-	type and data are the type code and data dictionary as parse_wallet would
-	give to item_callback.
-	data's __key__, __value__ and __type__ are ignored; only the primary data
-	fields are used.
-	"""
-	d = data
-	kds = BCDataStream()
-	vds = BCDataStream()
-
-	# Write the type code to the key
-	kds.write_string(type)
-	vds.write("")                         # Ensure there is something
-
-	try:
-		if type == "tx":
-			raise NotImplementedError("Writing items of type 'tx'")
-			kds.write(d['tx_id'])
-		elif type == "name":
-			kds.write_string(d['hash'])
-			vds.write_string(d['name'])
-		elif type == "version":
-			vds.write_uint32(d['version'])
-		elif type == "minversion":
-			vds.write_uint32(d['minversion'])
-		elif type == "setting":
-			raise NotImplementedError("Writing items of type 'setting'")
-			kds.write_string(d['setting'])
-			#d['value'] = parse_setting(d['setting'], vds)
-		elif type == "key":
-			kds.write_string(d['public_key'])
-			vds.write_string(d['private_key'])
-		elif type == "wkey":
-			kds.write_string(d['public_key'])
-			vds.write_string(d['private_key'])
-			vds.write_int64(d['created'])
-			vds.write_int64(d['expires'])
-			vds.write_string(d['comment'])
-		elif type == "ckey":
-			kds.write_string(d['public_key'])
-			vds.write_string(d['crypted_key'])
-		elif type == "defaultkey":
-			vds.write_string(d['key'])
-		elif type == "pool":
-			kds.write_int64(d['n'])
-			vds.write_int32(d['nVersion'])
-			vds.write_int64(d['nTime'])
-			vds.write_string(d['public_key'])
-		elif type == "acc":
-			kds.write_string(d['account'])
-			vds.write_int32(d['nVersion'])
-			vds.write_string(d['public_key'])
-		elif type == "acentry":
-			kds.write_string(d['account'])
-			kds.write_uint64(d['n'])
-			vds.write_int32(d['nVersion'])
-			vds.write_int64(d['nCreditDebit'])
-			vds.write_int64(d['nTime'])
-			vds.write_string(d['otherAccount'])
-			vds.write_string(d['comment'])
-		elif type == "bestblock":
-			vds.write_int32(d['nVersion'])
-			vds.write_compact_size(len(d['hashes']))
-			for h in d['hashes']:
-				vds.write(h)
-		else:
-			print "Unknown key type: "+type
-
-		# Write the key/value pair to the database
-		db.put(kds.input, vds.input)
-
-	except Exception, e:
-		print("ERROR writing to wallet.dat, type %s"%type)
-		print("data dictionary: %r"%data)
-		traceback.print_exc()
-
-
-def read_wallet(json_db, db_env, db_file, print_wallet, print_wallet_transactions, transaction_filter):
-
-	db = open_wallet(db_env, db_file)
-
-	json_db['keys'] = []
-	json_db['pool'] = []
-	json_db['names'] = {}
-
-	def item_callback(type, d):
-
-		global password
-
-		if type == "name":
-			json_db['names'][d['hash']] = d['name']
-
-		elif type == "version":
-			json_db['version'] = d['version']
-
-		elif type == "minversion":
-			json_db['minversion'] = d['minversion']
-
-		elif type == "setting":
-			if not json_db.has_key('settings'): json_db['settings'] = {}
-			json_db["settings"][d['setting']] = d['value']
-
-		elif type == "defaultkey":
-			json_db['defaultkey'] = public_key_to_bc_address(d['key'])
-
-		elif type == "key":
-			addr = public_key_to_bc_address(d['public_key'])
-			compressed = d['public_key'][0] != '\04'
-			sec = SecretToASecret(PrivKeyToSecret(d['private_key']), compressed)
-			private_keys.append(sec)
-			json_db['keys'].append({'addr' : addr, 'sec' : sec})
-#            json_db['keys'].append({'addr' : addr, 'sec' : sec,
-#                'secret':PrivKeyToSecret(d['private_key']).encode('hex'),
-#                'pubkey':d['public_key'].encode('hex'),
-#                'privkey':d['private_key'].encode('hex')})
-
-		elif type == "wkey":
-			if not json_db.has_key('wkey'): json_db['wkey'] = []
-			json_db['wkey']['created'] = d['created']
-
-		elif type == "ckey":
-			addr = public_key_to_bc_address(d['public_key'])
-			ckey = d['crypted_key']
-			pubkey = d['public_key']
-			json_db['keys'].append( {'addr' : addr, 'ckey': ckey.encode('hex'), 'pubkey': pubkey.encode('hex') })
-
-		elif type == "mkey":
-			mkey = {}
-			mkey['nID'] = d['nID']
-			mkey['crypted_key'] = d['crypted_key'].encode('hex')
-			mkey['salt'] = d['salt'].encode('hex')
-			mkey['nDeriveIterations'] = d['nDeriveIterations']
-			mkey['nDerivationMethod'] = d['nDerivationMethod']
-			mkey['vchOtherDerivationParameters'] = d['vchOtherDerivationParameters'].encode('hex')
-			json_db['mkey'] = mkey
-
-			if password == None and \
-				('json' in opts or 'keysforaddrs' in opts or 'keys' in opts):
-				from mmgen.util import get_bitcoind_passphrase
-				password = get_bitcoind_passphrase("Enter password: ",opts)
-
-			if password != None:
-				global crypter
-				if crypter == 'pycrypto':
-					crypter = Crypter_pycrypto()
-				elif crypter == 'ssl':
-					crypter = Crypter_ssl()
-				else:
-					crypter = Crypter_pure()
-					logging.warning("pycrypto or libssl not found, decryption may be slow")
-				res = crypter.SetKeyFromPassphrase(password, d['salt'], d['nDeriveIterations'], d['nDerivationMethod'])
-				if res == 0:
-					logging.error("Unsupported derivation method")
-					sys.exit(1)
-				masterkey = crypter.Decrypt(d['crypted_key'])
-				crypter.SetKey(masterkey)
-
-		elif type == "pool":
-			json_db['pool'].append( {'n': d['n'], 'addr': public_key_to_bc_address(d['public_key']), 'nTime' : d['nTime'] } )
-
-		elif type == "acc":
-			json_db['acc'] = d['account']
-#			msg("Account %s (current key: %s)"%(d['account'], public_key_to_bc_address(d['public_key'])))
-
-		elif type == "acentry":
-			json_db['acentry'] = (d['account'], d['nCreditDebit'], d['otherAccount'], time.ctime(d['nTime']), d['n'], d['comment'])
-
-		elif type == "bestblock":
-			json_db['bestblock'] = d['hashes'][0][::-1].encode('hex_codec')
-
-		else:
-			json_db[type] = 'unsupported'
-
-	parse_wallet(db, item_callback)
-
-	db.close()
-
-	for k in json_db['keys']:
-		addr = k['addr']
-		if addr in json_db['names'].keys():
-			k["label"] = json_db['names'][addr]
-		else:
-			k["reserve"] = 1
-
-	if 'mkey' in json_db.keys() and password != None:
-		check = True
-		for k in json_db['keys']:
-			ckey = k['ckey'].decode('hex')
-			public_key = k['pubkey'].decode('hex')
-			crypter.SetIV(Hash(public_key))
-			secret = crypter.Decrypt(ckey)
-			compressed = public_key[0] != '\04'
-
-			if check:
-				check = False
-				pkey = EC_KEY(int('0x' + secret.encode('hex'), 16))
-				if public_key != GetPubKey(pkey, compressed):
-					logging.error("wrong password")
-					sys.exit(1)
-
-			sec = SecretToASecret(secret, compressed)
-			k['sec'] = sec
-			k['secret'] = secret.encode('hex')
-			del(k['ckey'])
-			del(k['secret'])
-			del(k['pubkey'])
-			private_keys.append(sec)
-
-	del(json_db['pool'])
-	del(json_db['names'])
-
-
-# Non-portable.  For Windows, works only if supplied filename is in current dir
-
-# main()
-
-import os.path
-infile = os.path.abspath(cmd_args[0])
-db_dir,db_file = os.path.dirname(infile),os.path.basename(infile)
-
-#	print "[%s] [%s]" % (db_dir,db_file)
-
-db_env = create_env(db_dir)
-
-read_wallet(json_db, db_env, db_file, True, True, "")
-
-if json_db.get('minversion') > max_version:
-	print "Version mismatch (must be <= %d)" % max_version
-	exit(1)
-
-wallet_addrs = [i['addr'] for i in json_db['keys']]
-
-if 'json' in opts:
-	data = [json.dumps(json_db, sort_keys=True, indent=4)]
-	ext,what = "json","json dump"
-
-elif 'keys' in opts:
-	data = sorted([i['sec'] for i in json_db['keys']])
-	ext,what = "keys","private keys"
-
-elif 'addrs' in opts:
-	data = sorted([i['addr'] for i in json_db['keys']])
-	ext,what = "addrs","addresses"
-
-elif 'keysforaddrs' in opts:
-	from mmgen.util import get_lines_from_file
-	usr_addrs = set(get_lines_from_file(opts['keysforaddrs'],"addresses",trim_comments=True))
-	data = [i['sec'] for i in json_db['keys'] if i['addr'] in usr_addrs]
-	ext,what = "keys","private keys"
-	if len(data) < len(usr_addrs):
-		msg("Warning: not all requested keys found")
-
-len_arg = "%s" % len(wallet_addrs) \
-   if len(data) == len(wallet_addrs) or ext == "json" \
-   else "%s:%s" % (len(data),len(wallet_addrs))
-
-from mmgen.util import make_chksum_8,write_walletdat_dump_to_file,write_to_stdout
-wallet_id = make_chksum_8(str(sorted(wallet_addrs)))
-
-data = "\n".join(data) + "\n"
-
-# Output data
-if 'stdout' in opts:
-	confirm = False if 'addrs' in opts else True
-	write_to_stdout(data,"secret keys",confirm)
-elif not sys.stdout.isatty():
-	write_to_stdout(data,"secret keys",confirm=False)
-else:
-	write_walletdat_dump_to_file(wallet_id, data, len_arg, ext, what, opts)
+import mmgen.main
+mmgen.main.main("pywallet")

+ 6 - 52
mmgen-tool

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C) 2013 by philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -15,57 +15,11 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
 """
-mmgen-tool: Perform various Bitcoin-related operations - part of the MMGen suite
+mmgen-tool:  Perform various Bitcoin-related operations.
+             Part of the MMGen suite
 """
 
-import sys
-import mmgen.config as g
-import mmgen.tool as tool
-from mmgen.Opts import *
-
-help_data = {
-	'prog_name': g.prog_name,
-	'desc':    "Perform various BTC-related operations",
-	'usage':   "[opts] <command> <command args>",
-	'options': """
--d, --outdir=       d Specify an alternate directory 'd' for 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(tool.command_help,g.prog_name)
-}
-
-opts,cmd_args = parse_opts(sys.argv,help_data)
-
-if 'quiet' in opts:   g.quiet = True
-if 'verbose' in opts: g.verbose = True
-
-if len(cmd_args) < 1:
-	usage(help_data)
-	sys.exit(1)
-
-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.tool_usage(g.prog_name, command)
-	sys.exit(0)
-
-args = tool.process_args(g.prog_name, command, cmd_args)
-
-tool.opts = opts
-
-#print command + "(" + ", ".join(args) + ")"
-eval("tool." + command + "(" + ", ".join(args) + ")")
+import mmgen.main
+mmgen.main.main("tool")

+ 6 - 202
mmgen-txcreate

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C) 2013-2014 by philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -15,207 +15,11 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
 """
-mmgen-txcreate: Create a BTC transaction, sending to specified addresses
+mmgen-txcreate: Create a Bitcoin transaction from MMGen- or non-MMGen inputs
+                to MMGen- or non-MMGen outputs
 """
 
-import sys
-from decimal import Decimal
-
-import mmgen.config as g
-from mmgen.Opts import *
-from mmgen.license import *
-from mmgen.tx import *
-from mmgen.util import msg, msg_r, user_confirm
-
-help_data = {
-	'prog_name': g.prog_name,
-	'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
-""".format(g=g),
-	'notes': """
-
-Transaction inputs are chosen from a list of the user's unpent outputs
-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 {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,pnm=g.proj_name)
-}
-
-opts,cmd_args = parse_opts(sys.argv,help_data)
-
-if 'quiet' in opts: g.quiet = True
-
-if g.debug: show_opts_and_cmd_args(opts,cmd_args)
-
-c = connect_to_bitcoind()
-
-if not 'info' in opts:
-	do_license_msg(immed=True)
-
-	tx_out,addr_data,b2m_map,acct_data,change_addr = {},[],{},[],""
-
-	addrfiles = [a for a in cmd_args if get_extension(a) == g.addrfile_ext]
-	cmd_args = set(cmd_args) - set(addrfiles)
-
-	for a in addrfiles:
-		check_infile(a)
-		addr_data.append(parse_addrs_file(a))
-
-	def mmaddr2btcaddr(c,mmaddr,acct_data,addr_data,b2m_map):
-		# assume mmaddr has already been checked
-		btcaddr,label = mmaddr2btcaddr_bitcoind(c,mmaddr,acct_data)
-		if not btcaddr:
-			if addr_data:
-				btcaddr,label = mmaddr2btcaddr_addrfile(mmaddr,addr_data)
-			else:
-				msg(txmsg['addrfile_no_data_msg'] % mmaddr)
-				sys.exit(2)
-
-		b2m_map[btcaddr] = mmaddr,label
-		return btcaddr
-
-	for a in cmd_args:
-		if "," in a:
-			a1,a2 = a.split(",")
-			if is_btc_addr(a1):
-				btcaddr = a1
-			elif is_mmgen_addr(a1):
-				btcaddr = mmaddr2btcaddr(c,a1,acct_data,addr_data,b2m_map)
-			else:
-				msg("%s: unrecognized subargument in argument '%s'" % (a1,a))
-				sys.exit(2)
-
-			if is_btc_amt(a2):
-				tx_out[btcaddr] = normalize_btc_amt(a2)
-			else:
-				msg("%s: invalid amount in argument '%s'" % (a2,a))
-				sys.exit(2)
-		elif is_mmgen_addr(a) or is_btc_addr(a):
-			if change_addr:
-				msg("ERROR: More than one change address specified: %s, %s" %
-						(change_addr, a))
-				sys.exit(2)
-			change_addr = a if is_btc_addr(a) else \
-				mmaddr2btcaddr(c,a,acct_data,addr_data,b2m_map)
-			tx_out[change_addr] = 0
-		else:
-			msg("%s: unrecognized argument" % a)
-			sys.exit(2)
-
-	if not tx_out:
-		msg("At least one output must be specified on the command line")
-		sys.exit(2)
-
-	tx_fee = opts['tx_fee'] if 'tx_fee' in opts else g.tx_fee
-	tx_fee = normalize_btc_amt(tx_fee)
-	if tx_fee > g.max_tx_fee:
-		msg("Transaction fee too large: %s > %s" % (tx_fee,g.max_tx_fee))
-		sys.exit(2)
-
-if g.debug: show_opts_and_cmd_args(opts,cmd_args)
-
-#write_to_file("bogus_unspent.json", repr(us), opts); sys.exit()
-
-#if False:
-if g.bogus_wallet_data:
-	import mmgen.rpc
-	us = eval(get_data_from_file(g.bogus_wallet_data))
-else:
-	us = c.listunspent()
-
-if not us: msg(txmsg['no_spendable_outputs']); sys.exit(2)
-
-unspent = sort_and_view(us,opts)
-
-total = trim_exponent(sum([i.amount for i in unspent]))
-
-msg("Total unspent: %s BTC (%s outputs)" % (total, len(unspent)))
-if 'info' in opts: sys.exit(0)
-
-send_amt = sum([tx_out[i] for i in tx_out.keys()])
-msg("Total amount to spend: %s%s" % (
-		(send_amt or "Unknown")," BTC" if send_amt else ""))
-
-while True:
-	sel_nums = select_outputs(unspent,
-			"Enter a range or space-separated list of outputs to spend: ")
-	msg("Selected output%s: %s" %
-		(("" if len(sel_nums) == 1 else "s"), " ".join(str(i) for i in sel_nums))
-	)
-	sel_unspent = [unspent[i-1] for i in sel_nums]
-
-	mmaddrs = set([parse_mmgen_label(i.account)[0] for i in sel_unspent])
-	mmaddrs.discard("")
-
-	if mmaddrs and len(mmaddrs) < len(sel_unspent):
-		msg(txmsg['mixed_inputs'] % ", ".join(sorted(mmaddrs)))
-		if not user_confirm("Accept?"):
-			continue
-
-	total_in = trim_exponent(sum([i.amount for i in sel_unspent]))
-	change   = trim_exponent(total_in - (send_amt + tx_fee))
-
-	if change >= 0:
-		prompt = "Transaction produces %s BTC in change.  OK?" % change
-		if user_confirm(prompt,default_yes=True):
-			break
-	else:
-		msg(txmsg['not_enough_btc'] % change)
-
-if change > 0 and not change_addr:
-	msg(txmsg['throwaway_change'] % change)
-	sys.exit(2)
-
-if change_addr in tx_out and not change:
-	msg("Warning: Change address will be unused as transaction produces no change")
-	del tx_out[change_addr]
-
-for k,v in tx_out.items(): tx_out[k] = float(v)
-
-if change > 0: tx_out[change_addr] = float(change)
-
-tx_in = [{"txid":i.txid, "vout":i.vout} for i in sel_unspent]
-
-if g.debug:
-	print "tx_in:", repr(tx_in)
-	print "tx_out:", repr(tx_out)
-
-tx_hex = c.createrawtransaction(tx_in,tx_out)
-qmsg("Transaction successfully created")
-prompt = "View decoded transaction? (y)es, (N)o, (v)iew in pager"
-reply = prompt_and_get_char(prompt,"YyNnVv",enter_ok=True)
-
-if reply and reply in "YyVv":
-	pager = True if reply in "Vv" else False
-	view_tx_data(c,[i.__dict__ for i in sel_unspent],tx_hex,b2m_map,pager=pager)
-
-prompt = "Save transaction?"
-if user_confirm(prompt,default_yes=True):
-	amt = send_amt or change
-	tx_id = make_chksum_6(unhexlify(tx_hex)).upper()
-	outfile = "tx_%s[%s].%s" % (tx_id,amt,g.rawtx_ext)
-	data = "{} {} {}\n{}\n{}\n{}\n".format(
-			tx_id, amt, make_timestamp(),
-			tx_hex,
-			repr([i.__dict__ for i in sel_unspent]),
-			repr(b2m_map)
-		)
-	write_to_file(outfile,data,opts,"transaction",False,True)
-else:
-	msg("Transaction not saved")
+import mmgen.main
+mmgen.main.main("txcreate")

+ 5 - 67
mmgen-txsend

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C) 2013-2014 by philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -15,72 +15,10 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
-"""
-mmgen-txsend: Broadcast a Bitcoin transaction to the network
-"""
-
-import sys
 
-import mmgen.config as g
-from mmgen.Opts import *
-from mmgen.license import *
-from mmgen.tx import *
-from mmgen.util import msg,check_infile,get_lines_from_file,confirm_or_exit
-
-help_data = {
-	'prog_name': g.prog_name,
-	'desc':    "Send a Bitcoin transaction signed by {}-txsign".format(g.proj_name.lower()),
-	'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
 """
-}
-
-opts,cmd_args = parse_opts(sys.argv,help_data)
-
-if 'quiet' in opts: g.quiet = True
-
-if len(cmd_args) == 1:
-	infile = cmd_args[0]; check_infile(infile)
-else: usage(help_data)
-
-# Begin execution
-
-do_license_msg()
-
-tx_data = get_lines_from_file(infile,"signed transaction data")
-
-metadata,tx_hex,inputs_data,b2m_map = parse_tx_data(tx_data,infile)
-
-qmsg("Signed transaction file '%s' is valid" % infile)
-
-c = connect_to_bitcoind()
-
-prompt = "View transaction data? (y)es, (N)o, (v)iew in pager"
-reply = prompt_and_get_char(prompt,"YyNnVv",enter_ok=True)
-if reply and reply in "YyVv":
-	p = True if reply in "Vv" else False
-	view_tx_data(c,inputs_data,tx_hex,b2m_map,metadata,pager=p)
-
-warn   = "Once this transaction is sent, there's no taking it back!"
-what   = "broadcast this transaction to the network"
-expect =  "YES, I REALLY WANT TO DO THIS"
-
-if g.quiet: warn,expect = "","YES"
-
-confirm_or_exit(warn, what, expect)
-
-msg("Sending transaction")
-
-try:
-	tx_id = c.sendrawtransaction(tx_hex)
-except:
-	msg("Unable to send transaction")
-	sys.exit(3)
-
-msg("Transaction sent: %s" % tx_id)
+mmgen-txsend: Broadcast a transaction signed by 'mmgen-txsign' to the network
+"""
 
-of = "tx_{}[{}].out".format(*metadata[:2])
-write_to_file(of, tx_id+"\n",opts,"transaction ID",True,True)
+import mmgen.main
+mmgen.main.main("txsend")

+ 5 - 193
mmgen-txsign

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C) 2013-2014 by philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -15,198 +15,10 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
 """
-mmgen-txsign: Sign a Bitcoin transaction generated by mmgen-txcreate
+mmgen-txsign: Sign a transaction generated by 'mmgen-txcreate'
 """
 
-import sys
-
-import mmgen.config as g
-from mmgen.Opts import *
-from mmgen.license import *
-from mmgen.tx import *
-from mmgen.util import msg,qmsg
-
-help_data = {
-	'prog_name': g.prog_name,
-	'desc':    "Sign Bitcoin transactions generated by {}-txcreate".format(g.proj_name.lower()),
-	'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= 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
--V, --skip-key-preverify Skip optional key pre-verification step
--b, --from-brain=    l,p Generate keys from a user-created password,
-                         i.e. a "brainwallet", using seed length 'l' and
-                         hash preset 'p'
--w, --use-wallet-dat     Get keys from a running bitcoind
--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'
--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,pnm=g.proj_name),
-	'notes': """
-
-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
-{pnl}-addrgen and {pnl}-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 {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 {pnm} keys using 'bitcoind importprivkey'.
-
-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 (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,pnm=g.proj_name,pnl=g.proj_name.lower())
-}
-
-opts,infiles = parse_opts(sys.argv,help_data)
-
-for l in (
-('tx_id', 'info'),
-('keys_from_file','all_keys_from_file')
-): warn_incompatible_opts(opts,l)
-
-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)
-
-c = connect_to_bitcoind()
-
-saved_seeds = {}
-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 'keys_from_file' in opts:
-	from mmgen.crypto import mmgen_decrypt
-	fn = opts['keys_from_file']
-	if get_extension(fn) == g.mmenc_ext:
-		enc_d = get_data_from_file(fn,"encrypted keylist")
-		dec_d = mmgen_decrypt(enc_d,"",opts)
-		if dec_d:
-			keys_from_file = remove_comments(dec_d.split("\n"))
-		else:
-			msg("Decryption of encrypted keylist failed")
-			sys.exit(2)
-	else:
-		keys_from_file = get_lines_from_file(fn,"key data",trim_comments=True)
-else: keys_from_file = []
-
-for tx_file in tx_files:
-	m = "" if 'tx_id' in opts else "transaction data"
-	tx_data = get_lines_from_file(tx_file,m)
-
-	metadata,tx_hex,inputs_data,b2m_map = parse_tx_data(tx_data,tx_file)
-	qmsg("Successfully opened transaction file '%s'" % tx_file)
-
-	if 'tx_id' in opts:
-		msg(metadata[0])
-		sys.exit(0)
-
-	if 'info' in opts:
-		view_tx_data(c,inputs_data,tx_hex,b2m_map,metadata)
-		sys.exit(0)
-
-# Are inputs mmgen addresses?
-	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_inputs and not keys and not 'use_wallet_dat' in opts:
-		missing_keys_errormsg(other_inputs)
-		sys.exit(2)
-
-	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
-
-	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)))
-
-	prompt = "View transaction data? (y)es, (N)o, (v)iew in pager"
-	reply = prompt_and_get_char(prompt,"YyNnVv",enter_ok=True)
-	if reply and reply in "YyVv":
-		p = True if reply in "Vv" else False
-		view_tx_data(c,inputs_data,tx_hex,b2m_map,metadata,pager=p)
-
-	sig_data = [
-		{"txid":i['txid'],"vout":i['vout'],"scriptPubKey":i['scriptPubKey']}
-			for i in inputs_data]
-
-	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_inputs:
-		if keys:
-			sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
-		else:
-			sig_tx = sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys,opts)
-
-	if sig_tx['complete']:
-		prompt = "OK\nSave signed transaction?"
-		if user_confirm(prompt,default_yes=True):
-			outfile = "tx_%s[%s].%s" % (metadata[0],metadata[1],g.sigtx_ext)
-			data = "{}\n{}\n{}\n{}\n".format(
-					" ".join(metadata[:2] + [make_timestamp()]),
-					sig_tx['hex'],
-					repr(inputs_data),
-					repr(b2m_map)
-				)
-			write_to_file(outfile,data,opts,"signed transaction",True,True)
-	else:
-		msg("failed\nSome keys were missing.  Transaction could not be signed.")
-		sys.exit(3)
+import mmgen.main
+mmgen.main.main("txsign")

+ 6 - 97
mmgen-walletchk

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C) 2013-2014 by philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -15,102 +15,11 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
-"""
-mmgen-walletchk: Check integrity of a mmgen deterministic wallet, display
-                 information about it and export seed and mnemonic data
-"""
-
-import sys
-import mmgen.config as g
-from mmgen.Opts import *
-from mmgen.util import *
-from mmgen.crypto import get_seed_from_wallet,wallet_to_incog_data
-
-help_data = {
-	'prog_name': g.prog_name,
-	'desc':  """Check integrity of an {} deterministic wallet, display
-                    its information, and export seed and mnemonic data.
-             """.format(g.proj_name),
-	'usage':   "[opts] [filename]",
-	'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
--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)
--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.
 """
-}
-
-opts,cmd_args = parse_opts(sys.argv,help_data)
-
-if 'quiet' in opts: g.quiet = True
-if 'verbose' in opts: g.verbose = True
-if 'export_incog_hidden' in opts or 'export_incog_hex' in opts:
-	opts['export_incog'] = True
-
-if len(cmd_args) != 1: usage(help_data)
-
-check_infile(cmd_args[0])
-
-if 'export_mnemonic' in opts:
-	qmsg("Exporting mnemonic data to file by user request")
-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)
-
-	if "export_incog_hidden" in opts:
-		export_to_hidden_incog(incog_enc,opts)
-	else:
-		seed_len = (len(incog_enc)-g.salt_len-g.aesctr_iv_len)*8
-		fn = "%s-%s-%s[%s,%s].%s" % (
-			seed_id, key_id, iv_id, seed_len, preset,
-			g.incog_hex_ext if "export_incog_hex" in opts else g.incog_ext
-		)
-		data = pretty_hexdump(incog_enc,2,8,line_nums=False) \
-					if "export_incog_hex" in opts else incog_enc
-		export_to_file(fn, data, opts, "incognito wallet data")
-
-	sys.exit()
-
-seed = get_seed_from_wallet(cmd_args[0], opts)
-if seed: qmsg("Wallet is OK")
-else:
-	msg("Error opening wallet")
-	sys.exit(2)
-
-if 'export_mnemonic' in opts:
-	wl = get_default_wordlist()
-	from mmgen.mnemonic import get_mnemonic_from_seed
-	p = True if g.debug else False
-	mn = get_mnemonic_from_seed(seed, wl, g.default_wl, print_info=p)
-	fn = "%s.%s" % (make_chksum_8(seed).upper(), g.mn_ext)
-	export_to_file(fn, " ".join(mn)+"\n", opts, "mnemonic data")
+mmgen-walletchk: Check integrity of an MMGen deterministic wallet, display
+                 information about it and export it to various formats
+"""
 
-elif 'export_seed' in opts:
-	from mmgen.bitcoin import b58encode_pad
-	data = col4(b58encode_pad(seed))
-	chk = make_chksum_6(b58encode_pad(seed))
-	fn = "%s.%s" % (make_chksum_8(seed).upper(), g.seed_ext)
-	export_to_file(fn, "%s %s\n" % (chk,data), opts, "seed data")
+import mmgen.main
+mmgen.main.main("walletchk")

+ 5 - 131
mmgen-walletgen

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C) 2013-2014 by philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -15,136 +15,10 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
 """
-mmgen-walletgen: Generate a mmgen deterministic wallet
+mmgen-walletgen: Generate an MMGen deterministic wallet
 """
 
-import sys, os
-from hashlib import sha256
-
-import mmgen.config as g
-from mmgen.Opts import *
-from mmgen.license import *
-from mmgen.util import *
-from mmgen.crypto import *
-
-help_data = {
-	'prog_name': g.prog_name,
-	'desc':    "Generate an {} deterministic wallet".format(g.proj_name),
-	'usage':   "[opts] [infile]",
-	'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
--H, --show-hash-presets    Show information on available hash presets
--l, --seed-len=         n  Create seed of length 'n'. Options: {seed_lens}
-                           (default: {g.seed_len})
--L, --label=            l  Label to identify this wallet (32 chars max.
-                           Allowed symbols: A-Z, a-z, 0-9, " ", "_", ".")
--p, --hash-preset=      p  Use scrypt.hash() parameters from preset 'p'
-                           (default: '{g.hash_preset}')
--P, --passwd-file=      f  Get passphrase from file 'f'
--q, --quiet                Produce quieter output; overwrite files without
-                           prompting
--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,
-                           i.e. a "brainwallet", using seed length 'l' and
-                           hash preset 'p' (comma-separated)
--g, --from-incog           Generate wallet from an incognito-format wallet
--m, --from-mnemonic        Generate wallet from an Electrum-like mnemonic
--s, --from-seed            Generate wallet from a seed in .{g.seed_ext} format
-""".format(seed_lens=",".join([str(i) for i in g.seed_lens]), g=g),
-	'notes': """
-
-By default (i.e. when invoked without any of the '--from-<what>' options),
-{g.prog_name} generates a wallet based on a random seed.
-
-Data for the --from-<what> options will be taken from <infile> if <infile>
-is specified.  Otherwise, the user will be prompted to enter the data.
-
-For passphrases all combinations of whitespace are equal, and leading and
-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
-attacks, the brainwallet hash preset must be specified by the user, using
-the 'p' parameter of the '--from-brain' option.  This preset should be
-stronger than the one used for hashing the seed (i.e. the default value or
-the one specified in the '--hash-preset' option).
-
-The '--from-brain' option also requires the user to specify a seed length
-(the 'l' parameter), which overrides both the default and any one given in
-the '--seed-len' option.
-
-For a brainwallet passphrase to always generate the same keys and
-addresses, the same 'l' and 'p' parameters to '--from-brain' must be used
-in all future invocations with that passphrase.
-""".format(g=g)
-}
-
-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)
-
-if len(cmd_args) == 1:
-	infile = cmd_args[0]
-	check_infile(infile)
-	ext = infile.split(".")[-1]
-	ok_exts = g.seedfile_exts
-	for e in ok_exts:
-		if e == ext: break
-	else:
-		msg(
-"Input file must have one of the following extensions: .%s" % ", .".join(ok_exts))
-		sys.exit(1)
-elif len(cmd_args) == 0:
-	infile = ""
-else: usage(help_data)
-
-# Begin execution
-
-do_license_msg()
-
-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)),
-		"continue")
-
-for i in 'from_mnemonic','from_brain','from_seed','from_incog':
-	if infile or (i in opts):
-		seed = get_seed_retry(infile,opts)
-		if "from_incog" in opts or get_extension(infile) == g.incog_ext:
-			qmsg(cmessages['incog'] % make_chksum_8(seed))
-		else: qmsg("")
-		break
-else:
-	# Truncate random data for smaller seed lengths
-	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)
-
-key = make_key(passwd, salt, opts['hash_preset'])
-
-enc_seed = encrypt_seed(seed, key)
-
-write_wallet_to_file(seed,passwd,make_chksum_8(key),salt,enc_seed,opts)
+import mmgen.main
+mmgen.main.main("walletgen")

+ 8 - 1
mmgen/Opts.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C) 2013-2014 by philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -16,6 +16,10 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+"""
+Opts.py:  Option handling routines for the MMGen suite
+"""
+
 import sys
 import mmgen.config as g
 import mmgen.opt.Opts
@@ -65,6 +69,9 @@ def parse_opts(argv,help_data):
 		if v in opts: typeconvert_override_var(opts,v)
 		else: opts[v] = eval("g."+v)
 
+	if "verbose" in opts: g.verbose = True
+	if "quiet" in opts:   g.quiet = True
+
 	if g.debug: print "opts after typeconvert: %s" % opts
 
 	return opts,args

+ 4 - 2
mmgen/__init__.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C) 2013-2014 by philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -15,8 +15,10 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
 """
-MMGen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
+MMGen = Multi-Mode GENerator, a Bitcoin cold storage/tracking solution for
+        the command line
 """
 __all__ = [
 	'rpc',

+ 22 - 27
mmgen/addr.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C) 2013-2014 by philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -15,8 +15,9 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
 """
-addr.py:  Address generation/display routines for mmgen suite
+addr.py:  Address generation/display routines for the MMGen suite
 """
 
 import sys
@@ -68,8 +69,8 @@ def generate_addrs(seed, addrnums, opts):
 			from subprocess import Popen, PIPE
 			keyconv = "keyconv"
 
-	fmt = "num addr" if opts['gen_what'] == ("addrs") else (
-		"num sec wif" if opts['gen_what'] == ("keys") else "num sec wif addr")
+	fmt = "num addr" if opts['gen_what'] == ["addrs"] else (
+		"num sec wif" if opts['gen_what'] == ["keys"] else "num sec wif addr")
 
 	from collections import namedtuple
 	addrinfo = namedtuple("addrinfo",fmt)
@@ -78,37 +79,31 @@ def generate_addrs(seed, addrnums, opts):
 	t_addrs,num,pos,out = len(addrnums),0,0,[]
 	addrnums.sort()  # needed only if caller didn't sort
 
-	try:
-		while pos != t_addrs:
-			seed = sha512(seed).digest()
-			num += 1 # round
-
-			if g.debug: print "Seed round %s: %s" % (num, hexlify(seed))
-			if num != addrnums[pos]: continue
+	ws = 'key' if 'keys' in opts['gen_what'] else 'address'
+	if t_addrs != 1: wp = ws+"s" if ws == 'key' else ws+"es"
 
-			pos += 1
+	while pos != t_addrs:
+		seed = sha512(seed).digest()
+		num += 1 # round
 
-			qmsg_r("\rGenerating %s %s (%s of %s)" %
-						(opts['gen_what'][-1],num,pos,t_addrs))
+		if g.debug: print "Seed round %s: %s" % (num, hexlify(seed))
+		if num != addrnums[pos]: continue
 
-			# Secret key is double sha256 of seed hash round /num/
-			sec = sha256(sha256(seed).digest()).hexdigest()
-			wif = numtowif(int(sec,16))
+		pos += 1
 
-			if 'addrs' in opts['gen_what']: addr = \
-				Popen([keyconv, wif], stdout=PIPE).stdout.readline().split()[1] \
-				if keyconv else privnum2addr(int(sec,16))
+		qmsg_r("\rGenerating %s #%s (%s of %s)" % (ws,num,pos,t_addrs))
 
-			out.append(eval("addrinfo("+addrinfo_args+")"))
+		# Secret key is double sha256 of seed hash round /num/
+		sec = sha256(sha256(seed).digest()).hexdigest()
+		wif = numtowif(int(sec,16))
 
-	except KeyboardInterrupt:
-		msg("\nUser interrupt")
-		sys.exit(1)
+		if 'addrs' in opts['gen_what']: addr = \
+			Popen([keyconv, wif], stdout=PIPE).stdout.readline().split()[1] \
+			if keyconv else privnum2addr(int(sec,16))
 
-	w = 'key' if 'keys' in opts['gen_what'] else 'address'
-	if t_addrs != 1: w = w+"s" if w == 'key' else w+"es"
+		out.append(eval("addrinfo("+addrinfo_args+")"))
 
-	qmsg("\rGenerated %s %s%s"%(t_addrs, w, " "*15))
+	qmsg("\rGenerated %s %s%s"%(t_addrs, wp, " "*15))
 
 	return out
 

+ 2 - 1
mmgen/bitcoin.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # MMGen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C) 2013-2014 by philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -15,6 +15,7 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
 """
 bitcoin.py:  Bitcoin address/key conversion functions
 """

+ 3 - 2
mmgen/config.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C) 2013-2014 by philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -15,8 +15,9 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
 """
-config.py:  Constants and configuration options for the mmgen suite
+config.py:  Constants and configuration options for the MMGen suite
 """
 
 import sys, os

+ 10 - 6
mmgen/crypto.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C) 2013-2014 by philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -15,8 +15,9 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
 """
-crypto.py:  Cryptographic and related routines for the mmgen-tool utility
+crypto.py:  Cryptographic and related routines for the 'mmgen-tool' utility
 """
 
 import sys
@@ -368,27 +369,30 @@ def _get_seed_from_brain_passphrase(words,opts):
 # Vars for mmgen_*crypt functions only
 salt_len,sha256_len,nonce_len = 32,32,32
 
-def mmgen_encrypt(data,hash_preset,opts):
+def mmgen_encrypt(data,what="data",hash_preset='3',opts={}):
 	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")
+	vmsg("Encrypting %s" % what)
 	qmsg("Using %s hash preset of '%s'" % (m,hp))
 	passwd = get_new_passphrase("passphrase",{})
 	key = make_key(passwd, salt, hp)
 	enc_d = encrypt_data(sha256(nonce+data).digest() + nonce + data, key,
-				int(hexlify(iv),16))
+				int(hexlify(iv),16), what=what)
 	return salt+iv+enc_d
 
 
-def mmgen_decrypt(data,hash_preset,opts):
+def mmgen_decrypt(data,what="data",hash_preset='3',opts={}):
 	dstart = salt_len + g.aesctr_iv_len
 	salt,iv,enc_d = data[:salt_len],data[salt_len:dstart],data[dstart:]
 	hp,m = (hash_preset,"user-requested") if hash_preset else ('3',"default")
+	vmsg("Preparing to decrypt %s" % what)
 	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(hexlify(iv),16))
+	dec_d = decrypt_data(enc_d, key, int(hexlify(iv),16), what)
 	if dec_d[:sha256_len] == sha256(dec_d[sha256_len:]).digest():
+		vmsg("Success. Passphrase and hash preset are correct")
 		return dec_d[sha256_len+nonce_len:]
 	else:
 		msg("Incorrect passphrase or hash preset")

+ 3 - 1
mmgen/license.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C) 2013-2014 by philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -15,6 +15,7 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
 """
 license.py:  Show the license
 """
@@ -594,6 +595,7 @@ def do_license_msg(immed=False):
 	prompt = "%s " % gpl['prompt'].strip()
 
 	while True:
+		from mmgen.util import my_raw_input
 		reply = get_char(prompt, immed_chars="wc" if immed else "")
 		if reply == 'w':
 			from mmgen.term import do_pager

+ 44 - 0
mmgen/main.py

@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+#
+# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
+# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""
+main.py - Script launcher for the MMGen suite
+"""
+
+import sys, termios
+from mmgen.util import msg
+
+def main(progname):
+	fd = sys.stdin.fileno()
+	old = termios.tcgetattr(fd)
+	try:
+		if   progname == "addrgen":    import mmgen.main_addrgen
+		elif progname == "addrimport": import mmgen.main_addrimport
+		elif progname == "keygen":     import mmgen.main_addrgen
+		elif progname == "passchg":    import mmgen.main_passchg
+		elif progname == "pywallet":   import mmgen.main_pywallet
+		elif progname == "tool":       import mmgen.main_tool
+		elif progname == "txcreate":   import mmgen.main_txcreate
+		elif progname == "txsend":     import mmgen.main_txsend
+		elif progname == "txsign":     import mmgen.main_txsign
+		elif progname == "walletchk":  import mmgen.main_walletchk
+		elif progname == "walletgen":  import mmgen.main_walletgen
+	except KeyboardInterrupt:
+		msg("\nUser interrupt")
+		termios.tcsetattr(fd, termios.TCSADRAIN, old)
+		sys.exit(1)

+ 191 - 0
mmgen/main_addrgen.py

@@ -0,0 +1,191 @@
+#!/usr/bin/env python
+#
+# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
+# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""
+mmgen-addrgen: Generate a series or range of addresses from an MMGen
+               deterministic wallet
+"""
+
+import sys
+
+import mmgen.config as g
+from mmgen.Opts import *
+from mmgen.license import *
+from mmgen.util import *
+from mmgen.crypto import *
+from mmgen.addr import *
+from mmgen.tx import make_addr_data_chksum
+
+what = "keys" if sys.argv[0].split("-")[-1] == "keygen" else "addresses"
+
+help_data = {
+	'prog_name': g.prog_name,
+	'desc': """Generate a list or range of {} from an {g.proj_name} wallet,
+                  mnemonic, seed or password""".format(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
+-c, --save-checksum     Save address list checksum to file
+-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(
+		*(
+		   (
+"\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                        '{}-txsign'".format(g.proj_name.lower()),
+"\n-x, --b16               Print secret keys in hexadecimal too"
+			)
+		if what == "keys" else ("","","")),
+		seed_lens=", ".join([str(i) for i in g.seed_lens]),
+		what=what, g=g
+),
+	'notes': """
+
+Addresses are given in a comma-separated list.  Hyphen-separated ranges are
+also allowed.{}
+
+If available, the external 'keyconv' program will be used for address
+generation.
+
+Data for the --from-<what> options will be taken from <infile> if <infile>
+is specified.  Otherwise, the user will be prompted to enter the data.
+
+For passphrases all combinations of whitespace are equal, and leading and
+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.
+
+BRAINWALLET NOTE:
+
+As brainwallets require especially strong hashing to thwart dictionary
+attacks, the brainwallet hash preset must be specified by the user, using
+the 'p' parameter of the '--from-brain' option
+
+The '--from-brain' option also requires the user to specify a seed length
+(the 'l' parameter)
+
+For a brainwallet passphrase to always generate the same keys and addresses,
+the same 'l' and 'p' parameters to '--from-brain' must be used in all future
+invocations with that passphrase
+""".format("\n\nBy default, both addresses and secret keys are generated."
+				if what == "keys" else "")
+}
+
+opts,cmd_args = parse_opts(sys.argv,help_data)
+
+if 'show_hash_presets' in opts: show_hash_presets()
+if 'from_incog_hex' in opts or 'from_incog_hidden' in opts:
+	opts['from_incog'] = True
+
+if g.debug: show_opts_and_cmd_args(opts,cmd_args)
+
+if len(cmd_args) == 1 and (
+			'from_mnemonic' in opts
+			or 'from_brain' in opts
+			or 'from_seed' in opts
+			or 'from_incog_hidden' in opts
+		):
+	infile,addr_idx_arg = "",cmd_args[0]
+elif len(cmd_args) == 2:
+	infile,addr_idx_arg = cmd_args
+	check_infile(infile)
+else: usage(help_data)
+
+addr_idxs = parse_address_list(addr_idx_arg)
+
+if not addr_idxs: sys.exit(2)
+
+do_license_msg()
+
+# Interact with user:
+if what == "keys" and not g.quiet:
+	confirm_or_exit(cmessages['unencrypted_secret_keys'], 'continue')
+
+# Generate data:
+
+seed    = get_seed_retry(infile,opts)
+seed_id = make_chksum_8(seed)
+
+for l in (
+	('flat_list', 'no_addresses'),
+	('flat_list', 'b16'),
+): warn_incompatible_opts(opts,l)
+
+opts['gen_what'] = \
+	["addrs"] if what == "addresses" else (
+	["keys"] if 'no_addresses' in opts else ["addrs","keys"])
+addr_data        = generate_addrs(seed, addr_idxs, opts)
+addr_data_chksum = make_addr_data_chksum([(a.num,a.addr)
+		for a in addr_data]) if 'addrs' in opts['gen_what'] else ""
+addr_data_str    = format_addr_data(
+		addr_data, addr_data_chksum, seed_id, addr_idxs, opts)
+
+outfile_base = "{}[{}]".format(seed_id, fmt_addr_idxs(addr_idxs))
+
+if 'flat_list' in opts and user_confirm("Encrypt key list?"):
+	hp = get_hash_preset_from_user('3')
+	addr_data_str = mmgen_encrypt(addr_data_str,"key list",hp,opts)
+	enc_ext = "." + g.mmenc_ext
+else: enc_ext = ""
+
+# Output data:
+if 'stdout' in opts or not sys.stdout.isatty():
+	if enc_ext and sys.stdout.isatty():
+		msg("Cannot write encrypted data to screen.  Exiting")
+		sys.exit(2)
+	c = True if (what == "keys" and not g.quiet and sys.stdout.isatty()) else False
+	write_to_stdout(addr_data_str,what,c)
+else:
+	confirm_overwrite = False if g.quiet else True
+	outfile = "%s.%s%s" % (outfile_base, (
+		g.keylist_ext if 'flat_list' in opts else (
+		g.keyfile_ext if opts['gen_what'] == ("keys") else (
+		g.addrfile_ext if opts['gen_what'] == ("addrs") else "akeys"))), enc_ext)
+	write_to_file(outfile,addr_data_str,opts,what,confirm_overwrite,True)
+
+if 'addrs' in opts['gen_what']:
+	msg("Checksum for address data {}: {}".format(outfile_base,addr_data_chksum))
+	if 'save_checksum' in opts:
+		a = "address data checksum"
+		write_to_file(outfile_base+".chk",addr_data_chksum,opts,a,False,True)
+	else:
+		qmsg("This checksum will be used to verify the address file in the future.")
+		qmsg("Record it to a safe location.")

+ 140 - 0
mmgen/main_addrimport.py

@@ -0,0 +1,140 @@
+#!/usr/bin/env python
+#
+# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
+# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""
+mmgen-addrimport: Import addresses into a MMGen bitcoind watching wallet
+"""
+
+import sys
+from mmgen.Opts   import *
+from mmgen.license import *
+from mmgen.util import *
+from mmgen.tx import connect_to_bitcoind,parse_addrs_file
+
+help_data = {
+	'prog_name': g.prog_name,
+	'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
+-l, --addrlist= f Import the non-mmgen Bitcoin addresses listed in file 'f'
+-q, --quiet       Suppress warnings
+-r, --rescan      Rescan the blockchain.  Required if address to import is
+                  on the blockchain and has a balance.  Rescanning is slow.
+"""
+}
+
+opts,cmd_args = parse_opts(sys.argv,help_data)
+
+if len(cmd_args) != 1 and not 'addrlist' in opts:
+	msg("You must specify an mmgen address list (and/or non-mmgen addresses with the '--addrlist' option)")
+	sys.exit(1)
+
+if cmd_args:
+	check_infile(cmd_args[0])
+	seed_id,addr_data = parse_addrs_file(cmd_args[0])
+else:
+	seed_id,addr_data = "",[]
+
+if 'addrlist' in opts:
+	lines = get_lines_from_file(opts['addrlist'],"non-mmgen addresses",
+			trim_comments=True)
+	addr_data += [(None,l) for l in lines]
+
+from mmgen.bitcoin import verify_addr
+qmsg_r("Validating addresses...")
+for i in addr_data:
+	if not verify_addr(i[1],verbose=True):
+		msg("%s: invalid address" % i)
+		sys.exit(2)
+qmsg("OK")
+
+import mmgen.config as g
+g.http_timeout = 3600
+
+c = connect_to_bitcoind()
+
+m = """
+WARNING: You've chosen the '--rescan' option.  Rescanning the block chain is
+necessary only if an address you're importing is already on the block chain
+and has a balance.  Note that the rescanning process is very slow (>30 min.
+for each imported address on a low-powered computer).
+	""".strip() if "rescan" in opts else """
+WARNING: If any of the addresses you're importing is already on the block chain
+and has a balance, you must exit the program now and rerun it using the
+'--rescan' option.  Otherwise you may ignore this message and continue.
+""".strip()
+
+if g.quiet: m = ""
+confirm_or_exit(m, "continue", expect="YES")
+
+err_flag = False
+
+def import_address(addr,label,rescan):
+	try:
+		c.importaddress(addr,label,rescan)
+	except:
+		global err_flag
+		err_flag = True
+
+
+w1 = len(str(len(addr_data))) * 2 + 2
+w2 = len(str(max([i[0] for i in addr_data if i[0]]))) + 12
+
+if "rescan" in opts:
+	import threading
+	import time
+	msg_fmt = "\r%s %-" + str(w1) + "s %-34s %-" + str(w2) + "s"
+else:
+	msg_fmt = "\r%-" + str(w1) + "s %-34s %-" + str(w2) + "s"
+
+msg("Importing addresses")
+for n,i in enumerate(addr_data):
+	if i[0]:
+		comment = " " + i[2] if len(i) == 3 else ""
+		label = "%s:%s%s" % (seed_id,i[0],comment)
+	else: label = "non-mmgen"
+
+	if "rescan" in opts:
+		t = threading.Thread(target=import_address, args=(i[1],label,True))
+		t.daemon = True
+		t.start()
+
+		start = int(time.time())
+
+		while True:
+			if t.is_alive():
+				elapsed = int(time.time() - start)
+				msg_r(msg_fmt % (
+						secs_to_hms(elapsed),
+						("%s/%s:" % (n+1,len(addr_data))),
+						i[1], "(" + label + ")"
+					)
+				)
+				time.sleep(1)
+			else:
+				if err_flag: msg("\nImport failed"); sys.exit(2)
+				msg("\nOK")
+				break
+	else:
+		import_address(i[1],label,rescan=False)
+		msg_r(msg_fmt % (("%s/%s:" % (n+1,len(addr_data))),
+							i[1], "(" + label + ")"))
+		if err_flag: msg("\nImport failed"); sys.exit(2)
+		msg(" - OK")

+ 127 - 0
mmgen/main_passchg.py

@@ -0,0 +1,127 @@
+#!/usr/bin/env python
+#
+# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
+# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""
+mmgen-passchg: Change an MMGen deterministic wallet's passphrase, label or
+               hash preset
+"""
+
+import sys
+from mmgen.Opts import *
+from mmgen.util import *
+from mmgen.crypto import *
+import mmgen.config as g
+
+help_data = {
+	'prog_name': g.prog_name,
+	'desc':  """Change the passphrase, hash preset or label of an {}
+                  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'
+-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': """
+
+NOTE: The key ID will change if either the passphrase or hash preset are
+      changed
+"""
+}
+
+opts,cmd_args = parse_opts(sys.argv,help_data)
+
+if 'show_hash_presets' in opts: show_hash_presets()
+
+if len(cmd_args) != 1:
+	msg("One input file must be specified")
+	sys.exit(2)
+infile = cmd_args[0]
+
+# Old key:
+label,metadata,hash_preset,salt,enc_seed = get_data_from_wallet(infile)
+seed_id,key_id = metadata[:2]
+
+# Repeat on incorrect pw entry
+prompt = "Enter %spassphrase: " % (""
+		if 'keep_old_passphrase' in opts else "old ")
+while True:
+	passwd = get_mmgen_passphrase(prompt,{})
+	key = make_key(passwd, salt, hash_preset)
+	seed = decrypt_seed(enc_seed, key, seed_id, key_id)
+	if seed: break
+
+changed = {}
+
+if 'label' in opts:
+	if opts['label'] != label:
+		msg("Label changed: '%s' -> '%s'" % (label, opts['label']))
+		changed['label'] = True
+	else:
+		msg("Label is unchanged: '%s'" % (label))
+else: opts['label'] = label  # Copy the old label
+
+if 'hash_preset' in opts:
+	if hash_preset != opts['hash_preset']:
+		qmsg("Hash preset has changed (%s -> %s)" %
+			(hash_preset, opts['hash_preset']))
+		changed['preset'] = True
+	else:
+		msg("Hash preset is unchanged")
+else:
+	opts['hash_preset'] = hash_preset
+
+if 'keep_old_passphrase' in opts:
+	msg("Keeping old passphrase by user request")
+else:
+	new_passwd = get_new_passphrase("new passphrase", opts)
+
+	if new_passwd == passwd:
+		qmsg("Passphrase is unchanged")
+	else:
+		qmsg("Passphrase has changed")
+		passwd = new_passwd
+		changed['passwd'] = True
+
+if 'preset' in changed or 'passwd' in changed: # Update key ID, salt
+	qmsg("Will update salt and key ID")
+
+	from hashlib import sha256
+
+	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))
+	key_id = new_key_id
+	enc_seed = encrypt_seed(seed, key)
+elif not 'label' in changed:
+	msg("Data unchanged.  No file will be written")
+	sys.exit(2)
+
+write_wallet_to_file(seed, passwd, key_id, salt, enc_seed, opts)

+ 1677 - 0
mmgen/main_pywallet.py

@@ -0,0 +1,1677 @@
+#!/usr/bin/env python
+#
+# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
+# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""
+mmgen-pywallet: Dump contents of a bitcoind wallet to file
+"""
+
+# Changes by Philemon:
+#   password entry at prompt
+#   dump keys, addresses or keys for specified addresses (output in flat list)
+
+# PyWallet 1.2.1 (Public Domain)
+# http://github.com/joric/pywallet
+# Most of the actual PyWallet code placed in the public domain.
+# PyWallet includes portions of free software, listed below.
+
+# BitcoinTools (wallet.dat handling code, MIT License)
+# https://github.com/gavinandresen/bitcointools
+# Copyright (c) 2010 Gavin Andresen
+
+# python-ecdsa (EC_KEY implementation, MIT License)
+# http://github.com/warner/python-ecdsa
+# "python-ecdsa" Copyright (c) 2010 Brian Warner
+# Portions written in 2005 by Peter Pearson and placed in the public domain.
+
+# SlowAES (aes.py code, Apache 2 License)
+# http://code.google.com/p/slowaes/
+# Copyright (c) 2008, Josh Davis (http://www.josh-davis.org),
+# Alex Martelli (http://www.aleax.it)
+# Ported from C code written by Laurent Haan (http://www.progressive-coding.com)
+
+from bsddb.db import *
+import sys, time
+import json
+import logging
+import struct
+import StringIO
+import traceback
+import socket
+import types
+import string
+import exceptions
+import hashlib
+import random
+import math
+
+import mmgen.config as g
+from mmgen.Opts import *
+from mmgen.util import msg
+
+max_version = 60000
+addrtype = 0
+json_db = {}
+private_keys = []
+password = None
+
+help_data = {
+	'prog_name': g.prog_name,
+	'desc':    "Dump contents of a bitcoind wallet to file",
+	'usage':   "[opts] <bitcoind wallet file>",
+	'options': """
+-h, --help             Print this help message
+-d, --outdir=       d  Specify an alternate directory 'd' for output
+-e, --echo-passphrase  Display passphrase on screen upon entry
+-j, --json             Dump wallet in json format
+-k, --keys             Dump all private keys (flat list)
+-a, --addrs            Dump all addresses (flat list)
+-K, --keysforaddrs= f  Dump private keys for addresses listed in file 'f'
+-P, --passwd-file=  f  Get passphrase from file 'f'
+-S, --stdout           Dump to stdout rather than file
+"""
+}
+
+opts,cmd_args = parse_opts(sys.argv,help_data)
+from mmgen.Opts import warn_incompatible_opts
+warn_incompatible_opts(opts,('json','keys','addrs','keysforaddrs'))
+
+if len(cmd_args) == 1:
+	from mmgen.util import check_infile
+	check_infile(cmd_args[0])
+else:
+	usage(help_data)
+
+if ('json' not in opts and 'keys' not in opts
+		and 'addrs' not in opts and 'keysforaddrs' not in opts):
+			usage(help_data)
+
+
+# from the SlowAES project, http://code.google.com/p/slowaes (aes.py)
+
+def append_PKCS7_padding(s):
+	"""return s padded to a multiple of 16-bytes by PKCS7 padding"""
+	numpads = 16 - (len(s)%16)
+	return s + numpads*chr(numpads)
+
+def strip_PKCS7_padding(s):
+	"""return s stripped of PKCS7 padding"""
+	if len(s)%16 or not s:
+		raise ValueError("String of len %d can't be PCKS7-padded" % len(s))
+	numpads = ord(s[-1])
+	if numpads > 16:
+		raise ValueError("String ending with %r can't be PCKS7-padded" % s[-1])
+	return s[:-numpads]
+
+class AES(object):
+	# valid key sizes
+	keySize = dict(SIZE_128=16, SIZE_192=24, SIZE_256=32)
+
+	# Rijndael S-box
+	sbox =  [0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67,
+			0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59,
+			0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7,
+			0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1,
+			0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05,
+			0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83,
+			0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29,
+			0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b,
+			0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa,
+			0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c,
+			0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc,
+			0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec,
+			0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19,
+			0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee,
+			0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49,
+			0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
+			0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4,
+			0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6,
+			0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70,
+			0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9,
+			0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e,
+			0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1,
+			0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0,
+			0x54, 0xbb, 0x16]
+
+	# Rijndael Inverted S-box
+	rsbox = [0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3,
+			0x9e, 0x81, 0xf3, 0xd7, 0xfb , 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f,
+			0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb , 0x54,
+			0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b,
+			0x42, 0xfa, 0xc3, 0x4e , 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24,
+			0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25 , 0x72, 0xf8,
+			0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d,
+			0x65, 0xb6, 0x92 , 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda,
+			0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84 , 0x90, 0xd8, 0xab,
+			0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3,
+			0x45, 0x06 , 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1,
+			0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b , 0x3a, 0x91, 0x11, 0x41,
+			0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6,
+			0x73 , 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9,
+			0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e , 0x47, 0xf1, 0x1a, 0x71, 0x1d,
+			0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b ,
+			0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0,
+			0xfe, 0x78, 0xcd, 0x5a, 0xf4 , 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07,
+			0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f , 0x60,
+			0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f,
+			0x93, 0xc9, 0x9c, 0xef , 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5,
+			0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61 , 0x17, 0x2b,
+			0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55,
+			0x21, 0x0c, 0x7d]
+
+	def getSBoxValue(self,num):
+		"""Retrieves a given S-Box Value"""
+		return self.sbox[num]
+
+	def getSBoxInvert(self,num):
+		"""Retrieves a given Inverted S-Box Value"""
+		return self.rsbox[num]
+
+	def rotate(self, word):
+		""" Rijndael's key schedule rotate operation.
+
+		Rotate a word eight bits to the left: eg, rotate(1d2c3a4f) == 2c3a4f1d
+		Word is an char list of size 4 (32 bits overall).
+		"""
+		return word[1:] + word[:1]
+
+	# Rijndael Rcon
+	Rcon = [0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36,
+			0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97,
+			0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72,
+			0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66,
+			0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04,
+			0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d,
+			0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3,
+			0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61,
+			0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a,
+			0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40,
+			0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc,
+			0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5,
+			0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a,
+			0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d,
+			0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c,
+			0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35,
+			0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4,
+			0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc,
+			0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08,
+			0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a,
+			0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d,
+			0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2,
+			0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74,
+			0xe8, 0xcb ]
+
+	def getRconValue(self, num):
+		"""Retrieves a given Rcon Value"""
+		return self.Rcon[num]
+
+	def core(self, word, iteration):
+		"""Key schedule core."""
+		# rotate the 32-bit word 8 bits to the left
+		word = self.rotate(word)
+		# apply S-Box substitution on all 4 parts of the 32-bit word
+		for i in range(4):
+			word[i] = self.getSBoxValue(word[i])
+		# XOR the output of the rcon operation with i to the first part
+		# (leftmost) only
+		word[0] = word[0] ^ self.getRconValue(iteration)
+		return word
+
+	def expandKey(self, key, size, expandedKeySize):
+		"""Rijndael's key expansion.
+
+		Expands an 128,192,256 key into an 176,208,240 bytes key
+
+		expandedKey is a char list of large enough size,
+		key is the non-expanded key.
+		"""
+		# current expanded keySize, in bytes
+		currentSize = 0
+		rconIteration = 1
+		expandedKey = [0] * expandedKeySize
+
+		# set the 16, 24, 32 bytes of the expanded key to the input key
+		for j in range(size):
+			expandedKey[j] = key[j]
+		currentSize += size
+
+		while currentSize < expandedKeySize:
+			# assign the previous 4 bytes to the temporary value t
+			t = expandedKey[currentSize-4:currentSize]
+
+			# every 16,24,32 bytes we apply the core schedule to t
+			# and increment rconIteration afterwards
+			if currentSize % size == 0:
+				t = self.core(t, rconIteration)
+				rconIteration += 1
+			# For 256-bit keys, we add an extra sbox to the calculation
+			if size == self.keySize["SIZE_256"] and ((currentSize % size) == 16):
+				for l in range(4): t[l] = self.getSBoxValue(t[l])
+
+			# We XOR t with the four-byte block 16,24,32 bytes before the new
+			# expanded key.  This becomes the next four bytes in the expanded
+			# key.
+			for m in range(4):
+				expandedKey[currentSize] = expandedKey[currentSize - size] ^ \
+						t[m]
+				currentSize += 1
+
+		return expandedKey
+
+	def addRoundKey(self, state, roundKey):
+		"""Adds (XORs) the round key to the state."""
+		for i in range(16):
+			state[i] ^= roundKey[i]
+		return state
+
+	def createRoundKey(self, expandedKey, roundKeyPointer):
+		"""Create a round key.
+		Creates a round key from the given expanded key and the
+		position within the expanded key.
+		"""
+		roundKey = [0] * 16
+		for i in range(4):
+			for j in range(4):
+				roundKey[j*4+i] = expandedKey[roundKeyPointer + i*4 + j]
+		return roundKey
+
+	def galois_multiplication(self, a, b):
+		"""Galois multiplication of 8 bit characters a and b."""
+		p = 0
+		for counter in range(8):
+			if b & 1: p ^= a
+			hi_bit_set = a & 0x80
+			a <<= 1
+			# keep a 8 bit
+			a &= 0xFF
+			if hi_bit_set:
+				a ^= 0x1b
+			b >>= 1
+		return p
+
+	#
+	# substitute all the values from the state with the value in the SBox
+	# using the state value as index for the SBox
+	#
+	def subBytes(self, state, isInv):
+		if isInv: getter = self.getSBoxInvert
+		else: getter = self.getSBoxValue
+		for i in range(16): state[i] = getter(state[i])
+		return state
+
+	# iterate over the 4 rows and call shiftRow() with that row
+	def shiftRows(self, state, isInv):
+		for i in range(4):
+			state = self.shiftRow(state, i*4, i, isInv)
+		return state
+
+	# each iteration shifts the row to the left by 1
+	def shiftRow(self, state, statePointer, nbr, isInv):
+		for i in range(nbr):
+			if isInv:
+				state[statePointer:statePointer+4] = \
+						state[statePointer+3:statePointer+4] + \
+						state[statePointer:statePointer+3]
+			else:
+				state[statePointer:statePointer+4] = \
+						state[statePointer+1:statePointer+4] + \
+						state[statePointer:statePointer+1]
+		return state
+
+	# galois multiplication of the 4x4 matrix
+	def mixColumns(self, state, isInv):
+		# iterate over the 4 columns
+		for i in range(4):
+			# construct one column by slicing over the 4 rows
+			column = state[i:i+16:4]
+			# apply the mixColumn on one column
+			column = self.mixColumn(column, isInv)
+			# put the values back into the state
+			state[i:i+16:4] = column
+
+		return state
+
+	# galois multiplication of 1 column of the 4x4 matrix
+	def mixColumn(self, column, isInv):
+		if isInv: mult = [14, 9, 13, 11]
+		else: mult = [2, 1, 1, 3]
+		cpy = list(column)
+		g = self.galois_multiplication
+
+		column[0] = g(cpy[0], mult[0]) ^ g(cpy[3], mult[1]) ^ \
+					g(cpy[2], mult[2]) ^ g(cpy[1], mult[3])
+		column[1] = g(cpy[1], mult[0]) ^ g(cpy[0], mult[1]) ^ \
+					g(cpy[3], mult[2]) ^ g(cpy[2], mult[3])
+		column[2] = g(cpy[2], mult[0]) ^ g(cpy[1], mult[1]) ^ \
+					g(cpy[0], mult[2]) ^ g(cpy[3], mult[3])
+		column[3] = g(cpy[3], mult[0]) ^ g(cpy[2], mult[1]) ^ \
+					g(cpy[1], mult[2]) ^ g(cpy[0], mult[3])
+		return column
+
+	# applies the 4 operations of the forward round in sequence
+	def aes_round(self, state, roundKey):
+		state = self.subBytes(state, False)
+		state = self.shiftRows(state, False)
+		state = self.mixColumns(state, False)
+		state = self.addRoundKey(state, roundKey)
+		return state
+
+	# applies the 4 operations of the inverse round in sequence
+	def aes_invRound(self, state, roundKey):
+		state = self.shiftRows(state, True)
+		state = self.subBytes(state, True)
+		state = self.addRoundKey(state, roundKey)
+		state = self.mixColumns(state, True)
+		return state
+
+	# Perform the initial operations, the standard round, and the final
+	# operations of the forward aes, creating a round key for each round
+	def aes_main(self, state, expandedKey, nbrRounds):
+		state = self.addRoundKey(state, self.createRoundKey(expandedKey, 0))
+		i = 1
+		while i < nbrRounds:
+			state = self.aes_round(state, self.createRoundKey(expandedKey, 16*i))
+			i += 1
+		state = self.subBytes(state, False)
+		state = self.shiftRows(state, False)
+		state = self.addRoundKey(state, self.createRoundKey(expandedKey, 16*nbrRounds))
+		return state
+
+	# Perform the initial operations, the standard round, and the final
+	# operations of the inverse aes, creating a round key for each round
+	def aes_invMain(self, state, expandedKey, nbrRounds):
+		state = self.addRoundKey(state, self.createRoundKey(expandedKey, 16*nbrRounds))
+		i = nbrRounds - 1
+		while i > 0:
+			state = self.aes_invRound(state, self.createRoundKey(expandedKey, 16*i))
+			i -= 1
+		state = self.shiftRows(state, True)
+		state = self.subBytes(state, True)
+		state = self.addRoundKey(state, self.createRoundKey(expandedKey, 0))
+		return state
+
+	# encrypts a 128 bit input block against the given key of size specified
+	def encrypt(self, iput, key, size):
+		output = [0] * 16
+		# the number of rounds
+		nbrRounds = 0
+		# the 128 bit block to encode
+		block = [0] * 16
+		# set the number of rounds
+		if size == self.keySize["SIZE_128"]: nbrRounds = 10
+		elif size == self.keySize["SIZE_192"]: nbrRounds = 12
+		elif size == self.keySize["SIZE_256"]: nbrRounds = 14
+		else: return None
+
+		# the expanded keySize
+		expandedKeySize = 16*(nbrRounds+1)
+
+		# Set the block values, for the block:
+		# a0,0 a0,1 a0,2 a0,3
+		# a1,0 a1,1 a1,2 a1,3
+		# a2,0 a2,1 a2,2 a2,3
+		# a3,0 a3,1 a3,2 a3,3
+		# the mapping order is a0,0 a1,0 a2,0 a3,0 a0,1 a1,1 ... a2,3 a3,3
+		#
+		# iterate over the columns
+		for i in range(4):
+			# iterate over the rows
+			for j in range(4):
+				block[(i+(j*4))] = iput[(i*4)+j]
+
+		# expand the key into an 176, 208, 240 bytes key
+		# the expanded key
+		expandedKey = self.expandKey(key, size, expandedKeySize)
+
+		# encrypt the block using the expandedKey
+		block = self.aes_main(block, expandedKey, nbrRounds)
+
+		# unmap the block again into the output
+		for k in range(4):
+			# iterate over the rows
+			for l in range(4):
+				output[(k*4)+l] = block[(k+(l*4))]
+		return output
+
+	# decrypts a 128 bit input block against the given key of size specified
+	def decrypt(self, iput, key, size):
+		output = [0] * 16
+		# the number of rounds
+		nbrRounds = 0
+		# the 128 bit block to decode
+		block = [0] * 16
+		# set the number of rounds
+		if size == self.keySize["SIZE_128"]: nbrRounds = 10
+		elif size == self.keySize["SIZE_192"]: nbrRounds = 12
+		elif size == self.keySize["SIZE_256"]: nbrRounds = 14
+		else: return None
+
+		# the expanded keySize
+		expandedKeySize = 16*(nbrRounds+1)
+
+		# Set the block values, for the block:
+		# a0,0 a0,1 a0,2 a0,3
+		# a1,0 a1,1 a1,2 a1,3
+		# a2,0 a2,1 a2,2 a2,3
+		# a3,0 a3,1 a3,2 a3,3
+		# the mapping order is a0,0 a1,0 a2,0 a3,0 a0,1 a1,1 ... a2,3 a3,3
+
+		# iterate over the columns
+		for i in range(4):
+			# iterate over the rows
+			for j in range(4):
+				block[(i+(j*4))] = iput[(i*4)+j]
+		# expand the key into an 176, 208, 240 bytes key
+		expandedKey = self.expandKey(key, size, expandedKeySize)
+		# decrypt the block using the expandedKey
+		block = self.aes_invMain(block, expandedKey, nbrRounds)
+		# unmap the block again into the output
+		for k in range(4):
+			# iterate over the rows
+			for l in range(4):
+				output[(k*4)+l] = block[(k+(l*4))]
+		return output
+
+class AESModeOfOperation(object):
+
+	aes = AES()
+
+	# structure of supported modes of operation
+	modeOfOperation = dict(OFB=0, CFB=1, CBC=2)
+
+	# converts a 16 character string into a number array
+	def convertString(self, string, start, end, mode):
+		if end - start > 16: end = start + 16
+		if mode == self.modeOfOperation["CBC"]: ar = [0] * 16
+		else: ar = []
+
+		i = start
+		j = 0
+		while len(ar) < end - start:
+			ar.append(0)
+		while i < end:
+			ar[j] = ord(string[i])
+			j += 1
+			i += 1
+		return ar
+
+	# Mode of Operation Encryption
+	# stringIn - Input String
+	# mode - mode of type modeOfOperation
+	# hexKey - a hex key of the bit length size
+	# size - the bit length of the key
+	# hexIV - the 128 bit hex Initilization Vector
+	def encrypt(self, stringIn, mode, key, size, IV):
+		if len(key) % size:
+			return None
+		if len(IV) % 16:
+			return None
+		# the AES input/output
+		plaintext = []
+		iput = [0] * 16
+		output = []
+		ciphertext = [0] * 16
+		# the output cipher string
+		cipherOut = []
+		# char firstRound
+		firstRound = True
+		if stringIn != None:
+			for j in range(int(math.ceil(float(len(stringIn))/16))):
+				start = j*16
+				end = j*16+16
+				if  end > len(stringIn):
+					end = len(stringIn)
+				plaintext = self.convertString(stringIn, start, end, mode)
+				# print 'PT@%s:%s' % (j, plaintext)
+				if mode == self.modeOfOperation["CFB"]:
+					if firstRound:
+						output = self.aes.encrypt(IV, key, size)
+						firstRound = False
+					else:
+						output = self.aes.encrypt(iput, key, size)
+					for i in range(16):
+						if len(plaintext)-1 < i:
+							ciphertext[i] = 0 ^ output[i]
+						elif len(output)-1 < i:
+							ciphertext[i] = plaintext[i] ^ 0
+						elif len(plaintext)-1 < i and len(output) < i:
+							ciphertext[i] = 0 ^ 0
+						else:
+							ciphertext[i] = plaintext[i] ^ output[i]
+					for k in range(end-start):
+						cipherOut.append(ciphertext[k])
+					iput = ciphertext
+				elif mode == self.modeOfOperation["OFB"]:
+					if firstRound:
+						output = self.aes.encrypt(IV, key, size)
+						firstRound = False
+					else:
+						output = self.aes.encrypt(iput, key, size)
+					for i in range(16):
+						if len(plaintext)-1 < i:
+							ciphertext[i] = 0 ^ output[i]
+						elif len(output)-1 < i:
+							ciphertext[i] = plaintext[i] ^ 0
+						elif len(plaintext)-1 < i and len(output) < i:
+							ciphertext[i] = 0 ^ 0
+						else:
+							ciphertext[i] = plaintext[i] ^ output[i]
+					for k in range(end-start):
+						cipherOut.append(ciphertext[k])
+					iput = output
+				elif mode == self.modeOfOperation["CBC"]:
+					for i in range(16):
+						if firstRound:
+							iput[i] =  plaintext[i] ^ IV[i]
+						else:
+							iput[i] =  plaintext[i] ^ ciphertext[i]
+					# print 'IP@%s:%s' % (j, iput)
+					firstRound = False
+					ciphertext = self.aes.encrypt(iput, key, size)
+					# always 16 bytes because of the padding for CBC
+					for k in range(16):
+						cipherOut.append(ciphertext[k])
+		return mode, len(stringIn), cipherOut
+
+	# Mode of Operation Decryption
+	# cipherIn - Encrypted String
+	# originalsize - The unencrypted string length - required for CBC
+	# mode - mode of type modeOfOperation
+	# key - a number array of the bit length size
+	# size - the bit length of the key
+	# IV - the 128 bit number array Initilization Vector
+	def decrypt(self, cipherIn, originalsize, mode, key, size, IV):
+		# cipherIn = unescCtrlChars(cipherIn)
+		if len(key) % size:
+			return None
+		if len(IV) % 16:
+			return None
+		# the AES input/output
+		ciphertext = []
+		iput = []
+		output = []
+		plaintext = [0] * 16
+		# the output plain text string
+		stringOut = ''
+		# char firstRound
+		firstRound = True
+		if cipherIn != None:
+			for j in range(int(math.ceil(float(len(cipherIn))/16))):
+				start = j*16
+				end = j*16+16
+				if j*16+16 > len(cipherIn):
+					end = len(cipherIn)
+				ciphertext = cipherIn[start:end]
+				if mode == self.modeOfOperation["CFB"]:
+					if firstRound:
+						output = self.aes.encrypt(IV, key, size)
+						firstRound = False
+					else:
+						output = self.aes.encrypt(iput, key, size)
+					for i in range(16):
+						if len(output)-1 < i:
+							plaintext[i] = 0 ^ ciphertext[i]
+						elif len(ciphertext)-1 < i:
+							plaintext[i] = output[i] ^ 0
+						elif len(output)-1 < i and len(ciphertext) < i:
+							plaintext[i] = 0 ^ 0
+						else:
+							plaintext[i] = output[i] ^ ciphertext[i]
+					for k in range(end-start):
+						stringOut += chr(plaintext[k])
+					iput = ciphertext
+				elif mode == self.modeOfOperation["OFB"]:
+					if firstRound:
+						output = self.aes.encrypt(IV, key, size)
+						firstRound = False
+					else:
+						output = self.aes.encrypt(iput, key, size)
+					for i in range(16):
+						if len(output)-1 < i:
+							plaintext[i] = 0 ^ ciphertext[i]
+						elif len(ciphertext)-1 < i:
+							plaintext[i] = output[i] ^ 0
+						elif len(output)-1 < i and len(ciphertext) < i:
+							plaintext[i] = 0 ^ 0
+						else:
+							plaintext[i] = output[i] ^ ciphertext[i]
+					for k in range(end-start):
+						stringOut += chr(plaintext[k])
+					iput = output
+				elif mode == self.modeOfOperation["CBC"]:
+					output = self.aes.decrypt(ciphertext, key, size)
+					for i in range(16):
+						if firstRound:
+							plaintext[i] = IV[i] ^ output[i]
+						else:
+							plaintext[i] = iput[i] ^ output[i]
+					firstRound = False
+					if originalsize is not None and originalsize < end:
+						for k in range(originalsize-start):
+							stringOut += chr(plaintext[k])
+					else:
+						for k in range(end-start):
+							stringOut += chr(plaintext[k])
+					iput = ciphertext
+		return stringOut
+
+# end of aes.py code
+
+# pywallet crypter implementation
+
+crypter = None
+
+try:
+	from Crypto.Cipher import AES
+	crypter = 'pycrypto'
+except:
+	pass
+
+class Crypter_pycrypto( object ):
+	def SetKeyFromPassphrase(self, vKeyData, vSalt, nDerivIterations, nDerivationMethod):
+		if nDerivationMethod != 0:
+			return 0
+		data = vKeyData + vSalt
+		for i in xrange(nDerivIterations):
+			data = hashlib.sha512(data).digest()
+		self.SetKey(data[0:32])
+		self.SetIV(data[32:32+16])
+		return len(data)
+
+	def SetKey(self, key):
+		self.chKey = key
+
+	def SetIV(self, iv):
+		self.chIV = iv[0:16]
+
+	def Encrypt(self, data):
+		return AES.new(self.chKey,AES.MODE_CBC,self.chIV).encrypt(data)[0:32]
+
+	def Decrypt(self, data):
+		return AES.new(self.chKey,AES.MODE_CBC,self.chIV).decrypt(data)[0:32]
+
+try:
+	if not crypter:
+		import ctypes
+		import ctypes.util
+		ssl = ctypes.cdll.LoadLibrary (ctypes.util.find_library ('ssl') or 'libeay32')
+		crypter = 'ssl'
+except:
+	pass
+
+class Crypter_ssl(object):
+	def __init__(self):
+		self.chKey = ctypes.create_string_buffer (32)
+		self.chIV = ctypes.create_string_buffer (16)
+
+	def SetKeyFromPassphrase(self, vKeyData, vSalt, nDerivIterations, nDerivationMethod):
+		if nDerivationMethod != 0:
+			return 0
+		strKeyData = ctypes.create_string_buffer (vKeyData)
+		chSalt = ctypes.create_string_buffer (vSalt)
+		return ssl.EVP_BytesToKey(ssl.EVP_aes_256_cbc(), ssl.EVP_sha512(), chSalt, strKeyData,
+			len(vKeyData), nDerivIterations, ctypes.byref(self.chKey), ctypes.byref(self.chIV))
+
+	def SetKey(self, key):
+		self.chKey = ctypes.create_string_buffer(key)
+
+	def SetIV(self, iv):
+		self.chIV = ctypes.create_string_buffer(iv)
+
+	def Encrypt(self, data):
+		buf = ctypes.create_string_buffer(len(data) + 16)
+		written = ctypes.c_int(0)
+		final = ctypes.c_int(0)
+		ctx = ssl.EVP_CIPHER_CTX_new()
+		ssl.EVP_CIPHER_CTX_init(ctx)
+		ssl.EVP_EncryptInit_ex(ctx, ssl.EVP_aes_256_cbc(), None, self.chKey, self.chIV)
+		ssl.EVP_EncryptUpdate(ctx, buf, ctypes.byref(written), data, len(data))
+		output = buf.raw[:written.value]
+		ssl.EVP_EncryptFinal_ex(ctx, buf, ctypes.byref(final))
+		output += buf.raw[:final.value]
+		return output
+
+	def Decrypt(self, data):
+		buf = ctypes.create_string_buffer(len(data) + 16)
+		written = ctypes.c_int(0)
+		final = ctypes.c_int(0)
+		ctx = ssl.EVP_CIPHER_CTX_new()
+		ssl.EVP_CIPHER_CTX_init(ctx)
+		ssl.EVP_DecryptInit_ex(ctx, ssl.EVP_aes_256_cbc(), None, self.chKey, self.chIV)
+		ssl.EVP_DecryptUpdate(ctx, buf, ctypes.byref(written), data, len(data))
+		output = buf.raw[:written.value]
+		ssl.EVP_DecryptFinal_ex(ctx, buf, ctypes.byref(final))
+		output += buf.raw[:final.value]
+		return output
+
+class Crypter_pure(object):
+	def __init__(self):
+		self.m = AESModeOfOperation()
+		self.cbc = self.m.modeOfOperation["CBC"]
+		self.sz = self.m.aes.keySize["SIZE_256"]
+
+	def SetKeyFromPassphrase(self, vKeyData, vSalt, nDerivIterations, nDerivationMethod):
+		if nDerivationMethod != 0:
+			return 0
+		data = vKeyData + vSalt
+		for i in xrange(nDerivIterations):
+			data = hashlib.sha512(data).digest()
+		self.SetKey(data[0:32])
+		self.SetIV(data[32:32+16])
+		return len(data)
+
+	def SetKey(self, key):
+		self.chKey = [ord(i) for i in key]
+
+	def SetIV(self, iv):
+		self.chIV = [ord(i) for i in iv]
+
+	def Encrypt(self, data):
+		mode, size, cypher = self.m.encrypt(data, self.cbc, self.chKey, self.sz, self.chIV)
+		return ''.join(map(chr, cypher))
+
+	def Decrypt(self, data):
+		chData = [ord(i) for i in data]
+		return self.m.decrypt(chData, self.sz, self.cbc, self.chKey, self.sz, self.chIV)
+
+# secp256k1
+
+_p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2FL
+_r = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141L
+_b = 0x0000000000000000000000000000000000000000000000000000000000000007L
+_a = 0x0000000000000000000000000000000000000000000000000000000000000000L
+_Gx = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798L
+_Gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8L
+
+# python-ecdsa code (EC_KEY implementation)
+
+class CurveFp( object ):
+	def __init__( self, p, a, b ):
+		self.__p = p
+		self.__a = a
+		self.__b = b
+
+	def p( self ):
+		return self.__p
+
+	def a( self ):
+		return self.__a
+
+	def b( self ):
+		return self.__b
+
+	def contains_point( self, x, y ):
+		return ( y * y - ( x * x * x + self.__a * x + self.__b ) ) % self.__p == 0
+
+class Point( object ):
+	def __init__( self, curve, x, y, order = None ):
+		self.__curve = curve
+		self.__x = x
+		self.__y = y
+		self.__order = order
+		if self.__curve: assert self.__curve.contains_point( x, y )
+		if order: assert self * order == INFINITY
+
+	def __add__( self, other ):
+		if other == INFINITY: return self
+		if self == INFINITY: return other
+		assert self.__curve == other.__curve
+		if self.__x == other.__x:
+			if ( self.__y + other.__y ) % self.__curve.p() == 0:
+				return INFINITY
+			else:
+				return self.double()
+
+		p = self.__curve.p()
+		l = ( ( other.__y - self.__y ) * \
+					inverse_mod( other.__x - self.__x, p ) ) % p
+		x3 = ( l * l - self.__x - other.__x ) % p
+		y3 = ( l * ( self.__x - x3 ) - self.__y ) % p
+		return Point( self.__curve, x3, y3 )
+
+	def __mul__( self, other ):
+		def leftmost_bit( x ):
+			assert x > 0
+			result = 1L
+			while result <= x: result = 2 * result
+			return result / 2
+
+		e = other
+		if self.__order: e = e % self.__order
+		if e == 0: return INFINITY
+		if self == INFINITY: return INFINITY
+		assert e > 0
+		e3 = 3 * e
+		negative_self = Point( self.__curve, self.__x, -self.__y, self.__order )
+		i = leftmost_bit( e3 ) / 2
+		result = self
+		while i > 1:
+			result = result.double()
+			if ( e3 & i ) != 0 and ( e & i ) == 0: result = result + self
+			if ( e3 & i ) == 0 and ( e & i ) != 0: result = result + negative_self
+			i = i / 2
+		return result
+
+	def __rmul__( self, other ):
+		return self * other
+
+	def __str__( self ):
+		if self == INFINITY: return "infinity"
+		return "(%d,%d)" % ( self.__x, self.__y )
+
+	def double( self ):
+		if self == INFINITY:
+			return INFINITY
+
+		p = self.__curve.p()
+		a = self.__curve.a()
+		l = ( ( 3 * self.__x * self.__x + a ) * \
+					inverse_mod( 2 * self.__y, p ) ) % p
+		x3 = ( l * l - 2 * self.__x ) % p
+		y3 = ( l * ( self.__x - x3 ) - self.__y ) % p
+		return Point( self.__curve, x3, y3 )
+
+	def x( self ):
+		return self.__x
+
+	def y( self ):
+		return self.__y
+
+	def curve( self ):
+		return self.__curve
+
+	def order( self ):
+		return self.__order
+
+INFINITY = Point( None, None, None )
+
+def inverse_mod( a, m ):
+	if a < 0 or m <= a: a = a % m
+	c, d = a, m
+	uc, vc, ud, vd = 1, 0, 0, 1
+	while c != 0:
+		q, c, d = divmod( d, c ) + ( c, )
+		uc, vc, ud, vd = ud - q*uc, vd - q*vc, uc, vc
+	assert d == 1
+	if ud > 0: return ud
+	else: return ud + m
+
+class Signature( object ):
+	def __init__( self, r, s ):
+		self.r = r
+		self.s = s
+
+class Public_key( object ):
+	def __init__( self, generator, point ):
+		self.curve = generator.curve()
+		self.generator = generator
+		self.point = point
+		n = generator.order()
+		if not n:
+			raise RuntimeError, "Generator point must have order."
+		if not n * point == INFINITY:
+			raise RuntimeError, "Generator point order is bad."
+		if point.x() < 0 or n <= point.x() or point.y() < 0 or n <= point.y():
+			raise RuntimeError, "Generator point has x or y out of range."
+
+	def verifies( self, hash, signature ):
+		G = self.generator
+		n = G.order()
+		r = signature.r
+		s = signature.s
+		if r < 1 or r > n-1: return False
+		if s < 1 or s > n-1: return False
+		c = inverse_mod( s, n )
+		u1 = ( hash * c ) % n
+		u2 = ( r * c ) % n
+		xy = u1 * G + u2 * self.point
+		v = xy.x() % n
+		return v == r
+
+class Private_key( object ):
+	def __init__( self, public_key, secret_multiplier ):
+		self.public_key = public_key
+		self.secret_multiplier = secret_multiplier
+
+	def der( self ):
+		hex_der_key = '06052b8104000a30740201010420' + \
+			'%064x' % self.secret_multiplier + \
+			'a00706052b8104000aa14403420004' + \
+			'%064x' % self.public_key.point.x() + \
+			'%064x' % self.public_key.point.y()
+		return hex_der_key.decode('hex')
+
+	def sign( self, hash, random_k ):
+		G = self.public_key.generator
+		n = G.order()
+		k = random_k % n
+		p1 = k * G
+		r = p1.x()
+		if r == 0: raise RuntimeError, "amazingly unlucky random number r"
+		s = ( inverse_mod( k, n ) * \
+					( hash + ( self.secret_multiplier * r ) % n ) ) % n
+		if s == 0: raise RuntimeError, "amazingly unlucky random number s"
+		return Signature( r, s )
+
+class EC_KEY(object):
+	def __init__( self, secret ):
+		curve = CurveFp( _p, _a, _b )
+		generator = Point( curve, _Gx, _Gy, _r )
+		self.pubkey = Public_key( generator, generator * secret )
+		self.privkey = Private_key( self.pubkey, secret )
+		self.secret = secret
+
+# end of python-ecdsa code
+
+# pywallet openssl private key implementation
+
+def i2d_ECPrivateKey(pkey, compressed=False):
+	if compressed:
+		key = '3081d30201010420' + \
+			'%064x' % pkey.secret + \
+			'a081a53081a2020101302c06072a8648ce3d0101022100' + \
+			'%064x' % _p + \
+			'3006040100040107042102' + \
+			'%064x' % _Gx + \
+			'022100' + \
+			'%064x' % _r + \
+			'020101a124032200'
+	else:
+		key = '308201130201010420' + \
+			'%064x' % pkey.secret + \
+			'a081a53081a2020101302c06072a8648ce3d0101022100' + \
+			'%064x' % _p + \
+			'3006040100040107044104' + \
+			'%064x' % _Gx + \
+			'%064x' % _Gy + \
+			'022100' + \
+			'%064x' % _r + \
+			'020101a144034200'
+
+	return key.decode('hex') + i2o_ECPublicKey(pkey, compressed)
+
+def i2o_ECPublicKey(pkey, compressed=False):
+	# public keys are 65 bytes long (520 bits)
+	# 0x04 + 32-byte X-coordinate + 32-byte Y-coordinate
+	# 0x00 = point at infinity, 0x02 and 0x03 = compressed, 0x04 = uncompressed
+	# compressed keys: <sign> <x> where <sign> is 0x02 if y is even and 0x03 if y is odd
+	if compressed:
+		if pkey.pubkey.point.y() & 1:
+			key = '03' + '%064x' % pkey.pubkey.point.x()
+		else:
+			key = '02' + '%064x' % pkey.pubkey.point.x()
+	else:
+		key = '04' + \
+			'%064x' % pkey.pubkey.point.x() + \
+			'%064x' % pkey.pubkey.point.y()
+
+	return key.decode('hex')
+
+# bitcointools hashes and base58 implementation
+
+def hash_160(public_key):
+	md = hashlib.new('ripemd160')
+	md.update(hashlib.sha256(public_key).digest())
+	return md.digest()
+
+def public_key_to_bc_address(public_key):
+	h160 = hash_160(public_key)
+	return hash_160_to_bc_address(h160)
+
+def hash_160_to_bc_address(h160):
+	vh160 = chr(addrtype) + h160
+	h = Hash(vh160)
+	addr = vh160 + h[0:4]
+	return b58encode(addr)
+
+def bc_address_to_hash_160(addr):
+	bytes = b58decode(addr, 25)
+	return bytes[1:21]
+
+__b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
+__b58base = len(__b58chars)
+
+def b58encode(v):
+	""" encode v, which is a string of bytes, to base58.
+	"""
+
+	long_value = 0L
+	for (i, c) in enumerate(v[::-1]):
+		long_value += (256**i) * ord(c)
+
+	result = ''
+	while long_value >= __b58base:
+		div, mod = divmod(long_value, __b58base)
+		result = __b58chars[mod] + result
+		long_value = div
+	result = __b58chars[long_value] + result
+
+	# Bitcoin does a little leading-zero-compression:
+	# leading 0-bytes in the input become leading-1s
+	nPad = 0
+	for c in v:
+		if c == '\0': nPad += 1
+		else: break
+
+	return (__b58chars[0]*nPad) + result
+
+def b58decode(v, length):
+	""" decode v into a string of len bytes
+	"""
+	long_value = 0L
+	for (i, c) in enumerate(v[::-1]):
+		long_value += __b58chars.find(c) * (__b58base**i)
+
+	result = ''
+	while long_value >= 256:
+		div, mod = divmod(long_value, 256)
+		result = chr(mod) + result
+		long_value = div
+	result = chr(long_value) + result
+
+	nPad = 0
+	for c in v:
+		if c == __b58chars[0]: nPad += 1
+		else: break
+
+	result = chr(0)*nPad + result
+	if length is not None and len(result) != length:
+		return None
+
+	return result
+
+# end of bitcointools base58 implementation
+
+
+# address handling code
+
+def Hash(data):
+	return hashlib.sha256(hashlib.sha256(data).digest()).digest()
+
+def EncodeBase58Check(secret):
+	hash = Hash(secret)
+	return b58encode(secret + hash[0:4])
+
+def DecodeBase58Check(sec):
+	vchRet = b58decode(sec, None)
+	secret = vchRet[0:-4]
+	csum = vchRet[-4:]
+	hash = Hash(secret)
+	cs32 = hash[0:4]
+	if cs32 != csum:
+		return None
+	else:
+		return secret
+
+def PrivKeyToSecret(privkey):
+	if len(privkey) == 279:
+		return privkey[9:9+32]
+	else:
+		return privkey[8:8+32]
+
+def SecretToASecret(secret, compressed=False):
+	vchIn = chr((addrtype+128)&255) + secret
+	if compressed: vchIn += '\01'
+	return EncodeBase58Check(vchIn)
+
+def ASecretToSecret(sec):
+	vch = DecodeBase58Check(sec)
+	if vch and vch[0] == chr((addrtype+128)&255):
+		return vch[1:]
+	else:
+		return False
+
+def regenerate_key(sec):
+	b = ASecretToSecret(sec)
+	if not b:
+		return False
+	b = b[0:32]
+	secret = int('0x' + b.encode('hex'), 16)
+	return EC_KEY(secret)
+
+def GetPubKey(pkey, compressed=False):
+	return i2o_ECPublicKey(pkey, compressed)
+
+def GetPrivKey(pkey, compressed=False):
+	return i2d_ECPrivateKey(pkey, compressed)
+
+def GetSecret(pkey):
+	return ('%064x' % pkey.secret).decode('hex')
+
+def is_compressed(sec):
+	b = ASecretToSecret(sec)
+	return len(b) == 33
+
+# bitcointools wallet.dat handling code
+
+def create_env(db_dir):
+	db_env = DBEnv(0)
+	r = db_env.open(db_dir, (DB_CREATE|DB_INIT_LOCK|DB_INIT_LOG|DB_INIT_MPOOL|DB_INIT_TXN|DB_THREAD|DB_RECOVER))
+	return db_env
+
+def parse_CAddress(vds):
+	d = {'ip':'0.0.0.0','port':0,'nTime': 0}
+	try:
+		d['nVersion'] = vds.read_int32()
+		d['nTime'] = vds.read_uint32()
+		d['nServices'] = vds.read_uint64()
+		d['pchReserved'] = vds.read_bytes(12)
+		d['ip'] = socket.inet_ntoa(vds.read_bytes(4))
+		d['port'] = vds.read_uint16()
+	except:
+		pass
+	return d
+
+def deserialize_CAddress(d):
+	return d['ip']+":"+str(d['port'])
+
+def parse_BlockLocator(vds):
+	d = { 'hashes' : [] }
+	nHashes = vds.read_compact_size()
+	for i in xrange(nHashes):
+		d['hashes'].append(vds.read_bytes(32))
+		return d
+
+def deserialize_BlockLocator(d):
+  result = "Block Locator top: "+d['hashes'][0][::-1].encode('hex_codec')
+  return result
+
+def parse_setting(setting, vds):
+	if setting[0] == "f":    # flag (boolean) settings
+		return str(vds.read_boolean())
+	elif setting[0:4] == "addr": # CAddress
+		d = parse_CAddress(vds)
+		return deserialize_CAddress(d)
+	elif setting == "nTransactionFee":
+		return vds.read_int64()
+	elif setting == "nLimitProcessors":
+		return vds.read_int32()
+	return 'unknown setting'
+
+class SerializationError(Exception):
+	""" Thrown when there's a problem deserializing or serializing """
+
+class BCDataStream(object):
+	def __init__(self):
+		self.input = None
+		self.read_cursor = 0
+
+	def clear(self):
+		self.input = None
+		self.read_cursor = 0
+
+	def write(self, bytes):    # Initialize with string of bytes
+		if self.input is None:
+			self.input = bytes
+		else:
+			self.input += bytes
+
+	def map_file(self, file, start):    # Initialize with bytes from file
+		self.input = mmap.mmap(file.fileno(), 0, access=mmap.ACCESS_READ)
+		self.read_cursor = start
+	def seek_file(self, position):
+		self.read_cursor = position
+	def close_file(self):
+		self.input.close()
+
+	def read_string(self):
+		# Strings are encoded depending on length:
+		# 0 to 252 :    1-byte-length followed by bytes (if any)
+		# 253 to 65,535 : byte'253' 2-byte-length followed by bytes
+		# 65,536 to 4,294,967,295 : byte '254' 4-byte-length followed by bytes
+		# ... and the Bitcoin client is coded to understand:
+		# greater than 4,294,967,295 : byte '255' 8-byte-length followed by bytes of string
+		# ... but I don't think it actually handles any strings that big.
+		if self.input is None:
+			raise SerializationError("call write(bytes) before trying to deserialize")
+
+		try:
+			length = self.read_compact_size()
+		except IndexError:
+			raise SerializationError("attempt to read past end of buffer")
+
+		return self.read_bytes(length)
+
+	def write_string(self, string):
+		# Length-encoded as with read-string
+		self.write_compact_size(len(string))
+		self.write(string)
+
+	def read_bytes(self, length):
+		try:
+			result = self.input[self.read_cursor:self.read_cursor+length]
+			self.read_cursor += length
+			return result
+		except IndexError:
+			raise SerializationError("attempt to read past end of buffer")
+
+		return ''
+
+	def read_boolean(self): return self.read_bytes(1)[0] != chr(0)
+	def read_int16(self): return self._read_num('<h')
+	def read_uint16(self): return self._read_num('<H')
+	def read_int32(self): return self._read_num('<i')
+	def read_uint32(self): return self._read_num('<I')
+	def read_int64(self): return self._read_num('<q')
+	def read_uint64(self): return self._read_num('<Q')
+
+	def write_boolean(self, val): return self.write(chr(1) if val else chr(0))
+	def write_int16(self, val): return self._write_num('<h', val)
+	def write_uint16(self, val): return self._write_num('<H', val)
+	def write_int32(self, val): return self._write_num('<i', val)
+	def write_uint32(self, val): return self._write_num('<I', val)
+	def write_int64(self, val): return self._write_num('<q', val)
+	def write_uint64(self, val): return self._write_num('<Q', val)
+
+	def read_compact_size(self):
+		size = ord(self.input[self.read_cursor])
+		self.read_cursor += 1
+		if size == 253:
+			size = self._read_num('<H')
+		elif size == 254:
+			size = self._read_num('<I')
+		elif size == 255:
+			size = self._read_num('<Q')
+		return size
+
+	def write_compact_size(self, size):
+		if size < 0:
+			raise SerializationError("attempt to write size < 0")
+		elif size < 253:
+			self.write(chr(size))
+		elif size < 2**16:
+			self.write('\xfd')
+			self._write_num('<H', size)
+		elif size < 2**32:
+			self.write('\xfe')
+			self._write_num('<I', size)
+		elif size < 2**64:
+			self.write('\xff')
+			self._write_num('<Q', size)
+
+	def _read_num(self, format):
+		(i,) = struct.unpack_from(format, self.input, self.read_cursor)
+		self.read_cursor += struct.calcsize(format)
+		return i
+
+	def _write_num(self, format, num):
+		s = struct.pack(format, num)
+		self.write(s)
+
+def open_wallet(db_env, db_file="wallet.dat", writable=False):
+	db = DB(db_env)
+	flags = DB_THREAD | (DB_CREATE if writable else DB_RDONLY)
+	try:
+		r = db.open(db_file, "main", DB_BTREE, flags)
+	except DBError:
+		r = True
+
+	if r is not None:
+		logging.error("Couldn't open " + db_file + "/main. Try quitting Bitcoin and running this again.")
+		sys.exit(1)
+
+	return db
+
+def parse_wallet(db, item_callback):
+	kds = BCDataStream()
+	vds = BCDataStream()
+
+	for (key, value) in db.items():
+		d = { }
+
+		kds.clear(); kds.write(key)
+		vds.clear(); vds.write(value)
+
+		type = kds.read_string()
+
+		d["__key__"] = key
+		d["__value__"] = value
+		d["__type__"] = type
+
+		try:
+			if type == "tx":
+				d["tx_id"] = kds.read_bytes(32)
+			elif type == "name":
+				d['hash'] = kds.read_string()
+				d['name'] = vds.read_string()
+			elif type == "version":
+				d['version'] = vds.read_uint32()
+			elif type == "minversion":
+				d['minversion'] = vds.read_uint32()
+			elif type == "setting":
+				d['setting'] = kds.read_string()
+				d['value'] = parse_setting(d['setting'], vds)
+			elif type == "key":
+				d['public_key'] = kds.read_bytes(kds.read_compact_size())
+				d['private_key'] = vds.read_bytes(vds.read_compact_size())
+			elif type == "wkey":
+				d['public_key'] = kds.read_bytes(kds.read_compact_size())
+				d['private_key'] = vds.read_bytes(vds.read_compact_size())
+				d['created'] = vds.read_int64()
+				d['expires'] = vds.read_int64()
+				d['comment'] = vds.read_string()
+			elif type == "ckey":
+				d['public_key'] = kds.read_bytes(kds.read_compact_size())
+				d['crypted_key'] = vds.read_bytes(vds.read_compact_size())
+			elif type == "mkey":
+				d['nID'] = kds.read_int32()
+				d['crypted_key'] = vds.read_bytes(vds.read_compact_size())
+				d['salt'] = vds.read_bytes(vds.read_compact_size())
+				d['nDerivationMethod'] = vds.read_int32()
+				d['nDeriveIterations'] = vds.read_int32()
+				d['vchOtherDerivationParameters'] = vds.read_bytes(vds.read_compact_size())
+			elif type == "defaultkey":
+				d['key'] = vds.read_bytes(vds.read_compact_size())
+			elif type == "pool":
+				d['n'] = kds.read_int64()
+				d['nVersion'] = vds.read_int32()
+				d['nTime'] = vds.read_int64()
+				d['public_key'] = vds.read_bytes(vds.read_compact_size())
+			elif type == "acc":
+				d['account'] = kds.read_string()
+				d['nVersion'] = vds.read_int32()
+				d['public_key'] = vds.read_bytes(vds.read_compact_size())
+			elif type == "acentry":
+				d['account'] = kds.read_string()
+				d['n'] = kds.read_uint64()
+				d['nVersion'] = vds.read_int32()
+				d['nCreditDebit'] = vds.read_int64()
+				d['nTime'] = vds.read_int64()
+				d['otherAccount'] = vds.read_string()
+				d['comment'] = vds.read_string()
+			elif type == "bestblock":
+				d['nVersion'] = vds.read_int32()
+				d.update(parse_BlockLocator(vds))
+
+			item_callback(type, d)
+
+		except Exception, e:
+			traceback.print_exc()
+			print("ERROR parsing wallet.dat, type %s" % type)
+			print("key data in hex: %s"%key.encode('hex_codec'))
+			print("value data in hex: %s"%value.encode('hex_codec'))
+			sys.exit(1)
+
+def update_wallet(db, type, data):
+	"""Write a single item to the wallet.
+	db must be open with writable=True.
+	type and data are the type code and data dictionary as parse_wallet would
+	give to item_callback.
+	data's __key__, __value__ and __type__ are ignored; only the primary data
+	fields are used.
+	"""
+	d = data
+	kds = BCDataStream()
+	vds = BCDataStream()
+
+	# Write the type code to the key
+	kds.write_string(type)
+	vds.write("")                         # Ensure there is something
+
+	try:
+		if type == "tx":
+			raise NotImplementedError("Writing items of type 'tx'")
+			kds.write(d['tx_id'])
+		elif type == "name":
+			kds.write_string(d['hash'])
+			vds.write_string(d['name'])
+		elif type == "version":
+			vds.write_uint32(d['version'])
+		elif type == "minversion":
+			vds.write_uint32(d['minversion'])
+		elif type == "setting":
+			raise NotImplementedError("Writing items of type 'setting'")
+			kds.write_string(d['setting'])
+			#d['value'] = parse_setting(d['setting'], vds)
+		elif type == "key":
+			kds.write_string(d['public_key'])
+			vds.write_string(d['private_key'])
+		elif type == "wkey":
+			kds.write_string(d['public_key'])
+			vds.write_string(d['private_key'])
+			vds.write_int64(d['created'])
+			vds.write_int64(d['expires'])
+			vds.write_string(d['comment'])
+		elif type == "ckey":
+			kds.write_string(d['public_key'])
+			vds.write_string(d['crypted_key'])
+		elif type == "defaultkey":
+			vds.write_string(d['key'])
+		elif type == "pool":
+			kds.write_int64(d['n'])
+			vds.write_int32(d['nVersion'])
+			vds.write_int64(d['nTime'])
+			vds.write_string(d['public_key'])
+		elif type == "acc":
+			kds.write_string(d['account'])
+			vds.write_int32(d['nVersion'])
+			vds.write_string(d['public_key'])
+		elif type == "acentry":
+			kds.write_string(d['account'])
+			kds.write_uint64(d['n'])
+			vds.write_int32(d['nVersion'])
+			vds.write_int64(d['nCreditDebit'])
+			vds.write_int64(d['nTime'])
+			vds.write_string(d['otherAccount'])
+			vds.write_string(d['comment'])
+		elif type == "bestblock":
+			vds.write_int32(d['nVersion'])
+			vds.write_compact_size(len(d['hashes']))
+			for h in d['hashes']:
+				vds.write(h)
+		else:
+			print "Unknown key type: "+type
+
+		# Write the key/value pair to the database
+		db.put(kds.input, vds.input)
+
+	except Exception, e:
+		print("ERROR writing to wallet.dat, type %s"%type)
+		print("data dictionary: %r"%data)
+		traceback.print_exc()
+
+
+def read_wallet(json_db, db_env, db_file, print_wallet, print_wallet_transactions, transaction_filter):
+
+	db = open_wallet(db_env, db_file)
+
+	json_db['keys'] = []
+	json_db['pool'] = []
+	json_db['names'] = {}
+
+	def item_callback(type, d):
+
+		global password
+
+		if type == "name":
+			json_db['names'][d['hash']] = d['name']
+
+		elif type == "version":
+			json_db['version'] = d['version']
+
+		elif type == "minversion":
+			json_db['minversion'] = d['minversion']
+
+		elif type == "setting":
+			if not json_db.has_key('settings'): json_db['settings'] = {}
+			json_db["settings"][d['setting']] = d['value']
+
+		elif type == "defaultkey":
+			json_db['defaultkey'] = public_key_to_bc_address(d['key'])
+
+		elif type == "key":
+			addr = public_key_to_bc_address(d['public_key'])
+			compressed = d['public_key'][0] != '\04'
+			sec = SecretToASecret(PrivKeyToSecret(d['private_key']), compressed)
+			private_keys.append(sec)
+			json_db['keys'].append({'addr' : addr, 'sec' : sec})
+#            json_db['keys'].append({'addr' : addr, 'sec' : sec,
+#                'secret':PrivKeyToSecret(d['private_key']).encode('hex'),
+#                'pubkey':d['public_key'].encode('hex'),
+#                'privkey':d['private_key'].encode('hex')})
+
+		elif type == "wkey":
+			if not json_db.has_key('wkey'): json_db['wkey'] = []
+			json_db['wkey']['created'] = d['created']
+
+		elif type == "ckey":
+			addr = public_key_to_bc_address(d['public_key'])
+			ckey = d['crypted_key']
+			pubkey = d['public_key']
+			json_db['keys'].append( {'addr' : addr, 'ckey': ckey.encode('hex'), 'pubkey': pubkey.encode('hex') })
+
+		elif type == "mkey":
+			mkey = {}
+			mkey['nID'] = d['nID']
+			mkey['crypted_key'] = d['crypted_key'].encode('hex')
+			mkey['salt'] = d['salt'].encode('hex')
+			mkey['nDeriveIterations'] = d['nDeriveIterations']
+			mkey['nDerivationMethod'] = d['nDerivationMethod']
+			mkey['vchOtherDerivationParameters'] = d['vchOtherDerivationParameters'].encode('hex')
+			json_db['mkey'] = mkey
+
+			if password == None and \
+				('json' in opts or 'keysforaddrs' in opts or 'keys' in opts):
+				from mmgen.util import get_bitcoind_passphrase
+				password = get_bitcoind_passphrase("Enter password: ",opts)
+
+			if password != None:
+				global crypter
+				if crypter == 'pycrypto':
+					crypter = Crypter_pycrypto()
+				elif crypter == 'ssl':
+					crypter = Crypter_ssl()
+				else:
+					crypter = Crypter_pure()
+					logging.warning("pycrypto or libssl not found, decryption may be slow")
+				res = crypter.SetKeyFromPassphrase(password, d['salt'], d['nDeriveIterations'], d['nDerivationMethod'])
+				if res == 0:
+					logging.error("Unsupported derivation method")
+					sys.exit(1)
+				masterkey = crypter.Decrypt(d['crypted_key'])
+				crypter.SetKey(masterkey)
+
+		elif type == "pool":
+			json_db['pool'].append( {'n': d['n'], 'addr': public_key_to_bc_address(d['public_key']), 'nTime' : d['nTime'] } )
+
+		elif type == "acc":
+			json_db['acc'] = d['account']
+#			msg("Account %s (current key: %s)"%(d['account'], public_key_to_bc_address(d['public_key'])))
+
+		elif type == "acentry":
+			json_db['acentry'] = (d['account'], d['nCreditDebit'], d['otherAccount'], time.ctime(d['nTime']), d['n'], d['comment'])
+
+		elif type == "bestblock":
+			json_db['bestblock'] = d['hashes'][0][::-1].encode('hex_codec')
+
+		else:
+			json_db[type] = 'unsupported'
+
+	parse_wallet(db, item_callback)
+
+	db.close()
+
+	for k in json_db['keys']:
+		addr = k['addr']
+		if addr in json_db['names'].keys():
+			k["label"] = json_db['names'][addr]
+		else:
+			k["reserve"] = 1
+
+	if 'mkey' in json_db.keys() and password != None:
+		check = True
+		for k in json_db['keys']:
+			ckey = k['ckey'].decode('hex')
+			public_key = k['pubkey'].decode('hex')
+			crypter.SetIV(Hash(public_key))
+			secret = crypter.Decrypt(ckey)
+			compressed = public_key[0] != '\04'
+
+			if check:
+				check = False
+				pkey = EC_KEY(int('0x' + secret.encode('hex'), 16))
+				if public_key != GetPubKey(pkey, compressed):
+					logging.error("wrong password")
+					sys.exit(1)
+
+			sec = SecretToASecret(secret, compressed)
+			k['sec'] = sec
+			k['secret'] = secret.encode('hex')
+			del(k['ckey'])
+			del(k['secret'])
+			del(k['pubkey'])
+			private_keys.append(sec)
+
+	del(json_db['pool'])
+	del(json_db['names'])
+
+
+# Non-portable.  For Windows, works only if supplied filename is in current dir
+
+# main()
+
+import os.path
+infile = os.path.abspath(cmd_args[0])
+db_dir,db_file = os.path.dirname(infile),os.path.basename(infile)
+
+#	print "[%s] [%s]" % (db_dir,db_file)
+
+db_env = create_env(db_dir)
+
+read_wallet(json_db, db_env, db_file, True, True, "")
+
+if json_db.get('minversion') > max_version:
+	print "Version mismatch (must be <= %d)" % max_version
+	exit(1)
+
+wallet_addrs = [i['addr'] for i in json_db['keys']]
+
+if 'json' in opts:
+	data = [json.dumps(json_db, sort_keys=True, indent=4)]
+	ext,what = "json","json dump"
+
+elif 'keys' in opts:
+	data = sorted([i['sec'] for i in json_db['keys']])
+	ext,what = "keys","private keys"
+
+elif 'addrs' in opts:
+	data = sorted([i['addr'] for i in json_db['keys']])
+	ext,what = "addrs","addresses"
+
+elif 'keysforaddrs' in opts:
+	from mmgen.util import get_lines_from_file
+	usr_addrs = set(get_lines_from_file(opts['keysforaddrs'],"addresses",trim_comments=True))
+	data = [i['sec'] for i in json_db['keys'] if i['addr'] in usr_addrs]
+	ext,what = "keys","private keys"
+	if len(data) < len(usr_addrs):
+		msg("Warning: not all requested keys found")
+
+len_arg = "%s" % len(wallet_addrs) \
+   if len(data) == len(wallet_addrs) or ext == "json" \
+   else "%s:%s" % (len(data),len(wallet_addrs))
+
+from mmgen.util import make_chksum_8,write_walletdat_dump_to_file,write_to_stdout
+wallet_id = make_chksum_8(str(sorted(wallet_addrs)))
+
+data = "\n".join(data) + "\n"
+
+# Output data
+if 'stdout' in opts or not sys.stdout.isatty():
+	c = False if ('addrs' in opts or not sys.stdout.isatty()) else True
+	write_to_stdout(data,"secret keys",c)
+else:
+	write_walletdat_dump_to_file(wallet_id, data, len_arg, ext, what, opts)

+ 70 - 0
mmgen/main_tool.py

@@ -0,0 +1,70 @@
+#!/usr/bin/env python
+#
+# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
+# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""
+mmgen-tool:  Perform various Bitcoin-related operations.
+             Part of the MMGen suite
+"""
+
+import sys
+import mmgen.config as g
+import mmgen.tool as tool
+from mmgen.Opts import *
+
+help_data = {
+	'prog_name': g.prog_name,
+	'desc':    "Perform various BTC-related operations",
+	'usage':   "[opts] <command> <command args>",
+	'options': """
+-d, --outdir=       d Specify an alternate directory 'd' for 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(tool.command_help,g.prog_name)
+}
+
+opts,cmd_args = parse_opts(sys.argv,help_data)
+
+if len(cmd_args) < 1:
+	usage(help_data)
+	sys.exit(1)
+
+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.tool_usage(g.prog_name, command)
+	sys.exit(0)
+
+args = tool.process_args(g.prog_name, command, cmd_args)
+
+tool.opts = opts
+
+#print command + "(" + ", ".join(args) + ")"
+eval("tool." + command + "(" + ", ".join(args) + ")")

+ 221 - 0
mmgen/main_txcreate.py

@@ -0,0 +1,221 @@
+#!/usr/bin/env python
+#
+# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
+# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""
+mmgen-txcreate: Create a Bitcoin transaction from MMGen- or non-MMGen inputs
+                to MMGen- or non-MMGen outputs
+"""
+
+import sys
+from decimal import Decimal
+
+import mmgen.config as g
+from mmgen.Opts import *
+from mmgen.license import *
+from mmgen.tx import *
+from mmgen.util import msg, msg_r, user_confirm
+
+help_data = {
+	'prog_name': g.prog_name,
+	'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
+""".format(g=g),
+	'notes': """
+
+Transaction inputs are chosen from a list of the user's unpent outputs
+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 {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,pnm=g.proj_name)
+}
+
+opts,cmd_args = parse_opts(sys.argv,help_data)
+
+if g.debug: show_opts_and_cmd_args(opts,cmd_args)
+
+c = connect_to_bitcoind()
+
+if not 'info' in opts:
+	do_license_msg(immed=True)
+
+	tx_out,addr_data,b2m_map,acct_data,change_addr = {},[],{},[],""
+
+	addrfiles = [a for a in cmd_args if get_extension(a) == g.addrfile_ext]
+	cmd_args = set(cmd_args) - set(addrfiles)
+
+	for a in addrfiles:
+		check_infile(a)
+		addr_data.append(parse_addrs_file(a))
+
+	def mmaddr2btcaddr(c,mmaddr,acct_data,addr_data,b2m_map):
+		# assume mmaddr has already been checked
+		btcaddr,label = mmaddr2btcaddr_bitcoind(c,mmaddr,acct_data)
+		if not btcaddr:
+			if addr_data:
+				btcaddr,label = mmaddr2btcaddr_addrfile(mmaddr,addr_data)
+			else:
+				msg(txmsg['addrfile_no_data_msg'] % mmaddr)
+				sys.exit(2)
+
+		b2m_map[btcaddr] = mmaddr,label
+		return btcaddr
+
+	for a in cmd_args:
+		if "," in a:
+			a1,a2 = a.split(",")
+			if is_btc_addr(a1):
+				btcaddr = a1
+			elif is_mmgen_addr(a1):
+				btcaddr = mmaddr2btcaddr(c,a1,acct_data,addr_data,b2m_map)
+			else:
+				msg("%s: unrecognized subargument in argument '%s'" % (a1,a))
+				sys.exit(2)
+
+			if is_btc_amt(a2):
+				tx_out[btcaddr] = normalize_btc_amt(a2)
+			else:
+				msg("%s: invalid amount in argument '%s'" % (a2,a))
+				sys.exit(2)
+		elif is_mmgen_addr(a) or is_btc_addr(a):
+			if change_addr:
+				msg("ERROR: More than one change address specified: %s, %s" %
+						(change_addr, a))
+				sys.exit(2)
+			change_addr = a if is_btc_addr(a) else \
+				mmaddr2btcaddr(c,a,acct_data,addr_data,b2m_map)
+			tx_out[change_addr] = 0
+		else:
+			msg("%s: unrecognized argument" % a)
+			sys.exit(2)
+
+	if not tx_out:
+		msg("At least one output must be specified on the command line")
+		sys.exit(2)
+
+	tx_fee = opts['tx_fee'] if 'tx_fee' in opts else g.tx_fee
+	tx_fee = normalize_btc_amt(tx_fee)
+	if tx_fee > g.max_tx_fee:
+		msg("Transaction fee too large: %s > %s" % (tx_fee,g.max_tx_fee))
+		sys.exit(2)
+
+if g.debug: show_opts_and_cmd_args(opts,cmd_args)
+
+#write_to_file("bogus_unspent.json", repr(us), opts); sys.exit()
+
+#if False:
+if g.bogus_wallet_data:
+	import mmgen.rpc
+	us = eval(get_data_from_file(g.bogus_wallet_data))
+else:
+	us = c.listunspent()
+
+if not us: msg(txmsg['no_spendable_outputs']); sys.exit(2)
+
+unspent = sort_and_view(us,opts)
+
+total = trim_exponent(sum([i.amount for i in unspent]))
+
+msg("Total unspent: %s BTC (%s outputs)" % (total, len(unspent)))
+if 'info' in opts: sys.exit(0)
+
+send_amt = sum([tx_out[i] for i in tx_out.keys()])
+msg("Total amount to spend: %s%s" % (
+		(send_amt or "Unknown")," BTC" if send_amt else ""))
+
+while True:
+	sel_nums = select_outputs(unspent,
+			"Enter a range or space-separated list of outputs to spend: ")
+	msg("Selected output%s: %s" %
+		(("" if len(sel_nums) == 1 else "s"), " ".join(str(i) for i in sel_nums))
+	)
+	sel_unspent = [unspent[i-1] for i in sel_nums]
+
+	mmaddrs = set([parse_mmgen_label(i.account)[0] for i in sel_unspent])
+	mmaddrs.discard("")
+
+	if mmaddrs and len(mmaddrs) < len(sel_unspent):
+		msg(txmsg['mixed_inputs'] % ", ".join(sorted(mmaddrs)))
+		if not user_confirm("Accept?"):
+			continue
+
+	total_in = trim_exponent(sum([i.amount for i in sel_unspent]))
+	change   = trim_exponent(total_in - (send_amt + tx_fee))
+
+	if change >= 0:
+		prompt = "Transaction produces %s BTC in change.  OK?" % change
+		if user_confirm(prompt,default_yes=True):
+			break
+	else:
+		msg(txmsg['not_enough_btc'] % change)
+
+if change > 0 and not change_addr:
+	msg(txmsg['throwaway_change'] % change)
+	sys.exit(2)
+
+if change_addr in tx_out and not change:
+	msg("Warning: Change address will be unused as transaction produces no change")
+	del tx_out[change_addr]
+
+for k,v in tx_out.items(): tx_out[k] = float(v)
+
+if change > 0: tx_out[change_addr] = float(change)
+
+tx_in = [{"txid":i.txid, "vout":i.vout} for i in sel_unspent]
+
+if g.debug:
+	print "tx_in:", repr(tx_in)
+	print "tx_out:", repr(tx_out)
+
+tx_hex = c.createrawtransaction(tx_in,tx_out)
+qmsg("Transaction successfully created")
+prompt = "View decoded transaction? (y)es, (N)o, (v)iew in pager"
+reply = prompt_and_get_char(prompt,"YyNnVv",enter_ok=True)
+
+if reply and reply in "YyVv":
+	pager = True if reply in "Vv" else False
+	view_tx_data(c,[i.__dict__ for i in sel_unspent],tx_hex,b2m_map,pager=pager)
+
+prompt = "Save transaction?"
+if user_confirm(prompt,default_yes=True):
+	amt = send_amt or change
+	tx_id = make_chksum_6(unhexlify(tx_hex)).upper()
+	outfile = "tx_%s[%s].%s" % (tx_id,amt,g.rawtx_ext)
+	data = "{} {} {}\n{}\n{}\n{}\n".format(
+			tx_id, amt, make_timestamp(),
+			tx_hex,
+			repr([i.__dict__ for i in sel_unspent]),
+			repr(b2m_map)
+		)
+	write_to_file(outfile,data,opts,"transaction",False,True)
+else:
+	msg("Transaction not saved")

+ 85 - 0
mmgen/main_txsend.py

@@ -0,0 +1,85 @@
+#!/usr/bin/env python
+#
+# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
+# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""
+mmgen-txsend: Broadcast a transaction signed by 'mmgen-txsign' to the network
+"""
+
+import sys
+
+import mmgen.config as g
+from mmgen.Opts import *
+from mmgen.license import *
+from mmgen.tx import *
+from mmgen.util import msg,check_infile,get_lines_from_file,confirm_or_exit
+
+help_data = {
+	'prog_name': g.prog_name,
+	'desc':    "Send a Bitcoin transaction signed by {}-txsign".format(g.proj_name.lower()),
+	'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
+"""
+}
+
+opts,cmd_args = parse_opts(sys.argv,help_data)
+
+if len(cmd_args) == 1:
+	infile = cmd_args[0]; check_infile(infile)
+else: usage(help_data)
+
+# Begin execution
+
+do_license_msg()
+
+tx_data = get_lines_from_file(infile,"signed transaction data")
+
+metadata,tx_hex,inputs_data,b2m_map = parse_tx_data(tx_data,infile)
+
+qmsg("Signed transaction file '%s' is valid" % infile)
+
+c = connect_to_bitcoind()
+
+prompt = "View transaction data? (y)es, (N)o, (v)iew in pager"
+reply = prompt_and_get_char(prompt,"YyNnVv",enter_ok=True)
+if reply and reply in "YyVv":
+	p = True if reply in "Vv" else False
+	view_tx_data(c,inputs_data,tx_hex,b2m_map,metadata,pager=p)
+
+warn   = "Once this transaction is sent, there's no taking it back!"
+what   = "broadcast this transaction to the network"
+expect =  "YES, I REALLY WANT TO DO THIS"
+
+if g.quiet: warn,expect = "","YES"
+
+confirm_or_exit(warn, what, expect)
+
+msg("Sending transaction")
+
+try:
+	tx_id = c.sendrawtransaction(tx_hex)
+except:
+	msg("Unable to send transaction")
+	sys.exit(3)
+
+msg("Transaction sent: %s" % tx_id)
+
+of = "tx_{}[{}].out".format(*metadata[:2])
+write_to_file(of, tx_id+"\n",opts,"transaction ID",True,True)

+ 213 - 0
mmgen/main_txsign.py

@@ -0,0 +1,213 @@
+#!/usr/bin/env python
+#
+# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
+# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""
+mmgen-txsign: Sign a transaction generated by 'mmgen-txcreate'
+"""
+
+import sys
+
+import mmgen.config as g
+from mmgen.Opts import *
+from mmgen.license import *
+from mmgen.tx import *
+from mmgen.util import msg,qmsg
+
+help_data = {
+	'prog_name': g.prog_name,
+	'desc':    "Sign Bitcoin transactions generated by {}-txcreate".format(g.proj_name.lower()),
+	'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= 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
+-v, --verbose            Produce more verbose output
+-V, --skip-key-preverify Skip optional key pre-verification step
+-b, --from-brain=    l,p Generate keys from a user-created password,
+                         i.e. a "brainwallet", using seed length 'l' and
+                         hash preset 'p'
+-w, --use-wallet-dat     Get keys from a running bitcoind
+-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'
+-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,pnm=g.proj_name),
+	'notes': """
+
+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
+{pnl}-addrgen and {pnl}-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 {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 {pnm} keys using 'bitcoind importprivkey'.
+
+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 (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,pnm=g.proj_name,pnl=g.proj_name.lower())
+}
+
+opts,infiles = parse_opts(sys.argv,help_data)
+
+for l in (
+('tx_id', 'info'),
+('keys_from_file','all_keys_from_file')
+): warn_incompatible_opts(opts,l)
+
+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)
+
+c = connect_to_bitcoind()
+
+saved_seeds = {}
+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 'keys_from_file' in opts:
+	from mmgen.crypto import mmgen_decrypt
+	fn = opts['keys_from_file']
+	d = get_data_from_file(fn,"keylist")
+	if get_extension(fn) == g.mmenc_ext or not \
+		  is_b58_str(remove_comments(d.split("\n"))[0][:55]):
+		qmsg("Keylist appears to be encrypted")
+		while True:
+			hp = get_hash_preset_from_user('3')
+			d_dec = mmgen_decrypt(d,"encrypted keylist",hp,opts)
+			if d_dec: d = d_dec; break
+			else: msg("Trying again...")
+	keys_from_file = remove_comments(d.split("\n"))
+else: keys_from_file = []
+
+for tx_file in tx_files:
+	m = "" if 'tx_id' in opts else "transaction data"
+	tx_data = get_lines_from_file(tx_file,m)
+
+	metadata,tx_hex,inputs_data,b2m_map = parse_tx_data(tx_data,tx_file)
+	qmsg("Successfully opened transaction file '%s'" % tx_file)
+
+	if 'tx_id' in opts:
+		msg(metadata[0])
+		sys.exit(0)
+
+	if 'info' in opts:
+		view_tx_data(c,inputs_data,tx_hex,b2m_map,metadata)
+		sys.exit(0)
+
+# Are inputs mmgen addresses?
+	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_inputs and not keys and not 'use_wallet_dat' in opts:
+		missing_keys_errormsg(other_inputs)
+		sys.exit(2)
+
+	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
+
+	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)))
+
+	prompt = "View transaction data? (y)es, (N)o, (v)iew in pager"
+	reply = prompt_and_get_char(prompt,"YyNnVv",enter_ok=True)
+	if reply and reply in "YyVv":
+		p = True if reply in "Vv" else False
+		view_tx_data(c,inputs_data,tx_hex,b2m_map,metadata,pager=p)
+
+	sig_data = [
+		{"txid":i['txid'],"vout":i['vout'],"scriptPubKey":i['scriptPubKey']}
+			for i in inputs_data]
+
+	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_inputs:
+		if keys:
+			sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
+		else:
+			sig_tx = sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys,opts)
+
+	if sig_tx['complete']:
+		prompt = "OK\nSave signed transaction?"
+		if user_confirm(prompt,default_yes=True):
+			outfile = "tx_%s[%s].%s" % (metadata[0],metadata[1],g.sigtx_ext)
+			data = "{}\n{}\n{}\n{}\n".format(
+					" ".join(metadata[:2] + [make_timestamp()]),
+					sig_tx['hex'],
+					repr(inputs_data),
+					repr(b2m_map)
+				)
+			write_to_file(outfile,data,opts,"signed transaction",True,True)
+	else:
+		msg("failed\nSome keys were missing.  Transaction could not be signed.")
+		sys.exit(3)

+ 115 - 0
mmgen/main_walletchk.py

@@ -0,0 +1,115 @@
+#!/usr/bin/env python
+#
+# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
+# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""
+mmgen-walletchk: Check integrity of an MMGen deterministic wallet, display
+                 information about it and export it to various formats
+"""
+
+import sys
+import mmgen.config as g
+from mmgen.Opts import *
+from mmgen.util import *
+from mmgen.crypto import get_seed_from_wallet,wallet_to_incog_data
+
+help_data = {
+	'prog_name': g.prog_name,
+	'desc':  """Check integrity of an {} deterministic wallet, display
+                    its information, and export seed and mnemonic data.
+             """.format(g.proj_name),
+	'usage':   "[opts] [filename]",
+	'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
+-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)
+-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.
+"""
+}
+
+opts,cmd_args = parse_opts(sys.argv,help_data)
+
+if 'export_incog_hidden' in opts or 'export_incog_hex' in opts:
+	opts['export_incog'] = True
+
+if len(cmd_args) != 1: usage(help_data)
+
+check_infile(cmd_args[0])
+
+if 'export_mnemonic' in opts:
+	qmsg("Exporting mnemonic data to file by user request")
+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)
+
+	if "export_incog_hidden" in opts:
+		export_to_hidden_incog(incog_enc,opts)
+	else:
+		seed_len = (len(incog_enc)-g.salt_len-g.aesctr_iv_len)*8
+		fn = "%s-%s-%s[%s,%s].%s" % (
+			seed_id, key_id, iv_id, seed_len, preset,
+			g.incog_hex_ext if "export_incog_hex" in opts else g.incog_ext
+		)
+		data = pretty_hexdump(incog_enc,2,8,line_nums=False) \
+					if "export_incog_hex" in opts else incog_enc
+		export_to_file(fn, data, opts, "incognito wallet data")
+
+	sys.exit()
+
+seed = get_seed_from_wallet(cmd_args[0], opts)
+if seed: qmsg("Wallet is OK")
+else:
+	msg("Error opening wallet")
+	sys.exit(2)
+
+if 'export_mnemonic' in opts:
+	wl = get_default_wordlist()
+	from mmgen.mnemonic import get_mnemonic_from_seed
+	p = True if g.debug else False
+	mn = get_mnemonic_from_seed(seed, wl, g.default_wl, print_info=p)
+	fn = "%s.%s" % (make_chksum_8(seed).upper(), g.mn_ext)
+	export_to_file(fn, " ".join(mn)+"\n", opts, "mnemonic data")
+
+elif 'export_seed' in opts:
+	from mmgen.bitcoin import b58encode_pad
+	data = col4(b58encode_pad(seed))
+	chk = make_chksum_6(b58encode_pad(seed))
+	fn = "%s.%s" % (make_chksum_8(seed).upper(), g.seed_ext)
+	export_to_file(fn, "%s %s\n" % (chk,data), opts, "seed data")

+ 149 - 0
mmgen/main_walletgen.py

@@ -0,0 +1,149 @@
+#!/usr/bin/env python
+#
+# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
+# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""
+mmgen-walletgen: Generate an MMGen deterministic wallet
+"""
+
+import sys, os
+from hashlib import sha256
+
+import mmgen.config as g
+from mmgen.Opts import *
+from mmgen.license import *
+from mmgen.util import *
+from mmgen.crypto import *
+
+help_data = {
+	'prog_name': g.prog_name,
+	'desc':    "Generate an {} deterministic wallet".format(g.proj_name),
+	'usage':   "[opts] [infile]",
+	'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
+-H, --show-hash-presets    Show information on available hash presets
+-l, --seed-len=         n  Create seed of length 'n'. Options: {seed_lens}
+                           (default: {g.seed_len})
+-L, --label=            l  Label to identify this wallet (32 chars max.
+                           Allowed symbols: A-Z, a-z, 0-9, " ", "_", ".")
+-p, --hash-preset=      p  Use scrypt.hash() parameters from preset 'p'
+                           (default: '{g.hash_preset}')
+-P, --passwd-file=      f  Get passphrase from file 'f'
+-q, --quiet                Produce quieter output; overwrite files without
+                           prompting
+-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,
+                           i.e. a "brainwallet", using seed length 'l' and
+                           hash preset 'p' (comma-separated)
+-g, --from-incog           Generate wallet from an incognito-format wallet
+-m, --from-mnemonic        Generate wallet from an Electrum-like mnemonic
+-s, --from-seed            Generate wallet from a seed in .{g.seed_ext} format
+""".format(seed_lens=",".join([str(i) for i in g.seed_lens]), g=g),
+	'notes': """
+
+By default (i.e. when invoked without any of the '--from-<what>' options),
+{g.prog_name} generates a wallet based on a random seed.
+
+Data for the --from-<what> options will be taken from <infile> if <infile>
+is specified.  Otherwise, the user will be prompted to enter the data.
+
+For passphrases all combinations of whitespace are equal, and leading and
+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
+attacks, the brainwallet hash preset must be specified by the user, using
+the 'p' parameter of the '--from-brain' option.  This preset should be
+stronger than the one used for hashing the seed (i.e. the default value or
+the one specified in the '--hash-preset' option).
+
+The '--from-brain' option also requires the user to specify a seed length
+(the 'l' parameter), which overrides both the default and any one given in
+the '--seed-len' option.
+
+For a brainwallet passphrase to always generate the same keys and
+addresses, the same 'l' and 'p' parameters to '--from-brain' must be used
+in all future invocations with that passphrase.
+""".format(g=g)
+}
+
+opts,cmd_args = parse_opts(sys.argv,help_data)
+
+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 len(cmd_args) == 1:
+	infile = cmd_args[0]
+	check_infile(infile)
+	ext = infile.split(".")[-1]
+	ok_exts = g.seedfile_exts
+	for e in ok_exts:
+		if e == ext: break
+	else:
+		msg(
+"Input file must have one of the following extensions: .%s" % ", .".join(ok_exts))
+		sys.exit(1)
+elif len(cmd_args) == 0:
+	infile = ""
+else: usage(help_data)
+
+# Begin execution
+
+do_license_msg()
+
+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)),
+		"continue")
+
+for i in 'from_mnemonic','from_brain','from_seed','from_incog':
+	if infile or (i in opts):
+		seed = get_seed_retry(infile,opts)
+		if "from_incog" in opts or get_extension(infile) == g.incog_ext:
+			qmsg(cmessages['incog'] % make_chksum_8(seed))
+		else: qmsg("")
+		break
+else:
+	# Truncate random data for smaller seed lengths
+	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)
+
+key = make_key(passwd, salt, opts['hash_preset'])
+
+enc_seed = encrypt_seed(seed, key)
+
+write_wallet_to_file(seed,passwd,make_chksum_8(key),salt,enc_seed,opts)

+ 1 - 1
mmgen/mn_electrum.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C) 2013-2014 by philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by

+ 1 - 1
mmgen/mn_tirosh.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C) 2013-2014 by philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by

+ 3 - 2
mmgen/mnemonic.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C) 2013-2014 by philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -15,8 +15,9 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
 """
-mnemonic.py:  Mnemomic routines for the mmgen suite
+mnemonic.py:  Mnemomic routines for the MMGen suite
 """
 
 wl_checksums = {

+ 46 - 68
mmgen/term.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C) 2013-2014 by philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -15,8 +15,9 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
 """
-term.py:  Terminal-handling routines for the mmgen suite
+term.py:  Terminal-handling routines for the MMGen suite
 """
 
 import sys, os, struct
@@ -30,16 +31,12 @@ def _kb_hold_protect_unix():
 
 	timeout = float(0.3)
 
-	try:
-		while True:
-			key = select([sys.stdin], [], [], timeout)[0]
-			if key: sys.stdin.read(1)
-			else: break
-	except KeyboardInterrupt:
-		msg("\nUser interrupt")
-		sys.exit(1)
-	finally:
-		termios.tcsetattr(fd, termios.TCSADRAIN, old)
+	while True:
+		key = select([sys.stdin], [], [], timeout)[0]
+		if key: sys.stdin.read(1)
+		else:
+			termios.tcsetattr(fd, termios.TCSADRAIN, old)
+			break
 
 
 def _get_keypress_unix(prompt="",immed_chars="",prehold_protect=True):
@@ -51,44 +48,35 @@ def _get_keypress_unix(prompt="",immed_chars="",prehold_protect=True):
 	old = termios.tcgetattr(fd)
 	tty.setcbreak(fd)
 
-	try:
-		while True:
-			# Protect against held-down key before read()
-			key = select([sys.stdin], [], [], timeout)[0]
-			ch = sys.stdin.read(1)
-			if prehold_protect:
-				if key: continue
-			if immed_chars == "ALL" or ch in immed_chars:
-				return ch
-			if immed_chars == "ALL_EXCEPT_ENTER" and not ch in "\n\r":
-				return ch
-			# Protect against long keypress
-			key = select([sys.stdin], [], [], timeout)[0]
+	while True:
+		# Protect against held-down key before read()
+		key = select([sys.stdin], [], [], timeout)[0]
+		ch = sys.stdin.read(1)
+		if prehold_protect:
 			if key: continue
-			else: return ch
-	except KeyboardInterrupt:
-		msg("\nUser interrupt")
-		sys.exit(1)
-	finally:
-		termios.tcsetattr(fd, termios.TCSADRAIN, old)
+		if immed_chars == "ALL" or ch in immed_chars: break
+		if immed_chars == "ALL_EXCEPT_ENTER" and not ch in "\n\r": break
+		# Protect against long keypress
+		key = select([sys.stdin], [], [], timeout)[0]
+		if not key: break
+
+	termios.tcsetattr(fd, termios.TCSADRAIN, old)
+	return ch
+
 
 
 def _kb_hold_protect_mswin():
 
 	timeout = float(0.5)
 
-	try:
+	while True:
+		hit_time = time.time()
 		while True:
-			hit_time = time.time()
-			while True:
-				if msvcrt.kbhit():
-					msvcrt.getch()
-					break
-				if float(time.time() - hit_time) > timeout:
-					return
-	except KeyboardInterrupt:
-		msg("\nUser interrupt")
-		sys.exit(1)
+			if msvcrt.kbhit():
+				msvcrt.getch()
+				break
+			if float(time.time() - hit_time) > timeout:
+				return
 
 
 def _get_keypress_mswin(prompt="",immed_chars="",prehold_protect=True):
@@ -96,27 +84,23 @@ def _get_keypress_mswin(prompt="",immed_chars="",prehold_protect=True):
 	msg_r(prompt)
 	timeout = float(0.5)
 
-	try:
-		while True:
-			if msvcrt.kbhit():
-				ch = msvcrt.getch()
+	while True:
+		if msvcrt.kbhit():
+			ch = msvcrt.getch()
 
-				if ord(ch) == 3: raise KeyboardInterrupt
+			if ord(ch) == 3: raise KeyboardInterrupt
 
-				if immed_chars == "ALL" or ch in immed_chars:
-					return ch
-				if immed_chars == "ALL_EXCEPT_ENTER" and not ch in "\n\r":
-					return ch
+			if immed_chars == "ALL" or ch in immed_chars:
+				return ch
+			if immed_chars == "ALL_EXCEPT_ENTER" and not ch in "\n\r":
+				return ch
 
-				hit_time = time.time()
+			hit_time = time.time()
 
-				while True:
-					if msvcrt.kbhit(): break
-					if float(time.time() - hit_time) > timeout:
-						return ch
-	except KeyboardInterrupt:
-		msg("\nUser interrupt")
-		sys.exit(1)
+			while True:
+				if msvcrt.kbhit(): break
+				if float(time.time() - hit_time) > timeout:
+					return ch
 
 
 def _get_terminal_size_linux():
@@ -221,13 +205,7 @@ def do_pager(text):
 			p = Popen([pager], stdin=PIPE, shell=shell)
 		except: pass
 		else:
-			try:
-				p.communicate(text+end+"\n")
-			except KeyboardInterrupt:
-				# Has no effect.  Why?
-				if pager != "less":
-					msg("\n(User interrupt)\n")
-			finally:
-				msg_r("\r")
-				break
+			p.communicate(text+end+"\n")
+			msg_r("\r")
+			break
 	else: print text+end

+ 19 - 25
mmgen/tests/bitcoin.py

@@ -38,26 +38,23 @@ def b58_randenc():
 		sys.exit(9)
 
 def keyconv_compare_randloop(loops, quiet=False):
-	try:
-		for i in range(1,int(loops)+1):
-
+	for i in range(1,int(loops)+1):
 
-			wif = numtowif_rand(quiet=True)
 
-			if not quiet: sys.stderr.write("-- %s --\n" % i)
-			ret = keyconv_compare(wif,quiet)
-			if ret == False: sys.exit(9)
+		wif = numtowif_rand(quiet=True)
 
-			if quiet:
-				sys.stderr.write("\riteration: %i " % i)
+		if not quiet: sys.stderr.write("-- %s --\n" % i)
+		ret = keyconv_compare(wif,quiet)
+		if ret == False: sys.exit(9)
 
 		if quiet:
-			sys.stderr.write("\r%s iterations completed\n" % i)
-		else:
-			print "%s iterations completed" % i
+			sys.stderr.write("\riteration: %i " % i)
+
+	if quiet:
+		sys.stderr.write("\r%s iterations completed\n" % i)
+	else:
+		print "%s iterations completed" % i
 
-	except KeyboardInterrupt:
-		msg("\nUser interrupt")
 
 def keyconv_compare(wif,quiet=False):
 	do_msg = nomsg if quiet else msg
@@ -144,17 +141,14 @@ def b58tohex_pad(s_in, quiet=False):
 	b58tohex(s_in,f_dec=b.b58decode_pad, f_enc=b.b58encode_pad, quiet=quiet)
 
 def	hextob58_pad_randloop(loops, quiet=False):
-	try:
-		for i in range(1,int(loops)+1):
-			r = hexlify(get_random(32))
-			hextob58(r,f_enc=b.b58encode_pad, f_dec=b.b58decode_pad, quiet=quiet)
-			if not quiet: print
-			if not i % 100 and quiet:
-				sys.stderr.write("\riteration: %i " % i)
-
-		sys.stderr.write("\r%s iterations completed\n" % i)
-	except KeyboardInterrupt:
-		msg("\nUser interrupt")
+	for i in range(1,int(loops)+1):
+		r = hexlify(get_random(32))
+		hextob58(r,f_enc=b.b58encode_pad, f_dec=b.b58decode_pad, quiet=quiet)
+		if not quiet: print
+		if not i % 100 and quiet:
+			sys.stderr.write("\riteration: %i " % i)
+
+	sys.stderr.write("\r%s iterations completed\n" % i)
 
 def test_wiftohex(s_in,f_dec=b.wiftohex,f_enc=b.numtowif):
 	print "Input:         %s" % s_in

+ 7 - 6
mmgen/tool.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C) 2013-2014 by philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -15,8 +15,9 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
 """
-tool.py:  Routines and data for the mmgen-tool utility
+tool.py:  Routines and data for the 'mmgen-tool' utility
 """
 
 import sys
@@ -415,9 +416,9 @@ def hex2wif(hexpriv,compressed=False):
 	print bitcoin.hextowif(hexpriv,compressed)
 
 
-def encrypt(infile,outfile="",hash_preset=''):
+def encrypt(infile,outfile="",hash_preset='3'):
 	data = get_data_from_file(infile,"data for encryption")
-	enc_d = mmgen_encrypt(data,hash_preset,opts)
+	enc_d = mmgen_encrypt(data,"",hash_preset,opts)
 	if outfile == '-':
 		write_to_stdout(enc_d,"encrypted data",confirm=True)
 	else:
@@ -426,9 +427,9 @@ def encrypt(infile,outfile="",hash_preset=''):
 		write_to_file(outfile, enc_d, opts,"encrypted data",True,True)
 
 
-def decrypt(infile,outfile="",hash_preset=''):
+def decrypt(infile,outfile="",hash_preset='3'):
 	enc_d = get_data_from_file(infile,"encrypted data")
-	dec_d = mmgen_decrypt(enc_d,hash_preset,opts)
+	dec_d = mmgen_decrypt(enc_d,"",hash_preset,opts)
 	if outfile == '-':
 		write_to_stdout(dec_d,"decrypted data",confirm=True)
 	else:

+ 9 - 3
mmgen/tx.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C) 2013-2014 by philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -15,6 +15,7 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
 """
 tx.py:  Bitcoin transaction routines
 """
@@ -453,6 +454,12 @@ def is_btc_addr(s):
 	from mmgen.bitcoin import verify_addr
 	return verify_addr(s)
 
+def is_b58_str(s):
+	from mmgen.bitcoin import b58a
+	for ch in s:
+		if ch not in b58a: return False
+	return True
+
 
 def mmaddr2btcaddr_bitcoind(c,mmaddr,acct_data):
 
@@ -720,8 +727,7 @@ def preverify_keys(addrs_orig, keys_orig):
 
 		if addrs:
 			s = "" if len(addrs) == 1 else "es"
-			msg("No keys found for the following non-%s address%s:" %
-					(g.proj_name,s))
+			msg("No keys found for the following address%s:" % s)
 			print "  %s" % "\n  ".join(addrs)
 			sys.exit(2)
 

+ 26 - 20
mmgen/util.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C) 2013-2014 by philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -15,6 +15,7 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
 """
 util.py:  Low-level routines imported by other modules for the MMGen suite
 """
@@ -342,11 +343,11 @@ def write_to_stdout(data, what, confirm=True):
 	sys.stdout.write(data)
 
 
-def write_to_file(outfile,data,opts,what="data",confirm=False,verbose=False):
+def write_to_file(outfile,data,opts,what="data",confirm_overwrite=False,verbose=False):
 
 	if 'outdir' in opts: outfile = make_full_path(opts['outdir'],outfile)
 
-	if confirm:
+	if confirm_overwrite:
 		from os import stat
 		try:
 			stat(outfile)
@@ -370,11 +371,9 @@ def export_to_file(outfile, data, opts, what="data"):
 
 	if 'stdout' in opts:
 		write_to_stdout(data, what, confirm=True)
-	elif not sys.stdout.isatty():
-		write_to_stdout(data, what, confirm=False)
 	else:
-		c = False if g.quiet else True
-		write_to_file(outfile,data,opts,what,c,True)
+		confirm_overwrite = False if g.quiet else True
+		write_to_file(outfile,data,opts,what,confirm_overwrite,True)
 
 
 from mmgen.bitcoin import b58decode_pad,b58encode_pad
@@ -430,9 +429,9 @@ def write_wallet_to_file(seed, passwd, key_id, salt, enc_seed, opts):
 	outfile="{}-{}[{},{}].{}".format(
 		seed_id,key_id,seed_len,hash_preset,g.wallet_ext)
 
-	c = False if g.quiet else True
 	d = "\n".join((chk,)+lines)+"\n"
-	write_to_file(outfile,d,opts,"wallet",c,True)
+	confirm_overwrite = False if g.quiet else True
+	write_to_file(outfile,d,opts,"wallet",confirm_overwrite,True)
 
 	if g.verbose:
 		display_control_data(label,metadata,hash_preset,salt,enc_seed)
@@ -684,22 +683,29 @@ def export_to_hidden_incog(incog_enc,opts):
 	msg("Data written to file '%s' at offset %s" %
 			(os.path.relpath(outfile),offset))
 
-
 from mmgen.term import kb_hold_protect,get_char
 
+def get_hash_preset_from_user(hp='3'):
+	p = "Enter hash preset, or hit ENTER to accept the default ('%s'): " % hp
+	while True:
+		ret = my_raw_input(p)
+		if ret:
+			if ret in g.hash_presets.keys(): return ret
+			else:
+				msg("Invalid input.  Valid choices are %s" %
+						", ".join(sorted(g.hash_presets.keys())))
+				continue
+		else: return hp
+
+
 def my_raw_input(prompt,echo=True):
 	msg_r(prompt)
 	kb_hold_protect()
-	try:
-		if echo:
-			reply = raw_input("")
-		else:
-			from getpass import getpass
-			reply = getpass("")
-	except KeyboardInterrupt:
-		msg("\nUser interrupt")
-		sys.exit(1)
-
+	if echo:
+		reply = raw_input("")
+	else:
+		from getpass import getpass
+		reply = getpass("")
 	kb_hold_protect()
 	return reply