xvin1111 commited on
Commit
3d5ca59
·
verified ·
1 Parent(s): d71f829

#!/usr/bin/env python3

Browse files

# -*- coding: utf-8 -*-

"""
btc_key_finder.py

A one‑stop utility that:
• Accepts a seed (hex string or BIP‑39 mnemonic)
• Derives many candidate private keys with three strategies:
– PBKDF2‑HMAC‑SHA‑512 “stretch‑and‑mix”
– BIP‑44 hierarchical deterministic (HD) tree
– Pattern‑based functions (Fibonacci, modular‑multiply, rotate)
• Converts each private key to a legacy Bitcoin address (starts with “1”)
• Queries Blockstream’s public API for balances (bulk, rate‑limited)
• Optionally filters for a vanity prefix (e.g. “1H8U”)
• Writes a tidy JSON report (report.json) containing keys, addresses,
balances and any matches.

Dependencies (install once):
pip install bip-utils ecdsa requests tqdm

Author: ChatGPT (OpenAI)
Date: 2025‑09‑29
"""

import argparse
import hashlib
import json
import sys
import time
import base64
import logging
from typing import Callable, Dict, List
import requests

# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

# ----------------------------------------------------------------------
# 3rd‑party imports – keep them at the top so the user sees the requirements
# ----------------------------------------------------------------------
try:
from bip_utils import Bip39SeedGenerator, Bip44, Bip44Coins, Bip44Changes, Base58Encoder
from ecdsa import SECP256k1, SigningKey
import requests
from tqdm import tqdm
except ImportError as exc: # pragma: no cover
sys.stderr.write(
"Missing dependency. Install with:\n"
" pip install bip-utils ecdsa requests tqdm\n"
)
raise SystemExit(1)


# ----------------------------------------------------------------------
# Helper: private‑key (hex) → legacy P2PKH address (Base58Check, starts with “1”)
# ----------------------------------------------------------------------
def privkey_to_address(priv_hex: str) -> str:
"""Return a legacy Bitcoin address (Base58Check) from a 32-byte hex private key."""
sk: SigningKey = SigningKey.from_string(bytes.fromhex(priv_hex), curve=SECP256k1)
vk = sk.get_verifying_key()
# Compressed public key (33 bytes)
pub = b"\x02" + vk.to_string()[:32] if vk.to_string()[-1] % 2 == 0 else b"\x03" + vk.to_string()[:32]
# HASH160 = RIPEMD160(SHA256(pubkey))
h160: bytes = hashlib.new("ripemd160", hashlib.sha256(pub).digest()).digest()
payload: bytes = b"\x00" + h160 # version byte 0x00 = mainnet
checksum: bytes = hashlib.sha256(hashlib.sha256(payload).digest()).digest()[:4]
address: str = Base58Encoder.Encode(payload + checksum)
return address


# ----------------------------------------------------------------------
# 1️⃣ Seed handling (hex string or BIP‑39 mnemonic)
# ----------------------------------------------------------------------
def seed_from_hex(hex_str: str) -> bytes:
"""Interpret a raw hex string as seed bytes."""
try:
return bytes.fromhex(hex_str.strip())
except ValueError as exc: # pragma: no cover
raise ValueError("Invalid hex seed.") from exc


def seed_from_mnemonic(mnemonic: str, passphrase: str = "") -> bytes:
"""Generate the BIP-39 seed (512-bit) from a mnemonic + optional passphrase."""
return Bip39SeedGenerator(mnemonic).Generate(passphrase)


# ----------------------------------------------------------------------
# 2️⃣ Derivation strategies
# ----------------------------------------------------------------------
# ------------------------------------------------------------------
# a) PBKDF2‑HMAC‑SHA‑512 “stretch‑and‑mix”
# ------------------------------------------------------------------
def pbkdf2_derivations(
seed: bytes,
salt_prefix: str = "bitcoin",
iterations: int = 2048,
count: int = 10,
) -> Dict[str, str]:
"""Return `count` private keys (hex) derived via PBKDF2."""
from hashlib import pbkdf2_hmac

out = {}
for i in range(count):
full_salt: bytes = (salt_prefix + str(i)).encode()
key: bytes = pbkdf2_hmac("sha512", seed, full_salt, iterations, dklen= 32)
out[f"pbkdf2_{i}"] = key.hex()
return out


# ------------------------------------------------------------------
# b) BIP‑44 HD derivation (standard Bitcoin main‑net path)
# ------------------------------------------------------------------
def hd_derivations(
seed: bytes,
account: int = 0,
change: Bip44Changes = Bip44Changes.CHAIN_EXT,
num_addresses: int = 20,
) -> Dict[str, Dict[str, str]]:
"""
Return a dict:
derivation_name → {"address": <legacy>, "privkey": <hex>}
Uses BIP‑44 path m/44'/0'/account'/change/i
"""
master = Bip44.FromSeed(seed, Bip44Coins.BITCOIN)
acct = master.Purpose().Coin().Account(account).Change(Bip44Changes(change))

out: Dict[str, Dict[str, str]] = {}
for i in range(num_addresses):
addr: str = acct.AddressIndex(i).PublicKey().ToAddress()
priv: str = acct.AddressIndex(i).PrivateKey().Raw().ToHex()
out[f"bip44_{i}"] = {"address": addr, "privkey": priv}
return out


