防止 Merkle 树中的第二原像攻击:完整指南

本文深入探讨了 Merkle 树中的第二原像攻击,解释了其原理以及如何利用中间节点来伪造叶子节点的证明。文章还详细介绍了四种有效的防御策略,包括使用不同的哈希函数、对叶子节点进行双重哈希、添加前缀以及限制叶子节点的长度,并分析了这些方法在实际应用中的适用性。

第二原像攻击简介

构建一个简单的 Merkle 树

假设我们有一个数组 ["L1", "L2", "L3", "L4"],只是为了保持 Merkle 树的简单性。考虑到我们将使用 keccak256 哈希,每个值在使用前都会被哈希处理,作为叶子节点。所以树的叶子节点看起来像这样:

Level 1

接下来,我们将逐步哈希这些哈希值的连接,逐步构建树。

Level 2

接下来,我们将连接最后两个节点并对其进行哈希以获得根,瞧,我们有了一个完整的 Merkle 树。

Tree

理解攻击:第二原像攻击如何工作

Merkle 证明的验证函数接受两个东西:一个是非哈希的叶子节点(假设我们要证明存在的叶子节点是 L1),以及一个证明数组,该数组具有使用叶子节点形成根的中间节点。它首先对叶子节点进行哈希,然后构造根。如果从证明和 L1 导出的根与存储的根匹配,则验证通过。值得注意的是,证明的长度仅为 log₂(叶子数量),这使得它非常紧凑 —— 例如,证明一个具有 100 万个叶子的树的成员资格只需要 20 个哈希,这就是 Merkle 树对于大型数据集如此高效的原因。

rust 中 验证函数的函数签名如下所示:

fn verify(leaf: &[u8], proof: &[[u8; 32]]) -> bool

为了证明 L1,我们需要树中的以下节点:[hash(L2), hash(b)],标记为红色。

Nodes Marked

从给定的叶子节点和证明节点生成根的路径如下所示

Group 16 (6)

现在考虑一下我们如何证明一个甚至不存在于此 Merkle 树中的叶子节点。如果我们提供 a 作为叶子节点(其中 a 是 hash(L1) 和 hash(L2) 的连接),并提供 [hash(b)] 作为证明数组呢?验证将首先哈希 a 以获得 hash(a),然后将其与 hash(b) 连接并再次哈希以获得相同的根。即使 a 从未作为叶子节点存在,这也会成功,它实际上是一个中间节点。

Group 25 (1)

我们能够为一个甚至不存在的叶子节点获得相同的根。

防御策略:防止第二原像攻击的四种方法

1. 为叶子节点和节点使用单独的哈希函数

假设我们使用 Keccak256 哈希节点,使用 SHA256 哈希叶子节点。让我们看看在这种情况下会发生什么。采用与上面相同的例子,其中:

leaf = a -> L1 的哈希和 L2 的哈希的串联
proof = [hash(b)] -> hash(L3) 的哈希和 hash(L4) 的哈希的串联

现在,由于 a 是作为叶子节点提供的,验证函数将尝试使用 SHA256 哈希它,而不是在原始树中使用 Keccak256 进行哈希,从而导致完全不同的根。因此,第二原像攻击将失败。

Group 18 (3)

2. 双重哈希叶子节点

这与第一个解决方案类似。唯一的区别是,我们不是使用不同的哈希,而是将叶子节点哈希两次,内部节点哈希一次。假设我们使用 Keccak256 哈希。树看起来像这样:

Group 19 (4)

现在,如果我们尝试将 a 作为伪造的叶子节点输入,验证器将对其哈希两次,从而导致不同的根和不同的哈希。

Group 20 (1)

3. 添加前缀以分隔叶子节点和节点哈希

在这种情况下,哈希将保持不变,也不会有双重哈希,但是在哈希之前,我们将添加一些前缀。叶子节点和节点的前缀将有所不同。例如:

叶子节点前缀 = "0x01"
节点前缀 = "0x02"

树看起来像这样:

Group 21

并且给定 a 作为叶子节点,它将使用前缀 0x01 而不是 0x02 哈希它,从而导致不同的叶子节点。

Group 22

4. 对叶子节点使用字节长度限制

这就是 OpenZeppelin 在 Merkle 树库中防止此攻击的方式。考虑到 Solidity 中的哈希为 32 字节,在每个级别连接两个 32 字节的哈希会导致 64 字节,然后将这 64 字节哈希回 32 字节。因此,如果我们在哈希之前限制叶子节点只能为 32 字节,则可以防止此攻击。方法如下:考虑以下仅接受 32 字节作为叶子节点的树。现在,如果攻击者尝试提供 a(它是 hash(L1) 和 hash(L2) 的连接,总计 64 字节)作为叶子节点,它将被拒绝,因为它超过了 32 字节的限制,从而防止了攻击。

结论

第二原像攻击利用了 Merkle 树中叶子节点和内部节点哈希之间缺乏区分,从而允许攻击者伪造不存在数据的证明。幸运的是,存在多种有效的缓解措施:使用不同的哈希函数、双重哈希叶子节点、添加前缀或限制叶子节点大小。所有这些方法都具有相同的核心原则:确保内部节点不能伪装成叶子节点以产生相同的根。

在生产环境中实施 Merkle 树时,重要的是要理解并非所有预防技术都可以有意义地组合。以下是一个比较:

Screenshot 2025-10-06 at 11.04.12 AM

关键的见解是,结合两种从根本上改变哈希工作方式的技术(单独的哈希函数 + 双重哈希)是多余且浪费的,而前缀是普遍兼容的,因为它只是添加元数据而不改变哈希机制本身。

我们将继续发布与金属密切相关的构建者和审计员的实用、高信号深度研究。 关注 Adevar Labs 在 X 上 LinkedIn 以保持更新。

  • 原文链接: adevarlabs.com/blog/prev...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Adevar labs
Adevar labs
Blockchain Security Firm | Rust, Solidity, Move, and beyond. Vulnerability discovery, practical remediation, and complete audit reports | Ship Safely.