验证
Label 309 的三种验证器角色、判定状态、终局深度,以及带类型的错误目录——任何人如何仅凭公开基础设施就得出相同的结论。
Label 309 是被验证的,从不是被断言的。发布者把一个内容哈希以 label 309 锚定在 Cardano 上;从那一刻起,这项主张就立足于它自己的字节,任何持有该交易引用的人都能核查它。本页定义这次核查 如何 运行:三种验证器角色、每一种各触及与不触及什么、它们交出的判定状态、判定在其之下保持暂定的确认深度,以及那套带类型的错误目录——它让两个独立实现对同一输入得出同一种失败。
它的决定性属性是 服务独立性。一个合规的验证器,仅凭公开链、一个由验证器自选的 Cardano 浏览器或网关,以及——对内容主张和密封主张而言——同样由验证器自选的内容寻址存储网关,就能得出它的判定。它从不联系发布者。本标准不指名任何特定提供方;网关是运营者所提供的一个输入。
三种角色,每一种都是严格扩展
验证是分层的。每种角色都做它上一层角色所做的一切,再加上一项能力。处于较低层的角色本身就是一个完整、有用的验证器——它只是证明得更少。
| 角色 | 增加的能力 | 触及的对象 |
|---|---|---|
| 结构校验器 | 在记录字节之上做模式 + 域合规检查 | 无——一个纯函数 |
| 公开验证器 | 链解析、链上包含性、签名核验 | 一个 Cardano 浏览器 + 内容网关 |
| 接收方验证器 | 对密封载荷的试解密、明文哈希重算 | 验证器自己的私钥 |
结构校验器——一个作用于字节的纯函数
结构校验器是一个从字节串到结果的单一函数。它 不做任何 I/O、不做任何密码学签名核验、也不做任何解密。它从不接触网络、交易或密钥。给定相同输入,它每一次、在任何地方都返回相同输出——正是这一点让它能在发布者的工具链里、在第三方索引器里,或在一个确认长期良构性的归档工具里,在提交之前就跑起来,且全程无需服务器。
它的流水线是固定的:
1. resource bounds — a local, non-normative guard on input size; never a
Label 309 conformance error.
2. canonical decode — decode with a canonical-CBOR decoder (RFC 8949 §4.2.1):
definite lengths, sorted map keys, no duplicate keys,
valid UTF-8. Any malformed or non-canonical input →
a single MALFORMED_CBOR.
3. schema parse — type, length, and the chunk/length bounds; a strict
object mode that rejects unknown fields.
4. domain rules — cross-field constraints the schema cannot express:
registry membership, the items-or-merkle rule, COSE
structural shape, URI reconstruction, envelope shape.
5. result — { valid, record } with optional warnings/info, or a
sorted list of typed issues.校验器与档次无关:无论某个下游验证器打算对哪个子集采取行动,它都解析完整的 v1 模式。错误会让记录失败;告警与信息项会被呈现,但仍让记录有效。关键在于,它确认一个 COSE*Sign1 的 *形态_——四元素数组、分离式(null)载荷、一个良构的受保护头部——但 从不验证签名,也从不仅因为签名算法是它不认识的就拒绝一条记录(那会被标记为 SIGNATURE_UNSUPPORTED,严重级别 info,记录保持有效)。验证签名是公开验证器的活。本趟所强制的模式见 记录。
公开验证器——链、包含性与签名
公开验证器在结构校验器之上叠加了链。给定一个 Cardano 交易引用,它会:
- 解析一个验证器自选的浏览器。 浏览器链是一个输入;验证器按顺序逐个尝试,拉取 链上原始交易 CBOR——绝不是浏览器的元数据 JSON 投影。JSON 视图把 CBOR 主类型坍缩成一个 JSON 联合,并丢弃 map 键顺序、定长封帧和字节-与-文本判别符,因此一个从它重新编码的验证器无法复现出逐字节精确的签名输入,于是一条合规记录上的每个签名都会失败。单个提供方的否定回答 并非 链权威——交易可能就在链上,只是那个提供方不知道——所以验证器在交出
TX_NOT_FOUND之前会咨询所有其余的提供方;若所有提供方都不可达,它则交出PROVIDER_UNAVAILABLE。 - 把拉取到的字节绑定到交易引用。 在从一笔拉取到的交易里读取 任何东西 之前,验证器先在拉取到的交易体字节之上重算
blake2b-256——按账本定义,这就是交易 ID——一旦与所请求的哈希不符就拒绝该响应。随后它在拉取到的辅助数据字节之上重算blake2b-256,一旦与现已核验过的交易体的auxiliary_data_hash不符就拒绝。两个摘要都是在 与拉取到的完全一致 的字节之上计算的,绝不是某种重新编码。任何一项检查失败的响应都携带着可证伪的错误字节,会被丢弃;若没有提供方给出一个能挺过这道绑定的响应,报告就携带TX_INTEGRITY_MISMATCH。这一步之后,记录及其周围交易的每一个字节都被密码学地承诺到了调用方所提供的哈希上——没有哪个浏览器能在不制造一个 blake2b-256 第二原像的前提下替换、篡改或截断记录。 - 拆解辅助数据。 Conway 时代的账本承认三种辅助数据编码,全都有效:一个裸的无标签元数据 map、一个两元素
[ metadata, scripts ]数组,以及一个 tag-259 map(元数据在键0之下)。验证器 MUST 接受这三种,并 纯粹依据顶层 CBOR 类型与标签 来分派——一个 tag-259 值是带键 map 形态,一个无标签数组是两元素形态,而一个无标签 map 总是元数据 map 本身。它 MUST NOT 去检查某个 map 的键来猜测形态;任何其他顶层形态,或任何非 259 的标签,都是MALFORMED_CBOR。若被绑定的交易不携带任何 label-309 元数据,验证器交出METADATA_NOT_FOUND——这是一个 可归因于记录 的结果,因为被绑定的交易本身就证明了这种缺失:没有哪个提供方能在不让绑定失败的前提下剥掉那段元数据。 - 重组整体分块数组。 label-309 的值是被切成一个由 ≤ 64 字节字节串组成的数组的规范 CBOR 记录体。验证器按顺序把元素逐字节拼接——返回原始字节,不做重新编码趟——这样那道规范 CBOR 检查仍能抓住一个不合规的链上编码。
- 结构校验 重组后的记录体(上一层角色)。校验器一旦拒绝,报告就短路为判定
failed。 - 检查确认深度(见下文)。一笔低于阈值的交易会在此处停下,判定为
pending。 - 在严格 Ed25519 之下验证每一个记录级签名。 它不做解密。签名解析、域分隔后的载荷,以及严格的验证规则,都在 签名 中规定。
- 拉取并哈希核对内容,只就那些它能归因于某个 URI 自身内容地址的字节来追究记录的责任(见下文)。它不做解密。
为什么用原始 CBOR,而非 JSON
签名是在记录体逐字节精确的规范 CBOR 之上计算的。元数据的 JSON 投影在构造上就是有损的——它无法往返回到那些字节。从 JSON 重新编码会破坏一条合规记录上的每一个签名。链上原始交易 CBOR 才是任何密码学检查唯一权威的输入;JSON 视图是供人类显示用的,在验证已经通过之后才用。
接收方验证器——解密并重算
接收方验证器是一个额外持有一把私钥的公开验证器。对于一个寻址给它的密封项,它用自己的密钥对链上的密钥槽进行 试解密,命中时恢复出内容密钥,解密密文,再 对照链上承诺重算明文哈希——从而把加密字节与内容存在性主张之间的闭环合上。由于每个密封项都至少携带一个内容哈希条目,这次重算总有一个具体对象可供比对。密封信封、密钥槽,以及解封装构造,都在 密封 PoE 中规定。
在接收方动用任何 KEM 或 AEAD 原语之前,它会重跑结构校验器已经跑过的那套相同的信封形态与资源检查,先把一个结构上无效或过大的信封拒掉。其中两道原语之前的防护是部署期钉定的资源上限,而非线上字段:参考上限是 MAX_SLOTS = 1024 槽,以及解码后的 enc 信封 65536 字节,两者都远高于约束任何诚实记录的那道约 16 KiB 的 Cardano 交易元数据天花板,因此一条超过任一上限的记录即为畸形,并在任何单个原语运行之前就以 ENC_SLOTS_TOO_MANY 或 ENC_ENVELOPE_TOO_LARGE 被拒绝。各部署 MAY 把它们收得更紧。
有一道形态检查对安全性是承重的:封装材料 MUST 在单个 slots[] 内彼此互异——对 x25519 是所有 epk 值,对 mlkem768x25519 是所有 kem_ct 值。记录内部出现重复时,会在任何 KEM 或 AEAD 原语运行之前就以 ENC_SLOTS_DUPLICATE_KEM_MATERIAL 被拒绝,因为重复的 epk/kem_ct 会破坏零 nonce 包裹所依赖的逐槽密钥唯一性。跨记录或跨密钥的密钥复用是生产方的义务,验证器无法察觉;只有记录内部的重复可被察觉。这三项检查都是结构性的——校验器在每条记录上都强制它们(重复材料、槽位计数和解码后信封这几个码属于 Part-A 码);接收方只是作为纵深防御再跑一遍。
解封装本身是接收方从信封——绝不从任何旁路输入——复现出的两个密码学步骤。首先,它在循环之前把 slots 转录哈希 slots_hash 重算一次,并在每个槽位之间保持不变:slots_hash = SHA-256("cardano-poe-slots-transcript-v1" || canonicalEncode(SLOTS_TRANSCRIPT)),其中转录钉死了头部字段、线上的密钥槽集(每个密钥槽字段都是单个字节串——没有逐字段的分块需要规范化),以及经由 hashes_hash 绑定的该项哈希主张。绑定 hashes_hash 正是让接收方仅凭链上字节、在任何密文拉取之前就能确认信封是为 这份确切哈希主张 而密封的那一环——一个被拼接到带有不同 hashes map 的项上的信封会令 MAC 失败。它遍历 所有 密钥槽、不提前跳出;只有当某个密钥槽所产出的内容密钥也能就着那个不变的 slots_hash 复现出线上的 slots_mac 时,它才被接受。接受还折入了一个 秘密无关的有效性比特:在经典路径上,一个被炮制来把 X25519 共享密钥逼成全零值的密钥槽会把该比特置假(一次对照 0^32 的恒定时间比较),KEK 被恒定时间地选成一个由 0^32 派生出的虚值,使循环做同样的工作,而该比特又门控着这个密钥槽的接受——一个无效 ECDH 的密钥槽无论其包裹或 MAC 如何都绝不会打开。包裹打开与稍后的内容打开都是 原子的:在 AEAD 标签失败时,它们不返回任何明文,交回的候选值(被包裹的密钥,或内容明文)是一个与那段失败密文无关的固定或伪随机虚值——任何未经校验的明文都绝不会被释放。
一把接收方密钥 MAY 合法地命中不止一个密钥槽:生产方可以把同一个内容密钥、向同一个接收方、跨好几个密钥槽密封,每个槽配一份全新的逐槽 KEM 材料,以此把接收方数量撑大——这是一种正当的隐私技术,且与重复封装材料的拒绝截然不同,后者只在 epk/kem_ct 完全相同时触发。验证器选取 第一个 命中的密钥,且 MUST NOT 仅因有好几个槽命中就拒绝。它 MUST 拒绝的唯一反常情形,是两个命中的密钥槽恢复出 不同 的内容密钥(以恒定时间比较):循环携带一个 cek_conflict 比特,若任何靠后的命中产出与已选密钥不同的值,便交出那个唯一的通用失败。这是纵深防御——在槽集承诺之下,一个不同密钥的命中本就不可行,于是这项检查只是安全地失败关闭。
随后,在所恢复出的内容加密密钥之上,它派生出内容密钥(该密钥的一个 HKDF 派生叶,以 enc.nonce 加 salt),并逐块打开那段 分段 STREAM 密文,在释放某块的明文之前先校验该块的标签。逐块 AAD 为空:所有头部上下文都已经通过 slots_mac 传递性地绑定到了密钥上,因此翻动任何头部字段都会改变接收方所派生的东西,于是流根本打不开。截断由末块标志捕获——缺失的末块、跟在其后的数据、一个落在非末块上的末块标志,或一个短的非末块,都会作为 TAMPERED_CIPHERTEXT 失败。这套分段格式不施加任何密码学层面的载荷天花板(那个 88 位逐块计数器允许 2^88 个块);现实中的上限是一道部署期的拒绝服务策略,随着流被读取而增量地强制。在口令路径上,接收方先读取那段前导的 32 字节承诺头部,并以恒定时间比较,之后才打开任何块。
接收方路径正是错误目录显出其精确度的地方:它把 没有任何密钥槽接受这把密钥(错误的接收方)这一情形,与某个密钥槽接受了密钥、但 槽集 或 密文 被篡改这一情形区分开来。这些是不同的安全主张,携带不同的码(见下文)。然而一个 不受信任的调用方 无论何种原因都只收到恰好 一个通用失败形态——没有槽位打开、槽集被篡改,或内容标签失败——而 响应 从不透露发生的是哪一种,也不透露是哪个槽位命中;那些带类型的码只是给受信任本地调用方的内部诊断。
时延遵循一个明确的模型。验证器 MAY 在无命中检查处——早于内容解密——返回,于是一个非接收方与一个接收方会花费明显不同的时间。那种差异只透露 接收方-与-非接收方,绝不透露是哪个槽位命中,也没有任何密钥材料。非接收方与「内容密文无法打开」的接收方之间,时延一致 并非 必需,且 MUST NOT 强制要求一次虚值内容打开——那会把内容解密的代价强加给每一个路过者。确实 成立的那条恒定时间保证,是 跨槽位 那条:在单把私钥的一趟里,循环以恒定时间比较遍历所有密钥槽,因此不泄露这把密钥解开了 哪个 槽位(如果有的话)。
终局:确认深度
Cardano 的结算是概率性的。一笔仅一个区块深的交易仍可能被一次短的重组孤立掉;一笔许多区块深的交易则已以压倒性的可能性结算。一个把仅一个区块深的记录称为 valid 的验证器,会让攻击者在一条竞争分叉上重新锚定一条相互矛盾的记录,并对两条都收获一个「valid」判定——从而悄然破坏整个证明所依赖的只增不改假设。
所以,当一条记录停留在某个确认深度阈值之下时,验证器把它报告为 pending,而非 failed。推荐的 通用阈值是 ≥ 15 个区块(大约五分钟)。该阈值是验证器策略,而非线上常量:处理高价值或具证据效力记录的部署 SHOULD 把它朝硬终局调高,而验证器 MUST 呈现它所使用的阈值,以便消费方在其上叠加一个更严格的策略。一条 pending 记录是良构且已上链的;它只是还没结算得足够深,可能在稍后的一次重试中解析为 valid。
确认深度、区块高度和区块时间,都是 由浏览器断言、验证器从不捏造的事实。交易引用绑定让交易 内容 无需信任,但 关于 它的链上事实无法从字节推导出来——交易 CBOR 不携带时间戳或高度。深度按 (解析浏览器的链顶高度) − (包含区块的高度) + 1 计算,因此链顶区块里的交易深度恰好为 1;区块时间是包含区块所在时隙的 POSIX 时间戳(整数秒、UTC),取自浏览器的时间字段,绝不由验证器重算。由于这些事实立足于浏览器之言,验证器 SHOULD 从至少两个独立浏览器解析它们,并呈现任何分歧;那些以区块时间为承重的部署——法律公证、截止期争议——则 MUST 交叉核对它。报告携带所解析的深度,连同它所比对的那个阈值,以及 block_time(在可得时还有 block_slot)。
判定状态
验证器以 四种机器判定 之一作结,每一种都与一个进程退出码一一配对,这样一个调用方——一个 CI 关卡、一个监控器、一个脚本——无需解析结构化报告,就能把一个可归因于记录的失败与一个短暂的运维失败区分开。统领一切的准则是 归因:要给一条记录定罪,需要记录自己的字节——或可归因地绑定到它引用的字节——确实能提供的证据。任何提供方的不端行为都无法制造出一个 failed。
| 判定 | 退出码 | 含义 |
|---|---|---|
| valid | 0 | 验证器跑过的每一项检查都返回 ok;不存在任何 error 级别的问题。 |
| failed | 1 | 一个 可归因于记录 的失败:结构校验器拒绝了字节、某个签名未通过验证、一个可归因的哈希不匹配、被绑定的交易不携带任何 label-309 元数据(METADATA_NOT_FOUND),或某条 deny-host 规则触发了。 |
| unverifiable | 2 | 没有可归因于记录的错误,但某项必需的检查无法运行、或无法被 归因——交易没有解析出来、没有提供方响应挺过绑定(TX_INTEGRITY_MISMATCH),或所承诺的内容/密文无法获取或归因。同一条记录可能在重试时、或在另一个网关下验证为 valid。 |
| pending | 3 | 结构良构且已上链,但低于确认深度阈值(INSUFFICIENT_CONFIRMATIONS);可能会结算。来自一条 pending 记录的任何结果都不得被当作最终结论呈现。 |
那些不可归因于记录的验证器宿主运行时失败,使用退出码 4 及以上,且不对应任何判定。
当存在任何 error 级别的问题时,MUST NOT 报告 valid 判定;一条记录 MAY 在 warnings 和/或 info 列表非空的情况下仍为 valid。两个层级都不得为了让记录通过而把一个错误「软化」成告警。每种判定都为它自己的情形保留:pending 绝不顶替 valid 或 failed,而一个可归因于提供方的失败是 unverifiable,绝非 failed。
一道 承诺下限 统领着可得性:当内容检查跑过了、但可得性失败导致记录的内容承诺 没有 一个被真正核验过时,判定是 unverifiable,绝非 valid——一个 valid 判定意味着至少有一个内容承诺被检查过。完整性结果不受这道下限影响:可归因的字节若未通过某个承诺,无论别的什么是可得的,都产出 failed。
内容地址绑定与归因
第 8 步(以及对接收方验证器而言,解密)从验证器自选的内容寻址存储网关拉取字节——而网关是不受信任的。上文那条判定准则取决于一个验证器 MUST 能就每一段拉取到的字节流回答的问题:这些字节能否被归因于 URI 本身? 两种方案都是内容寻址的,所以都能:
ipfs://——在拉取到的内容之上重算 CID(对原始编解码器的 CID 直接取 multihash;对 DAG 形态的 CID,沿所解析路径逐块取摘要)。ar://——校验那笔已签名的 Arweave 交易,并对照其data_root重算分块 Merkle 树;对一个 ANS-104 数据项,重算深哈希、验证所有者签名,并检查该签名的 SHA-256 是否等于 URI 中的 ID。
满足记录自身摘要的字节无需绑定检查——记录的承诺至少与存储层的一样强。在 确实 施加绑定之处,归因决定了一次不匹配意味着什么:
- 可归因的字节——绑定已核验,或由调用方带外提供——在未通过某个承诺时会被追究到记录头上:项内容报
URI_INTEGRITY_MISMATCH(一个 硬 完整性失败,无论任何同级 URI 持有什么)、叶子列表报MERKLE_*家族、可归因的密文数据块报TAMPERED_CIPHERTEXT。判定failed。 - 不可归因的字节——绑定未核验,或核验了却失败——指控的是提供服务的 提供方,绝非记录:
URI_PROVIDER_INTEGRITY_MISMATCH(告警)。验证器带着其余的 URI 和网关继续;一项最终没有任何可归因字节的主张,会以一个可得性结果作结(CONTENT_UNAVAILABLE、MERKLE_LEAVES_UNAVAILABLE、CIPHERTEXT_UNAVAILABLE),判定unverifiable——就好像什么都没拉取过一样。
这是交易引用绑定在存储侧的对应物:一个行为不端的网关只能降级 可得性,绝不能动判定。URI_INTEGRITY_MISMATCH 与 URI_PROVIDER_INTEGRITY_MISMATCH 因此是不同的码——前者给记录定罪,后者给提供方定罪——一个把两者混为一谈的验证器,会让一个敌意网关伪造出一个 failed。解密之后的明文哈希复核无需归因限定词:在已认证的信封之下打开的密文,由 AEAD 自身归因,因此那里的明文哈希不匹配(URI_INTEGRITY_MISMATCH)总是可归因于记录的,并强制 failed。
带类型的错误目录
每一种失败模式都解析为一个来自单一封闭目录的码。码是 SCREAMING_SNAKE_CASE,且一个合规实现 MUST 恰好发出那些字符串——绝不是某个解析器的内部小写码,也绝不是自由文本消息。两种语言的两个实现,在同一输入上得出同一个码;完整的规范性清单由一致性测试套件逐字节地钉死,且该目录是锁定的(码只能经修订增补)。
严重级别模型
每个问题都携带三种严重级别之一,而这种区分是承重的:
- error——使判定失效。一个
valid结果不能与任何error共存。 - warning——一次非致命的运行时反常(单个网关失败了、一个叶子列表只部分可得),它不阻止
valid。 - info——一次刻意的不检查:验证器 选择 不去评估的某个方面(一个超出其档次的字段、一个不认识的可选算法)。一个 info 项不是被软化的错误,也绝不被当作错误来用。
有一个码自成一类:INSUFFICIENT_CONFIRMATIONS 映射到 pending 判定,而非映射到某个严重级别,因为记录是良构的、只是在等待结算。
错误家族
该目录归入若干家族。一组有代表性——而非穷尽——的码:
| 家族 | 严重级别 | 代表性的码 |
|---|---|---|
| 畸形 / 非规范 CBOR | error | MALFORMED_CBOR、CHUNK_TOO_LARGE |
| 模式 | error | SCHEMA_TYPE_MISMATCH、SCHEMA_MISSING_REQUIRED、SCHEMA_UNKNOWN_FIELD、SCHEMA_EMPTY_RECORD |
| 不支持的算法 | error | UNSUPPORTED_HASH_ALG、UNSUPPORTED_AEAD_ALG、UNSUPPORTED_KEM_ALG、UNSUPPORTED_MERKLE_COMMIT_ALG |
| 浏览器 / 元数据 | error / pending | TX_NOT_FOUND、PROVIDER_UNAVAILABLE、TX_INTEGRITY_MISMATCH(→ unverifiable)、METADATA_NOT_FOUND(→ failed);INSUFFICIENT_CONFIRMATIONS(→ pending) |
| 签名 | error / info | MALFORMED_SIG_COSE_SIGN1、SIGNER_KEY_UNRESOLVED、SIGNATURE_INVALID、WALLET_ADDRESS_MISMATCH;SIGNATURE_UNSUPPORTED(info) |
| 加密 / KEM / 包裹 | error | KEM_EPK_LENGTH_MISMATCH、KEM_CT_LENGTH_MISMATCH、WRAP_LENGTH_MISMATCH、ENC_SLOTS_DUPLICATE_KEM_MATERIAL、ENC_SLOTS_TOO_MANY、ENC_ENVELOPE_TOO_LARGE、ENC_REQUIRES_CONTENT_HASH |
| 解密结果 | error | WRONG_DECRYPTION_INPUT_SHAPE、WRONG_RECIPIENT_KEY、TAMPERED_HEADER、TAMPERED_CIPHERTEXT、KDF_DERIVATION_FAILED |
| URI / 内容 | error / warning | INVALID_URI、URI_TARGET_FORBIDDEN、URI_INTEGRITY_MISMATCH、CONTENT_UNAVAILABLE(→ unverifiable)、CIPHERTEXT_UNAVAILABLE(→ unverifiable);URI_PROVIDER_INTEGRITY_MISMATCH、URI_FETCH_FAILED(告警) |
| Merkle 列表承诺 | error / warning / info | MERKLE_ROOT_MISMATCH、SCHEMA_MERKLE_LEAF_COUNT_MISMATCH;MERKLE_LEAVES_UNAVAILABLE(双重);MERKLE_UNSUPPORTED(双重) |
| 服务独立性 | error | SERVICE_INDEPENDENCE_VIOLATION |
那些网络/策略码(TX_NOT_FOUND、PROVIDER_UNAVAILABLE、CONTENT_UNAVAILABLE、CIPHERTEXT_UNAVAILABLE)——以及 TX_INTEGRITY_MISMATCH,它的不匹配是对照提供方而非记录可证的——在问题列表里是 error 级别(它们阻止一个 valid 判定),但 不 可归因于记录,所以它们映射到 unverifiable,绝非 failed。URI_PROVIDER_INTEGRITY_MISMATCH 是同一准则下的逐次拉取告警。有几个码携带 双重严重级别——ENC_UNSUPPORTED、MERKLE_UNSUPPORTED、OUT_OF_PROFILE_SKIPPED 和 MERKLE_LEAVES_UNAVAILABLE——默认读作 info/warning,并在一个严格上下文中(接收方角色、merkle-only 升级、严格端到端模式,或承诺下限)升格为 error。
接收方路径是诊断上最精确的家族。对一个 受信任的本地调用方,一次失败的解密解析为三个内部码之一:WRONG_RECIPIENT_KEY 意味着没有任何密钥槽接受所提供的密钥(从未恢复出任何内容密钥);TAMPERED_HEADER 意味着恢复出了一把密钥、但候选内容密钥未能就着 slots_hash 复现出 slots_mac(某个密钥槽、某个头部字段,或 slots_mac 本身被改动了);TAMPERED_CIPHERTEXT 意味着槽集完好、但恢复出密钥之后内容 AEAD 标签失败了。这三者对那个本地调用方在结构上是可区分的,而它们之间的边界不泄露任何密钥材料。对一个 不受信任的外部调用方,这三者都坍缩成上文描述的那个唯一通用失败,从响应形态上无从区分。在时延模型之下,一次被允许的提前无命中返回,把 WRONG_RECIPIENT_KEY(一个非接收方)与那两个接收方侧的篡改结果分开,而后两者彼此之间再无进一步区分——于是这种诊断精确度永远不会沦为一个按槽位或按密钥选择的预言机。
各个码如何映射到四种判定
一个由结构校验器发出的码,意味着记录字节不合规。公开验证器会用判定 failed——退出码 1——和校验器的问题列表把报告短路掉,而不再运行任何后续的链上或密码学工作。一个仅在结构校验通过 之后 才发出的码——一个未通过验证的签名、一个未匹配的 可归因 哈希、被绑定交易上的 METADATA_NOT_FOUND——同样是 failed:每一个都可归因于记录,都是记录自己字节所提供的证据。
一个短暂的或可归因于提供方的失败则是另一种判定:无法拉取或无法归因的内容、一个不可达的浏览器,或一个供出未通过交易引用绑定的字节的提供方,全都映射到 unverifiable——退出码 2——因为没有一个是记录的过错,且同一条记录可能在重试时验证为 valid。因此退出码所保留的那道区分,在失败状态之间是三路的:可归因于记录的 failed(1)、运维或可归因于提供方的 unverifiable(2),以及低于阈值的 pending(3)。
有几个码携带依上下文而定的严重级别。一个读到比它所声明档次更丰富的记录的验证器(例如一个纯哈希验证器遇到一个密封项),在显示上下文中把那个多出来的字段报作 info 并仍然校验哈希主张,或在一个严格的端到端审计上下文中报作 error。同样地,一个不认识的 可选 签名算法是 info——内容存在性主张并不依赖它——因此即便一个咨询性签名无法验证,一份公开的纯哈希证明仍然 valid。
服务独立性不是可选项
验证从不反向触及发布者。一个合规验证器发起的每一个出站调用,都指向运营者所选的基础设施——一个用于查交易的 Cardano 浏览器,以及用于任何 ar:// 或 ipfs:// 字节的内容寻址存储网关。这是被结构性地强制的,而非一句代码注释里的承诺:
- 每一个网络调用都经由单一的出口包装器路由,它把
url、method、status、字节数和用途——对 每一个 调用,成功、失败和重试一视同仁——记入报告上一份强制的审计轨迹。一个拿不出该轨迹的验证器,无法证明它的独立性。 - 那个包装器接受一份部署提供的 deny-host 列表,并对任何指向匹配主机的调用以
SERVICE_INDEPENDENCE_VIOLATION硬失败。该列表是一个运营者输入——一致性套件会用实现者自己的域名来填充它——而非 Label 309 的某个线上常量。 - 一个一致性测试装置在一个网络中针对冻结的夹具交易运行验证器,而在该网络里运营者自己的域名解析到无处可去,并断言验证器仍然返回
valid。该断言是在操作系统网络层做出的,而非靠对源码做 grep——一个靠硬编码 IP 触及禁用主机的验证器会通过源码扫描,却会在这项测试上失败。
验证器需要一个可达的 Cardano 浏览器,此外不需要任何实现者专属的东西。内容网关、一把接收方私钥,以及一个链下叶子列表,是分别解锁内容、接收方和 Merkle 检查的可选输入。它们没有一个是本标准所指名的服务,也没有一个是发布者。