Exemplo n.º 1
0
def recover():

    droid.dialogCreateAlert("Wallet not found","Do you want to create a new wallet, or restore an existing one?")
    droid.dialogSetPositiveButtonText('Create')
    droid.dialogSetNeutralButtonText('Restore')
    droid.dialogSetNegativeButtonText('Cancel')
    droid.dialogShow()
    response = droid.dialogGetResponse().result
    droid.dialogDismiss()
    if response.get('which') == 'negative':
        exit(1)

    is_recovery = response.get('which') == 'neutral'

    if not is_recovery:
        wallet.new_seed(None)
    else:
        if modal_question("Input method",None,'QR Code', 'mnemonic'):
            code = droid.scanBarcode()
            r = code.result
            if r:
                seed = r['extras']['SCAN_RESULT']
            else:
                exit(1)
        else:
            m = modal_input('Mnemonic','please enter your code')
            try:
                seed = mnemonic.mn_decode(m.split(' '))
            except:
                modal_dialog('error: could not decode this seed')
                exit(1)

        wallet.seed = str(seed)

    modal_dialog('Your seed is:', wallet.seed)
    modal_dialog('Mnemonic code:', ' '.join(mnemonic.mn_encode(wallet.seed)) )

    msg = "recovering wallet..." if is_recovery else "creating wallet..."
    droid.dialogCreateSpinnerProgress("Electrum", msg)
    droid.dialogShow()

    wallet.init_mpk( wallet.seed )
    WalletSynchronizer(wallet,True).start()
    wallet.update()

    droid.dialogDismiss()
    droid.vibrate()

    if is_recovery:
        if wallet.is_found():
            wallet.update_tx_history()
            wallet.fill_addressbook()
            modal_dialog("recovery successful")
        else:
            if not modal_question("no transactions found for this seed","do you want to keep this wallet?"):
                exit(1)

    change_password_dialog()
    wallet.save()
Exemplo n.º 2
0
def importMM(wordlist):
    print("for testing purposes only!")
    sk = MiniNero.recoverSK(wordlist)
    print("vk", vk)
    print("pvk", MiniNero.publicFromSecret(vk))
    key = mnemonic.mn_encode(sk)
    cks = MiniNero.electrumChecksum(key)
    print(key + " "+cks)
Exemplo n.º 3
0
def importMM(wordlist):
    print("for testing purposes only!")
    sk = MiniNero.recoverSK(wordlist)
    print("vk", vk)
    print("pvk", MiniNero.publicFromSecret(vk))
    key = mnemonic.mn_encode(sk)
    cks = MiniNero.electrumChecksum(key)
    print(key + " "+cks)
Exemplo n.º 4
0
def seed_dialog():
    if wallet.use_encryption:
        password = droid.dialogGetPassword('Seed').result
        if not password: return
    else:
        password = None

    try:
        seed = wallet.pw_decode(wallet.seed, password)
    except:
        modal_dialog('error', 'incorrect password')
        return

    modal_dialog('Your seed is', seed)
    modal_dialog('Mnemonic code:', ' '.join(mnemonic.mn_encode(seed)))
Exemplo n.º 5
0
def seed_dialog():
    if wallet.use_encryption:
        password  = droid.dialogGetPassword('Seed').result
        if not password: return
    else:
        password = None
    
    try:
        seed = wallet.pw_decode( wallet.seed, password)
    except:
        modal_dialog('error','incorrect password')
        return

    modal_dialog('Your seed is',seed)
    modal_dialog('Mnemonic code:', ' '.join(mnemonic.mn_encode(seed)) )
