eos原始碼賞析(二十一):EOS智慧合約之區塊簽名的天龍八“步”
在上篇文章中我們提到了,由使用者操作會產生各種事務,事務的鏈上執行是由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<signature_type( const digest_type& )>& 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<signed_block_header&>(*p->block) = p->header;
11 } /// sign_block
- 第三步:呼叫block_header_state中sign
1 void block_header_state::sign( const std::function<signature_type(const digest_type&)>& 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開發或金庸。
個人微訊號