chain-anchor-v1 — receipt-format-agnostic public-chain anchor reference

Status: draft 1, 2026-05-12. Audience: anyone shipping an AI-agent receipt format (AAR, RFC 3161-wrapped, C2PA, consent-receipt, custom JSON) who wants a second, issuer-independent proof root. Goal: define a tiny canonical object any receipt format can embed to point at an immutable public-chain commitment of the same payload, so a verifier can fall back to the chain if the issuer's signing key, TSA cert, or hosted ledger ever becomes untrusted or unavailable.

The field is intentionally generic. Satsignal is the reference implementation; any anchor provider that writes a root hash into a public chain can fit the same shape.

1. Why this exists

Every receipt format in the agent-receipt space (Mar 2026 survey) bottoms out in one trust root: an issuer Ed25519 key, a TSA X.509 chain, a private L2's validator set, or a vendor's hosted verifier. Each is a single point of revocation, compromise, or shutdown. A chain_anchor field lets the same receipt carry a second, independent proof — anchored in a public chain that the issuer does not control — without forcing the receipt format to change anything else.

One field. No SDK dependency. Verifiable offline with a block explorer and stdlib hashing.

2. The object

Canonical JSON. Required fields first; verifiers MUST reject if any required field is missing or malformed.

{
  "v": 1,
  "system": "satsignal",
  "chain": "bsv-mainnet",
  "txid": "<64-hex>",
  "root_hash": "<64-hex, sha-256>",

  "category": "commitment",
  "height": 891245,
  "block_hash": "<64-hex>",
  "anchor_id": "anc_01HXYZ...",
  "workspace": "o_RuM4CKvx0q",
  "issued_at": "2026-05-12T18:00:00Z"
}

Required

FieldTypeMeaning
vintSpec version. 1 for this document.
systemstringAnchor provider identifier, lowercase. satsignal for Satsignal-issued anchors; other providers MAY register their own (see §7).
chainstringPublic chain identifier, lowercase, hyphenated. bsv-mainnet, bsv-testnet, btc-mainnet, eth-mainnet.
txidstringLowercase hex transaction id on chain.
root_hashstringLowercase hex SHA-256 of the merkle root committed in the transaction's anchor envelope. Verifiers MUST check this against the on-chain payload.

Optional

FieldTypeMeaning
categorystringWhat is being anchored, in the provider's taxonomy. For Satsignal: one of commitment, evidence_bundle, policy_snapshot, reveal. Other providers define their own values. Verifiers that don't recognize the value MUST treat the anchored payload as opaque and rely on the outer receipt for interpretation.
heightintBlock height. Convenience for cheap "is it buried yet?" checks.
block_hashstringLowercase hex block hash. Enables SPV-style verification without trusting an explorer.
anchor_idstringProvider-scoped opaque id, useful for fetching extended metadata via the provider's API (never required to verify).
workspacestringProvider-scoped issuer scope. For Satsignal, the workspace slug.
issued_atstringRFC 3339 UTC timestamp the anchor was issued. Informational; the chain's block time is authoritative.

Unknown fields MUST be ignored, not rejected, so providers can extend the object without breaking existing verifiers.

3. String form (compact)

For HTTP headers, query strings, log lines, or any place a single token is more practical than a JSON object:

chain-anchor:1:satsignal:bsv-mainnet:<txid>:<root_hash>

Colon-separated, lowercase, no whitespace. Field order is fixed: chain-anchor, v, system, chain, txid, root_hash. Optional fields are not representable in the string form — use the JSON object when you need them. A verifier MUST accept either form and produce the same result.

4. Where to embed it

The spec defines the shape, not the placement. Recommended slots:

When a receipt carries more than one anchor (e.g. a batched evidence bundle anchored both in Satsignal and a partner chain), use chain_anchors: [ ... ] instead. Verifiers MUST accept both shapes.

4.1 Batched anchoring

Many useful anchoring patterns commit multiple receipts under a single chain transaction — e.g. an L2 checkpointing its hourly block roots into one BSV transaction, an AAR issuer batching receipts to amortize anchor cost, a TSA-style service committing a batch of client tokens. In all of these, the on-chain root_hash is a merkle root over N leaves, and each leaf is the content hash of one participant.

A chain_anchor carried by an individual receipt in such a batch SHOULD include two additional optional fields:

FieldTypeMeaning
leaf_hashstringLowercase hex SHA-256 of the canonical content of this receipt — the leaf this receipt occupies in the merkle tree.
inclusion_proofarrayOrdered path from the leaf to the on-chain root, per SPEC_merkle_row.md: each entry is `{"sib": "<64-hex>", "side": "L""R"}`. Leaf-level entries first, root-level entries last.

Verification adds two steps to §5 between the existing steps 3 and 5:

When leaf_hash and inclusion_proof are both absent, the receipt is the entire anchored payload (the single-receipt case in §5). When both are present, the receipt is one leaf in a batch.

Two worked examples are hosted at /samples/chain-anchor/:

Both samples include stdlib-only reproduction recipes; the existing /verify page resolves the txid, extracts the on-chain root, and walks the proof end-to-end.

