evidence verify
One command that answers the question your AI audit logs can't: who guarantees this log wasn't altered by the system that produced it?
$ evidence verify receipts.jsonl
entries checked : 5
chain verdict : CHAIN VALID
independence : SELF-ATTESTED — would not survive independence challenge.
Every hash in this chain was produced and stored by the same
system it describes; a compromised producer can re-sign the
entire chain. No external anchor found.
Why this exists
The EU AI Act's Article 12 requires automatic event logging for high-risk AI systems (Annex III obligations now apply from December 2, 2027 under the July 2026 Omnibus amendment; retention ≥6 months under Articles 19/26(6)). Most vendors answer with hash-chained logs.
A hash chain proves sequence. It does not prove independence. If the system that writes the log is compromised — or simply motivated — it can rewrite history and re-sign the entire chain. The log is a diary, not an affidavit. The enforcer cannot be the notary.
evidence verify checks three levels:
- CHAIN — is every entry linked to the previous one?
- TAMPER — does every entry's content still match its hash?
- INDEPENDENCE — does the chain reference an anchor outside the producing system? If not, it says so, plainly.
It works on any log that follows the receipt format below — including logs from other gateways and proxies. Point it at your current vendor's export and see what an auditor would see.
Install
Go 1.21+:
go build -o evidence .
./evidence verify sample_selfattested.jsonl
Python reference implementation (identical logic, used for cross-testing):
python3 evidence_verify.py sample_selfattested.jsonl
v0.3: pip install + LangChain
pip install evidence-verify # core, zero dependencies
pip install "evidence-verify[langchain]" # + the callback handler
Every LLM and tool call in a LangChain app becomes a chained receipt:
from evidence_verify import EvidenceCallbackHandler
handler = EvidenceCallbackHandler("receipts.jsonl")
llm.invoke(prompt, config={"callbacks": [handler]})
By default only SHA-256 fingerprints of prompts/responses are recorded —
the evidence file proves what happened without containing your data
(include_text=True opts into full text). Then:
evidence verify receipts.jsonl
evidence anchor receipts.jsonl > anchor.json # store this OUTSIDE
evidence verify receipts.jsonl --anchor anchor.json
Exit codes
0 = chain valid and anchored (round-trip verified when --anchor is given). 1 = self-attested, mismatch, or broken/tampered.
2 = usage or file error. CI can gate on 0.
Receipt format (v0.1 — spec RFC coming)
One JSON object per line (JSONL):
{
"seq": 1,
"ts": "2026-08-09T10:01:00Z",
"event": { "model": "...", "request_sha256": "...", "response_sha256": "...", "decision": "allow" },
"prev_hash": "<sha256 of previous entry; 64 zeros for genesis>",
"entry_hash": "<sha256 of seq|ts|canonical(event)|prev_hash>",
"anchor": { "type": "external", "provider": "...", "ref": "..." }
}
canonical(event) = JSON with sorted keys, no whitespace (RFC 8785-style).
The Go and Python implementations are byte-compatible.
v0.2: anchor round-trip
Anchor a chain (store the output OUTSIDE the producing system — a separate repo, a gist, S3, a timestamping service):
python evidence_verify.py anchor receipts.jsonl > anchor.json
Verify against the anchor (local file or https URL):
python evidence_verify.py verify receipts.jsonl --anchor anchor.json
python evidence_verify.py verify receipts.jsonl --anchor https://example.com/anchor.json
Round-trip verification catches what hash chains alone cannot: full history rewrites (re-signed chains) and truncation. The independence guarantee is exactly as strong as the independence of where the anchor lives.
Samples
sample_selfattested.jsonl— valid chain, no anchor (what most vendors ship)sample_anchored.jsonl— valid chain with external anchor referencessample_tampered.jsonl— one entry silently edited; the verifier catches it
Regenerate with python3 make_samples.py.
Roadmap
- Ed25519 signature verification (asymmetric, third-party verifiable)
- eIDAS-compatible qualified timestamp validation for anchors
- Adapters: Kong plugin export, LangChain callback, LiteLLM, OpenAI compliance-log export (which retains ~30 days by default — mind the 6-month retention gap)
License
Apache-2.0. Verification stays free forever. That's the point.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file evidence_verify-0.3.0.tar.gz.
File metadata
- Download URL: evidence_verify-0.3.0.tar.gz
- Upload date:
- Size: 12.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
37be8be954c4ef3db8733ffd0e6d9b5da17bc85bb92b5977a5f8701b56d85a97
|
|
| MD5 |
2819a9251ec48538c134702d022f1831
|
|
| BLAKE2b-256 |
8228d8db2c138e3aa13bdeab2e46faa2d8b9d9f0d2fd3f1643198d8cd36e4632
|
File details
Details for the file evidence_verify-0.3.0-py3-none-any.whl.
File metadata
- Download URL: evidence_verify-0.3.0-py3-none-any.whl
- Upload date:
- Size: 13.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d2f26d2ecfa0200e928743672fc7be480944c842964f0c3b6b72ec205283b02f
|
|
| MD5 |
a39e67a6a549e8497354ef8cf0411ce3
|
|
| BLAKE2b-256 |
6ce7c6c9741ee43d342632918f6cc5ab823ad0429eacadedaa8976d4e26e9483
|