Exemplo n.º 6
0
def deterministicVK():
    while True:
        print('.'),
        tmp = MiniNero.intToHex(rand.getrandbits(64 *
                                                 8))  # 8 bits to a byte ...
        sk = MiniNero.sc_reduce_key(MiniNero.cn_fast_hash(tmp))

        #s = "3c817618dcbfed122a64e592bb441d73300da9123686224a84e0eab1f075117e"; for testing
        #sk = MiniNero.sc_reduce_key(s)
        vk = MiniNero.getViewMM(sk)  #note this is the sc_reduced version..
        worked = 1
        try:
            MiniNero.toPoint(vk)
        except:
            worked = 0
            print("bad vk")
        if vk == MiniNero.sc_reduce_key(
                vk) and worked == 1:  #already reduced + vk on curve
            break

    print("found keys")
    print("secret spend key:", sk)
    print("secret view key:", vk)
    vk2 = MiniNero.cn_fast_hash(MiniNero.scalarmultKey(vk, 2))
    print("secret view key2:", vk2)
    vk3 = MiniNero.cn_fast_hash(MiniNero.scalarmultKey(vk, 3))
    print("secret view key3:", vk3)

    pk = MiniNero.publicFromSecret(sk)
    print("public spend key:", pk)
    pvk = MiniNero.publicFromSecret(vk)
    print("public view key:", pvk)
    pvk2 = MiniNero.publicFromSecret(vk2)
    print("public view key2:", pvk2)
    pvk3 = MiniNero.publicFromSecret(vk3)
    print("public view key3:", pvk3)

    addr = MiniNero.getAddrMM(sk)
    print("in future this will get all addresses")
    print("receiving address", addr)
    wl = mnemonic.mn_encode(s)
    cks = MiniNero.electrumChecksum(wl)
    print(cks)
    print("mnemonic:", wl + " " + cks)
Exemplo n.º 7
0
def moneroProofOfFile(fi):
    s = cnHashOfFile(fi)
    #s = MiniNero.sc_reduce_key(s) #if you are testing, insert
    #an s below this line

    sk = MiniNero.sc_reduce_key(s)
    print("secret spend key:", sk)
    vk = MiniNero.getView(sk)
    print("secret view key:", vk)
    pk = MiniNero.publicFromSecret(sk)
    print("public spend key:", pk)
    pvk = MiniNero.publicFromSecret(vk)
    print("public view key:", pvk)
    wl = mnemonic.mn_encode(s)
    cks = MiniNero.electrumChecksum(wl)
    print(cks)
    print("mnemonic:", wl + " " + cks)

    return MiniNero.encode_addr(MiniNero.netVersion(), pk, pvk)
Exemplo n.º 8
0
def deterministicVK():
    while True:
        print("."),
        tmp = MiniNero.intToHex(rand.getrandbits(64 * 8))  # 8 bits to a byte ...
        sk = MiniNero.sc_reduce_key(MiniNero.cn_fast_hash(tmp))

        # s = "3c817618dcbfed122a64e592bb441d73300da9123686224a84e0eab1f075117e"; for testing
        # sk = MiniNero.sc_reduce_key(s)
        vk = MiniNero.getViewMM(sk)  # note this is the sc_reduced version..
        worked = 1
        try:
            MiniNero.toPoint(vk)
        except:
            worked = 0
            print("bad vk")
        if vk == MiniNero.sc_reduce_key(vk) and worked == 1:  # already reduced + vk on curve
            break

    print("found keys")
    print("secret spend key:", sk)
    print("secret view key:", vk)
    vk2 = MiniNero.cn_fast_hash(MiniNero.scalarmultKey(vk, 2))
    print("secret view key2:", vk2)
    vk3 = MiniNero.cn_fast_hash(MiniNero.scalarmultKey(vk, 3))
    print("secret view key3:", vk3)

    pk = MiniNero.publicFromSecret(sk)
    print("public spend key:", pk)
    pvk = MiniNero.publicFromSecret(vk)
    print("public view key:", pvk)
    pvk2 = MiniNero.publicFromSecret(vk2)
    print("public view key2:", pvk2)
    pvk3 = MiniNero.publicFromSecret(vk3)
    print("public view key3:", pvk3)

    addr = MiniNero.getAddrMM(sk)
    print("in future this will get all addresses")
    print("receiving address", addr)
    wl = mnemonic.mn_encode(s)
    cks = MiniNero.electrumChecksum(wl)
    print(cks)
    print("mnemonic:", wl + " " + cks)
Exemplo n.º 9
0
def keysBoth():
    print("This is for private testing purposes only, use at your own risk!")
    print(
        "this function will generate an address that is compatible both with the main client and with MyByteRub"
    )
    print("shen noether- mrl")
    print(" ")
    while True:
        print('.'),
        sk = skGen()

        #s = "3c817618dcbfed122a64e592bb441d73300da9123686224a84e0eab1f075117e"; for testing

        vk = MiniNero.getViewMM(sk)  #note this is the sc_reduced version..
        worked = 1
        #uncomment below lines to make viewkey a point..
        try:
            MiniNero.toPoint(vk)
        except:
            worked = 0
            print("bad vk")

        if vk == MiniNero.sc_reduce_key(vk) and worked == 1:  #already reduced
            break
    print("found key")
    print("secret spend key:", sk)
    print("secret view key:", vk)
    pk = MiniNero.publicFromSecret(sk)
    print("public spend key:", pk)
    pvk = MiniNero.publicFromSecret(vk)
    print("public view key:", pvk)
    addr = MiniNero.getAddrMM(sk)
    print("receiving address", addr)
    wl = mnemonic.mn_encode(sk)
    cks = MiniNero.electrumChecksum(wl)
    print(cks)
    print("mnemonic:", wl + " " + cks)
    return sk, vk, pk, pvk, addr, wl, cks
Exemplo n.º 10
0
def keysBoth():
    print("This is for private testing purposes only, use at your own risk!")
    print("this function will generate an address that is compatible both with the main client and with MyMonero")
    print("shen noether- mrl")
    print(" ")
    while True:
        print('.'),
        sk = skGen()

        #s = "3c817618dcbfed122a64e592bb441d73300da9123686224a84e0eab1f075117e"; for testing

        vk = MiniNero.getViewMM(sk) #note this is the sc_reduced version..
        worked = 1
        #uncomment below lines to make viewkey a point..
        try:
            MiniNero.toPoint(vk)
        except:
            worked =0
            print("bad vk")

        if vk == MiniNero.sc_reduce_key(vk) and worked == 1: #already reduced
            break
    print("found key")
    print("secret spend key:", sk)
    print("secret view key:", vk)
    pk = MiniNero.publicFromSecret(sk)
    print("public spend key:", pk)
    pvk = MiniNero.publicFromSecret(vk)
    print("public view key:", pvk)
    addr =  MiniNero.getAddrMM(sk)
    print("receiving address", addr)
    wl = mnemonic.mn_encode(sk)
    cks = MiniNero.electrumChecksum(wl)
    print(cks)
    print("mnemonic:", wl + " " + cks)
    return sk, vk, pk, pvk, addr, wl, cks
Exemplo n.º 11
0
 def getseed(self):
     import mnemonic
     seed = self.wallet.decode_seed(self.password)
     return { "hex":seed, "mnemonic": ' '.join(mnemonic.mn_encode(seed)) }
Exemplo n.º 12
0
print "len(wordList):", len(wordList)

########################################################################################

print "Enter some random characters and press <enter>."
rawEntropy = raw_input()

print "Enter a title for this paper wallet and press <enter>."
title = raw_input().strip()
print "working...\n\n"

# generate passPhrase
entropy = rawEntropy
entropy += os.urandom(32) + str(random.randrange(2**256)) + str(int(time.time())**7)  # from Vitalik
entropyHash = hashlib.sha256(entropy).hexdigest()
words = mnemonic.mn_encode(entropyHash)
if len(words) != WORDS:
    raise Exception("Encode error.")
passPhrase = " ".join(words)

seed = mnemonic.mn_decode(passPhrase.split(" "))
rootPrivKey = pbt.electrum_privkey(seed, 0, 0)
rootAddress = pbt.electrum_address(seed, 0, 0)
mpk = pbt.electrum_mpk(seed)

# output
parts = [" ".join(words[:WORDS / 2]), " ".join(words[WORDS / 2:])]

for i, p in enumerate(parts):
    part = " ".join(p)
    for c in range(2):
