# btcrseed.py -- btcrecover mnemonic sentence library
# Copyright (C) 2014-2017 Christopher Gurnee
#
# This file is part of btcrecover.
#
# btcrecover 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
# 2 of the License, or (at your option) any later version.
#
# btcrecover 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/

# If you find this program helpful, please consider a small
# donation to the developer at the following Bitcoin address:
#
#           3Au8ZodNHPei7MQiSVAWb7NB2yqsb48GW4
#
#                      Thank You!

# TODO: finish pythonizing comments/documentation

# (all optional futures for 2.7 except unicode_literals)
from __future__ import print_function, absolute_import, division

__version__ = "0.7.3"

from . import btcrpass
from .addressset import AddressSet
import sys, os, io, base64, hashlib, hmac, difflib, coincurve, itertools, \
       unicodedata, collections, struct, glob, atexit, re, random, multiprocessing


# Order of the base point generator, from SEC 2
GENERATOR_ORDER = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141L

ADDRESSDB_DEF_FILENAME = "addresses.db"

def full_version():
    return "seedrecover {}, {}".format(
        __version__,
        btcrpass.full_version()
    )


################################### Utility Functions ###################################


def bytes_to_int(bytes_rep):
    """convert a string of bytes (in big-endian order) to a long integer

    :param bytes_rep: the raw bytes
    :type bytes_rep: str
    :return: the unsigned integer
    :rtype: long
    """
    return long(base64.b16encode(bytes_rep), 16)

def int_to_bytes(int_rep, min_length):
    """convert an unsigned integer to a string of bytes (in big-endian order)

    :param int_rep: a non-negative integer
    :type int_rep: long or int
    :param min_length: the minimum output length
    :type min_length: int
    :return: the raw bytes, zero-padded (at the beginning) if necessary
    :rtype: str
    """
    assert int_rep >= 0
    hex_rep = "{:X}".format(int_rep)
    if len(hex_rep) % 2 == 1:    # The hex decoder below requires
        hex_rep = "0" + hex_rep  # exactly 2 chars per byte.
    return base64.b16decode(hex_rep).rjust(min_length, "\0")


dec_digit_to_base58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
base58_digit_to_dec = { b58:dec for dec,b58 in enumerate(dec_digit_to_base58) }


def base58check_to_bytes(base58_rep, expected_size):
    """decode a base58check string to its raw bytes

    :param base58_rep: check-code appended base58-encoded string
    :type base58_rep: str
    :param expected_size: the expected number of decoded bytes (excluding the check code)
    :type expected_size: int
    :return: the base58-decoded bytes
    :rtype: str
    """
    base58_stripped = base58_rep.lstrip("1")

    int_rep = 0
    for base58_digit in base58_stripped:
        int_rep *= 58
        int_rep += base58_digit_to_dec[base58_digit]

    # Convert int to raw bytes
    all_bytes  = int_to_bytes(int_rep, expected_size + 4)

    zero_count = next(zeros for zeros,byte in enumerate(all_bytes) if byte != "\0")
    if len(base58_rep) - len(base58_stripped) != zero_count:
        raise ValueError("prepended zeros mismatch")

    if hashlib.sha256(hashlib.sha256(all_bytes[:-4]).digest()).digest()[:4] != all_bytes[-4:]:
        raise ValueError("base58 check code mismatch")

    return all_bytes[:-4]

def base58check_to_hash160(base58_rep):
    """convert from a base58check address to its hash160 form

    :param base58_rep: check-code appended base58-encoded address
    :type base58_rep: str
    :return: the hash of the pubkey/redeemScript, then the version byte
    :rtype: (str, str)
    """
    decoded_bytes = base58check_to_bytes(base58_rep, 1 + 20)
    return decoded_bytes[1:], decoded_bytes[0]

BIP32ExtendedKey = collections.namedtuple("BIP32ExtendedKey",
    "version depth fingerprint child_number chaincode key")
#
def base58check_to_bip32(base58_rep):
    """decode a bip32-serialized extended key from its base58check form

    :param base58_rep: check-code appended base58-encoded bip32 extended key
    :type base58_rep: str
    :return: a namedtuple containing: version depth fingerprint child_number chaincode key
    :rtype: BIP32ExtendedKey
    """
    decoded_bytes = base58check_to_bytes(base58_rep, 4 + 1 + 4 + 4 + 32 + 33)
    return BIP32ExtendedKey(decoded_bytes[0:4],  ord(decoded_bytes[ 4:5]), decoded_bytes[ 5:9],
        struct.unpack(">I", decoded_bytes[9:13])[0], decoded_bytes[13:45], decoded_bytes[45:])

def compress_pubkey(uncompressed_pubkey):
    """convert an uncompressed public key into a compressed public key

    :param uncompressed_pubkey: the uncompressed public key
    :type uncompressed_pubkey: str
    :return: the compressed public key
    :rtype: str
    """
    assert len(uncompressed_pubkey) == 65 and uncompressed_pubkey[0] == "\x04"
    return chr((ord(uncompressed_pubkey[-1]) & 1) + 2) + uncompressed_pubkey[1:33]


print = btcrpass.safe_print  # use btcrpass's print which never dies from printing Unicode


################################### Wallets ###################################

# A class decorator which adds a wallet class to a registered
# list that can later be selected by a user in GUI mode
selectable_wallet_classes = []
def register_selectable_wallet_class(description):
    def _register_selectable_wallet_class(cls):
        selectable_wallet_classes.append((cls, description))
        return cls
    return _register_selectable_wallet_class


# Loads a wordlist from a file into a list of Python unicodes. Note that the
# unicodes are normalized in NFC format, which is not what BIP39 requires (NFKD).
wordlists_dir = os.path.join(os.path.dirname(__file__), "wordlists")
def load_wordlist(name, lang):
    filename = os.path.join(wordlists_dir, "{}-{}.txt".format(name, lang))
    with io.open(filename, encoding="utf_8_sig") as wordlist_file:
        wordlist = []
        for word in wordlist_file:
            word = word.strip()
            if word and not word.startswith(u"#"):
                wordlist.append(unicodedata.normalize("NFC", word))
    return wordlist


def calc_passwords_per_second(checksum_ratio, kdf_overhead, scalar_multiplies):
    """estimate the number of mnemonics that can be checked per second (per CPU core)

    :param checksum_ratio: chances that a random mnemonic has the correct checksum [0.0 - 1.0]
    :type checksum_ratio: float
    :param kdf_overhead: overhead in seconds imposed by the kdf per each guess
    :type kdf_overhead: float
    :param scalar_multiplies: count of EC scalar multiplications required per each guess
    :type scalar_multiplies: int
    :return: estimated mnemonic check rate in hertz (per CPU core)
    :rtype: float
    """
    return 1.0 / (checksum_ratio * (kdf_overhead + scalar_multiplies*0.0001) + 0.00001)


############### WalletBase ###############

# Methods common to most wallets, but overridden by WalletEthereum
class WalletBase(object):

    def __init__(self, loading = False):
        assert loading, "use load_from_filename or create_from_params to create a " + self.__class__.__name__

    @staticmethod
    def _addresses_to_hash160s(addresses):
        hash160s = set()
        for address in addresses:
            hash160, version_byte = base58check_to_hash160(address)
            if ord(version_byte) != 0:
                raise ValueError("not a Bitcoin P2PKH address; version byte is {:#04x}".format(ord(version_byte)))
            hash160s.add(hash160)
        return hash160s

    @staticmethod
    def pubkey_to_hash160(uncompressed_pubkey):
        """convert from an uncompressed public key to its Bitcoin compressed-pubkey hash160 form

        :param uncompressed_pubkey: SEC 1 EllipticCurvePoint OctetString
        :type uncompressed_pubkey: str
        :return: ripemd160(sha256(compressed_pubkey))
        :rtype: str
        """
        return hashlib.new("ripemd160", hashlib.sha256(compress_pubkey(uncompressed_pubkey)).digest()).digest()


############### Electrum1 ###############

