防篡改的审计轨迹,意思是任何对历史数据的改动都能被检测出来。经典做法 —— 借鉴自区块链但简单得多 —— 就是哈希链:每条记录里都包含上一条的密码学哈希。改动任何一条历史记录,后面所有的哈希都会跟着变。
做法
对每张发票,计算:
hash_n = SHA256(prev_hash || canonicalize(payload_n))
// 其中 canonicalize() 产生一个确定性的字节序列
// (键已排序、无空白、数字格式稳定)。把哈希存到发票本身上。要证明任何一张单独发票的完整性,你只需要验证它的哈希等于把上一张发票的哈希和规范化后的载荷一起重算出来的结果。ZATCA Phase-2 强制的就是这个方案。
为什么规范化(canonicalization)是一切
同一逻辑对象,如果两次 JSON 序列化字节不一样,哈希就会不一样。RFC 8785(JCS —— JSON Canonicalization Scheme)是标准做法:按字典序排键、不带无意义空白、数字采用最短唯一形式、字符串按 RFC 8259 转义。
按租户分链,而不是全局一条
每个租户跑各自的链,而不是全局一条。两个原因:(1) 你可以按租户分片存储和计算,不用协调写入;(2) 一个租户链上的损坏不会污染所有人的审计轨迹。
事后核验
审计师(或者出于防御,你自己)可以从 genesis 重放整条链:把第一张发票的载荷在 prev_hash 为空的情况下哈希一遍,检查是否对得上存储的哈希,然后逐张往下走。任何偏差都会精确定位被篡改的那条记录。Web Crypto 的 subtle.digest 在任何现代运行时(Node、Deno、浏览器)里都能给你 SHA-256。
Invocie 在每张发票和每条 ComplianceLog 记录上都写一条链式哈希。这两条链是独立的:发票链用于 ZATCA 风格的证明,日志链用于运维审计。