TeachMeBitcoin

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

  1. 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.
  2. 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.
  3. Logarithmic Loop (scalar_multiply): Double-and-Add reads the binary bits of our private key. If the bit is 1, 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
Address copied to clipboard!