程式設計師視角的錢包建立到交易簽名 - Kai | Jeth 第二期

稀土君發表於2018-07-18

編者按:本文系 imToken 首席架構師 Kai 講師,在由掘金技術社群主辦,以太坊社群基金會、以太坊愛好者與 ConsenSys 協辦的《開發者的以太坊入門指南 | Jeth 第二期 - 杭州場》 活動上的分享整理。Jeth 圍繞以太坊技術開發主題的系列線下活動。每期 Jeth 會邀請以太坊開發領域的優秀技術團隊和工程師線上下分享技術乾貨。旨在為開發者提供線下技術交流互動機會,幫助開發者成長。

程式設計師視角的錢包建立到交易簽名 - Kai | Jeth 第二期

本場分享視訊回放連結(B 站)

我是 imToken 的首席架構師 Kai。開篇我先給大家丟擲這次分享的核心要點: 在以太坊上,你的私鑰就是你的賬戶。

程式設計師視角的錢包建立到交易簽名 - Kai | Jeth 第二期

生成隨機數

程式設計師視角的錢包建立到交易簽名 - Kai | Jeth 第二期

一開始你什麼都沒有,你需要生成一個隨機數,隨機數我覺得程式設計師都很熟悉,某個事情隨機發生需要找一個結果,要產生隨機數。

私鑰生成

隨機數會生成私鑰,這時你有兩個選擇:

1. 直接隨機生成 32 bytes

直接隨機生成 32 bytes 的二進位制內容,把這個作為自己的私鑰。假如你開啟任何一個以太坊的錢包,你併入到私鑰的介面,你隨便輸出一個東西,只要位數符合要求,它都可以作為錢包導進去。但這裡面有一個安全性的問題,大家想到我這樣可以看到別人的錢包,但是自己可以回去算下,32個位元組可以生成多少個私鑰。

2. 確定性推導

程式設計師視角的錢包建立到交易簽名 - Kai | Jeth 第二期

這個做法在錢包裡很常見。錢包生成一個隨機數,然後再拿這個隨機數查字典,得到12個單詞左右的助記詞,再通過路徑拿到你的私鑰。

程式設計師視角的錢包建立到交易簽名 - Kai | Jeth 第二期

GitHub 連結:https://github.com/bitcoinjs/bip39

助記詞的生成過程也很有趣,你把隨機數拆分11個 bits,就有了11個二進位制的字串,社群裡面有一個叫做 bip39 的二進位制字串的對應規範,裡面有一個字典,你根據拿到的字串查字典,查到11個字串對應的11個單詞。最後對前面這11個詞做一個 Checksum ,你的錢包得到12個助記詞,最終可以推出你的私鑰,進而推出你的錢包賬戶。

Hierarchical Deterministic Wallet 分層-確定性-錢包

這裡有兩個概念,一個叫做分層,另一個叫做確定性

程式設計師視角的錢包建立到交易簽名 - Kai | Jeth 第二期

什麼是分層,一開始隨機數 seed 推匯出了一把 Master key,這把 Master key 在下面會繼續推導 Child keys,再往下推能得到私鑰。這就是前面所提到的確定性推導。我知道一份助記詞,這個助記詞無論如何,按照固定的路徑可以推出一個可以預期的私鑰,叫做確定性;分層你有很多把私鑰,很多把私鑰裡面從一個助記詞裡面一層一層倒出來,所以我們在這裡叫做分層決定性錢包。

BIP44

程式設計師視角的錢包建立到交易簽名 - Kai | Jeth 第二期

助記詞有了,我們要推導私鑰,私鑰的推導路徑就是由這樣一串字元格式:m/purpose/coin/account/change/address_index, 一層一層推匯出來的,我們叫它 BIP44。不同的鏈有不同的推導路徑,比如說,以太坊是60。你有一個助記詞之後,你可以根據確定性的路徑推匯出不同的私鑰,不同的私鑰就是在不同的這些區塊鏈上面的一個身份的一個錢包。

儲存私鑰

程式設計師視角的錢包建立到交易簽名 - Kai | Jeth 第二期

隨機數變成助記詞,助記詞變成私鑰,私鑰如 STR 何儲存下來,畢竟你的錢包不可能使用一次,比如說你有一個朋友談到眾籌,1.8元買到以太坊,他轉給你2個,這2個以太坊你要存下來,你存下來之後,你的私鑰需要落地儲存。但儲存不是簡單地儲存即可。網路空間不安全,每天都有人被盜 QQ 號,區塊鏈世界裡面當你的賬戶資產被盜是不可逆的,這時你需要做安全加密的的儲存。

最終你的私鑰會變成 V3 KeyStore 這樣的長字串。

程式設計師視角的錢包建立到交易簽名 - Kai | Jeth 第二期

程式設計師視角的錢包建立到交易簽名 - Kai | Jeth 第二期

  • 這裡 KDF 的演算法是給一個金鑰通過 hash 的方式,計算 N 遍,比如說這裡面有一個 N 值是26萬次,這裡面給出的密碼會被這個 KDF 計算26萬次之後輸出所對應的金鑰,因此別人想要破解的話,他需要計算的次數也需要26萬次,這相當耗費 CPU 資源如果你這個錢包利用很高的值,這是加密的過程。
  • mac 值就是用作校驗的。通過剛剛這個演算法,把你的金鑰儲存之後每次輸入密碼解開,解開之後怎麼樣通過驗證這個密碼是正確的,我把這個密碼走前面的這個部分,出來的結果是否對得上,你每次輸入密碼解開錢包簽名裡面,這個時候會把你的金鑰按照同樣的過程走一遍,做一個 hash 對比一下這個 mac 值對應得上,如果對得上,說明加密的這個結果就是你用這個密碼加密的。

私鑰如何轉換到賬戶地址

程式設計師視角的錢包建立到交易簽名 - Kai | Jeth 第二期

私鑰有了,也落地儲存了,而你的私鑰要轉換到賬戶地址,這裡有另外的一個推導過程。

程式設計師視角的錢包建立到交易簽名 - Kai | Jeth 第二期

這裡用到了前面提的不對稱加密演算法,先做一個橢圓曲線給你一把金鑰,還有一把公鑰,就是他把私鑰和公鑰,把公鑰投入到 hash ,再去拿第一位的一部分作為一個地址,你的錢包就有一個地址。

程式設計師視角的錢包建立到交易簽名 - Kai | Jeth 第二期

這是一個比較複雜的數學過程,作為我們做工程的人來講,大 K 的最終的結果是一把私鑰,小 k 是一把公鑰;公鑰跟私鑰中間的關係是固定的,只能單向推導——從私鑰推到公鑰;這個橢圓曲線是比特幣最早實現時所選的橢圓曲線,是相對而言效率最高的橢圓曲線。

轉賬

程式設計師視角的錢包建立到交易簽名 - Kai | Jeth 第二期

剛剛這些事情做完之後,你就可以去轉賬,但是轉賬之前,你的賬戶需要有一些資產。

程式設計師視角的錢包建立到交易簽名 - Kai | Jeth 第二期

在以太坊中轉賬的資料結構如上圖所示,包括一個賬戶交易計數 Nonce,你要轉給誰,你要轉多少錢,以及在最下面有一個簽名。你拿你的私鑰去對這個交易做一個簽名。這裡面有很多種引數,我們一個一個來看一下。

Gas

程式設計師視角的錢包建立到交易簽名 - Kai | Jeth 第二期

首先是Gas,Gas 是以太坊代幣的機消耗機制,你發起一個以太坊交易的時候,你必須消耗一定的以太幣並算到 Gas 中,Gas 最終會流到礦工手裡。Gas 的機制是先扣再退——發起交易時,你最初付出的 Gas 要寫得大一點,跑完一條交易以後,最終的結果是消耗的 Gas 比填寫的要少,系統會把剩下的退還。

Gas 就是讓礦工打包需要付出的費用,你自己可以定義需要多少個 Gas,每個 Gas 值多少以太幣,並告訴礦工說,我願意為這筆交易付出多少錢、這筆錢轉給誰和轉多少。這裡面 Gas 的計算最終在以太坊的虛擬機器中按一條一條的指令扣費,你的這些轉賬裡面,按照指令條數和儲存空間計費,最終把你的手續費扣下來。

重放保護

程式設計師視角的錢包建立到交易簽名 - Kai | Jeth 第二期

Nonce 是賬戶交易計數,比方說你的第一筆交易 Nonce 是0,然後1、2、3以此類推加上去。

ChainID 是分叉鏈區分。以太坊和比特幣都有自己的測試網路,假如說你在測試網路上面簽署一筆交易,別人把它放到主網中執行,這時會發生轉賬。ChainID 會在當前跑的區塊鏈裡面,記錄下你的 ID,防止你測試時引發交易。別人拿到這筆交易到主網上面重新執行時,會有一個負責所有交易的計數器,可以防止交易的二次發生。

