收到一段订单交付凭证的“包含路径”,校验器可以不用下载全部订单就复算树根。但如果根本身来自攻击者,校验成功只是说“攻击者的树确实放了这条凭证”。这与“订单有效、付款最终确认、货物真实交付”是三个不同问题。

先修:区块链(02):哈希之前,先说清究竟签了哪些字节 的长度编码与域分离;对上一层哈希如何绑定两段字节有基本概念。模型固定三个合成叶 order-0、order-1、order-2;叶哈希用 leaf 标签,内部节点用 node 标签,奇数层末叶复制,最终根再绑定叶数量。这些是教学规则,并非 Bitcoin 的 double-SHA256 Merkle 规则。

从叶子走到根

flowchart BT
  L0[H leaf order-0] --> A[H node L0 L1]
  L1[H leaf order-1] --> A
  L2[H leaf order-2] --> B[H node L2 L2]
  B --> R[H tree 叶数3 与 H node A B]
  A --> R

证明最后一个叶的包含,需要提供它在每层的兄弟哈希:第一层是复制的自己,第二层是左侧 A。校验方给定预期根、叶子、索引、总叶数和路径,按索引决定左右顺序,复算直到根;路径太短、叶数不合或孤叶的复制关系不符,都不能通过。验证算法对三叶正例返回 True;把最后一叶改为 forged、把兄弟换为另一串 32 字节、把预期根换掉、把叶数从 3 改成 4,测试全为 False。

更重要的反例:攻击者自己用 forged 造一棵新树,再附上新树的根和正确路径,包含检查又会返回 True。计算过程没有坏,坏在“预期根”未经可信渠道取得。实际轻客户端至少要核对头部链及相应信任前提,还要分开判断区块有效、交易有效和确认策略;只给一条 Merkle 路径不会验证完整账本。

校验步骤 本地能排除 仍然依赖
叶哈希与路径复算 叶/路径被静默改动 可信的根与编码规则
根绑定叶数量 本模型的部分树长度歧义 数据发布、交易唯一性等额外规则
对照独立可信根 恶意提供的新根 根从何处取得及其历史有效性

本模型是有限模型:没有 Bitcoin 的 witness commitment、真实区块头、PoW 验证、SPV 同步或区块重组。即使后来以真实节点得到一个根,也要记录其区块高度、区块哈希、验证规则和同步状态,不能把这段纯 Python 计算伪装成链上证明。原始测试见 examples/blockchain/evidence/00-05/,完整代码 SHA 与断言保存在对应运行的 manifest。

奇数层复制最后叶会产生与“叶子本身”相同的首层兄弟哈希,因此校验器不允许证明提供任意兄弟;当索引指向该层最后一项时,兄弟必须与当前哈希相等。实现还要求按叶数恰好用完所需层数,拒绝额外层与少一层。把叶数绑定到最终根,是这套教学承诺中明确承认树规模的一种做法;它不能代替网络的树构造规范,更不能直接导出 Bitcoin Core 的区块 Merkle 根。

假如攻击者提供旧可信区块的根和一条旧凭证,校验可能仍成功:包含证明不带“这是最新状态”的语义。应用需要独立规则描述高度、可接受的重组风险,以及从旧状态到新状态的撤销。把一个有效旧证明拿来重复领取资产,问题出在消费状态与授权边界,不在树哈希公式。

两道练习

  1. 手算结构:三叶场景下证明 order-2 包含需要几层兄弟?若省去一层,只核对一个中间哈希能否得出根?答案:两层;分别是自己与左子树的哈希。少一层时检验器的 width 仍大于 1,拒绝提前当作根。
  2. 替换可信输入:同时把 order-0 和预期根换成攻击者新树的值,原算法能发现“这不是规范历史”吗?答案:不能;它会对恶意新根返回 True,但与原可信根不等。这是可信起点的失败,不是哈希碰撞。

可迁移原则:每份证明都要问“证明的命题是什么”和“验证者的可信输入来自哪里”。软件供应链哈希、审计日志与数据可用性证明同样适用。

参考资料与核验边界

系列导航:区块链(03):签名证明授权了哪条消息 · 04 Merkle 包含 · 区块链(05):同一笔支付在 UTXO 与账户账本里的两种状态迁移;全系列入口 区块链(00):从中心化订单基线开始。