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()
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)
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)))
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)) )
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)
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)
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)
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
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
def getseed(self): import mnemonic seed = self.wallet.decode_seed(self.password) return { "hex":seed, "mnemonic": ' '.join(mnemonic.mn_encode(seed)) }
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):
def get_mnemonic(self, password): import mnemonic s = self.get_seed(password) return ' '.join(mnemonic.mn_encode(s))
# 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:]
def getseed(self): import mnemonic seed = self.wallet.decode_seed(self.password) return {"hex": seed, "mnemonic": ' '.join(mnemonic.mn_encode(seed))}
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))
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()
######################################################################################## 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))