一些有趣的事情

程式設計師視角的錢包建立到交易簽名 - Kai | Jeth 第二期

  • 不指定轉賬人:首先轉賬的時候沒有指定說是誰轉賬?它只有 to,但沒有 from。
  • 交易簽名時就確定了 TxHash:交易簽名的時候,交易的引數最終會算出來一個 hash,這個 hash 會讓你到網路上交易之前,告知你 Gas 的費用。
  • Out of Gas:當你付出的礦工費 Gas 不夠的時候,系統就會扣除你的礦工費,並告訴你由於 Gas 不足導致交易失敗,但是照樣把你這一條交易打包進去。

私鑰簽名

程式設計師視角的錢包建立到交易簽名 - Kai | Jeth 第二期

得到簽名之後,就會有橢圓曲線還原出你的公鑰,公鑰可以算出來你的地址,所以這是剛剛所說的以太坊的交易裡面沒有 from。因為有橢圓曲線這個非對稱加密的存在,它拿到你的公鑰後,可以反推出這個地址是否為發出人的地址。這也就意味著,如果大家的 nonce 和交易計數是一致的話,無論這個交易發給誰簽名,我只要拿簽名後的資料就可以上傳並執行這筆交易。

交易廣播

程式設計師視角的錢包建立到交易簽名 - Kai | Jeth 第二期

交易簽名完之後你要廣播並上鏈,上圖是以太坊礦工打包的記錄。

交易上鍊的過程

程式設計師視角的錢包建立到交易簽名 - Kai | Jeth 第二期

交易上鍊的過程分為四點,首先傳送交易到指定節點,或前面提到的某一個授信節點。對於錢包來說,它們會執行自己的節點,像以太坊會有官方的客戶端 Gas。我們做錢包需要自己運維提供服務的節點池,使用者把這一條交易發到節點上面,節點之間互相通訊,把這筆交易的資訊擴散到全網,然後礦工節點(即專門做打包交易的節點)有一個打包池。以太坊交易的活躍程度比你當前打包要快,特別是網路擁堵的時候,放到這個池子之後,礦工在池子裡面挑選一些對於他們有利的,誰願意花錢打包,被礦工選中打包到區塊裡面。

礦工打包策略

程式設計師視角的錢包建立到交易簽名 - Kai | Jeth 第二期

礦工打包策略很簡單,那就是向錢看。網路蒐集全網蒐集到各種各樣的交易上傳到 TxPool 即交易池當中。礦工總是在尋找找到開出更高 Gas,也就是說願意付出更多手續費的交易,並打包進來。因為最終的前30名的 Gas 費用最終落入礦工,所以他們就會由高到低排序,並先服務 Gas 開價更高的富人。在打包的過程中有可能出現空塊,出空塊的意義很大,因為你打包一塊出來的同時,還可以拿到以太坊網路本身給你的獎勵。

Gas 的技巧

程式設計師視角的錢包建立到交易簽名 - Kai | Jeth 第二期

  • 交易比較堵的時候,你所發起的交易 Gas 比較低,仍在交易池中待打包。這時你可以再發一筆交易,用高 GasPrice 替換掉 TxPool 中的交易。
  • 想讓礦工更快的打包時,可以在把這些交易替換掉,若要加速交易的確認,也可以給出比較高的 GasPrice。
  • 通過網路情況計算最優 GasPrice,因為每個時間節點網路上面發生的 GasPrice 不一樣,我們按照網路情況自動給你一個最優的,以一個較低的 GasPrice 儘快打包。

礦工現狀

程式設計師視角的錢包建立到交易簽名 - Kai | Jeth 第二期

一條鏈上數目龐大的節點,能保證你的礦工足夠的分散。所有的打包的礦工都是在同一個大樓裡面,我控制這個大樓就是控制這個網路。目前來說主鏈上只有礦池,沒有孤兒礦工。因為礦池是全資料的節點,礦池找了很多礦工(也就是礦機),把交易派發給礦工,讓它們算出來比較合理的塊,並儘快地把塊打包。現在來說礦機幾乎是不知道區塊鏈,它不會理解這一串長鏈有什麼意義。另外,跟礦機和礦池強大的計算能力相比,你的電腦計算能力很弱,挖礦的效率跟不上。所以目前來看,礦池才是金主,即網路的實際控制人。