Exemplo n.º 13
0
 def get_mnemonic(self, password):
     import mnemonic
     s = self.get_seed(password)
     return ' '.join(mnemonic.mn_encode(s))
Exemplo n.º 14
0
# Wyager's simple BIP32 public generation script
# https://github.com/wyager/Bitcoin-HD-wallet-generator

import bitcoin
import mnemonic
import os
import hashlib
import hmac

hmac_hash = lambda key, data: hmac.new(key, data, hashlib.sha512).digest()

print "Generating seed..."
hex_seed_256 = os.urandom(32).encode('hex')
print "Initial seed (raw data): " + hex_seed_256
print "Electrum mnemonic:" + " ".join(mnemonic.mn_encode(hex_seed_256))

hex_seed_512 = hmac_hash("Bitcoin mnemonic", hex_seed_256).encode('hex')
print "derived seed (raw): " + hex_seed_512
# Master key, master chain, Master pubkey, Master pubkey (compressed format)
k, c, K, Kcomp = bitcoin.bip32_init(hex_seed_512)


def generate(path, key, chain):
    """
	generate(path, key, chain) -> key2, chain2
	path is a list of tuples like [(index1, is_hardened),(index2, is_hardened)]
	key is the root private key
	chain is the root chain
	key2 and chain2 are the results of following this BIP32 derivation path
	"""
    (index, is_hardened), path2 = path[0], path[1:]
Exemplo n.º 15
0
 def getseed(self):
     import mnemonic
     seed = self.wallet.decode_seed(self.password)
     return {"hex": seed, "mnemonic": ' '.join(mnemonic.mn_encode(seed))}
Exemplo n.º 16
0
def show_seed_dialog(wallet, password, parent):
    if not wallet.seed:
        show_message("No seed")
        return
    try:
        seed = wallet.pw_decode( wallet.seed, password)
    except:
        show_message("Incorrect password")
        return
    dialog = gtk.MessageDialog(
        parent = parent,
        flags = gtk.DIALOG_MODAL, 
        buttons = gtk.BUTTONS_OK, 
        message_format = "Your wallet generation seed is:\n\n" + seed \
            + "\n\nPlease keep it in a safe place; if you lose it, you will not be able to restore your wallet.\n\n" \
            + "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:\n\n\"" + ' '.join(mnemonic.mn_encode(seed)) + "\"" )
    dialog.set_title("Seed")
    dialog.show()
    dialog.run()
    dialog.destroy()
Exemplo n.º 17
0
def show_seed_dialog(wallet, password, parent):
    if not wallet.seed:
        show_message("No seed")
        return
    try:
        seed = wallet.pw_decode( wallet.seed, password)
    except:
        show_message("Incorrect password")
        return
    dialog = gtk.MessageDialog(
        parent = parent,
        flags = gtk.DIALOG_MODAL, 
        buttons = gtk.BUTTONS_OK, 
        message_format = "Your wallet generation seed is:\n\n" + seed \
            + "\n\nPlease keep it in a safe place; if you lose it, you will not be able to restore your wallet.\n\n" \
            + "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:\n\n\"" + ' '.join(mnemonic.mn_encode(seed)) + "\"" )
    dialog.set_title("Seed")
    dialog.show()
    dialog.run()
    dialog.destroy()
