哈希指针:不仅要存放地址,还要存放这个结构体的哈希值,一般用H表示一个哈希指针
这样做的好处是:我们不光可以找到这个结构体的位置,同时还能够检测出这个结构体的内容有没有被篡改,因为我们保存了他的哈希值。
比特币中一个最基本的数据结构就是区块链,那么区块链与普通的链表相比有什么区别呢
用哈希指针代替了普通的指针,Block chain is a linked list using hash pointers.
我们可以画一个区块链
这是一个小型的区块链,最前面的区块是系统中产生的第一个区块,叫做创世纪块(genesis block),最后一个区块是最近产生的区块,每个区块都包含指向前一个区块的哈希指针,H(),最后一个区块也有一个指向它的哈希指针H()保存在系统里。
【注意】取哈希的时候是把整个内容合在一起取哈希,比如第三个的哈希指针就是把前一个的整个内容包括里面的哈希指针合在一起算出一个哈希值来,由第三个指向第二个。
这种数据结构的好处是什么?
通过这样的数据结构可以实现tamper-evident log
比如说,有一个人篡改了区块链中的某个内容,假如改了第四个区块的内容,那么第五个区块的哈希指针就对不上,因为第五个区块的哈希指针是根据第四个区块的整个内容算出来的,所以呢,第五个区块的哈希指针也得改,而这个哈希指针一改,后面的哈希指针又对不上,所以也得改,到最后一个区块,系统中保留的那个哈希也得改,而这个哈希值是我们保存下来的,好处就是,只要记住最后这个哈希值,就可以检测出对区块链中任何部位的修改,不论是在哪个部位做了改动,最后都会导致我保存的这个哈希值会发生变化,这个是区块链和普通链表的一个区别,普通链表,你可以改变其中的一个元素,对其他元素是没有影响的,而区块链是牵一发而动全身,你改变前面任何一个区块,都会引发多米诺骨牌效应,后面所有的区块都得跟着改,有了这个性质,比特币的有些节点就不一定要保存整条区块链的内容,比如,他只要保存最近的几千个区块,以前的就不用存了,如果要用到以前的区块,可以去问系统中的其他节点去要这个区块,有些节点可能是有恶意的,这是个去中心化的系统,那怎么知道别人给你的区块是不是正确的,就要用到哈希指针的性质,比如说,我只保存了最后两个区块,前面的没有保存,别人给我一个倒数第三的区块,可以算一下里面的哈希值,再和他后面保存的倒数第二 区块的哈希值对比一下就知道了
比特币中的另外一个数据结构是Merkle tree,可能没有听说过Merkle tree,但一定听说过binary tree(二叉树)。
Merkle tree和binary tree的区别
下面的都是数据块(data blocks),上面的都是哈希指针(hash pointers)
好处:只要记住根哈希值(root hash),就能检测出任何部位的修改,跟上面的例子类似,且效率更高
比特币当中,各个区块用哈希指针连接在一起,每个区块包含的交易是组织成一个Merkle tree的形式,底下每一个数据块实际上是一个交易tx(transaction),每个区块分为两部分,分为块头和块身,block header和block body,这个block header里面有这个根哈希值,就这个区块所包含的所有交易组成的Merkle tree的根哈希值是存在这个区块的block header里面,但是block header里没有交易的具体内容,只有一个根哈希值,block body里面是有交易的列表的。
Merkle tree的作用
提供Merkle proof
比特币中的节点分为两类,一类是全节点,一类是轻节点
全节点:保存整个区块的内容block header,block body都有,有交易的具体信息。
轻节点:比如说手机上的比特币钱包的应用就属于轻节点,只保存block header
这样就带来一个问题,如果你要向一个轻节点证明某个交易是写入到区块链的,该怎么证明?
比如说,你向我买些东西,需要给我转一笔钱,你告诉我说,你给我转钱的交易已经写到区块链里了,这个支付已经完成了,我是个轻节点,比如我就只能在手机上查一查,我怎么知道这个交易是不是已经被写到区块链里了,这里就要用到Merkle proof了,接下行。
找到这个交易的所在的位置,从这个交易往上,一直到根节点的这条路径,这条路径就是我们说的Merkle proof
最下面这一行是它里面包含的交易,假设某个轻节点想知道图中标注黄色的这个交易是不是被包含在了这个Merkle tree里,这个轻节点没有保存交易列表,没有Merkle tree的具体内容,只有一个根哈希值,因为根哈希值是保存在block header里面的。这个轻节点向某个全节点发出请求,请求黄色交易被包含在Merkle tree里面的Merkle proof,全节点收到请求后,只要把图中标注红色的哈希值发给轻节点就行了,有了这个哈希值之后,轻节点可以在本地计算出图中标为绿色的这三个哈希值。
首先算出黄色交易的哈希值,然后根全节点提供的红色的哈希值拼接起来
可以算出上一层的绿色的哈希值,然后再跟旁边的红色的哈希值拼接起来
算出上一层的绿色的哈希值,再跟旁边的红色的哈希值拼接起来,就可以算出整棵树的根哈希值
轻节点把这个根哈希值和block header里的根哈希值比较一下,就能知道这个黄色的交易是不是在这个Merkle tree里,全节点在Merkle proof里提供的这几个哈希值就是从黄色交易叶节点的位置到树根的路径用到的这些哈希值,轻节点收到Merkle proof后,只要从下往上验证,沿途的这些哈希值都是正确的就行了,为什么?想想,你给我转钱的交易是不是真的写到了这个区块里,我是个轻节点,我只有一个block header,block header里只有这个根哈希值,你传给我这样一个路径,我检查一下这个哈希值算的都是正确的,注意,我查的时候只能查黄色交易分支上的哈希值,旁边的红色的哈希值,我是差不了的,毕竟那条路径里面没有黄色交易右边那个交易的内容,所以红色哈希值的正确性我不知道,上几层类似,我只要验证我有数据的交易所在的分支是符合要求的,只要这个根哈希值和block header里的根哈希值是一样的就行了,这是因为Merkle tree的性质,只要里面任何一个部位修改,就会影响到跟哈希值,如果跟哈希值是保持不变的,说明里面所有的内容都是没有被改过的。大家设想一下,如果我篡改了黄色交易,那它算出来的绿色的哈希值就会跟着改,要通过调旁边的红色的哈希值制造出整个节点的哈希值不变的情况,这是属于人为制造哈希碰撞,即使给你这个自由度,旁边的红色的哈希值可以任意修改,因为有collision resistance的性质,也制造不出这样的哈希碰撞。
刚刚的证明过程叫做proof of membership 或者proof of inclusion
对于轻节点来说,你发给我这样一个Merkle proof,我要验证他,复杂度是多少?
如果底下的一层一共有n个交易的话,那么Merkle proof的复杂度是
θ ( l o g ( n ) ) ; 这 是 比 较 高 效 的 θ(log(n));这是比较高效的 θ(log(n));这是比较高效的
那能不能证明Merkle tree里面没有包含某个交易,就是proof of non-membership,最简单的方法就是把整棵树传给轻节点,然后轻节点收到之后,我验证一下这个树的构造都是正确的,每一层用到的哈希值都是正确的
,说明这个树里,只有这个叶节点,没有别的,那我找的交易不在里面,就证明了proof of non-membership。这个复杂度是线性的θ(n),不是对数级别的,这是个比较笨的方法。那有什么高效的方法证明不存在。
如果不对这个叶节点的排列顺序做任何假设的话,那么是没有的,没有什么高效的方法,但如果对中国叶节点的排列顺序做要求。
比如说按照哈希值从小到大排序,比如要查找的那个交易在黄色交易和第四个交易的中间,这两个交易都要往上到根节点,轻节点收到后,验证一下这两个交易都要往上到根节点的路径都是正确的,根节点也是没有改动的,说明这两个节点在原来的Merkle tree确实是相邻的节点,如果找的那个交易应该是插在两者之间的,但是没有出现,所以这个交易就是不存在的,这个复杂度也是θ(log(n))的,代价是要排个序,排好序的叫做sorted Merkle tree.
比特币当中没有用到这个sorted Merkle tree,因为不需要做这个不存在证明。
今天讲的区块链和Merkle tree都用到了哈希指针,那哈希指针还能用到什么地方,其实只要是无环的,哈希指针,都可以代替普通指针
会出现循环依赖,这是不行的。