按照中本聪的原文,SPV指的是“支付验证“,而不是“交易验证”。这两种验证有很大区别。
“交易验证”非常复杂,涉及到验证是否有足够余额可供支出、是否存在双花、脚本能否通过等等,通常由运行完全节点的矿工来完成。
“支付验证”则比较简单,只判断用于“支付”的那笔交易是否已经被验证过,并得到了多少的算力保护(多少确认数)。
spv会在区块链查找交易(为了验证支付),并且需要连接到一个全节点来检索必要的数据。这个机制允许在仅运行一个全节点的情况下有多个轻钱包。
为了实现 SPV,需要有一个方式来检查是否一个区块包含了某笔交易,而无须下载整个区块。这就是 Merkle 树所要完成的事情。
每个块都会有一个 Merkle 树,它从叶子节点(树的底部)开始,一个叶子节点就是一个交易哈希(比特币使用双 SHA256 哈希)。叶子节点的数量必须是双数,但是并非每个块都包含了双数的交易。因为,如果一个块里面的交易数为单数,那么就将最后一个叶子节点(也就是 Merkle 树的最后一个交易,不是区块的最后一笔交易)复制一份凑成双数。
从下往上,两两成对,连接两个节点哈希,将组合哈希作为新的哈希。新的哈希就成为新的树节点。重复该过程,直到仅有一个节点,也就是树根。根哈希然后就会当做是整个块交易的唯一标示,将它保存到区块头,然后用于工作量证明。
Merkle 树的好处就是一个节点可以在不下载整个块的情况下,验证是否包含某笔交易。并且这些只需要一个交易哈希,一个 Merkle 树根哈希和一个 Merkle 路径。
我们看下用go怎么实现Merkle 树:
1.先从结构体开始
每个 MerkleNode 包含数据和指向左右分支的指针。MerkleTree 实际上就是连接到下个节点的根节点,然后依次连接到更远的节点,等等:
type MerkleTree struct {
RootNode *MerkleNode
}
type MerkleNode struct {
Left *MerkleNode
Right *MerkleNode
Data []byte
}
2.创建一个新的节点
由两个MerkleNode 和他们拼接成一起然后计算的hash的data组成的小树组成:
func NewMerkleNode(left, right *MerkleNode, data []byte) *MerkleNode {
mNode := MerkleNode{}
//左右节点为null表示第一次计算直接hash数据
if left == nil && right == nil {
hash := sha256.Sum256(data)
mNode.Data = hash[:]
} else {
prevHashes := append(left.Data, right.Data...)
hash := sha256.Sum256(prevHashes)
mNode.Data = hash[:]
}
mNode.Left = left
mNode.Right = right
return &mNode
}
3.生成Merkle 树
将传过来的交易数据循环hash计算形成Merkle 树
func NewMerkleTree(data [][]byte) *MerkleTree {
var nodes []MerkleNode
//因为,如果一个块里面的交易数为单数,那么就将最后一个叶子节点(也就是 Merkle 树的最后一个交易,不是区块的最后一笔交易)复制一份凑成双数
if len(data)%2 != 0 {
data = append(data, data[len(data)-1])
}
//将所有的交易数据进行第一次hash计算放到一个数组,数组长度为偶数
for _, datum := range data {
node := NewMerkleNode(nil, nil, datum)
nodes = append(nodes, *node)
}
//一次循环数组长度变为原来的1/2,所以外层时间复杂度为O(logn)
for i := 0; i < len(data)/2; i++ {
var newLevel []MerkleNode
//数组第1和第2组成新的数组第一位的node,依次递推
for j := 0; j < len(nodes); j += 2 {
node := NewMerkleNode(&nodes[j], &nodes[j+1], nil)
newLevel = append(newLevel, *node)
}
nodes = newLevel
}
//最终数组里面只会有一个node即Merkle 树根节点
mTree := MerkleTree{&nodes[0]}
return &mTree
}
上面就形成了比特币区块头的Merkle 树根节点,在检查交易是否存在的时候就简单多了。
。
验证某个交易是否真实存在时,理论上,用户可以通过以下方式进行验证:
(为了简化模型,我们假设用tx_hash来定位block。这种方法有被“交易可锻性”攻击的风险,实际应用中可以根据output_point来定位。)
假设需要验证Data01是否在该区块中,那么我需要得到Node12 ,Node22,Node32的Hash,这些都必须通过与全节点的交互获得,得到这些之后,就可以依据构建规则构建树直至根,然后和Merkle 树根节点比对即可确定这个交易是否存在该区块。