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协议下载的特点是,下载的人越多,提供的带宽也越多,种子也会越来越多,下载速度就越快

Merkle Tree算法_第1张图片Merkle Tree算法_第2张图片

这样的好处是,如果有一个小块数据在传输过程中损坏了,那我只要重新下载这一个数据块就行了,不用重新下载整个文件。当然这就要求每个数据块都拥有自己的哈希值。BT 下载的时候,在下载真正的数据之前,我们会先下载一个哈希列表的。这时有一个问题就出现了,那么多的哈希,我们怎么保证它们本身都是正确地呢?答案是我们需要一个根哈希。把每个小块的哈希值拼到一起,然后对整个这个长长的字符串再做一次哈希运算,最终的结果就是哈希列表的根哈希。于是,如果我们能够从服务器得到一个正确的根哈希(Torrent文件),就可以用它来校验哈希列表中的每一个哈希都是正确的,进而可以保证下载的每一个数据块的正确性了,这里就要用到Merkle Tree算法。

Merkle Tree

先看它的结构。

Merkle Tree算法_第3张图片

在最底层,和哈希列表一样,我们把数据分成小的数据块,有相应地哈希和它对应。但是往上走,并不是直接去运算根哈希,而是把相邻的两个哈希合并成一个字符串,然后运算这个字符串的哈希,这样每两个哈希就结婚生子,得到了一个”子哈希“。如果最底层的哈希总数是单数,那到最后必然出现一个单身哈希,这种情况就直接对它进行哈希运算,所以也能得到它的子哈希。于是往上推,依然是一样的方式,可以得到数目更少的新一级哈希,最终必然形成一棵倒挂的树,到了树根的这个位置,这一代就剩下一个根哈希了,我们把它叫做 Merkle root.

Merkle Tree算法_第4张图片


相对于 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的节点需要每个子节点进行比较。


你可能感兴趣的:(区块链技术原理)