A regular audit log records what a system says it did. A cryptographic AI audit trail proves what it actually did — and that the record hasn't been touched since. Not a matter of degree: a specific set of mechanisms — a signature over a canonical serialisation, a key ID, a hash chaining each receipt to the one before it, and an independently held archive. This walks through what each one does, and what happens when you remove it.
Every AgenticRail gate receipt is Ed25519-signed (legacy receipts before 2026-06-07 remain HMAC, verify-only) and independently verifiable offline.
Try the demo Public keysMost AI agent deployments produce audit logs — step names, timestamps, decisions, inputs, living in a database or a log aggregator. When an auditor asks what the agent did, you export the records and present them.
The problem isn't the content. It's that nothing in the records proves they haven't changed since they were written. A database row can be updated. A log file can be overwritten. An administrator with the right permissions can delete an inconvenient entry, and nobody outside the system can detect it.
A regulator asks an AI operator to prove human oversight ran before each automated decision last quarter. The operator produces the logs. Every record shows the oversight step completed first.
The regulator's question: how do we know these records haven't been updated since? The operator's answer: access controls and a change-management process. The regulator's problem: that requires trusting the operator. The point of the requirement was evidence that doesn't.
Encryption protects data in transit and at rest — it says nothing about whether the data was modified. Cryptographic integrity is a different property, built from a specific set of mechanisms. Remove any one and the trail loses its tamper-evidence.
k2_2026-06-07_ed25519. Signing keys rotate periodically. Without a key ID, a verifier checking receipts after a rotation can't tell which key to use. With one, verification selects the correct key regardless of when the receipt was written, across any number of rotations, with no re-signing.prev_receipt_hash — a SHA-256 of the entire previous receipt's canonical content, signature included. Altering an earlier receipt breaks every hash after it, not just that record — a single tampered receipt is caught immediately, and where it broke is visible.| Attack or failure mode | Regular log | Cryptographic trail |
|---|---|---|
| Modify a DENY to ALLOW after the fact | Undetectable | Signature breaks on verification |
| Delete a record showing a violation | Undetectable | Breaks the hash chain from that point on |
| Change a timestamp to alter apparent order | Undetectable | Timestamp is a signed field — signature breaks |
| Rewrite an entire chain, re-signed with the real key | N/A | Not caught by the chain alone — caught by comparison against the independent archive |
| Verify old receipts after key rotation | N/A | Key ID selects the correct historical key |
| Third-party verification without system access | Impossible | Needs only the receipt and the public key |
That third-to-last row is the honest one, worth sitting with rather than glossing over: the signature and the hash chain protect against tampering by anyone without the signing key. They don't, alone, protect against the operator itself rewriting history — that's what the independent archive is for, and it's the reason it exists rather than being a redundant extra layer.
pack_id: "24449424...e020" // SHA-256 decision: "ALLOW" reasons: [] executed: true // permitted — not proof the downstream action ran meta: { model_id: "client:acme-underwriting", sequence_id: "underwriting-9f3a1", step: "bias_audit", function: "bias_audit", action_type: "VALIDATE_INPUT", policy_map_ids: ["msmd_policy_v1"] } payload_hash: "9080bd2a...86cb" prev_receipt_hash: "b6a18d23...338d" // hash of the FULL prior receipt ts_ms: 1747043892114 key_id: "k2_2026-06-07_ed25519" signature_alg: "Ed25519" signature: "TpQr8f3aXz9c2b1d..." // base64, over canonical JSON of everything above
None of these fields are metadata that can change after signing — every one is inside the signed content. model_id, sequence_id, step, function, and action_type live inside meta, not at the top level.
System A serialises with a plain JSON.stringify(). System B sorts keys first. If the receipt's keys aren't already alphabetical, the two produce different byte sequences from the same object — different signatures — and System B incorrectly reports a genuine receipt as tampered. The problem compounds across languages: Python's json.dumps(), JavaScript's JSON.stringify(), Go's encoding/json all serialise the same object differently by default. A receipt signed in one and verified in another fails unless both use the same canonical form.
Keys sorted alphabetically at every nesting level, no whitespace, consistent number formatting, arrays in order. The same shape as RFC 8785 (JSON Canonicalization Scheme) — any conforming implementation produces identical bytes.
On 2026-07-11, a sealed receipt was deliberately overwritten in storage with a fabricated version, without the real signing key. The live compliance report caught it immediately — signature invalid, chain broken. The receipt was restored and the report returned clean. Two things that test showed, worth stating plainly rather than just asserting: the hash chain alone can't protect its own last link (nothing commits to a chain's final receipt but its own signature), and a corruption made with the real signing key would re-sign cleanly and only be caught by the separate archive comparison — not by the chain or the signature alone. That's the honest division of labour between the two mechanisms, confirmed by actually trying to break it, not just claimed.
None of EU AI Act Article 12, ISO 42001 A.6.1.6, or NIST Measure 2.4 use the word "cryptographic." All three require properties only cryptographic signing provides — and a cryptographic audit trail produces evidence toward each, without itself constituting full compliance with any of them.
Run a sequence, generate a receipt, and verify it yourself against the published keys — no account, no callback to AgenticRail.
Try the demo Verify a receipt