默克尔证明
API 返回的每一次解析结果都附带一份默克尔证明。它让你可以拿答案去与某个状态根比对,而不必信任发来它的 服务器。
这棵树
PiNS 把所有名称保存在一棵基于 128 位密钥空间的紧凑稀疏默克尔树中。名称的密钥是
SHA-256(domain_name) 的前 16 个字节,因此名称分布均匀,谁也无法选择自己的名称落在哪里。
「紧凑」意味着叶子位于其密钥前缀唯一的最浅深度,而不是固定的 128 层。在一棵存有数千个名称的树中, 大多数证明只有十来个哈希——小到足以在手机上、浏览器扩展里或钱包内部完成验证。
在动手写验证器之前,有两点后果需要知道:
- 证明长度是可变的。 请从响应中读取
proof_depth;千万不要假定某个固定大小。 - 空子树在任何高度都是 32 个零字节。 因此空树的根全是零——刚初始化的索引器报告
0000…0000是 正确的,而不是坏了。
证明包含什么
| 字段 | 含义 |
|---|---|
merkle_proof | 兄弟哈希,[0] 最深(离叶子最近) |
proof_depth | 兄弟节点数量 — 必须等于 merkle_proof.length |
proof_terminal | 路径末端是什么:Occupied、Vacant 或 Blocked |
smt_root | 这些兄弟节点所重建出的根 |
存在与不存在
正是终端节点,让一份证明能够像宣称「已注册」那样有说服力地宣称「这个名称未注册」:
Occupied— 路径末端是该密钥自己的叶子。名称存在,且给你的记录就是树中那一条。Vacant— 那里是一棵空子树。名称未注册。Blocked— 该位置被另一个名称的叶子占据。名称未注册;响应会附带那条阻塞记录,好让你验证它 哈希后确实等于在那里找到的叶子,并且它的密钥确实共享同样的前缀。
如果没有 Blocked,索引器就可以谎称某个位置是空的,从而隐藏一次注册。由于阻塞记录被完整附上,你可以
去验证这个说法,而不是被迫接受它。
验证
把记录哈希成一片叶子,然后依据密钥的比特在每一步决定走左还是走右,把兄弟节点逐层折叠回根。如果你算到
了 smt_root,这条记录正是该树所承诺的内容。
还剩一个问题:smt_root 是真正的根吗?去问 BNB Smart Chain 上的锚定合约就能回答。这两个步骤,
以及经过测试的 Python、PHP 和 JavaScript 代码,都在验证证明。