在上篇文章中我们提到了,由用户操作会产生各种事务,事务的链上执行是由push_transaction来完成的,我们简单的划分了下,具体可参考eos源码赏析(二十):EOS智能合约之push_transaction的天龙八“步” 。我们知道,在区块生产或者打包事务信息的时候免不了对数据进行签名,同时对于非本节点的区块信息也要进行验签。当然,针对每一个事务也都有签名及验签的过程,我们今天以区块的签名过程为例,来谈谈eosio中的签名是如何实现的。由于本人在该领域接触较少,行文中难免出现纰漏和差错,还望各位读者能及时批评指正。
本文主要分为以下内容:
- SHA256简介
- eos区块签名的天龙八“步”
1、SHA256简介
我们在eos的源码阅读过程中,不管有没有在意,或多或少的都会遇到SHA256,或者在合约的开发过程中遇到checksum256。我们来看:
SHA全拼:Secure Hash Algorithm,又称为安全散列算法,是一种能计算出一个数字消息所对应到的,长度固定的字符串(又称消息摘要)的算法。且若输入的消息不同,它们对应到不同字符串的概率很高;而SHA是FIPS所认证的五种安全散列算法。这些算法之所以称作“安全”是基于以下两点(根据官方标准的描述):由消息摘要反推原输入消息,从计算理论上来说是很困难的。想要找到两组不同的消息对应到相同的消息摘要,从计算理论上来说也是很困难的。任何对输入消息的变动,都有很高的概率导致其产生的消息摘要迥异。
对于任意长度的消息,SHA256都会产生一个256bit长的哈希值,称作消息摘要。这个摘要相当于是个长度为32个字节的数组,通常用一个长度为64的十六进制字符串来表示,这就是我们看到在eosio中,一些数据经过hash之后变成了64位的字符串的原因。
在eosio中基于安全裤openssl实现了SHA的部分功能,关于如何实现以及SHA实现的原理不作为本文的主要内容,包括中秋节期间被媒体大做文章的黎曼猜想,感兴趣的朋友也可以在群内一起讨论,我们接下来看区块生产之后是如何进行签名的。
2、eos区块签名的天龙八“步”
在上篇文章中,我们将push_transaction简单的分为八步,有利于我们进行代码的阅读,在本文中同样将区块签名的过程分为八步,通过每一步日志打印的结果来查看eos中区块签名进行了哪些动作:
- 第一步:producer_plugin区块生产之后启动签名
1void producer_plugin_impl::produce_block() {
2 //获取chain及block_header相关内容
3 auto signature_provider_itr = _signature_providers.find( pbs->block_signing_key );
4 ....
5 //等等操作,这里根据当前节点的sign_key进行签名
6 ....
7 //启动签名
8 chain.sign_block( [&]( const digest_type& d ) {
9 auto debug_logger = maybe_make_debug_time_logger();
10 return signature_provider_itr->second(d);
11 } );
12}
- 第二步:controller中开始进行区块签名,这里我们加了日志方便接下来的对比
1 void sign_block( const std::function& signer_callback ) {
2 std::string strState = "";
3 auto p = pending->_pending_block_state;
4 strState = fc::json::to_string(*p);
5 dlog("contorller sign_block begin:${state}", ("state", strPending));
6 p->sign( signer_callback );
7 strState = fc::json::to_string(*p);
8 dlog("contorller sign_block end:${state}", ("state", strPending));
9
10 static_cast(*p->block) = p->header;
11 } /// sign_block
- 第三步:调用block_header_state中sign
1 void block_header_state::sign( const std::function& signer ) {
2 auto d = sig_digest();
3 dlog(block_header_state::sign":${state}", ("state", d));
4 header.producer_signature = signer( d );
5 EOS_ASSERT( block_signing_key == fc::crypto::public_key( header.producer_signature, d ), wrong_signing_key, "block is signed with unexpected key" );
6 }
- 第四步:调用block_header_state中的sign_digest获取摘要信息
这里我们也加了相应的日志方便对比:
1 digest_type block_header_state::sig_digest()const {
2 std::string strHeaderDig = "";
3 strHeaderDig = fc::json::to_string(header.digest());
4 dlog("block_header_state::sig_digest begin,header digest:${state}", ("state", strHeaderDig));
5 std::string strBmRoot = "";
6 strBmRoot = fc::json::to_string(blockroot_merkle.get_root());
7 dlog("block_header_state::sig_digest begin,bm root:${state}", ("state", strBmRoot));
8 auto header_bmroot = digest_type::hash( std::make_pair( header.digest(), blockroot_merkle.get_root() ) );
9 dlog("header_bmroot:${state}", ("state", header_bmroot));
10 dlog("pending_schedule_hash:${state}", ("state", pending_schedule_hash));
11 return digest_type::hash( std::make_pair(header_bmroot, pending_schedule_hash) );
12 }
在这一步中,我们看到首先对区块的头信息header进行了hash获取了其摘要信息,而后将摘要信息和默克尔树的最后一个元素pair之后再次进行hash,最后将本次hash的结果和本节点轮流出块的hash(每个生产节点是固定的)pair之后再次进行hash,也就是进行了三次hash的过程。关于默克尔树在区块链或者说在eos中的应用,我们在后续的文章中也会做一些简单的介绍,然后我们来看取区块头本身的hash是如何实现的。
- 第五步:获取区块头信息的摘要信息及默克尔树的最后一个元素可以看到,获取区块头信心的摘要信息也是经过一次hash散列完成。
1 digest_type block_header::digest()const
2 {
3 return digest_type::hash(*this);
4 }
5 //按递增顺序获取当前节点的默克尔树
6 DigestType get_root() const {
7 if (_node_count > 0) {
8 return _active_nodes.back();
9 } else {
10 return DigestType();
11 }
12 }
- 第六步:基于openssl的sha256 hash的实现
1 static sha256 hash( const T& t )
2 {
3 sha256::encoder e;
4 fc::raw::pack(e,t);//将需要散列的信息t打包至加密信息e里面
5 return e.result(); //返回打包的结果
6 }
7
8 //sha256的结果
9 sha256 sha256::encoder::result() {
10 sha256 h;
11 SHA256_Final((uint8_t*)h.data(), &my->ctx );
12 return h;
13 }
在hash的实现过程中,我们可以看到使用了fc库中的pack将需要散列的信息打包到加密变量e里面,而在查看pack的过程中可以发现其依据变量类型对pack进行了多次重载,最终使用openssl库中的SHA256_Final将hash结果返回。
- 第七步:将签名结果放到区块头信息中
1header.producer_signature = signer( d );
- 第八步:签名前几签名后信息对比
1//区块头信息签名之前
2 "header": {
3 "timestamp": "2018-09-26T10:58:49.000",
4 "producer": "eosio",
5 "confirmed": 0,
6 "previous": "0001336d4c819c9656e3d8f9619afb65b4d94eb368cb7cbf1b8a0b3175dcfdff",
7 "transaction_mroot": "0000000000000000000000000000000000000000000000000000000000000000",
8 "action_mroot": "c2fd5cfedbf61c357b14a05dcdb3ab186aabb394c385f2f2a4daf79fb35cf454",
9 "schedule_version": 0,
10 "header_extensions": [],
11 "producer_signature": "SIG_K1_111111111111111111111111111111111111111111111111111111111111111116uk5ne"
12 },
13//区块头信息签名之后
14 "header": {
15 "timestamp": "2018-09-26T10:58:49.000",
16 "producer": "eosio",
17 "confirmed": 0,
18 "previous": "0001336d4c819c9656e3d8f9619afb65b4d94eb368cb7cbf1b8a0b3175dcfdff",
19 "transaction_mroot": "0000000000000000000000000000000000000000000000000000000000000000",
20 "action_mroot": "c2fd5cfedbf61c357b14a05dcdb3ab186aabb394c385f2f2a4daf79fb35cf454",
21 "schedule_version": 0,
22 "header_extensions": [],
23 "producer_signature": "SIG_K1_KdhkFF5W2YVtNdwmCVYmdw3WMoKCcCgestut6wHsWtRuekjaHv7BZkWU4UXJqboozf6JonXru9hVfQVcptWCN23s6YpFjX"
24 }
我们在签名的各个步骤中分别加了日志,由于区块信息较长,这里我们贴出区块头信息在签名前和签名后的对比。
通过对比可以发现,在基本信息保持一致的情况下,经过上面的八步操作,producer_signatrue发生了变化,至此也完成了区块签名的整个流程。
本文从区块生产过程出发,一步步介绍区块在生产过程中是如何实现SHA256签名的。eos中关于hash的内容很多,从钱包到账户,从action到transaction皆是如此,感兴趣的同学可以自己摸索下。
如果你觉得我的文章对你有一定的帮助,请点击文章末尾的喜欢该作者。
如果你对eos开发感兴趣,欢迎关注本公众号,一起学习eos开发。
微信公众号
有任何疑问或者指教请添加本人个人微信,当然有对eos开发感兴趣或者金庸粉的也可以添加一起交流,备注eos开发或金庸。
个人微信号