Repository: s4larie/walletool Branch: main Commit: c4b6a68d66f1 Files: 16 Total size: 18.1 KB Directory structure: gitextract_1pwe1tfb/ ├── .github/ │ └── workflows/ │ └── ruff.yml ├── .gitignore ├── README.md ├── check_bchain.py ├── check_dogechain.py ├── testdata/ │ ├── README.md │ ├── btc.txt │ └── ltc.txt ├── walletool/ │ ├── __init__.py │ ├── bc_data_stream.py │ ├── consts.py │ ├── init_env.py │ ├── utils.py │ ├── wallet_files.py │ └── wallet_items.py └── wt_extract_keys.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/ruff.yml ================================================ # https://beta.ruff.rs name: ruff on: push: branches: - master pull_request: branches: - master jobs: ruff: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - run: pip install --user ruff - run: ruff --format=github --ignore=E402,E722,F401 --line-length=171 --target-version=py37 . ================================================ FILE: .gitignore ================================================ *.dat *.py[cod] .coverage .idea htmlcov ================================================ FILE: README.md ================================================ walletool ~ a tool for reading wallet.dat files =============================================== A utility for extracting cryptocurrency wallet data from wallet.dat files. Installation ------------ * Install Python 3.x. * Install the `bsddb3` module (if you're on Windows, use Gohlke's site). Extracting private keys from Bitcoin-QT/Litecoin-QT wallets ----------------------------------------------------------- * Have your `wallet.dat` handy. * For Bitcoin, run `python wt_extract_keys.py -d wallet.dat -v 0` * For Litecoin, run `python wt_extract_keys.py -d wallet.dat -v 48` A list of addresses / private keys is printed. YMMV :) ================================================ FILE: check_bchain.py ================================================ from walletool import init_env import json import re import requests import argparse var_re = re.compile('var (.+?) = (.+?);') def main(): ap = argparse.ArgumentParser() ap.add_argument('file', help='address file; one address per line') ap.add_argument('--coin', required=True, help='e.g. XPM') ap.add_argument('--ignore-no-tx', action='store_true') args = ap.parse_args() for line in open(args.file): line = line.strip() r = requests.get('https://bchain.info/%s/addr/%s' % (args.coin, line)) if r.status_code == 404: continue vs = {} for m in var_re.finditer(r.text): key, value = m.groups() if key == 'startTime': continue try: value = json.loads(value.replace('\'', '"')) except json.JSONDecodeError: pass vs[key] = value if args.ignore_no_tx and vs['total_tx'] == 0: continue print(vs) if __name__ == '__main__': main() ================================================ FILE: check_dogechain.py ================================================ from walletool import init_env import argparse import json import requests import sys import time def main(): ap = argparse.ArgumentParser() ap.add_argument('file', help='address file; one address per line') ap.add_argument('--ignore-empty', action='store_true') args = ap.parse_args() for line in open(args.file): line = line.strip() while True: r = requests.get('https://dogechain.info/api/v1/address/balance/%s' % line) if r.status_code == 429: # Too Many Requests print('Throttled, hold on...', file=sys.stderr) time.sleep(60) continue break r.raise_for_status() r = r.json() if args.ignore_empty and float(r['balance']) == 0: continue r['addr'] = line print(r) time.sleep(0.5) if __name__ == '__main__': main() ================================================ FILE: testdata/README.md ================================================ This directory contains some sanity-checking data. Please don't send currency into the addresses herein... ================================================ FILE: testdata/btc.txt ================================================ 1MmsSmihQ9QbVzR6p8e6qvkaFrGzkBGMCJ L3Ha4x43eyLWJxgcLwTc6NsDV5VWypB7uz8YV48pBtSMDTreSsi6 ================================================ FILE: testdata/ltc.txt ================================================ LbCt2ihPfKWt1MuuyRQuDXLRLEnSSa1hh1 6vtmjqJjeAx3n2Yk27GoqNiFaK8Sk81oqecWEuKnEKZhX7c5rFw ================================================ FILE: walletool/__init__.py ================================================ # -- encoding: UTF-8 -- from . import init_env ================================================ FILE: walletool/bc_data_stream.py ================================================ # -- encoding: UTF-8 -- import sys assert sys.version_info[0] == 3 # TODO: Use six for 2/3 compat # From Joric's pywallet. import struct class SerializationError(Exception): pass class BCDataStream(object): def __init__(self, input): self.input = bytes(input) self.read_cursor = 0 def read_string(self): # Strings are encoded depending on length: # 0 to 252 : 1-byte-length followed by bytes (if any) # 253 to 65,535 : byte'253' 2-byte-length followed by bytes # 65,536 to 4,294,967,295 : byte '254' 4-byte-length followed by bytes # ... and the Bitcoin client is coded to understand: # greater than 4,294,967,295 : byte '255' 8-byte-length followed by bytes of string # ... but I don't think it actually handles any strings that big. try: length = self.read_compact_size() except IndexError: raise SerializationError("attempt to read past end of buffer") return self.read_bytes(length) def read_bytes(self, length): try: result = self.input[self.read_cursor:self.read_cursor + length] self.read_cursor += length return result except IndexError: raise SerializationError("attempt to read past end of buffer") def read_boolean(self): return self.read_bytes(1)[0] != chr(0) def read_int16(self): return self._read_num('= b58_base: div, mod = divmod(long_value, b58_base) result = b58_chars[mod] + result long_value = div result = b58_chars[long_value] + result # Bitcoin does a little leading-zero-compression: # leading 0-bytes in the input become leading-1s nPad = 0 for c in v: if c == 0: nPad += 1 else: break return (b58_chars[0] * nPad) + result def b58decode(v, length): """ decode v into a string of len bytes """ long_value = 0 for (i, c) in enumerate(v[::-1]): long_value += b58_chars.find(c) * (b58_base ** i) result = '' while long_value >= 256: div, mod = divmod(long_value, 256) result = chr(mod) + result long_value = div result = chr(long_value) + result nPad = 0 for c in v: if c == b58_chars[0]: nPad += 1 else: break result = chr(0) * nPad + result if length is not None and len(result) != length: return None return result def double_sha256(data): return hashlib.sha256(hashlib.sha256(data).digest()).digest() def encode_base58_check(secret): hash = double_sha256(secret) return b58encode(secret + hash[0:4]) def privkey_to_secret(privkey): if len(privkey) == 279: return privkey[9:9 + 32] else: return privkey[8:8 + 32] def secret_to_asecret(secret, version): prefix = (version + 128) & 255 vchIn = bytes([prefix]) + secret return encode_base58_check(vchIn) def hash_160(public_key): md = hashlib.new('ripemd160') md.update(hashlib.sha256(public_key).digest()) return md.digest() def public_key_to_bc_address(public_key, version): h160 = hash_160(public_key) return hash_160_to_bc_address(h160, version) def hash_160_to_bc_address(h160, version): vh160 = bytes([int(version)]) + h160 h = double_sha256(vh160) addr = vh160 + h[0:4] return b58encode(addr) def bc_address_to_hash_160(addr): bytes = b58decode(addr, 25) return bytes[1:21] ================================================ FILE: walletool/wallet_files.py ================================================ # -- encoding: UTF-8 -- import collections import os def read_wallet_dat(filename): from bsddb3 import db filename = os.path.realpath(filename) env = db.DBEnv() env.set_lk_detect(db.DB_LOCK_DEFAULT) env.open( os.path.dirname(filename), db.DB_PRIVATE | db.DB_THREAD | db.DB_INIT_LOCK | db.DB_INIT_MPOOL | db.DB_CREATE, ) d = db.DB(env) d.open(filename, 'main', db.DB_BTREE, db.DB_THREAD | db.DB_RDONLY) return collections.OrderedDict((k, d[k]) for k in d.keys()) ================================================ FILE: walletool/wallet_items.py ================================================ # -- encoding: UTF-8 -- import socket from binascii import hexlify from walletool.bc_data_stream import BCDataStream from walletool.utils import privkey_to_secret, secret_to_asecret, public_key_to_bc_address def parse_TxIn(vds): d = {} d['prevout_hash'] = vds.read_bytes(32) d['prevout_n'] = vds.read_uint32() d['scriptSig'] = vds.read_bytes(vds.read_compact_size()) d['sequence'] = vds.read_uint32() return d def parse_TxOut(vds): d = {} d['value'] = vds.read_int64() / 1e8 d['scriptPubKey'] = vds.read_bytes(vds.read_compact_size()) return d def inversetxid(txid): txid = hexlify(txid).decode() if len(txid) != 64: raise ValueError('txid %r length != 64' % txid) new_txid = "" for i in range(32): new_txid += txid[62 - 2 * i] new_txid += txid[62 - 2 * i + 1] return new_txid def parse_CAddress(vds): d = {'ip': '0.0.0.0', 'port': 0, 'nTime': 0} try: d['nVersion'] = vds.read_int32() d['nTime'] = vds.read_uint32() d['nServices'] = vds.read_uint64() d['pchReserved'] = vds.read_bytes(12) d['ip'] = socket.inet_ntoa(vds.read_bytes(4)) d['port'] = vds.read_uint16() except: pass return d def parse_BlockLocator(vds): d = {'hashes': []} nHashes = vds.read_compact_size() for i in range(nHashes): d['hashes'].append(vds.read_bytes(32)) return d def parse_setting(setting, vds): if setting[0] == "f": # flag (boolean) settings return str(vds.read_boolean()) elif setting[0:4] == "addr": # CAddress return parse_CAddress(vds) elif setting == "nTransactionFee": return vds.read_int64() elif setting == "nLimitProcessors": return vds.read_int32() return {'unknown': vds} class WalletItem: item_type = None def __init__(self, key, value, type, data): self.key = key self.value = value self.type = type self.data = data def __repr__(self): return '<%s item: %s>' % (self.type, self.data) @classmethod def parse(cls, key, value): kds = BCDataStream(key) vds = BCDataStream(value) type = kds.read_string().decode() data = {} # From Pywallet: if type == 'tx': data['tx_id'] = inversetxid(kds.read_bytes(32)) start = vds.read_cursor data['version'] = vds.read_int32() n_vin = vds.read_compact_size() data['txIn'] = [] for i in range(n_vin): data['txIn'].append(parse_TxIn(vds)) n_vout = vds.read_compact_size() data['txOut'] = [] for i in range(n_vout): data['txOut'].append(parse_TxOut(vds)) data['lockTime'] = vds.read_uint32() data['tx'] = vds.input[start:vds.read_cursor] data['txv'] = value data['txk'] = key elif type == 'name': data['hash'] = kds.read_string() data['name'] = vds.read_string() elif type == 'version': data['version'] = vds.read_uint32() elif type == 'minversion': data['minversion'] = vds.read_uint32() elif type == 'setting': data['setting'] = kds.read_string() data['value'] = parse_setting(data['setting'].decode(), vds) elif type == 'key': data['public_key'] = kds.read_bytes(kds.read_compact_size()) data['private_key'] = vds.read_bytes(vds.read_compact_size()) elif type == 'wkey': data['public_key'] = kds.read_bytes(kds.read_compact_size()) data['private_key'] = vds.read_bytes(vds.read_compact_size()) data['created'] = vds.read_int64() data['expires'] = vds.read_int64() data['comment'] = vds.read_string() elif type == 'defaultkey': data['key'] = vds.read_bytes(vds.read_compact_size()) elif type == 'pool': data['n'] = kds.read_int64() data['nVersion'] = vds.read_int32() data['nTime'] = vds.read_int64() data['public_key'] = vds.read_bytes(vds.read_compact_size()) elif type == 'acc': data['account'] = kds.read_string() data['nVersion'] = vds.read_int32() data['public_key'] = vds.read_bytes(vds.read_compact_size()) elif type == 'acentry': data['account'] = kds.read_string() data['n'] = kds.read_uint64() data['nVersion'] = vds.read_int32() data['nCreditDebit'] = vds.read_int64() data['nTime'] = vds.read_int64() data['otherAccount'] = vds.read_string() data['comment'] = vds.read_string() elif type == 'bestblock': data['nVersion'] = vds.read_int32() data.update(parse_BlockLocator(vds)) elif type == 'ckey': data['public_key'] = kds.read_bytes(kds.read_compact_size()) data['encrypted_private_key'] = vds.read_bytes(vds.read_compact_size()) elif type == 'mkey': data['nID'] = kds.read_uint32() data['encrypted_key'] = vds.read_string() data['salt'] = vds.read_string() data['nDerivationMethod'] = vds.read_uint32() data['nDerivationIterations'] = vds.read_uint32() data['otherParams'] = vds.read_string() for item_cls in cls.__subclasses__(): if item_cls.item_type == type: break else: item_cls = cls return item_cls(key, value, type, data) class KeyWalletItem(WalletItem): item_type = 'key' def get_address(self, version): return public_key_to_bc_address(self.data['public_key'], version=version) def get_private_key(self, version): secret = privkey_to_secret(self.data['private_key']) asecret = secret_to_asecret(secret, version=version) return asecret def parse_wallet_dict(wallet_dict): for key, value in wallet_dict.items(): yield WalletItem.parse(key, value) ================================================ FILE: wt_extract_keys.py ================================================ from walletool import init_env from walletool.wallet_files import read_wallet_dat from walletool.wallet_items import parse_wallet_dict, KeyWalletItem from walletool.consts import addrtypes import argparse def main(): ap = argparse.ArgumentParser() ap.add_argument('-d', '--dat', help='wallet.dat path', required=True, dest='filename') ap.add_argument('-v', '--version', help='address version, as integer, 0xHEX, or any of the following known coins:\n[%s]' % ', '.join(sorted(addrtypes)), required=True) args = ap.parse_args() if args.version.startswith('0x'): version = int(args.version[2:], 16) elif args.version.isdigit(): version = int(args.version) else: if args.version not in addrtypes: raise ValueError('invalid version (see --help)') version = addrtypes[args.version] w_data = read_wallet_dat(args.filename) addr_tuples = [] for item in parse_wallet_dict(w_data): if isinstance(item, KeyWalletItem): address = item.get_address(version=version) privkey = item.get_private_key(version=version) addr_tuples.append((address, privkey)) for address, privkey in addr_tuples: print(address, privkey) if __name__ == '__main__': main()