@register_selectable_wallet_class("Electrum 1.x (including wallets later upgraded to 2.x)")
class WalletElectrum1(WalletBase):

    _words = None
    @classmethod
    def _load_wordlist(cls):
        if not cls._words:
            cls._words      = tuple(map(str, load_wordlist("electrum1", "en")))  # also converts to ASCII
            cls._word_to_id = { word:id for id,word in enumerate(cls._words) }

    @property
    def word_ids(self):      return xrange(len(self._words))
    @classmethod
    def id_to_word(cls, id): return cls._words[id]

    @staticmethod
    def is_wallet_file(wallet_file):
        wallet_file.seek(0)
        # returns "maybe yes" or "definitely no"
        return None if wallet_file.read(2) == b"{'" else False

    def __init__(self, loading = False):
        super(WalletElectrum1, self).__init__(loading)
        self._master_pubkey        = None
        self._passwords_per_second = None

        self._load_wordlist()
        self._num_words = len(self._words)  # needs to be an instance variable so it can be pickled

    def passwords_per_seconds(self, seconds):
        if not self._passwords_per_second:
            self._passwords_per_second = \
                calc_passwords_per_second(1, 0.12, 1 if self._master_pubkey else self._addrs_to_generate + 1)
        return max(int(round(self._passwords_per_second * seconds)), 1)

    # Load an Electrum1 wallet file (the part of it we need, just the master public key)
    @classmethod
    def load_from_filename(cls, wallet_filename):
        from ast import literal_eval
        with open(wallet_filename) as wallet_file:
            wallet = literal_eval(wallet_file.read(btcrpass.MAX_WALLET_FILE_SIZE))  # up to 64M, typical size is a few k
        return cls._load_from_dict(wallet)

    @classmethod
    def _load_from_dict(cls, wallet):
        seed_version = wallet.get("seed_version")
        if seed_version is None:             raise ValueError("Unrecognized wallet format (Electrum1 seed_version not found)")
        if seed_version != 4:                raise NotImplementedError("Unsupported Electrum1 seed version " + seed_version)
        if not wallet.get("use_encryption"): raise ValueError("Electrum1 wallet is not encrypted")
        master_pubkey = base64.b16decode(wallet["master_public_key"], casefold=True)
        if len(master_pubkey) != 64:         raise ValueError("Electrum1 master public key is not 64 bytes long")
        self = cls(loading=True)
        self._master_pubkey = "\x04" + master_pubkey  # prepend the uncompressed tag
        return self

    # Creates a wallet instance from either an mpk, an addresses container and address_limit,
    # or a hash160s container. If none of these were supplied, prompts the user for each.
    @classmethod
    def create_from_params(cls, mpk = None, addresses = None, address_limit = None, hash160s = None, is_performance = False):
        self = cls(loading=True)

        # Process the mpk (master public key) argument
        if mpk:
            if len(mpk) != 128:
                raise ValueError("an Electrum 1.x master public key must be exactly 128 hex digits long")
            try:
                mpk = base64.b16decode(mpk, casefold=True)
                # (it's assigned to the self._master_pubkey later)
            except TypeError as e:
                raise ValueError(e)  # consistently raise ValueError for any bad inputs

        # Process the hash160s argument
        if hash160s:
            if mpk:
                print("warning: addressdb is ignored when an mpk is provided", file=sys.stderr)
                hash160s = None
            else:
                self._known_hash160s = hash160s

        # Process the addresses argument
        if addresses:
            if mpk or hash160s:
                print("warning: addresses are ignored when an mpk or addressdb is provided", file=sys.stderr)
                addresses = None
            else:
                self._known_hash160s = self._addresses_to_hash160s(addresses)

        # Process the address_limit argument
        if address_limit:
            if mpk:
                print("warning: address limit is ignored when an mpk is provided", file=sys.stderr)
                address_limit = None
            else:
                address_limit = int(address_limit)
                if address_limit <= 0:
                    raise ValueError("the address limit must be > 0")
                # (it's assigned to self._addrs_to_generate later)

        # If mpk, addresses, and hash160s arguments were all not provided, prompt the user for an mpk first
        if not mpk and not addresses and not hash160s:
            init_gui()
            while True:
                mpk = tkSimpleDialog.askstring("Electrum 1.x master public key",
                    "Please enter your master public key if you have it, or click Cancel to search by an address instead:",
                    initialvalue="c79b02697b32d9af63f7d2bd882f4c8198d04f0e4dfc5c232ca0c18a87ccc64ae8829404fdc48eec7111b99bda72a7196f9eb8eb42e92514a758f5122b6b5fea"
                        if is_performance else None)
                if not mpk:
                    break  # if they pressed Cancel, stop prompting for an mpk
                mpk = mpk.strip()
                try:
                    if len(mpk) != 128:
                        raise TypeError()
                    mpk = base64.b16decode(mpk, casefold=True)  # raises TypeError() on failure
                    break
                except TypeError:
                    tkMessageBox.showerror("Master public key", "The entered Electrum 1.x key is not exactly 128 hex digits long")

        # If an mpk has been provided (in the function call or from a user), convert it to the needed format
        if mpk:
            assert len(mpk) == 64, "mpk is 64 bytes long (after decoding from hex)"
            self._master_pubkey = "\x04" + mpk  # prepend the uncompressed tag

        # If an mpk wasn't provided (at all), and addresses and hash160s arguments also
        # weren't provided (in the original function call), prompt the user for addresses.
        else:
            if not addresses and not hash160s:
                # init_gui() was already called above
                self._known_hash160s = None
                while True:
                    addresses = tkSimpleDialog.askstring("Addresses",
                        "Please enter at least one address from your wallet, "
                        "preferably some created early in your wallet's lifetime:",
                        initialvalue="17LGpN2z62zp7RS825jXwYtE7zZ19Mxxu8" if is_performance else None)
                    if not addresses: break
                    addresses.replace(",", " ")
                    addresses.replace(";", " ")
                    addresses = addresses.split()
                    if not addresses: break
                    try:
                        # (raises ValueError or TypeError on failure):
                        self._known_hash160s = self._addresses_to_hash160s(addresses)
                        break
                    except (ValueError, TypeError) as e:
                        tkMessageBox.showerror("Addresses", "An entered address is invalid ({})".format(e))

                # If there are still no hash160s available (and no mpk), check for an address database before giving up
                if not self._known_hash160s:
                    if os.path.isfile(ADDRESSDB_DEF_FILENAME):
                        print("Using address database file '"+ADDRESSDB_DEF_FILENAME+"' in the current directory.")
                    else:
                        print("notice: address database file '"+ADDRESSDB_DEF_FILENAME+"' does not exist in current directory", file=sys.stderr)
                        sys.exit("canceled")

            if not address_limit:
                init_gui()  # might not have been called yet
                before_the = "one(s) you just entered" if addresses else "first one in actual use"
                address_limit = tkSimpleDialog.askinteger("Address limit",
                    "Please enter the address generation limit. Smaller will\n"
                    "be faster, but it must be equal to at least the number\n"
                    "of addresses created before the "+before_the+":", minvalue=1)
                if not address_limit:
                    sys.exit("canceled")
            self._addrs_to_generate = address_limit

            if not self._known_hash160s:
                print("Loading address database ...")
                self._known_hash160s = AddressSet.fromfile(open(ADDRESSDB_DEF_FILENAME, "rb"))

        return self

    # Performs basic checks so that clearly invalid mnemonic_ids can be completely skipped
    @staticmethod
    def verify_mnemonic_syntax(mnemonic_ids):
        return len(mnemonic_ids) == 12 and None not in mnemonic_ids

    # This is the time-consuming function executed by worker thread(s). It returns a tuple: if a mnemonic
    # is correct return it, else return False for item 0; return a count of mnemonics checked for item 1
    def return_verified_password_or_false(self, mnemonic_ids_list):
        # Copy some vars into local for a small speed boost
        l_sha256     = hashlib.sha256
        hashlib_new  = hashlib.new
        num_words    = self._num_words
        num_words2   = num_words * num_words

        for count, mnemonic_ids in enumerate(mnemonic_ids_list, 1):
            # Compute the binary seed from the word list the Electrum1 way
            seed = ""
            for i in xrange(0, 12, 3):
                seed += "{:08x}".format( mnemonic_ids[i    ]
                     + num_words  * (   (mnemonic_ids[i + 1] - mnemonic_ids[i    ]) % num_words )
                     + num_words2 * (   (mnemonic_ids[i + 2] - mnemonic_ids[i + 1]) % num_words ))
            #
            unstretched_seed = seed
            for i in xrange(100000):  # Electrum1's seed stretching
                seed = l_sha256(seed + unstretched_seed).digest()

            # If a master public key was provided, check the pubkey derived from the seed against it
            if self._master_pubkey:
                try:
                    if coincurve.PublicKey.from_valid_secret(seed).format(compressed=False) == self._master_pubkey:
                        return mnemonic_ids, count  # found it
                except ValueError: continue

            # Else derive addrs_to_generate addresses from the seed, searching for a match with known_hash160s
            else:
                master_privkey = bytes_to_int(seed)

                try: master_pubkey_bytes = coincurve.PublicKey.from_valid_secret(seed).format(compressed=False)[1:]
                except ValueError: continue

                for seq_num in xrange(self._addrs_to_generate):
                    # Compute the next deterministic private/public key pair the Electrum1 way.
                    # FYI we derive a privkey first, and then a pubkey from that because it's
                    # likely faster than deriving a pubkey directly from the base point and
                    # seed -- it means doing a simple modular addition instead of a point
                    # addition (plus a scalar point multiplication which is needed for both).
                    d_offset  = bytes_to_int( l_sha256(l_sha256(
                            "{}:0:{}".format(seq_num, master_pubkey_bytes)  # 0 means: not a change address
                        ).digest()).digest() )
                    d_privkey = int_to_bytes((master_privkey + d_offset) % GENERATOR_ORDER, 32)

                    d_pubkey  = coincurve.PublicKey.from_valid_secret(d_privkey).format(compressed=False)
                    # Compute the hash160 of the *uncompressed* public key, and check for a match
                    if hashlib_new("ripemd160", l_sha256(d_pubkey).digest()).digest() in self._known_hash160s:
                        return mnemonic_ids, count  # found it

        return False, count

    # Configures the values of four globals used later in config_btcrecover():
    # mnemonic_ids_guess, close_mnemonic_ids, num_inserts, and num_deletes
    @classmethod
    def config_mnemonic(cls, mnemonic_guess = None, closematch_cutoff = 0.65):
        # If a mnemonic guess wasn't provided, prompt the user for one
        if not mnemonic_guess:
            init_gui()
            mnemonic_guess = tkSimpleDialog.askstring("Electrum seed",
                "Please enter your best guess for your Electrum seed:")
            if not mnemonic_guess:
                sys.exit("canceled")

        mnemonic_guess = str(mnemonic_guess)  # ensures it's ASCII

        # Convert the mnemonic words into numeric ids and pre-calculate similar mnemonic words
        global mnemonic_ids_guess, close_mnemonic_ids
        mnemonic_ids_guess = ()
        # close_mnemonic_ids is a dict; each dict key is a mnemonic_id (int), and each
        # dict value is a tuple containing length 1 tuples, and finally each of the
        # length 1 tuples contains a single mnemonic_id which is similar to the dict's key
        close_mnemonic_ids = {}
        for word in mnemonic_guess.lower().split():
            close_words = difflib.get_close_matches(word, cls._words, sys.maxint, closematch_cutoff)
            if close_words:
                if close_words[0] != word:
                    print("'{}' was in your guess, but it's not a valid Electrum seed word;\n"
                          "    trying '{}' instead.".format(word, close_words[0]))
                mnemonic_ids_guess += cls._word_to_id[close_words[0]],
                close_mnemonic_ids[mnemonic_ids_guess[-1]] = tuple( (cls._word_to_id[w],) for w in close_words[1:] )
            else:
                print("'{}' was in your guess, but there is no similar Electrum seed word;\n"
                      "    trying all possible seed words here instead.".format(word))
                mnemonic_ids_guess += None,

        global num_inserts, num_deletes
        num_inserts = max(12 - len(mnemonic_ids_guess), 0)
        num_deletes = max(len(mnemonic_ids_guess) - 12, 0)
        if num_inserts:
            print("Seed sentence was too short, inserting {} word{} into each guess."
                  .format(num_inserts, "s" if num_inserts > 1 else ""))
        if num_deletes:
            print("Seed sentence was too long, deleting {} word{} from each guess."
                  .format(num_deletes, "s" if num_deletes > 1 else ""))

    # Produces a long stream of differing and incorrect mnemonic_ids guesses (for testing)
    @staticmethod
    def performance_iterator():
        # See WalletBIP39.performance_iterator() for details
        prefix = tuple(random.randrange(len(WalletElectrum1._words)) for i in xrange(8))
        for guess in itertools.product(xrange(len(WalletElectrum1._words)), repeat = 4):
            yield prefix + guess


############### BIP32 ###############

