Spaces:
Running
#!/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
|
@@ -1,10 +1,13 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
|
| 4 |
-
colorFrom: red
|
| 5 |
colorTo: purple
|
|
|
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
|
|
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
| 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).
|