講了這麼多,大家應該理解到我文章開頭下的結論————你的賬戶就是一把私鑰,而且你的賬戶有了一把私鑰之後,拿你的私鑰做了簽名。這時就會發一筆交易,你可以轉賬,你有私鑰地址後可以接收別人轉過來的資產。接下來我們往下繼續探究賬戶的結構,從底層思考以太坊。

更底層去看賬戶

狀態機模型

程式設計師視角的錢包建立到交易簽名 - Kai | Jeth 第二期

你可以把以太坊賬戶想象成狀態機模型的結構。如圖, alice 和 Bob 發生一筆交易併發生簽名(Nonce = 1, Balance = 3)然後這筆交易被區塊打包。打包之前,這個 alice 有42個,打包完成之後變成39個且 Nonce +1。

Ethereum Account (https://github.com/ethereumjs/ethereumjs-account)

程式設計師視角的錢包建立到交易簽名 - Kai | Jeth 第二期

這裡 nonce 和 balbance 不必贅述,當 Stateroot 不為空時,這個賬號是一個合約,即合約的狀態就存在該欄位中。 CodeHash 是你的合約賬戶裡面的具體程式碼,但不完全是在這兩個欄位裡面,這兩個欄位引用到底下的時候,只是一個指引你去找到對應東西的索引。

合約賬戶

  • 只包含程式碼
  • 可儲存狀態

合約賬戶跟普通賬戶不一樣,普通使用者的狀態是空的,不能夠儲存狀態。以太坊使用者的錢包狀態是每個人有多少錢。

一些賬戶有趣的事情

  • Ether 餘額記錄在賬戶本身
  • Token 餘額記錄對應在合約賬戶上

為什麼要講這個呢?大家知道以太坊去年因為很多專案發眾籌而變得很火。對於眾籌來說,每個 Token 即每個代幣都是一個合約,這個合約的資料記錄在合約賬戶上,而像以太坊的餘額是記錄在賬戶本身。

程式設計師視角的錢包建立到交易簽名 - Kai | Jeth 第二期

在這裡舉標準的以太坊 ERC-20 代幣規範作為例子。decimal 是餘額中小數點的位數。當使用者用這個方法查代幣餘額時,記錄在這個部署的合約中。剩下的提供給我們更加常用的方法,包括授權,而在授權之後你可以做一些很有趣的事情,比如去中心化交易。去中心化交易依賴於標準,把你的代幣授權給合約的介面。需要留意以太坊本身的 Gas 是不可變更的,這與 imToken 不同。

程式設計師視角的錢包建立到交易簽名 - Kai | Jeth 第二期

剛剛說有合約,你要去呼叫這個合約,呼叫合約的時候讀、寫。讀合約的時候可以通過 JSON-RPC 的介面;寫合約就是發交易,把呼叫合約的方法寫在前面的交易的 data 欄位,兩種呼叫都需要進行 solidity-ABI 編碼,裡面有調取合約的方法,每次發這個交易的時候,你要根據這個編碼編出來再呼叫。

程式設計師視角的錢包建立到交易簽名 - Kai | Jeth 第二期

程式設計師視角的錢包建立到交易簽名 - Kai | Jeth 第二期

編碼的過程大概是這樣的:你這裡有一個 balance ,發起交易時,首先要把 ID 找出來,把你的引數再編碼,加起來拼接在一起,最終你做呼叫的時候讀取合約,寫一下的 data 引數。

One More Thing

如何安全儲存錢包

  1. 離線儲存
  2. 硬體錢包
  3. 多籤錢包
  • 對於我們做錢包來說,我們接每一條鏈,我們弄清楚在這一條鏈上發生了上面,這個鏈如何上傳交易。對於以太坊本身的節點實現,我們也有一定的瞭解;
  • 要蒐集使用者還沒有的發生交易;
  • 礦池要足夠大,要容納下我們自己的使用者的交易。

想了解更多,我推薦閱讀 Ethereumjs 原始碼。

Ethereumjs 在我的分享中,講到程式碼相關的內容都附帶了 GitHub 連結,原始碼或者裡面的例子都來自於對應的倉庫。若想快速瞭解一下以太坊如何運作,不妨去看看 Ethereumjs 的原始碼。

程式設計師視角的錢包建立到交易簽名 - Kai | Jeth 第二期

GitHub: kaichen(github.com/kaichen)

Twitter: _kaichen

以上就是我今天分享的內容,謝謝大家。

相關文章