Skip to content

Latest commit

 

History

History
277 lines (216 loc) · 20.1 KB

File metadata and controls

277 lines (216 loc) · 20.1 KB

ANS-4: Transparency & Receipts

Status: DRAFT v1.0 Spec: ANS-4 (Transparency & Receipts) Version: 0.2.0 Date: 2026-07-18 Audience: implementers building or operating an ANS Transparency Log (TL), and AHPs and verifiers consuming SCITT receipts

1. Scope

ANS-4 specifies how registration and identity events flow into an append-only cryptographic log that produces verifiable receipts. ANS-4 is part of the ANS notary layer, which records what can be cryptographically verified without making judgments about trust. Trust scoring is out of scope for ANS and lives in the separate Trust Index specification. There is one Merkle tree; agent and Verified-Identity events share it, read through per-object indexes (§5.3).

ANS-4 specifies:

  • The inner-event JCS canonicalization, the sealed envelope, and the leaf-hash rule.
  • The TransparencyLog contract: append with producer-signature verification, per-object reads, checkpointing.
  • The TL's public verification API surface, including the C2SP checkpoint and tile endpoints.
  • Producer authentication (RA → TL).
  • Receipt distribution: live retrieval.
  • Optional checkpoint anchoring to the Hedera Consensus Service per HCS-14 and HCS-27 (§7).

ANS-4 is deployment-optional. Single-tenant deployments with no cross-organizational trust MAY skip it.

ANS-4 does not specify:

  • The wire format an RA submits over (HTTPS POST is recommended; the spec is transport-agnostic on this point).
  • How verifiers score the freshness of a receipt. Receipt-staleness scoring is out of scope here.

2. Terminology

  • SCITT statement: the unit the TL accepts and seals — the JCS-canonical inner-event payload plus a producer signature. Aligned to draft-ietf-scitt-architecture.
  • Sealed envelope: the record the TL appends — {payload: {logId, producer: {event, keyId, signature}}, schemaVersion, signature, status}, where the outer signature is the TL's own attestation. The leaf hash covers the fully signed envelope (§3).
  • SCITT receipt: the proof of inclusion the TL returns — a binary COSE_Sign1 carrying the inclusion proof (§5.2).
  • Producer key: the signing key an RA instance uses to sign events submitted to the TL. Distinct from the TL's own signing key (which signs checkpoints, receipts, attestations, and status tokens).
  • Checkpoint: a C2SP signed note recording the log's size and root hash at a point in time. Produced as batches seal; consumed by consistency-proof verifiers.

3. Cryptographic standards

Requirement Standard Rule
Canonicalization JCS (RFC 8785) All JSON payloads MUST be canonicalized before signing or hashing. Without deterministic serialization, two implementations hashing the same logical object produce different results
Signature format JWS Detached (RFC 7515 Appendix F) Payload is not embedded. Signatures are stored in separate fields from the data they sign
Co-located signatures JWS Detached When a signature resides in the same JSON object as its data, the signature fields MUST be excluded from the signed payload. The exclusion scope MUST be explicit
Signature wire format JWS Compact <protected_header>..<signature> (two dots; empty middle section is the detached payload)
Algorithms ES256 (ECDSA P-256/SHA-256) Every TL-produced signature — checkpoint, attestation, receipt, status token — and every producer key is ES256. The JWS layer additionally recognizes RS256 for interoperability (verified if presented, never emitted) and EdDSA on the ANS-0 identity-proof surface
Leaf hash RFC 6962 §2.1 SHA-256(0x00 ‖ JCS(envelope)) over the fully signed envelope — including the TL's outer attestation signature. This is the leaf Tessera appends and the hash an offline verifier recomputes when walking an inclusion proof

JWS protected headers: alg and kid are REQUIRED on every signature; typ, timestamp (unix seconds at signing), and raid (RA instance identifier, producer signatures only) are OPTIONAL.

