Created
January 25, 2025 14:30
-
-
Save snapo/ad17e54ffa50cbb3233fab38ff9a5e15 to your computer and use it in GitHub Desktop.
Bitcoin public key from scratch without any external library
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| def modinv(a, m): | |
| g, x, y = extended_gcd(a, m) | |
| return x % m if g == 1 else None | |
| def extended_gcd(a, b): | |
| if a == 0: | |
| return (b, 0, 1) | |
| else: | |
| g, y, x = extended_gcd(b % a, a) | |
| return (g, x - (b // a) * y, y) | |
| def point_add(p1, p2, p, a): | |
| if p1 is None: | |
| return p2 | |
| if p2 is None: | |
| return p1 | |
| x1, y1 = p1 | |
| x2, y2 = p2 | |
| if x1 != x2: | |
| m = ((y2 - y1) * modinv((x2 - x1) % p, p)) % p | |
| x3 = (m * m - x1 - x2) % p | |
| y3 = (m * (x1 - x3) - y1) % p | |
| else: | |
| if (y1 + y2) % p == 0: | |
| return None | |
| m = ((3 * x1 * x1 + a) * modinv((2 * y1) % p, p)) % p | |
| x3 = (m * m - 2 * x1) % p | |
| y3 = (m * (x1 - x3) - y1) % p | |
| return (x3, y3) | |
| def point_double(p, p_curve, a_curve): | |
| return point_add(p, p, p_curve, a_curve) | |
| def scalar_mult(k, p, p_curve, a_curve): | |
| result = None | |
| current = p | |
| while k > 0: | |
| if k % 2 == 1: | |
| result = point_add(result, current, p_curve, a_curve) | |
| current = point_double(current, p_curve, a_curve) | |
| k = k // 2 | |
| return result | |
| def generate_public_key(private_key, p_curve, a_curve, G): | |
| return scalar_mult(private_key, G, p_curve, a_curve) | |
| def right_rotate(n, bits): | |
| return ((n >> bits) | (n << (32 - bits))) & 0xFFFFFFFF | |
| def custom_sha256(data): | |
| h0 = 0x6a09e667 | |
| h1 = 0xbb67ae85 | |
| h2 = 0x3c6ef372 | |
| h3 = 0xa54ff53a | |
| h4 = 0x510e527f | |
| h5 = 0x9b05688c | |
| h6 = 0x1f83d9ab | |
| h7 = 0x5be0cd19 | |
| K = [ | |
| 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, | |
| 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, | |
| 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, | |
| 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, | |
| 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, | |
| 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, | |
| 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, | |
| 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 | |
| ] | |
| bit_len = len(data) * 8 | |
| data += b'\x80' | |
| while (len(data) * 8) % 512 != 448: | |
| data += b'\x00' | |
| data += bit_len.to_bytes(8, 'big') | |
| for i in range(0, len(data), 64): | |
| chunk = data[i:i+64] | |
| w = [0]*64 | |
| for t in range(16): | |
| w[t] = int.from_bytes(chunk[t*4:(t+1)*4], 'big') | |
| for t in range(16, 64): | |
| s0 = right_rotate(w[t-15], 7) ^ right_rotate(w[t-15], 18) ^ (w[t-15] >> 3) | |
| s1 = right_rotate(w[t-2], 17) ^ right_rotate(w[t-2], 19) ^ (w[t-2] >> 10) | |
| w[t] = (w[t-16] + s0 + w[t-7] + s1) & 0xFFFFFFFF | |
| a, b, c, d, e, f, g, h = h0, h1, h2, h3, h4, h5, h6, h7 | |
| for t in range(64): | |
| s1 = right_rotate(e, 6) ^ right_rotate(e, 11) ^ right_rotate(e, 25) | |
| ch = (e & f) ^ ((~e) & g) | |
| temp1 = (h + s1 + ch + K[t] + w[t]) & 0xFFFFFFFF | |
| s0 = right_rotate(a, 2) ^ right_rotate(a, 13) ^ right_rotate(a, 22) | |
| maj = (a & b) ^ (a & c) ^ (b & c) | |
| temp2 = (s0 + maj) & 0xFFFFFFFF | |
| h, g, f, e, d, c, b, a = g, f, e, (d + temp1) & 0xFFFFFFFF, c, b, a, (temp1 + temp2) & 0xFFFFFFFF | |
| h0 = (h0 + a) & 0xFFFFFFFF | |
| h1 = (h1 + b) & 0xFFFFFFFF | |
| h2 = (h2 + c) & 0xFFFFFFFF | |
| h3 = (h3 + d) & 0xFFFFFFFF | |
| h4 = (h4 + e) & 0xFFFFFFFF | |
| h5 = (h5 + f) & 0xFFFFFFFF | |
| h6 = (h6 + g) & 0xFFFFFFFF | |
| h7 = (h7 + h) & 0xFFFFFFFF | |
| return (h0.to_bytes(4, 'big') + h1.to_bytes(4, 'big') + | |
| h2.to_bytes(4, 'big') + h3.to_bytes(4, 'big') + | |
| h4.to_bytes(4, 'big') + h5.to_bytes(4, 'big') + | |
| h6.to_bytes(4, 'big') + h7.to_bytes(4, 'big')) | |
| def ripemd160(data): | |
| h0 = 0x67452301 | |
| h1 = 0xEFCDAB89 | |
| h2 = 0x98BADCFE | |
| h3 = 0x10325476 | |
| h4 = 0xC3D2E1F0 | |
| K_left = [0x00000000] * 16 + [0x5A827999] * 16 + [0x6ED9EBA1] * 16 + [0x8F1BBCDC] * 16 + [0xA953FD4E] * 16 | |
| K_right = [0x50A28BE6] * 16 + [0x5C4DD124] * 16 + [0x6D703EF3] * 16 + [0x7A6D76E9] * 16 + [0x00000000] * 16 | |
| r_left = [ | |
| 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, | |
| 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8, | |
| 3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12, | |
| 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2, | |
| 4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13 | |
| ] | |
| r_right = [ | |
| 5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12, | |
| 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2, | |
| 15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13, | |
| 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14, | |
| 12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11 | |
| ] | |
| s_left = [ | |
| 11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8, | |
| 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12, | |
| 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5, | |
| 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12, | |
| 9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6 | |
| ] | |
| s_right = [ | |
| 8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6, | |
| 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11, | |
| 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5, | |
| 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8, | |
| 8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11 | |
| ] | |
| data = bytearray(data) | |
| bit_len = len(data) * 8 | |
| data.append(0x80) | |
| while (len(data) + 8) % 64 != 0: | |
| data.append(0) | |
| data += bit_len.to_bytes(8, 'little') | |
| for i in range(0, len(data), 64): | |
| block = data[i:i+64] | |
| X = [int.from_bytes(block[j:j+4], 'little') for j in range(0, 64, 4)] | |
| a, b, c, d, e = h0, h1, h2, h3, h4 | |
| A, B, C, D, E = h0, h1, h2, h3, h4 | |
| for j in range(80): | |
| func = [lambda b, c, d: (b ^ c ^ d) & 0xFFFFFFFF, | |
| lambda b, c, d: ((b & c) | (~b & d)) & 0xFFFFFFFF, | |
| lambda b, c, d: ((b | ~c) ^ d) & 0xFFFFFFFF, | |
| lambda b, c, d: ((b & d) | (c & ~d)) & 0xFFFFFFFF, | |
| lambda b, c, d: (b ^ (c | ~d)) & 0xFFFFFFFF][j // 16] | |
| K = K_left[j] | |
| r = r_left[j] | |
| s = s_left[j] | |
| t = rol((a + func(b, c, d) + X[r] + K) & 0xFFFFFFFF, s) + e | |
| t &= 0xFFFFFFFF | |
| a, e, d, c, b = e, d, rol(c, 10), b, t | |
| func_r = [lambda B, C, D: (B ^ (C | ~D)) & 0xFFFFFFFF, | |
| lambda B, C, D: ((B & D) | (C & ~D)) & 0xFFFFFFFF, | |
| lambda B, C, D: ((B | ~C) ^ D) & 0xFFFFFFFF, | |
| lambda B, C, D: ((B & C) | (~B & D)) & 0xFFFFFFFF, | |
| lambda B, C, D: (B ^ C ^ D) & 0xFFFFFFFF][j // 16] | |
| K_r = K_right[j] | |
| r_r = r_right[j] | |
| s_r = s_right[j] | |
| T = rol((A + func_r(B, C, D) + X[r_r] + K_r) & 0xFFFFFFFF, s_r) + E | |
| T &= 0xFFFFFFFF | |
| A, E, D, C, B = E, D, rol(C, 10), B, T | |
| h0, h1, h2, h3, h4 = ( | |
| (h1 + c + D) & 0xFFFFFFFF, | |
| (h2 + d + E) & 0xFFFFFFFF, | |
| (h3 + e + A) & 0xFFFFFFFF, | |
| (h4 + a + B) & 0xFFFFFFFF, | |
| (h0 + b + C) & 0xFFFFFFFF | |
| ) | |
| return (h0.to_bytes(4, 'little') + | |
| h1.to_bytes(4, 'little') + | |
| h2.to_bytes(4, 'little') + | |
| h3.to_bytes(4, 'little') + | |
| h4.to_bytes(4, 'little')) | |
| def base58_encode(data): | |
| alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' | |
| leading_zeros = 0 | |
| for b in data: | |
| if b == 0: | |
| leading_zeros += 1 | |
| else: | |
| break | |
| num = int.from_bytes(data, 'big') | |
| encoded = '' | |
| while num > 0: | |
| num, rem = divmod(num, 58) | |
| encoded = alphabet[rem] + encoded | |
| return '1' * leading_zeros + encoded | |
| def create_bitcoin_address(public_key, compressed=True): | |
| x, y = public_key | |
| if compressed: | |
| prefix = b'\x02' if y % 2 == 0 else b'\x03' | |
| pub_bytes = prefix + x.to_bytes(32, 'big') | |
| else: | |
| prefix = b'\x04' | |
| pub_bytes = prefix + x.to_bytes(32, 'big') + y.to_bytes(32, 'big') | |
| sha_hash = custom_sha256(pub_bytes) | |
| hash160 = ripemd160(sha_hash) | |
| version = b'\x00' | |
| payload = version + hash160 | |
| checksum = custom_sha256(custom_sha256(payload))[:4] | |
| address_bytes = payload + checksum | |
| return base58_encode(address_bytes) | |
| def rol(n, k): | |
| return ((n << k) | (n >> (32 - k))) & 0xFFFFFFFF | |
| # Constants for secp256k1 curve | |
| p_curve = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F | |
| a_curve = 0 | |
| b_curve = 7 | |
| G = (0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798, | |
| 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8) | |
| n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 | |
| # Verification | |
| def run_tests(): | |
| data = b"hello world" | |
| ripemd160_hash_result = ripemd160(data) | |
| hash_hex = ripemd160_hash_result.hex() | |
| expected_hash = "98c615784ccb5fe5936fbc0cbe9dfdb408d92f0f" | |
| print("Computed RIPEMD-160:", hash_hex) | |
| print("Expected RIPEMD-160:", expected_hash) | |
| print("Match:", hash_hex == expected_hash) | |
| sha256_hash_result = custom_sha256(data) | |
| hash_hex = sha256_hash_result.hex() | |
| expected_hash = "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9" | |
| print("Computed SHA256:", hash_hex) | |
| print("Expected SHA256:", expected_hash) | |
| print("Match:", hash_hex == expected_hash) | |
| text_private_key = "hello world" | |
| private_key_bytes = custom_sha256(text_private_key.encode('utf-8')) | |
| private_key = int.from_bytes(private_key_bytes, 'big') | |
| private_key = private_key % n | |
| if private_key == 0: | |
| private_key = 1 | |
| public_key = generate_public_key(private_key, p_curve, a_curve, G) | |
| print(f"Computed Private Key: {private_key}") | |
| expected_private_key = 83814198383102558219731078260892729932246618004265700685467928187377105751529 | |
| print(f"Expected Private Key: {expected_private_key}") | |
| print("Match:", private_key == expected_private_key) | |
| print(f"Computed Public Key (uncompressed): {public_key}") | |
| expected_public_key = (105624846695532326856326728600450604588687366100141971494147850277295743338055, 23066382292353154965196454399106566849602933801539768475019884165938294161133) # Replace with the expected public key coordinates | |
| print(f"Expected Public Key (uncompressed): {expected_public_key}") | |
| print("Match:", public_key == expected_public_key) | |
| x, y = public_key | |
| prefix = b'\x02' if y % 2 == 0 else b'\x03' | |
| compressed_pubkey = prefix + x.to_bytes(32, 'big') | |
| print(f"Computed Public Key (compressed): {compressed_pubkey.hex()}") | |
| expected_compressed_pubkey = "03e9858b6e48eb93d8f27aa76b60806298c4c7dd94077ad6c3ff97c44937888647" | |
| print(f"Expected Public Key (compressed): {expected_compressed_pubkey}") | |
| print("Match:", compressed_pubkey.hex() == expected_compressed_pubkey) | |
| bitcoin_address = create_bitcoin_address(public_key) | |
| print(f"Computed Bitcoin Address: {bitcoin_address}") | |
| expected_bitcoin_address = "12q7HJP6LFwMHFWCogVzjq7BsHt8tqWfur" | |
| print(f"Expected Bitcoin Address: {expected_bitcoin_address}") | |
| print("Match:", bitcoin_address == expected_bitcoin_address) | |
| debug = 0 | |
| if debug == 1: | |
| run_tests() | |
| else: | |
| text_private_key = "My Privatekey to the Bitcoin Brainwallet (!!!!! NEVER USE FUCKING BRAINWALLETS ONLY USE TRUE RANDOM DATA!!!!!)" | |
| private_key_bytes = custom_sha256(text_private_key.encode('utf-8')) | |
| private_key = int.from_bytes(private_key_bytes, 'big') | |
| private_key = private_key % n | |
| if private_key == 0: | |
| private_key = 1 | |
| public_key = generate_public_key(private_key, p_curve, a_curve, G) | |
| print(f"Private Key: {private_key}") | |
| print(f"Public Key (uncompressed): {public_key}") | |
| x, y = public_key | |
| prefix = b'\x02' if y % 2 == 0 else b'\x03' | |
| compressed_pubkey = prefix + x.to_bytes(32, 'big') | |
| print(f"Public Key (compressed): {compressed_pubkey.hex()}") | |
| # Generate Bitcoin address using compressed public key | |
| bitcoin_address_compressed = create_bitcoin_address(public_key, compressed=True) | |
| print(f"Bitcoin Address (compressed): {bitcoin_address_compressed}") |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I had trust issues with all the python librarys that use external imports... thats why i created it....
Absolutely no fucking gurantee that this is correct, do your own research too and verify every step of the code until you are sure how the cryptography behind works.