如何验证 trezor 钱包的签名
How to verify a signature made by trezor wallet
我想验证一条由我的 trezor 硬件钱包签名的消息。
基本上我有这些资料。
.venv/bin/trezorctl btc get-public-node -n 0
Passphrase required:
Confirm your passphrase:
node.depth: 1
node.fingerprint: ea66f037
node.child_num: 0
node.chain_code: e02030f2a7dfb474d53a96cb26febbbe3bd3b9756f4e0a820146ff1fb4e0bd99
node.public_key: 026b4cc594c849a0d9a124725997604bc6a0ec8f100b621b1eaed4c6094619fc46
xpub: xpub69cRfCiJ5BVzesfFdsTgEb29SskY74wYfjTRw5kdctGN2xp1HF4udTP21t68PAQ4CBq1Rn3wAsWr84wiDiRmmSZLwkEkv4qK5T5Y7EXebyQ
$ .venv/bin/trezorctl btc sign-message 'aaa' -n 0
Please confirm action on your Trezor device
Passphrase required:
Confirm your passphrase:
message: aaa
address: 17DB2Q3oZVkQAffkpFvF4cwsXggu39iKdQ
signature: IHQ7FDJy6zjwMImIsFcHGdhVxAH7ozoEoelN2EfgKZZ0JVAbvnGN/w8zxiMivqkO8ijw8fXeCMDt0K2OW7q2GF0=
我想使用 python3-ecdsa。当我想用任何有效的 public 密钥验证签名时,我得到一个 AssertionError: (65, 64),因为签名的 base64.b64decode 是 65 字节,但应该是 64。
当我想将 node.public_key 加载到 ecdsa.VerifyingKey 中时,我得到一个 AssertionError: (32, 64),因为 bytes.fromhex return 32 个字节,但是我发现的每个示例public 键使用 64 个字节。
可能我需要将 bip32 xpub 转换为 public 密钥,但我真的不知道该怎么做。
解决方案
python-ecdsa 需要 0.14 或更高版本才能处理 public 密钥的压缩格式。
import ecdsa
import base64
import hashlib
class DoubleSha256:
def __init__(self, *args, **kwargs):
self._m = hashlib.sha256(*args, **kwargs)
def __getattr__(self, attr):
if attr == 'digest':
return self.double_digest
return getattr(self._m, attr)
def double_digest(self):
m = hashlib.sha256()
m.update(self._m.digest())
return m.digest()
def pad_message(message):
return "\x18Bitcoin Signed Message:\n".encode('UTF-8') + bytes([len(message)]) + message.encode('UTF-8')
public_key_hex = '026b4cc594c849a0d9a124725997604bc6a0ec8f100b621b1eaed4c6094619fc46'
public_key = bytes.fromhex(public_key_hex)
message = pad_message('aaa')
sig = base64.b64decode('IHQ7FDJy6zjwMImIsFcHGdhVxAH7ozoEoelN2EfgKZZ0JVAbvnGN/w8zxiMivqkO8ijw8fXeCMDt0K2OW7q2GF0=')
vk = ecdsa.VerifyingKey.from_string(public_key, curve=ecdsa.SECP256k1)
print(vk.verify(sig[1:], message, hashfunc=DoubleSha256))
Public-key. 从数学上讲,椭圆曲线 public key 是曲线上的一个点。对于比特币使用的椭圆曲线,secp256k1,以及其他X9-style(Weierstrass形式)曲线,有(在实践中)两个最初由X9.62建立并重复使用的标准表示许多其他人:
未压缩格式:由一个值为 0x04 的八位字节组成,后跟两个大小等于包含(仿射)X 和 Y 坐标的曲线顺序大小的块。对于 secp256k1,这是 1+32x2 = 65 个八位字节
压缩格式:由一个值为 0x02 或 0x03 的八位字节组成,指示 Y 坐标的奇偶校验,后跟一个大小等于包含 X 坐标的曲线顺序的块。对于 secp256k1,这是 1+32 = 33 个八位字节
你的trezor输出的publickey是第二种形式,0x02+32个八位字节=33个八位字节。不是 32。
我从未见过至少不接受标准未压缩格式的 X9EC 库 (ECDSA and/or ECDH),通常两者都不接受。可以想象你的 python 库只需要没有前导 0x04 的未压缩形式,但如果是这样的话,除非在文档或代码中提供了很好的解释,否则这种无偿且相当危险的非标准性会让我怀疑它的质量.如果您确实需要将压缩形式转换为未压缩形式,则必须实现曲线方程,对于 secp256k1 可以在标准参考中找到,更不用说许多实现了。计算 x^3 + a*x + b
,在 F_p 中取平方根,然后选择具有正确奇偶校验的正值或负值(同意此处的前导字节 0x02)。
'xpub' 是 分层确定性 密钥的 base58check 编码,它不仅是 EC(DSA) 密钥,还为密钥派生过程添加了元数据。如果你 base58 解码它并删除检查,你会得到(十六进制):
0488B21E01EA66F03700000000E02030F2A7DFB474D53A96CB26FEBBBE3BD3B9756F4E0A820146FF1FB4E0BD99026B4CC594C849A0D9A124725997604BC6A0EC8F100B621B1EAED4C6094619FC46good
与您的显示完全一样:
0488B21E fixed prefix
01 .depth
EA66F037 .fingerprint
00000000 .child_num
E02030F2A7DFB474D53A96CB26FEBBBE3BD3B9756F4E0A820146FF1FB4E0BD99 .chain_code
026B4CC594C849A0D9A124725997604BC6A0EC8F100B621B1EAED4C6094619FC46 .public_key
Confirming this, the ripemd160 of sha256 of (the bytes that are shown in hex as) 026B4CC594C849A0D9A124725997604BC6A0EC8F100B621B1EAED4C6094619FC46 is (the bytes shown in hex as) 441e1d2adf9ff2a6075d71d0d8782228e0df47f8, and prefixing the version byte 00 for mainnet to that and base58check encoding gives the地址 17DB2Q3oZVkQAffkpFvF4cwsXggu39iKdQ 如图所示。
签名。 从数学上讲,X9.62 类型的 ECDSA 签名是两个整数,称为 r 和 s。有两种 不同的 标准来表示它们,比特币使用两种不同的标准:
ASN.1 DER 格式。 DER 是一种通用编码,包含 'tag' 和 'length' 元数据和可变长度数据,具体取决于数值,此处为 r 和 s;对于 secp256k1,此编码通常为 70 到 72 个八位字节,但偶尔会更少。然而,为了避免某些 'malleability' 攻击,当前比特币需要使用小于一半曲线顺序的 's' 值,通常称为 'low-s',这将 ASN.1 DER 编码的最大长度减少到71 个八位字节。比特币将其用于 transaction 签名,并在其后紧接着添加一个 'sighash' 字节(在 'scriptsig' 又名赎回脚本中)指示有关如何计算签名的某些选项(因此应该得到验证)。
'plain' 或 P1363 格式。这是固定长度的,仅由作为固定长度块的 r 和 s 值组成;对于 secp256k1,这是 64 个八位字节。比特币将其用于消息签名,但它 添加 'recovery' 字节' 到 beginning 允许确定 publickey如有必要,从消息和签名中提取,总共 65 个八位字节。
参见 https://bitcoin.stackexchange.com/questions/38351/ecdsa-v-r-s-what-is-v/38909 and https://bitcoin.stackexchange.com/questions/12554/why-the-signature-is-always-65-13232-bytes-long。
如果您的 python 库是为通用 ECDSA 而不是比特币设计的,并且想要一个 64 字节的签名,那几乎可以肯定是 'plain' 格式,它对应于比特币消息签名(这里从 base64 解码)删除了第一个字节。
我想验证一条由我的 trezor 硬件钱包签名的消息。 基本上我有这些资料。
.venv/bin/trezorctl btc get-public-node -n 0
Passphrase required:
Confirm your passphrase:
node.depth: 1
node.fingerprint: ea66f037
node.child_num: 0
node.chain_code: e02030f2a7dfb474d53a96cb26febbbe3bd3b9756f4e0a820146ff1fb4e0bd99
node.public_key: 026b4cc594c849a0d9a124725997604bc6a0ec8f100b621b1eaed4c6094619fc46
xpub: xpub69cRfCiJ5BVzesfFdsTgEb29SskY74wYfjTRw5kdctGN2xp1HF4udTP21t68PAQ4CBq1Rn3wAsWr84wiDiRmmSZLwkEkv4qK5T5Y7EXebyQ
$ .venv/bin/trezorctl btc sign-message 'aaa' -n 0
Please confirm action on your Trezor device
Passphrase required:
Confirm your passphrase:
message: aaa
address: 17DB2Q3oZVkQAffkpFvF4cwsXggu39iKdQ
signature: IHQ7FDJy6zjwMImIsFcHGdhVxAH7ozoEoelN2EfgKZZ0JVAbvnGN/w8zxiMivqkO8ijw8fXeCMDt0K2OW7q2GF0=
我想使用 python3-ecdsa。当我想用任何有效的 public 密钥验证签名时,我得到一个 AssertionError: (65, 64),因为签名的 base64.b64decode 是 65 字节,但应该是 64。 当我想将 node.public_key 加载到 ecdsa.VerifyingKey 中时,我得到一个 AssertionError: (32, 64),因为 bytes.fromhex return 32 个字节,但是我发现的每个示例public 键使用 64 个字节。 可能我需要将 bip32 xpub 转换为 public 密钥,但我真的不知道该怎么做。
解决方案
python-ecdsa 需要 0.14 或更高版本才能处理 public 密钥的压缩格式。
import ecdsa
import base64
import hashlib
class DoubleSha256:
def __init__(self, *args, **kwargs):
self._m = hashlib.sha256(*args, **kwargs)
def __getattr__(self, attr):
if attr == 'digest':
return self.double_digest
return getattr(self._m, attr)
def double_digest(self):
m = hashlib.sha256()
m.update(self._m.digest())
return m.digest()
def pad_message(message):
return "\x18Bitcoin Signed Message:\n".encode('UTF-8') + bytes([len(message)]) + message.encode('UTF-8')
public_key_hex = '026b4cc594c849a0d9a124725997604bc6a0ec8f100b621b1eaed4c6094619fc46'
public_key = bytes.fromhex(public_key_hex)
message = pad_message('aaa')
sig = base64.b64decode('IHQ7FDJy6zjwMImIsFcHGdhVxAH7ozoEoelN2EfgKZZ0JVAbvnGN/w8zxiMivqkO8ijw8fXeCMDt0K2OW7q2GF0=')
vk = ecdsa.VerifyingKey.from_string(public_key, curve=ecdsa.SECP256k1)
print(vk.verify(sig[1:], message, hashfunc=DoubleSha256))
Public-key. 从数学上讲,椭圆曲线 public key 是曲线上的一个点。对于比特币使用的椭圆曲线,secp256k1,以及其他X9-style(Weierstrass形式)曲线,有(在实践中)两个最初由X9.62建立并重复使用的标准表示许多其他人:
未压缩格式:由一个值为 0x04 的八位字节组成,后跟两个大小等于包含(仿射)X 和 Y 坐标的曲线顺序大小的块。对于 secp256k1,这是 1+32x2 = 65 个八位字节
压缩格式:由一个值为 0x02 或 0x03 的八位字节组成,指示 Y 坐标的奇偶校验,后跟一个大小等于包含 X 坐标的曲线顺序的块。对于 secp256k1,这是 1+32 = 33 个八位字节
你的trezor输出的publickey是第二种形式,0x02+32个八位字节=33个八位字节。不是 32。
我从未见过至少不接受标准未压缩格式的 X9EC 库 (ECDSA and/or ECDH),通常两者都不接受。可以想象你的 python 库只需要没有前导 0x04 的未压缩形式,但如果是这样的话,除非在文档或代码中提供了很好的解释,否则这种无偿且相当危险的非标准性会让我怀疑它的质量.如果您确实需要将压缩形式转换为未压缩形式,则必须实现曲线方程,对于 secp256k1 可以在标准参考中找到,更不用说许多实现了。计算 x^3 + a*x + b
,在 F_p 中取平方根,然后选择具有正确奇偶校验的正值或负值(同意此处的前导字节 0x02)。
'xpub' 是 分层确定性 密钥的 base58check 编码,它不仅是 EC(DSA) 密钥,还为密钥派生过程添加了元数据。如果你 base58 解码它并删除检查,你会得到(十六进制):
0488B21E01EA66F03700000000E02030F2A7DFB474D53A96CB26FEBBBE3BD3B9756F4E0A820146FF1FB4E0BD99026B4CC594C849A0D9A124725997604BC6A0EC8F100B621B1EAED4C6094619FC46good
与您的显示完全一样:
0488B21E fixed prefix
01 .depth
EA66F037 .fingerprint
00000000 .child_num
E02030F2A7DFB474D53A96CB26FEBBBE3BD3B9756F4E0A820146FF1FB4E0BD99 .chain_code
026B4CC594C849A0D9A124725997604BC6A0EC8F100B621B1EAED4C6094619FC46 .public_key
Confirming this, the ripemd160 of sha256 of (the bytes that are shown in hex as) 026B4CC594C849A0D9A124725997604BC6A0EC8F100B621B1EAED4C6094619FC46 is (the bytes shown in hex as) 441e1d2adf9ff2a6075d71d0d8782228e0df47f8, and prefixing the version byte 00 for mainnet to that and base58check encoding gives the地址 17DB2Q3oZVkQAffkpFvF4cwsXggu39iKdQ 如图所示。
签名。 从数学上讲,X9.62 类型的 ECDSA 签名是两个整数,称为 r 和 s。有两种 不同的 标准来表示它们,比特币使用两种不同的标准:
ASN.1 DER 格式。 DER 是一种通用编码,包含 'tag' 和 'length' 元数据和可变长度数据,具体取决于数值,此处为 r 和 s;对于 secp256k1,此编码通常为 70 到 72 个八位字节,但偶尔会更少。然而,为了避免某些 'malleability' 攻击,当前比特币需要使用小于一半曲线顺序的 's' 值,通常称为 'low-s',这将 ASN.1 DER 编码的最大长度减少到71 个八位字节。比特币将其用于 transaction 签名,并在其后紧接着添加一个 'sighash' 字节(在 'scriptsig' 又名赎回脚本中)指示有关如何计算签名的某些选项(因此应该得到验证)。
'plain' 或 P1363 格式。这是固定长度的,仅由作为固定长度块的 r 和 s 值组成;对于 secp256k1,这是 64 个八位字节。比特币将其用于消息签名,但它 添加 'recovery' 字节' 到 beginning 允许确定 publickey如有必要,从消息和签名中提取,总共 65 个八位字节。
参见 https://bitcoin.stackexchange.com/questions/38351/ecdsa-v-r-s-what-is-v/38909 and https://bitcoin.stackexchange.com/questions/12554/why-the-signature-is-always-65-13232-bytes-long。
如果您的 python 库是为通用 ECDSA 而不是比特币设计的,并且想要一个 64 字节的签名,那几乎可以肯定是 'plain' 格式,它对应于比特币消息签名(这里从 base64 解码)删除了第一个字节。