Deriving Public Keys in Pure Python
From TeachMeBitcoin, the free encyclopedia
⏱️ 4 min read
Deriving Public Keys in Pure Python
To understand how wallets calculate public keys internally, we can write a pure Python script from scratch. This script uses only Python's standard library—no third-party dependencies like OpenSSL or cryptographically compiled packages.
🐍 Complete secp256k1 Derivation Code
Save the following code as derive_key.py and run it in your terminal. It implements the Extended Euclidean Algorithm for modular division and the Double-and-Add algorithm for elliptic curve scalar multiplication.
# secp256k1 Elliptic Curve Parameters
PRIME_MODULUS = 2**256 - 2**32 - 977
CURVE_ORDER = 115792089237316195423570985008687907852837564279074904382605163141518161494337
A = 0
B = 7
# Standard Generator Point G Coordinates
Gx = 55072459810915901509377402928376483501625479262943716618585936830388508481352
Gy = 32670510020484725357984185191420138981443653138804934149022649060592864694403
G = (Gx, Gy)
def mod_inverse(a, m):
"""
Computes the modular multiplicative inverse of a modulo m
using the Extended Euclidean Algorithm.
"""
if a == 0:
return 0
lm, hm = 1, 0
low, high = a % m, m
while low > 1:
ratio = high // low
nm, nh = hm - ratio * lm, high - ratio * low
lm, hm, low, high = nm, nh, nh, low
return lm % m
def point_add(P, Q):
"""Adds two points on the secp256k1 elliptic curve."""
if P is None: return Q
if Q is None: return P
x1, y1 = P
x2, y2 = Q
if x1 == x2 and y1 != y2:
return None # Point at infinity (null element)
if x1 == x2:
# Point Doubling formula (tangent line)
slope = (3 * x1 * x1 + A) * mod_inverse(2 * y1, PRIME_MODULUS) % PRIME_MODULUS
else:
# Point Addition formula (secant line)
slope = (y2 - y1) * mod_inverse(x2 - x1, PRIME_MODULUS) % PRIME_MODULUS
x3 = (slope * slope - x1 - x2) % PRIME_MODULUS
y3 = (slope * (x1 - x3) - y1) % PRIME_MODULUS
return (x3, y3)
def scalar_multiply(scalar, point):
"""Multiplies a point by a scalar using Double-and-Add."""
result = None
doubler = point
k = scalar % CURVE_ORDER # Ensure key is within the bounds
while k > 0:
if k & 1:
result = point_add(result, doubler)
doubler = point_add(doubler, doubler)
k >>= 1
return result
def format_public_key(point, compressed=True):
"""Formats the elliptic curve point into standard hex representations."""
x_hex = f"{point[0]:064x}"
y_hex = f"{point[1]:064x}"
if compressed:
# Determine parity (even/odd) of the y-coordinate
prefix = "02" if point[1] % 2 == 0 else "03"
return prefix + x_hex
else:
# Standard uncompressed format
return "04" + x_hex + y_hex
# --- EXAMPLE DERIVATION ---
if __name__ == "__main__":
# 1. Choose a sample private key scalar
private_key = 0x1e994230c5a550a455a477461d4e2400bc976541705c93541484ff947ef3e906
print(f"Private Key (Hex): {private_key:064x}\n")
# 2. Derive the coordinate point K = k * G
public_point = scalar_multiply(private_key, G)
print(f"Derived Elliptic Curve Point Coordinates:")
print(f"X: {public_point[0]:064x}")
print(f"Y: {public_point[1]:064x}\n")
# 3. Format into blockchain-ready outputs
uncompressed_hex = format_public_key(public_point, compressed=False)
compressed_hex = format_public_key(public_point, compressed=True)
print(f"Uncompressed Public Key (65 bytes):\n{uncompressed_hex}\n")
print(f"Compressed Public Key (33 bytes):\n{compressed_hex}")
🛠️ Step-by-Step Code Walkthrough
- Extended Euclidean Division (
mod_inverse): In elliptic curve algebra, we cannot perform floating-point division (like/). Instead, we must divide integers over our finite field by calculating the multiplicative inverse of the denominator modulo $p$, and multiplying it by the numerator. - Doubling vs. Adding (
point_add): The point addition function automatically checks whether $P = Q$. If so, it uses the tangent slope equation $\lambda = \frac{3x_P^2}{2y_P}$ for doubling. If $P \neq Q$, it uses the standard slope equation $\lambda = \frac{y_Q - y_P}{x_Q - x_P}$ for addition. - Logarithmic Loop (
scalar_multiply): Double-and-Add reads the binary bits of our private key. If the bit is1, it adds the current state of the doubled base point to the accumulator. It then doubles the base point in preparation for the next bit position, executing the complete calculation in microseconds!
☕ Help support TeachMeBitcoin
TeachMeBitcoin is an ad-free, open-source educational repository curated by a passionate team of Bitcoin researchers and educators for public benefit. If you found our articles helpful, please consider supporting our hosting and ongoing content updates with a clean donation:
Ethereum:
0x578417C51783663D8A6A811B3544E1f779D39A85
Bitcoin:
bc1q77k9e95rn669kpzyjr8ke9w95zhk7pa5s63qzz
Solana:
4ycT2ayqeMucixj3wS8Ay8Tq9NRDYRPKYbj3UGESyQ4J