class WalletBIP32(WalletBase):

    def __init__(self, path = None, loading = False):
        super(WalletBIP32, self).__init__(loading)
        self._chaincode            = None
        self._passwords_per_second = None

        # Split the BIP32 key derivation path into its constituent indexes
        if not path:  # Defaults to BIP44
            path = "m/44'/0'/0'/"
            # Append the internal/external (change) index to the path in create_from_params()
            self._append_last_index = True
        else:
            self._append_last_index = False
        path_indexes = path.split("/")
        if path_indexes[0] == "m" or path_indexes[0] == "":
            del path_indexes[0]   # the optional leading "m/"
        if path_indexes[-1] == "":
            del path_indexes[-1]  # the optional trailing "/"
        self._path_indexes = ()
        for path_index in path_indexes:
            if path_index.endswith("'"):
                self._path_indexes += int(path_index[:-1]) + 2**31,
            else:
                self._path_indexes += int(path_index),

    def passwords_per_seconds(self, seconds):
        if not self._passwords_per_second:
            scalar_multiplies = 0
            for i in self._path_indexes:
                if i < 2147483648:          # if it's a normal child key
                    scalar_multiplies += 1  # then it requires a scalar multiply
            if not self._chaincode:
                scalar_multiplies += self._addrs_to_generate + 1  # each addr. to generate req. a scalar multiply
            self._passwords_per_second = \
                calc_passwords_per_second(self._checksum_ratio, self._kdf_overhead, scalar_multiplies)
        return max(int(round(self._passwords_per_second * seconds)), 1)

    # Creates a wallet instance from either an mpk, an addresses container and address_limit,
    # or a hash160s container. If none of these were supplied, prompts the user for each.
    # (the BIP32 key derivation path is by default BIP44's account 0)
    @classmethod
    def create_from_params(cls, mpk = None, addresses = None, address_limit = None, hash160s = None, path = None, is_performance = False):
        self = cls(path, loading=True)

        # Process the mpk (master public key) argument
        if mpk:
            if not mpk.startswith("xpub"):
                raise ValueError("the BIP32 extended public key must begin with 'xpub'")
            mpk = base58check_to_bip32(mpk)
            # (it's processed more later)

        # Process the hash160s argument
        if hash160s:
            if mpk:
                print("warning: addressdb is ignored when an mpk is provided", file=sys.stderr)
                hash160s = None
            else:
                self._known_hash160s = hash160s

        # Process the addresses argument
        if addresses:
            if mpk or hash160s:
                print("warning: addresses are ignored when an mpk or addressdb is provided", file=sys.stderr)
                addresses = None
            else:
                self._known_hash160s = self._addresses_to_hash160s(addresses)

        # Process the address_limit argument
        if address_limit:
            if mpk:
                print("warning: address limit is ignored when an mpk is provided", file=sys.stderr)
                address_limit = None
            else:
                address_limit = int(address_limit)
                if address_limit <= 0:
                    raise ValueError("the address limit must be > 0")
                # (it's assigned to self._addrs_to_generate later)

        # If mpk, addresses, and hash160s arguments were all not provided, prompt the user for an mpk first
        if not mpk and not addresses and not hash160s:
            init_gui()
            while True:
                mpk = tkSimpleDialog.askstring("Master extended public key",
                    "Please enter your master extended public key (xpub) if you "
                    "have it, or click Cancel to search by an address instead:",
                    initialvalue=self._performance_xpub() if is_performance else None)
                if not mpk:
                    break  # if they pressed Cancel, stop prompting for an mpk
                mpk = mpk.strip()
                try:
                    if not mpk.startswith("xpub"):
                        raise ValueError("not a BIP32 extended public key (doesn't start with 'xpub')")
                    mpk = base58check_to_bip32(mpk)
                    break
                except ValueError as e:
                    tkMessageBox.showerror("Master extended public key", "The entered key is invalid ({})".format(e))

        # If an mpk has been provided (in the function call or from a user), extract the
        # required chaincode and adjust the path to match the mpk's depth and child number
        if mpk:
            if mpk.depth == 0:
                print("xpub depth: 0")
                assert mpk.child_number == 0, "child_number == 0 when depth == 0"
            else:
                if mpk.child_number < 2**31:
                    child_num = mpk.child_number
                else:
                    child_num = str(mpk.child_number - 2**31) + "'"
                print("xpub depth:       {}\n"
                      "xpub fingerprint: {}\n"
                      "xpub child #:     {}"
                      .format(mpk.depth, base64.b16encode(mpk.fingerprint), child_num))
            self._chaincode = mpk.chaincode
            if mpk.depth <= len(self._path_indexes):                  # if this, ensure the path
                self._path_indexes = self._path_indexes[:mpk.depth]   # length matches the depth
                if self._path_indexes and self._path_indexes[-1] != mpk.child_number:
                    raise ValueError("the extended public key's child # doesn't match "
                                     "the corresponding index of this wallet's path")
            elif mpk.depth == 1 + len(self._path_indexes) and self._append_last_index:
                self._path_indexes += mpk.child_number,
            else:
                raise ValueError(
                    "the extended public key's depth exceeds the length of this wallet's path ({})"
                    .format(len(self._path_indexes)))

        else:  # else if not mpk

            # If we don't have an mpk but need to append the last
            # index, assume it's the external (non-change) chain
            if self._append_last_index:
                self._path_indexes += 0,

            # If an mpk wasn't provided (at all), and addresses and hash160s arguments also
            # weren't provided (in the original function call), prompt the user for addresses.
            if not addresses and not hash160s:
                # init_gui() was already called above
                self._known_hash160s = None
                while True:
                    addresses = tkSimpleDialog.askstring("Addresses",
                        "Please enter at least one address from the first account in your wallet, "
                        "preferably some created early in the account's lifetime:",
                        initialvalue="17LGpN2z62zp7RS825jXwYtE7zZ19Mxxu8" if is_performance else None)
                    if not addresses: break
                    addresses.replace(",", " ")
                    addresses.replace(";", " ")
                    addresses = addresses.split()
                    if not addresses: break
                    try:
                        # (raises ValueError or TypeError on failure):
                        self._known_hash160s = self._addresses_to_hash160s(addresses)
                        break
                    except (ValueError, TypeError) as e:
                        tkMessageBox.showerror("Addresses", "An entered address is invalid ({})".format(e))

                # If there are still no hash160s available (and no mpk), check for an address database before giving up
                if not self._known_hash160s:
                    if os.path.isfile(ADDRESSDB_DEF_FILENAME):
                        print("Using address database file '"+ADDRESSDB_DEF_FILENAME+"' the in current directory.")
                    else:
                        print("notice: address database file '"+ADDRESSDB_DEF_FILENAME+"' does not exist in current directory", file=sys.stderr)
                        sys.exit("canceled")

            if not address_limit:
                init_gui()  # might not have been called yet
                before_the = "one(s) you just entered" if addresses else "first one in actual use"
                address_limit = tkSimpleDialog.askinteger("Address limit",
                    "Please enter the address generation limit. Smaller will\n"
                    "be faster, but it must be equal to at least the number\n"
                    "of addresses created before the "+before_the+":", minvalue=1)
                if not address_limit:
                    sys.exit("canceled")
            self._addrs_to_generate = address_limit

            if not self._known_hash160s:
                print("Loading address database ...")
                self._known_hash160s = AddressSet.fromfile(open(ADDRESSDB_DEF_FILENAME, "rb"))

        return self

    # Performs basic checks so that clearly invalid mnemonic_ids can be completely skipped
    @staticmethod
    def verify_mnemonic_syntax(mnemonic_ids):
        # Length must be divisible by 3 and all ids must be present
        return len(mnemonic_ids) % 3 == 0 and None not in mnemonic_ids

    # This is the time-consuming function executed by worker thread(s). It returns a tuple: if a mnemonic
    # is correct return it, else return False for item 0; return a count of mnemonics checked for item 1
    def return_verified_password_or_false(self, mnemonic_ids_list):
        for count, mnemonic_ids in enumerate(mnemonic_ids_list, 1):

            # Check the (BIP39 or Electrum2) checksum; most guesses will fail this test
            if not self._verify_checksum(mnemonic_ids):
                continue

            # Convert the mnemonic sentence to seed bytes (according to BIP39 or Electrum2)
            seed_bytes = hmac.new("Bitcoin seed", self._derive_seed(mnemonic_ids), hashlib.sha512).digest()

            if self._verify_seed(seed_bytes):
                return mnemonic_ids, count  # found it

        return False, count

    def _verify_seed(self, seed_bytes):
        # Derive the chain of private keys for the specified path as per BIP32
        privkey_bytes   = seed_bytes[:32]
        chaincode_bytes = seed_bytes[32:]
        for i in self._path_indexes:

            if i < 2147483648:  # if it's a normal child key, derive the compressed public key
                try: data_to_hmac = coincurve.PublicKey.from_valid_secret(privkey_bytes).format()
                except ValueError: return False
            else:               # else it's a hardened child key
                data_to_hmac = "\0" + privkey_bytes  # prepended "\0" as per BIP32
            data_to_hmac += struct.pack(">I", i)  # append the index (big-endian) as per BIP32

            seed_bytes = hmac.new(chaincode_bytes, data_to_hmac, hashlib.sha512).digest()

            # The child private key is the parent one + the first half of the seed_bytes (mod n)
            privkey_bytes   = int_to_bytes((bytes_to_int(seed_bytes[:32]) +
                                            bytes_to_int(privkey_bytes)) % GENERATOR_ORDER, 32)
            chaincode_bytes = seed_bytes[32:]

        # If an extended public key was provided, check the derived chain code against it
        if self._chaincode:
            if chaincode_bytes == self._chaincode:
                return True  # found it

        else:
            # (note: the rest assumes the address index isn't hardened)

            # Derive the final public keys, searching for a match with known_hash160s
            # (these first steps below are loop invariants)
            try: data_to_hmac = coincurve.PublicKey.from_valid_secret(privkey_bytes).format()
            except ValueError: return False
            privkey_int = bytes_to_int(privkey_bytes)
            #
            for i in xrange(self._addrs_to_generate):
                seed_bytes = hmac.new(chaincode_bytes,
                    data_to_hmac + struct.pack(">I", i), hashlib.sha512).digest()

                # The final derived private key is the parent one + the first half of the seed_bytes
                d_privkey_bytes = int_to_bytes((bytes_to_int(seed_bytes[:32]) +
                                                privkey_int) % GENERATOR_ORDER, 32)

                d_pubkey = coincurve.PublicKey.from_valid_secret(d_privkey_bytes).format(compressed=False)
                if self.pubkey_to_hash160(d_pubkey) in self._known_hash160s:
                    return True

        return False

    # Returns a dummy xpub for performance testing purposes
    @staticmethod
    def _performance_xpub():
        # an xpub at path m/44'/0'/0', as Mycelium for Android would export
        return "xpub6BgCDhMefYxRS1gbVbxyokYzQji65v1eGJXGEiGdoobvFBShcNeJt97zoJBkNtbASLyTPYXJHRvkb3ahxaVVGEtC1AD4LyuBXULZcfCjBZx"


############### BIP39 ###############

@register_selectable_wallet_class("Standard BIP39/BIP44 (Mycelium, TREZOR, Ledger, Bither, Blockchain.info, Jaxx)")
class WalletBIP39(WalletBIP32):
    FIRSTFOUR_TAG = "-firstfour"

    # Load the wordlists for all languages (actual one to use is selected in config_mnemonic() )
    _language_words = {}
    @classmethod
    def _load_wordlists(cls):
        assert not cls._language_words, "_load_wordlists() should only be called once from the first init()"
        cls._do_load_wordlists("bip39")
        for wordlist_lang in cls._language_words.keys():  # takes a copy of the keys so the dict can be safely changed
            wordlist = cls._language_words[wordlist_lang]
            assert len(wordlist) == 2048, "BIP39 wordlist has 2048 words"
            # Special case for the four languages whose words may be truncated to the first four letters
            if wordlist_lang in ("en", "es", "fr", "it"):
                cls._language_words[wordlist_lang + cls.FIRSTFOUR_TAG] = [ w[:4] for w in wordlist ]
    #
    @classmethod
    def _do_load_wordlists(cls, name, wordlist_langs = None):
        if not wordlist_langs:
            wordlist_langs = []
            for filename in glob.iglob(os.path.join(wordlists_dir, name + "-??*.txt")):
                wordlist_langs.append(os.path.basename(filename)[len(name)+1:-4])  # e.g. "en", or "zh-hant"
        for lang in wordlist_langs:
            assert lang not in cls._language_words, "wordlist not already loaded"
            cls._language_words[lang] = load_wordlist(name, lang)

    @property
    def word_ids(self): return self._words
    @staticmethod
    def id_to_word(id): return id  # returns a UTF-8 encoded bytestring

    def __init__(self, path = None, loading = False):
        super(WalletBIP39, self).__init__(path, loading)
        if not self._language_words:
            self._load_wordlists()
        pbkdf2_library_name = btcrpass.load_pbkdf2_library().__name__  # btcrpass's pbkdf2 library is used in _derive_seed()
        self._kdf_overhead = 0.0026 if pbkdf2_library_name == "hashlib" else 0.013

    def __setstate__(self, state):
        # (Re)load the pbkdf2 library if necessary
        btcrpass.load_pbkdf2_library()
        self.__dict__ = state

    # Converts a mnemonic word from a Python unicode (as produced by load_wordlist())
    # into a bytestring (of type str) in the format required by BIP39
    @staticmethod
    def _unicode_to_bytes(word):
        assert isinstance(word, unicode)
        return intern(unicodedata.normalize("NFKD", word).encode("utf_8"))

    # Configures the values of four globals used later in config_btcrecover():
    # mnemonic_ids_guess, close_mnemonic_ids, num_inserts, and num_deletes;
    # also selects the appropriate wordlist language to use
    def config_mnemonic(self, mnemonic_guess = None, lang = None, passphrase = u"", expected_len = None, closematch_cutoff = 0.65):
        if expected_len:
            if expected_len < 12:
                raise ValueError("minimum BIP39 sentence length is 12 words")
            if expected_len > 24:
                raise ValueError("maximum BIP39 sentence length is 24 words")
            if expected_len % 3 != 0:
                raise ValueError("BIP39 sentence length must be evenly divisible by 3")

        # Do most of the work in this function:
        passphrase = self._config_mnemonic(mnemonic_guess, lang, passphrase, expected_len, closematch_cutoff)

        # The pbkdf2-derived salt, based on the passphrase, as per BIP39 (needed by _derive_seed());
        # first ensure that this version of Python supports the characters present in the passphrase
        if sys.maxunicode < 65536:  # if this Python is a "narrow" Unicode build
            for c in passphrase:
                c = ord(c)
                if 0xD800 <= c <= 0xDBFF or 0xDC00 <= c <= 0xDFFF:
                    raise ValueError("this version of Python doesn't support passphrases with Unicode code points > "+str(sys.maxunicode))
        self._derivation_salt = "mnemonic" + self._unicode_to_bytes(passphrase)

        # Special case for wallets which tell users to record only the first four letters of each word;
        # convert all short words into long ones (intentionally done *after* the finding of close words).
        # Specifically, update self._words and the globals mnemonic_ids_guess and close_mnemonic_ids.
        if self._lang.endswith(self.FIRSTFOUR_TAG):
            long_lang_words = self._language_words[self._lang[:-len(self.FIRSTFOUR_TAG)]]
            assert isinstance(long_lang_words[0], unicode),  "long words haven't yet been converted into bytes"
            assert isinstance(self._words[0],     bytes),    "short words have already been converted into bytes"
            assert len(long_lang_words) == len(self._words), "long and short word lists have the same length"
            long_lang_words = [ self._unicode_to_bytes(l) for l in long_lang_words ]
            short_to_long   = { s:l for s,l in zip(self._words, long_lang_words) }
            self._words     = long_lang_words
            #
            global mnemonic_ids_guess  # the to-be-replaced short-words guess
            long_ids_guess = ()        # the new long-words guess
            for short_id in mnemonic_ids_guess:
                long_ids_guess += None if short_id is None else short_to_long[short_id],
            mnemonic_ids_guess = long_ids_guess
            #
            global close_mnemonic_ids
            if close_mnemonic_ids:
                assert isinstance(close_mnemonic_ids.iterkeys()  .next(),       bytes), "close word keys have already been converted into bytes"
                assert isinstance(close_mnemonic_ids.itervalues().next()[0][0], bytes), "close word values have already been converted into bytes"
                for key in close_mnemonic_ids.keys():  # takes a copy of the keys so the dict can be safely changed
                    vals = close_mnemonic_ids.pop(key)
                    # vals is a tuple containing length-1 tuples which in turn each contain one word in bytes-format
                    close_mnemonic_ids[short_to_long[key]] = tuple( (short_to_long[v[0]],) for v in vals )

        # Calculate each word's index in binary (needed by _verify_checksum())
        self._word_to_binary = { word : "{:011b}".format(i) for i,word in enumerate(self._words) }

        # Chances a checksum is valid, e.g. 1/16 for 12 words, 1/256 for 24 words
        self._checksum_ratio = 2.0**( -( len(mnemonic_ids_guess) + num_inserts - num_deletes )//3 )
    #
    def _config_mnemonic(self, mnemonic_guess, lang, passphrase, expected_len, closematch_cutoff):

        # If a mnemonic guess wasn't provided, prompt the user for one
        if not mnemonic_guess:
            init_gui()
            mnemonic_guess = tkSimpleDialog.askstring("Seed",
                "Please enter your best guess for your seed (mnemonic):")
            if not mnemonic_guess:
                sys.exit("canceled")

        # Note: this is not in BIP39's preferred encoded form yet, instead it's
        # in the same format as load_wordlist creates (NFC normalized Unicode)
        mnemonic_guess = unicodedata.normalize("NFC", unicode(mnemonic_guess).lower()).split()
        if len(mnemonic_guess) == 1:  # assume it's a logographic script (no spaces, e.g. Chinese)
            mnemonic_guess = tuple(mnemonic_guess)

        # Select the appropriate wordlist language to use
        if not lang:
            language_word_hits = {}  # maps a language id to the # of words found in that language
            for word in mnemonic_guess:
                for lang, one_languages_words in self._language_words.iteritems():
                    if word in one_languages_words:
                        language_word_hits.setdefault(lang, 0)
                        language_word_hits[lang] += 1
            if len(language_word_hits) == 0:
                raise ValueError("can't guess wordlist language: 0 valid words")
            if len(language_word_hits) == 1:
                best_guess = language_word_hits.popitem()
            else:
                sorted_hits = language_word_hits.items()
                sorted_hits.sort(key=lambda x: x[1])  # sort based on hit count
                best_guess   = sorted_hits[-1]
                second_guess = sorted_hits[-2]
                # at least 20% must be exclusive to the best_guess language
                if best_guess[1] - second_guess[1] < 0.2 * len(mnemonic_guess):
                    raise ValueError("can't guess wordlist language: top best guesses ({}, {}) are too close ({}, {})"
                                     .format(best_guess[0], second_guess[0], best_guess[1], second_guess[1]))
            # at least half must be valid words
            if best_guess[1] < 0.5 * len(mnemonic_guess):
                raise ValueError("can't guess wordlist language: best guess ({}) has only {} valid word(s)"
                                 .format(best_guess[0], best_guess[1]))
            lang = best_guess[0]
        #
        try:
            words = self._language_words[lang]
        except KeyError:  # consistently raise ValueError for any bad inputs
            raise ValueError("can't find wordlist for language code '{}'".format(lang))
        self._lang = lang
        print("Using the '{}' wordlist.".format(lang))

        # Build the mnemonic_ids_guess and pre-calculate similar mnemonic words
        global mnemonic_ids_guess, close_mnemonic_ids
        self._initial_words_valid = True  # start off by assuming they're all valid
        mnemonic_ids_guess        = ()    # the best initial guess (w/no invalid words)
        #
        # close_mnemonic_ids is a dict; each dict key is a mnemonic_id (a string), and
        # each dict value is a tuple containing length 1 tuples, and finally each of the
        # length 1 tuples contains a single mnemonic_id which is similar to the dict's key;
        # e.g.: { "a-word" : ( ("a-ward", ), ("a-work",) ), "other-word" : ... }
        close_mnemonic_ids = {}
        for word in mnemonic_guess:
            close_words = difflib.get_close_matches(word, words, sys.maxint, closematch_cutoff)
            if close_words:
                if close_words[0] != word:
                    print(u"'{}' was in your guess, but it's not a valid seed word;\n"
                          u"    trying '{}' instead.".format(word, close_words[0]))
                    self._initial_words_valid = False
                mnemonic_ids_guess += self._unicode_to_bytes(close_words[0]),  # *now* convert to BIP39's format
                close_mnemonic_ids[mnemonic_ids_guess[-1]] = \
                    tuple( (self._unicode_to_bytes(w),) for w in close_words[1:] )
            else:
                if __name__ == b"__main__":
                    print(u"'{}' was in your guess, but there is no similar seed word;\n"
                          u"    trying all possible seed words here instead.".format(word))
                else:
                    print(u"'{}' was in your seed, but there is no similar seed word.".format(word))
                self._initial_words_valid = False
                mnemonic_ids_guess += None,

        guess_len = len(mnemonic_ids_guess)
        if not expected_len:  # (this is always explicitly specified for Electrum 2 seeds)
            assert not isinstance(self, WalletElectrum2), "WalletBIP39._config_mnemonic: expected_len is specified for WalletElectrum2 objects"
            if guess_len < 12:
                expected_len = 12
            elif guess_len > 24:
                expected_len = 24
            else:
                off_by = guess_len % 3
                if off_by == 1:
                    expected_len = guess_len - 1
                elif off_by == 2:
                    expected_len = guess_len + 1
                else:
                    expected_len = guess_len

        global num_inserts, num_deletes
        num_inserts = max(expected_len - guess_len, 0)
        num_deletes = max(guess_len - expected_len, 0)
        if num_inserts and not isinstance(self, WalletElectrum2):
            print("Seed sentence was too short, inserting {} word{} into each guess."
                  .format(num_inserts, "s" if num_inserts > 1 else ""))
        if num_deletes:
            print("Seed sentence was too long, deleting {} word{} from each guess."
                  .format(num_deletes, "s" if num_deletes > 1 else ""))

        # Now that we're done with the words in Unicode format,
        # convert them to BIP39's encoding and save for future reference
        self._words = tuple(map(self._unicode_to_bytes, words))

        if passphrase is True:
            init_gui()
            while True:
                passphrase = tkSimpleDialog.askstring("Passphrase",
                    "Please enter the passphrase you added when the seed was first created:", show="*")
                if not passphrase:
                    sys.exit("canceled")
                if passphrase == tkSimpleDialog.askstring("Passphrase", "Please re-enter the passphrase:", show="*"):
                    break
                tkMessageBox.showerror("Passphrase", "The passphrases did not match, try again.")
        return passphrase

    # Called by WalletBIP32.return_verified_password_or_false() to verify a BIP39 checksum
    def _verify_checksum(self, mnemonic_words):
        # Convert from the mnemonic_words (ids) back to the entropy bytes + checksum
        bit_string        = "".join(self._word_to_binary[w] for w in mnemonic_words)
        cksum_len_in_bits = len(mnemonic_words) // 3  # as per BIP39
        entropy_bytes     = bytearray()
        for i in xrange(0, len(bit_string) - cksum_len_in_bits, 8):
            entropy_bytes.append(int(bit_string[i:i+8], 2))
        cksum_int = int(bit_string[-cksum_len_in_bits:], 2)
        #
        # Calculate and verify the checksum
        return ord(hashlib.sha256(entropy_bytes).digest()[:1]) >> 8-cksum_len_in_bits \
               == cksum_int

    # Called by WalletBIP32.return_verified_password_or_false() to create a binary seed
    def _derive_seed(self, mnemonic_words):
        # Note: the words are already in BIP39's normalized form
        return btcrpass.pbkdf2_hmac("sha512", b" ".join(mnemonic_words), self._derivation_salt, 2048)

    # Produces a long stream of differing and incorrect mnemonic_ids guesses (for testing)
    # (uses mnemonic_ids_guess, num_inserts, and num_deletes globals as set by config_mnemonic())
    def performance_iterator(self):
        # This used to just itereate through the entire space, starting at "abandon abandon abandon ..."
        # and "counting" up from there. However "abandon abandon ... abandon about" is actually in use
        # in the blockchain (buggy software or people just messing around), and in address-database
        # mode this creates an unwanted positive hit, so now we have to start with a random prefix.
        length = len(mnemonic_ids_guess) + num_inserts - num_deletes
        assert length >= 12
        prefix = tuple(random.choice(self._words) for i in xrange(length-4))
        for guess in itertools.product(self._words, repeat=4):
            yield prefix + guess


############### bitcoinj ###############

@register_selectable_wallet_class("Bitcoinj compatible (MultiBit HD (Beta 8+), Bitcoin Wallet for Android/BlackBerry, Hive, breadwallet)")
class WalletBitcoinj(WalletBIP39):

    def __init__(self, path = None, loading = False):
        # Just calls WalletBIP39.__init__() with a hardcoded path
        if path: raise ValueError("can't specify a BIP32 path with Bitcoinj wallets")
        super(WalletBitcoinj, self).__init__("m/0'/0/", loading)

    @staticmethod
    def is_wallet_file(wallet_file):
        wallet_file.seek(0)
        if wallet_file.read(1) == b"\x0a":  # protobuf field number 1 of type length-delimited
            network_identifier_len = ord(wallet_file.read(1))
            if 1 <= network_identifier_len < 128:
                wallet_file.seek(2 + network_identifier_len)
                if wallet_file.read(1) in b"\x12\x1a":   # field number 2 or 3 of type length-delimited
                    return True
        return False

    # Load a bitcoinj wallet file (the part of it we need, just the chaincode)
    @classmethod
    def load_from_filename(cls, wallet_filename):
        from . import wallet_pb2
        pb_wallet = wallet_pb2.Wallet()
        with open(wallet_filename, "rb") as wallet_file:
            pb_wallet.ParseFromString(wallet_file.read(btcrpass.MAX_WALLET_FILE_SIZE))  # up to 64M, typical size is a few k
        if pb_wallet.encryption_type == wallet_pb2.Wallet.UNENCRYPTED:
            raise ValueError("this bitcoinj wallet is not encrypted")

        # Search for the (one and only) master public extended key (whose path length is 0)
        self = None
        for key in pb_wallet.key:
            if  key.HasField("deterministic_key") and len(key.deterministic_key.path) == 0:
                assert not self, "only one master public extended key is in the wallet file"
                assert len(key.deterministic_key.chain_code) == 32, "chaincode length is 32 bytes"
                self = cls(loading=True)
                self._chaincode = key.deterministic_key.chain_code
                # Because it's the *master* xpub, it has an empty path
                self._path_indexes = ()

        if not self:
            raise ValueError("No master public extended key was found in this bitcoinj wallet file")
        return self

    # Returns a dummy xpub for performance testing purposes
    @staticmethod
    def _performance_xpub():
        # an xpub at path m/0', as Bitcoin Wallet for Android/BlackBerry would export
        return "xpub67tjk7ug7iNivs1f1pmDswDDbk6kRCe4U1AXSiYLbtp6a2GaodSUovt3kNrDJ2q18TBX65aJZ7VqRBpnVJsaVQaBY2SANYw6kgZf4QLCpPu"


############### Electrum2 ###############

@register_selectable_wallet_class('Electrum 2.x ("standard" wallets initially created with 2.x)')
class WalletElectrum2(WalletBIP39):

    # From Electrum 2.x's mnemonic.py (coalesced)
    CJK_INTERVALS = (
        ( 0x1100,  0x11ff),
        ( 0x2e80,  0x2fdf),
        ( 0x2ff0,  0x2fff),
        ( 0x3040,  0x31ff),
        ( 0x3400,  0x4dbf),
        ( 0x4e00,  0xa4ff),
        ( 0xa960,  0xa97f),
        ( 0xac00,  0xd7ff),
        ( 0xf900,  0xfaff),
        ( 0xff00,  0xffef),
        (0x16f00, 0x16f9f),
        (0x1b000, 0x1b0ff),
        (0x20000, 0x2a6df),
        (0x2a700, 0x2b81f),
        (0x2f800, 0x2fa1d),
        (0xe0100, 0xe01ef))

    # Load the wordlists for all languages (actual one to use is selected in config_mnemonic() )
    @classmethod
    def _load_wordlists(cls):
        assert not cls._language_words, "_load_wordlists() should only be called once from the first init()"
        cls._do_load_wordlists("electrum2")
        cls._do_load_wordlists("bip39", ("en", "es", "ja", "zh-hans"))  # only the four bip39 ones used by Electrum2
        assert all(len(w) >= 1411 for w in cls._language_words.values()), \
               "Electrum2 wordlists are at least 1411 words long" # because we assume a max mnemonic length of 13

    def __init__(self, path = None, loading = False):
        # Just calls WalletBIP39.__init__() with a hardcoded path
        if path: raise ValueError("can't specify a BIP32 path with Electrum 2.x wallets")
        super(WalletElectrum2, self).__init__("m/0/", loading)
        self._checksum_ratio   = 1.0 / 256.0  # 1 in 256 checksums are valid on average
        self._needs_passphrase = None

    @staticmethod
    def is_wallet_file(wallet_file):
        wallet_file.seek(0)
        data = wallet_file.read(8)
        if data[0] == b"{":
            return None  # "maybe yes"
        try:   data = base64.b64decode(data)
        except TypeError: return False  # "definitely no"
        if data.startswith(b"BIE1"):
            sys.exit("error: Electrum 2.8+ fully-encrypted wallet files cannot be read,\n"
                     "try to recover from your master extended public key or an address instead")
        return False  # "definitely no"

    # Load an Electrum2 wallet file (the part of it we need, just the master public key)
    @classmethod
    def load_from_filename(cls, wallet_filename):
        import json

        with open(wallet_filename) as wallet_file:
            wallet = json.load(wallet_file)
        wallet_type = wallet.get("wallet_type")
        if not wallet_type:
            raise ValueError("Unrecognized wallet format (Electrum2 wallet_type not found)")
        if wallet_type == "old":  # if it's been converted from 1.x to 2.y (y<7), return a WalletElectrum1 object
            return WalletElectrum1._load_from_dict(wallet)
        if not wallet.get("use_encryption"):
            raise ValueError("Electrum2 wallet is not encrypted")
        seed_version = wallet.get("seed_version", "(not found)")
        if wallet.get("seed_version") not in (11, 12, 13):  # all 2.x versions as of Oct 2016
            raise NotImplementedError("Unsupported Electrum2 seed version " + unicode(seed_version))
        if wallet_type != "standard":
            raise NotImplementedError("Unsupported Electrum2 wallet type: " + wallet_type)

        mpk = needs_passphrase = None
        while True:  # "loops" exactly once; only here so we've something to break out of

            # Electrum 2.7+ standard wallets have a keystore
            keystore = wallet.get("keystore")
            if keystore:
                keystore_type = keystore.get("type", "(not found)")

                # Wallets originally created by an Electrum 2.x version
                if keystore_type == "bip32":
                    mpk = keystore["xpub"]
                    if keystore.get("passphrase"):
                        needs_passphrase = True
                    break

                # Former Electrum 1.x wallet after conversion to Electrum 2.7+ standard-wallet format
                elif keystore_type == "old":
                    # Construct and return a WalletElectrum1 object
                    mpk = base64.b16decode(keystore["mpk"], casefold=True)
                    if len(mpk) != 64:
                        raise ValueError("Electrum1 master public key is not 64 bytes long")
                    self = WalletElectrum1(loading=True)
                    self._master_pubkey = "\x04" + mpk  # prepend the uncompressed tag
                    return self

                else:
                    print("warning: found unsupported keystore type " + keystore_type, file=sys.stderr)

            # Electrum 2.0 - 2.6.4 wallet (of any wallet type)
            mpks = wallet.get("master_public_keys")
            if mpks:
                mpk = mpks.values()[0]
                break

            raise RuntimeError("No master public keys found in Electrum2 wallet")

        assert mpk
        wallet = cls.create_from_params(mpk)
        wallet._needs_passphrase = needs_passphrase
        return wallet

    # Converts a mnemonic word from a Python unicode (as produced by load_wordlist())
    # into a bytestring (of type str) via the same method as Electrum 2.x
    @staticmethod
    def _unicode_to_bytes(word):
        assert isinstance(word, unicode)
        word = unicodedata.normalize("NFKD", word)
        word = filter(lambda c: not unicodedata.combining(c), word)  # Electrum 2.x removes combining marks
        return intern(word.encode("utf_8"))

    def config_mnemonic(self, mnemonic_guess = None, lang = None, passphrase = u"", expected_len = None, closematch_cutoff = 0.65):
        if expected_len is None:
            expected_len_specified = False
            if self._needs_passphrase:
                expected_len = 12
                print("notice: presence of a mnemonic passphrase implies a 12-word long Electrum 2.7+ mnemonic",
                      file=sys.stderr)
            else:
                init_gui()
                if tkMessageBox.askyesno("Electrum 2.x version",
                        "Did you CREATE your wallet with Electrum version 2.7 (released Oct 2 2016) or later?"
                        "\n\nPlease choose No if you're unsure.",
                        default=tkMessageBox.NO):
                    expected_len = 12
                else:
                    expected_len = 13
        else:
            expected_len_specified = True
            if expected_len > 13:
                raise ValueError("maximum mnemonic length for Electrum2 is 13 words")

        if self._needs_passphrase and not passphrase:
            passphrase = True  # tells self._config_mnemonic() to prompt for a passphrase below
            init_gui()
            tkMessageBox.showwarning("Passphrase",
                'This Electrum seed was extended with "custom words" (a seed passphrase) when it '
                "was first created. You will need to enter it to continue.\n\nNote that this seed "
                "passphrase is NOT the same as the wallet password that's entered to spend funds.")
        # Calls WalletBIP39's generic version (note the leading _) with the mnemonic
        # length (which for Electrum2 wallets alone is treated only as a maximum length)
        passphrase = self._config_mnemonic(mnemonic_guess, lang, passphrase, expected_len, closematch_cutoff)

        # Python 2.x running Electrum 2.x has a Unicode bug where if there are any code points > 65535,
        # they might be normalized differently between different Python 2 builds (narrow vs. wide Unicode)
        assert isinstance(passphrase, unicode)
        if sys.maxunicode < 65536:  # the check for narrow Unicode builds looks for UTF-16 surrogate pairs:
            maybe_buggy = any(0xD800 <= ord(c) <= 0xDBFF or 0xDC00 <= ord(c) <= 0xDFFF for c in passphrase)
        else:                       # the check for wide Unicode builds:
            maybe_buggy = any(ord(c) > 65535 for c in passphrase)
        if maybe_buggy:
            print("warning: due to Unicode incompatibilities, it's strongly recommended\n"
                  "         that you run seedrecover.py on the same computer (or at least\n"
                  "         the same OS) where you created your wallet", file=sys.stderr)

        if expected_len_specified and num_inserts:
            print("notice: for Electrum 2.x, --mnemonic-length is the max length tried, but not necessarily the min",
                  file=sys.stderr)

        # The pbkdf2-derived salt (needed by _derive_seed()); Electrum 2.x is similar to BIP39,
        # however it differs in the iffy(?) normalization procedure and the prepended string
        import string
        passphrase = unicodedata.normalize("NFKD", passphrase)  # problematic w/Python narrow Unicode builds, same as Electrum
        passphrase = passphrase.lower()  # (?)
        passphrase = filter(lambda c: not unicodedata.combining(c), passphrase)  # remove combining marks
        passphrase = u" ".join(passphrase.split())  # replace whitespace sequences with a single ASCII space
        # remove ASCII whitespace between CJK characters (?)
        passphrase = u"".join(c for i,c in enumerate(passphrase) if not (
                c in string.whitespace
            and any(intvl[0] <= ord(passphrase[i-1]) <= intvl[1] for intvl in self.CJK_INTERVALS)
            and any(intvl[0] <= ord(passphrase[i+1]) <= intvl[1] for intvl in self.CJK_INTERVALS)))
        self._derivation_salt = "electrum" + passphrase.encode("utf_8")

        # Electrum 2.x doesn't separate mnemonic words with spaces in sentences for any CJK
        # scripts when calculating the checksum or deriving a binary seed (even though this
        # seem inappropriate for some CJK scripts such as Hiragana as used by the ja wordlist)
        self._space = "" if self._lang in ("ja", "zh-hans", "zh-hant") else " "

    # Performs basic checks so that clearly invalid mnemonic_ids can be completely skipped
    @staticmethod
    def verify_mnemonic_syntax(mnemonic_ids):
        # As long as each wordlist has at least 1411 words (checked by _load_wordlists()),
        # a valid mnemonic is at most 13 words long (and all ids must be present)
        return len(mnemonic_ids) <= 13 and None not in mnemonic_ids

    # Called by WalletBIP32.return_verified_password_or_false() to verify an Electrum2 checksum
    def _verify_checksum(self, mnemonic_words):
        return hmac.new("Seed version", self._space.join(mnemonic_words), hashlib.sha512) \
               .digest()[0] == "\x01"

    # Called by WalletBIP32.return_verified_password_or_false() to create a binary seed
    def _derive_seed(self, mnemonic_words):
        # Note: the words are already in Electrum2's normalized form
        return btcrpass.pbkdf2_hmac("sha512", self._space.join(mnemonic_words), self._derivation_salt, 2048)

    # Returns a dummy xpub for performance testing purposes
    @staticmethod
    def _performance_xpub():
        # an xpub at path m, as Electrum would export
        return "xpub661MyMwAqRbcGsUXkGBkytQkYZ6M16bFWwTocQDdPSm6eJ1wUsxG5qty1kTCUq7EztwMscUstHVo1XCJMxWyLn4PP1asLjt4gPt3HkA81qe"


############### Ethereum ###############

@register_selectable_wallet_class('Ethereum Standard BIP39/BIP44 (Jaxx, MetaMask, MyEtherWallet, TREZOR, Exodus, NOT Ledger)')
class WalletEthereum(WalletBIP39):

    def __init__(self, path = None, loading = False):
        if not path: path = "m/44'/60'/0'/0/"
        super(WalletEthereum, self).__init__(path, loading)
        global sha3
        import sha3

    def __setstate__(self, state):
        super(WalletEthereum, self).__setstate__(state)
        # (re-)load the required libraries after being unpickled
        global sha3
        import sha3

    @classmethod
    def create_from_params(cls, *args, **kwargs):
        if isinstance(kwargs.get("hash160s"), AddressSet):
            raise ValueError("can't use an address database with Ethereum wallets")
        self = super(WalletEthereum, cls).create_from_params(*args, **kwargs)
        if hasattr(self, "_known_hash160s") and isinstance(self._known_hash160s, AddressSet):
            raise ValueError("can't use an address database with Ethereum wallets")
        return self

    @staticmethod
    def _addresses_to_hash160s(addresses):
        hash160s = set()
        for address in addresses:
            if address[:2].lower() == "0x":
                address = address[2:]
            if len(address) != 40:
                raise ValueError("length (excluding any '0x' prefix) of Ethereum addresses must be 40")
            cur_hash160 = base64.b16decode(address, casefold=True)
            if not address.islower():  # verify the EIP55 checksum unless all letters are lowercase
                checksum = sha3.keccak_256(base64.b16encode(cur_hash160).lower()).digest()
                for nibble, c in enumerate(address, 0):
                    if c.isalpha() and \
                       c.isupper() != bool(ord(checksum[nibble // 2]) & (0b1000 if nibble&1 else 0b10000000)):
                            raise ValueError("invalid EIP55 checksum")
            hash160s.add(cur_hash160)
        return hash160s

    @staticmethod
    def pubkey_to_hash160(uncompressed_pubkey):
        """convert from an uncompressed public key to its Ethereum hash160 form

        :param uncompressed_pubkey: SEC 1 EllipticCurvePoint OctetString
        :type uncompressed_pubkey: str
        :return: last 20 bytes of keccak256(raw_64_byte_pubkey)
        :rtype: str
        """
        assert len(uncompressed_pubkey) == 65 and uncompressed_pubkey[0] == "\x04"
        return sha3.keccak_256(uncompressed_pubkey[1:]).digest()[-20:]


################################### Main ###################################


tk_root = None
def init_gui():
    global tk_root, tk, tkFileDialog, tkSimpleDialog, tkMessageBox
    if not tk_root:

        if sys.platform == "win32":
            # Some py2exe .dll's, when registered as Windows shell extensions (e.g. SpiderOak), can interfere
            # with Python scripts which spawn a shell (e.g. a file selection dialog). The code below blocks
            # required modules from loading and prevents any such py2exe .dlls from causing too much trouble.
            sys.modules["win32api"] = None
            sys.modules["win32com"] = None

        import Tkinter as tk
        import tkFileDialog, tkSimpleDialog, tkMessageBox
        tk_root = tk.Tk(className="seedrecover.py")  # initialize library
        tk_root.withdraw()                           # but don't display a window (yet)


# seed.py uses routines from password.py to generate guesses, however instead
# of dealing with passwords (immutable sequences of characters), it deals with
# seeds (represented as immutable sequences of mnemonic_ids). More specifically,
# seeds are tuples of mnemonic_ids, and a mnemonic_id is just an int for Electrum1,
# or a UTF-8 bytestring id for most other wallet types.

# These are simple typo generators; see btcrpass.py for additional information.
# Instead of returning iterables of sequences of characters (iterables of strings),
# these return iterables of sequences of mnemonic_ids (iterables of partial seeds).
#
@btcrpass.register_simple_typo("deleteword")
def delete_word(mnemonic_ids, i):
    return (),
#
@btcrpass.register_simple_typo("replaceword")
def replace_word(mnemonic_ids, i):
    if mnemonic_ids[i] is None: return (),      # don't touch invalid words
    return ((new_id,) for new_id in loaded_wallet.word_ids if new_id != mnemonic_ids[i])
#
@btcrpass.register_simple_typo("replacecloseword")
def replace_close_word(mnemonic_ids, i):
    if mnemonic_ids[i] is None: return (),      # don't touch invalid words
    return close_mnemonic_ids[mnemonic_ids[i]]  # the pre-calculated similar words
#
@btcrpass.register_simple_typo("replacewrongword")
def replace_wrong_word(mnemonic_ids, i):
    if mnemonic_ids[i] is not None: return (),  # only replace invalid words
    return ((new_id,) for new_id in loaded_wallet.word_ids)


# Builds a command line and then runs btcrecover with it.
#   typos     - max number of mistakes to apply to each guess
#   big_typos - max number of "big" mistakes to apply to each guess;
#               a big mistake involves replacing or inserting a word using the
#               full word list, and significantly increases the search time
#   min_typos - min number of mistakes to apply to each guess
num_inserts = num_deletes = 0
def run_btcrecover(typos, big_typos = 0, min_typos = 0, is_performance = False, extra_args = []):
    if typos < 0:  # typos == 0 is silly, but causes no harm
        raise ValueError("typos must be >= 0")
    if big_typos < 0:
        raise ValueError("big-typos must be >= 0")
    if big_typos > typos:
        raise ValueError("typos includes big_typos, therefore it must be >= big_typos")
    # min_typos < 0 is silly, but causes no harm
    # typos < min_typos is an error; it's checked in btcrpass.parse_arguments()

    # Local copies of globals whose changes should only be visible locally
    l_num_inserts = num_inserts
    l_num_deletes = num_deletes

    # Number of words that were definitely wrong in the guess
    num_wrong = sum(map(lambda id: id is None, mnemonic_ids_guess))

    # Start building the command-line arguments
    btcr_args = "--typos " + str(typos)

    if is_performance:
        btcr_args += " --performance"
        # These typos are not supported by seedrecover with --performance testing:
        l_num_inserts = l_num_deletes = num_wrong = 0

    # First, check if there are any required typos (if there are missing or extra
    # words in the guess) and adjust the max number of other typos to later apply

    any_typos  = typos  # the max number of typos left after removing required typos
    #big_typos =        # the max number of "big" typos after removing required typos (an arg from above)

    if l_num_deletes:  # if the guess is too long (extra words need to be deleted)
        any_typos -= l_num_deletes
        btcr_args += " --typos-deleteword"
        if l_num_deletes < typos:
            btcr_args += " --max-typos-deleteword " + str(l_num_deletes)

    if num_wrong:      # if any of the words were invalid (and need to be replaced)
        any_typos -= num_wrong
        big_typos -= num_wrong
        btcr_args += " --typos-replacewrongword"
        if num_wrong < typos:
            btcr_args += " --max-typos-replacewrongword " + str(num_wrong)

    # For (only) Electrum2, num_inserts are not required, so we try several sub-phases with a
    # different number of inserts each time; for all others the total num_inserts are required
    if isinstance(loaded_wallet, WalletElectrum2):
        num_inserts_to_try = xrange(l_num_inserts + 1)  # try a range
    else:
        num_inserts_to_try = l_num_inserts,             # only try the required max
    for subphase_num, cur_num_inserts in enumerate(num_inserts_to_try, 1):

        # Create local copies of these which are reset at the beginning of each loop
        l_any_typos = any_typos
        l_big_typos = big_typos
        l_btcr_args = btcr_args

        ids_to_try_inserting = None
        if cur_num_inserts:  # if the guess is too short (words need to be inserted)
            l_any_typos -= cur_num_inserts
            l_big_typos -= cur_num_inserts
            # (instead of --typos-insert we'll set inserted_items=ids_to_try_inserting below)
            ids_to_try_inserting = ((id,) for id in loaded_wallet.word_ids)
            l_btcr_args += " --max-adjacent-inserts " + str(cur_num_inserts)
            if cur_num_inserts < typos:
                l_btcr_args += " --max-typos-insert " + str(cur_num_inserts)

        # For >1 subphases, print this out now or just after the skip-this-phase check below
        if len(num_inserts_to_try) > 1:
            subphase_msg = "  - subphase {}/{}: with {} inserted seed word{}".format(
                subphase_num, len(num_inserts_to_try),
                cur_num_inserts, "" if cur_num_inserts == 1 else "s")
        if subphase_num > 1:
            print(subphase_msg)
            maybe_skipping = "the remainder of this phase."
        else:
            maybe_skipping = "this phase."

        if l_any_typos < 0:  # if too many typos are required to generate valid mnemonics
            print("Not enough mistakes permitted to produce a valid seed; skipping", maybe_skipping)
            return False
        if l_big_typos < 0:  # if too many big typos are required to generate valid mnemonics
            print("Not enough entirely different seed words permitted; skipping", maybe_skipping)
            return False
        assert typos >= cur_num_inserts + l_num_deletes + num_wrong

        if subphase_num == 1 and len(num_inserts_to_try) > 1:
            print(subphase_msg)

        # Because btcrecover doesn't support --min-typos-* on a per-typo basis, it ends
        # up generating some invalid guesses. We can use --min-typos to filter out some
        # of them (the remainder is later filtered out by verify_mnemonic_syntax()).
        min_typos = max(min_typos, cur_num_inserts + l_num_deletes + num_wrong)
        if min_typos:
            l_btcr_args += " --min-typos " + str(min_typos)

        # Next, if the required typos above haven't consumed all available typos
        # (as specified by the function's args), add some "optional" typos

        if l_any_typos:
            l_btcr_args += " --typos-swap"
            if l_any_typos < typos:
                l_btcr_args += " --max-typos-swap " + str(l_any_typos)

            if l_big_typos:  # if there are any big typos left, add the replaceword typo
                l_btcr_args += " --typos-replaceword"
                if l_big_typos < typos:
                    l_btcr_args += " --max-typos-replaceword " + str(l_big_typos)

            # only add replacecloseword typos if they're not already covered by the
            # replaceword typos added above and there exists at least one close word
            num_replacecloseword = l_any_typos - l_big_typos
            if num_replacecloseword > 0 and any(len(ids) > 0 for ids in close_mnemonic_ids.itervalues()):
                l_btcr_args += " --typos-replacecloseword"
                if num_replacecloseword < typos:
                    l_btcr_args += " --max-typos-replacecloseword " + str(num_replacecloseword)

        btcrpass.parse_arguments(
            l_btcr_args.split() + extra_args,
            inserted_items= ids_to_try_inserting,
            wallet=         loaded_wallet,
            base_iterator=  (mnemonic_ids_guess,) if not is_performance else None, # the one guess to modify
            perf_iterator=  lambda: loaded_wallet.performance_iterator(),
            check_only=     loaded_wallet.verify_mnemonic_syntax
        )
        (mnemonic_found, not_found_msg) = btcrpass.main()

        if mnemonic_found:
            return mnemonic_found
        elif not_found_msg is None:
            return None  # An error occurred or Ctrl-C was pressed inside btcrpass.main()

    return False  # No error occurred; the mnemonic wasn't found


def register_autodetecting_wallets():
    """Registers wallets which can do file auto-detection with btcrecover's auto-detect mechanism

    :rtype: None
    """
    btcrpass.clear_registered_wallets()
    for wallet_cls, description in selectable_wallet_classes:
        if hasattr(wallet_cls, "is_wallet_file"):
            btcrpass.register_wallet_class(wallet_cls)


def main(argv):
    global loaded_wallet
    loaded_wallet = wallet_type = None
    create_from_params     = {}  # additional args to pass to wallet_type.create_from_params()
    config_mnemonic_params = {}  # additional args to pass to wallet.config_mnemonic()
    phase                  = {}  # if only one phase is requested, the args to pass to run_btcrecover()
    extra_args             = []  # additional args to pass to btcrpass.parse_arguments() (in run_btcrecover())

    if argv or "_ARGCOMPLETE" in os.environ:
        import argparse
        parser = argparse.ArgumentParser()
        parser.add_argument("--wallet",      metavar="FILE",        help="the wallet file")
        parser.add_argument("--wallet-type", metavar="TYPE",        help="if not using a wallet file, the wallet type")
        parser.add_argument("--mpk",         metavar="XPUB-OR-HEX", help="if not using a wallet file, the master public key")
        parser.add_argument("--addrs",       metavar="ADDRESS",     nargs="+", help="if not using an mpk, address(es) in the wallet")
        parser.add_argument("--addressdb",   metavar="FILE", nargs="?", help="if not using addrs, use a full address database (default: %(const)s)", const=ADDRESSDB_DEF_FILENAME)
        parser.add_argument("--addr-limit",  type=int, metavar="COUNT", help="if using addrs or addressdb, the generation limit")
        parser.add_argument("--typos",       type=int, metavar="COUNT", help="the max number of mistakes to try (default: auto)")
        parser.add_argument("--big-typos",   type=int, metavar="COUNT", help="the max number of big (entirely different word) mistakes to try (default: auto or 0)")
        parser.add_argument("--min-typos",   type=int, metavar="COUNT", help="enforce a min # of mistakes per guess")
        parser.add_argument("--close-match",type=float,metavar="CUTOFF",help="try words which are less/more similar for each mistake (0.0 to 1.0, default: 0.65)")
        parser.add_argument("--passphrase",  action="store_true",       help="the mnemonic is augmented with a known passphrase (BIP39 or Electrum 2.x only)")
        parser.add_argument("--passphrase-prompt", action="store_true", help="prompt for the mnemonic passphrase via the terminal (default: via the GUI)")
        parser.add_argument("--mnemonic-prompt",   action="store_true", help="prompt for the mnemonic guess via the terminal (default: via the GUI)")
        parser.add_argument("--mnemonic-length", type=int, metavar="WORD-COUNT", help="the length of the correct mnemonic (default: auto)")
        parser.add_argument("--language",    metavar="LANG-CODE",       help="the wordlist language to use (see wordlists/README.md, default: auto)")
        parser.add_argument("--bip32-path",  metavar="PATH",            help="path (e.g. m/0'/0/) excluding the final index (default: BIP44 account 0)")
        parser.add_argument("--skip",        type=int, metavar="COUNT", help="skip this many initial passwords for continuing an interrupted search")
        parser.add_argument("--threads", type=int, metavar="COUNT", help="number of worker threads (default: number of CPUs, {})".format(btcrpass.cpus))
        parser.add_argument("--worker",      metavar="ID#/TOTAL#",  help="divide the workload between TOTAL# servers, where each has a different ID# between 1 and TOTAL#")
        parser.add_argument("--max-eta",     type=int,              help="max estimated runtime before refusing to even start (default: 168 hours, i.e. 1 week)")
        parser.add_argument("--no-eta",      action="store_true",   help="disable calculating the estimated time to completion")
        parser.add_argument("--no-dupchecks",action="store_true",   help="disable duplicate guess checking to save memory")
        parser.add_argument("--no-progress", action="store_true",   help="disable the progress bar")
        parser.add_argument("--no-pause",    action="store_true",   help="never pause before exiting (default: auto)")
        parser.add_argument("--performance", action="store_true",   help="run a continuous performance test (Ctrl-C to exit)")
        parser.add_argument("--btcr-args",   action="store_true",   help=argparse.SUPPRESS)
        parser.add_argument("--version","-v",action="store_true",   help="show full version information and exit")

        # Optional bash tab completion support
        try:
            import argcomplete
            argcomplete.autocomplete(parser)
        except ImportError:
            pass
        assert argv

        # Parse the args; unknown args will be passed to btcrpass.parse_arguments() iff --btcr-args is specified
        args, extra_args = parser.parse_known_args(argv)
        if extra_args and not args.btcr_args:
            parser.parse_args(argv)  # re-parse them just to generate an error for the unknown args
            assert False

        # Version information is always printed by seedrecover.py, so just exit
        if args.version: sys.exit(0)

        if args.wallet:
            loaded_wallet = btcrpass.load_wallet(args.wallet)

        # Look up the --wallet-type arg in the list of selectable_wallet_classes
        if args.wallet_type:
            if args.wallet:
                print("warning: --wallet-type is ignored when a wallet is provided", file=sys.stderr)
            else:
                args.wallet_type  = args.wallet_type.lower()
                wallet_type_names = []
                for cls, desc in selectable_wallet_classes:
                    wallet_type_names.append(cls.__name__.replace("Wallet", "", 1).lower())
                    if wallet_type_names[-1] == args.wallet_type:
                        wallet_type = cls
                        break
                else:
                    wallet_type_names.sort()
                    sys.exit("--wallet-type must be one of: " + ", ".join(wallet_type_names))

        if args.mpk:
            if args.wallet:
                print("warning: --mpk is ignored when a wallet is provided", file=sys.stderr)
            else:
                create_from_params["mpk"] = args.mpk

        if args.addrs:
            if args.wallet:
                print("warning: --addrs is ignored when a wallet is provided", file=sys.stderr)
            else:
                create_from_params["addresses"] = args.addrs

        if args.addr_limit is not None:
            if args.wallet:
                print("warning: --addr-limit is ignored when a wallet is provided", file=sys.stderr)
            else:
                create_from_params["address_limit"] = args.addr_limit

        if args.addressdb and not os.path.isfile(args.addressdb):
            sys.exit("file '{}' does not exist".format(args.addressdb))

        if args.typos is not None:
            phase["typos"] = args.typos

        if args.big_typos is not None:
            phase["big_typos"] = args.big_typos
            if not args.typos:
                phase["typos"] = args.big_typos

        if args.min_typos is not None:
            if not phase.get("typos"):
                sys.exit("--typos must be specified when using --min_typos")
            phase["min_typos"] = args.min_typos

        if args.close_match is not None:
            config_mnemonic_params["closematch_cutoff"] = args.close_match

        if args.mnemonic_prompt:
            encoding = sys.stdin.encoding or "ASCII"
            if "utf" not in encoding.lower():
                print("terminal does not support UTF; mnemonics with non-ASCII chars might not work", file=sys.stderr)
            mnemonic_guess = raw_input("Please enter your best guess for your mnemonic (seed)\n> ")
            if not mnemonic_guess:
                sys.exit("canceled")
            if isinstance(mnemonic_guess, str):
                mnemonic_guess = mnemonic_guess.decode(encoding)  # convert from terminal's encoding to unicode
            config_mnemonic_params["mnemonic_guess"] = mnemonic_guess

        if args.passphrase_prompt:
            import getpass
            encoding = sys.stdin.encoding or "ASCII"
            if "utf" not in encoding.lower():
                print("warning: terminal does not support UTF; passwords with non-ASCII chars might not work", file=sys.stderr)
            print("(note your passphrase will not be displayed as you type)")
            while True:
                passphrase = getpass.getpass("Please enter the passphrase you added when the seed was first created: ")
                if not passphrase:
                    sys.exit("canceled")
                if passphrase == getpass.getpass("Please re-enter the passphrase: "):
                    break
                print("The passphrases did not match, try again.")
            if isinstance(passphrase, str):
                passphrase = passphrase.decode(encoding)  # convert from terminal's encoding to unicode
            config_mnemonic_params["passphrase"] = passphrase
        elif args.passphrase:
            config_mnemonic_params["passphrase"] = True  # config_mnemonic() will prompt for one

        if args.language:
            config_mnemonic_params["lang"] = args.language.lower()

        if args.mnemonic_length is not None:
            config_mnemonic_params["expected_len"] = args.mnemonic_length

        if args.bip32_path:
            if args.wallet:
                print("warning: --bip32-path is ignored when a wallet is provided", file=sys.stderr)
            else:
                create_from_params["path"] = args.bip32_path

        # These arguments and their values are passed on to btcrpass.parse_arguments()
        for argkey in "skip", "threads", "worker", "max_eta":
            if args.__dict__[argkey] is not None:
                extra_args.extend(("--"+argkey.replace("_", "-"), str(args.__dict__[argkey])))

        # These arguments (which have no values) are passed on to btcrpass.parse_arguments()
        for argkey in "no_eta", "no_dupchecks", "no_progress":
            if args.__dict__[argkey]:
                extra_args.append("--"+argkey.replace("_", "-"))

        if args.performance:
            create_from_params["is_performance"] = phase["is_performance"] = True
            phase.setdefault("typos", 0)
            if not args.mnemonic_prompt:
                # Create a dummy mnemonic; only its language and length are used for anything
                config_mnemonic_params["mnemonic_guess"] = " ".join("act" for i in xrange(args.mnemonic_length or 12))

        if args.addressdb:
            print("Loading address database ...")
            create_from_params["hash160s"] = AddressSet.fromfile(open(args.addressdb, "rb"))

    else:  # else if no command-line args are present
        global pause_at_exit
        pause_at_exit = True
        atexit.register(lambda: pause_at_exit and
                                not multiprocessing.current_process().name.startswith("PoolWorker-") and
                                raw_input("Press Enter to exit ..."))

    if not loaded_wallet and not wallet_type:  # neither --wallet nor --wallet-type were specified

        # Ask for a wallet file
        init_gui()
        wallet_filename = tkFileDialog.askopenfilename(title="Please select your wallet file if you have one")
        if wallet_filename:
            loaded_wallet = btcrpass.load_wallet(wallet_filename)  # raises on failure; no second chance

    if not loaded_wallet:    # if no wallet file was chosen

        if not wallet_type:  # if --wallet-type wasn't specified

            # Without a wallet file, we can't automatically determine the wallet type, so prompt the
            # user to select a wallet that's been registered with @register_selectable_wallet_class
            selectable_wallet_classes.sort(key=lambda x: x[1])  # sort by description
            class WalletTypeDialog(tkSimpleDialog.Dialog):
                def body(self, master):
                    self.wallet_type     = None
                    self._index_to_cls   = []
                    self._selected_index = tk.IntVar(value= -1)
                    for i, (cls, desc) in enumerate(selectable_wallet_classes):
                        self._index_to_cls.append(cls)
                        tk.Radiobutton(master, variable=self._selected_index, value=i, text=desc) \
                            .pack(anchor=tk.W)
                def validate(self):
                    if self._selected_index.get() < 0:
                        tkMessageBox.showwarning("Wallet Type", "Please select a wallet type")
                        return False
                    return True
                def apply(self):
                    self.wallet_type = self._index_to_cls[self._selected_index.get()]
            #
            wallet_type_dialog = WalletTypeDialog(tk_root, "Please select your wallet type")
            wallet_type = wallet_type_dialog.wallet_type
            if not wallet_type:
                sys.exit("canceled")

        try:
            loaded_wallet = wallet_type.create_from_params(**create_from_params)
        except TypeError as e:
            matched = re.match("create_from_params\(\) got an unexpected keyword argument '(.*)'", str(e))
            if matched:
                sys.exit("{} does not support the {} option".format(wallet_type.__name__, matched.group(1)))
            raise
        except ValueError as e:
            sys.exit(e)

    try:
        loaded_wallet.config_mnemonic(**config_mnemonic_params)
    except TypeError as e:
        matched = re.match("config_mnemonic\(\) got an unexpected keyword argument '(.*)'", str(e))
        if matched:
            sys.exit("{} does not support the {} option".format(loaded_wallet.__class__.__name__, matched.group(1)))
        raise
    except ValueError as e:
        sys.exit(e)

    # Seeds for some wallet types have a checksum which is unlikely to be correct
    # for the initial provided seed guess; if it is correct, let the user know
    try:
        if (  loaded_wallet._initial_words_valid
          and loaded_wallet.verify_mnemonic_syntax(mnemonic_ids_guess)
          and loaded_wallet._verify_checksum(mnemonic_ids_guess) ):
            print(u"Initial seed guess has a valid checksum ({:.2g}% chance).".format(loaded_wallet._checksum_ratio * 100.0))
    except AttributeError: pass

    # Now that most of the GUI code is done, undo any Windows shell extension workarounds from init_gui()
    if sys.platform == "win32" and tk_root:
        del sys.modules["win32api"]
        del sys.modules["win32com"]
        # Some py2exe-compiled .dll shell extensions set sys.frozen, which should only be set
        # for "frozen" py2exe .exe's; this causes problems with multiprocessing, so delete it
        try:
            del sys.frozen
        except AttributeError: pass

    if phase:
        phases = (phase,)
    # Set reasonable defaults for the search phases
    else:
        # If each guess is very slow, separate out the first two phases
        passwords_per_seconds = loaded_wallet.passwords_per_seconds(1)
        if passwords_per_seconds < 25:
            phases = [ dict(typos=1), dict(typos=2, min_typos=2) ]
        else:
            phases = [ dict(typos=2) ]
        #
        # These two phases are added to all searches
        phases.extend(( dict(typos=1, big_typos=1), dict(typos=2, big_typos=1, min_typos=2) ))
        #
        # Add a final more thorough phase if it's not likely to take more than a few hours
        if len(mnemonic_ids_guess) <= 13 and passwords_per_seconds >=  750 or \
           len(mnemonic_ids_guess) <= 19 and passwords_per_seconds >= 2500:
            phases.append(dict(typos=3, big_typos=1, min_typos=3, extra_args=["--no-dupchecks"]))

    for phase_num, phase_params in enumerate(phases, 1):

        # Print a friendly message describing this phase's search settings
        print("Phase {}/{}: ".format(phase_num, len(phases)), end="")
        if phase_params["typos"] == 1:
            print("1 mistake", end="")
        else:
            print("up to {} mistakes".format(phase_params["typos"]), end="")
        if phase_params.get("big_typos"):
            if phase_params["big_typos"] == phase_params["typos"] == 1:
                print(" which can be an entirely different seed word.")
            else:
                print(", {} of which can be an entirely different seed word.".format(phase_params["big_typos"]))
        else:
            print(", excluding entirely different seed words.")

        # Perform this phase's search
        phase_params.setdefault("extra_args", []).extend(extra_args)
        mnemonic_found = run_btcrecover(**phase_params)

        if mnemonic_found:
            return " ".join(loaded_wallet.id_to_word(i) for i in mnemonic_found).decode("utf_8")
        elif mnemonic_found is None:
            return None  # An error occurred or Ctrl-C was pressed inside btcrpass.main()
        else:
            print("Seed not found" + ( ", sorry..." if phase_num==len(phases) else "" ))

    return False  # No error occurred; the mnemonic wasn't found

def show_mnemonic_gui(mnemonic_sentence):
    """may be called *after* main() to display the successful result iff the GUI is in use

    :param mnemonic_sentence: the mnemonic sentence that was found
    :type mnemonic_sentence: unicode
    :rtype: None
    """
    assert tk_root
    global pause_at_exit
    padding = 6
    tk.Label(text="WARNING: seed information is sensitive, carefully protect it and do not share", fg="red") \
        .pack(padx=padding, pady=padding)
    tk.Label(text="Seed found:").pack(side=tk.LEFT, padx=padding, pady=padding)
    entry = tk.Entry(width=80, readonlybackground="white")
    entry.insert(0, mnemonic_sentence)
    entry.config(state="readonly")
    entry.select_range(0, tk.END)
    entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=padding, pady=padding)
    tk_root.deiconify()
    tk_root.lift()
    entry.focus_set()
    tk_root.mainloop()  # blocks until the user closes the window
    pause_at_exit = False