天天看點

100行代碼實作MerkleTree!

Merkle樹作為區塊鍊中重要的資料結構,有着很廣泛的應用。

100行代碼實作MerkleTree!

基本就是每個葉節點作為資料存儲點,對它進行哈希之後,得到最初的葉子節點,再兩兩相加哈希,一直類推,到隻剩一個節點,即根節點即可。

說到哈希啊,就不得不提到祖師爺Leslie Lamport:

100行代碼實作MerkleTree!

就當你發現Hash、LaTex,拜占庭将軍問題、Paxos都是一個人搞出來的之後,那種五體投地的感覺....orz

Leslie Lamport在1973年第一個提出hash簽名方案,hash有多偉大就不用說了吧。

Merkle樹其實最早不是應用在區塊鍊中的,Ralph Merkle:

100行代碼實作MerkleTree!

他最先提出Merkle樹應用在數字簽名中:Merkle提出一種方法可以保持簽名N條不同的消息的能力,而公鑰成本沒有爆炸性增長。Merkle的方法大概類似這種: 

100行代碼實作MerkleTree!

Merkle樹在這裡有一個粗略的描述:它們提供了一種方法,用這種方法可以收集很多不同的值,這樣這些值可以由單個“根”散清單示。給出這個散列值,就很容易生成一個元素在hash樹中的簡單的證明,這個證明的大小是樹中葉節點的個數。比如說上圖,我要驗證OTS1是否正确,我隻需要驗證圖中黑色的H2和H6即可,根據H2和H6我們就能哈希出樹根,對比區塊中的樹根是否一緻即可。

那麼如何應用在區塊鍊中呢?

我們知道啊,區塊鍊中的資料和交易量都是非常大的,一般情況下,一個區塊中包含幾百上千筆交易是很常見的。由于區塊鍊的去中心化特性,網絡中的每個節點必須是獨立,自給自足的,也就是每個節點必須存儲一個區塊鍊的完整副本。那麼随着越來越多的交易,以及越來越多的挖礦,資料存儲成為了一筆巨大的開銷。

如何縮小這筆開銷呢?

區塊鍊中每個區塊都會有一個 Merkle 樹,它從葉節點開始,一個葉節點就是一個交易哈希。葉節點的數量必須是雙數,但是并非每個塊都包含了雙數的交易。如果一個塊裡面的交易數為單數,那麼就将最後一個葉節點(也就是Merkle樹的最後一個交易,不是區塊的最後一筆交易)複制一份湊成雙數。

從下往上,兩兩成對,連接配接兩個節點哈希,将組合哈希作為新的哈希。新的哈希就成為新的樹節點。重複該過程,直到僅有一個節點,也就是樹根。根哈希然後就會當做是整個塊交易的唯一标示,将它儲存到區塊頭,然後用于工作量證明。

也就是說,我們不必存儲所有的交易資訊在區塊中,隻需存儲Merkle樹根就行了,大大減小了存儲量。

100行代碼實作MerkleTree!

像上圖這樣,就不用把所有交易存儲下來啦~

那麼,如何用代碼優雅的實作它呢?

Merkle樹有一個特點,初始化時它的葉節點個數必須是雙數,如果不是雙數,那麼複制最後一個葉節點湊成雙。

Duang~代碼來了

老規矩,先引入包:

package main

import (
  "crypto/sha256"
  "fmt"
  "strconv"
)      

定義Merkle樹和樹節點資料結構:

type MerkleTreeNode struct {
  Data  []byte
  Hash  []byte
  Left  *MerkleTreeNode
  Right *MerkleTreeNode
}

type MerkleTree struct {
  Root *MerkleTreeNode
}      

實作一個隊列,簡單的Push和Pop,友善建樹,因為建樹肯定是每次兩兩合并,新生成的節點放後面,類似于哈夫曼樹建樹過程:

type Queue struct {
  List []MerkleTreeNode
}

func NewQueue() *Queue {
  list := make([]MerkleTreeNode, 0)
  return &Queue{list}
}

func (q *Queue) Push(node *MerkleTreeNode) {
  q.List = append(q.List, *node)
}

func (q *Queue) Pop() *MerkleTreeNode {
  if q.Len() == 0 {
    panic("Empty!")
  }
  node := q.List[0]
  q.List = q.List[1:]
  return &node
}

func (q *Queue) Len() int {
  return len(q.List)
}      

生成Merkle樹節點部分,注意啊,因為Merkle樹是一種完全二叉樹,是以它的節點隻有兩種情況:兩個孩子的非葉節點和無孩子的葉節點:

func NewMerkleTreeNode(left, right *MerkleTreeNode, data []byte) *MerkleTreeNode {
  var node MerkleTreeNode

  if left == nil && right == nil {
    hash := sha256.Sum256(data)
    node.Hash = hash[:]
  } else {
    childHash := append(left.Hash, right.Hash...)
    hash := sha256.Sum256(childHash)
    node.Hash = hash[:]
  }

  node.Left = left
  node.Right = right
  node.Data = data
  return &node
}      

建樹函數,使用一個隊列來進行建樹:

func NewMerkleTree(data [][]byte) *MerkleTree {
  var root MerkleTree
  nodes := NewQueue()
  if len(data)%2 != 0 {
    data = append(data, data[len(data)-1])
  }

  for _, i := range data {
    nodes.Push(NewMerkleTreeNode(nil, nil, i))
  }

  for nodes.Len() > 1 {
    left := nodes.Pop()
    right := nodes.Pop()
    node := NewMerkleTreeNode(left, right, nil)
    nodes.Push(node)
  }
  root.Root = nodes.Pop()
  return &root
}      

先序周遊看看咱們建立的樹是否成功:

func PreOrderVisit(root *MerkleTreeNode) {
  if root != nil {
    fmt.Print(root.Data)
    PreOrderVisit(root.Left)
    PreOrderVisit(root.Right)
  }
}      

寫個main函數看看結果呗,建立一個存儲6個資料的Merkle樹:

func main() {
  data := make([][]byte, 6)
  for i := 0; i < 6; i++ {
    data[i] = []byte(strconv.Itoa(i))
  }
  root := NewMerkleTree(data)
  PreOrderVisit(root.Root)
}      

列印看看:

100行代碼實作MerkleTree!

對應的樹就是這樣子:

100行代碼實作MerkleTree!

好啦,簡單的Merkle樹就實作了,對于它的插入删除,那就是優化和工程問題了,二叉平衡樹啊紅黑樹啊伸展樹啊都可以的,主要是reigns還不能手撸一個紅黑樹,此事以後再議~

繼續閱讀