def decrypt_electrum_seed(wallet_file, get_password_fn):
    """decrypt the seed in an Electrum wallet file

    :param wallet_file: an open Electrum 1.x or 2.x file
    :type wallet_file: file
    :param get_password_fn: a callback returning a password that's called iff one is required
    :type get_password_fn: function
    :return: the (typically hex-encoded) decrypted key and the mnemonic
    :rtype: (str, str)
    """

    with wallet_file:
        wallet_file_text = wallet_file.read()
    try:
        # Electrum 1.x
        wallet = ast.literal_eval(wallet_file_text)
    except Exception:
        # Electrum 2.x
        wallet = json.loads(wallet_file_text)
    del wallet_file_text

    seed_version = wallet.get('seed_version')
    if seed_version is None:
        warn('seed_version not found')
    elif seed_version not in (4, 11):
        warn('unexpected seed_version: ' + str(seed_version))

    wallet_type = wallet.get('wallet_type')
    if not wallet_type:
        warn('wallet_type not found')
    elif wallet_type not in ('old', 'standard'):
        warn('untested wallet_type: ' + wallet['wallet_type'])

    if wallet.get('use_encryption'):

        b64_encrypted_data = wallet['seed']

        # Carefully check base64 encoding and truncate it at the first unrecoverable character group
        b64_chars_set = set(
            'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/')
        assert len(b64_chars_set) == 64
        for i in xrange(0, len(b64_encrypted_data),
                        4):  # iterate over 4-character long groups
            char_group_len = len(b64_encrypted_data[i:])

            if char_group_len == 1:
                warn(
                    'ignoring unrecoverable base64 suffix {!r} in encrypted seed'
                    .format(b64_encrypted_data[i:]))
                b64_encrypted_data = b64_encrypted_data[:i]
                break

            elif 2 <= char_group_len <= 3:
                warn(
                    'adding padding to incomplete base64 suffix {!r} in encrypted seed'
                    .format(b64_encrypted_data[i:]))
                b64_encrypted_data += '=' * (4 - char_group_len)

            for j, c in enumerate(b64_encrypted_data[i:i + 4]
                                  ):  # check the 4 characters in this group
                if c not in b64_chars_set:
                    if j > 1 and c == '=':  # padding characters are allowed in positions 2 and 3 of a group,
                        b64_chars_set = '='  # and once one is found all the rest must be padding
                    else:
                        warn(
                            'found invalid base64 char {!r} at position {} in encrypted seed; ignoring the rest'
                            .format(c, i + j))
                        if j <= 1:  # character groups of length 0 or 1 are invalid: the entire group is truncated
                            b64_encrypted_data = b64_encrypted_data[:i]
                        else:  # else truncate and replace invalid characters with padding
                            b64_encrypted_data = b64_encrypted_data[:i + j]
                            b64_encrypted_data += '=' * (4 - j)
                        break

        # Decode base64 and then extract the IV and encrypted_seed
        iv_and_encrypted_seed = b64_encrypted_data.decode('base64')
        if seed_version == 4 and len(iv_and_encrypted_seed) != 64:
            warn('encrypted seed plus iv is {} bytes long; expected 64'.format(
                len(iv_and_encrypted_seed)))
        iv = iv_and_encrypted_seed[:16]
        encrypted_seed = iv_and_encrypted_seed[16:]
        if len(encrypted_seed) < 16:
            warn(
                'length of encrypted seed, {}, is less than one AES block (16), giving up'
                .format(len(encrypted_seed)))
            return None, None
        encrypted_seed_mod_blocksize = len(encrypted_seed) % 16
        if encrypted_seed_mod_blocksize != 0:
            warn(
                'length of encrypted seed, {}, is not a multiple of the AES block size (16); truncating {} bytes'
                .format(len(encrypted_seed), encrypted_seed_mod_blocksize))
            encrypted_seed = encrypted_seed[:-encrypted_seed_mod_blocksize]

        password = get_password_fn()  # get a password via the callback
        if password is None:
            return None, None
        if unicodedata.normalize('NFC', password) != unicodedata.normalize(
                'NFD', password):
            if password == unicodedata.normalize('NFC', password):
                the_default = 'NFC'
            elif password == unicodedata.normalize('NFD', password):
                the_default = 'NFD'
            else:
                the_default = 'a combination'
            warn(
                'password has different NFC and NFD encodings; only trying the default ({})'
                .format(the_default))
        password = password.encode('UTF-8')

        # Derive the encryption key
        key = hashlib.sha256(hashlib.sha256(password).digest()).digest()

        # Decrypt the seed
        key_expander = aespython.key_expander.KeyExpander(256)
        block_cipher = aespython.aes_cipher.AESCipher(
            key_expander.expand(map(ord, key)))
        stream_cipher = aespython.cbc_mode.CBCMode(block_cipher, 16)
        stream_cipher.set_iv(bytearray(iv))
        seed = bytearray()
        for i in xrange(0, len(encrypted_seed), 16):
            seed.extend(
                stream_cipher.decrypt_block(map(ord,
                                                encrypted_seed[i:i + 16])))
        padding_len = seed[-1]
        # check for PKCS7 padding
        if not (1 <= padding_len <= 16
                and seed.endswith(chr(padding_len) * padding_len)):
            warn(
                'not removing invalid PKCS7 padding (the password was probably entered wrong)'
            )
            seed = str(seed)
        else:
            seed = str(seed[:-padding_len])

    else:
        seed = wallet['seed']

    # For Electrum 2.x, there's no additional hex encoding; we're done
    if seed_version == 11:
        try:
            return None, seed.decode('UTF-8')
        except UnicodeDecodeError:
            return None, seed

    if len(seed) != 32:
        warn('decrypted seed is {} characters long, expected 32'.format(
            len(seed)))

    # For Electrum 1.x, carefully check hex encoding and truncate it at the first non-hex digit
    hex_seed = seed
    for i, h in enumerate(hex_seed):
        if not ('0' <= h <= '9' or 'a' <= h <= 'f'):
            if 'A' <= h <= 'F':
                warn('found unexpected capital hex digit')
            else:
                warn(
                    'found invalid hex digit {!r} at position {} in decrypted seed; ignoring the rest'
                    .format(h, i))
                hex_seed = hex_seed[:i]
                break

    # Count number of hex digits for informational purposes
    if len(hex_seed) != len(seed):
        hex_digit_count = 0
        for h in seed:
            if '0' <= h <= '9' or 'a' <= h <= 'f':
                hex_digit_count += 1
        warn(
            'info: {} out of {} characters in decrypted seed are lowercase hex digits'
            .format(hex_digit_count, len(seed)))

    if len(hex_seed) < 8:
        warn('length of valid hex-encoded digits is less than 8, giving up')
        return seed, None
    else:
        if len(hex_seed) % 8 != 0:
            warn(
                'length of hex-encoded digits is not divisible by 8, some digits will not be included in the mnemonic'
            )
        return seed, ' '.join(mnemonic.mn_encode(hex_seed))
