1. 程式人生 > >Merkle Tree(默克爾樹)

Merkle Tree(默克爾樹)

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

 * 1. Hash*

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


如果從一個穩定的伺服器進行下載,採用單一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的特點

  • 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鏈才被定義為有效。
  
  

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上的一個問題,才稍微有點明白。

  對於Merkle Tree資料塊的更新操作其實是很簡單的,更新完資料塊,然後接著更新其到樹根路徑上的Hash值就可以了,這樣不會改變Merkle Tree的結構。但是,插入和刪除操作肯定會改變Merkle Tree的結構,如下圖,一種插入操作是這樣的:
  
  

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

插入的演算法,滿足下面條件:

  • re-hashing操作的次數控制在log(n)以內
  • 資料塊的校驗在log(n)+1以內
  • 除非原始樹的n是偶數,插入資料後的樹沒有孤兒,並且如果有孤兒,那麼孤兒是最後一個數據塊
  • 資料塊的順序保持一致
  • 插入後的Merkle Tree保持平衡

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

  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的應用

1. 數字簽名

  最初Merkle Tree目的是高效的處理Lamport one-time signatures。 每一個Lamport key只能被用來簽名一個訊息,但是與Merkle tree結合可以來簽名多條Merkle。這種方法成為了一種高效的數字簽名框架,即Merkle Signature Scheme。

2. P2P網路

  在P2P網路中,Merkle Tree用來確保從其他節點接受的資料塊沒有損壞且沒有被替換,甚至檢查其他節點不會欺騙或者釋出虛假的塊。大家所熟悉的BT下載就是採用了P2P技術來讓客戶端之間進行資料傳輸,一來可以加快資料下載速度,二來減輕下載伺服器的負擔。BT即BitTorrent,是一種中心索引式的P2P檔案分分析通訊協議[7]。

  要進下載必須從中心索引伺服器獲取一個副檔名為torrent的索引檔案(即大家所說的種子),torrent檔案包含了要共享檔案的資訊,包括檔名,大小,檔案的Hash資訊和一個指向Tracker的URL[8]。Torrent檔案中的Hash資訊是每一塊要下載的檔案內容的加密摘要,這些摘要也可執行在下載的時候進行驗證。大的torrent檔案是Web伺服器的瓶頸,而且也不能直接被包含在RSS或gossiped around(用流言傳播協議進行傳播)。一個相關的問題是大資料塊的使用,因為為了保持torrent檔案的非常小,那麼資料塊Hash的數量也得很小,這就意味著每個資料塊相對較大。大資料塊影響節點之間進行交易的效率,因為只有當大資料塊全部下載下來並校驗通過後,才能與其他節點進行交易。

  就解決上面兩個問題是用一個簡單的Merkle Tree代替Hash List。設計一個層數足夠多的滿二叉樹,葉節點是資料塊的Hash,不足的葉節點用0來代替。上層的節點是其對應孩子節點串聯的hash。Hash演算法和普通torrent一樣採用SHA1。其資料傳輸過程和第一節中描述的類似。
  
  

3. Trusted Computing

  可信計算是可信計算組為分散式計算環境中參與節點的計算平臺提供端點可信性而提出的。可信計算技術在計算平臺的硬體層引入可信平臺模組(Trusted Platform,TPM),實際上為計算平臺提供了基於硬體的可信根(Root of trust,RoT)。從可信根出發,使用信任鏈傳遞機制,可信計算技術可對本地平臺的硬體及軟體實施逐層的完整性度量,並將度量結果可靠地儲存再TPM的平臺配置暫存器(Platform configuration register,PCR)中,此後遠端計算平臺可通過遠端驗證機制(Remote Attestation)比對本地PCR中度量結果,從而驗證本地計算平臺的可信性。可信計算技術讓分散式應用的參與節點擺脫了對中心伺服器的依賴,而直接通過使用者機器上的TPM晶片來建立信任,使得建立擴充套件性更好、可靠性更高、可用性更強的安全分散式應用成為可能。可信計算技術的核心機制是遠端驗證(remote attestation),分散式應用的參與結點正是通過遠端驗證機制來建立互信,從而保障應用的安全。

首先,RAMT 在核心中維護的不再是一張完整性度量值列表(ML),而是一棵完整性度量值雜湊樹(integrity measurement hash tree,簡稱IMHT).其中,IMHT的葉子結點儲存的資料物件是待驗證計算平臺上被度量的各種程式的完整性雜湊值,而其內部結點則依據Merkle 雜湊樹的構建規則由子結點的連線的雜湊值動態生成。

  其次,為了維護IMHT 葉子結點的完整性,RAMT 需要使用TPM 中的一段儲存器來儲存IMHT 可信根雜湊的值。

  再次,RAMT 的完整性驗證過程基於認證路徑(authentication path)實施.認證路徑是指IMHT 上從待驗證葉子結點到根雜湊的路徑。

4. IPFS

  IPFS(InterPlanetary File System)是很多NB的網際網路技術的綜合體,如DHT( Distributed HashTable,分散式雜湊表),Git版本控制系統,Bittorrent等。它建立了一個P2P的叢集,這個叢集允許IPFS物件的交換。全部的IPFS物件形成了一個被稱作Merkle DAG的加密認證資料結構。

IPFS物件是一個含有兩個域的資料結構:

  • Data – 非結構的二進位制資料,大小小於256kB
  • Links – 一個Link資料結構的陣列。IPFS物件通過他們連結到其他物件

Link資料結構包含三個域:

  • Name – Link的名字
  • Hash – Link連結到物件的Hash
  • Size – Link連結到物件的累積大小,包括它的Links

通過Name和Links,IPFS的集合組成了一個Merkle DAG(有向無環圖)。

對於小檔案(<256kB),是一個沒有Links的IPFS物件。

對於大檔案,被表示為一個檔案塊(<256kB)的集合。只有擁有最小的Data的物件來代表這個大檔案。這個物件的Links的名字都為空字串。

目錄結構:目錄是沒有資料的IPFS物件,它的連結指向其包含的檔案和目錄。

 IPFS可以表示Git使用的資料結構,Git commit object。Commit Object主要的特點是他有一個或多個名為’parent0’和‘parent1’等的連結(這些連結指向前一個版本),以及一個名為object的物件(在Git中成為tree)指向引用這個commit的檔案系統結構。
 
 

5. BitCoin和Ethereum

Merkle Proof最早的應用是Bitcoin,它是由中本聰在2009年描述並建立的。Bitcoin的Blockchain利用Merkle proofs來儲存每個區塊的交易。

而這樣做的好處,也就是中本聰描述到的“簡化支付驗證”(Simplified Payment Verification,SPV)的概念:一個“輕客戶端”(light client)可以僅下載鏈的區塊頭即每個區塊中的80byte的資料塊,僅包含五個元素,而不是下載每一筆交易以及每一個區塊:

  • 上一區塊頭的雜湊值
  • 時間戳
  • 挖礦難度值
  • 工作量證明隨機數(nonce)
  • 包含該區塊交易的Merkle Tree的根雜湊

如果客戶端想要確認一個交易的狀態,它只需簡單的發起一個Merkle proof請求,這個請求顯示出這個特定的交易在Merkle trees的一個之中,而且這個Merkle Tree的樹根在主鏈的一個區塊頭中。

  但是Bitcoin的輕客戶端有它的侷限。一個侷限是,儘管它可以證明包含的交易,但是它不能進行涉及當前狀態的證明(如數字資產的持有,名稱註冊,金融合約的狀態等)。

  Bitcoin如何查詢你當前有多少幣?一個比特幣輕客戶端,可以使用一種協議,它涉及查詢多個節點,並相信其中至少會有一個節點會通知你,關於你的地址中任何特定的交易支出,而這可以讓你實現更多的應用。但對於其他更為複雜的應用而言,這些遠遠是不夠的。一筆交易影響的確切性質(precise nature),可以取決於此前的幾筆交易,而這些交易本身則依賴於更為前面的交易,所以最終你可以驗證整個鏈上的每一筆交易。為了解決這個問題,Ethereum的Merkle Tree的概念,會更進一步。
  
Ethereum的Merkle Proof

 每個以太坊區塊頭不是包括一個Merkle樹,而是為三種物件設計的三棵樹:

  • 交易Transaction
  • 收據Receipts(本質上是顯示每個交易影響的多塊資料)
  • 狀態State

這使得一個非常先進的輕客戶端協議成為了可能,它允許輕客戶端輕鬆地進行並核實以下型別的查詢答案:

  • 這筆交易被包含在特定的區塊中了麼?
  • 告訴我這個地址在過去30天中,發出X型別事件的所有例項(例如,一個眾籌合約完成了它的目標)
  • 目前我的賬戶餘額是多少?
  • 這個賬戶是否存在?
  • 假如在這個合約中執行這筆交易,它的輸出會是什麼?

第一種是由交易樹(transaction tree)來處理的;第三和第四種則是由狀態樹(state tree)負責處理,第二種則由收據樹(receipt tree)處理。計算前四個查詢任務是相當簡單的。伺服器簡單地找到物件,獲取Merkle分支,並通過分支來回復輕客戶端。

  第五種查詢任務同樣也是由狀態樹處理,但它的計算方式會比較複雜。這裡,我們需要構建一個Merkle狀態轉變證明(Merkle state transition proof)。從本質上來講,這樣的證明也就是在說“如果你在根S的狀態樹上執行交易T,其結果狀態樹將是根為S’,log為L,輸出為O” (“輸出”作為存在於以太坊的一種概念,因為每一筆交易都是一個函式呼叫;它在理論上並不是必要的)。

  為了推斷這個證明,伺服器在本地建立了一個假的區塊,將狀態設為 S,並在請求這筆交易時假裝是一個輕客戶端。也就是說,如果請求這筆交易的過程,需要客戶端確定一個賬戶的餘額,這個輕客戶端(由伺服器模擬的)會發出一個餘額查詢請求。如果需要輕客戶端在特點某個合約的儲存中查詢特定的條目,這個輕客戶端就會發出這樣的請求。也就是說伺服器(通過模擬一個輕客戶端)正確迴應所有自己的請求,但伺服器也會跟蹤它所有發回的資料。

  然後,伺服器從上述的這些請求中把資料合併並把資料以一個證明的方式傳送給客戶端。

  然後,客戶端會進行相同的步驟,但會將伺服器提供的證明作為一個數據庫來使用。如果客戶端進行步驟的結果和伺服器提供的是一樣的話,客戶端就接受這個證明。
  
 

MPT(Merkle Patricia Trees)

前面我們提到,最為簡單的一種Merkle Tree大多數情況下都是一棵二叉樹。然而,Ethereum所使用的Merkle Tree則更為複雜,我們稱之為“梅克爾.帕特里夏樹”(Merkle Patricia tree)。

  對於驗證屬於list格式(本質上來講,它就是一系列前後相連的資料塊)的資訊而言,二叉Merkle Tree是非常好的資料結構。對於交易樹來說,它們也同樣是不錯的,因為一旦樹已經建立,花多少時間來編輯這棵樹並不重要,樹一旦建立了,它就會永遠存在並且不會改變。

  但是,對於狀態樹,情況會更復雜些。以太坊中的狀態樹基本上包含了一個鍵值對映,其中的鍵是地址,而值包括賬戶的宣告、餘額、隨機數nounce、程式碼以及每一個賬戶的儲存(其中儲存本身就是一顆樹)。例如,摩登測試網路(the Morden testnet )的創始狀態如下所示:
  
  

然而,不同於交易歷史記錄,狀態樹需要經常地進行更新:賬戶餘額和賬戶的隨機數nonce經常會更變,更重要的是,新的賬戶會頻繁地插入,儲存的鍵( key)也會經常被插入以及刪除。我們需要這樣的資料結構,它能在一次插入、更新、刪除操作後快速計算到樹根,而不需要重新計算整個樹的Hash。這種資料結構同樣得包括兩個非常好的第二特徵:

  • 樹的深度是有限制的,即使考慮攻擊者會故意地製造一些交易,使得這顆樹儘可能地深。不然,攻擊者可以通過操縱樹的深度,執行拒絕服務攻擊(DOS attack),使得更新變得極其緩慢。
  • 樹的根只取決於資料,和其中的更新順序無關。換個順序進行更新,甚至重新從頭計算樹,並不會改變根。

      MPT是最接近同時滿足上面的性質的的資料結構。MPT的工作原理的最簡單的解釋是,值通過鍵來儲存,鍵被編碼到搜尋樹必須要經過的路徑中。每個節點有16個孩子,因此路徑又16進位制的編碼決定:例如,鍵‘dog’的16進位制編碼是6 4 6 15 6 7,所以從root開始到第六個分支,然後到第四個,再到第六個,再到第十五個,這樣依次進行到達樹的葉子。

      在實踐中,當樹稀少時也會有一些額外的優化,我們會使過程更為有效,但這是基本的原則。

本文為轉發,原本地址