原始数据
账户记录、交易、收据、文件块或白名单地址。哈希函数最终只接收字节,不理解“余额”的语义。
Ethereum learning path · 第十一章 · 第二课
Ethereum learning path · 11.02
Merkle Tree 的价值不在“长得像树”,而在于把任意多、按规则排列的数据 压成一枚固定长度承诺,并允许验证者只拿一条短路径,独立检查某个值是否属于那份数据。
你不能从根恢复整棵树;但数据、顺序或编码只要改变,根通常就会改变。
平衡二叉树中,只需每层一个兄弟哈希;证明与验证成本随树高对数增长。
你仍需知道根来自哪个区块、编码规则是什么,以及底层数据是否可获得。
00 / ORIENTATION
想象你只信任 32 字节,却要核对数百万条状态。关键不是把数据“缩小”,而是把信任锚点缩小。
上一课把 Ethereum State 看成“所有账户与合约状态的集合”。真正的节点当然可以保存并重算全部状态; 但手机、浏览器、桥、Rollup 或一份离线审计程序,往往只想核对其中一个账户或一个存储槽。
如果远程服务器说“这个地址在区块 B 的余额是 5 ETH”,你有三个选择: 完全相信服务器、下载整个状态自己重建,或要求服务器交出一份可以对照区块承诺验证的短证明。 Merkle Tree 为第三条路提供了基础构件。
Precise definition
Merkle Tree 是一种基于哈希的认证数据结构: 叶子承诺具体数据,内部节点承诺按顺序排列的子节点,最终根哈希承诺整份有序数据及其结构。
账户记录、交易、收据、文件块或白名单地址。哈希函数最终只接收字节,不理解“余额”的语义。
原始数据按规范编码后得到的值或哈希。叶子的格式属于协议规则,不能由验证者临时猜。
通常由有序子节点再次哈希而成。左右位置是输入的一部分,交换顺序通常会改变父哈希。
固定长度的最终摘要。比较两枚根很便宜,但根本身不会告诉你是哪一条数据发生了变化。
从目标叶子到根,每一层缺失的兄弟节点,加上左右方向与必要的编码元数据。
验证的参照物。它可能来自已最终确认区块、轻客户端或你已信任的签名清单。
仅说“我们使用 Merkle Tree”无法让两个实现算出同一枚根。协议至少要回答下面五个问题; 其中任何一项不同,得到的根都可能不同。
5、字符串 "5"、十六进制 0x05 与 32 字节整数不是同一输入。
叶子可以直接是固定长度块,也可以是 H(prefix ∥ encodedData)。
常见形式是 H(prefix ∥ left ∥ right);哈希函数、前缀和输入顺序都必须固定。
按原始顺序、按键排序、按索引定位或沿 key 的比特路径,表达的是不同结构。
复制末叶、补零、提升孤儿节点或显式混入长度都可行,但验证双方必须采用同一规则。
域分离可让“叶子字节”与“内部节点字节”无法被混作同一种对象,减少结构歧义。
前半课用四叶、平衡二叉 Merkle Tree 教清根与证明。它不是以太坊执行层状态树的逐节点复制品。 当前执行状态使用 modified Merkle-Patricia Trie;Patricia 的路径压缩、十六进制分支与 RLP 留到下一课。
01 / DATA → ROOT
先把每条记录变成叶子,再把相邻承诺按确定顺序两两合并;改动只沿祖先路径向上传播。
下面的教学树使用 SHA-256 与显式域分离。叶子前加 0x00,
内部节点前加 0x01。这样做是为了把概念讲清,不代表执行层 MPT 采用同一套具体编码。
Lᵢ = SHA-256(0x00 ∥ UTF8(dataᵢ))
P₀₁ = SHA-256(0x01 ∥ L₀ ∥ L₁)
P₂₃ = SHA-256(0x01 ∥ L₂ ∥ L₃)
R = SHA-256(0x01 ∥ P₀₁ ∥ P₂₃)
符号 ∥ 表示字节拼接,不是文本中的加号,也不是把十六进制字符串直接相连。
c916ccc5a590…323d5ee
6dd9899e4de4…d79922f
78244ac594f5…3386b93
52a347438bdd…5b38fac
74c4e979dde2…179fc8eb
70a3261adb2c…c83537a5
fda6fd5428ab…eb709304
初始根为 c916ccc5…323d5ee。改变 Alice 会重算 L₀、P₀₁ 与 Root;右侧子树 P₂₃ 不需要改变。
52a347438bdd78cc…5b38fac
c916ccc5a590743d…323d5ee
Alice 的叶子变化后,它的兄弟 Bob 没变,右半棵树也没变。重算只发生在
L₀ → P₀₁ → Root 这条祖先链。对一棵有 n 个叶子的平衡二叉树,
树高约为 log₂(n),所以单点更新只影响约 log₂(n) 个层级。
“持久化 Merkle Tree”还可以复用未改变的旧节点,因此同一数据库可保留多个历史根, 只为变更路径创建新节点。以太坊状态树的工程实现更复杂,但“未变子树可由旧哈希继续代表” 是理解状态根高效更新的重要直觉。
H(left ∥ right) 通常不等于 H(right ∥ left)。证明必须携带方向或可推导位置。
大小写、空格、Unicode 规范化、整数大小端与字段长度都会改变输入字节。
同一组叶子采用不同分组、补齐或提升规则,最终根可以不同。
不同前缀避免某段字节既被解释成原始叶子、又被解释成两个子摘要的拼接。
02 / MERKLE PROOF
证明不是“从叶子到根的所有节点”,而是验证者在每一层缺少的兄弟承诺。
要证明 Alice 位于四叶树的第 0 号位置,验证者已经拿到了 Alice 的数据,
因此不需要再次发送 L₀;它只缺同层的 L₁ 与另一半子树的 P₂₃。
position = 0 · bits = 0074c4e979dde2…179fc8eb78244ac594f5…3386b93c916ccc5a590…323d5ee验证通过:使用目标值、位置和 2 个兄弟哈希,重算得到可信根 c916ccc5…323d5ee。
node = hashLeaf(encodedValue)
for each (sibling, direction) in proof:
if direction == "sibling_on_left":
node = hashParent(sibling, node)
else:
node = hashParent(node, sibling)
accept if node == trustedRoot
左右方向不能省略。假设当前节点在父节点右边,拼接顺序必须是
sibling ∥ current;若当前节点在左边,则是 current ∥ sibling。
有些树可从固定索引逐位推导方向,因此证明格式不一定显式传一串“左/右”。
平衡二叉树每上升一层,覆盖的叶子数翻倍:1、2、4、8……覆盖 n 个叶子需要约
log₂(n) 层。单叶证明每层补一个兄弟摘要,所以证明哈希数量与树高相同。
这里比较的是概念二叉树的哈希负载,不包含位置、编码和协议封装;MPT 证明大小不能直接套用这条简单公式。
复杂度只描述增长趋势。真实证明大小还取决于分支数、节点编码、键路径、稀疏程度与共享节点。 以太坊当前十六叉 MPT 的单层可能需要更多兄弟信息;未来二叉状态树提案的一项动机正是缩小常规证明。
若要同时证明多个相近叶子,它们的路径常共享上层兄弟节点。Merkle multiproof 只提供恢复相关子树所需的最小辅助节点,能去掉重复哈希。以太坊共识规范专门定义了基于 generalized index 的多证明辅助索引算法。[5]
03 / SECURITY BOUNDARIES
Merkle Proof 是密码学完整性证明,不是事实审查、共识证明或数据可用性证明的万能替代。
验证通过的准确含义是:在约定哈希与编码规则下,这个值与这条路径能够重建你给定的根。 它没有自动回答根是否属于规范链、数据从现实世界采集得是否正确,也没有保证其他叶子随时可下载。
| 问题 | 能否单独保证 | 还缺什么 |
|---|---|---|
| 给定值属于给定根 | 可以 | 正确证明、位置、编码与安全哈希假设 |
| 数据未被悄悄修改 | 可以检测 | 先前可信根或签名根作为比较锚点 |
| 根属于以太坊规范链 | 不可以 | 共识验证、轻客户端或可信区块头来源 |
| 叶子陈述的是现实真相 | 不可以 | 数据来源、预言机、签名与业务验证 |
| 整份数据仍然可获得 | 不可以 | 数据可用性机制、存储与传播保证 |
| 根一定对应唯一可行数据集 | 计算上近似 | 抗碰撞哈希与无歧义编码;不是数学上的绝对不可能 |
若攻击者能主动制造碰撞,就可能让不同节点或数据共享同一承诺,完整性保证会崩溃。
给定一份已承诺输入,攻击者应难以再找出另一份输入产生同一根。
编码若有歧义,双方甚至不需要攻破哈希,就可能对“同一条记录”计算出不同叶子。
攻击者完全可以为伪造数据自建一棵自洽的树,并交给你一份能通过它自己假根的证明。
先知道哪个区块头应被接受。
读取该区块承诺的 stateRoot 等字段。
服务端可以不受信任,但必须给出路径节点。
只有重算根等于锚点,目标值才被接受。
普通的有序叶子 Merkle Tree 很容易证明“某值在第 i 个位置”,却未必能仅凭一条缺失路径证明 “某个键在全集中不存在”。一种做法是先按键排序,再证明查询键落在两个相邻键之间; 另一种做法是采用把整个键空间映射为路径的 sparse Merkle Tree 或 trie。
以太坊 MPT 可以通过路径在空分支终止,或在叶子/扩展节点处出现不匹配,提供不存在证明所需的信息。 这正是下一课要进入的 Patricia Trie 结构细节。EIP-1186 也明确讨论了账户或存储值不存在时的证明。 [8]
一份对旧区块根有效的证明可以完全正确,却已经不是“当前状态”。验证时必须把
value + proof + root + block identifier 视为同一组对象;latest 还可能发生重组,
safe 与 finalized 表达的共识保证也不同。
04 / ETHEREUM MAPPING
执行层与共识层都依赖根承诺,但数据结构、编码、哈希函数和证明格式并不相同。
说“以太坊把状态放在 Merkle Tree 里”作为入门直觉没有问题;进入协议层后,必须马上补上限定: 当前执行状态是 modified Merkle-Patricia Trie,共识对象则用 SSZ Merkleization。
stateRoot
承诺执行完区块后所有账户状态;账户值包含 nonce、balance、storageRoot、codeHash。
transactionsRoot
承诺执行区块中的交易集合与索引映射。
receiptsRoot
承诺交易收据,包括执行结果与日志等信息。
Keccak-256 + RLP
节点以 RLP 编码;较长节点通常由 Keccak 哈希引用,短节点可内联。
hash_tree_root(object)
把 SSZ 对象分成 32 字节 chunks,并递归计算二叉 Merkle 根。
SHA-256
共识规范的基础哈希函数,与执行层广泛使用的 Keccak-256 不同。
generalized index
根编号 1;节点 k 的左右子节点为 2k 与 2k+1,可稳定定位嵌套字段。
zero padding + mix-in
容器、向量、列表与可变长度数据按 SSZ 类型规则补齐并混入长度。
| 维度 | 本课四叶教学树 | 执行层状态 MPT | 共识层 SSZ |
|---|---|---|---|
| 主要目的 | 理解根与证明 | 认证键值状态并支持更新/查找 | 序列化共识对象并生成 hash-tree-root |
| 分支结构 | 平衡二叉 | 十六进制路径、branch/extension/leaf | 类型驱动的二叉 Merkle 树 |
| 哈希示例 | SHA-256 | Keccak-256 | SHA-256 |
| 编码 | UTF-8 + 教学前缀 | RLP + hex-prefix 路径编码 | SSZ 类型规则 |
| 位置来源 | 数组索引 | 键哈希后的 nibble 路径 | generalized index |
| 能否直接互换证明 | 不能 | 不能 | 不能 |
世界状态树的键是账户地址的 Keccak 摘要路径,叶子值是账户对象。合约账户对象中的
storageRoot 又承诺这个合约自己的存储 trie。因此证明某个存储槽通常要走两段:
先证明账户属于 stateRoot,再用账户中认证过的 storageRoot 证明槽值。
[2][6]
从规范链区块取得 stateRoot。
沿 keccak(address) 路径重建 stateRoot。
认证 nonce · balance · storageRoot · codeHash。
沿 keccak(slot) 路径重建该账户的 storageRoot。
只有两段根都匹配,槽值才被锚定到该区块状态。
三者都出现在执行区块头语境中,却承诺不同对象。节点重执行区块交易后,会检查新状态根、 交易根、收据根等承诺与区块头是否一致;任一不匹配都不能把该执行结果当作有效区块接受。 [3][4]
账户余额、nonce、合约代码哈希与各自存储根的整体承诺。
交易列表/索引映射的承诺,不等于交易执行后的状态。
状态、累计 Gas、日志等收据数据的承诺,供验证与日志查询使用。
SSZ 对 BeaconState 等共识对象的 hash-tree-root,不是执行层 world stateRoot。
05 / ETH_GETPROOF
eth_getProof 返回账户与可选存储槽的值及 MPT 路径节点;真正的信任锚仍是你选定区块的 stateRoot。
Ethereum Execution APIs 定义了 eth_getProof(address, storageKeys, block)。
你可以传入地址、要核对的 32 字节存储键列表,以及区块号、区块哈希或
latest / safe / finalized 等标签。[6]
{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_getProof",
"params": [
"0xAbC…123",
["0x000…007"],
"finalized"
]
}
不要只记录“finalized”这个会移动的标签;验证存档时应保存实际 block hash / number 与该区块头的 stateRoot。
blockHash → executionHeader.stateRoot
RPC 响应只是原材料。若应用直接相信返回的 balance 而不解析证明、重建 MPT 节点并与可信
stateRoot 比较,它仍然只是在相信 RPC 服务商。EIP-1186 是历史提案;实现时应优先核对当前
Ethereum Execution APIs 与所用客户端行为。[6][8]
链下保存大量地址/额度,只把根写入合约;领取者提交自己的叶子与证明,合约按 O(log n) 验证。
设备不保存全部状态,只追踪经过共识认证的根,再向任意服务端索取目标数据与路径。
把一个系统的根锚定到另一个系统后,可用证明认证消息、提现或状态;安全性还依赖桥与最终性规则。
大文件留在链下,仅在链上保存根;之后能证明某个片段属于原承诺,但根不保证文件仍可下载。
06 / ADVANCED
真正棘手的地方往往不在哈希函数,而在结构、编码、长度、缺失值与升级兼容。
复制末叶、补零或提升孤儿节点都会产生不同根。SSZ 用类型驱动的零值补齐;其他协议可能采用不同约定。
可变长度列表仅补零可能让不同长度映射到同一填充形状,因此 SSZ 会把列表长度 mix in 到根。
需要有序邻居、完整键空间或 trie 路径语义。一个只承诺无序集合的普通树不能凭空证明某键缺失。
合并重叠路径形成 multiproof;验证器还必须知道目标位置集合与辅助节点的规范顺序。
根让你检测错误数据,却不能逼迫任何人交出数据。Rollup 等系统必须另外解决数据发布与可获得问题。
哈希函数、编码或树形变更都会改变根。证明格式必须绑定版本,不能拿旧规则验证新结构。
普通树常按数组位置组织叶子;Sparse Merkle Tree 把巨大固定键空间的每个可能位置都概念化为叶子, 大量空节点由预计算默认哈希代表;Trie 则按键的字符、nibble 或 bit 逐段寻路。 三者都能使用 Merkle 哈希认证结构,但“如何定位数据”完全不同。
Patricia 优化会压缩只有单一分支的长路径;以太坊 MPT 再加入十六叉分支、RLP 与短节点内联等规则。
因此本课的 log₂(n) 二叉证明图不能当作 MPT 的线级实现图。
EIP-7864 提议把账户头、代码与存储放入统一二叉树,并面向常规 Merkle Proof 与未来有效性证明优化。 它指出当前 MPT 的十六叉结构、RLP、tree-of-trees 与代码未入树等特征不利于证明系统; 二叉分支能减少单层需要携带的兄弟摘要。[10]
EIP-7864 当前是 Draft,提案中的最终哈希函数也尚未确定。2026 协议优先级把二叉树与无状态化放在长期方向。 学习时应掌握“根与证明”的不变量,同时把具体树格式视为可升级的协议层。
07 / REVIEW
固定长度根绑定整份有序数据,却不能恢复原始数据。
编码、哈希、顺序、树形、补齐与域分离缺一不可。
未改变子树可继续由旧摘要代表,更新不必重算全部节点。
验证者用值、位置、兄弟哈希与可信根重建路径。
每上升一层,覆盖叶子数翻倍;证明每层只补一个兄弟。
假根也能配假数据生成自洽证明;先认证根的来源。
证明值属于根,不证明现实陈述正确,也不保证全部数据可下载。
执行层当前是 MPT;共识层使用 SSZ 二叉 Merkleization。
请选择一个答案。
请选择一个答案。
请选择一个答案。
请选择一个答案。
请选择一个答案。
请选择一个答案。
手画四叶树,任选一个叶子,写出验证者需要的两个兄弟摘要及左右拼接顺序。
打开区块浏览器的某个执行区块,分别找到 state root、transactions root 与 receipts root,并用一句话说明三者对象。
在测试环境调用 eth_getProof,保存实际 block hash,不只保存会移动的 latest 标签;尝试用 MPT 库离线验账户证明。
08 / GLOSSARY & SOURCES
本课核对日期为 2026-07-26。路线图与 Draft EIP 会变化;当前主网事实应以已激活规范为准。
11.02 · Complete
现在你已经能从数据算根、从证明验根,也能分清完整性、共识、真实性与数据可用性的边界。 下一课将打开以太坊执行层的真实状态结构:看 Patricia Trie 如何把键路径、十六叉分支、 路径压缩与 Merkle 认证组合成 State Trie、Storage Trie、Transaction Trie。