For context
I am using the azurerm_key_vault_key terraform resource to create an secp256k1 key. I can output the X and Y coordinates for this key as strings that look like this (appears to be a base64 url-encoded, but I cannot find any docs that clearly define this)
"x": "ARriqkpHlC1Ia1Tk86EM_bqH_9a88Oh2zMYF3fUUGJw"
"y": "wTYd3CEiwTk1n-lFPdpZ51P4Z0EzlVNXLvJMY-k55pQ"
The problem
I need to convert this into a public key in hexadecimal format. I have tried the following in Python:
import ecdsa
import binascii
import base64
def base64url_to_bytes(base64url_string):
padding = '=' * (4 - (len(base64url_string) % 4))
base64_string = base64url_string.replace('-', '+').replace('_', '/') + padding
return base64.b64decode(base64_string)
def compress_point(x, y):
"""Compresses the point (x, y) to a 33-byte compressed public key."""
prefix = "02" if y % 2 == 0 else "03"
return prefix + x
def decompress_point(compressed_key):
"""Decompresses the compressed public key to (x, y)."""
prefix = compressed_key[:2]
x = compressed_key[2:]
y = ecdsa.ellipticcurve.Point(ecdsa.SECP256k1.curve, int(x, 16), int(prefix, 16))
return x, y.y()
x_value = base64url_to_bytes("ARriqkpHlC1Ia1Tk86EM_bqH_9a88Oh2zMYF3fUUGJw")
y_value = base64url_to_bytes("wTYd3CEiwTk1n-lFPdpZ51P4Z0EzlVNXLvJMY-k55pQ")
x, y = int.from_bytes(x_value, byteorder='big'), int.from_bytes(y_value, byteorder='big')
# Compress the point
compressed_key = compress_point(format(x, 'x'), y)
print(compressed_key)
decompressed_x, decompressed_y = decompress_point(compressed_key)
print("Decompressed X:", decompressed_x)
print("Decompressed Y:", decompressed_y)
It compresses, but decompress_point throws an assertion error, which suggests that I am doing something wrong either in the compression or decompression step.
Here is the full output of the script above:
0211ae2aa4a47942d486b54e4f3a10cfdba87ffd6bcf0e876ccc605ddf514189c
Traceback (most recent call last):
File "get_public_key3.py", line 32, in <module>
decompressed_x, decompressed_y = decompress_point(compressed_key)
File "get_public_key3.py", line 19, in decompress_point
y = ecdsa.ellipticcurve.Point(ecdsa.SECP256k1.curve, int(x, 16), int(prefix, 16))
File "/home/ubuntu/.local/lib/python3.8/site-packages/ecdsa/ellipticcurve.py", line 1090, in __init__
assert self.__curve.contains_point(x, y)
AssertionError
I am not at all familiar with the packages in use here and the code above has mostly been put together from various bits and pieces I could find online.
Could someone provide me with a definitive answer on how to do this?
Base64url is a common encoding, your x and y look formally Base64url encoded. Also, a Base64url decoding provides an EC point valid for secp256k1:
An invalid EC point would result in an error message. So it is most likely indeed a Base64url encoding.
Converting your Base64url encoded x and y coordinates to a compressed or uncompressed key is easy. You don't even need the ecdsa library.
Note that for secp256k1 x and y must each have a size of 32 bytes in compressed or uncompressed format. If they should have numerically a value, which is smaller, they are to be padded from the front with 0x00 values to 32 bytes. A possible conversion looks as follows:
Note that your
compress_point()does not take padding into account (which is however not relevant for your values, since x and y are 32 bytes), nor does it return the hex encoded value with a leading 0 if the leading byte is single-digit (soformat(x, 'x')returns the hex encoded value as 63-digit11ae2aa4a47942d486b54e4f3a10cfdba87ffd6bcf0e876ccc605ddf514189c).For completeness: If x and y are already integers, the conversion to padded bytes objects can be implemented simply as follows:
To convert a compressed key to an uncompressed key, the y value must be reconstructed, which requires modular arrithmetic. For this purpose ecdsa can be used (which implements the necessary math under the hood):
Likewise, of course, converting an uncompressed key to a compressed one is just as possible.
Note that your
decompress_point()appliesecdsa.ellipticcurve.Point()incorrectly. The constructor requires as 2nd and 3rd parameters x and y coordinates as integers (see 1st code snippet).For completeness:
from_string()can also handle the raw format, i.e. the concatenation of the 32 bytes x and 32 bytes y coordinate: