mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
1096 字
3 分钟
Merkle 树(Merkle Tree)
2026-06-13

一、为什么需要 Merkle 树#

假设你管理着一个包含 100 万条记录的数据库,如何确认其中某一条记录没有被篡改?

最直觉的做法是:把所有数据重新哈希一遍,跟已知的摘要做比对。但这样做代价太高——验证一条记录需要读取并哈希 100 万条数据。

Merkle 树解决了这个问题。它把数据组织成一棵哈希二叉树,每个内部节点存储子节点哈希的哈希。要验证单个叶子,只需要从叶子到根路径上的 O(log n) 个兄弟哈希——就像检查一排封条中哪一层被破坏了一样。

这个数据结构是 Git、ZFS、Bitcoin、IPFS 等系统的基石。

二、现实类比#

想象一个防篡改的运输封条系统。每个箱子贴上独一无二的蜡封,箱子装入板条箱后,板条箱的封印由所有箱子的蜡封推导而来。板条箱再装入集装箱,集装箱有自己的总封印。如果其中一件物品被掉包,它上方的每一层封印都会破损——你只需检查 log(n) 个封印就能定位被篡改的箱子,而无需打开所有箱子。

三、核心思想#

Merkle 树是一棵哈希二叉树。每个叶子节点包含数据块的哈希值,每个内部节点包含其两个子节点拼接后的哈希值。根哈希是整个数据集的指纹。

graph TB R["Root Hash<br>H(H12 + H34)"] R --> H12["H12<br>H(H1+H2)"] R --> H34["H34<br>H(H3+H4)"] H12 --> H1["H1<br>hash(Data A)"] H12 --> H2["H2<br>hash(Data B)"] H34 --> H3["H3<br>hash(Data C)"] H34 --> H4["H4<br>hash(Data D)"] H1 --- A["Data A"] H2 --- B["Data B"] H3 --- C["Data C"] H4 --- D["Data D"] style R fill:#f97316,stroke:#9a3412,color:#fff style H12 fill:#fbbf24,stroke:#92400e style H34 fill:#fbbf24,stroke:#92400e

验证 Data C 的过程

  1. 计算 H3 = hash(Data C)
  2. 用兄弟 H4 计算 H34 = hash(H3 + H4)
  3. 用叔伯 H12 计算根 = hash(H12 + H34)
  4. 比较根哈希是否与已知的根哈希一致

只需要 H4 和 H12 两个兄弟节点,就能完成验证——这就是 O(log n) 的魅力。

属性
验证代价每个叶子 O(log n) 次哈希
树构建O(n) 次哈希
证明空间O(log n) 个兄弟哈希
篡改检测任何修改都会改变根哈希

四、变体与对比#

变体特点适用场景
二叉 Merkle 树每个节点两个子节点,最常见Bitcoin、以太坊
Merkle Patricia Tree结合 Patricia 树和 Merkle 树,支持键值查找以太坊状态树
Merkle DAG有向无环图而非树,允许多父节点IPFS、Git
Merkle Mountain Range多棵树的链式排列,支持追加Tombchain、OpenTimestamps

与其他完整性验证方案对比:

方案验证代价证明大小动态更新
全量哈希O(n)O(1)重新哈希全部
Merkle 树O(log n)O(log n)O(log n)
布隆过滤器O(k)O(m)不支持删除

五、多语言实现#

Go 实现#

package merkle
import (
"crypto/sha256"
"encoding/hex"
)
// MerkleTree 表示一棵 Merkle 树
type MerkleTree struct {
Root *Node
Leaves []*Node
}
// Node 表示树中的一个节点
type Node struct {
Hash string
Left *Node
Right *Node
}
// NewMerkleTree 从数据块列表构建 Merkle 树
func NewMerkleTree(data [][]byte) *MerkleTree {
// 构建叶子节点
var leaves []*Node
for _, d := range data {
hash := sha256.Sum256(d)
leaves = append(leaves, &Node{Hash: hex.EncodeToString(hash[:])})
}
// 奇数个叶子时复制最后一个
if len(leaves)%2 != 0 {
leaves = append(leaves, leaves[len(leaves)-1])
}
// 自底向上构建树
currentLevel := leaves
for len(currentLevel) > 1 {
var nextLevel []*Node
for i := 0; i < len(currentLevel); i += 2 {
left := currentLevel[i]
right := currentLevel[i+1]
combined := left.Hash + right.Hash
hash := sha256.Sum256([]byte(combined))
parent := &Node{
Hash: hex.EncodeToString(hash[:]),
Left: left,
Right: right,
}
nextLevel = append(nextLevel, parent)
}
currentLevel = nextLevel
}
return &MerkleTree{Root: currentLevel[0], Leaves: leaves}
}
// VerifyProof 验证某个叶子属于这棵树
func VerifyProof(leafHash string, proof []string, indices []bool, rootHash string) bool {
current := leafHash
for i, sibling := range proof {
combined := sibling + current
if indices[i] {
combined = current + sibling
}
hash := sha256.Sum256([]byte(combined))
current = hex.EncodeToString(hash[:])
}
return current == rootHash
}

TypeScript 实现#

function hash(data: string): string {
let h = 0x811c9dc5;
for (let i = 0; i < data.length; i++) {
h ^= data.charCodeAt(i);
h = Math.imul(h, 0x01000193);
}
return (h >>> 0).toString(16).padStart(8, "0");
}
class MerkleTree {
private leaves: string[];
private layers: string[][];
constructor(data: string[]) {
// 奇数个叶子时复制最后一个
if (data.length % 2 !== 0) data.push(data[data.length - 1]);
this.leaves = data.map((d) => hash(d));
this.layers = [this.leaves];
this.build();
}
private build(): void {
let current = this.leaves;
while (current.length > 1) {
const next: string[] = [];
for (let i = 0; i < current.length; i += 2) {
next.push(hash(current[i] + current[i + 1]));
}
this.layers.push(next);
current = next;
}
}
getRoot(): string {
const top = this.layers[this.layers.length - 1];
return top[0];
}
// 获取验证某叶子所需的兄弟哈希路径
getProof(index: number): { hash: string; isRight: boolean }[] {
const proof: { hash: string; isRight: boolean }[] = [];
let idx = index;
for (let layer = 0; layer < this.layers.length - 1; layer++) {
const siblingIdx = idx % 2 === 0 ? idx + 1 : idx - 1;
proof.push({
hash: this.layers[layer][siblingIdx],
isRight: siblingIdx > idx,
});
idx = Math.floor(idx / 2);
}
return proof;
}
// 验证叶子属于这棵树
static verify(
leafHash: string,
proof: { hash: string; isRight: boolean }[],
rootHash: string
): boolean {
let current = leafHash;
for (const step of proof) {
current = step.isRight
? hash(current + step.hash)
: hash(step.hash + current);
}
return current === rootHash;
}
}

六、生产验证#

项目源码用途
Gittree.c#L136-L171每个 commit、tree、blob 都通过 SHA-1 内容寻址,构成 Merkle DAG。修改任何文件中的一个字节都会改变到根 commit 的所有哈希,使 git fsck 能高效验证仓库完整性
ZFSblkptr.c每个块在父块指针中存储校验和,从数据块到 uberblock 形成 Merkle 树。zpool scrub 遍历此树检测静默数据损坏(bit rot)
Bitcoinmerkle.cpp区块头只存储 Merkle 根哈希,SPV 轻节点通过 Merkle 证明验证交易存在性,无需下载完整区块

七、小结#

何时使用

  • 需要高效验证大数据集中单条记录的完整性
  • 参与方之间互不信任,需要密码学证明
  • 网络带宽有限,只传输必要的证明路径
  • 数据需要追加写入并持续验证

何时不用

  • 数据量很小(直接全量哈希更简单)
  • 所有参与方完全可信(不需要验证机制)
  • 频繁的随机更新和删除(树重建代价高)
  • 不需要完整性验证的场景

八、参考资料#

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

Merkle 树(Merkle Tree)
https://blog.souloss.com/posts/programming/data-structures/data-structures-merkle-tree/
作者
Tsukimi
发布于
2026-06-13
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时