Exemplo n.º 19
0
def recover():

    droid.dialogCreateAlert(
        "Wallet not found",
        "Do you want to create a new wallet, or restore an existing one?")
    droid.dialogSetPositiveButtonText('Create')
    droid.dialogSetNeutralButtonText('Restore')
    droid.dialogSetNegativeButtonText('Cancel')
    droid.dialogShow()
    response = droid.dialogGetResponse().result
    droid.dialogDismiss()
    if response.get('which') == 'negative':
        exit(1)

    is_recovery = response.get('which') == 'neutral'

    if not is_recovery:
        wallet.new_seed(None)
    else:
        if modal_question("Input method", None, 'QR Code', 'mnemonic'):
            code = droid.scanBarcode()
            r = code.result
            if r:
                seed = r['extras']['SCAN_RESULT']
            else:
                exit(1)
        else:
            m = modal_input('Mnemonic', 'please enter your code')
            try:
                seed = mnemonic.mn_decode(m.split(' '))
            except:
                modal_dialog('error: could not decode this seed')
                exit(1)

        wallet.seed = str(seed)

    modal_dialog('Your seed is:', wallet.seed)
    modal_dialog('Mnemonic code:', ' '.join(mnemonic.mn_encode(wallet.seed)))

    msg = "recovering wallet..." if is_recovery else "creating wallet..."
    droid.dialogCreateSpinnerProgress("Electrum", msg)
    droid.dialogShow()

    wallet.init_mpk(wallet.seed)
    WalletSynchronizer(wallet, True).start()
    wallet.update()

    droid.dialogDismiss()
    droid.vibrate()

    if is_recovery:
        if wallet.is_found():
            wallet.update_tx_history()
            wallet.fill_addressbook()
            modal_dialog("recovery successful")
        else:
            if not modal_question("no transactions found for this seed",
                                  "do you want to keep this wallet?"):
                exit(1)

    change_password_dialog()
    wallet.save()
