How to check an export
An export of an audit trail, or a package for an agency, can be checked by anyone who holds it, offline. Each holds the entries, each with its salt (a random value kept with the entry, so that its hash alone reveals nothing of what it says), each entry's proof that it is in its batch, and each batch's record. It is signed with the Platform's export key.
The tool
Download audit-verify, a small open program that needs only Node.js 22.18 or later. Its SHA-256 is 5bdcb65def954f492a0e24745f680a9f332a3ba4eaad59601eeac14103e70265. Run it on the file:
node audit-verify.ts export.json --key 74fd75cba0d011651fe272481edf65806f47b3ce4027ca44652fa076b6f10110
It prints VERIFIED or FAILED, each problem by chain, entry number and batch number. It never prints what an entry says. Always give it the whole public key below, as above: without it, nothing verifies, since anyone can sign a file with a key of their own.
The export key
| Key id | 25e1b76a3c206168 |
|---|---|
| Public key, Ed25519 | 74fd75cba0d011651fe272481edf65806f47b3ce4027ca44652fa076b6f10110 |
The key id is the first 16 hex digits of the SHA-256 of the public key, to tell keys apart at a glance. It is too short to prove a key: give the tool the whole public key. A file signed with any other key was not made here.
What the tool checks
In short: that no entry in the file changed after the file was signed, that each entry is in its batch as sealed, and that the file holds only what the Platform signed. It works offline. The steps, for anyone who wants to check the tool itself:
- The signature: Ed25519 over the tag PLAT-EXPORT-v1 and the manifest in canonical JSON (RFC 8785), with the key above.
- Each entry: its body in canonical JSON, with its salt and its chain's entry tag, hashes to the entry hash the manifest signed. The body is the entry's tenant_id, seq, occurred_at, actor_type, actor_id, event_type, refs, payload, ip and session_id.
- Each entry's path inside its chain's part of the batch leads to that part's root; so does the part's last leaf, rebuilt from the chain's tag, owner, first and last entry and last chain hash.
- Each part's root, with its salt and the tag PLAT-CHAINROOT-v1, walked up the batch tree, gives the batch's root.
- Each batch's fingerprint is SHA-256 of the tag PLAT-ANCHOR-v1, the batch number and the end of its window in milliseconds since 1970 (8 bytes each, big-endian), the previous fingerprint (32 zero bytes for the first batch) and the batch's root.
- Each entry's chain hash: SHA-256 of its chain's tag, the chain hash before it and its entry hash; entry 1 starts from the chain's genesis value. The tool says which entries the file holds, and names each gap between them: a file of chosen days or of one matter leaves the others out.
- The file holds each signed entry once, with its salt, and nothing the manifest did not sign.
- Each batch follows the one before it.
- For a package: its summary, one line per search, is what the entries say, and every provider call it holds has a decision that allowed it and a certified purpose, recorded before it. The provider calls come from the internal chain, the Platform's own trail, which no account reads: a package holds only those made for the account.
What a trail does not show: an action that was never recorded, a change made before its batch was sealed by someone able to rewrite the whole unsealed end, and a rewrite by someone who holds both the audit service's store and the export key. An export you already hold still shows any later change to what it holds. Searches' criteria and results are never in a trail.