├── .gitignore ├── requirements.txt ├── README.md └── main.py /.gitignore: -------------------------------------------------------------------------------- 1 | myenv/ 2 | __pycache__/ 3 | .vscode -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # Cryptography primitives (RIPEMD‑160 implementation) 2 | pycryptodome>=3.20 3 | 4 | # Human‑readable Base58 / Base58Check encoder‑decoder 5 | base58>=2.1 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bitcoin Key Generator 2 | 3 | A Python-based Bitcoin key generation tool that implements elliptic curve cryptography for generating Bitcoin private keys, public keys, and addresses. This tool supports multiple address formats including legacy P2PKH, nested SegWit P2SH-P2WPKH, and native SegWit P2WPKH (bech32). 4 | 5 | ## Features 6 | 7 | - Generate Bitcoin private keys and corresponding public keys using secp256k1 elliptic curve 8 | - Support for multiple Bitcoin address formats: 9 | - Legacy P2PKH addresses (starting with '1') 10 | - Nested SegWit P2SH-P2WPKH addresses (starting with '3') 11 | - Native SegWit P2WPKH bech32 addresses (starting with 'bc1') 12 | - Parallel processing for bulk key generation 13 | - Verbose mode for debugging elliptic curve operations 14 | - Convert compressed public keys to multiple address formats 15 | - Export results to CSV format 16 | 17 | ## Requirements 18 | 19 | - Python 3.6+ 20 | - `pycryptodome` - For RIPEMD-160 hashing 21 | - `base58` - For Base58 encoding/decoding 22 | 23 | ## Installation 24 | 25 | ```bash 26 | pip install pycryptodome base58 27 | ``` 28 | 29 | ## Usage 30 | 31 | ### Generate a Single Key 32 | 33 | Generate a Bitcoin key pair from a specific private key: 34 | 35 | ```bash 36 | python bitcoin_keygen.py --privatekey 12345 37 | ``` 38 | 39 | Output: 40 | ``` 41 | private_key_decimal: 12345 42 | public_key_x: 89565891926547004231252920425935692360644145829622209833684329913297188986597 43 | public_key_y: 12158399299693830322967808612713398636155367887041628176798871954788371653930 44 | bitcoin_address: 1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH 45 | compressed_pubkey: 0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798 46 | ``` 47 | 48 | ### Convert Public Key to Addresses 49 | 50 | Convert a compressed public key to all supported address formats: 51 | 52 | ```bash 53 | python bitcoin_keygen.py --pubkey 0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798 54 | ``` 55 | 56 | Output: 57 | ``` 58 | compressed_pubkey: 0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798 59 | P2PKH (legacy) : 1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH 60 | P2SH-P2WPKH : 3JvL6Ymt8MVWiCNHC7oWU6nLeHNJKLZGLN 61 | P2WPKH (bech32) : bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 62 | ``` 63 | 64 | ### Bulk Key Generation 65 | 66 | Generate multiple keys in parallel: 67 | 68 | ```bash 69 | python bitcoin_keygen.py --start 1 --end 10000 --output keys.csv --workers 4 70 | ``` 71 | 72 | Parameters: 73 | - `--start`: Starting private key value 74 | - `--end`: Ending private key value (exclusive) 75 | - `--output`: Output CSV file (default: output.csv) 76 | - `--workers`: Number of parallel workers (default: CPU count) 77 | - `--chunk-size`: Keys per chunk (default: 1000) 78 | - `--no-y`: Exclude Y coordinate from output 79 | 80 | ### Verbose Mode 81 | 82 | Enable detailed logging of elliptic curve operations: 83 | 84 | ```bash 85 | python bitcoin_keygen.py --privatekey 12345 --verbose 86 | ``` 87 | 88 | ## Output Format 89 | 90 | The CSV output includes the following columns: 91 | - `private_key`: Private key in decimal format 92 | - `public_key_x`: X coordinate of the public key 93 | - `public_key_y`: Y coordinate of the public key (unless --no-y is used) 94 | - `address`: Bitcoin P2PKH address 95 | 96 | ## Technical Details 97 | 98 | ### Elliptic Curve Parameters 99 | 100 | This tool uses the secp256k1 elliptic curve with the following parameters: 101 | - Field prime: `p = 2^256 - 2^32 - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1` 102 | - Generator point: 103 | - X: `55066263022277343669578718895168534326250603453777594175500187360389116729240` 104 | - Y: `32670510020758816978083085130507043184471273380659243275938904335757337482424` 105 | 106 | ### Address Generation Process 107 | 108 | 1. Generate private key (random 256-bit number) 109 | 2. Calculate public key using elliptic curve point multiplication 110 | 3. Compress public key (33 bytes) 111 | 4. Generate addresses: 112 | - **P2PKH**: SHA256(pubkey) → RIPEMD160 → Base58Check with version 0x00 113 | - **P2SH-P2WPKH**: Create witness script → SHA256 → RIPEMD160 → Base58Check with version 0x05 114 | - **P2WPKH**: SHA256(pubkey) → RIPEMD160 → Bech32 encode with HRP 'bc' 115 | 116 | ## Performance 117 | 118 | The tool uses parallel processing for bulk generation: 119 | - Typical performance: 10,000-50,000 keys/second (depending on CPU) 120 | - Memory efficient streaming to CSV file 121 | - Configurable chunk size for optimization 122 | 123 | ## Security Considerations 124 | 125 | **WARNING**: This tool is for educational and testing purposes only. 126 | 127 | - **DO NOT** use sequentially generated private keys for real Bitcoin storage 128 | - **DO NOT** use predictable or low-entropy private keys 129 | - For production use, always use cryptographically secure random number generation 130 | - Private keys should be kept secret and stored securely 131 | - Consider using hardware wallets for significant Bitcoin holdings 132 | 133 | ## Legal Disclaimer 134 | 135 | This software is provided "as is", without warranty of any kind, express or implied. The authors are not responsible for any loss of funds or other damages arising from the use of this software. Users are responsible for understanding Bitcoin security best practices and the risks involved in cryptocurrency management. 136 | 137 | ## License 138 | 139 | This project is released under the MIT License. See LICENSE file for details. 140 | 141 | ## Contributing 142 | 143 | Contributions are welcome! Please feel free to submit pull requests or open issues for bugs and feature requests. 144 | 145 | ## Acknowledgments 146 | 147 | This implementation is based on the Bitcoin protocol specifications and uses standard cryptographic libraries for hash functions and encoding. -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import argparse 3 | from concurrent.futures import ProcessPoolExecutor, as_completed 4 | from hashlib import sha256 5 | from Crypto.Hash import RIPEMD 6 | import base58 7 | import time 8 | 9 | p = 2**256 - 2**32 - 2**9 - 2**8 - 2**7 - 2**6 - 2**4 - 1 10 | genX = 55066263022277343669578718895168534326250603453777594175500187360389116729240 11 | genY = 32670510020758816978083085130507043184471273380659243275938904335757337482424 12 | CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l' 13 | 14 | def _bech32_polymod(vals): 15 | g = [0x3b6a57b2,0x26508e6d,0x1ea119fa,0x3d4233dd,0x2a1462b3] 16 | chk = 1 17 | for v in vals: 18 | b = chk >> 25 19 | chk = (chk & 0x1ffffff) << 5 ^ v 20 | for i in range(5): 21 | if (b >> i) & 1: 22 | chk ^= g[i] 23 | return chk 24 | 25 | def _hrp_expand(hrp): 26 | return [ord(x) >> 5 for x in hrp] + [0] + [ord(x) & 31 for x in hrp] 27 | 28 | def _convert_bits(data, from_bits, to_bits, pad=True): 29 | acc = 0; bits = 0; ret = [] 30 | maxv = (1 << to_bits) - 1 31 | for b in data: 32 | acc = (acc << from_bits) | b 33 | bits += from_bits 34 | while bits >= to_bits: 35 | bits -= to_bits 36 | ret.append((acc >> bits) & maxv) 37 | if pad and bits: 38 | ret.append((acc << (to_bits - bits)) & maxv) 39 | return ret 40 | 41 | def bech32_encode(hrp, witver, witprog): 42 | data = [witver] + _convert_bits(witprog, 8, 5) 43 | values = _hrp_expand(hrp) + data 44 | polymod = _bech32_polymod(values + [0,0,0,0,0,0]) ^ 1 45 | checksum = [(polymod >> 5*(5-i)) & 31 for i in range(6)] 46 | return hrp + '1' + ''.join(CHARSET[d] for d in data + checksum) 47 | 48 | def pubkey_to_addresses(pubkey_hex): 49 | """Return (P2PKH, P2SH‑P2WPKH, P2WPKH) for a compressed pubkey hex.""" 50 | pk = bytes.fromhex(pubkey_hex) 51 | h160 = RIPEMD.new(sha256(pk).digest()).digest() 52 | 53 | # legacy P2PKH (version 0x00) 54 | p2pkh = base58.b58encode_check(b'\x00' + h160).decode() 55 | 56 | # nested SegWit P2SH‑P2WPKH (redeemScript = 0x0014{h160}) 57 | redeem = b'\x00\x14' + h160 58 | h160_r = RIPEMD.new(sha256(redeem).digest()).digest() 59 | p2sh_p2w = base58.b58encode_check(b'\x05' + h160_r).decode() 60 | 61 | # native SegWit P2WPKH (bech32 HRP 'bc', version 0) 62 | bech = bech32_encode('bc', 0, h160) 63 | 64 | return p2pkh, p2sh_p2w, bech 65 | 66 | def mmi(x, p=p): 67 | return pow(x, -1, p) 68 | 69 | def doublepoint(x, y): 70 | slope = (3 * x * x * mmi(2 * y)) % p 71 | return ((slope * slope - 2 * x) % p, (slope * (x - (slope * slope - 2 * x) % p) - y) % p) 72 | 73 | def addpoint(x1, y1, x2, y2): 74 | if x1 == x2 and y1 == y2: 75 | return doublepoint(x1, y1) 76 | slope = ((y1 - y2) * mmi(x1 - x2)) % p 77 | newx = (slope * slope - x1 - x2) % p 78 | newy = (slope * (x1 - newx) - y1) % p 79 | return newx, newy 80 | 81 | def multiplypoint(k, gx=genX, gy=genY): 82 | x, y = gx, gy 83 | for bit in bin(k)[3:]: 84 | x, y = doublepoint(x, y) 85 | if bit == '1': 86 | x, y = addpoint(x, y, gx, gy) 87 | return x, y 88 | 89 | def generate(privatekeydecimal): 90 | pubx, puby = multiplypoint(privatekeydecimal) 91 | priv_bytes = privatekeydecimal.to_bytes(32, 'big') 92 | wif = base58.b58encode_check(b'\x80' + priv_bytes + b'\x01').decode() 93 | prefix = b'\x02' if (puby & 1)==0 else b'\x03' 94 | sha_digest = sha256(prefix + pubx.to_bytes(32, 'big')).digest() 95 | ripe = RIPEMD.new(sha_digest).digest() 96 | addr = base58.b58encode_check(b'\x00' + ripe).decode() 97 | # Return Y coordinate as well 98 | return privatekeydecimal, pubx, puby, addr 99 | 100 | def mmi_verbose(x, p=p): 101 | inv = pow(x, -1, p) 102 | logging.debug(f"mmi({x}) → {inv}") 103 | return inv 104 | 105 | def doublepoint_verbose(x, y): 106 | logging.debug(f"doublepoint start: ({x}, {y})") 107 | slope = (3 * x * x * mmi_verbose(2 * y)) % p 108 | newx = (slope * slope - 2 * x) % p 109 | newy = (slope * (x - newx) - y) % p 110 | logging.debug(f"doublepoint result: slope={slope}, new=({newx},{newy})") 111 | return newx, newy 112 | 113 | def addpoint_verbose(x1, y1, x2, y2): 114 | logging.debug(f"addpoint start: P1=({x1},{y1}), P2=({x2},{y2})") 115 | if x1 == x2 and y1 == y2: 116 | return doublepoint_verbose(x1, y1) 117 | slope = ((y1 - y2) * mmi_verbose(x1 - x2)) % p 118 | newx = (slope * slope - x1 - x2) % p 119 | newy = (slope * (x1 - newx) - y1) % p 120 | logging.debug(f"addpoint result: slope={slope}, new=({newx},{newy})") 121 | return newx, newy 122 | 123 | def multiplypoint_verbose(k, gx=genX, gy=genY): 124 | logging.info(f"Starting multiplypoint_verbose(k={k})") 125 | x, y = gx, gy 126 | print(bin(k)[3:]) 127 | for idx, bit in enumerate(bin(k)[3:], start=1): 128 | print('idx, bit: ', idx, bit) 129 | logging.debug(f"[{idx}] doubling ({x},{y})") 130 | x, y = doublepoint_verbose(x, y) 131 | if bit == '1': 132 | logging.debug(f"[{idx}] adding generator ({gx},{gy})") 133 | x, y = addpoint_verbose(x, y, gx, gy) 134 | logging.info(f"Result of multiplypoint_verbose: ({x},{y})") 135 | return x, y 136 | 137 | def generate_verbose(privatekeydecimal): 138 | pubx, puby = multiplypoint_verbose(privatekeydecimal) 139 | logging.debug(f"Public key coords: x={pubx}, y={puby}") 140 | priv_bytes = privatekeydecimal.to_bytes(32, 'big') 141 | wif = base58.b58encode_check(b'\x80' + priv_bytes + b'\x01').decode() 142 | prefix = b'\x02' if (puby & 1)==0 else b'\x03' 143 | sha_digest = sha256(prefix + pubx.to_bytes(32, 'big')).digest() 144 | ripe = RIPEMD.new(sha_digest).digest() 145 | addr = base58.b58encode_check(b'\x00' + ripe).decode() 146 | logging.info(f"WIF={wif}, address={addr}") 147 | # Return Y coordinate as well 148 | return privatekeydecimal, pubx, puby, addr 149 | 150 | def chunked_range(start, end, chunk_size): 151 | for i in range(start, end, chunk_size): 152 | yield (i, min(i + chunk_size, end)) 153 | 154 | def process_chunk(chunk, gen_func): 155 | s, e = chunk 156 | out = [] 157 | for i in range(s, e): 158 | result = gen_func(i) 159 | if len(result) == 4: # New format with Y 160 | priv, pubx, puby, addr = result 161 | out.append(f"{priv},{pubx},{puby},{addr}\n") 162 | else: # Old format without Y (backward compatibility) 163 | priv, pubx, addr = result 164 | out.append(f"{priv},{pubx},{addr}\n") 165 | return "".join(out) 166 | 167 | def process_range_parallel(start, end, output, workers, chunk_size, gen_func, include_y=True): 168 | total = end - start 169 | chunks = list(chunked_range(start, end, chunk_size)) 170 | t0 = time.time() 171 | 172 | # Write header if file is new 173 | try: 174 | with open(output, 'x') as f: 175 | if include_y: 176 | f.write("private_key,public_key_x,public_key_y,address\n") 177 | else: 178 | f.write("private_key,public_key_x,address\n") 179 | except FileExistsError: 180 | pass 181 | 182 | with ProcessPoolExecutor(max_workers=workers) as exe, open(output, 'a', buffering=1<<20) as f: 183 | futures = {exe.submit(process_chunk, c, gen_func): c for c in chunks} 184 | done = 0 185 | for fut in as_completed(futures): 186 | data = fut.result() 187 | f.write(data) 188 | done += (futures[fut][1] - futures[fut][0]) 189 | logging.info(f"Progress: {done}/{total}") 190 | elapsed = time.time() - t0 191 | print(f"Completed {total} keys in {elapsed:.2f}s ⇒ {total/elapsed:.2f} keys/sec") 192 | 193 | def main(): 194 | parser = argparse.ArgumentParser("Bitcoin keygen") 195 | parser.add_argument("--privatekey", type=int) 196 | parser.add_argument("--start", type=int) 197 | parser.add_argument("--end", type=int) 198 | parser.add_argument("--output", type=str, default="output.csv") 199 | parser.add_argument("--workers", type=int, default=None) 200 | parser.add_argument("--chunk-size", type=int, default=1_000) 201 | parser.add_argument("-v", "--verbose", action="store_true", help="log every EC step (slower!)") 202 | parser.add_argument("--no-y", action="store_true", help="Don't include Y coordinate in output") 203 | parser.add_argument("--pubkey", type=str, help="33‑byte compressed pubkey hex → print 3 addresses") 204 | 205 | args = parser.parse_args() 206 | level = logging.DEBUG if args.verbose else logging.INFO 207 | logging.basicConfig(level=level, format="%(asctime)s [%(levelname)s] %(message)s") 208 | 209 | gen_func = generate_verbose if args.verbose else generate 210 | 211 | if args.privatekey is not None: 212 | result = gen_func(args.privatekey) 213 | if len(result) == 4: 214 | priv, pubx, puby, addr = result 215 | print("private_key_decimal:", priv) 216 | print("public_key_x: ", pubx) 217 | print("public_key_y: ", puby) 218 | print("bitcoin_address: ", addr) 219 | 220 | # Also show compressed public key format 221 | prefix = '02' if (puby & 1) == 0 else '03' 222 | compressed = prefix + format(pubx, '064x') 223 | print("compressed_pubkey: ", compressed) 224 | elif args.pubkey: 225 | p2pkh, p2sh, bech = pubkey_to_addresses(args.pubkey) 226 | print("compressed_pubkey:", args.pubkey) 227 | print("P2PKH (legacy) :", p2pkh) 228 | print("P2SH‑P2WPKH :", p2sh) 229 | print("P2WPKH (bech32) :", bech) 230 | return 231 | elif args.start is not None and args.end is not None: 232 | process_range_parallel(args.start, args.end, args.output, args.workers, args.chunk_size, gen_func, not args.no_y) 233 | else: 234 | parser.print_help() 235 | 236 | if __name__ == "__main__": 237 | main() --------------------------------------------------------------------------------