1. 程式人生 > >Merkle Tree(默克爾樹)演算法的 查詢(不是查詢節點用的,而是查詢錯亂節點),新建,更新

Merkle Tree(默克爾樹)演算法的 查詢(不是查詢節點用的,而是查詢錯亂節點),新建,更新

fei33423 Merkle Tree 

注意比特幣的 tree , spv 簡單支付證明,先通過服務端找到交易的塊. 即證明了.

Merkle Tree概念

這裡寫圖片描述

Merkle Tree,通常也被稱作Hash Tree,顧名思義,就是儲存hash值的一棵樹。Merkle樹的葉子是資料塊(例如,檔案或者檔案的集合)的hash值。非葉節點是其對應子節點串聯字串的hash。[1]

 phil 注: 這張圖,有誤導作用.節點不一定是2^n 次方.

1、Hash

Hash是一個把任意長度的資料對映成固定長度資料的函式[2]。例如,對於資料完整性校驗,最簡單的方法是對整個資料做Hash運算得到固定長度的Hash值,然後把得到的Hash值公佈在網上,這樣使用者下載到資料之後,對資料再次進行Hash運算,比較運算結果和網上公佈的Hash值進行比較,如果兩個Hash值相等,說明下載的資料沒有損壞。可以這樣做是因為輸入資料的稍微改變就會引起Hash運算結果的面目全非,而且根據Hash值反推原始輸入資料的特徵是困難的。[3]
  這裡寫圖片描述


如果從一個穩定的伺服器進行下載,採用單一Hash是可取的。但如果資料來源不穩定,一旦資料損壞,就需要重新下載,這種下載的效率是很低的。

2、Hash List
在點對點網路中作資料傳輸的時候,會同時從多個機器上下載資料,而且很多機器可以認為是不穩定或者不可信的。為了校驗資料的完整性,更好的辦法是把大的檔案分割成小的資料塊(例如,把分割成2K為單位的資料塊)。這樣的好處是,如果小塊資料在傳輸過程中損壞了,那麼只要重新下載這一快資料就行了,不用重新下載整個檔案。

怎麼確定小的資料塊沒有損壞哪?只需要為每個資料塊做Hash。BT下載的時候,在下載到真正資料之前,我們會先下載一個Hash列表。那麼問題又來了,怎麼確定這個Hash列表本事是正確的哪?答案是把每個小塊資料的Hash值拼到一起,然後對這個長字串在作一次Hash運算,這樣就得到Hash列表的根Hash(Top Hash or Root Hash)。下載資料的時候,首先從可信的資料來源得到正確的根Hash,就可以用它來校驗Hash列表了,然後通過校驗後的Hash列表校驗資料塊。
這裡寫圖片描述

3、 Merkle Tree

Merkle Tree可以看做Hash List的泛化(Hash List可以看作一種特殊的Merkle Tree,即樹高為2的多叉Merkle Tree)。

在最底層,和雜湊列表一樣,我們把資料分成小的資料塊,有相應地雜湊和它對應。但是往上走,並不是直接去運算根雜湊,而是把相鄰的兩個雜湊合併成一個字串,然後運算這個字串的雜湊,這樣每兩個雜湊就結婚生子,得到了一個”子雜湊“。如果最底層的雜湊總數是單數,那到最後必然出現一個單身雜湊,這種情況就直接對它進行雜湊運算,所以也能得到它的子雜湊。於是往上推,依然是一樣的方式,可以得到數目更少的新一級雜湊,最終必然形成一棵倒掛的樹,到了樹根的這個位置,這一代就剩下一個根雜湊了,我們把它叫做 Merkle Root[3]。

在p2p網路下載網路之前,先從可信的源獲得檔案的Merkle Tree樹根。一旦獲得了樹根,就可以從其他從不可信的源獲取Merkle tree。通過可信的樹根來檢查接受到的Merkle Tree。如果Merkle Tree是損壞的或者虛假的,就從其他源獲得另一個Merkle Tree,直到獲得一個與可信樹根匹配的Merkle Tree。

