How to Generate Cryptographic Proof of AI Agent Authorization for EU AI Act Article 14 Compliance
EU AI Act Article 14 enforcement starts August 2, 2026. If you're building AI agents that access sensitive data, process customer information, or make autonomous decisions -you need to demonstrate human oversight with verifiable artifacts.
Not logs. Not observability traces. Cryptographic proof.
In this post, I'll show you how we built Verigate -a cryptographic trust infrastructure for AI agents -and how you can use it to generate tamper-evident authorization receipts that any auditor can verify offline.
This content was created for the Build with Gemini XPRIZE.
The Problem
Every AI agent platform today -LangChain, CrewAI, Google ADK, Zapier AI -lets agents take actions. But none of them produce independently verifiable proof that the action was authorized according to policy.
When your agent:
- Reads customer PII from a database
- Sends an email on behalf of a user
- Processes a refund
- Modifies a CRM record
...what evidence exists that this action was authorized? A database log? That can be modified. An observability trace? That's vendor-dependent. A timestamp? That proves when, not whether.
Article 14 of the EU AI Act requires deployers to demonstrate five capabilities:
- Understand the AI system's capabilities and limitations
- Monitor the system's operation
- Interpret outputs correctly
- Override or stop the system
- Record what happened and prove it
That fifth requirement is where most teams fail. You need artifacts that are:
- Independently verifiable -without trusting the system that produced them
- Tamper-evident -modification is detectable
- Immutable -can't be retroactively altered
The Architecture: Ed25519 + SHA-256 + Merkle + Base L2
Here's how Verigate solves this:
Step 1: Every Agent Action Gets a Signed Receipt
When an agent requests authorization, the gateway evaluates policy rules (allowlist, resource scope, rate limit) and produces an Ed25519-signed receipt:
{
"body": {
"v": "1",
"seq": "42",
"ts": "2026-06-26T10:30:00Z",
"request_digest": "sha256:0e6d5b86f01f...",
"policy_version": "sha256:d59a1e4171e6...",
"decision": "approve",
"reasons": [],
"prev_receipt": "sha256:b3f51c8824bc..."
},
"sig": {
"alg": "EdDSA",
"kid": "gateway-prod-a1b2c3d4",
"value": "7WiFneT3tLRtE2Iztm..."
},
"receipt_hash": "sha256:2a3e65a3ade468..."
}
Key properties:
- Ed25519 (EdDSA) signature -no HS256, no symmetric keys, no shared secrets
- SHA-256 action digest -the intent (agent_id + action + resource) is canonicalized via RFC 8785 JCS before hashing
- Policy version hash -proves which policy was active when the decision was made
Step 2: Receipts Are Hash-Chained
Each receipt's prev_receipt field contains the SHA-256 hash of the previous receipt. This creates a tamper-evident chain:
Receipt #1 (genesis) → prev: sha256:0000...0000
Receipt #2 → prev: sha256(Receipt #1)
Receipt #3 → prev: sha256(Receipt #2)
...
Modify any receipt in the chain, and every subsequent prev_receipt hash becomes invalid. Insert or delete a receipt, and the sequence numbers break.
Step 3: Merkle Tree with Inclusion Proofs
Receipt hashes are organized into a Merkle tree using domain-separated hashing:
Leaf: SHA256("BI_RECEIPT_LEAF_V1" || 0x00 || receipt_hash)
Node: SHA256("BI_RECEIPT_NODE_V1" || 0x00 || left || right)
This lets you prove a specific receipt is included in a batch without downloading all receipts. The /v1/engine/merkle/proof endpoint returns the sibling hashes and directions.
Step 4: On-Chain Anchoring (Base L2)
For regulated industries, the Merkle root can be anchored on Base mainnet (chain ID 8453) as transaction calldata:
Anchor TX → burn address (0x000...000)
Value: 0
Calldata: 32-byte Merkle root
This creates an immutable timestamp proving the receipt chain existed at a specific block height. Verifiable on BaseScan by anyone, forever.
Zero LLM in the Authorization Path
Here's what makes this architecture unique: the authorization decision is fully deterministic. No AI model can influence whether an action is allowed or denied. The policy engine evaluates three rule types:
- Allowlist -is this action in the permitted set?
- Resource Scope -is this resource in the permitted scope?
- Rate Limit -has this agent exceeded its quota?
All three must pass. Any failure → deny.
Gemini (via Vertex AI) powers six AI agents that sit outside the authorization path:
- Auditor -analyzes receipt chains against OWASP and NIST frameworks
- Recommender -proposes policy changes from CONFLICT patterns
- Investigator -synthesizes incident reports
- Coordinator -manages A2A agent discovery
- Isolator -quarantines agents on HIGH/CRITICAL incidents
- Onboarding/Support/Marketing -business operations
The security boundary is explicit: AI advises, the gateway decides.
Try It: 5-Minute Integration
Install and authorize your first action:
from sdk import Verigate
# Provision a tenant (or use an existing API key)
vg = Verigate(api_key="as_...")
# Register your agent
vg.register_agent("my-bot", name="My Bot", capabilities=["read", "query"])
# Authorize an action
result = vg.authorize("my-bot", action="read", resource="/data/users")
print(f"Decision: {result.decision}")
print(f"Receipt: {result.receipt_hash}")
# Verify the chain
chain = vg.verify_chain()
print(f"Chain valid: {chain['valid']}")
Generate a compliance report:
report = vg.generate_compliance_report(
agent_name="my-bot",
agent_description="Reads customer profiles from staging database",
capabilities=["read", "query"],
data_types=["PII", "customer_records"],
frameworks=["EU AI Act", "HIPAA", "SOC 2"],
)
print(f"Findings: {len(report.findings)}")
# Download PDF: GET /v1/compliance/report/{report.report_id}/pdf
Or use the MCP server with Claude Desktop:
{
"mcpServers": {
"verigate": {
"command": "python",
"args": ["/path/to/mcp_server.py"],
"env": { "VERIGATE_API_KEY": "as_..." }
}
}
}
56 tools available -authorize, verify, register agents/resources/actions, generate compliance reports, chat with the multi-agent system.
Free Quick-Scan
Not ready to commit? Try the free compliance quick-scan -describe your agent and get 3 EU AI Act findings in 30 seconds. No signup required.
Full report with all 6 frameworks (EU AI Act, HIPAA, SOC 2, DORA, NIST AI RMF, OWASP LLM Top 10): $299 one-time.
Links
- Live product: verigate.cloud
- EU AI Act compliance page: verigate.cloud/eu-ai-act
- Free quick-scan: verigate.cloud/v1/compliance/quick-scan
- GitHub: github.com/4KInc/agentshield
- Security: verigate.cloud/security
Built with Google Gemini 2.5 (Vertex AI), Google ADK 2.1, Cloud Run, and Firestore.
Top comments (2)
This is basically notarization for agent actions, and like any notary stamp the trust ends up resting on the signing key rather than the paperwork around it. The hash chain and Merkle proofs are clean for catching tampering inside a chain, but what I keep circling back to is the key itself. The receipt carries a kid like gateway-prod-a1b2c3d4, so a year later, how does an auditor know that key was the legitimate gateway key at signing time and not one an operator swapped in afterward? Is there a separate key transparency record that the on-chain root ties back to, or is key trust assumed out of band?
Great question - you're pointing at the exact right trust anchor.
Short answer: The on-chain anchor solves this. When a Merkle root is anchored on Base L2, the KID that signed those receipts is implicitly committed to at that block height. If an operator swapped the key afterward and re-signed old receipts, the receipt hashes would change, the Merkle root would change, and it would no longer match the on-chain calldata. So the chain itself acts as a key transparency record.
Longer answer: You're right that without anchoring, trust ultimately rests on the operator not swapping keys. That's the same trust model as any CA or code-signing infrastructure. We handle this at
three levels:
What we don't have yet (and you're implicitly asking about): a dedicated key transparency log (like CT logs for TLS certs) where key rotations are publicly recorded. That's a natural next step - publish KID rotations as signed entries in the artifact log, anchor those alongside receipts. Good feature request.
The design philosophy is: the anchoring layer makes key trust verifiable rather than assumed. Without it, you're trusting the operator. With it, you're trusting math.