如何验证 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) 441e1d2adf9ff2a60​​75d71d0d8782228e0df47f8, 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 解码)删除了第一个字节。