Merkle Tree和Hash List的主要區別是,可以直接下載並立即驗證Merkle Tree的一個分支。因為可以將檔案切分成小的資料塊,這樣如果有一塊資料損壞,僅僅重新下載這個資料塊就行了。如果檔案非常大,那麼Merkle tree和Hash list都很到,但是Merkle tree可以一次下載一個分支,然後立即驗證這個分支,如果分支驗證通過,就可以下載資料了。而Hash list只有下載整個hash list才能驗證。
  這裡寫圖片描述

Merkle Tree的特點

  1. MT是一種樹,大多數是二叉樹,也可以多叉樹,無論是幾叉樹,它都具有樹結構的所有特點; phil 注: 注意不是二叉排序樹
  2. Merkle Tree的葉子節點的value是資料集合的單元資料或者單元資料HASH。
  3. 非葉子節點的value是根據它下面所有的葉子節點值,然後按照Hash演算法計算而得出的。[4][5]
      

通常,加密的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鏈才被定義為有效。

Merkle Tree的操作

1、建立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
這裡寫圖片描述
易得,建立Merkle Tree是O(n)複雜度(這裡指O(n)次hash運算),n是資料塊的大小。得到Merkle Tree的樹高是log(n)+1。

2、檢索資料塊

為了更好理解,我們假設有A和B兩臺機器,A需要與B相同目錄下有8個檔案,檔案分別是f1 f2 f3 ….f8。這個時候我們就可以通過Merkle Tree來進行快速比較。假設我們在檔案建立的時候每個機器都構建了一個Merkle Tree。具體如下圖:
這裡寫圖片描述
從上圖可得知,葉子節點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)。過程描述圖如下:

這裡寫圖片描述
從上圖可以得知真個過程可以很快的找到對應的不相同的檔案。

3、更新,插入和刪除

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

  對於Merkle Tree資料塊的更新操作其實是很簡單的,更新完資料塊,然後接著更新其到樹根路徑上的Hash值就可以了,這樣不會改變Merkle Tree的結構。但是,插入和刪除操作肯定會改變Merkle Tree的結構,如下圖,一種插入操作是這樣的:
這裡寫圖片描述
插入資料塊0後(考慮資料塊的位置),Merkle Tree的結構是這樣的:
這裡寫圖片描述
而[6]中的同學在考慮一種插入的演算法,滿足下面條件:
- re-hashing操作的次數控制在log(n)以內
- 資料塊的校驗在log(n)+1以內
- 除非原始樹的n是偶數,插入資料後的樹沒有孤兒,並且如果有孤兒,那麼孤兒是最後一個數據塊
- 資料塊的順序保持一致
- 插入後的Merkle Tree保持平衡

然後上面的插入結果就會變成這樣:
這裡寫圖片描述
根據[6]中回答者所說,Merkle Tree的插入和刪除操作其實是一個工程上的問題,不同問題會有不同的插入方法。如果要確保樹是平衡的或者是樹高是log(n)的,可以用任何的標準的平衡二叉樹的模式,如AVL樹,紅黑樹,伸展樹,2-3樹等。這些平衡二叉樹的更新模式可以在O(lgn)時間內完成插入操作,並且能保證樹高是O(lgn)的。那麼很容易可以看出更新所有的Merkle Hash可以在O((lgn)2)時間內完成(對於每個節點如要更新從它到樹根O(lgn)個節點,而為了滿足樹高的要求需要更新O(lgn)個節點)。如果仔細分析的話,更新所有的hash實際上可以在O(lgn)時間內完成,因為要改變的所有節點都是相關聯的,即他們要不是都在從某個葉節點到樹根的一條路徑上,或者這種情況相近。

[6]的回答者說實際上Merkle Tree的結構(是否平衡,樹高限制多少)在大多數應用中並不重要,而且保持資料塊的順序也在大多數應用中也不需要。因此,可以根據具體應用的情況,設計自己的插入和刪除操作。一個通用的Merkle Tree插入刪除操作是沒有意義的。