#!/usr/bin/env python3 # Description: Bitcoin Miner script using GPU and CPU. # Usage: python3 miner-btc.py # Author: Justin Oros # Source: https://github.com/JustinOros import json import hashlib import struct import time import os import requests from datetime import datetime from multiprocessing import Process, Queue, cpu_count from binascii import unhexlify, hexlify CONFIG_FILE = "miner-btc.json" def get_default_config(): """Generate default configuration with dynamic values""" return { "pool_enabled": False, "pool_url": "stratum+tcp://pool.example.com:3333", "pool_username": "your_bitcoin_address", "pool_password": "x", "solo_rpc_url": "http://127.0.0.1:8332", "solo_rpc_user": "bitcoinrpc", "solo_rpc_password": "your_rpc_password", "coinbase_address": "your_bitcoin_address_for_rewards", "threads": cpu_count(), "scan_time": 60, "update_interval": 5 } def load_or_create_config(): """Load config from file or create default config""" if not os.path.exists(CONFIG_FILE): print(f"[INFO] Config file not found. Creating {CONFIG_FILE}...") default_config = get_default_config() with open(CONFIG_FILE, 'w') as f: json.dump(default_config, f, indent=4) print(f"\n{'='*70}") print(f" āœ… Created {CONFIG_FILE}") print(f"{'='*70}") print("\nPlease configure the following settings:") print("\nšŸ“ FOR SOLO MINING:") print(" • solo_rpc_user: Your Bitcoin Core RPC username") print(" • solo_rpc_password: Your Bitcoin Core RPC password") print(" • coinbase_address: Your Bitcoin address (where rewards go)") print("\nšŸ“ FOR POOL MINING:") print(" • Set pool_enabled: true") print(" • pool_url: Your mining pool's stratum URL") print(" • pool_username: Usually your Bitcoin address") print(" • pool_password: Usually 'x' or pool-specific password") print("\nšŸ“ OPTIONAL:") print(f" • threads: Number of CPU cores to use (default: {default_config['threads']} detected)") print(" • update_interval: Seconds between work updates (default: 5)") print(f"\n{'='*70}") print("Edit miner-btc.json and run this script again.") print(f"{'='*70}\n") return None with open(CONFIG_FILE, 'r') as f: config = json.load(f) print(f"[INFO] Loaded configuration from {CONFIG_FILE}") return config def sha256d(data): """Double SHA-256 hash (Bitcoin's mining algorithm)""" return hashlib.sha256(hashlib.sha256(data).digest()).digest() def bits_to_target(bits): """Convert compact bits representation to full target""" exp = bits >> 24 mant = bits & 0xffffff target = mant * (1 << (8 * (exp - 3))) return target def target_to_bits(target): """Convert target to compact bits representation""" # Find the most significant byte s = hex(target)[2:].rstrip('L') if len(s) % 2: s = '0' + s s = bytes.fromhex(s) # Find first non-zero byte for i, b in enumerate(s): if b != 0: break s = s[i:] # Take first 3 bytes if len(s) > 3: mantissa = struct.unpack('>I', b'\x00' + s[:3])[0] exponent = len(s) else: mantissa = struct.unpack('>I', b'\x00' * (4 - len(s)) + s)[0] exponent = len(s) return (exponent << 24) | mantissa def get_work_solo(config): """Get mining work from Bitcoin Core RPC (solo mining)""" headers = {'content-type': 'application/json'} # First, get block template payload = { "jsonrpc": "2.0", "id": "macminer", "method": "getblocktemplate", "params": [{"rules": ["segwit"]}] } try: response = requests.post( config['solo_rpc_url'], auth=(config['solo_rpc_user'], config['solo_rpc_password']), headers=headers, json=payload, timeout=10 ) if response.status_code != 200: print(f"[ERROR] RPC returned status {response.status_code}: {response.text}") return None result = response.json() if 'error' in result and result['error']: print(f"[ERROR] RPC error: {result['error']}") return None if 'result' not in result: print(f"[ERROR] No result in response: {result}") return None template = result['result'] # Build coinbase transaction coinbase_tx = build_coinbase_transaction(template, config['coinbase_address']) # Calculate merkle root with coinbase transactions = [coinbase_tx] + [unhexlify(tx['data']) for tx in template.get('transactions', [])] merkle_root = calculate_merkle_root(transactions) work = { 'version': template['version'], 'previousblockhash': template['previousblockhash'], 'merkleroot': hexlify(merkle_root).decode(), 'time': template['curtime'], 'bits': int(template['bits'], 16), 'height': template['height'], 'target': bits_to_target(int(template['bits'], 16)), 'coinbase_tx': hexlify(coinbase_tx).decode(), 'transactions': template.get('transactions', []) } return work except requests.exceptions.ConnectionError: print(f"[ERROR] Cannot connect to Bitcoin Core at {config['solo_rpc_url']}") print("[ERROR] Make sure Bitcoin Core is running with RPC enabled") return None except Exception as e: print(f"[ERROR] Exception getting work: {e}") import traceback traceback.print_exc() return None def build_coinbase_transaction(template, address): """Build a coinbase transaction""" # Simplified coinbase - in production, this needs proper scriptSig coinbase_script = ( struct.pack(' 1: if len(hashes) % 2 == 1: hashes.append(hashes[-1]) hashes = [sha256d(hashes[i] + hashes[i+1]) for i in range(0, len(hashes), 2)] return hashes[0] def build_block_header(work, nonce, extra_nonce=0): """Build block header for mining""" header = b'' header += struct.pack(' 0 else 0 stats_queue.put({ 'worker_id': worker_id, 'hashes': hashes, 'hashrate': hashrate }) # Check stop flag if stop_flag.value: break def display_stats(stats_queue, num_workers, config, work_info): """Display mining statistics""" worker_stats = {i: {'hashes': 0, 'hashrate': 0} for i in range(num_workers)} last_update = time.time() while True: try: # Update worker stats while not stats_queue.empty(): stat = stats_queue.get_nowait() worker_id = stat['worker_id'] worker_stats[worker_id] = { 'hashes': stat['hashes'], 'hashrate': stat['hashrate'] } # Update display every second if time.time() - last_update >= 1.0: # Calculate total hashrate total_hashrate = sum(w['hashrate'] for w in worker_stats.values()) total_hashes = sum(w['hashes'] for w in worker_stats.values()) # Clear screen and display stats os.system('clear' if os.name == 'posix' else 'cls') print("=" * 70) print(" MacMiner - Bitcoin Miner for Apple Silicon") print("=" * 70) print(f"Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") print(f"Mode: {'Pool Mining' if config['pool_enabled'] else 'Solo Mining'}") if work_info['height']: print(f"Block Height: {work_info['height']}") print(f"Difficulty: {work_info['difficulty']:,.2f}") print(f"Workers: {num_workers}") print(f"Total Hashes: {total_hashes:,}") print(f"Total Hashrate: {total_hashrate/1000:,.2f} KH/s ({total_hashrate:,.0f} H/s)") print("-" * 70) for worker_id in sorted(worker_stats.keys()): stats = worker_stats[worker_id] print(f"Worker {worker_id:2d}: {stats['hashrate']/1000:>8.2f} KH/s | {stats['hashes']:>12,} hashes") print("=" * 70) print("Press Ctrl+C to stop mining") last_update = time.time() time.sleep(0.1) except KeyboardInterrupt: break except Exception as e: print(f"[ERROR] Stats display error: {e}") time.sleep(1) def main(): """Main mining loop""" print("=" * 70) print(" MacMiner - Bitcoin Miner for Apple Silicon") print("=" * 70) # Load configuration config = load_or_create_config() if config is None: return # Display configuration print(f"\n{'='*70}") print(f" MACMINER CONFIGURATION") print(f"{'='*70}") print(f"Mining Mode: {'🌊 Pool Mining' if config['pool_enabled'] else 'ā›ļø Solo Mining'}") print(f"Threads: {config['threads']} CPU cores") if config['pool_enabled']: print(f"Pool URL: {config['pool_url']}") print(f"Pool Username: {config['pool_username']}") else: print(f"RPC URL: {config['solo_rpc_url']}") print(f"Coinbase Address: {config.get('coinbase_address', 'NOT SET')}") print(f"{'='*70}\n") if config['pool_enabled']: print(f"\n[ERROR] Pool mining not yet implemented.") print("Set 'pool_enabled': false in config for solo mining.") return # Test connection to Bitcoin Core print(f"\n[INFO] Testing connection to Bitcoin Core...") test_work = get_work_solo(config) if test_work is None: print("\n" + "="*70) print(" āš ļø BITCOIN CORE NOT DETECTED") print("="*70) print("\nMacMiner requires Bitcoin Core to be running and synchronized.") print("\nOption 1 - Install Bitcoin Core locally:") print(" 1. Download Bitcoin Core from: https://bitcoin.org") print(" 2. Install and run Bitcoin Core") print(" 3. Wait for blockchain to synchronize (this may take several days)") print(" 4. Configure RPC in ~/Library/Application Support/Bitcoin/bitcoin.conf:") print(" server=1") print(" rpcuser=yourusername") print(" rpcpassword=yourpassword") print(" rpcallowip=127.0.0.1") print(" 5. Restart Bitcoin Core") print(" 6. Update miner-btc.json with your RPC credentials") print(" 7. Run miner-btc.py again") print("\nOption 2 - Connect to remote Bitcoin Core:") print(" 1. Update 'solo_rpc_url' in miner-btc.json with remote host IP/hostname") print(" Example: \"http://192.168.1.100:8332\"") print(" 2. Ensure remote Bitcoin Core has rpcallowip configured for your IP") print(" 3. Update RPC credentials in miner-btc.json") print(" 4. Run miner-btc.py again") print("\n" + "="*70) return print(f"[SUCCESS] Connected! Block height: {test_work['height']}") print(f"[INFO] Current difficulty: {bits_to_target(test_work['bits']) / (2**224):,.2f}") # Initialize queues and shared state work_queue = Queue() result_queue = Queue() stats_queue = Queue() from multiprocessing import Value stop_flag = Value('i', 0) work_info = {'height': test_work['height'], 'difficulty': bits_to_target(test_work['bits']) / (2**224)} # Start worker processes num_workers = config['threads'] workers = [] print(f"\n[INFO] Starting {num_workers} mining workers...") for i in range(num_workers): p = Process(target=mine_worker, args=(i, config, work_queue, result_queue, stats_queue, stop_flag)) p.start() workers.append(p) time.sleep(1) # Start stats display in separate process stats_process = Process(target=display_stats, args=(stats_queue, num_workers, config, work_info)) stats_process.start() # Distribute initial work for _ in range(num_workers): work_queue.put(test_work) try: last_update = time.time() # Main loop - get new work and check for results while True: # Get new work periodically if time.time() - last_update >= config['update_interval']: new_work = get_work_solo(config) if new_work: work_info['height'] = new_work['height'] work_info['difficulty'] = bits_to_target(new_work['bits']) / (2**224) # Distribute to all workers for _ in range(num_workers * 2): # Extra work items in queue work_queue.put(new_work) last_update = time.time() # Check for found blocks while not result_queue.empty(): result = result_queue.get() if result['found']: # Submit the block submit_work_solo(config, result['work'], result['nonce']) time.sleep(1) except KeyboardInterrupt: print("\n\n[INFO] Stopping miners...") stop_flag.value = 1 # Wait for workers to stop for w in workers: w.join(timeout=3) if w.is_alive(): w.terminate() stats_process.terminate() stats_process.join(timeout=1) print("[INFO] Mining stopped. Goodbye!") if __name__ == "__main__": main()