Audit chain
Every protocol event — grants, revocations, cease notices, certificate issuance, value access, administrative actions — is appended to a single ordered audit chain.
Row format
Section titled “Row format”Each row carries the event body and two hashes:
row_hash = SHA256( prev_row_hash ‖ canonical_json(event) )prev_row_hash— the previous row’srow_hash, as 64 lowercase hex chars. The concatenation is textual: the 64-char hex string immediately followed by the canonical JSON string, then UTF-8 encoded and hashed.- The genesis value for the first row is 64 zero characters:
000…000(64 ×0). row_hashis stored alongside the row; the pair (prev_hash,row_hash) makes each row a link.
Event body rules
Section titled “Event body rules”- Event bodies MUST NOT contain personal data — names only field names, hashes, ids, and timestamps. This keeps the full chain exportable to auditors and anchorable externally without a redaction step.
- Bodies are hashed via canonical JSON; anything canonical JSON rejects (cycles, undefined, non-finite numbers) MUST be rejected at append time.
Storage requirements
Section titled “Storage requirements”- The chain MUST be append-only at the storage-permission level (the writing role holds no UPDATE/DELETE grant) and SHOULD additionally be protected by a trigger or equivalent.
- Appends MUST be serialized (the reference implementation takes an advisory lock around head-read + append) so two concurrent writers cannot fork the chain.
Verification
Section titled “Verification”To verify a slice of the chain given an expected starting prev_hash
(genesis, or a previously verified row hash):
prev = expected_first_prev_hashfor each row in order: assert row.prev_hash == prev # continuity assert SHA256(row.prev_hash ‖ canonical_json(row.event)) == row.row_hash prev = row.row_hashRewriting, inserting, or deleting any historical row breaks every
subsequent row_hash.
Test vector
Section titled “Test vector”Genesis:
prev_hash₀ = 0000000000000000000000000000000000000000000000000000000000000000Row 1 event:
{ "action": "share.created", "actor_type": "consumer", "entity_id": "sh_0000000000000000example0" }row_hash₁ = c0216b0d3dbdd053306dc3d0d2af1868832f3e5be408e97802c72bddc3882933Row 2 event (with prev_hash₂ = row_hash₁):
{ "action": "share.revoked", "actor_type": "consumer", "entity_id": "sh_0000000000000000example0" }row_hash₂ = 9a85c75b24de5aa169efb080cc49be4bfd60c26dc176e0de67a7efa2e4497d8bA conforming verifier MUST reproduce both digests.
Chain head checkpoints
Section titled “Chain head checkpoints”The current head (audit_seq, row_hash) is periodically
anchored externally. A checkpoint at sequence N commits
to every row ≤ N: to alter history the operator would have to break this
chain and the external ledger’s Merkle consistency proofs simultaneously.