區塊鏈100講:梅克爾樹保障區塊鏈數據不可篡改,想換根就要砍樹!

image

區塊鏈100講上期咱們講了哈希算法公開密鑰算法,說到哈希算法提到了一個名詞「Merkle tree」,梅克爾樹,這棵樹是啥呢?它是如何工做的呢,它們又可以提供些什麼價值呢,如今以及將來的?咱們這期來說講「Merkle tree」。node

1

Merkle tree

梅克爾樹,又叫哈希樹,顧名思義,就是存儲hash值的一棵樹。是一種二叉樹,由一個根節點、一組中間節點和一組葉節點組成。最下面的葉節點包含存儲數據或其哈希值,每一箇中間節點是它的兩個孩子節點內容的哈希值,根節點也是由它的兩個子節點內容的哈希值組成。算法

 image

比特幣的一個重要特性,這區塊是存在一個多級數據結構中的 。一個區塊的「哈希值」實際上只是 這個區塊的頭信息的哈希值,一個大約 200 個字節的數據,其中包含了時間戳,隨機數,上一個區 塊的哈希和一個存儲了這個區塊中全部交易的稱之爲默克爾樹的數據結構的根哈希。 數據庫

2

梅克爾樹的結構和特徵

image

如圖,咱們來看梅克爾樹的數據結構,全部的數據區塊兩兩分組(如圖的最底層),指向這些數據的哈希指針被儲存在上一層的父節點(parent node)中。安全

而這些父節點再次被兩兩分組,而且指向父節點的指針被儲存在上一層的父節點中,一直持續這個過程,直到咱們獲得一個單一的區塊,即樹根節點。服務器

根據梅克爾樹的數據結構,咱們能夠清楚的瞭解到,只要咱們記住最前面的樹根節點的哈希指針,咱們就能夠根據哈希指針回溯到表中的任意位置,這讓咱們能保證表中的數據不被篡改。微信

若是有人篡改了梅克爾樹底部的一些數據區塊,會致使上一層的哈希指針不匹配,那麼他不得不一直篡改上一層的哈希指針,直到數的頂端,而此刻,篡改即將終止,由於咱們存儲了樹根節點的哈希指針。網絡

所以,只要咱們記住樹根頂部的哈希指針,任何企圖篡改數據的行爲都會被檢測到,這讓數據篡改變得不可能實現。數據結構

梅克爾樹的特色:函數

  • MT是一種樹,大多數是二叉樹,也能夠多叉樹,不管是幾叉樹,它都具備樹結構的全部特色;學習

  • Merkle Tree的葉子節點的value是數據集合的單元數據或者單元數據HASH。

  • 非葉子節點的value是根據它下面全部的葉子節點值,而後按照Hash算法計算而得出的。

一般,加密的hash方法像SHA-2和MD5用來作hash。但若是僅僅防止數據不是蓄意的損壞或篡改,能夠改用一些安全性低但效率高的校驗和算法,如CRC。

Second Preimage Attack: Merkle tree的樹根並不表示樹的深度,這可能會致使second-preimage attack,即攻擊者建立一個具備相同Merkle樹根的虛假文檔。一個簡單的解決方法在Certificate Transparency中定義:當計算葉節點的hash時,在hash數據前加0x00。當計算內部節點是,在前面加0x01。另一些實現限制hash tree的根,經過在hash值前面加深度前綴。所以,前綴每一步會減小,只有當到達葉子時前綴依然爲正,提取的hash鏈才被定義爲有效。

3

梅克爾樹的形式

一、二進制梅克爾樹

梅克爾樹最爲常見和最簡單的形式,是二進制梅克爾樹( binary Mekle tree),其中一bucket單位的數據塊老是包含了兩個相鄰的塊或哈希,它的描述以下:

image

那麼,這種奇怪的哈希算法有什麼好處麼?爲何不直接將這些數據塊串接成一個單獨的大塊,用常規的哈希算法進行呢?答案在於,它容許了一個整齊的機制,咱們稱之爲梅克爾證實(Merkle proofs)

image

一個梅克爾證實包含了一個數據塊,這顆梅克爾樹的根哈希,以及包含了全部沿數據塊到根路徑哈希的「分支」。有人認爲,這種證實能夠驗證哈希的過程,至少是對分支而言。應用也很簡單:假設有一個大數據庫,而該數據庫的所有內容都存儲在梅克爾樹中,而且這顆梅克爾樹的根是公開而且可信的(例如,它是由足夠多個受信方進行數字簽名過的,或者它有不少的工做量證實)。那麼,假如一位用戶想在數據庫中進行一次鍵值查找(好比:「請告訴我,位置在85273的對象」),那他就能夠詢問梅克爾證實,並接受到一個正確的驗證證實,他收到的值,其實是數據庫在85273位置的特定根。它容許了一種機制,既能夠驗證少許的數據,例如一個哈希,也能夠驗證大型的數據庫(可能擴至無限)。