4.2 Dual-attest with RFC 3161

Many regulated workflows require — or strongly favor — an RFC 3161 timestamp from a Trusted Timestamping Authority. eIDAS Article 41(2) names this directly in the EU; several U.S. state evidence statutes and federal compliance regimes treat it as a recognized form of trusted timestamping. A chain_anchor does NOT replace a TSA token in those workflows — it sits alongside it.

The dual-attest pattern: the receipt envelope carries both

The two attestations have independent trust roots and independent failure modes:

PathTrust rootFailure modes
TSA tokenTSA's X.509 certificate chain + TSA's continued operationCert chain expires; CA revoked; TSA service shut down or unreachable; cert pinning policy diverges
chain_anchorPublic chain itself (block headers + PoW/consensus)Reorg below confirmation policy; chain itself disputed (vanishingly unlikely for established chains)

A verifier MAY accept either attestation as sufficient. Receipts are typically verified through both paths in parallel; long-tail verifiability (years after issue, when TSA infrastructure has rotated keys multiple times) leans on the chain path. Short-tail compliance check ("did a trusted timestamping authority sign this when it was issued?") leans on the TSA path.

Embedding shape. For receipt formats with a typed evidence array (such as AAR's evidenceRef[]), each attestation is its own entry — rfc3161/v1 for the TSA token and chain-anchor/v1 for the chain anchor. The rfc3161/v1 entry carries tsaToken (base64-encoded TimeStampResponse, ASN.1 DER per RFC 3161 §3.3), tsa (TSA identifier), and messageImprintHex (the canonical hash the token was issued over). For receipt formats without a typed array, the spec recommends sibling fields tsa_token + chain_anchor on the receipt envelope.

Order-of-operations subtlety. The TSA token is issued over the canonical receipt at a specific point in time; canonicalization for the TSA's message imprint MUST therefore EXCLUDE the eventual rfc3161/v1 entry (and any signature byte field — for AAR, that's signature.sig per §5.1 of the AAR spec). A verifier reconstructs the canonical bytes by stripping those fields, hashing, and comparing against the token's messageImprint.

Verifier rules.

A worked example — the same AAR receipt as §4.1, now with both a real RFC 3161 token from freetsa.org and the real BSV anchor — is hosted at /samples/chain-anchor/aar-receipt-2-dualattest.json. RECEIPTS.md includes an openssl ts -verify recipe for the TSA path. Both paths verify independently.

5. How to verify (stdlib-only)

Given a receipt and its embedded chain_anchor:

  1. Fetch the transaction at txid from any node, explorer, or local copy of the chain. No provider API call required.
  2. Extract the anchor envelope from the transaction's data output (per the provider's anchor format — for Satsignal, the documented OP_RETURN layout in SPEC_mbnt.md containing the version, subtype, and root hash).
  3. Check root_hash matches the value extracted from the envelope. Mismatch → reject.
  4. Check confirmations meet the verifier's policy. Below policy → treat as pending, not verified.
  5. Verify receipt content against root_hash using whatever inclusion proof the receipt format carries (merkle path, single-leaf hash, full-payload re-hash). Satisfied → the receipt is anchored.

No SDK, no account, no network call to the issuer. The verifier needs: a way to read a transaction, SHA-256, and the receipt's own inclusion proof scheme.

What anchoring proves

A successful verification establishes that the issuer of the receipt knew root_hash at or before the block time of txid. It does not prove the underlying content existed before that time (see the Satsignal trust page for the general framing). Adopters SHOULD NOT market chain_anchor as "proof of existence."

6. Worked example — AAR receipt with chain_anchor

{
  "v": "aar-1.0",
  "issuer": "did:web:botindex.io",
  "agent": "did:agent:abc",
  "action": { "type": "purchase", "amount_cents": 4200, "merchant": "acme" },
  "timestamp": "2026-05-12T17:59:58Z",
  "nonce": "0f3c...",
  "chain_anchor": {
    "v": 1,
    "system": "satsignal",
    "chain": "bsv-mainnet",
    "txid": "a3f1...e29c",
    "root_hash": "7b91...44d2",
    "category": "commitment",
    "height": 891245,
    "anchor_id": "anc_01HXYZABCDEF"
  },
  "signature": "ed25519:..."
}

The Ed25519 signature covers the entire object (JCS-canonicalized), including chain_anchor. A verifier with a stale or revoked issuer key can still establish that the receipt's root_hash was committed on BSV at height 891245.

7. Versioning and provider registry

Provider registry. The only currently recognized system value is satsignal. Additional providers are registered by pull request against this document. A registry entry is one line: the lowercase identifier and a link to the provider's anchor-envelope spec (equivalent of Satsignal's SPEC_mbnt.md), so that step 2 of §5 is unambiguous for verifiers.

8. Reference implementation

(Reference implementations to land in subsequent commits; this document is the wire-level contract they target.)

Source: docs/notary_spec/chain-anchor-v1.md. Email hello@satsignal.cloud for clarifications.