commit 6cf7c775de4b3e9e971b55e954c24ec971367a78 Author: tcsenpai Date: Thu Nov 28 19:00:13 2024 +0100 working commit diff --git a/.env b/.env new file mode 100644 index 0000000..c404307 --- /dev/null +++ b/.env @@ -0,0 +1,9 @@ +# Enable balance checking (true/false) +CHECK_BALANCES=true +# RPC URL for Ethereum node +RPC_URL=https://eth.llamarpc.com +# How many addresses to check in each batch +BALANCE_BATCH_SIZE=100 +# Desired address prefix +PREFIX=dEAD000000000000000042069420694206942069 +SYNC_MODE=false # Set to true for synchronous balance checking \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..b2a7a49 --- /dev/null +++ b/README.md @@ -0,0 +1,81 @@ +# Ethereum Vanity Address Generator with CUDA + +A high-performance Ethereum vanity address generator that uses CUDA GPU acceleration to quickly generate Ethereum addresses matching a desired prefix pattern. It also includes optional balance checking functionality across multiple RPC endpoints. + +## Features + +- šŸš€ CUDA GPU acceleration for fast address generation +- šŸ” Configurable prefix matching with support for hex characters +- šŸ’° Optional balance checking across multiple RPC endpoints +- ⚔ Batch processing for efficient balance checks +- šŸ”„ Automatic RPC failover and rate limit handling +- šŸ“Š Real-time status updates and progress tracking +- šŸ’¾ Automatic saving of addresses with balances to file + +## Requirements + +- Python 3.7+ +- CUDA-capable NVIDIA GPU +- PyCUDA +- Web3.py +- Other dependencies listed in requirements.txt + +## Installation + +1. Clone the repository: + ```bash + git clone https://github.com/tcsenpai/eth-vanity-address-generator-cuda.git + ``` +2. Install dependencies: + ```bash + pip install -r requirements.txt + ``` + +3. Configure settings in `.env` file: + ```bash + # Enable balance checking (true/false) + CHECK_BALANCES=true + # RPC URL for Ethereum node + RPC_URL=https://eth.llamarpc.com + # How many addresses to check in each batch + BALANCE_BATCH_SIZE=100 + # Desired address prefix + PREFIX=dEAD000000000000000042069420694206942069 + SYNC_MODE=false # Set to true for synchronous balance checking + ``` + +## Usage + +1. Run the script: + ```bash + python main.py + ``` + +2. Enter your desired address prefix when prompted, or configure it in the `.env` file. + +The script will begin generating addresses and checking balances if enabled. Status updates are printed every 10 seconds showing: + +- Time elapsed +- Total attempts +- Generation speed +- Balance check status +- Best match found so far + +When a matching address is found, it will be displayed along with its private key. + +## Configuration + +The following settings can be configured in the `.env` file: + +- `CHECK_BALANCES`: Enable/disable balance checking +- `RPC_URL`: Ethereum RPC endpoint URL +- `BALANCE_BATCH_SIZE`: Number of addresses to check in each batch +- `PREFIX`: Target address prefix +- `SYNC_MODE`: Use synchronous or asynchronous balance checking + +## Tips + +- Longer prefixes will take exponentially more time to find +- Consider using shorter prefixes for testing +- Multiple RPC endpoints are used for redundancy +- Found addresses with balances are saved to `found.txt` diff --git a/main.py b/main.py new file mode 100644 index 0000000..41d727f --- /dev/null +++ b/main.py @@ -0,0 +1,452 @@ +import pycuda.autoinit +import pycuda.driver as cuda +from pycuda.compiler import SourceModule +import numpy as np +from eth_keys import keys +from eth_utils import to_checksum_address +import time +import dotenv +import os +from web3 import Web3, AsyncWeb3 +import asyncio +from collections import deque +import aiohttp +import concurrent.futures +from threading import Lock + +cuda.init() + +dotenv.load_dotenv() + +# Add near the top after imports +RPC_URLS = [ + "https://eth.llamarpc.com", + "https://rpc.ankr.com/eth", + "https://ethereum.publicnode.com", + "https://1rpc.io/eth", +] + +# Add new configuration from .env +CHECK_BALANCES = os.getenv("CHECK_BALANCES", "false").lower() == "true" +BALANCE_BATCH_SIZE = int(os.getenv("BALANCE_BATCH_SIZE", "100")) +SYNC_MODE = os.getenv("SYNC_MODE", "false").lower() == "true" +BATCH_SIZE = int(os.getenv("BATCH_SIZE", "500")) # Number of addresses to check at once + +# Create a round-robin RPC selector +rpc_index = 0 + +# Add near the top after other global variables +last_balance_check = {"address": None, "balance": None, "rpc": None} + + +def get_next_web3(): + global rpc_index + web3 = Web3(Web3.HTTPProvider(RPC_URLS[rpc_index])) + rpc_index = (rpc_index + 1) % len(RPC_URLS) + return web3 + + +# Create a queue for addresses to check +address_queue = deque() + +# Add to imports and configuration +print_lock = Lock() +executor = concurrent.futures.ThreadPoolExecutor(max_workers=4) +pending_checks = [] + +# Add this near the top of the file, after imports +cuda_code = """ +#include + +__device__ __forceinline__ void generate_private_key(unsigned char *out, const int idx, curandState *state) { + // Generate 32 bytes (256 bits) for the private key using vectorized operations + uint4 *out_vec = (uint4*)&out[idx * 32]; + + // Generate 4 random 32-bit values using CUDA's optimized RNG + uint4 rand_val; + rand_val.x = curand(state); + rand_val.y = curand(state); + rand_val.z = curand(state); + rand_val.w = curand(state); + + // Store using vector operation (more efficient than byte-by-byte) + *out_vec = rand_val; +} + +extern "C" __global__ void generate_random(unsigned char *out, const int n) { + const int idx = blockIdx.x * blockDim.x + threadIdx.x; + if (idx >= n) return; + + // Initialize CUDA RNG state + curandState state; + curand_init(clock64() + idx, 0, 0, &state); + + // Generate private key using vectorized operations + generate_private_key(out, idx, &state); +} +""" + + +def check_single_balance(address, private_key): + """Non-blocking balance check with RPC failover""" + for _ in range(len(RPC_URLS)): # Try each RPC once + try: + web3 = get_next_web3() + balance = web3.eth.get_balance(address) + # Update last balance check info + global last_balance_check + last_balance_check = { + "address": address, + "balance": Web3.from_wei(balance, "ether"), + "rpc": web3.provider.endpoint_uri + } + if balance > 0: + with print_lock: + print(f"\n{'='*50}") + print(f"šŸ”„ Found address with balance!") + print(f"Address: {address}") + print(f"Balance: {Web3.from_wei(balance, 'ether')} ETH") + print(f"Private key: {private_key}") + print(f"{'='*50}\n") + with open("found.txt", "a") as f: + f.write(f"{address} {private_key}\n") + return balance + except Exception as e: + if "429" in str(e): # Rate limit error + with print_lock: + print(f"āš ļø Rate limit hit on {web3.provider.endpoint_uri}: {str(e)}") + time.sleep(1) # Back off a bit + else: + with print_lock: + print(f"āš ļø Error on {web3.provider.endpoint_uri}, trying next RPC: {str(e)}") + continue + + with print_lock: + print(f"āŒ All RPCs failed to check balance for {address}") + return None + + +async def generate_vanity_address(prefix, num_attempts=0): + mod = SourceModule( + cuda_code, no_extern_c=True, include_dirs=["/usr/local/cuda/include"] + ) + generate_random = mod.get_function("generate_random") + + # Use up to 4GB of GPU memory + max_memory = 4 * 1024 * 1024 * 1024 # 4GB in bytes + max_batch = max_memory // 32 # Each key is 32 bytes + current_batch = min(max_batch, 4000000) # Start with 4 million addresses + + # Create pinned memory directly instead of converting + private_keys = cuda.pagelocked_empty((current_batch, 32), dtype=np.uint8) + try: + gpu_private_keys = cuda.mem_alloc(private_keys.nbytes) + except cuda.LogicError as e: + print( + f"āš ļø GPU memory allocation failed with batch size {current_batch}. Trying smaller batch..." + ) + current_batch = current_batch // 2 + private_keys = cuda.pagelocked_empty((current_batch, 32), dtype=np.uint8) + gpu_private_keys = cuda.mem_alloc(private_keys.nbytes) + + # Optimize thread configuration for RTX 4060 + block_size = 1024 # Maximum threads per block + grid_size = (current_batch + block_size - 1) // block_size + + print(f"\nšŸ” Starting search for prefix: {prefix}") + print( + f"šŸ’¾ Batch size: {current_batch:,} addresses ({private_keys.nbytes / 1024 / 1024:.1f} MB)" + ) + print(f"🧮 Grid size: {grid_size} blocks Ɨ {block_size} threads") + print(f"šŸ’” This might take a while depending on prefix length...") + + start_time = time.time() + total_attempts = 0 + prefixes_checked = 0 + best_match = {"address": None, "similarity": 0, "private_key": None} + last_status_time = time.time() + total_balance_checks = 0 + active_checks = 0 + + def check_callback(future): + nonlocal active_checks, total_balance_checks + active_checks -= 1 + total_balance_checks += 1 + + address_batch = [] + private_key_batch = [] + + while True: + generate_random( + gpu_private_keys, + np.int32(current_batch), + block=(block_size, 1, 1), + grid=(grid_size, 1), + ) + + cuda.memcpy_dtoh(private_keys, gpu_private_keys) + total_attempts += current_batch + + # Process addresses without waiting for balance checks + for priv_key in private_keys: + priv_key_hex = "".join(format(x, "02x") for x in priv_key) + + try: + private_key = keys.PrivateKey(bytes.fromhex(priv_key_hex)) + public_key = private_key.public_key + address = to_checksum_address(public_key.to_address()) + addr_without_prefix = address[2:].lower() + + # Check for exact match first (fast path) + if addr_without_prefix.startswith(prefix.lower()): + # Found a match! Queue balance check if needed + if CHECK_BALANCES: + address_batch.append(address) + private_key_batch.append(priv_key_hex) + return private_key, address + + # Track best partial match + similarity = calculate_similarity(addr_without_prefix, prefix.lower()) + if similarity > best_match["similarity"]: + best_match = { + "address": address, + "similarity": similarity, + "private_key": priv_key_hex, + } + print( + f"šŸŽÆ Best match so far: {best_match['address']} ({best_match['similarity']} chars)" + ) + # Immediately check for balance if best match without waiting + ethBalance = check_single_balance(address, priv_key_hex) + print(f"šŸ’° Balance: {ethBalance} ETH") + # Queue balance check if enabled (without waiting) + if CHECK_BALANCES: + address_batch.append(address) + private_key_batch.append(priv_key_hex) + if len(address_batch) >= BATCH_SIZE: + # Schedule balance check without awaiting + asyncio.create_task( + batch_check_balances( + address_batch.copy(), private_key_batch.copy() + ) + ) + address_batch.clear() + private_key_batch.clear() + + except Exception as e: + continue + + # Status update every 10 seconds + current_time = time.time() + if current_time - last_status_time >= 10: + elapsed_time = current_time - start_time + speed = total_attempts / elapsed_time + prefix_speed = prefixes_checked / elapsed_time + print(f"\n{'='*30} Status Update {'='*30}") + print(f"ā±ļø Time elapsed: {elapsed_time:.1f}s") + print(f"šŸ”¢ Attempts: {total_attempts:,}") + print(f"šŸ” Prefixes checked: {prefixes_checked:,}") + print(f"⚔ Speed: {speed:,.2f} addr/s") + print(f"šŸš€ Prefix check speed: {prefix_speed:,.2f} prefixes/s") + if CHECK_BALANCES: + print(f"šŸ’° Balance checks queued: {len(address_batch)}") + print(f"āœ“ Total balance checks: {total_balance_checks:,}") + if last_balance_check["address"]: + print( + f"šŸ“Š Last check: {last_balance_check['address']} - {last_balance_check['balance']} ETH" + ) + print( + f"šŸŽÆ Best match so far: {best_match['address']} ({best_match['similarity']} chars)" + ) + print(f"{'='*72}\n") + last_status_time = current_time + + prefixes_checked += 1 # we checked a key + + # Only break if num_attempts is positive + if num_attempts > 0 and total_attempts >= num_attempts: + print(f"\nāš ļø Reached maximum attempts: {num_attempts:,}") + break + + return None, None + + +def suggest_leet_alternatives(text): + # Only include leet mappings that result in valid hex characters + leet_map = { + "a": "4", + "e": "3", + "i": "1", + "o": "0", + "s": "5", + "b": "8", + # Removed 't':'7' and 'g':'9' as they're not valid hex + } + alternatives = [text] + + # Generate variations + for char, leet_char in leet_map.items(): + for existing in alternatives.copy(): + if char in existing.lower(): + alternatives.append(existing.lower().replace(char, leet_char)) + + # Filter to ensure only valid hex strings are suggested + valid_chars = set("0123456789abcdefABCDEF") + valid_alternatives = [ + alt for alt in alternatives if all(c in valid_chars for c in alt) + ] + + return list(set(valid_alternatives))[:5] # Return up to 5 unique suggestions + + +def sanitize_prefix(prefix): + # Remove '0x' if present + prefix = prefix.replace("0x", "") + + # Check for valid hex characters + valid_chars = set("0123456789abcdefABCDEF") + if not all(c in valid_chars for c in prefix): + invalid_chars = [c for c in prefix if c not in valid_chars] + suggestions = suggest_leet_alternatives(prefix) + raise ValueError( + f"Invalid characters in prefix: {invalid_chars}\n" + f"Prefix must only contain hex characters (0-9, a-f)\n" + f"Try these leet alternatives: {', '.join(suggestions)}" + ) + + return prefix.lower() + + +def calculate_similarity(address, prefix): + """Count matching characters from start of address with prefix""" + for i, (a, p) in enumerate(zip(address, prefix)): + if a != p: + return i + return len(prefix) + + +async def batch_check_balances(addresses, private_keys): + """Check multiple balances in a single RPC call with failover""" + for _ in range(len(RPC_URLS)): # Try each RPC once + try: + web3 = get_next_web3() + + # Create batch payload + payload = [ + { + "jsonrpc": "2.0", + "method": "eth_getBalance", + "params": [addr, "latest"], + "id": i + } + for i, addr in enumerate(addresses) + ] + + async with aiohttp.ClientSession() as session: + async with session.post(web3.provider.endpoint_uri, json=payload) as response: + results = await response.json() + + # Process results + for result, address, private_key in zip(results, addresses, private_keys): + if "result" in result: + balance = int(result["result"], 16) # Convert hex to int + # Update last balance check info + global last_balance_check + last_balance_check = { + "address": address, + "balance": Web3.from_wei(balance, "ether"), + "rpc": web3.provider.endpoint_uri + } + if balance > 0: + with print_lock: + print(f"\n{'='*50}") + print(f"šŸ”„ Found address with balance!") + print(f"Address: {address}") + print(f"Balance: {Web3.from_wei(balance, 'ether')} ETH") + print(f"Private key: {private_key}") + print(f"{'='*50}\n") + with open("found.txt", "a") as f: + f.write(f"{address} {private_key}\n") + + return results + + except Exception as e: + if "429" in str(e): # Rate limit error + with print_lock: + print(f"āš ļø Rate limit hit on {web3.provider.endpoint_uri}: {str(e)}") + time.sleep(1) # Back off a bit + else: + with print_lock: + print(f"āš ļø Batch check error on {web3.provider.endpoint_uri}, trying next RPC: {str(e)}") + continue + + with print_lock: + print(f"āŒ All RPCs failed for batch balance check of {len(addresses)} addresses") + return None + + +async def determine_optimal_batch_size(rpc_url): + print("šŸ” Testing RPC batch size limits...") + optimal_size = 100 # Default fallback + + test_sizes = [2000, 1000, 500, 100] + for batch_size in test_sizes: + try: + web3 = Web3(Web3.HTTPProvider(rpc_url)) + addresses = ["0x" + "0" * 40] * batch_size + + payload = [ + { + "jsonrpc": "2.0", + "method": "eth_getBalance", + "params": [addr, "latest"], + "id": i, + } + for i, addr in enumerate(addresses) + ] + + async with aiohttp.ClientSession() as session: + async with session.post( + web3.provider.endpoint_uri, json=payload + ) as response: + results = await response.json() + if isinstance(results, list) and len(results) == batch_size: + optimal_size = batch_size + print(f"āœ… Found optimal batch size: {optimal_size}") + return optimal_size + + except Exception as e: + print(f"āŒ Batch size {batch_size} failed: {str(e)}") + continue + + print(f"ā„¹ļø Using conservative batch size: {optimal_size}") + return optimal_size + + +async def main(): + prefix = os.getenv("PREFIX") + if not prefix: + prefix = input("Enter desired address prefix (without 0x): ") + + try: + prefix = sanitize_prefix(prefix) + private_key, address = await generate_vanity_address(prefix) + + # Cleanup thread pool + executor.shutdown(wait=False) + + return private_key, address + + except ValueError as e: + print(f"Error: {e}") + exit(1) + + +if __name__ == "__main__": + SUPPORTED_SIZES = [] + for url in RPC_URLS: + SUPPORTED_SIZES.append(asyncio.run(determine_optimal_batch_size(url))) + BALANCE_BATCH_SIZE = min(SUPPORTED_SIZES) + print(f"šŸŽÆ Using safe batch size of {BALANCE_BATCH_SIZE} for all RPCs\n") + + asyncio.run(main()) diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..1918ac4 --- /dev/null +++ b/run.sh @@ -0,0 +1,5 @@ +export CUDA_CACHE_DISABLE=0 +export CUDA_CACHE_MAXSIZE=2147483647 +export CUDA_CACHE_PATH=/tmp/cuda-cache + +python main.py \ No newline at end of file