Skip to content

Instantly share code, notes, and snippets.

@snapo
Created January 25, 2025 14:30
Show Gist options
  • Select an option

  • Save snapo/ad17e54ffa50cbb3233fab38ff9a5e15 to your computer and use it in GitHub Desktop.

Select an option

Save snapo/ad17e54ffa50cbb3233fab38ff9a5e15 to your computer and use it in GitHub Desktop.
Bitcoin public key from scratch without any external library
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}")
@snapo
Copy link
Author

snapo commented Jan 25, 2025

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment