1. 程式人生 > >Merkle Tree演算法

Merkle Tree演算法

Merkle Tree 是由電腦科學家 Ralph Merkle 在很多年前提出的,並以他本人的名字來命名。不過,Merkle Tree 確實涉及到了很多有意思的實際應用,如比特幣錢包服務用 Merkle Tree 的機制來作”百分百準備金證明“ (http://blog.csdn.net/lucky_greenegg/article/details/51155252), Git 版本控制系統,ZFS 檔案系統以及點對點網路 BT 下載,都是通過 Merkle Tree 來進行完整性校驗,就是檢查一下資料有沒有損壞。

完整性校驗

其實要實現完整性校驗,最簡單的方法就是對要校驗的整個的資料檔案做個雜湊運算。現在網上最流行的檔案校驗方式是計算機MD5和SHA1,微軟釋出Windows作業系統或其它軟體,現在都採用CRC32結合SHA1的方式,幾乎百分之一百不會發生碰撞。

因為雜湊的最大特點是,如果你的輸入資料,稍微變了一點點,那麼經過雜湊運算,你得到的雜湊值將會變得面目全非(SimHash除外),哪怕只是一點點的修改,Hash值都會完全不一樣如果我們從一個穩定的伺服器上進行下載,那麼採用單個雜湊來進行校驗的形式是可以接受的。

但從理論角度,CRC不能完全可靠的驗證資料完整性,因為CRC多項式是線性結構,很容易通過改變資料方式達到CRC碰撞,假設一串帶有CRC校驗的程式碼在傳輸中,如果連續出現差錯,當出錯次數達到一定次數時,那麼幾乎可以肯定會出現一次碰撞(值不對但CRC結果正確)。

資料分塊

在點對點網路中作資料傳輸的時候,我們會從同時從多個機器上下載資料,而且其中很多機器可以認為是不穩定或者是不可信的,這時需要有更加巧妙的做法。實際中,點對點網路在傳輸資料的時候,其實都是把比較大的一個檔案,切成小的資料塊。

BitTorrent協議是架構於TCP/IP協議之上的一個P2P檔案傳輸協議,處於TCP/IP結構的應用層。根據BitTorrent協議,檔案釋出者會根據要釋出的檔案生成提供一個.torrent檔案,即種子檔案,也簡稱為“種子”。torrent檔案本質上是文字檔案,包含Tracker資訊和檔案資訊兩部分。Tracker資訊主要是BT下載中需要用到的Tracker伺服器的地址和針對Tracker伺服器的設定,檔案資訊是根據對目標檔案的計算生成的,計算結果根據BitTorrent協議內的B編碼規則進行編碼。它的主要原理是需要把提供下載的檔案虛擬分成大小相等的塊,塊大小必須為2k的整數次方(由於是虛擬分塊,硬碟上並不產生各個塊檔案),並把每個塊的索引資訊和Hash驗證碼寫入.torrent檔案中;所以,.torrent檔案就是被下載檔案的“索引”。

下載者要下載檔案內容,需要先得到相應的.torrent檔案,然後使用BT客戶端軟體進行下載。載時,BT客戶端首先解析.torrent檔案得到Tracker地址,然後連線Tracker伺服器。Tracker伺服器迴應下載者的請求,提供下載者其他下載者(包括髮布者)的IP。下載者再連線其他下載者,根據.torrent檔案,兩者分別告知對方自己已經有的塊,然後交換對方沒有的資料。此時不需要其他伺服器參與,分散了單個線路上的資料流量,因此減輕了伺服器負擔。下載者每得到一個塊,需要算出下載塊的Hash驗證碼與.torrent檔案中的對比,如果一樣則說明塊正確,不一樣則需要重新下載這個塊。這種規定是為了解決下載內容準確性的問題。

一般的HTTP/FTP下載,釋出檔案僅在某個或某幾個伺服器,下載的人太多,伺服器的頻寬很易不勝負荷,變得很慢。而BitTorrent協議下載的特點是,下載的人越多,提供的頻寬也越多,種子也會越來越多,下載速度就越快


這樣的好處是,如果有一個小塊資料在傳輸過程中損壞了,那我只要重新下載這一個資料塊就行了,不用重新下載整個檔案。當然這就要求每個資料塊都擁有自己的雜湊值。BT 下載的時候,在下載真正的資料之前,我們會先下載一個雜湊列表的。這時有一個問題就出現了,那麼多的雜湊,我們怎麼保證它們本身都是正確地呢?答案是我們需要一個根雜湊。把每個小塊的雜湊值拼到一起,然後對整個這個長長的字串再做一次雜湊運算,最終的結果就是雜湊列表的根雜湊。於是,如果我們能夠從伺服器得到一個正確的根雜湊(Torrent檔案),就可以用它來校驗雜湊列表中的每一個雜湊都是正確的,進而可以保證下載的每一個數據塊的正確性了,這裡就要用到Merkle Tree演算法。

Merkle Tree

先看它的結構。


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


相對於 Hash List,Merkle Tree 的明顯的一個好處是可以單獨拿出一個分支來(作為一個小樹)對部分資料進行校驗,這個很多使用場合就帶來了雜湊列表所不能比擬的方便和高效。

如上圖所示,葉子節點node7的value = hash(f1),是f1檔案的HASH;而其父親節點node3的value = hash(v7, v8),也就是其子節點node7 node8的值得HASH。就是這樣表示一個層級運算關係。root節點的value其實是所有葉子節點的value的唯一特徵。

假如A上的檔案5與B上的不一樣。我們怎麼通過兩個機器的merkle treee資訊找到不相同的檔案? 這個比較檢索過程如下:

1、首先比較v0是否相同,如果不同,檢索其孩子node1和node2.

2、v1 相同,v2不同。檢索node2的孩子node5 node6;

3、v5不同,v6相同,檢索比較node5的孩子node 11 和node 12

4、v11不同,v12相同。node 11為葉子節點,獲取其目錄資訊。

5、檢索比較完畢。

以上過程的理論複雜度是Log(N)。實際過程是大於這個複雜度的,因為不同value的節點需要每個子節點進行比較。