统一地址 (Unified Address) (ZIP-316) 验证
这是一份学习指南,并非封装好的解码器或可复制粘贴的支付库。它解释了统一地址的结构,帮助你理解维护中的库在底层做了什么。对于任何处理真实资金的操作,请遵循ZIP-316 规范以及下方链接的官方实现。
整体概览
统一地址 (UA) 是一个承载多种接收方类型的单一地址字符串:透明 (Transparent)、Sapling、Orchard,或其组合。支付钱包 (wallet) 会自动选择其支持的最佳接收方 pool。
可以将 UA 想象成一个装有数张标记卡片的密封信封。每张卡片代表一种联系你的不同方式。要检查一个地址,应用程序必须:
- 打开信封: 解码文本字符串。
- 还原内容顺序: 撤销保护性混淆(F4Jumble)。
- 读取每张卡片: 提取各个接收方。
- 执行协议规则: 根据 typecode 范围忽略或拒绝条目。
为什么"仅解码 Bech32m"还不够
UA 使用 Bech32m 文本编码,但仅解码 Bech32m 并不会揭示可用的接收方。
ZIP-316 会在编码前刻意使用 F4Jumble 混淆有效载荷。F4Jumble 确保即使只更改地址中的一个字符,也会完全改变解码输出。这可以防止地址可塑性攻击,即攻击者在地址中间替换字节,同时让前缀和后缀看起来仍然有效。
关键规则: 只有当应用程序执行完整的解码和验证流程时,可塑性保护才会生效。部分解码会在保留全部风险的同时移除安全保障。
解码流程,逐步说明
第 1 步:解码 Bech32m 并检查网络
- 人类可读部分 (HRP):
u 表示主网;utest 表示测试网。(主网 UA 以 u1 开头,其中 1 是 Bech32 分隔符。)
- 长度限制: 标准 Bech32m 强制执行 90 个字符的限制。UA 通常超过此限制,因此必须在解码器中禁用标准长度检查。
- 将 5 位 Bech32m 字转换回标准的 8 位字节。
第 2 步:逆转 F4Jumble
F4Jumble 是基于 BLAKE2b 构建的 4 轮 Feistel 网络:
- 左半部分长度:
min(64, floor(length / 2)) 字节。64 字节的上限对应 BLAKE2b 的最大输出大小。右半部分包含剩余有效载荷。
- 哈希函数: 使用固定个性化标签(
UA_F4Jumble_G 和 UA_F4Jumble_H)交替使用 G 和 H。
- 轮次顺序: 正向编码运行 G(0) → H(0) → G(1) → H(1)。逆转(还原混淆)运行 H(1) → G(1) → H(0) → G(0)。
- 范围检查: 拒绝超出 ZIP-316 有效载荷大小限制的输入。
第 3 步:移除填充并验证 HRP
在混淆前,编码器会附加包含 HRP 的 16 字节,并以零填充。
- 在还原混淆后移除最后 16 个字节。
- 确认嵌入的 HRP 与预期网络匹配(
u 或 utest)。这可以防止测试网地址被意外地在主网上接受。
第 4 步:提取接收方
剩余有效载荷由 (typecode, length, content) 个条目组成,其中 typecode 和长度存储为紧凑大小整数(小数值使用单个字节)。已知接收方 typecode:
| Typecode | 接收方类型 | 内容长度 |
|---|
0x00 | 透明 (P2PKH) | 20 字节 |
0x01 | 透明 (P2SH) | 20 字节 |
0x02 | Sapling | 43 字节 |
0x03 | Orchard | 43 字节 |
除此之外,ZIP-316 还为前向兼容性保留了另外两个范围:
0xC0–0xDF(非 MUST-understand 元数据): 使用方必须忽略此范围内其无法识别的元数据项。
0xE0 和 0xE1(已分配的 MUST-understand 到期元数据): 当前的 ZIP-316 注册表将其分配给地址到期高度和时间。使用方必须理解这些项目,否则应拒绝该地址。
0xE2–0xFC(未分配的 MUST-understand 元数据): 如果使用方在此范围内遇到无法识别的项目,必须拒绝该地址。
对于已知接收方类型,验证编码长度是否与该类型指定的内容长度一致。对于元数据项,使用其编码的紧凑大小长度确定内容长度。拒绝被截断的条目或任何尾随字节。
首选接收方顺序。 地址成功解析后,钱包或支付工具应按以下顺序选择最佳接收方:Orchard,然后是 Sapling,最后是透明。
强制性 ZIP-316 拒绝规则
成功解码并不意味着地址有效。 官方 Zcash 钱包会严格拒绝违反以下规则的地址。Web 工具也必须拒绝它们,以防止支付失败:
- 缺少屏蔽 (shielded) 接收方: 地址必须至少包含一个 Sapling 或 Orchard 接收方。仅含透明接收方的 UA 在 ZIP-316 下无效。
- 重复 typecode: 每种接收方类型最多只能出现一次。
- 未排序的 typecode: 接收方必须以严格升序的 typecode 顺序出现。
- 冲突的透明接收方: UA 可以包含 P2PKH 或 P2SH,但绝不能同时包含两者。
- 格式错误的条目或填充: 网络前缀不匹配、有效载荷被截断或长度不匹配,必须立即触发拒绝。
- 无法识别的 typecode: 使用方必须忽略无法识别的项目,但 MUST-understand 元数据范围(
0xE0–0xFC)内的项目除外;如无法识别,必须拒绝。在当前注册表中,0xE0 和 0xE1 是已分配的到期类型,而 0xE2–0xFC 尚未分配。此外,拒绝任何未能满足上述强制有效性规则的地址,包括必须具有 Sapling 或 Orchard 接收方的要求。
开发者最佳实践
- 比较已解析的接收方,而非原始字符串。 在检查相等性之前,先解码地址。
- 对于任何处理资金的操作,请使用维护中的库。 将官方 Rust crate(例如
zcash_address)编译为 WebAssembly,而不是部署自定义的 JavaScript 解码器。
- 谨慎对待手写解析器。 如果你为了学习而编写解析器,请将其视为学习项目,并在将其用于任何用途之前,使用下方官方测试向量进行测试。
官方规范和参考实现
术语表
| 术语 | 含义 |
|---|
| Unified Address (UA) | 将多个接收方 pool 捆绑在一起的单一地址字符串。 |
| Receiver | 特定的支付目的地类型(透明、Sapling 或 Orchard)。 |
| Bech32m | 用于 UA 字符串的文本编码方案。 |
| HRP | 人类可读部分或网络前缀(u 或 utest)。 |
| F4Jumble | 确保地址完整性的可逆混淆算法。 |
| Typecode | 每个条目中定义有效载荷内接收方类型的数字。 |
| Malleability | 未被检测到的地址字节未经授权修改。 |
另请参阅:Viewing Keys