比特幣系統的梅克爾證實

梅克爾證據的原始應用是比特幣系統(Bitcoin),它是由中本聰(Satoshi Nakamoto)在2009年描述而且創造的。比特幣區塊鏈使用了梅克爾證實,爲的是將交易存儲在每個區塊中:

image

而這樣作的好處,也就是中本聰描述到的「簡化支付驗證」(SPV)的概念:而不是下載每一筆交易以及每個區塊,一個「輕客戶端」(light client)能夠僅下載鏈的區塊頭,每一個區塊中僅包含五項內容,數據塊大小爲80字節:

  • 上一區塊頭的哈希值

  • 時間戳

  • 挖礦難度值

  • 工做量證實隨機數(nonce)

  • 包含該區塊交易的梅克爾樹的根哈希

若是一個輕客戶端但願肯定一筆交易的狀態,它能夠簡單地要求一個梅克爾證實,顯示出一個在梅克爾樹特定的交易,其根是在主鏈(main chain,非分叉鏈)上的區塊頭。

它會讓咱們走得很遠,但比特幣的輕客戶確實有其侷限性。一個特別的限制是,它們雖然能夠證實包含的交易,但沒法證實任何當前的狀態(例如:數字資產的持有,名稱註冊,金融合約的狀態等)。你如今擁有了多少個比特幣?一個比特幣輕客戶端,可使用一種協議,它涉及查詢多個節點,並相信其中至少會有一個節點會通知你,關於你的地址中任何特定的交易支出,而這可讓你實現更多的應用。但對於其餘更爲複雜的應用而言,這些遠遠是不夠的。一筆交易影響的確切性質(precise nature),能夠取決於此前的幾筆交易,而這些交易自己則依賴於更爲前面的交易,因此最終你能夠驗證整個鏈上的每一筆交易。爲了解決這個問題,以太坊的梅克爾樹的概念,會更進一步。

以太坊的梅克爾證實

以太坊的每個區塊頭,並不是只包含一顆梅克爾樹,而是包含了三顆梅克爾樹,分別對應了三種對象:

  • 交易(Transactions)

  • 收據(Receipts,基本上,它是展現每一筆交易影響的數據條)

  • 狀態(State)

image

這使得一個很是先進的輕客戶端協議成爲了可能,它容許輕客戶端輕鬆地進行並覈實如下類型的查詢答案:

  • 這筆交易被包含在特定的區塊中了麼?

  • 告訴我這個地址在過去30天中,發出X類型事件的全部實例(例如,一個衆籌合約完成了它的目標)

  • 目前個人帳戶餘額是多少?

  • 這個帳戶是否存在?

  • 僞裝在這個合約中運行這筆交易,它的輸出會是什麼?

第一種是由交易樹(transaction tree)來處理的;第三和第四種則是由狀態樹(state tree)負責處理,第二種則由收據樹(receipt tree)處理。計算前四個查詢任務是至關簡單的。服務器簡單地找到對象,獲取梅克爾分支,並經過分支來回復輕客戶端。

第五種查詢任務一樣也是由狀態樹處理,但它的計算方式會比較複雜。這裏,咱們須要構建下咱們稱之爲梅克爾狀態轉變的證實(Merkle state transition proof)。從本質上來說,這樣的證實也就是在說「若是你在根S的狀態樹上運行交易T,其結果狀態樹將是根爲S',log爲L,輸出爲O」 (「輸出」做爲存在於以太坊的一種概念,由於每一筆交易都是一個函數調用,它在理論上並非必要的)。

爲了推斷這個證實,服務器在本地建立了一個假的區塊,將狀態設爲 S,並僞裝是一個輕客戶端,同時請求這筆交易。也就是說,若是請求這筆交易的過程,須要客戶端肯定一個帳戶的餘額,這個輕客戶端會發出一個餘額疑問。若是這個輕客戶端須要檢查存儲在一個特定合約的特定項目,該輕客戶端會對此發出針對查詢。服務器會正確地「迴應」它全部的查詢,但服務器也會跟蹤它全部發回的數據。而後,服務器會把綜合數據發送給客戶端。客戶端會進行相同的步驟,但會使用它的數據庫所提供的證實。若是它的結果和服務器要求的是相同的,那客戶端就接受證實。

