Advertisement
HCP

debug2_find_unspent_multibitHD_txes.py

HCP
Jul 27th, 2017
547
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 9.90 KB | None | 0 0
  1. #!/usr/bin/python
  2.  
  3. # find_unspent_multibitHD_txes.py
  4. # Find unspent TXes within MultiBit HD wallets
  5. # Copyright (C) 2017, HCP
  6. # All rights reserved.
  7. #
  8. # Based on decrypt_bitcoinj_seed.pyw
  9. # Copyright (C) 2014, 2016 Christopher Gurnee
  10. #
  11. # Redistribution and use in source and binary forms, with or without
  12. # modification, are permitted provided that the following conditions
  13. # are met:
  14. #
  15. # 1. Redistributions of source code must retain the above copyright
  16. # notice, this list of conditions and the following disclaimer.
  17. #
  18. # 2. Redistributions in binary form must reproduce the above copyright
  19. # notice, this list of conditions and the following disclaimer in the
  20. # documentation and/or other materials provided with the distribution.
  21. #
  22. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  23. # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  24. # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  25. # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  26. # HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  27. # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  28. # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  29. # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  30. # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  31. # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  32. #  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  33.  
  34. # If you find this program helpful, please consider a small
  35. # donation to the developer at the following Bitcoin address:
  36. #
  37. #           1NyVDDmhZPcyKhyrkiUFZbqPPuiYxwTujb
  38. #
  39. #                      Thank You!
  40.  
  41. from __future__ import print_function
  42.  
  43. __version__ =  '0.4.0'
  44.  
  45. import hashlib, sys, os, getpass
  46. import aespython.key_expander, aespython.aes_cipher, aespython.cbc_mode
  47. import wallet_pb2, binascii, bitcoin
  48.  
  49. sha256 = hashlib.sha256
  50. md5    = hashlib.md5
  51.  
  52.  
  53. key_expander = aespython.key_expander.KeyExpander(256)
  54.  
  55. def aes256_cbc_decrypt(ciphertext, key, iv):
  56.     """decrypts the ciphertext using AES256 in CBC mode
  57.  
  58.    :param ciphertext: the encrypted ciphertext
  59.    :type ciphertext: str
  60.    :param key: the 256-bit key
  61.    :type key: str
  62.    :param iv: the 128-bit initialization vector
  63.    :type iv: str
  64.    :return: the decrypted ciphertext, or raises a ValueError if the key was wrong
  65.    :rtype: str
  66.    """
  67.     block_cipher  = aespython.aes_cipher.AESCipher( key_expander.expand(map(ord, key)) )
  68.     stream_cipher = aespython.cbc_mode.CBCMode(block_cipher, 16)
  69.     stream_cipher.set_iv(bytearray(iv))
  70.     plaintext = bytearray()
  71.     for i in xrange(0, len(ciphertext), 16):
  72.         plaintext.extend( stream_cipher.decrypt_block(map(ord, ciphertext[i:i+16])) )
  73.     padding_len = plaintext[-1]
  74.     # check for PKCS7 padding
  75.     if not (1 <= padding_len <= 16 and plaintext.endswith(chr(padding_len) * padding_len)):
  76.         raise ValueError('incorrect password')
  77.     return str(plaintext[:-padding_len])
  78.  
  79.  
  80. multibit_hd_password = None
  81. def load_wallet(wallet_file, get_password_fn):
  82.     """load and if necessary decrypt a bitcoinj wallet file
  83.  
  84.    :param wallet_file: an open bitcoinj wallet file
  85.    :type wallet_file: file
  86.    :param get_password_fn: a callback returning a password that's called iff one is required
  87.    :type get_password_fn: function
  88.    :return: the Wallet protobuf message or None if no password was entered when required
  89.    :rtype: wallet_pb2.Wallet
  90.    """
  91.  
  92.     wallet_file.seek(0)
  93.     magic_bytes = wallet_file.read(12)
  94.    
  95.     wallet_file.seek(0, os.SEEK_END)
  96.     wallet_size = wallet_file.tell()
  97.     wallet_file.seek(0)
  98.  
  99.     if magic_bytes[2:6] != b"org." and wallet_size % 16 == 0:
  100.         import pylibscrypt
  101.         takes_long = not pylibscrypt._done  # if a binary library wasn't found, this'll take a while
  102.  
  103.         ciphertext = wallet_file.read()
  104.         assert len(ciphertext) % 16 == 0
  105.  
  106.         password = get_password_fn(takes_long)
  107.         if not password:
  108.             return None
  109.  
  110.         # Derive the encryption key
  111.         salt = '\x35\x51\x03\x80\x75\xa3\xb0\xc5'
  112.         key  = pylibscrypt.scrypt(password.encode('utf_16_be'), salt, olen=32)
  113.  
  114.         # Decrypt the wallet ( v0.5.0+ )
  115.         try:
  116.             plaintext = aes256_cbc_decrypt(ciphertext[16:], key, ciphertext[:16])
  117.             if plaintext[2:6] != b"org.":
  118.                 raise ValueError('incorrect password')
  119.         except ValueError as e:
  120.             if e.args[0] == 'incorrect password':
  121.  
  122.                 # Decrypt the wallet ( < v0.5.0 )
  123.                 iv = '\xa3\x44\x39\x1f\x53\x83\x11\xb3\x29\x54\x86\x16\xc4\x89\x72\x3e'
  124.                 plaintext = aes256_cbc_decrypt(ciphertext, key, iv)
  125.  
  126.         global multibit_hd_password
  127.         multibit_hd_password = password
  128.  
  129.     # Else it's not whole-file encrypted
  130.     else:
  131.         password  = None
  132.         plaintext = wallet_file.read()
  133.  
  134.     # Parse the wallet protobuf
  135.     pb_wallet = wallet_pb2.Wallet()
  136.     try:
  137.         pb_wallet.ParseFromString(plaintext)
  138.     except Exception as e:
  139.         msg = 'not a wallet file: ' + str(e)
  140.         if password:
  141.             msg = "incorrect password (or " + msg + ")"
  142.         raise ValueError(msg)
  143.    
  144.     f = open('parsed_wallet.txt','w')
  145.     f.write(pb_wallet.__str__())
  146.     f.close()
  147.    
  148.     foundAddr = []
  149.    
  150.     for trans in pb_wallet.transaction:
  151.       if trans.pool == 4:
  152.         print("--------------------------------------------------------------------------------")
  153.         print("TXID: " + binascii.hexlify(trans.hash))
  154.         for out in trans.transaction_output:
  155.           print("")
  156.           script_hex = binascii.hexlify(out.script_bytes).decode()
  157.           print("script: " + script_hex)
  158.           faddr = bitcoin.script_to_address( script_hex )
  159.           print("Addr: " + faddr)
  160.           foundAddr.append(faddr)
  161.           print("Amt: " + str(out.value * 0.00000001) + " BTC")
  162.         print("")
  163.         print("--------------------------------------------------------------------------------")
  164.    
  165.     seed = None
  166.    
  167.     sys.stdout.write('Finding Seed....')
  168.    
  169.     salt = pb_wallet.encryption_parameters.salt
  170.     dkey = pylibscrypt.scrypt(password.encode('utf_16_be'), salt, olen=32)
  171.    
  172.     for wkey in pb_wallet.key:
  173.       if wkey.type == 3:
  174.         seed = aes256_cbc_decrypt(wkey.encrypted_deterministic_seed.encrypted_private_key, dkey, wkey.encrypted_deterministic_seed.initialisation_vector)
  175.         break
  176.        
  177.     if not seed:
  178.       print("No DETERMINISTIC_MNEMONIC seed found!")
  179.       return None
  180.     else:
  181.       print("Done!")
  182.     xprv = bitcoin.bip32_master_key(seed)
  183.    
  184.     xprvReceive = bitcoin.bip32_ckd(bitcoin.bip32_ckd(xprv, 2**31),0) #m/0'/0
  185.     xprvChange = bitcoin.bip32_ckd(bitcoin.bip32_ckd(xprv, 2**31),1) #m/0'/1
  186.    
  187.     rcvAddr = []
  188.     chgAddr = []
  189.     rcvPrivKey = []
  190.     chgPrivKey = []
  191.    
  192.     sys.stdout.write("Generating Addresses/Keys.")
  193.     for x in range(0,1000):
  194.       if x % 10 == 0:
  195.         sys.stdout.write(".")
  196.       childprivReceive = bitcoin.bip32_ckd(xprvReceive, x)
  197.       childprivChange = bitcoin.bip32_ckd(xprvChange, x)
  198.      
  199.       pkeyReceive = bitcoin.bip32_extract_key(childprivReceive)
  200.       pkeyChange = bitcoin.bip32_extract_key(childprivChange)
  201.      
  202.       #addressReceive = privtoaddr(pkeyReceive)
  203.       #addressChange = privtoaddr(pkeyChange)
  204.       rcvAddr.append(bitcoin.privtoaddr(pkeyReceive))
  205.       chgAddr.append(bitcoin.privtoaddr(pkeyChange))
  206.      
  207.       rcvPrivKey.append(bitcoin.encode_privkey(pkeyReceive, 'wif_compressed'))
  208.       chgPrivKey.append(bitcoin.encode_privkey(pkeyChange, 'wif_compressed'))
  209.     print("Done!")  
  210.    
  211.     print("--------------------------------------------------------------------------------")
  212.    
  213.     for addy in foundAddr:
  214.       if addy in rcvAddr:
  215.         print("")
  216.         print("Found Address: " + addy)
  217.         print("PrivateKey: " + rcvPrivKey[rcvAddr.index(addy)])
  218.       elif addy in chgAddr:
  219.         print("")
  220.         print("Found Change Address: " + addy)
  221.         print("PrivateKey: " + chgPrivKey[chgAddr.index(addy)])
  222.       else:
  223.         #print("")
  224.         #print("Address not found: " + addy)
  225.    
  226.     print("")
  227.     print("--------------------------------------------------------------------------------")
  228.      
  229.     return pb_wallet
  230.  
  231.  
  232. if __name__ == '__main__':
  233.  
  234.     if len(sys.argv) != 2 or sys.argv[1].startswith('-'):
  235.         sys.exit('usage: find_unspent_multibitHD_txes.py multibitHD-wallet-file')
  236.  
  237.     wallet_file = open(sys.argv[1], 'rb')
  238.  
  239.     def get_password_factory(prompt):
  240.         def get_password(takes_long_arg_ignored):  # must return unicode
  241.             encoding = sys.stdin.encoding or 'ASCII'
  242.             if 'utf' not in encoding.lower():
  243.                 print('terminal does not support UTF; passwords with non-ASCII chars might not work', file=sys.stderr)
  244.             password = getpass.getpass(prompt + ' ')
  245.             if isinstance(password, str):
  246.                 password = password.decode(encoding)  # convert from terminal's encoding to unicode
  247.             return password
  248.         return get_password
  249.  
  250.     # These functions differ between command-line and GUI runs
  251.     get_password  = get_password_factory('This wallet file is encrypted, please enter its password:')
  252.     get_pin       = get_password_factory("This wallet's seed is encrypted with a PIN or password, please enter it:")
  253.     display_error = lambda msg: print(msg, file=sys.stderr)
  254.    
  255.     # Load (and possibly decrypt) the wallet, retrying on bad passwords
  256.     while True:
  257.         try:
  258.             wallet = load_wallet(wallet_file, get_password)
  259.             if not wallet:  # if no password was entered
  260.                 sys.exit('canceled')
  261.             break
  262.         except ValueError as e:
  263.             display_error(str(e))
  264.             if not e.args[0].startswith('incorrect password'):
  265.                 raise
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement