Search code examples
pythoncryptographybitcoinpublic-key

How to generate public key from private key in python3 with out importing any module


When I run the command btc_address_dump "0x0000000000000000000000000000000000000000000000000000000000000001" the outputed public key is 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 then when I run the command btc_address_dump "0x0000000000000000000000000000000000000000000000000000000000000002" the outputed public key is 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a but when I use each of the above private key to test the below python3 script the output of the private key 0x0000000000000000000000000000000000000000000000000000000000000001 = 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 then the public key I got from the script when I input the private key 0x0000000000000000000000000000000000000000000000000000000000000002 is 043cea112c8dbb47dbffdd56023c41ccf7f1f5a0ef8786d3b0f3f01871a6c8d557d0f0a997646d7e87ba593fed9f320dd1739c722c7b357d6586e9fb73f578c0e0

The script I used is as follow:

# Define the elliptic curve parameters
p = 2**256 - 2**32 - 977
a = 0
b = 7
Gx = 55066263022277343669578718895168534326250603453777594175500187360389116729240
Gy = 32670510020758816978083085130507043184471273380659243275938904335757337482424

# Define the private key
private_key = 0x0000000000000000000000000000000000000000000000000000000000000002

# Convert the private key to decimal
private_key_decimal = int(private_key)

# Perform elliptic curve multiplication
Qx = Gx
Qy = Gy
for bit in bin(private_key_decimal)[3:]:
    Qx = (Qx**2 - Qy**2) % p
    Qy = (2*Qx*Qy) % p

    if bit == '1':
        Qx = (Qx*Gx - Qy*Gy) % p
        Qy = (Qx*Gy + Qy*Gx) % p

# Convert the x and y coordinates to hexadecimal
public_key_hex = '04' + format(Qx, '064x') + format(Qy, '064x')

# Print the uncompressed public key
print("Uncompressed Public Key:", public_key_hex)

My question is why does the script outputs the wrong public key for 0x0000000000000000000000000000000000000000000000000000000000000002


Solution

  • The following script produces the expected results:

    curve={
        'name': 'secp256k1',
        'p': 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f,
        'a': 0,
        'b': 7,
        'g': (0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798, 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8),
        'n': 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141,
        'h': 1
    }
    
    # Modular arithmetic ##########################################################
    
    def inverse_mod(k, p):
        """Returns the inverse of k modulo p.
    
        This function returns the only integer x such that (x * k) % p == 1.
    
        k must be non-zero and p must be a prime.
        """
        if k == 0:
        raise ZeroDivisionError('division by zero')
    
        if k < 0:
        # k ** -1 = p - (-k) ** -1  (mod p)
        return p - inverse_mod(-k, p)
    
        # Extended Euclidean algorithm.
        s, old_s = 0, 1
        t, old_t = 1, 0
        r, old_r = p, k
    
        while r != 0:
        quotient = old_r // r
        old_r, r = r, old_r - quotient * r
        old_s, s = s, old_s - quotient * s
        old_t, t = t, old_t - quotient * t
    
        gcd, x, y = old_r, old_s, old_t
    
        assert gcd == 1
        assert (k * x) % p == 1
    
        return x % p
    
    
    # Functions that work on curve points #########################################
    
    def is_on_curve(point):
        """Returns True if the given point lies on the elliptic curve."""
        if point is None:
        # None represents the point at infinity.
        return True
    
        x, y = point
    
        return (y * y - x * x * x - curve['a'] * x - curve['b']) % curve['p'] == 0
    
    
    def point_neg(point):
        """Returns -point."""
        assert is_on_curve(point)
    
        if point is None:
        # -0 = 0
        return None
    
        x, y = point
        result = (x, -y % curve['p'])
    
        assert is_on_curve(result)
    
        return result
    
    
    def point_add(point1, point2):
        """Returns the result of point1 + point2 according to the group law."""
        assert is_on_curve(point1)
        assert is_on_curve(point2)
    
        if point1 is None:
        # 0 + point2 = point2
        return point2
        if point2 is None:
        # point1 + 0 = point1
        return point1
    
        x1, y1 = point1
        x2, y2 = point2
    
        if x1 == x2 and y1 != y2:
        # point1 + (-point1) = 0
        return None
    
        if x1 == x2:
        # This is the case point1 == point2.
        m = (3 * x1 * x1 + curve['a']) * inverse_mod(2 * y1, curve['p'])
        else:
        # This is the case point1 != point2.
        m = (y1 - y2) * inverse_mod(x1 - x2, curve['p'])
    
        x3 = m * m - x1 - x2
        y3 = y1 + m * (x3 - x1)
        result = (x3 % curve['p'],
              -y3 % curve['p'])
    
        assert is_on_curve(result)
    
        return result
    
    
    def scalar_mult(k, point):
        """Returns k * point computed using the double and point_add algorithm."""
        assert is_on_curve(point)
    
        if k % curve['n'] == 0 or point is None:
        return None
    
        if k < 0:
        # k * point = -k * (-point)
        return scalar_mult(-k, point_neg(point))
    
        result = None
        addend = point
    
        while k:
        if k & 1:
            # Add.
            result = point_add(result, addend)
    
        # Double.
        addend = point_add(addend, addend)
    
        k >>= 1
    
        assert is_on_curve(result)
    
        return result
    
    #######################################################
    
    private_key = 0x0000000000000000000000000000000000000000000000000000000000000001
    public_key = scalar_mult(private_key, curve['g'])
    print('private key:')
    print(hex(private_key))
    print('public key:')
    print(hex(public_key[0]), hex(public_key[1]))
    
    print('---')
    
    private_key = 0x0000000000000000000000000000000000000000000000000000000000000002
    public_key = scalar_mult(private_key, curve['g'])
    print('private key:')
    print(hex(private_key))
    print('public key:')
    print(hex(public_key[0]), hex(public_key[1]))
    

    Running the script produces:

    private key:
    0x1
    public key:
    0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8
    ---
    private key:
    0x2
    public key:
    0xc6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5 0x1ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a
    

    References:

    https://andrea.corbellini.name/2015/05/30/elliptic-curve-cryptography-ecdh-and-ecdsa/

    https://github.com/andreacorbellini/ecc/blob/master/scripts/ecdsa.py