区块链中的DCDSA:椭圆曲线数字签名

既然我们已经可以产生椭圆曲线密钥对,我们接下来就用使用它来进行消息的签名和验证。我所指的消息是任何形式,无论是文本还是二进制形式,只要它们有被验证合法性的需要。特别的是,bitcoin客户端通过签名来证明交易的有效性,反之,矿工则是通过验证这样的签名,来批准并广播合法的交易。

ECDSA 签名

椭圆曲线签名算法就是ECDSA(Elliptic-Curve Digital Signature Algorithm).在ECDSA中,各方必须约定一个共同的哈希函数H, 因为我们将要签名的对象是H(消息),而不是消息本身。值得注意的是,只有签名方S有私钥的权限,验证方V只需要拿到相应的公钥就可以进行验证。本文中,我将使用上一章所创建的密钥对。

下面的案例中,我们签名的对象是SHA-256摘要。但bitcoin中指定的H函数是HASH256,也就是指双重SHA-256

签名

第一步把我们的消息存入文件,命名ex-message.txt

This is a very confidential message

之后,我们用私钥对其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

为了重用刚刚输出的结果,最好使用hexdup已生成的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[] = "This is a very confidential 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;

使用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);

验证

验证同样很简单,可以在ex-ecdsa-verify.c中查看:

uint8_t pub_bytes[33] = { ... };
uint8_t der_bytes[] = { ... };
const char message[] = "This is a very confidential 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_KEY *bbp_ec_new_pubkey(const uint8_t *pub_bytes, size_t pub_len);

另一方面,der_bytes是签名程序返回的DER格式的签名。我们将解码DER签名到更方便的ECDSA_SIG结构中,然后与消息摘要比较进行验证。

ECDSA_do_verify函数的返回值:

  • 1,签名合法
  • 0,签名不合法
  • -1,出现未知错误

注意:使用ECDSA_verify可以跳过签名的解码过程,因为它需要的输入值是DER形式的签名。

    原文作者:marmalade
    原文地址: https://segmentfault.com/a/1190000013195459
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