TL signing-key topology. One ECDSA P-256 key drives every outbound TL signature: the C2SP checkpoint's primary signature line, the JWS additional-signer line on the same checkpoint, the outer envelope attestation on every appended event, SCITT COSE receipts, and status tokens. GET /root-keys advertises exactly one verification line (§5.1); verifiers map the 4-byte kid in a receipt's protected header to that key in O(1). The single-key topology is intentional — multiple signing keys would force verifiers to maintain a key-rotation strategy that adds no security but doubles the implementation burden.

4. The TransparencyLog interface

The TransparencyLog contract is the protocol surface for ANS-4: append, per-object reads, and checkpointing.

append is the write operation. The RA POSTs the raw inner-event body with its producer signature in the X-Signature header (a detached JWS; request bodies are capped at 256 KiB). The TL validates the signature against the producer-key registry (§6), wraps the event in a sealed envelope, sequences it into the next available log position — batching with other events arriving in the same window — and signs a checkpoint covering the new tree root as each batch seals. The append response is 200 with the assigned logId; the TL dedupes by content hash, so a retried submission of the exact same signed bytes lands on the same leaf rather than a duplicate. Receipts are retrieved afterward from the per-object receipt endpoints.

Per-object reads serve one agent's or identity's sealed state: the badge (latest sealed event plus inclusion proof and computed status), the audit history (every event on the object's stream, each with its own proof), the COSE receipt, and — for agents — a status token (§5.2).

Bulk reads are the checkpoint endpoints plus the static C2SP tile surface (§5.2): consumers that want the whole log walk the tiles; there is no separate paginated range API.

5. Public verification API

A conforming TL MUST expose the following REST API surface (HTTPS; JSON unless noted):

Endpoint Purpose
GET /v1/agents/{agentId} Badge: sealed event, TL attestation, inclusion proof, and computed status for one agent
GET /v1/agents/{agentId}/audit Paginated history of the agent's events, each with its own proof
GET /v1/agents/{agentId}/receipt SCITT COSE receipt for the agent's leaf (binary COSE)
GET /v1/agents/{agentId}/status-token Short-lived signed status token (application/ans-status-token+cbor, §5.2)
GET /v1/log/checkpoint Latest signed checkpoint (JSON view)
GET /v1/log/checkpoint/history Checkpoint history with pagination, for consistency-proof verification
GET /v1/log/schema/{version} JSON Schema definition for a given event schema version
GET /root-keys The TL verification line (§5.1, text/plain)
GET /checkpoint The raw C2SP signed checkpoint note (static tile surface)
GET /tile/* C2SP tlog-tiles: Merkle tree tiles and entry bundles for bulk verification

Read-only endpoints require no authentication. Verification MUST NOT require access to producer public keys or knowledge of RA implementation details: every proof verifies against the TL's own published key.

5.1 Key distribution

The TL distributes its verification key via GET /root-keys as a single sumdb-note verifier line (text/plain), so any verifier can check checkpoint, receipt, attestation, and status-token signatures without contacting the RA. The single line reflects the single-key topology (§3); the 4-byte key hash embedded in the line is the same value receipts carry as the COSE kid.

5.2 Receipts, status tokens, checkpoints

COSE receipt profile. A receipt is a COSE_Sign1 (RFC 9052): protected header {alg: ES256, kid: <4-byte SPKI key hash>, vds: 1 (RFC 9162 SHA-256), CWT claims: {iss, iat}}; unprotected header {396 (verifiable-data-structure proofs): {-1: treeSize, -2: leafIndex, -3: inclusion path, -4: rootHash}}. The signature covers the leaf; a verifier recomputes the leaf hash from the envelope (§3) and walks the path to the signed root.

Status tokens. GET /v1/agents/{agentId}/status-token returns a short-lived signed assertion of the agent's computed status (application/ans-status-token+cbor; default TTL one hour), signed by the same TL key. Verifiers that need a fresh liveness signal without walking proofs consume this; verifiers MUST check the token's expiry themselves.

Checkpoints and tiles. The raw checkpoint at GET /checkpoint is a C2SP signed note. Its primary signature line carries <4-byte keyhash> ‖ <ASN.1 DER ECDSA signature over SHA-256(note body)>, base64-encoded per the note format; a standard-JWS additional-signer line is appended to the same note by the same key. The GET /tile/* surface serves the Merkle tree in C2SP tlog-tiles form for bulk and offline verification.

5.3 Identity surface

This surface is present only when the RA implements the optional Verified Identity capability (ANS-0 §12.2). A TL serving an RA without it exposes none of the /v1/identities/* routes and seals no IDENTITY_* events, and is fully conformant.

There is one transparency log — a single Merkle tree. Every event, agent or identity, is producer-signed and appended to that one tree in log order. A "stream" is a read index over that single log (by agentId or identityId), never a separate tree. The Verified-Identity object (ANS-0 §4) is sealed and read through this surface; ANS-0 defines the object and the visibility semantics, ANS-4 the routes.

The identity event family is IDENTITY_VERIFIED, IDENTITY_UPDATED, IDENTITY_REVOKED, IDENTITY_LINKED, IDENTITY_UNLINKED (ANS-0 §8.1). The sealed-payload field set and per-type required-field matrix are documented there; the machine-readable conformance schema is api/identity-event-schema-v2.json, which a sealed identity log entry MUST validate against. Proof, rotation, and revocation events are indexed under identityId; link/unlink events are additionally indexed under each named ansId for the read-time join. The TL public read surface gains:

Endpoint Purpose
GET /v1/identities/{identityId} Latest sealed event + proof + computed {status, keys[], keysLogId} for one identity
GET /v1/identities/{identityId}/audit Paginated history of the identity's event chain, each with its own proof
GET /v1/identities/{identityId}/receipt SCITT COSE receipt for the identity leaf
GET /v1/identities/{identityId}/agents Reverse join — currently-linked agents (paginated)
GET /v1/agents/{agentId}/identities The computed who-identities list for one agent (paginated)
GET /v1/agents/{agentId}/identities/history Link/unlink events naming this agent (audit envelope)

The agent badge (GET /v1/agents/{agentId}) additionally carries a computed identities[] array joining the agent's live who-identities, governed by the visibility predicate and read-side revocation terminality of ANS-0 §8.2–§8.3: a link is visible when the link's latest event is LINKED and the agent is live; a REVOKED identity stays listed with keys[] omitted; when the join cannot be computed the array is omitted and identitiesUnavailable: true is set. These reads require no authentication (the verifier's offline-evidence hop); only the ingest lane (§6) requires the producer credential.

6. Producer authentication

The TL MUST verify that each event came from an authorized RA instance before sealing it. Every submitted event carries a detached-JWS producer signature in the X-Signature header; the TL resolves the (raid, kid) pair from the signature's protected header against its producer-key registry and rejects an unknown, expired, or revoked key.

Producer-key registration:

  1. The operator generates the producer key pair under the RA's key manager; the private key never leaves the RA instance.
  2. The operator registers the public key with the TL (POST /internal/v1/producer-keys, admin-gated), supplying the client-chosen key_id, the ra_id, public_key_pem (SPKI), algorithm (ES256), and the required valid_from and expires_at window (valid_from must precede expires_at). A duplicate key_id is rejected (409). Bootstrap seeding via configuration is equivalent.
  3. The TL stores the key under (ra_id, key_id) and returns the key_id, status, and a fingerprint of the registered key.
  4. The RA carries the kid and raid in the protected header of every statement signature.

Rotation uses an overlap window: new keys are registered with future valid_from dates; both old and new remain active during the transition. Expiry and revocation both gate new ingest — an expired or revoked key can no longer sign events the TL will accept. Already-sealed events are unaffected: the log is append-only, and verifiers check the TL's own attestations, receipts, and checkpoints against /root-keys, never producer keys.

Agent and identity events ride the same producer lane (the same producer-key registry and signature check) through separate ingest routes: POST /v1/internal/agents/event and POST /v2/internal/agents/event for the agent event families and POST /v1/internal/identities/event for the identity event family. The TL rejects a cross-lane body — an IDENTITY_* payload on an agent route, or an AGENT_* payload on the identity route — with 422 INVALID_EVENT, so the two object types never interleave at ingest.

7. Checkpoint anchoring to Hedera (HCS)

The TL's checkpoints MAY additionally be published to the Hedera Consensus Service, anchoring the log's state roots in a public, append-only consensus history that neither the TL operator nor any single network node can rewrite. Two finalized Hashgraph Online standards define the shape:

  • HCS-27 (Merkle Tree Profile and Proofs) defines the checkpoint commitment: the TL publishes periodic Merkle root checkpoints (root hash and tree size) to a designated HCS topic as typed consensus messages. The HCS-27 "Merkle v1" tree profile is the RFC 9162 §2 Merkle Tree Hash — SHA-256 with the 0x00 leaf prefix and 0x01 node prefix — which is exactly the tree this TL already builds (§3), so an on-ledger root verifies directly against the log's tiles with no profile translation.
  • HCS-14 (Universal Agent ID) defines the portable agent identifier (uaid: scheme) used when referencing ANS-registered agents across networks; its ANS resolution profile maps UAIDs onto the DNS- and web-published ANS surface.

Only cryptographic commitments go on-ledger: log entries, metadata, and proof bundles remain off-ledger with the TL. A verifier checks an anchored checkpoint by fetching the topic message from at least two independent HCS mirror nodes, comparing it against the TL's published checkpoint byte-for-byte, and validating the TL signature via /root-keys (§5.1). The operator publishes the HCS topic ID alongside its TL origin documentation; pinning the topic ID to the TL's identity is the defense against topic substitution.

Status. The HCS-14 and HCS-27 standards are finalized. The open-source reference implementation does not yet ship the HCS publishing adapter; it slots in at the checkpoint boundary without changing the wire contract of any surface above.

8. Conformance

A conformant ANS-4 implementation:

  1. Accepts statements only with verified producer signatures (§6) and seals each into a leaf per the leaf-hash rule (§3).
  2. Dedupes appends by content hash, so byte-identical retries land on the same leaf.
  3. Returns SCITT receipts matching the COSE profile in §5.2.
  4. Exposes the verification API (§5) over HTTPS with no authentication on read-only endpoints.
  5. Implements producer authentication with overlap-window key rotation (§6).
  6. Distributes its verification key via /root-keys (§5.1).
  7. Honors JCS canonicalization, JWS Detached signatures, and the protected-header rules of §3.

Operator policy (rotation cadence, checkpoint batching window, status-token TTL, HCS anchoring cadence) is deployment choice; a conforming verifier MUST NOT reject a receipt because an operator's cadence differs from another deployment's.

9. Security considerations

9.1 RA / TL collusion

When the RA and TL are operated by the same entity, that entity could in principle forge events with no independent check, because the keys that sign producer signatures and TL checkpoints are under the same operator's control. The public checkpoint and tile surface is the mitigation the log itself provides: checkpoints are signed, history is retained, and any third party can fetch tiles and verify consistency between checkpoints — a rewritten log cannot produce consistent proofs against previously published checkpoints. Anchoring checkpoints to the Hedera Consensus Service (§7) strengthens this further: the on-ledger consensus history is outside the operator's control entirely. A federated deployment runs the RA and TL under different operators, eliminating collusion as a single-entity risk.

9.2 Producer-key compromise

A compromised producer key allows an attacker to submit forged events to the TL. The TL accepts them because the signature validates. The overlap-window rotation rule (§6) lets the operator revoke the compromised key without disturbing the log: revocation stops new ingest under that key, previously-sealed events remain verifiable through the TL's own signatures, and the sealed history bounds what the attacker could have injected to the compromise window.

9.3 Sealed event types

The TL records the agent event family — AGENT_REGISTERED, AGENT_RENEWED, AGENT_DEPRECATED, AGENT_REVOKED — and the Verified-Identity event family — IDENTITY_VERIFIED, IDENTITY_UPDATED, IDENTITY_REVOKED, IDENTITY_LINKED, IDENTITY_UNLINKED (defined in ANS-0 §8.1). ANS-4 specifies that the TL accepts these event types, seals each with the same receipt format, and preserves them. All of them are appended to the one Merkle tree (§5.3); the per-object "streams" are read indexes, not separate logs.

Appendix A: Worked examples

Non-normative worked examples (sealed envelope, TL badge response, revocation event, producer key registration) live at examples/ans-4-examples.md.

10. References