# ------------------------------------------------------------------
# c) Pattern‑based derivations (customizable)
# ------------------------------------------------------------------
def pattern_derivation(
seed: bytes,
mutate: Callable[[bytes, int], bytes],
count: int = 10,
) -> Dict[str, str]:
"""Apply `mutate(seed, i)` → SHA-256 → 32-byte private key (hex)."""
out: Dict[str, str] = {}
for i in range(count):
mutated = mutate(seed, i)
priv = hashlib.sha256(mutated).digest().hex()
out[f"pattern_{i}"] = priv
return out


# ---- Example mutation functions (feel free to add more) ----
def fib_mutate(seed: bytes, i: int) -> bytes:
"""Add the i-th Fibonacci number (mod 256) to every byte."""
a: int = 0
b: int = 1
for _ in range(i + 1):
a, b = b, (a + b) % 256
return bytes((b + x) % 256 for x in seed)


def mod_mul_mutate(seed: bytes, i: int) -> bytes:
"""Multiply each byte by (i+ 1) modulo 256."""
factor: int = (i + 1) % 256
return bytes((x * factor) % 256 for x in seed)


def rotate_mutate(seed: bytes, i: int) -> bytes:
"""Rotate the byte-array left by i positions."""
i: int = i % len(seed)
return seed[i:] + seed[:i]


# ----------------------------------------------------------------------
# 3️⃣ Bulk balance checker (rate‑limited, uses Blockstream API)
# ----------------------------------------------------------------------
BLOCKSTREAM_URL = "https://blockstream.info/api/address/{}"


def _fetch_one(address: str) -> Dict[str, int]:
"""Internal helper - fetch confirmed & unconfirmed satoshi balance for a single address."""
resp: requests.Response = requests.get(BLOCKSTREAM_URL.format(address), timeout=10)
resp.raise_for_status()
data: Dict = resp.json()
confirmed: int = data["chain_stats"]["funded_txo_sum"] - data["chain_stats"]["spent_txo_sum"]
unconfirmed: int = data["mempool_stats"]["funded_txo_sum"] - data["mempool_stats"]["spent_txo_sum"]
return {"confirmed": confirmed, "unconfirmed": unconfirmed}


def bulk_balance(
addresses: List[str],
batch_size: int = 20,
pause: float = 0.5,
) -> Dict[str, Dict]:
"""
Query balances for many Bitcoin addresses, respecting a simple rate-limit.

Parameters
----------
addresses : list[str]
List of legacy (1…), P2SH (3…) or bech32 (bc1…) addresses.
batch_size : int, optional
How many addresses to request before sleeping. 20 works well for the free Blockstream endpoint.
pause : float, optional
Seconds to sleep after each batch (default 0.5 s ≈ 2 req/s).

Returns
-------
dict
Mapping ``address → {confirmed, unconfirmed}``. If a request fails,
the value will contain an ``"error"`` key.
"""
results: Dict[str, Dict] = {}
total: int = len(addresses)

for start in range(0, total, batch_size):
batch: List[str] = addresses[start : start + batch_size]

for addr in batch:
retries = 3
for attempt in range(retries):
try:
results[addr] = _fetch_one(addr)
break # If successful, break out of the retry loop
except requests.exceptions.HTTPError as e:
if e.response.status_code == 429 and attempt < retries - 1:
logging.warning(
f"Rate limit exceeded for {addr}, retrying in {pause * (attempt + 1)} seconds..."
)
time.sleep(pause * (attempt + 1)) # Exponential backoff
else:
logging.error(f"HTTP error fetching balance for {addr}: {e}")
results[addr] = {"error": str(e)}
break # If it's not a rate limit error or last attempt, don't retry
except Exception as e:
logging.error(f"Error fetching balance for {addr}: {e}")
results[addr] = {"error": str(e)}
break # If there's an unexpected error, don't retry

# Sleep only if there is another batch coming
if start + batch_size < total:
logging.info(f"Sleeping for {pause} seconds...")
time.sleep(pause)

return results


# ----------------------------------------------------------------------
# 4️⃣ Vanity‑prefix filter (optional)
# ----------------------------------------------------------------------
def filter_by_prefix(candidates: Dict[str, str], prefix: str) -> Dict[str, Dict]:
"""
Return a dict of candidates whose *address* starts with ``prefix``.
The input ``candidates`` must be a mapping ``name -> privkey_hex``.
"""
matches: Dict[str, Dict] = {}
for name, priv in candidates.items():
addr: str = privkey_to_address(priv)
if addr.startswith(prefix):
matches[name] = {"address": ad

Files changed (1) hide show
  1. README.md +7 -4
README.md CHANGED
@@ -1,10 +1,13 @@
1
  ---
2
- title: Deepsite Project
3
- emoji: 🏆
4
- colorFrom: red
5
  colorTo: purple
 
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
1
  ---
2
+ title: DeepSite Project
3
+ colorFrom: pink
 
4
  colorTo: purple
5
+ emoji: 🐳
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite-v3
10
  ---
11
 
12
+ # Welcome to your new DeepSite project!
13
+ This project was created with [DeepSite](https://deepsite.hf.co).