image

二、帕特里夏樹(Patricia Trees)

前面咱們提到,最爲簡單的一種梅克爾樹是二進制梅克爾樹。然而,以太坊所使用的梅克爾樹則更爲複雜,咱們稱之爲「梅克爾.帕特里夏樹」(Merkle Patricia tree)。

二進制梅克爾樹對於驗證「清單」格式的信息而言,它是很是好的數據結構,本質上來說,它就是一系列先後相連的數據塊。而對於交易樹來講,它們也一樣是不錯的,由於一旦樹已經創建,花多少時間來編輯這顆樹並不重要,樹一旦創建了,它就會永遠存在。

而對狀態樹來講,狀況會更復雜些。以太坊中的狀態樹基本上包含了一個鍵值映射,其中的鍵是地址還有各類值,包括帳戶的聲明、餘額、隨機數、代碼以及每個帳戶的存儲(其中存儲自己就是一顆樹)。例如,摩登測試網絡(the Morden testnet )的創始狀態以下所示:

{"0000000000000000000000000000000000000001": {"balance": "1"},"0000000000000000000000000000000000000002": {"balance": "1"},"0000000000000000000000000000000000000003": {"balance": "1"},"0000000000000000000000000000000000000004": {"balance": "1"},"102e61f5d8f9bc71d0ad4a084df4e65e05ce0e1c": {"balance": "1606938044258990275541962092341162602522202993782792835301376"}}

然而,不一樣於交易歷史記錄,狀態樹須要常常地進行更新:帳戶餘額和帳戶的隨機數nonce常常會更變,更重要的是,新的帳戶會頻繁地插入,存儲的鍵( key)也會常常被插入以及刪除。而這樣的數據結構設計,咱們能夠在一次插入、更新編輯或者刪除操做以後,快速地計算出新的樹根(tree root),而無需從新計算整顆樹。此外,它還有兩個灰常好的次要特性:

  • 樹的深度是有限制的,即便考慮攻擊者會故意地製造一些交易,使得這顆樹儘量地深。否則,攻擊者能夠經過操縱樹的深度,執行拒絕服務攻擊(DOS attack),使得更新變得極其緩慢。

  • 樹的根只取決於數據,和其中的更新順序無關。換個順序進行更新,甚至從新從頭計算樹,並不會改變根。

而帕特里夏樹,簡單地說,或許最接近的解釋是,咱們能夠同時實現全部的這些特性。其工做原理,最爲簡單的解釋是,一個以編碼形式存儲到記錄樹的「路徑」的值。每一個節點會有16個子(children),因此路徑是由十六進制編碼來肯定的:例如,狗(dog)的鍵的編碼爲6 4 6 15 6 7,因此你會從這個根開始,降低到第六個子,而後到第四個,並依次類推,直到你達到終點。在實踐中,當樹稀少時也會有一些額外的優化,咱們會使過程更爲有效,但這是基本的原則。

4

Merkle Tree的操做

一、建立Merckle Tree

加入最底層有9個數據塊。

  • step1:(紅色線)對數據塊作hash運算,Node0i = hash(Data0i), i=1,2,…,9

  • step2: (橙色線)相鄰兩個hash塊串聯,而後作hash運算,Node1((i+1)/2) = hash(Node0i+Node0(i+1)), i=1,3,5,7;對於i=9, Node1((i+1)/2) = hash(Node0i)

  • step3: (黃色線)重複step2

  • step4:(綠色線)重複step2

  • step5:(藍色線)重複step2,生成Merkle Tree Root 

image

易得,建立Merkle Tree是O(n)複雜度(這裏指O(n)次hash運算),n是數據塊的大小。獲得Merkle Tree的樹高是log(n)+1。

二、檢索數據塊

爲了更好理解,咱們假設有A和B兩臺機器,A須要與B相同目錄下有8個文件,文件分別是f1 f2 f3 ….f8。這個時候咱們就能夠經過Merkle Tree來進行快速比較。假設咱們在文件建立的時候每一個機器都構建了一個Merkle Tree。具體以下圖: 

image

從上圖可得知,葉子節點node7的value = hash(f1),是f1文件的HASH;而其父親節點node3的value = hash(v7, v8),也就是其子節點node7 node8的值得HASH。就是這樣表示一個層級運算關係。root節點的value實際上是全部葉子節點的value的惟一特徵。

