一个比特币区块可以分为区块头和该区块包含的所有交易数据,区块头的大小是80字节,而一个交易的平均大小是250字节,平均一个区块包含超过500个交易,所以一个完整的区块大小几乎是区块头的1000倍。一个包含完整区块链数据的全节点占有可观的存储空间,但是许多比特币客户端(如智能手机等)不具备这样的条件,它们通常采用 SPV 技术只存储所有区块头组成的链表,这种设备被称为 SPV 客户端。

SPV 如何验证交易

SPV 节点因为没有交易数据,没办法像全节点一样独立验证一个交易,它必须向周围的全节点查询交易是否存在某个区块中,如果在一个区块中,并且这个区块已经得到做够多的确认(已经有6个新块在其上生成了),从而这个交易得到认证。 SPV 节点与周围的全节点建立连接,通过发送 getheaders 消息给对方来获取区块头,并且在连接上设置过滤器,获取自己感兴趣的交易数据以及新生成的区块头。

Merkle 树

SPV 节点没有完整的交易数据,是如何验证某个交易是否存在于区块中的呢?这里用到了一个数据结构 Merkle 树:

Hash Tree

Merkle 树是一棵满二叉树,叶节点是这个区块中各个交易数据的Hash值(运行两次SHA256),非页节点是对两个子节点的连接(HA+HB)求Hash值:

Merkle Tree

  1. HA = SHA256(SHA256(Transaction A Data))
  2. HB = SHA256(SHA256(Transaction B Data))
  3. HAB = SHA256(SHA256(HA+HB))

为了生成一棵满二叉树,当交易个数是奇数的时候,会复制最后一个交易作最后一个叶节点。 Merkele 树就是按照以上描述的规则之下而上生成,生成的根节点最后会保存到区块头中。

SPV 使用 Merkle 路径验证交易

现在SPV 节点想验证上图(Merkle Tree)中的交易A是否存在,只需要知道节点HB和HCD就可以算出来根节点,然后与区块头中保存的根节点作比较,如果相匹配就验证了A存在该区块中。在这里HB和HCD称作交易A的 Merkle 路径。

SPV 节点在和其他全节点的连接上设置了过滤器表示对A交易感兴趣,当有一个全节点遇到A交易后,就会通过 merkleblock 消息把该交易数据和其所在的区块头,以及 Mekle 路径数据传给它,SPV 节点可以验证A是否存在该区块中,并把区块头链接到自己的区块链中。

Bloom 过滤器

上文说SPV 节点可以设置过滤器过滤自己感兴趣的交易,而不用接收所有交易数据。这个过滤器最简单的实现是直接告诉对方想要什么样输出地址的交易等,从而可以在SPV 节点构建一个钱包。但是这个信息很可能被网络上的不怀好意的人监听,导致泄漏了自己的隐私。

Bloom 过滤器可以描述一个你想要的搜索模式而不用直接告诉具体的东西(就像在搜索引擎中搜索关键字或者设置一个正则表达式匹配字符串一样),它提供了一种高效的方式可以让你表达你想要的搜索模式的同时可以保护你的隐私。

使用者可以微调搜索模式,但是搜索模式越精确会带来越差的私密性,反之模式越不精确可能私密性越好。Bloom 过滤器的具体实现细节可以参考维基百科Bloom filter,类似对原始数据做哈希运算,然后把输出信息编码到一个二进制串里面。

SPV 节点会先把 bloom filter 初始化为空,意味着匹配不到任何数据,然后会把感兴趣的比特币地址,公钥,交易id,作为各个匹配模式集成到一个 bloom filter中,之后通过 filterload 消息发到全节点,在全节点这一方则会把每一个交易对这个 bloom filter做过滤,把匹配到的数据发回给 SPV 节点。

SPV 风险

SPV 节点可以验证一个交易是否存在,但是不能证实一个交易不存在,结果就是容易被不诚实的节点攻击,例如双花同一笔UTXO(Unspent Transaction Output),SPV 节点不能证实这个UTXO没有被其他交易花掉,有可能这个已经被花费的交易被不诚实的节点隐藏起来了。

虽然bloom filter 过滤器在一定程度上保护了 SPV 节点的隐私,但是并不是完全私密的,只要在网络上进行长期监控收集到足够多的数据,还是能获取到有用信息的。

为了解决这些风险,可以对连接通道进行加密并对连接的节点进行认证。