SSZ广义索引怎么定位字段? 图 1
SSZ广义索引怎么定位字段? · 图 1

广义索引不是“字段编号换个写法”,而是一条从可信Merkle根走到目标节点的二进制路线。只要先固定SSZ类型,再把路径拆成每一层的左右选择,就可以独立复算字段证明;反过来,如果类型、列表长度层或兄弟节点顺序有一个不一致,即使哈希函数完全正确也会得到错误根。

先在纸上读懂索引10

SSZ使用从1开始的二叉树索引:根是1,节点k的左孩子是2k,右孩子是2k+1。把索引写成二进制并去掉最高位的1,剩余位就是路径。十进制10等于二进制1010,去掉开头1后得到010,也就是从根开始“左、右、左”。这种读法比死记字段公式可靠,因为它也适用于嵌套容器。

        1
     2     3
   4  5   6  7
  8 9 10 11 ...

索引只说明节点位置,不说明叶子如何编码。验证前必须同时知道目标对象的SSZ schema、字段类型以及要证明的是值根、列表长度还是某个打包chunk。

容器字段如何变成广义索引

容器的字段会补齐到最近的2次幂叶子数。一个含5个字段的Container需要8个叶子槽,因此字段层深度为3。若字段从0编号,第2号字段位于底层索引2,其广义索引就是2³+2=10。字段0到4对应8到12,剩余13到15是零值填充。

输入对定位的影响
实际字段数5决定需要补齐到8个叶子
字段序号2决定底层偏移量
树深度3决定起始索引为8
最终gindex10二进制路径为010

嵌套字段不能把两段十进制索引直接相加。应先从外层字段走到子树根,再根据子类型继续追加路径位。

列表为什么容易证明错位

Vector的根只承诺固定数量元素形成的数据树;List还要把数据根与实际长度做一次mix-in。证明列表元素时,路径会先进入左侧的数据子树,而列表长度位于右侧节点。忽略这一层,往往会得到一个“看上去长度正确、哈希数量也正确”却永远无法匹配可信根的分支。

基本类型还可能被打包进32字节chunk。例如多个uint64共用同一叶子,字段序号不等于chunk序号。此时证明先验证chunk,再从chunk内按字节偏移解析目标值。不能把8字节值直接当作32字节叶子送入哈希。

从叶子复算到根

拿到leaf、proof数组、gindex和可信root后,从最低路径位开始逐层处理。当前位为1,说明当前节点是右孩子,应计算hash(兄弟节点 || 当前值);当前位为0,则计算hash(当前值 || 兄弟节点)。每处理一层就把索引除以2,直到回到根。

value = leaf
for sibling in proof:
  value = current_is_right ? hash(sibling + value) : hash(value + sibling)
  index = floor(index / 2)
accept only when value == trusted_root

proof长度应等于索引路径深度。少一个节点无法到根,多一个节点则证明对象与索引不匹配。生产代码还应验证每个节点恰好32字节。

一次失败证明的排查顺序

先核对可信根来自哪个slot或区块,再核对schema版本;然后打印gindex的二进制路径,检查字段编号是否从0开始;接着确认List长度mix-in、基本类型打包和零值填充;最后才检查兄弟节点顺序与哈希拼接方向。不要为了“跑通”而反转整个proof数组,因为不同库返回的顺序约定应由接口文档确认。

可复现记录至少包括:类型定义、对象标识、字段路径、gindex十进制与二进制、leaf、每层sibling、每一步中间哈希和可信root。这样第二位复核者无需相信界面结论,也能定位第一处分歧。

SSZ广义索引与Merkle证明的资料版本与边界

  1. ethereum.org SSZ:候选主题、当前接口或站内待复核页面。
  2. Ethereum Consensus Specs: Merkle proofs:机制、字段、操作路径与风险边界交叉验证。

资料访问日期为2026年7月23日。本文按当前规范解释机制和核验方法,不构成投资、法律或资金安全承诺。节点版本、链配置与接口字段可能变化,实际操作前应重新打开一级来源,并以目标环境返回为准。

延伸阅读:以太坊最终性Beacon验证者状态