假如A上的文件5與B上的不同。咱們怎麼經過兩個機器的merkle treee信息找到不相同的文件? 這個比較檢索過程以下:

  • Step1. 首先比較v0是否相同,若是不一樣,檢索其孩子node1和node2.

  • Step2. v1 相同,v2不一樣。檢索node2的孩子node5 node6;

  • Step3. v5不一樣,v6相同,檢索比較node5的孩子node 11 和node 12

  • Step4. v11不一樣,v12相同。node 11爲葉子節點,獲取其目錄信息。

  • Step5. 檢索比較完畢。

以上過程的理論複雜度是Log(N)。過程描述圖以下:

image

從上圖能夠得知真個過程能夠很快的找到對應的不相同的文件。

三、更新,插入和刪除

雖然網上有不少關於Merkle Tree的資料,但大部分沒有涉及Merkle Tree的更新、插入和刪除操做,討論Merkle Tree的檢索和遍歷的比較多。我也是很是困惑,一種樹結構的操做確定不只包括查找,也包括更新、插入和刪除的啊。後來查到stackexchange上的一個問題,才稍微有點明白。

對於Merkle Tree數據塊的更新操做實際上是很簡單的,更新完數據塊,而後接着更新其到樹根路徑上的Hash值就能夠了,這樣不會改變Merkle Tree的結構。可是,插入和刪除操做確定會改變Merkle Tree的結構,以下圖,一種插入操做是這樣的: 

image

插入數據塊0後(考慮數據塊的位置),Merkle Tree的結構是這樣的: 

image

在考慮一種插入的算法,知足下面條件:

  • re-hashing操做的次數控制在log(n)之內 

  • 數據塊的校驗在log(n)+1之內 

  • 除非原始樹的n是偶數,插入數據後的樹沒有孤兒,而且若是有孤兒,那麼孤兒是最後一個數據塊 

  • 數據塊的順序保持一致 

  • 插入後的Merkle Tree保持平衡

而後上面的插入結果就會變成這樣: 

image

Merkle Tree的插入和刪除操做實際上是一個工程上的問題,不一樣問題會有不一樣的插入方法。若是要確保樹是平衡的或者是樹高是log(n)的,能夠用任何的標準的平衡二叉樹的模式,如AVL樹,紅黑樹,伸展樹,2-3樹等。這些平衡二叉樹的更新模式能夠在O(lgn)時間內完成插入操做,而且能保證樹高是O(lgn)的。那麼很容易能夠看出更新全部的Merkle Hash能夠在O((lgn)2)時間內完成(對於每一個節點如要更新從它到樹根O(lgn)個節點,而爲了知足樹高的要求須要更新O(lgn)個節點)。若是仔細分析的話,更新全部的hash實際上能夠在O(lgn)時間內完成,由於要改變的全部節點都是相關聯的,即他們要不是都在從某個葉節點到樹根的一條路徑上,或者這種狀況相近。

實際上Merkle Tree的結構(是否平衡,樹高限制多少)在大多數應用中並不重要,並且保持數據塊的順序也在大多數應用中也不須要。所以,能夠根據具體應用的狀況,設計本身的插入和刪除操做。一個通用的Merkle Tree插入刪除操做是沒有意義的。

Merkle Tree目前的應用範圍包括:數字簽名、P2P網絡、Trusted Computing、比特幣以太坊的梅克爾證實等。

內容來源:

Vitalik Buterin博客、CSDN-星空專欄、陳建慧程序人生,公衆號-Astar區塊鏈實驗室、幣姥爺日記

補充閱讀:區塊鏈100講:從村裏的帳原本看什麼是區塊鏈

區塊鏈100講:區塊鏈爲何叫「區塊」「鏈」?

區塊鏈100講:總被提起的拜占庭問題究竟是什麼鬼?

區塊鏈100講:世界銀行說,比特幣給各國央行打了個樣

區塊鏈100講:用來抨擊區塊鏈的算力浪費,究竟是浪費了什麼?

區塊鏈100講:知乎千贊回答講清挖礦過程

區塊鏈100講:單挑or羣毆,兩種挖礦方式你要怎麼選?

區塊鏈100講:以太坊(Ethereum ETH)挖礦教程

區塊鏈100講:聽說,80%的人都搞不懂哈希算法

區塊鏈100講:公開密鑰算法不能不知道的4個概念

區塊鏈最全書單|深聊了50個微信羣,學習區塊鏈必讀這20本書

看了400多份白皮書,迴歸本質談區塊鏈技術(附所有白皮書下載連接)

如下是咱們的社區介紹,歡迎各類合做、交流、學習:)

image

相關文章
相關標籤/搜索