Watch di lesson video: Securing AI Agents with Cryptographic Receipts
(Lesson video and thumbnail go add by di Microsoft content team after merge, to match di lesson 14 / 15 pattern.)
Dis lesson go cover:
After you finish dis lesson, you go sabi how to:
Imagine say you don deploy AI agent for Contoso Travel. Di agent dey read customer request dem, call flights API to check options, and dey book seat for customer side. Last quarter, di agent process 50,000 bookings.
Today, one auditor come. Dem ask simple question: “Show me wetin your agent do.”
You give dem your log files. Auditor see dem and ask di harder question: “How I go know say dem no edit di logs?”
Dis na di audit-trail problem. Most agent deployment today dey rely on:
None of these fit answer auditor question without auditor needing to trust person (you, your cloud provider, or your database vendor). For inside use, that trust dey acceptable. For regulated workloads (finance, healthcare, or anything wey get EU AI Act), e no dey acceptable.
Cryptographic receipts solve dis by making every agent action fit verify by itself. Auditor no need trust you. Dem only need your public key and di receipt.
Receipt na JSON object wey record wetin agent do, sign with digital signature.
flowchart LR
A[Agent dey use tool] --> B[Build receipt payload]
B --> C[Make JSON correct like RFC 8785]
C --> D[SHA-256 hash]
D --> E[Ed25519 sign]
E --> F[Receipt wey get signature]
F --> G[Auditor dey check am offline]
G --> H{Signature correct?}
H -- yes --> I[Proof wey show if tamper happen]
H -- no --> J[Receipt no gree]
Minimal receipt look like dis:
{
"type": "agent.tool_call.v1",
"agent_id": "contoso-travel-bot",
"tool_name": "lookup_flights",
"tool_args_hash": "sha256:a3f9c1...",
"result_hash": "sha256:7b2e1d...",
"policy_id": "contoso-travel-policy-v3",
"timestamp": "2026-04-25T14:30:00Z",
"sequence": 47,
"previous_receipt_hash": "sha256:9d4e6a...",
"signature": {
"alg": "EdDSA",
"sig": "c5af83...",
"public_key": "8f3b2c..."
}
}
Three properties dey do di work:
Di signature. Di receipt sign by agent gateway using Ed25519 private key. Anybody wey get di public key fit verify di signature offline. If tamper with any field, di signature go invalid.
Canonical encoding. Before sign, receipt dey serialize using JSON Canonicalization Scheme (JCS, RFC 8785). Dis make sure say two different implement dey produce di same logical receipt and dem produce exact same bytes. Without canonicalization, different JSON serializers go give different signatures for di same content.
Hash chaining. Di previous_receipt_hash field link each receipt to di one wey come before am. If you remove or reorder receipt, e go break every receipt wey follow after am. Tampering go show for di chain even if individual signatures dem bypass.
Together, dis properties give three guarantees:
You no need special library to produce receipt. Cryptographic primitives dey widely available and di logic na just few dozen lines of Python.
Di hands-on exercises for code_samples/18-signed-receipts.ipynb go show di full flow. Di summary version:
import json
import hashlib
import base64
from nacl import signing
from jcs import canonicalize # RFC 8785 canonical JSON
def b64url_nopad(data: bytes) -> str:
return base64.urlsafe_b64encode(data).decode("ascii").rstrip("=")
def sha256_canonical(obj) -> str:
"""SHA-256 of a Python object's JCS-canonical JSON form."""
return f"sha256:{hashlib.sha256(canonicalize(obj)).hexdigest()}"
# Make or find una signing key (for production, keep am for key vault)
signing_key = signing.SigningKey.generate()
verify_key = signing_key.verify_key
# Build di receipt payload (no signature yet)
tool_args = {"origin": "SYD", "destination": "LAX"}
tool_result = [{"flight": "QF11", "price": 1850, "stops": 0}]
payload = {
"type": "agent.tool_call.v1",
"agent_id": "contoso-travel-bot",
"tool_name": "lookup_flights",
"tool_args_hash": sha256_canonical(tool_args),
"result_hash": sha256_canonical(tool_result),
"policy_id": "contoso-travel-policy-v3",
"timestamp": "2026-04-25T14:30:00Z",
"sequence": 0,
"previous_receipt_hash": None,
}
# Canonicalize, hash, sign.
canonical_bytes = canonicalize(payload)
message_hash = hashlib.sha256(canonical_bytes).digest()
signature_bytes = signing_key.sign(message_hash).signature
# Attach one structured signature object.
receipt = {
**payload,
"signature": {
"alg": "EdDSA",
"sig": b64url_nopad(signature_bytes),
"public_key": b64url_nopad(bytes(verify_key)),
},
}
Dis na di whole signing pipeline. Exercises inside di notebook go show each step.
Verification na di opposite operation:
import base64
import hashlib
from nacl import signing
from nacl.exceptions import BadSignatureError
from jcs import canonicalize
def b64url_decode(s: str) -> bytes:
padding = "=" * ((4 - len(s) % 4) % 4)
return base64.urlsafe_b64decode(s + padding)
def verify_receipt(receipt: dict) -> bool:
# Di signature na wan structured object: {"alg", "sig", "public_key"}.
sig_obj = receipt.get("signature")
if not sig_obj or sig_obj.get("alg") != "EdDSA":
return False
# Make di payload we dem actually sign again (everything except di signature).
payload = {k: v for k, v in receipt.items() if k != "signature"}
canonical_bytes = canonicalize(payload)
message_hash = hashlib.sha256(canonical_bytes).digest()
try:
verify_key = signing.VerifyKey(b64url_decode(sig_obj["public_key"]))
verify_key.verify(message_hash, b64url_decode(sig_obj["sig"]))
return True
except BadSignatureError:
return False
Dis function go take receipt and return True if signature valid, False if no. No network call, no service dependency, no trust needed for any third party.
To see tampering detection, di notebook go show:
tool_args_hash field.Dis na practical demo say receipts na tamper-evident: any small change go break di signature.
One signed receipt protect one action. Chain of receipts protect whole sequence.
flowchart LR
R0[Receipt 0<br/>origin] --> R1[Receipt 1]
R1 --> R2[Receipt 2]
R2 --> R3[Receipt 3]
R1 -. previous_receipt_hash .-> R0
R2 -. previous_receipt_hash .-> R1
R3 -. previous_receipt_hash .-> R2
Each receipt record di hash of previous receipt. To remove receipt 2 without noise, attacker need to either:
previous_receipt_hash field (go break receipt 3 signature)If private key dey hardware key vault and you publish public key wit each receipt, no attack fit happen without detection.
Di notebook go show:
previous_receipt_hash match actual hash of previous receipt.Na so you fit produce audit trail wey external auditor fit verify without trusting you.
Dis na di most important section for dis lesson. Receipts powerfull but dem get limit.
Receipts prove three tins:
Receipts no prove:
policy_id really evaluate or say e for allow dis action if check. Receipt only record wetin dem claim no wetin dem enforce.Dis limit matter for two reasons:
Common mistake na to think “we get receipts” mean “we dey governed.” E no true. Receipts na foundation. Governance na system wey you build on top.
Item 3 above deserve section on im own: action receipt talk “dis key sign dis content,” no “human authorize dis.” For high-risk actions (refunds, deletions, wire transfers), governance framework dey require dat missing statement, and e fit produce wit di same primitives wey you don learn for dis lesson.
Di next notebook code_samples/human-authorization-receipts.ipynb add second receipt type, human.approval.v1, for same envelope shape as lesson receipts (typed payload signed by Ed25519 over canonical SHA-256, with signature object outside signed bytes). Named approver sign full canonical action and im digest before execution; agent action receipt carry same action digest and parent_approval_ref, di receipt_hash of di approval, same convention as previous_receipt_hash for chain wey you build above. One verify_chain go check both artifacts under separate pinned key registries (approver keys vs agent keys), so code path shared but authorities no.
Di property wey dis buy, na this: human approve dis exact action and agent do exactly dat approved action. Notebook refusal fixtures na wetin make dis real no be just talk:
Each failure refuse with clear reason so auditor fit know if authority stale or if executed action change. Di rule for notebook na: signed approval no be authority alone. Authority dey only if both receipts still bind same canonical action at execution time. Co-signature path for same Internet-Draft wey dis lesson follow (draft-farley-acta-signed-receipts) na standards-track shape of dis pattern.
Python code for dis lesson minimal on purpose so you fit read every line and understand wetin dey happen. For production, you get two options:
Build direct on cryptographic primitives. Di 50 lines wey you see above enough for many use cases. PyNaCl (Ed25519) and jcs package (canonical JSON) na well-maintained and audited libraries.
Use production receipt library. Some open-source projects implement same pattern with extra features (key rotation, batch verification, JWK Set distribution, integration with policy engines):
draft-farley-acta-signed-receipts, revision 02) wey dey standards process, with shared conformance suite (agent-governance-testvectors) wey independent implementations use cross-verify for byte-identical canonical output.protect-mcp (npm) and @veritasacta/verify (npm) packages provide Node-based receipt signing and offline verification; dem fit wrap any MCP server with tamper-evident audit trail, include held-for-co-sign flow wey paused action emit approval receipt bound to action digest (WebAuthn-backed for desktop flow), same approval-receipt pattern as human-authorization notebook above.pip install nobulex) provide same Ed25519 + JCS signing pattern with LangChain and CrewAI, including published cross-validation test vectors and compliance mapping via OWASP PR #2210.Decision whether to build your own or use library similar to choice of building your own JWT library or use tested one: both okay; library save time and reduce audit risk; building from scratch force you understand primitives. Dis lesson teach from-scratch so you get solid foundation for either choice.
Test your understanding before you go practice exercise.
1. Receipt sign wit agent private Ed25519 key. Auditor get only public key. Auditor fit verify receipt offline?
2. Attacker modify policy_id field of receipt to claim say e govern by more permissive policy. Original signature cover original payload. Wetin happen for verification?
3. Why di receipt get tool_args_hash and result_hash instead of di raw arguments and result?
4. Di previous_receipt_hash field dey join each receipt to di one before am. If attacker quietly commot receipt for middle of di chain, wetin go no valid again?
5. Receipt verify correct. E mean say di agent action correct, true or follow policy?
Open code_samples/18-signed-receipts.ipynb and complete all four sections:
Stretch challenge 1: add one more field wey you pick to the receipt schema (example, request ID for tracing), update di way you sign to include am, then confirm say receipt still verify well. After dat, change di field after sign and check say verification fail. Dis one go make you sabi how every byte for di canonical encoding dey affect di signature.
Stretch challenge 2: SHA-256 hash two of your receipts together (join their correct bytes for one order) then put di digest as new field on third receipt before you sign am. Check say all three receipts still dey valid. You don build one-step inclusion proof: anyone wey get third receipt fit prove say first two dey when e sign am, but dem no need show all di content. Na di pattern wey selective-disclosure receipts dey use well well (Merkle commitments, RFC 6962).
Cryptographic receipts dey give AI agents audit trail wey be:
Dem no fit replace input validation, policy, or identity system. Dem be foundation for dem. When you dey use agents for regulated work or multi-org workflow or place weh auditor no fit trust you, receipts na how you keep audit trail honest.
Di most important tin: receipts prove who talk wetin and when. E no mean say wetin dem talk na true or correct. Remember dis because e be difference between honest provenance system and one wey go mislead.
When you ready to move from dis lesson to deploy receipt-signed agents for real environment:
https://your-org.example.com/.well-known/agent-keys.json.Join di Microsoft Foundry Discord to meet other learners, attend office hours, and get your AI Agents questions answer.
Dis lesson cover single-receipt signing and hash-chained sequences. Same process fit make many advanced patterns you go see as governance mature:
authorization_*) and post-execution (result_*) wit independent signatures, useful when authorization decision and result come from different actors or times. E build on top of lesson receipt format.result_hash. Real payloads dey richer than one tool call: reasoning before decision (model prediction, options, evidence and completeness, risk, accountability, gate outcome) fit dey inside payload seal by one receipt. E keep receipt small but payload fit grow by domain.signature.alg fit carry ML-DSA-65 (NIST post-quantum signature standard) when you wan move. Prepare for period wey receipts get dual sign.Disclaimer: Dis document don translate wit AI translation service Co-op Translator. Even tho we dey try make am correct, abeg make you know say automated translation fit get errors or mistakes. Di original document for dia own language na im be di correct source. For important info, make person wey sabi human translation do am. We no go responsible for any misunderstanding or wrong understanding wey fit happen because of dis translation.