比特币原理及其代码实践7-Merkle树


回忆:前面说过,比特币交易的解锁和上锁其实是一种基于堆栈的脚本语言,大家可以想一下,这种脚本语言还能干什么?

比特币的脚本语言启发了太坊创始人Vitalik Buterin(那年他20岁)创建以太坊,开发了智能合约。

全节点和轻节点

因为比特币的去中心化特性,网络中的每个节点必须是独立,自给自足的,也就是每个节点必须存储一个区块链的完整副本

随着越来越多的人使用比特币,这条规则变得越来越难以遵守:因为不太可能每个人都去运行一个全节点。并且,由于节点是网络中的完全参与者,它们负有相关责任:节点必须验证交易和区块。另外,要想与其他节点交互和下载新块,也有一定的网络流量需求。

对这个问题也有一个解决方案:简易支付验证(Simplified Payment Verification, SPV)。SPV 是一个比特币轻节点,它不需要下载整个区块链,也不需要验证区块和交易,它只用维护区块链header的信息。相反,它会在区块链查找交易(为了验证支付),并且需要连接到一个全节点来检索必要的数据。这个机制允许在仅运行一个全节点的情况下有多个轻钱包。

为了实现 SPV,需要有一个方式来检查是否一个区块包含了某笔交易,而无须下载整个区块。这就是 Merkle 树所要完成的事情。

Merkle树

每个块都会有一个 Merkle 树,一个叶子节点就是一个交易哈希。

叶子节点的数量必须是双数。

根哈希会当做是整个块交易的唯一标示,将它保存到区块头,然后用于工作量证明。

//Merkle树
type MerkleTree struct {
    //根节点
    RootNode *MerkleNode
}

//Merkle节点
type MerkleNode struct {
    Left  *MerkleNode
    Right *MerkleNode
    //hash值
    Data  []byte
}

//创建节点
func NewMerkleNode(left, right *MerkleNode, data []byte) *MerkleNode {
    mNode := MerkleNode{}

    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
}

//生成MerkleTree
func NewMerkleTree(data [][]byte) *MerkleTree {
    var nodes []MerkleNode

    if len(data)%2 != 0 {
        data = append(data, data[len(data)-1])
    }

    for _, datum := range data {
        node := NewMerkleNode(nil, nil, datum)
        nodes = append(nodes, *node)
    }

    for i := 0; i < len(data)/2; i++ {
        var newLevel []MerkleNode

        for j := 0; j < len(nodes); j += 2 {
            node := NewMerkleNode(&nodes[j], &nodes[j+1], nil)
            newLevel = append(newLevel, *node)
        }

        nodes = newLevel
    }

    mTree := MerkleTree{&nodes[0]}

    return &mTree
}

//获得block的bash值,实际就是取的根节点的hash值
func (b *Block) HashTransactions() []byte {
    var transactions [][]byte

    for _, tx := range b.Transactions {
        transactions = append(transactions, tx.Serialize())
    }
    mTree := NewMerkleTree(transactions)

    return mTree.RootNode.Data
}

SPV支付验证的实现步骤

(轻节点只知道自己的交易的hash和区块链的头部信息)

SPV进行简单支付验证时步骤如下:

  1. 计算待验证支付的交易哈希值;
  2. 轻节点会向全节点请求交易所在的区块和区块的MerkleTree;
  3. 根据哈希认证路径,计算默克尔树的根哈希值,将计算结果与本地区块头中的默克尔树的根哈希值进行比较;
  4. 验证通过后,再看一下自己的交易是否已经在最长的链上(即后面至少还有6个区块)。