Exemplo n.º 20
0
########################################################################################

print "Enter some random characters and press <enter>."
rawEntropy = raw_input()

print "Enter a title for this paper wallet and press <enter>."
title = raw_input().strip()
print "working...\n\n"

# generate passPhrase
entropy = rawEntropy
entropy += os.urandom(32) + str(random.randrange(2**256)) + str(
    int(time.time())**7)  # from Vitalik
entropyHash = hashlib.sha256(entropy).hexdigest()
words = mnemonic.mn_encode(entropyHash)
if len(words) != WORDS:
    raise Exception("Encode error.")
passPhrase = " ".join(words)

seed = mnemonic.mn_decode(passPhrase.split(" "))
rootPrivKey = pbt.electrum_privkey(seed, 0, 0)
rootAddress = pbt.electrum_address(seed, 0, 0)
mpk = pbt.electrum_mpk(seed)

# output
parts = [" ".join(words[:WORDS / 2]), " ".join(words[WORDS / 2:])]

for i, p in enumerate(parts):
    part = " ".join(p)
    for c in range(2):
def decrypt_electrum_seed(wallet_file, get_password_fn):
    """decrypt the seed in an Electrum wallet file

    :param wallet_file: an open Electrum 1.x or 2.x file
    :type wallet_file: file
    :param get_password_fn: a callback returning a password that's called iff one is required
    :type get_password_fn: function
    :return: the (typically hex-encoded) decrypted key and the mnemonic
    :rtype: (str, str)
    """

    with wallet_file:
        wallet_file_text = wallet_file.read()
    try:
        # Electrum 1.x
        wallet = ast.literal_eval(wallet_file_text)
    except Exception:
        # Electrum 2.x
        wallet = json.loads(wallet_file_text)
    del wallet_file_text

    seed_version = wallet.get('seed_version')
    if seed_version is None:
        warn('seed_version not found')
    elif seed_version not in (4, 11):
        warn('unexpected seed_version: ' + str(seed_version))

    wallet_type = wallet.get('wallet_type')
    if not wallet_type:
        warn('wallet_type not found')
    elif wallet_type not in ('old', 'standard'):
        warn('untested wallet_type: ' + wallet['wallet_type'])

    if wallet.get('use_encryption'):

        b64_encrypted_data = wallet['seed']

        # Carefully check base64 encoding and truncate it at the first unrecoverable character group
        b64_chars_set = set('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/')
        assert len(b64_chars_set) == 64
        for i in xrange(0, len(b64_encrypted_data), 4):  # iterate over 4-character long groups
            char_group_len = len(b64_encrypted_data[i:])

            if char_group_len == 1:
                warn('ignoring unrecoverable base64 suffix {!r} in encrypted seed'.format(b64_encrypted_data[i:]))
                b64_encrypted_data = b64_encrypted_data[:i]
                break

            elif 2 <= char_group_len <= 3:
                warn('adding padding to incomplete base64 suffix {!r} in encrypted seed'.format(b64_encrypted_data[i:]))
                b64_encrypted_data += '=' * (4 - char_group_len)

            for j,c in enumerate(b64_encrypted_data[i:i+4]):  # check the 4 characters in this group
                if c not in b64_chars_set:
                    if j > 1 and c == '=':   # padding characters are allowed in positions 2 and 3 of a group,
                        b64_chars_set = '='  # and once one is found all the rest must be padding
                    else:
                        warn('found invalid base64 char {!r} at position {} in encrypted seed; ignoring the rest'.format(c, i+j))
                        if j <= 1:  # character groups of length 0 or 1 are invalid: the entire group is truncated
                            b64_encrypted_data = b64_encrypted_data[:i]
                        else:       # else truncate and replace invalid characters with padding
                            b64_encrypted_data = b64_encrypted_data[:i+j]
                            b64_encrypted_data += '=' * (4-j)
                        break

        # Decode base64 and then extract the IV and encrypted_seed
        iv_and_encrypted_seed = b64_encrypted_data.decode('base64')
        if seed_version == 4 and len(iv_and_encrypted_seed) != 64:
            warn('encrypted seed plus iv is {} bytes long; expected 64'.format(len(iv_and_encrypted_seed)))
        iv             = iv_and_encrypted_seed[:16]
        encrypted_seed = iv_and_encrypted_seed[16:]
        if len(encrypted_seed) < 16:
            warn('length of encrypted seed, {}, is less than one AES block (16), giving up'.format(len(encrypted_seed)))
            return None, None
        encrypted_seed_mod_blocksize = len(encrypted_seed) % 16
        if encrypted_seed_mod_blocksize != 0:
            warn('length of encrypted seed, {}, is not a multiple of the AES block size (16); truncating {} bytes'
                 .format(len(encrypted_seed), encrypted_seed_mod_blocksize))
            encrypted_seed = encrypted_seed[:-encrypted_seed_mod_blocksize]

        password = get_password_fn()  # get a password via the callback
        if password is None:
            return None, None
        if unicodedata.normalize('NFC', password) != unicodedata.normalize('NFD', password):
            if password == unicodedata.normalize('NFC', password):
                the_default = 'NFC'
            elif password == unicodedata.normalize('NFD', password):
                the_default = 'NFD'
            else:
                the_default = 'a combination'
            warn('password has different NFC and NFD encodings; only trying the default ({})'.format(the_default))
        password = password.encode('UTF-8')

        # Derive the encryption key
        key = hashlib.sha256( hashlib.sha256( password ).digest() ).digest()

        # Decrypt the seed
        key_expander  = aespython.key_expander.KeyExpander(256)
        block_cipher  = aespython.aes_cipher.AESCipher( key_expander.expand(map(ord, key)) )
        stream_cipher = aespython.cbc_mode.CBCMode(block_cipher, 16)
        stream_cipher.set_iv(bytearray(iv))
        seed = bytearray()
        for i in xrange(0, len(encrypted_seed), 16):
            seed.extend( stream_cipher.decrypt_block(map(ord, encrypted_seed[i:i+16])) )
        padding_len = seed[-1]
        # check for PKCS7 padding
        if not (1 <= padding_len <= 16 and seed.endswith(chr(padding_len) * padding_len)):
            warn('not removing invalid PKCS7 padding (the password was probably entered wrong)')
            seed = str(seed)
        else:
            seed = str(seed[:-padding_len])

    else:
        seed = wallet['seed']

    # For Electrum 2.x, there's no additional hex encoding; we're done
    if seed_version == 11:
        try:
            return None, seed.decode('UTF-8')
        except UnicodeDecodeError:
            return None, seed

    if len(seed) != 32:
        warn('decrypted seed is {} characters long, expected 32'.format(len(seed)))

    # For Electrum 1.x, carefully check hex encoding and truncate it at the first non-hex digit
    hex_seed = seed
    for i,h in enumerate(hex_seed):
        if not ('0' <= h <= '9' or 'a' <= h <= 'f'):
            if 'A' <= h <= 'F':
                warn('found unexpected capital hex digit')
            else:
                warn('found invalid hex digit {!r} at position {} in decrypted seed; ignoring the rest'.format(h, i))
                hex_seed = hex_seed[:i]
                break

    # Count number of hex digits for informational purposes
    if len(hex_seed) != len(seed):
        hex_digit_count = 0
        for h in seed:
            if '0' <= h <= '9' or 'a' <= h <= 'f':
                hex_digit_count += 1
        warn('info: {} out of {} characters in decrypted seed are lowercase hex digits'
             .format(hex_digit_count, len(seed)))

    if len(hex_seed) < 8:
        warn('length of valid hex-encoded digits is less than 8, giving up')
        return seed, None
    else:
        if len(hex_seed) % 8 != 0:
            warn('length of hex-encoded digits is not divisible by 8, some digits will not be included in the mnemonic')
        return seed, ' '.join(mnemonic.mn_encode(hex_seed))