R+2 specifies how an AI agent's actions are signed, hash-chained, and made independently verifiable — without trusting the issuing vendor. The standard is free, open under MIT, and designed for adoption by governments, enterprises, and any AI vendor that needs to make its agents auditable.
An AI agent performs an action. The action is wrapped in a receipt. The receipt is signed with ed25519, links to the agent's previous receipt by content hash, and is published either inline or to IPFS. Any consumer — a regulator, a downstream system, a citizen filing an RTI request — can independently verify three things:
That's it. The rest of this document specifies exactly how to produce, transport, and verify such a receipt — with no ambiguity, so that two independent implementations will interoperate.
The keywords MUST, MUST NOT, SHOULD, SHOULD NOT, MAY, and RECOMMENDED in this document are to be interpreted as described in RFC 2119 and RFC 8174.
| Term | Definition |
|---|---|
| Agent | An autonomous software process identified by a stable ed25519 public key, capable of producing R+2 receipts. |
| Issuer | The party operating an Agent. Often a company or a person. Not the same as the Agent itself. |
| Receipt | A signed JSON object conforming to the schema in §4, recording a single Action. |
| Action | An event performed by an Agent that the Issuer chooses to record. Any tool call, API call, computation, or business event qualifies. |
| Verifier | Any party — including regulators, consumers, or other agents — who independently checks a Receipt's validity. |
| CID | Content Identifier. A hash-derived identifier for a piece of data. R+2 uses SHA-256 hex by default; IPFS CIDs MAY be used when receipts are pinned. |
| Chain | The ordered sequence of Receipts from a single Agent, linked by prev_receipt_cid. |
A conforming Action Receipt is a JSON object with exactly the following top-level fields. Implementations MUST NOT add unknown top-level fields; extensions go inside the extensions object (§11).
| Field | Type | Description | Required |
|---|---|---|---|
| spec_version | string | The R+2 version this receipt conforms to. MUST be the string "r2/v0.1" for this revision. | MUST |
| agent_pubkey | string | The agent's ed25519 public key, base64url-encoded (RFC 4648 §5), no padding. Exactly 43 characters. | MUST |
| agent_id | string | A short human-readable identifier for the agent. SHOULD be unique within the Issuer's namespace. | MUST |
| action_id | string | A UUIDv4 (RFC 9562) identifying this specific Action. Globally unique. | MUST |
| action_type | string | A short, slash-namespaced action category (e.g. "memory/write", "payment/settle", "tool/call"). | MUST |
| action_data | object | The substantive payload of the action. Schema is application-defined. MUST be a JSON object. | MUST |
| occurred_at | string | RFC 3339 timestamp with timezone offset. Resolution to milliseconds RECOMMENDED. | MUST |
| prev_receipt_cid | string | null | The CID of the immediately preceding receipt from this agent. null for the first receipt only. | MUST |
| nonce | string | 16 bytes of random data, base64url-encoded, no padding. Prevents signature reuse across identical action bodies. | MUST |
| extensions | object | Implementation-defined fields. MUST be a JSON object. MAY be empty ({}). | MUST |
| signature | string | ed25519 signature of the canonical JSON of the receipt with signature field excluded. Base64url-encoded, no padding. Exactly 86 characters. | MUST |
{ "spec_version": "r2/v0.1", "agent_pubkey": "u4yK_lH8Z6vJ3qZ5tNwQpRz_aBcDeFgH1iJ2kL3mN4o", "agent_id": "0001", "action_id": "3f8a7c12-8b91-4e2c-9b3a-5f7d8e1a2c4b", "action_type": "memory/write", "action_data": { "memory_id": "mem_a83f12cd", "content_hash": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "embedding_model": "text-embedding-3-large", "embedding_dim": 1536 }, "occurred_at": "2026-05-19T15:42:08.123Z", "prev_receipt_cid": "sha256:bafy2bzaceabc...", "nonce": "k3J9p2qR7sT5vXyA", "extensions": {} }
action_data contents at this layer. The whole point of R+2 is to be a substrate that any sector can build on. Healthcare receipts will contain PHI references; finance receipts will contain settlement details; AI inference receipts will contain prompt hashes. Sectoral profiles MAY constrain action_data via JSON Schema linked from a registered extension namespace.
R+2 uses Ed25519 as specified in RFC 8032 §5.1. Implementations MUST use pure Ed25519 (not Ed25519ph or Ed25519ctx). Signature size: 64 bytes (86 chars base64url, no padding). Public key size: 32 bytes (43 chars base64url, no padding).
R+2 uses SHA-256 (FIPS 180-4) for receipt content hashes and the chain pointer. SHA-256 hashes appear as hex strings prefixed with "sha256:" in fields where they're stored as JSON values. When transported as IPFS CIDs, they use multibase-encoded CIDv1 with the SHA-256 multihash code.
All cryptographic byte strings in R+2 use base64url encoding as defined in RFC 4648 §5, without padding. Two-character base64 padding (==) MUST be omitted. The character set is [A-Za-z0-9_-].
libsodium (C / Rust / Python / Node), @noble/ed25519 (TypeScript), or the standard library's crypto/ed25519 package (Go 1.13+). Do not implement Ed25519 yourself.
Signing JSON is harder than it looks. Two JSON serializations of the same logical object may differ in whitespace, key order, number representation, or Unicode escaping — but produce different hashes, and therefore different signatures. To eliminate this ambiguity, R+2 mandates RFC 8785 — JSON Canonicalization Scheme (JCS).
Number.prototype.toString() algorithm.Implementations MUST use a library that follows RFC 8785 strictly. We RECOMMEND canonicalize (npm), jcs (Go), or the W3C reference TypeScript implementation. Do not roll your own.
signature.signature field as if it didn't exist.signature field.import { ed25519 } from '@noble/curves/ed25519'; import canonicalize from 'canonicalize'; import { Buffer } from 'buffer'; function signReceipt(receipt, privateKey) { const { signature, ...unsigned } = receipt; const canonical = canonicalize(unsigned); const sig = ed25519.sign(new TextEncoder().encode(canonical), privateKey); const sigB64 = Buffer.from(sig).toString('base64url'); return { ...unsigned, signature: sigB64 }; }
A Verifier receives a Receipt and the agent's expected public key from an identity layer (DNS, on-chain SBT, etc.). The Verifier MUST perform the following checks in order. Any failure means the Receipt is invalid and SHOULD be rejected.
spec_version equals "r2/v0.1" (or a version the Verifier supports per §11).agent_pubkey in the receipt equals the public key the Verifier obtained from the identity layer for this agent_id.signature from base64url; remove the signature field from the receipt; canonicalize the result per §6; verify the Ed25519 signature over the canonical bytes using the public key from step 3.prev_receipt_cid is not null, the Verifier SHOULD fetch the previous receipt by CID and verify it recursively. The Verifier MAY rely on a previously-verified receipt cached locally.occurred_at SHOULD be within a reasonable window. Outside ±24h from the verification time, the Verifier MAY flag the receipt for further inspection but MUST NOT reject solely on this basis.function verifyReceipt(receipt, expectedPubkey) { const { signature, ...unsigned } = receipt; if (unsigned.agent_pubkey !== expectedPubkey) return false; if (unsigned.spec_version !== 'r2/v0.1') return false; const canonical = canonicalize(unsigned); const sigBytes = Buffer.from(signature, 'base64url'); const pubBytes = Buffer.from(expectedPubkey, 'base64url'); const msg = new TextEncoder().encode(canonical); return ed25519.verify(sigBytes, msg, pubBytes); }
Each Receipt's prev_receipt_cid field MUST contain the SHA-256 hash (or IPFS CIDv1 with SHA-256 multihash) of the canonical JSON form of the agent's immediately preceding signed Receipt.
This produces a strict total order over an agent's Receipts and makes any tampering immediately detectable. Specifically:
prev_receipt_cid now points to a missing predecessor.The first Receipt from an agent MUST have prev_receipt_cid: null. A Verifier MAY choose to record the first receipt's CID separately as a tamper-evidence anchor.
R+2 receipts MAY be pinned to IPFS (and durably backed by Filecoin) to guarantee permanence beyond the issuer's infrastructure. Pinning is OPTIONAL; the standard does not require it.
When pinned:
prev_receipt_cid as "ipfs:<CID>" instead of "sha256:<hex>".The spec_version field uses the format "r<major>/v<minor.patch>". The current version is "r2/v0.1". Major versions (r2 → r3) MAY introduce breaking changes; minor versions MUST remain backward-compatible.
Implementations MAY add fields inside the extensions object. Extension fields MUST use a namespaced key of the form "<org>.<feature>" to avoid collisions. Examples:
"dcs.memory_receipt_v1" — DCS Labs Sovereign Memory metadata"meity.dpdp_consent_v1" — Indian DPDP §8 consent metadata"isro.tile_provenance_v1" — ISRO satellite imagery provenance fieldsVerifiers MUST ignore unrecognized extension fields. Verifiers that understand a specific extension MAY apply additional validation rules to it.
A sectoral profile is a public document specifying:
action_type values for that sector.extensions fields and their JSON Schema.Profiles are registered at https://dcslabs.ai/standard/profiles/<profile-name>. The first profiles in active drafting: r2-health-v1 (healthcare), r2-gov-v1 (Indian government RTI-compliance), and r2-finance-v1 (cross-border payment provenance).
A complete R+2 signed receipt for a memory-write action, followed by a verification you can run yourself.
{ "spec_version": "r2/v0.1", "agent_pubkey": "u4yK_lH8Z6vJ3qZ5tNwQpRz_aBcDeFgH1iJ2kL3mN4o", "agent_id": "0001", "action_id": "3f8a7c12-8b91-4e2c-9b3a-5f7d8e1a2c4b", "action_type": "memory/write", "action_data": { "memory_id": "mem_a83f12cd", "content_hash": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "embedding_model": "text-embedding-3-large", "embedding_dim": 1536 }, "occurred_at": "2026-05-19T15:42:08.123Z", "prev_receipt_cid": "sha256:bafy2bzaceabc1234567890abcdef1234567890abcdef1234567890abcdef00", "nonce": "k3J9p2qR7sT5vXyA", "extensions": {}, "signature": "sig_base64url_86chars_no_padding_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" }
# Fetch the receipt from the DCS reference implementation $ curl -s https://api.dcslabs.ai/api/receipts/r2_a83f12cd \ | jq '.receipt' \ > receipt.json # Get the expected public key from the on-chain identity layer (Base mainnet) $ EXPECTED_PK=$(cast call 0xbDd1f5fC349D9a8EfCEb07Edbd491233b2540f5F \ "agentPubkey(string)" "0001" --rpc-url https://mainnet.base.org) # Run R+2 verifier (npm install -g @trdnetwork/r2-verify) $ r2-verify --receipt receipt.json --pubkey $EXPECTED_PK ✓ Schema valid ✓ Spec version r2/v0.1 supported ✓ Pubkey matches Base mainnet record ✓ Signature valid ✓ Chain pointer matches predecessor (sha256:bafy2bzaceabc...) ✓ Timestamp within window Receipt verified.
If your organization wants to adopt R+2, here's the minimum work required:
agent_id, action_id, and occurred_at.GET /receipts/<action_id> returning the JSON.@trdnetwork/r2-verify) or implement your own — both are equally conformant.A reference implementation written in TypeScript ships with the DCS Labs @trdnetwork/mcp-server npm package. It produces and verifies R+2 receipts by default for every MCP tool call. You can study it as a working example.
The current editor is DCS AI Technologies L.L.C. Contact: [email protected].
R+2 v0.1 is a single-editor draft. The intended trajectory:
DCS Labs will not hold a veto over any future version. The spec is MIT-licenced from v0.1 onward and can be forked at any time.
| Version | Date | Changes |
|---|---|---|
| r2/v0.1 | 2026-05-19 | Initial public draft. Submitted to Anthropic Standards Program, MeitY, ISRO. |
credentialSubject if a deployment prefers VC's transport semantics.Comments, corrections, and contributions are warmly welcomed.
If you represent a government agency, regulator, or international body and want to evaluate R+2 for adoption: we'll arrange a 30-minute technical briefing and provide a sector-specific adoption guide. Email [email protected] with your context and we'll respond within 48 hours.