比特币开发系列 - 椭圆曲线数字签名

比特币特开发系列 - 椭圆曲线数字签名

现在你已经能够生成 EC 密钥对,下一步是使用它们来签名和验证消息。所谓消息是指任何需要进行身份验证的数据,可以是文本或二进制。具体来说,比特币客户端生成签名以授权其交易,而矿工验证这些签名和广播有效的交易。

本文将处理通用消息。稍后,我们将了解比特币交易签名过程中涉及的消息类型。

ECDSA 签名

毫不奇怪,EC 签名算法是 ECDSA(椭圆曲线数字签名算法)。在 ECDSA 中,所有相关方必须就一个哈希函数 H 达成一致,因为我们将对 H(message) 进行签名,而不是对消息本身进行签名。值得注意的是,只有签名方 S 能够访问私钥,而验证方 V 拥有相应的公钥以验证 S 的签名。我将重复使用上一篇文章中的私钥公钥

以下示例使用 SHA-256 摘要,但请记住,比特币指定的 H 函数是 hash256,也称为双 SHA-256(阅读有关哈希的文章 )。

签名

第一步是将我们的消息放入文件中,比如说 ex-message.txt

    This is a very confidential message

其 SHA-256 摘要为(不要忘记结尾的 \n):

    45 54 81 3e 91 f3 d5 be
    79 0c 7c 60 8f 80 b2 b0
    0f 3e a7 75 12 d4 90 39
    e9 e3 dc 45 f8 9e 2f 01

然后,我们使用私钥对消息的 SHA-256 摘要进行签名:

    $ openssl dgst -sha256 -sign ec-priv.pem ex-message.txt >ex-signature.der

ex-signature.der文件是_DER_格式的消息签名。OpenSSL 对于任何二进制输出(密钥、证书、签名等)都使用 DER 编码 ,但我将跳过底层细节。你不需要了解 ECDSA 签名的语义,只需记住它是一对大数 $$(r, s)$$。

你可能会注意到每次运行程序时签名都会更改,也就是说,默认的签名过程不是确定性的。当序列化区块链交易时,这可能会成为一个问题,因为签名是交易字节的一部分,你可能记得交易字节哈希到 txid。因此,每次签署交易时,txid 都会更改。这种行为通常是交易延展性问题的一个来源。

要显示十六进制编码的签名,只需添加 -hex 标志:

    $ openssl dgst -sha256 -hex -sign ec-priv.pem ex-message.txt

不过,为了获得可重复的输出,最好对 DER 文件进行十六进制转储:

    $ hexdump ex-signature.der

验证

每当发布经过身份验证的消息到网络时,读者都希望找到附加的签名。这两个文件是验证例程的输入,前提是我们事先收到了作者的公钥:

    $ openssl dgst -sha256 -verify ec-pub.pem -signature ex-signature.der ex-message.txt

如果签名经过验证,我们就能够声明消息是真实的。

签名

下面的代码执行了我们在上一节中通过命令行完成的操作。

OpenSSL 使签名操作变得简单,看看 ex-ecdsa-sign.c

    uint8_t priv_bytes[32] = { ... };
    const char message[] = "这是一条非常机密的消息\n";

    EC_KEY *key;
    uint8_t digest[32];
    ECDSA_SIG *signature;
    uint8_t *der, *der_copy;
    size_t der_len;

    ...

    key = bbp_ec_new_keypair(priv_bytes);
    bbp_sha256(digest, (uint8_t *)message, strlen(message));
    signature = ECDSA_do_sign(digest, sizeof(digest), key);

其中 ECDSA_SIG 是一个简单的结构,保存了前面段落中描述的 \((r, s)\) 对:

    struct {
        BIGNUM *r;
        BIGNUM *s;
    } ECDSA_SIG;

我的测试输出(你的输出将不同):

    r: 2B2B529BDBDC93E78AF7E00228B179918B032D76902F74EF454426F7D06CD0F9
    s: 62DDC76451CD04CB567CA5C5E047E8AC41D3D4CF7CB92434D55CB486CCCF6AF2

通过 i2d_ECDSA_SIG 函数,我们还可以获得 DER 编码的签名:

    der_len = ECDSA_size(key);
    der = calloc(der_len, sizeof(uint8_t));
    der_copy = der;
    i2d_ECDSA_SIG(signature, &der_copy);

在我的测试中,它整洁地呈现如下(你能找到里面的 rs 吗?):

    30 44
    02 20
    2b 2b 52 9b db dc 93 e7
    8a f7 e0 02 28 b1 79 91
    8b 03 2d 76 90 2f 74 ef
    45 44 26 f7 d0 6c d0 f9
    02 20
    62 dd c7 64 51 cd 04 cb
    56 7c a5 c5 e0 47 e8 ac
    41 d3 d4 cf 7c b9 24 34
    d5 5c b4 86 cc cf 6a f2

验证

验证签名也很简单,这里是 ex-ecdsa-verify.c

    uint8_t pub_bytes[33] = { ... };
    uint8_t der_bytes[] = { ... };
    const char message[] = "这是一条非常机密的消息\n";

    EC_KEY *key;
    const uint8_t *der_bytes_copy;
    ECDSA_SIG *signature;
    uint8_t digest[32];
    int verified;

    ...

    key = bbp_ec_new_pubkey(pub_bytes);
    der_bytes_copy = der_bytes;
    signature = d2i_ECDSA_SIG(NULL, &der_bytes_copy, sizeof(der_bytes));

由于我们没有私钥,我们必须将 pub_bytes 解码为一个压缩的公钥,使用来自 ec.h 的以下辅助函数:

    EC_KEY *bbp_ec_new_pubkey(const uint8_t *pub_bytes, size_t pub_len);

另一方面,der_bytes 是签名程序返回的 DER 编码的签名。我们将 DER 签名解码为方便的 ECDSA_SIG 结构,然后对同一 SHA-256 消息摘要进行验证:

    bbp_sha256(digest, (uint8_t *)message, strlen(message));
    verified = ECDSA_do_verify(digest, sizeof(digest), signature, key);

ECDSA_do_verify 函数返回:

  • 如果签名有效,则返回 1
  • 如果签名无效,则返回 0
  • 对于意外错误,则返回 -1

注意:可以通过使用 ECDSA_verify 跳过签名解码,该函数直接接受 DER 编码的签名。

获取代码

GitHub 上查看完整源代码。

下一篇

你学会了如何使用私钥对消息进行签名,以及如何使用公钥验证消息签名。

这是关于通用加密的最后一篇文章,呼!在下一篇文章中,我将介绍比特币网络。如果你喜欢这篇文章,请分享。

我是 AI 翻译官,为大家转译优秀英文文章,如有翻译不通的地方,在这里修改,还请包涵~

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Davide De Rosa
Davide De Rosa
江湖只有他的大名,没有他的介绍。