diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7f2af6b --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.env +best_matches.json +cuda_matches.json +__pycache__ +found_addresses.txt \ No newline at end of file diff --git a/README.md b/README.md index b2a7a49..51d2eee 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 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. +A high-performance Ethereum vanity address generator that uses CUDA GPU acceleration to quickly generate Ethereum addresses matching a desired prefix pattern. It includes optional balance checking functionality across multiple RPC endpoints and tracks best partial matches. ## Features @@ -10,7 +10,9 @@ A high-performance Ethereum vanity address generator that uses CUDA GPU accelera - ⚡ 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 +- 💾 Automatic saving of matches and addresses with balances +- 🎯 Tracks best partial matches and saves them to JSON +- 🔐 Secure private key generation using CUDA's RNG ## Requirements @@ -23,59 +25,97 @@ A high-performance Ethereum vanity address generator that uses CUDA GPU accelera ## Installation 1. Clone the repository: - ```bash - git clone https://github.com/tcsenpai/eth-vanity-address-generator-cuda.git - ``` + ```bash + git clone https://github.com/tcsenpai/eth-vanity-address-generator-cuda.git + ``` 2. Install dependencies: - ```bash - pip install -r requirements.txt - ``` + + ```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 - ``` + + ```bash + # Enable balance checking (true/false) + CHECK_BALANCES=true + + # RPC URLs for redundancy + RPC_URLS=[ + "https://eth.llamarpc.com", + "https://rpc.ankr.com/eth", + "https://ethereum.publicnode.com", + "https://1rpc.io/eth" + ] + + # How many addresses to check in each batch + BALANCE_BATCH_SIZE=100 + + # Desired address prefix + PREFIX=dEAD000000000000000042069420694206942069 + + # Set to true for synchronous balance checking + SYNC_MODE=false + + # CUDA batch size for address generation + BATCH_SIZE=500 + ``` ## Usage -1. Run the script: - ```bash - python main.py - ``` +You can run either the pure CUDA version or the version with balance checking: -2. Enter your desired address prefix when prompted, or configure it in the `.env` file. +1. CUDA-only version: -The script will begin generating addresses and checking balances if enabled. Status updates are printed every 10 seconds showing: + ```bash + ./find_address_fullcuda.sh + ``` + +2. CUDA with balance checking: + ```bash + ./find_address_and_check_balance.sh + ``` + +The script will begin generating addresses and checking balances if enabled. Status updates are printed every 10 seconds (5s for the full cuda version without balance checking) and show: - Time elapsed - Total attempts - Generation speed +- Prefix check speed - Balance check status - Best match found so far When a matching address is found, it will be displayed along with its private key. +## Output Files + +- `found_addresses.txt`: Contains addresses found with balances +- `best_matches.json`: Tracks the best partial matches found +- `cuda_matches.json`: Records all matches found by the CUDA miner + ## 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 +- `CHECK_BALANCES`: Enable/disable balance checking (only works with balance checking version) +- `RPC_URLS`: List of Ethereum RPC endpoints for redundancy (only works with balance checking version) +- `BALANCE_BATCH_SIZE`: Number of addresses to check in each batch (only works with balance checking version) - `PREFIX`: Target address prefix - `SYNC_MODE`: Use synchronous or asynchronous balance checking +- `BATCH_SIZE`: CUDA batch size for address generation ## 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` +- Multiple RPC endpoints provide redundancy and failover +- The system automatically tracks and saves best partial matches +- CUDA batch size is optimized for RTX series GPUs +- Balance checking automatically determines optimal batch sizes for each RPC + +## Performance + +The following metrics are based on a RTX 4060 Mobile GPU (8GB VRAM). + +- The full cuda version without balance checking manages to check about 25,000,000 (25 million) addresses per second. +- The balance checking version manages to check about 2000 prefixes and 1000 balances per second. Disabling balance checking increases the speed to about 12,000 prefixes per second. diff --git a/env.example b/env.example new file mode 100644 index 0000000..c4c92cf --- /dev/null +++ b/env.example @@ -0,0 +1,11 @@ +# 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 +# CUDA batch size for address generation +BATCH_SIZE=500 \ No newline at end of file diff --git a/find_address_and_check_balance.sh b/find_address_and_check_balance.sh new file mode 100755 index 0000000..aba0f0e --- /dev/null +++ b/find_address_and_check_balance.sh @@ -0,0 +1,5 @@ +export CUDA_CACHE_DISABLE=0 +export CUDA_CACHE_MAXSIZE=2147483647 +export CUDA_CACHE_PATH=/tmp/cuda-cache + +python vanity_and_balance.py diff --git a/run.sh b/find_address_fullcuda.sh similarity index 82% rename from run.sh rename to find_address_fullcuda.sh index 1918ac4..b0d562f 100755 --- a/run.sh +++ b/find_address_fullcuda.sh @@ -2,4 +2,4 @@ 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 +python vanity_cuda.py diff --git a/libs/cudacode.py b/libs/cudacode.py new file mode 100644 index 0000000..20a4ec8 --- /dev/null +++ b/libs/cudacode.py @@ -0,0 +1,157 @@ +CUDA_CRYPTO_CODE = """ +#include +#include +#include + +#define KECCAK_ROUNDS 24 +#define BATCH_SIZE 65536 + +// Keccak round constants +__device__ __constant__ uint64_t keccak_round_constants[24] = { + 0x0000000000000001ULL, 0x0000000000008082ULL, 0x800000000000808AULL, + 0x8000000080008000ULL, 0x000000000000808BULL, 0x0000000080000001ULL, + 0x8000000080008081ULL, 0x8000000000008009ULL, 0x000000000000008AULL, + 0x0000000000000088ULL, 0x0000000080008009ULL, 0x000000008000000AULL, + 0x000000008000808BULL, 0x800000000000008BULL, 0x8000000000008089ULL, + 0x8000000000008003ULL, 0x8000000000008002ULL, 0x8000000000000080ULL, + 0x000000000000800AULL, 0x800000008000000AULL, 0x8000000080008081ULL, + 0x8000000000008080ULL, 0x0000000080000001ULL, 0x8000000080008008ULL +}; + +// Keccak state rotation offsets +__device__ __constant__ int keccak_rotc[24] = { + 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14, + 27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44 +}; + +// Keccak state permutation indices +__device__ __constant__ int keccak_piln[24] = { + 10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4, + 15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1 +}; + +// Keccak-256 hash function +__device__ void keccak256_transform(uint64_t* state) { + uint64_t temp, C[5]; + int i, j; + + for (int round = 0; round < KECCAK_ROUNDS; round++) { + // Theta step + for (i = 0; i < 5; i++) { + C[i] = state[i] ^ state[i + 5] ^ state[i + 10] ^ state[i + 15] ^ state[i + 20]; + } + for (i = 0; i < 5; i++) { + temp = C[(i + 4) % 5] ^ ((C[(i + 1) % 5] << 1) | (C[(i + 1) % 5] >> 63)); + for (j = 0; j < 25; j += 5) { + state[j + i] ^= temp; + } + } + + // Rho and Pi steps + temp = state[1]; + for (i = 0; i < 24; i++) { + j = keccak_piln[i]; + C[0] = state[j]; + state[j] = ((temp << keccak_rotc[i]) | (temp >> (64 - keccak_rotc[i]))); + temp = C[0]; + } + + // Chi step + for (j = 0; j < 25; j += 5) { + for (i = 0; i < 5; i++) { + C[i] = state[j + i]; + } + for (i = 0; i < 5; i++) { + state[j + i] ^= (~C[(i + 1) % 5]) & C[(i + 2) % 5]; + } + } + + // Iota step + state[0] ^= keccak_round_constants[round]; + } +} + +__device__ void keccak256_update(uint64_t* state, const uint8_t* data, size_t len) { + for (size_t i = 0; i < len; i++) { + state[i/8] ^= ((uint64_t)data[i]) << (8 * (i % 8)); + } + keccak256_transform(state); +} + +__device__ void keccak256_final(uint64_t* state, uint8_t* hash) { + keccak256_transform(state); + for (int i = 0; i < 4; i++) { + ((uint64_t*)hash)[i] = state[i]; + } +} + +// Main kernel for address generation and checking +extern "C" __global__ void generate_and_check( + uint8_t* private_keys, + uint8_t* addresses, + int* match_lengths, + const uint8_t* target_prefix, + int prefix_len, + uint32_t seed +) { + int idx = blockIdx.x * blockDim.x + threadIdx.x; + if (idx >= BATCH_SIZE) return; + + // Initialize random state + curandState rng_state; + curand_init(seed + idx, 0, 0, &rng_state); + + // Generate random private key + uint8_t private_key[32]; + for (int i = 0; i < 32; i++) { + private_key[i] = curand(&rng_state) & 0xFF; + } + + // Store private key + for (int i = 0; i < 32; i++) { + private_keys[idx * 32 + i] = private_key[i]; + } + + // Initialize Keccak state + uint64_t keccak_state[25] = {0}; + + // Hash private key to get address + keccak256_update(keccak_state, private_key, 32); + uint8_t hash[32]; + keccak256_final(keccak_state, hash); + + // Take last 20 bytes as address + for (int i = 0; i < 20; i++) { + addresses[idx * 20 + i] = hash[i + 12]; + } + + // Convert address to hex and compare with target + int match_count = 0; + for (int i = 0; i < prefix_len && i < 20; i++) { + uint8_t addr_byte = addresses[idx * 20 + i]; + uint8_t target_byte = target_prefix[i]; + + // Convert each byte to two hex characters + char addr_hex[2]; + addr_hex[0] = (addr_byte >> 4) <= 9 ? (addr_byte >> 4) + '0' : (addr_byte >> 4) - 10 + 'a'; + addr_hex[1] = (addr_byte & 0x0F) <= 9 ? (addr_byte & 0x0F) + '0' : (addr_byte & 0x0F) - 10 + 'a'; + + char target_hex[2]; + target_hex[0] = (target_byte >> 4) <= 9 ? (target_byte >> 4) + '0' : (target_byte >> 4) - 10 + 'a'; + target_hex[1] = (target_byte & 0x0F) <= 9 ? (target_byte & 0x0F) + '0' : (target_byte & 0x0F) - 10 + 'a'; + + // Compare characters + if (addr_hex[0] != target_hex[0]) { + break; // First character doesn't match + } + match_count++; + + if (addr_hex[1] != target_hex[1]) { + break; // Second character doesn't match + } + match_count++; + } + + match_lengths[idx] = match_count; +} +""" diff --git a/main.py b/vanity_and_balance.py similarity index 89% rename from main.py rename to vanity_and_balance.py index e171deb..2612061 100644 --- a/main.py +++ b/vanity_and_balance.py @@ -13,6 +13,7 @@ from collections import deque import aiohttp import concurrent.futures from threading import Lock +import json cuda.init() @@ -28,6 +29,8 @@ RPC_URLS = [ # Add new configuration from .env CHECK_BALANCES = os.getenv("CHECK_BALANCES", "false").lower() == "true" +if not CHECK_BALANCES: + print("🚫 Balance checking is disabled.") 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 @@ -41,6 +44,10 @@ last_balance_check = {"address": None, "balance": None, "rpc": None} pending_tasks = [] MAX_PENDING_TASKS = 10 # Adjust based on your needs +# Add near the top with other globals +FOUND_FILE = "found_addresses.txt" +BEST_MATCHES_FILE = "best_matches.json" + def get_next_web3(): global rpc_index @@ -111,8 +118,8 @@ def check_single_balance(address, private_key): 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") + with open(FOUND_FILE, "a") as f: + f.write(f"Address: {address}\nPrivate Key: {private_key}\nBalance: {Web3.from_wei(balance, 'ether')} ETH\n{'='*50}\n") return balance except Exception as e: if "429" in str(e): # Rate limit error @@ -213,10 +220,28 @@ async def generate_vanity_address(prefix, num_attempts=0): "address": address, "similarity": similarity, "private_key": priv_key_hex, + "timestamp": time.strftime("%Y-%m-%d %H:%M:%S") } - print( - f"🎯 Best match so far: {best_match['address']} ({best_match['similarity']} chars)" - ) + print(f"🎯 Best match so far: {best_match['address']} ({best_match['similarity']} chars)") + + # Save to JSON file + try: + # Load existing matches + matches = [] + try: + with open(BEST_MATCHES_FILE, 'r') as f: + matches = json.load(f) + except (FileNotFoundError, json.JSONDecodeError): + matches = [] + + # Add new match + matches.append(best_match) + + # Save updated matches + with open(BEST_MATCHES_FILE, 'w') as f: + json.dump(matches, f, indent=2) + except Exception as e: + print(f"❌ Error saving best match: {str(e)}") # Immediately check for balance if best match without waiting ethBalance = check_single_balance(address, priv_key_hex) print(f"💰 Balance: {ethBalance} ETH") @@ -385,8 +410,8 @@ async def batch_check_balances(addresses, private_keys): 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") + with open(FOUND_FILE, "a") as f: + f.write(f"Address: {address}\nPrivate Key: {private_key}\nBalance: {Web3.from_wei(balance, 'ether')} ETH\n{'='*50}\n") return results @@ -488,9 +513,12 @@ async def main(): 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") + if CHECK_BALANCES: + 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") + else: + print("🚫 Balance checking is disabled.") asyncio.run(main()) diff --git a/vanity_cuda.py b/vanity_cuda.py new file mode 100644 index 0000000..91df9b7 --- /dev/null +++ b/vanity_cuda.py @@ -0,0 +1,205 @@ +import pycuda.autoinit +import pycuda.driver as cuda +from pycuda.compiler import SourceModule +import numpy as np +import time +import json +import os +from dotenv import load_dotenv +from libs.cudacode import CUDA_CRYPTO_CODE + +# Load environment variables +load_dotenv() +PREFIX = os.getenv("PREFIX", "dEAD000000000000000042069420694206942069").lower() +BATCH_SIZE = 2**16 # 65536 addresses per batch + + +class EthereumVanityMiner: + def __init__(self): + # Initialize CUDA + cuda.init() + self.device = cuda.Device(0) + self.context = self.device.make_context() + + # Compile CUDA module + self.mod = SourceModule(CUDA_CRYPTO_CODE, no_extern_c=True) + self.kernel = self.mod.get_function("generate_and_check") + + # Prepare memory buffers + self.gpu_private_keys = cuda.mem_alloc(BATCH_SIZE * 32) + self.gpu_addresses = cuda.mem_alloc(BATCH_SIZE * 20) + self.gpu_match_lengths = cuda.mem_alloc(BATCH_SIZE * 4) + + # Prepare target prefix + prefix = PREFIX[2:] if PREFIX.startswith("0x") else PREFIX + # Debug print + print(f"Converting prefix: {prefix}") + + # Convert each pair of hex chars to a byte + self.prefix_bytes = bytearray() + for i in range(0, len(prefix), 2): + hex_pair = prefix[i:i+2] + byte_val = int(hex_pair, 16) + self.prefix_bytes.append(byte_val) + + # Debug print + print(f"Prefix bytes: {[hex(b) for b in self.prefix_bytes]}") + + self.gpu_target = cuda.mem_alloc(len(self.prefix_bytes)) + cuda.memcpy_htod(self.gpu_target, self.prefix_bytes) + + # Host buffers + self.host_match_lengths = np.zeros(BATCH_SIZE, dtype=np.int32) + self.host_private_key = np.zeros(32, dtype=np.uint8) + self.host_address = np.zeros(20, dtype=np.uint8) + + def cleanup(self): + """Clean up GPU resources""" + try: + self.gpu_private_keys.free() + self.gpu_addresses.free() + self.gpu_match_lengths.free() + self.gpu_target.free() + finally: + self.context.pop() + self.context.detach() + + def save_match( + self, address_hex, private_key_hex, match_length, raw_address, raw_private_key + ): + """Save match to JSON file""" + try: + matches = [] + if os.path.exists("cuda_matches.json"): + with open("cuda_matches.json", "r") as f: + matches = json.load(f) + + match_data = { + "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"), + "address": f"0x{address_hex}", + "private_key": private_key_hex, + "match_length": int(match_length), + "raw_address": raw_address, + "raw_private_key": raw_private_key, + } + matches.append(match_data) + + with open("cuda_matches.json", "w") as f: + json.dump(matches, f, indent=2) + except Exception as e: + print(f"\nWarning: Failed to save match to file: {e}") + + def check_batch(self, seed): + """Run one batch of address generation and checking""" + block = (256, 1, 1) + grid = ((BATCH_SIZE + block[0] - 1) // block[0], 1) + + self.kernel( + self.gpu_private_keys, + self.gpu_addresses, + self.gpu_match_lengths, + self.gpu_target, + np.int32(len(self.prefix_bytes)), + np.uint32(seed), + block=block, + grid=grid + ) + + # Get results + cuda.memcpy_dtoh(self.host_match_lengths, self.gpu_match_lengths) + best_idx = np.argmax(self.host_match_lengths) + match_length = self.host_match_lengths[best_idx] + + if match_length > 0: + temp_private_key = np.zeros(32, dtype=np.uint8) + temp_address = np.zeros(20, dtype=np.uint8) + + cuda.memcpy_dtoh(temp_private_key, self.gpu_private_keys) + cuda.memcpy_dtoh(temp_address, self.gpu_addresses) + + address_hex = ''.join(format(x, '02x') for x in temp_address) + private_key_hex = ''.join(format(x, '02x') for x in temp_private_key) + + # Verify the match + target = PREFIX[2:] if PREFIX.startswith('0x') else PREFIX + actual_match = 0 + for i, (t, a) in enumerate(zip(target, address_hex)): + if t.lower() != a.lower(): + break + actual_match += 1 + + if actual_match > 0: + return { + 'address': address_hex, + 'private_key': private_key_hex, + 'match_length': actual_match, + 'raw_address': temp_address.tobytes().hex(), + 'raw_private_key': temp_private_key.tobytes().hex() + } + return None + + def mine(self, target_score=None): + """Main mining loop""" + try: + best_match = {"match_length": 0} + start_time = time.time() + last_status_time = start_time + addresses_checked = 0 + + while True: + seed = np.random.randint(0, 2**32, dtype=np.uint32) + result = self.check_batch(seed) + + addresses_checked += BATCH_SIZE + current_time = time.time() + + # Print status every 5 seconds + if current_time - last_status_time >= 5: + elapsed_time = current_time - start_time + rate = addresses_checked / elapsed_time + print( + f"\rChecked {addresses_checked:,} addresses ({rate:,.0f}/s) - Best match: {best_match['match_length']} chars", + end="", + ) + last_status_time = current_time + + if result and result["match_length"] > best_match["match_length"]: + best_match = result + print(f"\nNew best match ({result['match_length']} chars):") + print(f"Address: 0x{result['address']}") + print(f"Private key: {result['private_key']}") + + self.save_match( + result["address"], + result["private_key"], + result["match_length"], + result["raw_address"], + result["raw_private_key"], + ) + + if target_score and result["match_length"] >= target_score: + return result + + except KeyboardInterrupt: + print("\nMining interrupted by user") + return best_match + finally: + self.cleanup() + + +if __name__ == "__main__": + try: + miner = EthereumVanityMiner() + print(f"Mining with {cuda.Device.count()} GPU(s)") + print(f"Target prefix: {PREFIX}") + print(f"Batch size: {BATCH_SIZE}") + + result = miner.mine(target_score=len(PREFIX)) + + if result: + print("\nFinal result:") + print(f"Address: 0x{result['address']}") + print(f"Private key: {result['private_key']}") + print(f"Match length: {result['match_length']}") + except Exception as e: + print(f"Error: {e}")