Search code examples
pythonbitcoinpublic-keyecdsa

Deriving an ECDSA uncompressed public key from a compressed one


I am currently trying to derive a Bitcoin uncompressed ECDSA public key from a compressed one.

According to this link on the Bitcoin wiki, it is possible to do so... But how?

To give you more details: as of now I have compressed keys (33-bytes-long) gathered on the bitcoin network.

They are of the following format: <1-byte-long prefix><32-bytes-long X>. From there, I would like to obtain an uncompressed key (65-bytes-long) whose format is: <1-byte-long prefix><32-bytes-long X><32-bytes-long Y>

According to this other link on the Bitcoin wiki, it should be as easy as solving the equation:

Y^2 = X^3 + 7

However, I cannot seem to get there. My value for Y is simply far-off. Here is my code (the value for the public key come from the Bitcoin wiki example):

import binascii
from decimal import *

expected_uncompressed_key_hex = '0450863AD64A87AE8A2FE83C1AF1A8403CB53F53E486D8511DAD8A04887E5B23522CD470243453A299FA9E77237716103ABC11A1DF38855ED6F2EE187E9C582BA6'
expected_y_hex = expected_uncompressed_key_hex[-64:]
expected_y_dec = int(expected_y_hex, 16)
x_hex = expected_uncompressed_key_hex[2:66]
if expected_y_dec % 2 == 0:
    prefix = "02"
else:
    prefix = "03"

artificial_compressed_key = prefix + x_hex

getcontext().prec = 500
test_dec = Decimal(int(x_hex, 16))
y_square_dec = test_dec**3 + 7
if prefix == "02":
    y_dec = - Decimal(y_square_dec).sqrt()
else:
    y_dec = Decimal(y_square_dec).sqrt()

computed_y_hex = hex(int(y_dec))
computed_uncompressed_key = "04" + x + computed_y_hex

For information, my outputs are:

computed_y_hex = '0X2D29684BD207BF6D809F7D0EB78E4FD61C3C6700E88AB100D1075EFA8F8FD893080F35E6C7AC2E2214F8F4D088342951'
expected_y_hex = '2CD470243453A299FA9E77237716103ABC11A1DF38855ED6F2EE187E9C582BA6'

Thank you for your help!


Solution

  • You need to calculate in the field Z_p, which mostly means that you have to reduce your number to the remainder after dividing with p after each calculation. Calculating this is called taking the modulo and is written as % p in python.

    Exponentiating in this field can be done more effectively than the naive way of just multiplying and reducing many times. This is called modular exponentiation. Python's built-in exponentation function pow(n,e,p) can take care of this.

    The remaining problem is to find the square root. Luckily secp256k1 is chosen in a special way (p%4=3), so that taking square roots is easy: A square root of x is x^((p+1)/4)%p.

    So a simplified version of your code becomes:

    import binascii
    
    p_hex = 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F'
    p = int(p_hex, 16)
    compressed_key_hex = '0250863AD64A87AE8A2FE83C1AF1A8403CB53F53E486D8511DAD8A04887E5B2352'
    x_hex = compressed_key_hex[2:66]
    x = int(x_hex, 16)
    prefix = compressed_key_hex[0:2]
    
    y_square = (pow(x, 3, p)  + 7) % p
    y_square_square_root = pow(y_square, (p+1)/4, p)
    if (prefix == "02" and y_square_square_root & 1) or (prefix == "03" and not y_square_square_root & 1):
        y = (-y_square_square_root) % p
    else:
        y = y_square_square_root
    
    computed_y_hex = format(y, '064x')
    computed_uncompressed_key = "04" + x_hex + computed_y_hex
    
    print computed_uncompressed_key