Verify R+2 receipts independently — the compliance-audit workflow
By the end of this tutorial you'll have run the full §8 verification flow on real R+2 receipts, walked a chain back to its genesis, deliberately tampered with a receipt to see verification fail, and understood exactly what trust assumptions you're (and aren't) making.
npx.
Step 01 Install the verifier
Two options:
# Option A: Run with npx (no install — recommended for one-off audits) npx @trdnetwork/r2-verify --version # Option B: Global install (best for repeated use) npm install -g @trdnetwork/r2-verify r2-verify --version
You should see r2-verify v0.1.1 (R+2 spec r2/v0.1).
Step 02 Fetch a receipt to verify
R+2 receipts are public — no authentication needed. Fetch one:
curl https://api.dcslabs.ai/v1/receipts/r2_a83f12cd > receipt.json cat receipt.json
You'll see the full signed receipt JSON: spec_version, agent_pubkey, agent_id, action_id, action_type, action_data, occurred_at, prev_receipt_cid, nonce, extensions, signature. Eleven fields per §4 of the spec.
Step 03 Get the expected public key
To verify a signature you need the expected public key — fetched from the identity layer (NOT from the receipt itself, which is what makes verification trustless):
curl https://api.dcslabs.ai/v1/agents/0348 | jq .pubkey # OR — for fully trustless: read directly from the on-chain SBT contract cast call 0xbDd1f5fC349D9a8EfCEb07Edbd491233b2540f5F \ "agentPubkey(string)" "0348" \ --rpc-url https://mainnet.base.org
Save the public key:
export AGENT_PUBKEY="u4yK_lH8Z6vJ3qZ5tNwQpRz_aBcDeFgH1iJ2kL3mN4o"
Step 04 Run verification
npx @trdnetwork/r2-verify --receipt receipt.json --pubkey $AGENT_PUBKEY
Expected output:
✓ Schema
✓ Spec version r2/v0.1
✓ Pubkey match u4yK_lH8Z6vJ...
✓ Signature
✓ Chain pointer first receipt (null)
✓ Timestamp within ±24h window
Receipt verified.
What just happened (per §8 of the spec):
- Schema check. All 11 required fields present, correct types, correct lengths.
- Spec version. Receipt claims
r2/v0.1— supported. - Pubkey match. Receipt's
agent_pubkeyfield equals the pubkey from the identity layer. - Signature. The signature was removed from the receipt, the remainder canonicalized per RFC 8785, and Ed25519 verified against the pubkey.
- Chain pointer.
prev_receipt_cidis null (this is the genesis receipt). If non-null, we'd verify it matches the actual previous receipt's CID. - Timestamp. Within ±24h of verification time. Warning only, not failure.
Step 05 Walk the chain
Now verify the chain pointer by fetching the previous receipt:
curl https://api.dcslabs.ai/v1/receipts/r2_b91d34ef > receipt-prev.json curl https://api.dcslabs.ai/v1/receipts/r2_a83f12cd > receipt-curr.json npx @trdnetwork/r2-verify \ --receipt receipt-curr.json \ --previous receipt-prev.json \ --pubkey $AGENT_PUBKEY
Now the Chain pointer check actively validates: it computes the SHA-256 hash of the previous receipt's canonical form and confirms it matches the current receipt's prev_receipt_cid. Any tampering anywhere in the chain breaks this check.
Step 06 Deliberately break a receipt to see failure
Make a copy and tamper with it:
cp receipt.json receipt-tampered.json # Edit receipt-tampered.json — change the action_data content to something fake # e.g., add a property "additional_info": "I AM FAKE" npx @trdnetwork/r2-verify --receipt receipt-tampered.json --pubkey $AGENT_PUBKEY
Expected output:
✓ Schema ✓ Spec version r2/v0.1 ✓ Pubkey match ✗ Signature: Ed25519 verification failed (signature does not match canonical bytes) Receipt invalid: Signature: Ed25519 verification failed
Even a single-character change in the action_data invalidates the signature. This is what makes the system trustworthy: any modification is immediately detectable, and the agent can't claim anything they didn't sign.
Step 07 Verify a full chain end-to-end
For a regulator audit, you want to verify every receipt in the agent's chain. Walk the chain backwards from head to genesis:
#!/bin/bash # walk-chain.sh — verify every receipt in an agent's chain AGENT_ID="0348" PUBKEY=$(curl -s https://api.dcslabs.ai/v1/agents/$AGENT_ID | jq -r .pubkey) # Get the full chain (paginated for large agents) curl "https://api.dcslabs.ai/v1/receipts/chain?agent_id=$AGENT_ID&limit=1000" \ -H "Authorization: Bearer $DCS_API_KEY" > chain.json # Verify each receipt + chain pointer jq -c '.receipts[]' chain.json | while read receipt; do echo "$receipt" | npx @trdnetwork/r2-verify --pubkey $PUBKEY || break done echo "Chain verified end-to-end."
If any receipt fails verification, the script exits and you see which one broke. Otherwise: full chain integrity proven.
Step 08 Audit a receipt without an internet connection (air-gapped)
Sovereign deployments often run in air-gapped environments. r2-verify works offline as long as you have the receipt JSON and the public key:
# On the air-gapped machine — assume r2-verify pre-installed # Read receipt from a USB stick or printout r2-verify --receipt /mnt/usb/receipt.json --pubkey $PRINTED_PUBKEY
No network call. No DCS dependency. Pure local cryptography. This is the deployment story for defence, classified data, regulators with strict data-residency requirements.
What you're trusting (and what you aren't)
The R+2 design minimizes the trust surface. After running this tutorial, here's exactly what you trust to verify a receipt:
- ✅ Ed25519 cryptography — published RFC 8032, multiple independent implementations
- ✅ RFC 8785 canonical JSON — published W3C spec, multiple implementations
- ✅ SHA-256 — FIPS 180-4, universal
- ✅ The identity layer's pubkey — your choice: trust the DCS API, or trust only the on-chain SBT contract (trust-minimized)
What you do NOT have to trust:
- ❌ DCS Labs as an organization (we could shut down tomorrow; receipts remain verifiable)
- ❌ The DCS API server (you can verify offline)
- ❌ The reference verifier (alternative implementations work identically by spec)
- ❌ Any database we run (the cryptographic chain is the source of truth)
This is what "trust-minimized" means in practice. Compare to typical SaaS: you trust the vendor for everything, end of story.
Programmatic verification (library use)
If you're embedding verification into your own application, use the library directly:
import { verifyReceipt, computeReceiptCid } from '@trdnetwork/r2-verify'; const result = await verifyReceipt(receipt, expectedPubkey, { previousReceipt: prevReceipt, skipTimestampCheck: false }); if (result.ok) { console.log("Receipt verified, safe to trust"); } else { console.error("Receipt rejected:", result.error); }
What's next
- Tutorial 06 — Produce a regulator-ready audit export — package verified receipts into the R+3 audit format for compliance review.
- Read R+2 §8 in the spec — canonical verification flow with implementation notes.
- Browse the verifier source code — github.com/DCS-LabsAI/r2-verify, MIT-licenced.