For context

我正在使用azurerm_key_vault_key terraform资源创建一个secp256k1密钥.我可以将这个键的X和Y坐标输出为如下所示的字符串(看起来是一个Base64 URL编码的,但我找不到任何明确定义这一点的文档)

"x": "ARriqkpHlC1Ia1Tk86EM_bqH_9a88Oh2zMYF3fUUGJw"
"y": "wTYd3CEiwTk1n-lFPdpZ51P4Z0EzlVNXLvJMY-k55pQ"

The problem

我需要将其转换为十六进制格式的公钥.我已经在Python中try 了以下内容:

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)

它会压缩,但decompress_point会抛出一个断言错误,这表明我在压缩或解压缩步骤中做错了什么.

以下是上述脚本的完整输出:

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

我对这里使用的包一点都不熟悉,上面的代码大多是从我可以在网上找到的各种零碎东西中拼凑而成的.

有人能就如何做到这一点给我一个明确的答案吗?

推荐答案

Base64url是一种常见的编码,您的x和y看起来都是由Base64url编码的.此外,Base64url解码提供了对secp256k1有效的EC点:

...
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')

point = ecdsa.ellipticcurve.Point(ecdsa.SECP256k1.curve, x, y)
print(point) # (499815257955367864408742079034045137860289053987516893620084695066060527772,87391995603390372321291482400901178939983763504428780242289494985570894079636)

无效的EC点将导致错误消息.因此,它很可能确实是Base64url编码.


将Base64url编码的x和y坐标转换为压缩或未压缩的键很容易.你甚至不需要ecdsa的图书馆.

请注意,对于secp256k1,x和y的大小必须分别为32字节的压缩或未压缩格式.如果它们应该有一个较小的数值,它们将从前面用0x00值填充到32个字节.可能的转换如下所示:

...
x_value = base64url_to_bytes("ARriqkpHlC1Ia1Tk86EM_bqH_9a88Oh2zMYF3fUUGJw")
y_value = base64url_to_bytes("wTYd3CEiwTk1n-lFPdpZ51P4Z0EzlVNXLvJMY-k55pQ")

# padding with leadng 0x00 values
x_32 = (32 - len(x_value)) * b'\0' + x_value
y_32 = (32 - len(y_value)) * b'\0' + y_value

compressed = (b'\02' if y_32[31] % 2 == 0 else b'\03') + x_32
uncompressed = b'\04' + x_32 + y_32

print(compressed.hex())    # 02011ae2aa4a47942d486b54e4f3a10cfdba87ffd6bcf0e876ccc605ddf514189c
print(uncompressed.hex())  # 04011ae2aa4a47942d486b54e4f3a10cfdba87ffd6bcf0e876ccc605ddf514189cc1361ddc2122c139359fe9453dda59e753f86741339553572ef24c63e939e694

请注意,您的compress_point()不考虑填充(然而,这与您的值无关,因为x和y是32个字节),如果前导字节是一位数,它也不会返回带有前导0的十六进制编码值(因此format(x, 'x')将十六进制编码值返回为63位11ae2aa4a47942d486b54e4f3a10cfdba87ffd6bcf0e876ccc605ddf514189c).

为了完整性:如果x和y已经是整数,则可以简单地实现到填充字节对象的转换,如下所示:

...
x_32 = x.to_bytes(32, 'big')
y_32 = y.to_bytes(32, 'big')

要将压缩密钥转换为未压缩密钥,必须重建y值,这需要模数到达.为此,可以使用ecdsa(它在幕后实现必要的数学运算):

from ecdsa import VerifyingKey, SECP256k1
vk = VerifyingKey.from_string(compressed, curve=SECP256k1)
uncompressed = vk.to_string('uncompressed')

print(uncompressed.hex()) # 04011ae2aa4a47942d486b54e4f3a10cfdba87ffd6bcf0e876ccc605ddf514189cc1361ddc2122c139359fe9453dda59e753f86741339553572ef24c63e939e694

当然,同样地,将未压缩密钥转换为压缩密钥也是可能的.

请注意,您的decompress_point()不正确地适用于ecdsa.ellipticcurve.Point().构造函数要求将x和y坐标作为第二个和第三个参数作为整数(请参阅第一个代码片段).


为了完整性:from_string()还可以处理RAW格式,即32字节x和32字节y坐标的串联:

...
from ecdsa import VerifyingKey, SECP256k1
raw = x_32 + y_32
vk = VerifyingKey.from_string(raw, curve=SECP256k1)
uncompressed = vk.to_string('uncompressed')

print(uncompressed.hex()) # 04011ae2aa4a47942d486b54e4f3a10cfdba87ffd6bcf0e876ccc605ddf514189cc1361ddc2122c139359fe9453dda59e753f86741339553572ef24c63e939e694

Python相关问答推荐

使用plotnine和Python构建地块

通过Selenium从页面获取所有H2元素

加速Python循环

Streamlit应用程序中的Plotly条形图中未正确显示Y轴刻度

为什么抓取的HTML与浏览器判断的元素不同?

我想一列Panadas的Rashrame,这是一个URL,我保存为CSV,可以直接点击

多处理队列在与Forking http.server一起使用时随机跳过项目

删除marplotlib条形图上的底边

Django—cte给出:QuerySet对象没有属性with_cte''''

Tkinter菜单自发添加额外项目

为什么在FastAPI中创建与数据库的连接时需要使用生成器?

在Google Drive中获取特定文件夹内的FolderID和文件夹名称

在极点中读取、扫描和接收有什么不同?

如何反转一个框架中列的值?

如何在Python中将超链接添加到PDF中每个页面的顶部?

如何使用pytest在traceback中找到特定的异常

如何使用大量常量优化代码?

如何编辑此代码,使其从多个EXCEL文件的特定工作表中提取数据以显示在单独的文件中

我如何处理超类和子类的情况

#将多条一维曲线计算成其二维数组(图像)表示