Merkle 樹是一種用於高效且安全地驗證大資料結構完整性和一致性的雜湊樹。它在比特幣網路中起到至關重要的作用。Merkle 樹是一種二叉樹結構,其中每個葉子節點包含資料塊的雜湊值,每個非葉子節點包含其子節點雜湊值的組合雜湊。
比特幣網路中的 Merkle 樹
在比特幣區塊鏈中,每個區塊包含多個交易。為了高效地驗證區塊內的交易,比特幣使用了 Merkle 樹。區塊頭包含一個 Merkle 根(Merkle Root),代表了該區塊內所有交易的雜湊摘要。
Merkle 樹的構建
- 葉子節點:每筆交易的雜湊值被用作葉子節點。
- 非葉子節點:每對葉子節點的雜湊值被組合並雜湊,形成上一級節點。這個過程遞迴進行,直到形成唯一的根節點,即 Merkle 根。
Merkle 樹的生成示例
假設一個區塊包含四筆交易 \(T1\)、\(T2\)、\(T3\) 和 \(T4\)。生成 Merkle 樹的步驟如下:
-
計算每筆交易的雜湊值 \(H1\)、\(H2\)、\(H3\)、\(H4\):
\[H1 = \text{Hash}(T1) \]\[H2 = \text{Hash}(T2) \]\[H3 = \text{Hash}(T3) \]\[H4 = \text{Hash}(T4) \] -
計算相鄰交易雜湊的組合雜湊 \(H12\) 和 \(H34\):
\[H12 = \text{Hash}(H1 || H2) \]\[H34 = \text{Hash}(H3 || H4) \] -
計算根節點雜湊 \(H1234\):
\[H1234 = \text{Hash}(H12 || H34) \]
最終,\(H1234\) 就是包含這四筆交易的 Merkle 根。
Merkle 樹的作用
- 驗證交易:透過 Merkle 樹,可以高效地驗證某筆交易是否包含在某個區塊中,而不需要檢查整個區塊。
- 輕客戶端(SPV):簡化支付驗證(SPV)客戶端可以透過請求區塊頭和所需交易的 Merkle 路徑來驗證交易,而不需要下載整個區塊鏈。
Merkle 路徑驗證
假設我們要驗證交易 \(T3\) 是否在某個區塊中:
- 獲取交易 \(T3\) 的雜湊 \(H3\)。
- 獲取 \(H3\) 的相鄰雜湊 \(H4\)。
- 計算組合雜湊 \(H34 = \text{Hash}(H3 || H4)\)。
- 獲取 \(H34\) 的相鄰雜湊 \(H12\)。
- 計算根節點雜湊 \(H1234 = \text{Hash}(H12 || H34)\) 並與區塊頭中的 Merkle 根比較。
如果計算出的根雜湊值與區塊頭中的 Merkle 根匹配,則驗證成功,說明交易 \(T3\) 包含在該區塊中。
btcd 中的 Merkle 樹實現
在 btcd
中,Merkle 樹的實現主要在 blockchain/merkle.go
檔案中。以下是該檔案中關鍵部分的詳細說明:
// HashMerkleBranches 採用兩個雜湊值,分別視為左樹節點和右樹節點,並返回它們串聯的雜湊值。用於幫助生成默克爾樹
func HashMerkleBranches(left, right *chainhash.Hash) chainhash.Hash {
// Concatenate the left and right nodes.
var hash [chainhash.HashSize * 2]byte
copy(hash[:chainhash.HashSize], left[:])
copy(hash[chainhash.HashSize:], right[:])
return chainhash.DoubleHashRaw(func(w io.Writer) error {
_, err := w.Write(hash[:])
return err
})
}
// BuildMerkleTreeStore creates a merkle tree from a slice of transactions,
// stores it using a linear array, and returns a slice of the backing array. A
// linear array was chosen as opposed to an actual tree structure since it uses
// about half as much memory. The following describes a merkle tree and how it
// is stored in a linear array.
func BuildMerkleTreeStore(transactions []*btcutil.Tx, witness bool) []*chainhash.Hash {
// Calculate how many entries are required to hold the binary merkle
// tree as a linear array and create an array of that size.
nextPoT := nextPowerOfTwo(len(transactions))
arraySize := nextPoT*2 - 1
merkles := make([]*chainhash.Hash, arraySize)
// Create the base transaction hashes and populate the array with them.
for i, tx := range transactions {
// If we're computing a witness merkle root, instead of the
// regular txid, we use the modified wtxid which includes a
// transaction's witness data within the digest. Additionally,
// the coinbase's wtxid is all zeroes.
switch {
case witness && i == 0:
var zeroHash chainhash.Hash
merkles[i] = &zeroHash
case witness:
wSha := tx.MsgTx().WitnessHash()
merkles[i] = &wSha
default:
merkles[i] = tx.Hash()
}
}
// Start the array offset after the last transaction and adjusted to the
// next power of two.
offset := nextPoT
for i := 0; i < arraySize-1; i += 2 {
switch {
// When there is no left child node, the parent is nil too.
case merkles[i] == nil:
merkles[offset] = nil
// When there is no right child, the parent is generated by
// hashing the concatenation of the left child with itself.
case merkles[i+1] == nil:
newHash := HashMerkleBranches(merkles[i], merkles[i])
merkles[offset] = &newHash
// The normal case sets the parent node to the double sha256
// of the concatenation of the left and right children.
default:
newHash := HashMerkleBranches(merkles[i], merkles[i+1])
merkles[offset] = &newHash
}
offset++
}
return merkles
}
BuildMerkleTreeStore
從交易切片建立默克爾樹,使用線性陣列儲存它,並返回支援陣列的切片。選擇線性陣列而不是實際的樹結構,因為它可以節省大約一半的記憶體。下面描述了merkle樹以及它如何儲存線上性陣列中。
root = h1234 = h(h12 + h34)
/ \
h12 = h(h1 + h2) h34 = h(h3 + h4)
/ \ / \
h1 = h(tx1) h2 = h(tx2) h3 = h(tx3) h4 = h(tx4)
以上儲存為線性陣列如下:
[h1 h2 h3 h4 h12 h34 root]
如上所示,默克爾根始終是陣列中的最後一個元素。
宣告:本作品採用署名-非商業性使用-相同方式共享 4.0 國際 (CC BY-NC-SA 4.0)進行許可,使用時請註明出處。
Author: mengbin
blog: mengbin
Github: mengbin92
cnblogs: 戀水無意
騰訊雲開發者社群:孟斯特