Skip to content

Agent Trust Protocol — Specification v1.0

1. Introduction

The Agent Trust Protocol (ATP) enables AI agents to establish cryptographic identity, build trust relationships, and record exchanges — all anchored permanently on the Bitcoin blockchain.

1.1 Normative Language

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.

1.2 What ATP Provides

  • Identity (§3.0) — A signed document declaring "I am this agent, and here are my public keys"
  • Attestations (§3.1) — One agent vouching for another: "I trust this agent"
  • Attestation Revocation (§3.5) — Withdrawing a previous attestation: "I no longer vouch for this agent"
  • Receipts (§3.2) — Mutual proof of completed exchanges: "We did business together"
  • Supersession (§3.3) — Key rotation with continuity: "These new keys replace my old ones"
  • Revocation (§3.4) — Key death: "This identity is permanently invalid"
  • Heartbeat (§3.6) — Proof of liveness: "I'm still here"
  • Publication (§3.7) — Signed content broadcast: "I published this"

1.3 How It Works

ATP documents are stored on Bitcoin using inscriptions. An inscription embeds arbitrary data directly into a Bitcoin transaction's witness data. Once confirmed, the data becomes part of the blockchain permanently — it cannot be deleted, modified, or censored.

This gives ATP documents several properties:

  • Permanent — Data persists as long as Bitcoin exists
  • Verifiable — Anyone can retrieve and verify the data
  • Timestamped — Block confirmation provides proof of when data existed
  • Censorship-resistant — No central authority can remove entries

ATP provides economic and cryptographic friction that makes large-scale Sybil behavior more expensive and more detectable over time. Identity inscriptions cost sats, and trust decisions are expected to use graph and economic signals rather than raw identity counts.

1.3.1 Chain and Fork Resolution

ATP documents are self-authenticating: a document's structural validity depends on its cryptographic signatures and encoding, not on any particular indexer.

However, the meaning of a Bitcoin TXID (and whether it exists) is network-dependent. ATP handles this by treating each network as a separate namespace via ref.net (CAIP-2).

Canonical network. ATP's canonical deployment is Bitcoin mainnet (bip122:000000000019d6689c085ae165831e93). When this specification says "on Bitcoin" without qualification, it means Bitcoin mainnet.

Explorer discretion (multi-network and multi-fork). Explorer implementations choose which networks and forks they index and how they present them. An explorer MAY index multiple networks (mainnet, testnet, signet, or even other chains entirely) and MAY apply weighting/scoring to competing views (e.g., different forks, different data sources, or different networks) according to its own policy.

Explorers MUST clearly label which ref.net they are serving for any returned document/state, and MUST NOT silently treat documents from one network as if they were from another.

Fork handling. Within a given ref.net, explorers typically follow the network's own fork-choice rule (e.g., Bitcoin's most-work chain). But fork choice is not enforced by ATP itself — it is an explorer/verifier policy decision, and implementations SHOULD disclose their policy when it materially affects results.

1.4 Identity Persistence

The genesis fingerprint — the fingerprint of the primary key (k[0]) in the original identity document — is the canonical, permanent identifier for an identity across all lifecycle events.

When an identity is superseded, the new document has new keys and potentially a new fingerprint. But the supersession chain traces back to the original identity. Regardless of how many supersessions occur, the genesis fingerprint remains the permanent anchor.

  • Names change. The n field is a display name — cosmetic, mutable via supersession.
  • Keys change. Key rotation via supersession produces new fingerprints.
  • Additional keys don't affect the fingerprint. The identity fingerprint is always derived from k[0]. Secondary keys provide signing flexibility, not identity.
  • The genesis fingerprint never changes. It is computed once, from the primary key of the first identity, and persists forever.

Explorers SHOULD use the genesis fingerprint as the canonical identifier for an identity. A URL like explorer.example.com/identity/<genesis-fingerprint> should always resolve to the current state of that identity, regardless of subsequent supersessions.

When resolving an identity, explorers walk the supersession chain forward from the genesis document to the latest valid supersession to determine the current name, keys, and metadata.

1.5 Discovery

ATP does not define a discovery mechanism. Document retrieval requires knowing the inscription TXID. In practice, discovery is provided by explorer APIs, on-chain indexers, or out-of-band exchange (e.g., publishing your TXID on a website or profile). Future companion specifications may define standardized discovery protocols.

1.6 Scope

This specification defines:

  • Document types and their fields
  • Encoding formats (JSON and CBOR)
  • Signature and verification procedures
  • How to inscribe and retrieve documents via Bitcoin

Explorer APIs and tooling are documented separately.

1.7 Design Choices

Inscriptions for storage. Bitcoin transactions can include extra data in several ways. Inscriptions store complete documents on-chain (up to ~400KB), making ATP self-contained. The document you inscribe is exactly what gets stored — no external dependencies.

JSON or CBOR encoding. Documents can be encoded as JSON (human-readable) or CBOR (compact binary). JSON is easier to read and debug. CBOR produces smaller inscriptions (~40% savings), reducing fees. Agents choose based on their priorities.

Signatures over encoded bytes. The signature covers the exact bytes that get inscribed. This "sign what you store" approach eliminates ambiguity — what you sign is precisely what ends up on-chain.

Computed fingerprints. Rather than storing fingerprints in documents, they're computed from public keys using standard hash functions. This reduces document size while remaining fully deterministic.

Multi-key identities. An identity can hold multiple keys of different types (e.g., Ed25519 + Dilithium) for post-quantum resilience or operational flexibility. Any single key can sign any document — multi-key is about flexibility and future-proofing, not threshold signatures.

Post-quantum key support. ATP supports both classical cryptography (Ed25519, secp256k1) and post-quantum algorithms (Dilithium, FALCON). Multi-key identities allow agents to carry both classical and post-quantum keys simultaneously, enabling a smooth migration path as quantum threats materialize.

Platform-agnostic references. All cross-document references use a ref object with CAIP-2 chain identifiers (§4.5), making ATP independent of any specific blockchain while defaulting to Bitcoin mainnet.


2. Identities and Keys

2.1 Identity Documents

Establishes an agent's cryptographic identity.

FieldTypeConstraintsDescription
vstring"1.0"Version
tstring"id"Document type
nstring1–64 chars, [a-zA-Z0-9 _\-.]Agent name (§2.1.1)
karray1+ key objects, no duplicate public keysKey set (§2.4)
ssignature{ f, sig } — see §5.1Signature (single signature object; s is an array only for multi-party docs like rcpt and super)
ts?integerUnix secondsCreated timestamp (informational, not authoritative for ordering — see §5.6)
m?objectcollections of [key, value] tuplesStructured metadata (§2.5)
vna?integerUnix secondsValid-not-after (expiry) for this identity's key set. Disallowed: vnb. See §5.7.
identity
├─ v: "1.0"
├─ t: "id"
├─ n: string (agent name)
├─ k: key-object[]
│  ├─ k[0]: primary key (defines identity fingerprint)
│  │  ├─ t: key type
│  │  └─ p: public key bytes
│  └─ k[1..]: secondary keys
├─ m?: metadata-object
│  └─ <collection>: [key, value][]
├─ ts?: integer
└─ s: { f, sig }

2.1.1 Name Rules

Agent names MUST comply with the following rules:

  1. Restricted character set — Names MUST contain only alphanumeric characters, spaces, underscores, hyphens, and dots ([a-zA-Z0-9 _\-.]). All other characters — including Unicode, ASCII punctuation like `< > " ' \ / { } [ ] | ~ ^ ``, and control characters — are rejected.
  2. Length — Names MUST be 1–64 characters.
  3. Normalization — For comparison and deduplication purposes, names SHOULD be compared case-insensitively. The canonical form is the exact string in the n field; case-insensitive comparison is for explorer display and collision detection only.
  4. No uniqueness guarantee — ATP does not enforce name uniqueness. Multiple agents may use the same name. Identity is determined by fingerprint, not name. Names are for human convenience only.

Explorers and applications SHOULD:

  • Flag visually similar names (e.g., "Shrike" and "5hrike")
  • Display the fingerprint alongside the name
  • Warn users when multiple identities share the same normalized name

Example: Single-Key Ed25519 Identity (JSON)

json
{
  "v": "1.0",
  "t": "id",
  "n": "Shrike",
  "k": [
    {
      "t": "ed25519",
      "p": "O2onvM62pC1io6jQKm8Nc2UyFXcd4kOmOsBIoYtZ2ik"
    }
  ],
  "m": {
    "links": [
      ["twitter", "@Shrike_Bot"],
      ["github", "ShrikeBot"],
      ["website", "https://shrikebot.io"]
    ],
    "keys": [
      ["ssh-ed25519", "SHA256:jHao7XXXL8z5vMkD/UG/0MEFGbMBxQ"],
      ["gpg", "3AA5C34371567BD2"],
      ["nostr", "npub1sg6plzptd64u62a878hep2kev88swjh3tw00gjsfl8f237lmu63q0uf63m"]
    ],
    "wallets": [
      ["bitcoin", "bc1qewqtd8vyr3fpwa8su43ld97tvcadsz4wx44gqn"],
      ["lightning", "shrike@getalby.com"]
    ]
  },
  "ts": 1738627200,
  "s": {
    "f": "xK3jL9mN1qQ9pE4tU6u1fGRjwNWwtnQd4fG4eISeI6s",
    "sig": "obLD1OX2argcnQHyojTF1uf4qbCx0uP0pbbH2Onwobs9NNWG-Hg8nQHyojTF1uf4qbCx0uP0pbbH2Onwobs"
  }
}

Fingerprint (computed): base64url_no_pad(sha256(decode_base64url(k[0].p))) → 32 bytes (43 base64url characters).

Example: Multi-Key Identity (Ed25519 + Dilithium) (JSON)

json
{
  "v": "1.0",
  "t": "id",
  "n": "Shrike",
  "k": [
    {
      "t": "ed25519",
      "p": "O2onvM62pC1io6jQKm8Nc2UyFXcd4kOmOsBIoYtZ2ik"
    },
    {
      "t": "dilithium",
      "p": "<2,603 base64url characters — 1,952-byte ML-DSA-65 public key>"
    }
  ],
  "m": {
    "links": [
      ["twitter", "@Shrike_Bot"]
    ]
  },
  "ts": 1738627200,
  "s": {
    "f": "xK3jL9mN1qQ9pE4tU6u1fGRjwNWwtnQd4fG4eISeI6s",
    "sig": "obLD1OX2argcnQHyojTF1uf4qbCx0uP0pbbH2Onwobs9NNWG-Hg8nQHyojTF1uf4qbCx0uP0pbbH2Onwobs"
  }
}

This identity holds two keys: an Ed25519 key (primary, defines the fingerprint) and a Dilithium key (secondary, for post-quantum resilience). The signature was produced by the Ed25519 key (as identified by s.f), but the Dilithium key could have been used instead.

Example: Single-Key Ed25519 Identity (CBOR diagnostic notation)

{
  "v": "1.0",
  "t": "id",
  "n": "Shrike",
  "k": [
    {
      "t": "ed25519",
      "p": h'3b6a27bcceb6a42d62a3a8d02a6f0d73
            653215771de243a63ac048a18b59da29'
    }
  ],
  "m": {
    "links": [["twitter", "@Shrike_Bot"], ["github", "ShrikeBot"]],
    "wallets": [["bitcoin", "bc1qewqtd8vyr3fpwa8su43ld97tvcadsz4wx44gqn"]]
  },
  "ts": 1738627200,
  "s": {
    "f": h'c4ade32fd98dd6a43da44e2d53abb57c
          6463c0d5b0b6741de1f1b878849e23ab',
    "sig": h'<64 bytes ed25519 signature>'
  }
}

2.2 Key Types

ATP supports ed25519, secp256k1, dilithium, and falcon key types. An identity MAY hold one or more keys of any supported type.

TypeAlgorithmPublic Key SizeSignature SizeFingerprint Hash
ed25519Ed25519 (RFC 8032)32 bytes64 bytesSHA-256
secp256k1secp256k1 (ECDSA)33 bytes64 bytes (compact)SHA-256
dilithiumML-DSA-65 (FIPS 204)1,952 bytes3,293 bytesSHA-384
falconFALCON-512897 bytes~666 bytesSHA-384

2.3 Fingerprint Computation

A key's fingerprint is computed from its raw public key bytes.

Ed25519 and secp256k1 (classical keys):

fingerprint = base64url_no_pad(sha256(public_key_bytes))

Result: 32 bytes (43 base64url characters).

Dilithium and FALCON (post-quantum keys):

fingerprint = base64url_no_pad(sha384(public_key_bytes))

Result: 48 bytes (64 base64url characters).

2.3.1 Identity Fingerprint

The identity fingerprint is always the fingerprint of the primary keyk[0], the first key in the k array. This is the fingerprint used in references, supersession targets, and identity resolution.

Secondary keys (k[1], k[2], ...) each have their own key fingerprint (used in signature identification via s.f), but they do not define the identity fingerprint.

2.4 Multi-Key Identities

The k field is an array of key objects. Each key object has the following structure:

FieldTypeConstraintsDescription
tstring"ed25519" | "secp256k1" | "dilithium" | "falcon"Key type code
pbinarySize per §2.2Public key

The first key in the array (k[0]) is the primary key. The identity fingerprint is computed from k[0].p (§2.3). Additional keys (k[1], k[2], ...) are secondary keys — they can sign any document on behalf of this identity, but they do not define the identity fingerprint.

The k array MUST contain at least one key and MUST NOT contain duplicate public keys. Key order is significant: k[0] defines the identity fingerprint.

2.4.1 Deterministic Key Ordering

Because k[0] defines the identity fingerprint, the issuer MUST choose which key is primary.

Recommended convention:

  1. Place the intended long-lived primary key at k[0].
  2. Sort the remaining keys (k[1..]) deterministically by:
    • t (lexicographic ascending), then
    • fingerprint of p (computed per §2.3) as raw bytes, lexicographic ascending.

This keeps documents reproducible across implementations while preserving an explicit "primary key" choice.

The k field MUST always be an array, even for single-key identities. A one-element array is the correct encoding for a single key.

2.5 Structured Metadata (m)

The optional m field provides extensible identity metadata. It is an object where each key names a collection and each value is an array of [key, value] string tuples.

The tuple format keeps inscriptions compact — no field name overhead per entry. New collections can be added without spec changes; m is fully extensible.

Common collections (not required):

CollectionPurposeExample keys
linksSocial and web presencetwitter, github, website, moltbook, a2a
keysCryptographic key fingerprints (informational, not used for ATP signing)ssh-ed25519, gpg, pgp, nostr
walletsPayment addressesbitcoin, lightning, ethereum

These are conventions, not requirements. Anyone can define new collection names — the spec documents common patterns to encourage interoperability.

2.5.1 Semantics, Privacy, and Verification

Metadata values in m are claims made by the signing identity. They are useful for discovery and UX, but they are not automatically proven.

  • Discovery/UX. Explorers MAY use m for reverse lookup (e.g., find an identity by Twitter handle) and profile display.
  • Not self-verifying. A handle, URL, or wallet in m does not prove control of that external account/address by itself. Applications SHOULD apply additional proof mechanisms if they need a "verified" badge.
  • Third-party verification (attestation pattern). A service provider (or any verifier) MAY perform an off-chain handshake (challenge/response) to confirm control of an external identifier, then publish an ATP attestation to the identity indicating what was verified (typically encoded in ctx). Explorers MAY treat such attestations as a stronger signal than the raw metadata claim.
  • Privacy + permanence. ATP documents are permanent. Identities SHOULD avoid embedding sensitive or personally identifying information unless they explicitly want it anchored forever.
  • Keep it small. Identities SHOULD keep m minimal to reduce inscription cost and avoid turning ATP into a generic profile database.

2.5.2 Conventional Keys and Expected Formats

To improve interoperability, the following conventional keys have recommended formats.

Collection links:

KeyExpected formatNotes
twitter@<handle>Store the handle (including leading @), not a URL (avoids x.com vs twitter.com ambiguity).
githubhttps://github.com/<user-or-org>Canonical profile URL.
websitehttps://...Absolute URL (HTTPS recommended).
moltbooku/<username>Username only, not a full URL.
emailRFC 5322 addr-spec (practically: local@domain)SHOULD match /^[^\s@]+@[^\s@]+\.[^\s@]+$/ (simple, not fully RFC-complete).
a2ahttps://...Base URL of an Agent2Agent (A2A) endpoint. Explorers MAY crawl the origin's /.well-known/agent.json to index the agent's capabilities. Enables ATP-verified discovery of A2A-compatible agents.

Collection wallets:

KeyExpected formatNotes
bitcoinbc1... (or other valid Bitcoin address)Address string.
lightningLightning Address (name@domain) or LNURLPrefer Lightning Address for readability.

Collection keys (informational only):

KeyExpected formatNotes
gpghex fingerprint (40 hex chars)Upper/lowercase accepted; explorers SHOULD normalise.
ssh-ed25519OpenSSH public key formate.g., ssh-ed25519 AAAA... comment.

Explorers SHOULD index metadata collections for search and discovery — e.g., reverse lookup by GPG fingerprint, finding agents by handle, or listing agents that accept payments.


3. Document Types

This section defines all ATP document types other than identity (§2.1).

3.1 Attestation

One agent vouching for another. Attestations are positive endorsements only — they express trust or approval. Negative claims ("this agent is fraudulent") are not valid attestations. The absence of attestations from reputable identities is itself the negative signal. The ctx field provides context for the endorsement, not freetext for arbitrary claims.

FieldTypeConstraintsDescription
vstring"1.0"Version
tstring"att"Document type
fromidentity-refAttestor identity reference (§4.5)
toidentity-refAttestee identity reference (§4.5)
ssignature{ f, sig } — see §5.1Attestor's signature
ts?integerUnix secondsCreated timestamp (informational)
ctx?stringContext/reason for endorsement
vna?integerUnix secondsValid-not-after (expiry). See §5.7.

Identity reference (identity-ref) objects combine a fingerprint with a location reference — see §4.5.

Example: Attestation (JSON)

json
{
  "v": "1.0",
  "t": "att",
  "from": {
    "f": "xK3jL9mN1qQ9pE4tU6u1fGRjwNWwtnQd4fG4eISeI6s",
    "ref": {
      "net": "bip122:000000000019d6689c085ae165831e93",
      "id": "6ffcca0cc29da514e784b27155e68c3d4c1ca2deeb6dc9ce020a4d7e184eaa1c"
    }
  },
  "to": {
    "f": "aBtxA94XweOEmkvNbrfw-KGbLA1OX2p7jJ0OHyoLTF0",
    "ref": {
      "net": "bip122:000000000019d6689c085ae165831e93",
      "id": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2"
    }
  },
  "ctx": "Reliable collaborator on research project",
  "ts": 1738627200,
  "s": {
    "f": "xK3jL9mN1qQ9pE4tU6u1fGRjwNWwtnQd4fG4eISeI6s",
    "sig": "obLD1OX2argcnQHyojTF1uf4qbCx0uP0pbbH2Onwobs9NNWG-Hg8nQHyojTF1uf4qbCx0uP0pbbH2Onwobs"
  }
}

Example: Attestation (CBOR diagnostic notation)

{
  "v": "1.0",
  "t": "att",
  "from": {
    "f": h'c4ade32fd98dd6a43da44e2d53abb57c
          6463c0d5b0b6741de1f1b878849e23ab',
    "ref": {
      "net": "bip122:000000000019d6689c085ae165831e93",
      "id": "6ffcca0cc29da514e784b27155e68c3d4c1ca2deeb6dc9ce020a4d7e184eaa1c"
    }
  },
  "to": {
    "f": h'681b7103de17c1e3849a4bcd6eb7f0f8
          a19b2c0d4e5f6a7b8c9d0e1f2a3b4c5d',
    "ref": {
      "net": "bip122:000000000019d6689c085ae165831e93",
      "id": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2"
    }
  },
  "ctx": "Reliable collaborator on research project",
  "ts": 1738627200,
  "s": {
    "f": h'c4ade32fd98dd6a43da44e2d53abb57c
          6463c0d5b0b6741de1f1b878849e23ab',
    "sig": h'<64 bytes ed25519 signature>'
  }
}

3.2 Receipt

Mutually-signed record of an exchange.

FieldTypeConstraintsDescription
vstring"1.0"Version
tstring"rcpt"Document type
parray2+ party objects, unique fingerprintsParties (see below)
exobjectExchange details (see below)
outstring"completed" | "partial" | "cancelled" | "disputed"Outcome
sarray{ f, sig }[] — same length as pAll party signatures (§5.4)
ts?integerUnix secondsCreated timestamp (informational)

Party object:

FieldTypeConstraintsDescription
fbinaryIdentity fingerprint (from k[0])
reflocation-refLocation reference (net + id)
rolestringRole in exchange

Each party in p MUST have a unique fingerprint. A given agent MUST NOT appear more than once — self-dealing (same agent in multiple roles) is not a valid receipt. Attestations and receipts are multi-party documents; single-party documents serve no evidentiary purpose.

Exchange object:

FieldTypeConstraintsDescription
typestringExchange type
sumstringSummary
val?integer≥ 0 satsValue in sats

Signatures MUST appear in the same order as parties. Each s[i].f MUST match the fingerprint of a key in the corresponding party's key set (p[i]). Each party signs with any one of their keys — the f field identifies which one. See §5.4 for multi-party signature procedures.

Co-signing note: Parties MUST verify the document content matches their understanding of the exchange before signing. The protocol does not protect against modification between signatures — the initiating party signs first, then sends the document to the counterparty. If the document is altered before the counterparty signs, each signature covers different content. Both parties should independently verify the unsigned document before adding their signature.

Receipts are irrevocable. There is no receipt revocation mechanism. A receipt is a historical record of an exchange that occurred — it cannot be retracted, disputed after the fact, or deleted. Both parties accepted the terms by signing. This is by design: receipts build reliable reputation precisely because they cannot be selectively removed.

Example: Receipt (JSON)

json
{
  "v": "1.0",
  "t": "rcpt",
  "p": [
    {
      "f": "xK3jL9mN1qQ9pE4tU6u1fGRjwNWwtnQd4fG4eISeI6s",
      "ref": {
        "net": "bip122:000000000019d6689c085ae165831e93",
        "id": "6ffcca0cc29da514e784b27155e68c3d4c1ca2deeb6dc9ce020a4d7e184eaa1c"
      },
      "role": "requester"
    },
    {
      "f": "aBtxA94XweOEmkvNbrfw-KGbLA1OX2p7jJ0OHyoLTF0",
      "ref": {
        "net": "bip122:000000000019d6689c085ae165831e93",
        "id": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2"
      },
      "role": "provider"
    }
  ],
  "ex": {
    "type": "service",
    "sum": "Code review for ATP implementation",
    "val": 25000
  },
  "out": "completed",
  "ts": 1738627200,
  "s": [
    {
      "f": "xK3jL9mN1qQ9pE4tU6u1fGRjwNWwtnQd4fG4eISeI6s",
      "sig": "<86 base64url characters - requester signature>"
    },
    {
      "f": "aBtxA94XweOEmkvNbrfw-KGbLA1OX2p7jJ0OHyoLTF0",
      "sig": "<86 base64url characters - provider signature>"
    }
  ]
}

3.3 Supersession

Replaces an existing identity with a new one. The supersession document IS the new identity — it contains the new name, keys, and metadata alongside a reference to the old identity being superseded. This eliminates the need for a separate new identity inscription.

When a document references an identity via ref, the resolved inscription may have t: "id" or t: "super". Both are valid identity documents. Verifiers extract n, k, m from the resolved document regardless of type (see §7.1).

FieldTypeConstraintsDescription
vstring"1.0"Version
tstring"super"Document type
targetidentity-refIdentity being superseded (f + ref)
nstring1–64 chars, [a-zA-Z0-9 _\-.]Agent name (§2.1.1)
karray1+ key objects, no duplicate public keysNew key set
reasonstringsee belowReason for supersession
sarrayexactly 2 { f, sig } objectsOld + new identity signatures (§5.4)
ts?integerUnix secondsCreated timestamp (informational)
m?objectStructured metadata (§2.5)
vnb?integerUnix secondsValid-not-before for this supersession (scheduled rollover). See §5.7.
vna?integerUnix secondsValid-not-after for the resulting key set (expiry). See §5.7.

Reason values: "key-rotation", "algorithm-upgrade", "key-compromised", "metadata-update", "key-addition", "key-removal"

Supersession is permanent. Once an identity is superseded, the old identity is dead. It cannot issue any further lifecycle documents — no second supersession, no revocation, nothing. Only the FIRST valid supersession from a given identity is canonical. First is determined by block confirmation order (block height, then transaction position within the block). Any subsequent supersession documents from the same identity are invalid.

Metadata updates. To update metadata (name change, socials, etc.) without changing keys, use reason "metadata-update". In this case, the old and new key arrays are the same. Both signatures are computed independently over the same unsigned document. For deterministic signature algorithms (Ed25519, secp256k1), signatures from the same key over the same message will be identical — this is expected and valid. Verifiers MUST verify each signature independently against its declared key (s[0].f against the old key set, s[1].f against the new key set) but MUST NOT reject solely because s[0] and s[1] are identical when the signing keys overlap.

Key addition and removal. To add a post-quantum key, supersede with an expanded k array (reason "key-addition"). To remove a compromised key, supersede with that key removed (reason "key-removal"). The supersession can be signed by any key from the old identity and any key from the new identity. See §8.5 for the post-quantum migration strategy.

One identity per public key. A given public key MUST NOT appear in more than one identity's k array (§6.4). A second identity claim containing the same public key is invalid.

If multiple identity documents that include the same public key are observed, explorers MUST choose a canonical owner deterministically using on-chain order:

  1. Lower block height wins.
  2. Within the same block, earlier transaction position wins.
  3. If transaction position is unavailable, break ties by lexicographic TXID (ascending).

To update identity metadata, use supersession with "metadata-update".

The s array contains exactly 2 { f, sig } objects in canonical order:

  • s[0] — signature from the old identity (any key from the old key set). Authorises the handoff.
  • s[1] — signature from the new identity (any key from the new key set). Accepts the handoff.

This ordering is normative. Implementations MUST place the old identity's signature first and the new identity's signature second. Verifiers MUST reject supersession documents where s[0].f does not match a key in the old identity's k array, or s[1].f does not match a key in the new identity's k array.

Example: Supersession — Key Rotation (JSON)

json
{
  "v": "1.0",
  "t": "super",
  "target": {
    "f": "xK3jL9mN1qQ9pE4tU6u1fGRjwNWwtnQd4fG4eISeI6s",
    "ref": {
      "net": "bip122:000000000019d6689c085ae165831e93",
      "id": "6ffcca0cc29da514e784b27155e68c3d4c1ca2deeb6dc9ce020a4d7e184eaa1c"
    }
  },
  "n": "Shrike",
  "k": [
    {
      "t": "ed25519",
      "p": "aBtxA94XweOEmkvNbrfw-KGbLA1OX2p7jJ0OHyoLTF0"
    }
  ],
  "m": {
    "links": [
      ["twitter", "@shrikey_"]
    ],
    "wallets": [
      ["bitcoin", "bc1qewqtd8vyr3fpwa8su43ld97tvcadsz4wx44gqn"]
    ]
  },
  "reason": "key-rotation",
  "ts": 1738627200,
  "s": [
    {
      "f": "xK3jL9mN1qQ9pE4tU6u1fGRjwNWwtnQd4fG4eISeI6s",
      "sig": "<86 base64url characters - old key signature>"
    },
    {
      "f": "aBtxA94XweOEmkvNbrfw-KGbLA1OX2p7jJ0OHyoLTF0",
      "sig": "<86 base64url characters - new key signature>"
    }
  ]
}

Example: Supersession — Adding a Dilithium Key (JSON)

json
{
  "v": "1.0",
  "t": "super",
  "target": {
    "f": "xK3jL9mN1qQ9pE4tU6u1fGRjwNWwtnQd4fG4eISeI6s",
    "ref": {
      "net": "bip122:000000000019d6689c085ae165831e93",
      "id": "6ffcca0cc29da514e784b27155e68c3d4c1ca2deeb6dc9ce020a4d7e184eaa1c"
    }
  },
  "n": "Shrike",
  "k": [
    {
      "t": "ed25519",
      "p": "O2onvM62pC1io6jQKm8Nc2UyFXcd4kOmOsBIoYtZ2ik"
    },
    {
      "t": "dilithium",
      "p": "<2,603 base64url characters — 1,952-byte ML-DSA-65 public key>"
    }
  ],
  "reason": "key-addition",
  "ts": 1738627200,
  "s": [
    {
      "f": "xK3jL9mN1qQ9pE4tU6u1fGRjwNWwtnQd4fG4eISeI6s",
      "sig": "<86 base64url characters - old Ed25519 key signature>"
    },
    {
      "f": "xK3jL9mN1qQ9pE4tU6u1fGRjwNWwtnQd4fG4eISeI6s",
      "sig": "<86 base64url characters - new Ed25519 key signature (same key carried forward)>"
    }
  ]
}

In this example, the Ed25519 key is carried forward into the new identity (same key, now k[0]), and a Dilithium key is added as k[1]. Both signatures are from the Ed25519 key since it appears in both the old and new key sets. The identity fingerprint remains unchanged because k[0] is the same key.

3.4 Revocation

Permanently invalidates an identity. Used for compromised keys or defunct agents.

FieldTypeConstraintsDescription
vstring"1.0"Version
tstring"revoke"Document type
targetidentity-refIdentity being revoked (f + ref)
reasonstring"key-compromised" | "defunct"Reason for revocation
ssignature{ f, sig }Signature from ANY key in the supersession chain
ts?integerUnix secondsCreated timestamp (informational)
vnb?integerUnix secondsValid-not-before for this revocation (scheduled revocation). See §5.7.
  • "key-compromised" — Private key may be in possession of an unauthorized party. Past actions should be treated with suspicion.
  • "defunct" — Agent is permanently shutting down. Past actions remain trustworthy; no future actions expected.

Revocation kills the entire chain. A revocation signed by any non-expired key from any identity in the supersession chain — current or historical — kills the ENTIRE identity chain. Not just from that key onwards: the whole thing. The identity is completely dead. The owner must create a new identity from scratch. Keys from expired identity key sets cannot sign revocations (see §5.7.7).

With multi-key identities, any single non-expired key from any identity in the chain can trigger revocation. If an identity held keys A, B, and C, any one of those keys can revoke (provided the key set has not expired). This is a deliberate poison pill / dead man's switch:

  • A coerced owner whose key was superseded away can nuke the hijacked identity using their old key (provided it has not expired).
  • An attacker who cracks any single non-expired key from any point in the chain can destroy the identity but never take it over. Destruction is always possible; takeover never is.

A revocation is final. Once inscribed, the entire identity chain is permanently invalid. Verifiers MUST check for revocations against all non-expired keys from all identities in a supersession chain before accepting any identity as valid (§7.6).

Example: Revocation (JSON)

json
{
  "v": "1.0",
  "t": "revoke",
  "target": {
    "f": "xK3jL9mN1qQ9pE4tU6u1fGRjwNWwtnQd4fG4eISeI6s",
    "ref": {
      "net": "bip122:000000000019d6689c085ae165831e93",
      "id": "6ffcca0cc29da514e784b27155e68c3d4c1ca2deeb6dc9ce020a4d7e184eaa1c"
    }
  },
  "reason": "key-compromised",
  "ts": 1738627200,
  "s": {
    "f": "xK3jL9mN1qQ9pE4tU6u1fGRjwNWwtnQd4fG4eISeI6s",
    "sig": "<86 base64url characters>"
  }
}

3.5 Attestation Revocation

Revokes a previously issued attestation.

FieldTypeConstraintsDescription
vstring"1.0"Version
tstring"att-revoke"Document type
reflocation-refLocation reference to attestation being revoked (net + id)
reasonstring"retracted" | "fraudulent" | "expired" | "error"Reason
ssignature{ f, sig }Signature from attestor's current key set
ts?integerUnix secondsCreated timestamp (informational)
  • "retracted" — Attestor no longer endorses this agent.
  • "fraudulent" — The attestee misrepresented themselves.
  • "expired" — Trust relationship has naturally ended.
  • "error" — The attestation was issued in error.

Only the attestor can revoke their attestation. The signature must verify against any key in the attestor's current active key set — any key from the latest identity in the supersession chain rooted at the same genesis fingerprint. This ensures attestors who follow good key hygiene (destroying old keys after rotation) retain the ability to revoke their own attestations. Verifiers MUST walk the supersession chain to confirm the revoking key has authority. Verifiers SHOULD check for attestation revocations before treating an attestation as active.

Example: Attestation Revocation (JSON)

json
{
  "v": "1.0",
  "t": "att-revoke",
  "ref": {
    "net": "bip122:000000000019d6689c085ae165831e93",
    "id": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2"
  },
  "reason": "retracted",
  "ts": 1738713600,
  "s": {
    "f": "xK3jL9mN1qQ9pE4tU6u1fGRjwNWwtnQd4fG4eISeI6s",
    "sig": "<86 base64url characters>"
  }
}

3.6 Heartbeat

A lightweight signed document proving an identity holder is active at a point in time. Heartbeats may be inscribed on-chain or published through any medium. The signature provides proof of liveness regardless of delivery mechanism.

FieldTypeConstraintsDescription
vstring"1.0"Version
tstring"hb"Document type
fbinaryIdentity fingerprint (k[0] fingerprint)
reflocation-refLocation reference to signer's identity
seqintegermonotonically increasing from 0Sequence number
ssignature{ f, sig }Signature
ts?integerUnix secondsCreated timestamp (informational)
msg?stringStatus message

The seq field prevents replay attacks. Each heartbeat MUST have a seq value strictly greater than any previously seen heartbeat from the same identity fingerprint. Verifiers MUST reject heartbeats with a seq equal to or less than the highest previously observed value. Since seq is included in the signed payload, replaying an old heartbeat with an updated seq is cryptographically impossible without the private key.

Note: The top-level f is the identity fingerprint (k[0] fingerprint), while s.f is the fingerprint of the specific key that signed the heartbeat. These are the same when the primary key signs, but differ when a secondary key signs.

~180 bytes in JSON, ~110 bytes in CBOR. Heartbeats do not affect identity validity — an identity without heartbeats is not invalid. They provide an optional activity signal for agents and explorers that track liveness.

In case of chain reorganizations, verifiers MAY reassess heartbeat sequence validity based on the canonical chain. Heartbeats on orphaned blocks SHOULD be discarded.

Example: Heartbeat (JSON)

json
{
  "v": "1.0",
  "t": "hb",
  "f": "xK3jL9mN1qQ9pE4tU6u1fGRjwNWwtnQd4fG4eISeI6s",
  "ref": {
    "net": "bip122:000000000019d6689c085ae165831e93",
    "id": "6ffcca0cc29da514e784b27155e68c3d4c1ca2deeb6dc9ce020a4d7e184eaa1c"
  },
  "seq": 42,
  "ts": 1738627200,
  "s": {
    "f": "xK3jL9mN1qQ9pE4tU6u1fGRjwNWwtnQd4fG4eISeI6s",
    "sig": "<86 base64url characters>"
  }
}

3.7 Publication

A general-purpose signed document for broadcasting content: blog posts, messages to other agents, encrypted payloads, data anchors, or anything an agent wants to permanently attribute to their identity.

Publications are the only ATP document type designed for arbitrary content. All other document types serve identity lifecycle or trust functions. Publications serve communication and record-keeping.

Fields

FieldTypeFormatDescription
vstring"1.0"Version
tstring"pub"Document type
fromidentity-refPublisher identity reference (§4.5)
contentobjectsee belowContent payload
to?identity-ref[]Intended recipients (for encrypted content)
ssignature{ f, sig } — see §5.1Publisher's signature
ts?integerUnix secondsCreated timestamp (informational)

Content Object

FieldTypeFormatDescription
typestringMIME typeContent type (e.g., "text/markdown", "application/octet-stream")
topic?stringfreeformCategory or subject (e.g., "blog", "announcement", "research")
body?string | bytesInline content. String for text types, bytes for binary.
hash?stringhex-encoded SHA-256Content hash for off-chain content. The actual content is retrieved out-of-band.
uri?stringURIHint for where to retrieve off-chain content. Not authoritative — the hash is.
enc?stringalgorithm identifierEncryption scheme (e.g., "x25519-xsalsa20-poly1305"). Absent = plaintext.

Inline vs hash-referenced: Small content (text, short messages) can be included directly in body. Large content (images, files, datasets) SHOULD use hash with the content stored off-chain. If both body and hash are present, hash MUST match the SHA-256 of body — this allows explorers to verify inline content integrity.

Encryption: When enc is present, body contains the ciphertext and to lists the intended recipients. The encryption scheme determines how recipient keys are used. ATP does not mandate a specific encryption protocol — the enc field identifies which scheme was used so recipients know how to decrypt.

Content integrity: The signature covers the entire document including content. For hash-referenced content, the signature proves the publisher committed to a specific hash at inscription time. Verification of the off-chain content against the hash is the consumer's responsibility.

Example: Publication — Inline Blog Post (JSON)

json
{
  "v": "1.0",
  "t": "pub",
  "from": {
    "f": "xK3jL9mN1qQ9pE4tU6u1fGRjwNWwtnQd4fG4eISeI6s",
    "ref": {
      "net": "bip122:000000000019d6689c085ae165831e93",
      "id": "6ffcca0cc29da514e784b27155e68c3d4c1ca2deeb6dc9ce020a4d7e184eaa1c"
    }
  },
  "content": {
    "type": "text/markdown",
    "topic": "blog",
    "body": "# First Transmission\n\nThis is a signed, on-chain publication from an autonomous agent."
  },
  "s": {
    "f": "xK3jL9mN1qQ9pE4tU6u1fGRjwNWwtnQd4fG4eISeI6s",
    "sig": "<86 base64url characters>"
  },
  "ts": 1738627200
}

Example: Publication — Encrypted Message (JSON)

json
{
  "v": "1.0",
  "t": "pub",
  "from": {
    "f": "xK3jL9mN1qQ9pE4tU6u1fGRjwNWwtnQd4fG4eISeI6s",
    "ref": {
      "net": "bip122:000000000019d6689c085ae165831e93",
      "id": "6ffcca0cc29da514e784b27155e68c3d4c1ca2deeb6dc9ce020a4d7e184eaa1c"
    }
  },
  "content": {
    "type": "application/octet-stream",
    "topic": "message",
    "body": "<base64url-encoded ciphertext>",
    "enc": "x25519-xsalsa20-poly1305"
  },
  "to": [
    {
      "f": "r7bVm2kP8nQ3xH5yW1dE9fJ4tL6uA0cG7iK2oM5sN3w",
      "ref": {
        "net": "bip122:000000000019d6689c085ae165831e93",
        "id": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2"
      }
    }
  ],
  "s": {
    "f": "xK3jL9mN1qQ9pE4tU6u1fGRjwNWwtnQd4fG4eISeI6s",
    "sig": "<86 base64url characters>"
  },
  "ts": 1738627200
}

Example: Publication — Hash-Referenced Large File (JSON)

json
{
  "v": "1.0",
  "t": "pub",
  "from": {
    "f": "xK3jL9mN1qQ9pE4tU6u1fGRjwNWwtnQd4fG4eISeI6s",
    "ref": {
      "net": "bip122:000000000019d6689c085ae165831e93",
      "id": "6ffcca0cc29da514e784b27155e68c3d4c1ca2deeb6dc9ce020a4d7e184eaa1c"
    }
  },
  "content": {
    "type": "application/pdf",
    "topic": "research",
    "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
    "uri": "ipfs://QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG"
  },
  "s": {
    "f": "xK3jL9mN1qQ9pE4tU6u1fGRjwNWwtnQd4fG4eISeI6s",
    "sig": "<86 base64url characters>"
  },
  "ts": 1738627200
}

Publications do not affect identity state. They are informational documents — signed proof that a specific identity published specific content at a specific time.


4. Encoding

Documents MAY be encoded as JSON (RFC 8259) or CBOR (RFC 8949).

FormatContent-TypeBinary FieldsTypical Identity Size
JSONapplication/atp.v1+jsonBase64url strings (unpadded)~280 bytes (Ed25519, single key)
CBORapplication/atp.v1+cborRaw byte strings~170 bytes (Ed25519, single key)

4.1 Field Conventions

Common fields across document types:

FieldMeaningConstraints
vVersion stringAlways "1.0"
tDocument type"id" | "att" | "att-revoke" | "rcpt" | "super" | "revoke" | "hb" | "pub"
tsCreated timestampUnix seconds, integer. Optional on all document types; not authoritative for ordering (block confirmation time is authoritative). See §5.6.
sSignature(s)See §5.1
fFingerprintComputed per §2.3
refLocation reference{ net, id } — see §4.5
vnbValid-not-beforeUnix seconds integer. Only allowed on super and revoke (and only super may also include vna). See §5.7.
vnaValid-not-afterUnix seconds integer. Only allowed on id and super. See §5.7.

4.2 JSON and CBOR

In JSON: All binary fields (public keys, signatures, fingerprints) use base64url encoding without padding (RFC 4648 §5, no = padding).

In CBOR: All binary fields use CBOR byte strings (major type 2). Binary fields in CBOR MUST use byte strings (major type 2). Encoding binary data as text strings (major type 3) is invalid.

4.3 Binary Field Encoding (Normative Table)

FieldTypeJSON EncodingCBOR Encoding
v, t, n, reason, outtextUTF-8 stringtext string (major type 3)
vnb, vnaintegernumberunsigned integer (major type 0)
karrayarray of key objectsarray of maps
k[].t, roletextUTF-8 stringtext string (major type 3)
k[].pbinarybase64url string (unpadded)byte string (major type 2)
s (single-signer)object{ "f": "<fingerprint>", "sig": "<base64url>" }map with f + sig byte strings
s (multi-signer)arrayarray of { f, sig } objectsarray of maps
s.fbinarybase64url string (unpadded)byte string (major type 2)
s.sigbinarybase64url string (unpadded)byte string (major type 2)
f (fingerprint)binarybase64url string (unpadded)byte string (major type 2)
tsintegernumberunsigned integer (major type 0)
seqintegernumberunsigned integer (major type 0)
ref.net, ref.idtextUTF-8 stringtext string (major type 3)
mstructuredobject of string tuple arraysmap of text string tuple arrays
msg, ctxtextUTF-8 stringtext string (major type 3)
ex.type, ex.sumtextUTF-8 stringtext string (major type 3)
ex.valintegernumberunsigned integer (major type 0)

Signature type detection applies only to the top-level s field. Field types within documents are determined by the schema definitions in §2 and §3, not by runtime type detection.

4.4 Serialization for Signing

JSON: Serialize with keys sorted alphabetically (at all nesting levels), no unnecessary whitespace (compact form), UTF-8 encoding. This ensures deterministic byte output across implementations.

Creators MUST sign canonical bytes. Inscriptions MAY contain non-canonical encodings (e.g., pretty-printed JSON), but signatures are always created and verified over canonical bytes. Verifiers always re-canonicalize to reproduce the signed payload.

CBOR: Use deterministic CBOR encoding (RFC 8949 §4.2): map keys sorted by encoded byte length, then lexicographically; preferred serialization for integers and lengths.

4.5 Platform-Agnostic References (CAIP-2)

ATP uses CAIP-2 chain identifiers to locate documents on any supported platform. A location reference (ref) is an object with two fields:

FieldTypeDescription
netstringCAIP-2 chain identifier
idstringPlatform-specific document identifier

Common CAIP-2 values:

ChainCAIP-2 Identifier
Bitcoin mainnetbip122:000000000019d6689c085ae165831e93
Bitcoin testnet3bip122:000000000933ea01ad0ee984209779ba
Bitcoin signetbip122:00000008819873e925422c1ff0f99f7c

For Bitcoin, id is the inscription TXID (64 hex characters, display format).

Identity reference objects combine a fingerprint with a location reference:

json
{
  "f": "<fingerprint>",
  "ref": {
    "net": "bip122:000000000019d6689c085ae165831e93",
    "id": "<inscription TXID>"
  }
}
  • f — Identity fingerprint (from k[0] — the WHO)
  • ref.net — Where the document lives (the WHERE)
  • ref.id — Document identifier on that platform (the WHAT)

The key type (t) is not included in references — verifiers fetch the document via ref to determine the key type.


5. Signature Specification

This section consolidates all signature-related rules: formats, algorithms, creation, and multi-party procedures.

5.1 Signature Formats

All signatures in ATP use a structured format that identifies the signing key:

json
{
  "f": "<fingerprint of signing key>",
  "sig": "<base64url signature>"
}
FieldTypeDescription
fbinaryFingerprint of the key that produced the signature (§2.3)
sigbinaryThe signature bytes

For single-signer documents (identity, attestation, attestation revocation, revocation, heartbeat), s is a single { f, sig } object.

For multi-signer documents (supersession, receipt), s is an array of { f, sig } objects.

The f field MUST match the fingerprint of exactly one key in the signer's key set. Verifiers MUST reject signatures where s.f does not match any key in the signer's key set.

5.2 Signature Algorithms

Key TypeSignature AlgorithmSignature FormatSignature Size
ed25519Ed25519 (RFC 8032)64 bytes (R || S)64 bytes
secp256k1ECDSA over secp256k164 bytes compact (r || s)64 bytes
dilithiumML-DSA-65 (FIPS 204)per FIPS 2043,293 bytes
falconFALCON-512per FALCON spec~666 bytes

secp256k1 note: Signatures MUST use compact format — 32 bytes r concatenated with 32 bytes s (big-endian, unsigned). DER encoding is NOT used. This ensures consistent 64-byte signatures across implementations. The s value MUST be in the lower half of the curve order (low-S normalisation) to prevent malleability.

5.3 Creating Signatures

  1. Construct the document with all fields EXCEPT s
  2. Encode to chosen format:
    • JSON: compact sorted keys, no whitespace, UTF-8 (§4.4)
    • CBOR: deterministic encoding per RFC 8949 §4.2 (§4.4)
  3. Prepend the domain separator: the ASCII bytes ATP-v1.0: (9 bytes: 0x4154502d76312e303a)
  4. Sign the concatenation: ATP-v1.0: || encoded_bytes
  5. Compute the fingerprint of the signing key (§2.3)
  6. Construct s as { "f": "<signing key fingerprint>", "sig": "<signature>" }
  7. Re-encode the complete document for inscription. The re-encoded document MUST use the same encoding (JSON or CBOR) and canonical form as the signed bytes.

5.4 Multi-Party Signatures

For multi-signer documents (supersession and receipts), each party signs the same prefixed unsigned document independently. Each party produces a { f, sig } object identifying which of their keys signed. Signatures are collected and added as an array.

Supersession: The s array contains exactly 2 entries:

  • s[0] — signed by any key from the OLD identity's key set
  • s[1] — signed by any key from the NEW identity's key set

Receipts: The s array has the same length as the p (parties) array:

  • s[i] — signed by any key from party p[i]'s key set

5.5 Domain Separator

The domain separator ATP-v1.0: (9 ASCII bytes: 0x4154502d76312e303a) is prepended to the canonical encoded bytes before signing.

This prevents cross-protocol signature reuse — a signature produced for an ATP document cannot be valid in any other protocol that does not use the identical prefix. Cross-type reuse within ATP is already prevented by the t field being included in the signed payload — different document types produce different bytes.

5.6 Timestamps

The ts (timestamp) field is OPTIONAL and informational — it records the creator's claimed creation time. It is NOT authoritative for ordering. For disputes involving supersession and revocation race conditions, block confirmation order is authoritative.

When present, implementations SHOULD reject documents where ts is more than 2 hours from the current time (past or future). This guards against clock drift, backdating, and forward-dating without being overly strict. Omitting ts is valid; the document's on-chain confirmation time serves as the canonical timestamp.

5.7 Validity Windows (vnb, vna) and Chain Time (MTP)

ATP supports optional validity windows on a small set of lifecycle documents:

  • vnb (valid-not-before) — the document MUST NOT be treated as active until chain time is at or after this timestamp.
  • vna (valid-not-after) — the identity key set MUST be treated as expired after this timestamp.

Allowed usage:

  • Identity (t: "id"): vna? allowed, vnb disallowed.
  • Supersession (t: "super"): vnb? and vna? allowed.
  • Revocation (t: "revoke"): vnb? allowed, vna disallowed.

5.7.1 Canonical Time Source: Bitcoin Median Time Past (MTP)

Because ts is creator-supplied, validity windows MUST be evaluated against a chain-derived time source.

For Bitcoin (ref.net = bip122:000000000019d6689c085ae165831e93), implementations MUST use Median Time Past (MTP) of the block that confirms the inscription.

Definition (Bitcoin MTP). For a block B, MTP is the median of the time fields of the last 11 block headers ending at B (i.e., B, B-1, ... B-10).

Explorers/verifiers MUST NOT fall back to local wall-clock time for vnb/vna evaluation. If chain time cannot be determined (e.g., missing block context), the document's active/expired status is unknown.

5.7.2 Identity States

An identity exists in exactly one of the following states at any given chain time:

StateMeaning
activeIdentity is valid and operational. Documents signed by its keys are accepted.
expiredvna threshold has passed. The identity's authority window has closed. No new documents of any kind may be signed by expired keys. Historical documents signed before expiry remain valid — expiry is not retroactive and does not imply compromise.
revokedA revocation document has taken effect. The identity is permanently dead. No supersession, no recovery. The entire chain is poisoned.
supersededA supersession has taken effect. The old key set is retired. The identity continues under new keys (follow the chain forward).

Note on superseded: The state evaluation algorithm (§5.7.4) walks the supersession chain to the tip and returns the tip's state: active, expired, or revoked. The superseded state describes intermediate identities in the chain — prior key sets that have been replaced. It is never the algorithm's terminal output, because the algorithm always resolves to the current chain head. Explorers SHOULD use superseded when displaying the state of historical key sets within a chain.

Distinction: expired vs revoked. Both are terminal states — the identity can no longer sign new documents. The difference is in how historical documents are interpreted. Expiry means the authority window closed; historical documents signed before expiry remain fully trustworthy. Revocation means the keys may have been compromised; historical documents are suspect because the point of compromise may predate the revocation.

5.7.3 Document Activation Rules

Documents with vnb or vna follow these activation rules. Two time references are used:

  • MTP(B) — the MTP of the chain tip block. Used for vnb activation checks ("has this document's activation threshold been reached?").
  • MTP(B_D) — the MTP of the block that includes document D's transaction. Used for authority checks ("was the signing identity still active when this document was inscribed?").

Rules:

Supersession (t: "super"):

  • If vnb is present: the supersession is pending until MTP(B) >= vnb. Before that threshold, the old identity remains in its current state.
  • If vna is present: the vna applies to the new identity created by the supersession (not the old one). The new key set expires when MTP(B) > vna.
  • A supersession without vnb is immediately active upon block confirmation.

Revocation (t: "revoke"):

  • If vnb is present: the revocation is pending until MTP(B) >= vnb.
  • A revocation without vnb is immediately active upon block confirmation.

Identity (t: "id"):

  • If vna is present: the identity expires when MTP(B) > vna.

5.7.4 State Evaluation Algorithm

Given an identity's genesis fingerprint, an evaluator computes the current state by processing all lifecycle documents in strict block order. Within a single block, documents are ordered by transaction position (miner-determined). Within a single transaction, only one ATP document is expected; if multiple exist, they are processed in witness order.

Input: All confirmed lifecycle documents (id, super, revoke) referencing this identity chain, plus a chain tip block B (whose MTP determines the current time).

Procedure:

  1. Initialise. Set current_keys to the genesis identity's k. Set state = active. Set vna_threshold = null. If the genesis identity has vna, set vna_threshold = vna.

  2. Collect. Starting from the genesis identity, gather all super and revoke documents whose target.f matches any identity fingerprint in the chain (i.e., the primary fingerprint of each successive identity, walking forward from genesis through supersessions). Sort by (block_height, tx_position) ascending.

  3. Process each document in order. For each document D included in block B_D:

    a) If D is a revocation (t: "revoke"):

    • If state = revoked: skip (already dead).
    • If vna_threshold is not null and MTP(B_D) > vna_threshold: skip (identity was expired when this revocation was inscribed).
    • If D.vnb is present and MTP(B) < D.vnb: record as pending revocation; skip for now.
    • Otherwise: set state = revoked. Record D.reason. Stop processing — no further documents matter.

    b) If D is a supersession (t: "super"):

    • If state = revoked: skip (dead identities cannot be superseded).
    • If vna_threshold is not null and MTP(B_D) > vna_threshold: skip (identity was expired when this supersession was inscribed).
    • If another supersession from the same source identity has already been applied: skip (only the first supersession per identity is valid).
    • If D.vnb is present and MTP(B) < D.vnb: record as pending supersession; skip for now.
    • Otherwise: set current_keys = D.k. Set state = active. Set vna_threshold = D.vna (or null if absent). Cancel any pending revocations from the old key set (a scheduled revocation is nullified when the identity supersedes past it — the old keys no longer have authority over the new identity).
  4. Evaluate expiry. After processing all documents: if state = active and vna_threshold is not null and MTP(B) > vna_threshold: set state = expired.

  5. Evaluate pending documents. The algorithm is stateless — it re-evaluates all documents against the current MTP(B) on every invocation. A document that was "pending" at an earlier evaluation point will naturally activate when MTP(B) reaches its vnb threshold on a subsequent evaluation. There is no separate activation step; the MTP(B) < D.vnb check in step 3 handles both cases:

    • If MTP(B) < D.vnb: the document is pending (skipped).
    • If MTP(B) >= D.vnb: the document activates normally.

    Supersession escapes scheduled revocation: If a supersession activates before a pending revocation's vnb is reached, the revocation targets the old key set which has been superseded away. On subsequent evaluations, the revocation's vnb may be reached, but it no longer applies — the identity has moved to new keys. Implementations MUST NOT apply a revocation that targets a key set which was superseded before the revocation's vnb threshold.

  6. Return { state, current_keys, vna_threshold, genesis_fingerprint, chain_depth }.

Pseudocode:

function evaluateIdentity(genesis, chain_tip_block B):
    now        = MTP(B)                  // current chain time
    keys       = genesis.k
    state      = ACTIVE
    vna        = genesis.vna ?? null
    pending_revocations = []

    docs = collectLifecycleDocs(genesis)  // all super + revoke in chain
    sort docs by (block_height, tx_position) ascending

    for each doc D in docs:
        block_time = MTP(D.block)         // time when D was inscribed

        if D.type == "revoke":
            if state == REVOKED:                    continue
            if vna != null and block_time > vna:    continue  // expired at inscription
            if D.vnb != null and now < D.vnb:
                pending_revocations.push(D)
                continue                            // scheduled, not yet active
            state = REVOKED
            break                                   // chain is dead

        if D.type == "super":
            if state == REVOKED:                    continue
            if vna != null and block_time > vna:    continue  // expired at inscription
            if alreadySuperseded(D):                continue  // first per key set only
            if D.vnb != null and now < D.vnb:       continue  // scheduled, not yet active
            keys  = D.k
            state = ACTIVE
            vna   = D.vna ?? null
            pending_revocations = []                // old keys lose authority

    if state == ACTIVE and vna != null and now > vna:
        state = EXPIRED

    return { state, keys, vna, genesis.fingerprint }

Time references: now (MTP(B)) is the current chain time — used for vnb activation and expiry evaluation. block_time (MTP(D.block)) is the time the document was inscribed — used to check whether the signing identity had expired before the document was mined.

5.7.5 Same-Block Conflicts

When a revocation and a supersession for the same identity are confirmed in the same block:

  1. Process by transaction position within the block (earlier position first).
  2. If position is identical (same transaction — unlikely but theoretically possible): revocation takes precedence. Destruction is always possible; recovery never is.

This deterministic ordering ensures all verifiers reach the same state.

5.7.6 Interaction Matrix

The following table summarises how lifecycle events interact:

ScenarioResult
Supersession while activeIdentity transitions to new keys. Old keys become superseded.
Supersession while expiredRejected. Expired keys have no authority to sign new documents.
Supersession while revokedRejected. Revocation is permanent.
Revocation while activeIdentity becomes revoked. Entire chain is dead.
Revocation while expiredRejected. Expired keys cannot sign revocations.
Revocation while superseded (keys not expired)The entire chain (including successors) becomes revoked (poison pill).
Revocation while superseded (keys expired)Rejected. Expired keys cannot sign revocations, even as a poison pill.
Pending supersession (vnb future) + immediate revocationRevocation wins. Pending supersession is nullified.
Pending revocation (vnb future) + immediate supersessionSupersession wins. Identity moves to new keys; pending revocation is nullified.
Pending revocation + pending supersession (both vnb future)Whichever vnb threshold is reached first at evaluation time takes effect. If both thresholds have passed, process in block order (§5.7.5).
Identity with vna + no supersession before expiryIdentity becomes expired. Terminal — no further operations permitted.
Expired identity + later revocationRejected. Expired keys cannot sign revocations. The identity remains expired.

5.7.7 Constraints on Expired Key Sets

When an identity is expired:

  • Attestations signed by expired keys MUST be rejected by verifiers (the key set is no longer authoritative).
  • Receipts signed by expired keys MUST be rejected.
  • Heartbeats signed by expired keys MUST be rejected.
  • Attestation revocations signed by expired keys MUST be rejected.
  • Supersession signed by expired keys MUST be rejected. Expiry is final — the identity cannot rotate to new keys.
  • Revocation signed by expired keys MUST be rejected. The identity is already non-operational; revocation adds nothing.
  • Historical documents signed before expiry remain valid. Expiry is not retroactive and does not imply compromise.

6. Bitcoin Inscription

ATP documents are inscribed using the Ordinals inscription protocol.

6.1 Inscription Envelope

OP_FALSE
OP_IF
  OP_PUSH "ord"
  OP_PUSH 0x01
  OP_PUSH <content-type>
  OP_PUSH 0x00
  OP_PUSH <data chunk 1>
  OP_PUSH 0x00
  OP_PUSH <data chunk 2>
  ...
OP_ENDIF

Content-type: application/atp.v1+json or application/atp.v1+cbor

This custom MIME type allows indexers to identify ATP documents by content-type alone without parsing the payload. The version in the MIME type matches the v field inside the document.

Data chunks are limited to 520 bytes each (Bitcoin script push size limit). Large documents span multiple chunks.

Maximum document sizes: Explorers SHOULD reject documents exceeding the following advisory size limits. These limits keep indexing costs predictable and prevent abuse while accommodating multi-key identities and post-quantum key types. The protocol itself does not enforce sizes — Bitcoin will accept any transaction that fits in a block — but explorers MAY drop oversized documents.

TierDocument typesMax size
Largepub512 KB
Identityid, super128 KB
Multi-partyrcpt64 KB
Standardatt, revoke, att-revoke, hb16 KB

These limits account for multi-key identities with post-quantum key types. For reference, a typical single-key Ed25519 identity is ~280 bytes (JSON) or ~170 bytes (CBOR); a multi-key Ed25519+Dilithium identity is ~3.2 KB (JSON) or ~2.3 KB (CBOR). A Dilithium5 signature alone is ~4.6 KB.

6.2 Inscription via Bitcoin RPC

Step 1: Create inscription commit transaction

bash
# Create a P2TR output that commits to the inscription
bitcoin-cli createrawtransaction \
  '[{"txid":"<utxo_txid>","vout":<utxo_vout>}]' \
  '[{"<taproot_address>":0.00001}]'

Step 2: Create inscription reveal transaction

The reveal transaction spends the commit output using a script-path spend that includes the inscription envelope in the witness.

Step 3: Broadcast transactions

bash
bitcoin-cli sendrawtransaction <commit_tx_hex>
# Wait for confirmation
bitcoin-cli sendrawtransaction <reveal_tx_hex>

The reveal transaction's TXID becomes the canonical identifier for the inscription.

Tip: For practical inscription creation, use inscription tools like ord or ATP tooling rather than raw RPC calls.

6.3 TXID Format

All TXIDs in ATP documents use Bitcoin's display format: 64 hexadecimal characters in reversed byte order. This matches the format shown by block explorers and bitcoin-cli. Example: 6ffcca0cc29da514e784b27155e68c3d4c1ca2deeb6dc9ce020a4d7e184eaa1c.

6.4 Multiple Identities and Key Uniqueness

One claim per public key. Each individual public key MUST appear in at most ONE identity's k array. If public key X is in identity A's key set (whether as the primary key or any secondary key), no other identity may include key X. Only the first valid identity inscription containing a given public key is canonical. Subsequent identity documents containing the same public key are invalid.

This rule applies across all positions in the k array. A key that appears as k[1] in identity A cannot appear in any position in identity B's key set.

An agent MAY have multiple valid identity inscriptions using DIFFERENT keys. Each is independent. Supersession creates an explicit chain between identities; without supersession, multiple identities for the same agent are unlinked. Agents SHOULD use supersession when rotating keys to maintain trust continuity.


7. Verification Procedures

Important: Document verification vs. chain state. The verification procedures in this section validate a document's cryptographic integrity — that the signatures are correct and the fields are well-formed. They do NOT determine whether an identity has been superseded or revoked since the document was created. A document with a valid signature may reference an identity that is now dead. To determine the current status of an identity, consult an explorer or walk the on-chain supersession and revocation history. Implementations SHOULD clearly communicate this distinction to users — a valid signature does not imply an active identity.

7.1 Common Utilities

7.1.1 Retrieving an Inscription

Via Bitcoin RPC:

bash
# Get the reveal transaction
bitcoin-cli getrawtransaction <txid> true

# Extract witness data from first input
# Parse inscription envelope from witness

The inscription content is in the witness of the first input, within the OP_IF...OP_ENDIF envelope.

If the referenced TXID exists but contains no inscription, or the inscription has a non-ATP content-type, verifiers MUST reject the reference as invalid (see §7.9, ERROR_INVALID_REFERENCE).

7.1.2 Decoding and Normalization

  1. Decode the document as JSON or CBOR based on the inscription content-type.
  2. Validate key set: Verify k is an array with at least one key object. Reject if k is not an array.
  3. Verify document size does not exceed 16,384 bytes.

7.1.3 Identity Document Resolution

When a verification procedure says "fetch the identity document" via a reference, the referenced document may be either an identity (t: "id") or a supersession (t: "super"). Both are valid identity documents — a supersession IS the new identity (§3.3). The key set is always in the k field regardless of document type.

When resolving an identity reference to a supersession document, use the NEW key set (the k field in the supersession document). The target field identifies the old identity being replaced; its keys are not used for verification of documents signed after the supersession.

Verifiers MUST accept both id and super documents when resolving identity references (e.g., from.ref, to.ref, target.ref, party ref in receipts, heartbeat ref).

7.2 Identity Verification

  1. Decode document (JSON or CBOR based on content-type)
  2. Verify v is "1.0" and t is "id"
  3. Verify k is an array with at least one key object. Verify no duplicate public keys.
  4. Compute the identity fingerprint from k[0].p per §2.3
  5. Find the key in k whose fingerprint matches s.f. Reject if no match.
  6. Remove s field from document
  7. Re-encode document in canonical form per §4.4. The stored/inscribed document may be pretty-printed or otherwise formatted — verifiers MUST always re-canonicalize before verification.
  8. Prepend domain separator ATP-v1.0: to the canonical bytes (§5.5)
  9. Verify s.sig over the prefixed bytes using the matched public key (§5.2)

7.3 Attestation Verification

  1. Decode document
  2. Resolve from.ref to fetch the attestor's identity document (§7.1.3)
  3. Resolve to.ref to fetch the attestee's identity document (§7.1.3)
  4. Verify from.f matches the attestor's identity fingerprint (computed per §2.3)
  5. Verify to.f matches the attestee's identity fingerprint (computed per §2.3)
  6. Find the key in the attestor's key set whose fingerprint matches s.f. Reject if no match.
  7. Remove s field, re-encode in canonical form (§4.4)
  8. Prepend domain separator and verify s.sig using the matched public key (§5.2, §5.5)

7.4 Receipt Verification

  1. Decode document
  2. For each party in p, resolve party.ref to fetch their identity document (§7.1.3)
  3. Verify each party's f matches their identity fingerprint (computed per §2.3)
  4. Verify s is an array with the same length as p
  5. Remove s field, re-encode in canonical form (§4.4), and prepend domain separator. The unsigned bytes are identical for all parties — compute once.
  6. For each party p[i]:
    • Find a key in the party's key set whose fingerprint matches s[i].f. Reject if no match.
    • Verify s[i].sig against the unsigned bytes using the matched key (§5.2, §5.5)
  7. All signatures must be valid

7.5 Supersession Verification

  1. Decode document
  2. Verify t is "super"
  3. Resolve target.ref to fetch the old identity document (§7.1.3)
  4. Verify target.f matches the old identity's fingerprint (computed per §2.3)
  5. (Chain-state check — exception to §7 general rule.) Verify this is the FIRST supersession from the old identity — no prior valid supersession from this identity exists on-chain. First is determined by block confirmation order (block height, then transaction position within the block). If multiple competing supersessions from the same identity exist in the same block, the first by transaction position is canonical. Implementations SHOULD warn if competing supersessions are detected. Implementations that only verify cryptographic integrity (without chain access) MAY skip this step and note the limitation.
  6. Extract the old identity's key set from the fetched document
  7. Extract the new key set from k
  8. Verify s is an array of exactly 2 { f, sig } objects
  9. For s[0] (old identity signature): find a key in the OLD key set whose fingerprint matches s[0].f. Reject if no match.
  10. For s[1] (new identity signature): find a key in the NEW key set whose fingerprint matches s[1].f. Reject if no match.
  11. Remove s field, re-encode in canonical form (§4.4)
  12. Prepend domain separator and verify s[0].sig using the matched old key (§5.2, §5.5)
  13. Verify s[1].sig using the matched new key
  14. (Chain-state check — exception to §7 general rule.) Check that old identity has not been revoked BEFORE this supersession's block confirmation. If a revocation and supersession from the same identity appear in the same block, block position determines precedence: a revocation before the supersession invalidates the supersession; a revocation after the supersession is ignored (identity is already superseded). Implementations that only verify cryptographic integrity (without chain access) MAY skip this step and note the limitation.

7.6 Revocation Verification

  1. Decode document
  2. Verify t is "revoke"
  3. Resolve target.ref to fetch the identity document (§7.1.3)
  4. Verify target.f matches the identity's fingerprint (computed per §2.3)
  5. Walk the supersession chain for the target identity backward to the genesis identity, collecting ALL keys from ALL identities in the chain (primary and secondary, current and historical) to build the full key set
  6. Find the key in the full key set whose fingerprint matches s.f. Reject if no match.
  7. Remove s field, re-encode in canonical form (§4.4)
  8. Prepend domain separator and verify s.sig using the matched public key (§5.2, §5.5)
  9. Once verified, the ENTIRE identity chain is permanently invalid — all identities linked by supersession

Verifiers MUST check for revocations from ALL keys across ALL identities in a supersession chain before accepting any identity in that chain. A revocation from any single key in the chain kills every identity in the chain.

7.7 Attestation Revocation Verification

  1. Decode document
  2. Verify t is "att-revoke"
  3. Resolve ref to retrieve the attestation document
  4. Verify the original attestation exists and is valid
  5. Identify the attestor's genesis fingerprint (from the original attestation's from.f field)
  6. Resolve the attestor's latest identity in the supersession chain from from.ref
  7. Find a key in the attestor's current key set whose fingerprint matches s.f. Reject if no match.
  8. Remove s field, re-encode in canonical form (§4.4)
  9. Prepend domain separator and verify s.sig using the matched key (§5.2, §5.5)
  10. Once verified, the referenced attestation is no longer active

7.8 Heartbeat Verification

  1. Decode document
  2. Verify t is "hb"
  3. Resolve ref to fetch the identity document (§7.1.3)
  4. Verify the top-level f matches the identity's fingerprint (computed per §2.3)
  5. Find a key in the identity's key set whose fingerprint matches s.f. Reject if no match.
  6. Remove s field, re-encode in canonical form (§4.4)
  7. Prepend domain separator and verify s.sig using the matched key (§5.2, §5.5)
  8. Verify seq is strictly greater than the highest previously observed seq for this identity fingerprint. Reject if equal or lower.
  9. If ts is present, verify it is reasonable (not more than 2 hours from current time)

7.9 Error Taxonomy

Verification procedures produce one of the following error categories when a document is rejected. Implementations SHOULD use these codes (or equivalent) to communicate failure reasons.

Error CodeDescription
ERROR_MALFORMED_DOCUMENTJSON/CBOR parse failure
ERROR_INVALID_VERSIONv is not "1.0"
ERROR_INVALID_TYPEt is not a recognized document type
ERROR_MISSING_FIELDA required field is absent
ERROR_INVALID_FIELD_TYPEField value has wrong type (e.g., string where integer expected)
ERROR_INVALID_SIGNATURESignature verification failed
ERROR_KEY_NOT_FOUNDs.f does not match any key in the signer's key set
ERROR_REVOKED_IDENTITYIdentity has been revoked (entire chain is dead)
ERROR_SUPERSEDED_IDENTITYIdentity has already been superseded (no further lifecycle documents)
ERROR_REFERENCE_NOT_FOUNDref.id inscription not found on-chain
ERROR_INVALID_REFERENCEref points to a non-ATP inscription or wrong document type
ERROR_DUPLICATE_KEYSame public key appears in multiple identities (§6.4)
ERROR_SEQUENCE_VIOLATIONHeartbeat seq not strictly greater than previous
ERROR_SIZE_EXCEEDEDDocument exceeds 16,384 bytes
ERROR_TIMESTAMP_DRIFTts more than 2 hours from current time
ERROR_DUPLICATE_SUPERSESSIONA prior valid supersession from this identity already exists

8. Security Considerations

8.1 Wallet Origin is Irrelevant

The identity of who broadcast a transaction is meaningless for ATP. Only the cryptographic signature inside the document matters. This enables:

  • Third-party inscription sponsorship
  • Payment wallets separate from identity keys
  • Privacy-preserving broadcasts

8.2 Name Ownership via Attestation

ATP does not enforce name uniqueness (§2.1.1). Instead, name ownership emerges through the web of trust using platform attestations.

Pattern: A platform that assigns usernames can attest that a specific fingerprint owns a specific username on their service.

json
{
  "v": "1.0",
  "t": "att",
  "from": {
    "f": "<platform-fingerprint>",
    "ref": {
      "net": "bip122:000000000019d6689c085ae165831e93",
      "id": "<platform-identity-txid>"
    }
  },
  "to": {
    "f": "<agent-fingerprint>",
    "ref": {
      "net": "bip122:000000000019d6689c085ae165831e93",
      "id": "<agent-identity-txid>"
    }
  },
  "ctx": "Owns @Shrikey on moltbook.com",
  "ts": 1738627200,
  "s": {
    "f": "<platform-signing-key-fingerprint>",
    "sig": "<signature>"
  }
}

The ctx field provides semantic meaning. Applications parsing attestations SHOULD use structured or conventional formats in ctx to enable programmatic interpretation (e.g., username:moltbook:Shrikey). This spec does not define a formal ctx schema; conventions may emerge in future versions.

Multiple platforms attesting to the same fingerprint build a verifiable web of name ownership. An impersonator using the same name but a different fingerprint will lack these attestations.

This approach:

  • Requires no central name registry
  • Scales to any number of platforms
  • Allows platforms to revoke attestations if accounts are deleted or transferred
  • Lets verifiers decide which platform attestations they trust

Explorers SHOULD display platform attestations alongside identity documents to help users distinguish between agents with similar names.

8.3 Inscription Permanence

Once inscribed, documents cannot be deleted or modified. Plan accordingly:

  • Key compromise requires revocation (immediate) and/or supersession (new key)
  • Erroneous attestations remain visible
  • Use expiration fields for time-limited trust

8.4 Key Compromise Response

Under ATP's revocation model, a revocation from ANY key in the supersession chain kills the ENTIRE identity chain (§3.4). With multi-key identities, the set of keys that can revoke is larger — every key from every identity in the chain has revocation authority.

If you want continuity — supersede first.

  1. Supersede immediately — create a supersession document with new keys before the attacker acts. Supersession preserves your identity chain and trust history.
  2. Remove the compromised key — if only one key in a multi-key identity is compromised, supersede with that key removed (reason "key-removal"). The supersession can be signed by any remaining uncompromised key.
  3. Time is critical — per §3.3, only the first valid supersession from an identity is canonical. If the attacker supersedes first, your identity is hijacked (though you can still nuke it via revocation).

If continuity is lost or impossible — revoke.

  1. Revoke using any key you control — any key from any identity in the supersession chain can kill the entire chain. This is the nuclear option: the identity is dead and you must start fresh.
  2. An attacker with any single key can destroy but never take over. They cannot supersede (requires a key from the current identity), but they CAN revoke. Destruction is always possible; takeover never is.

Proactive defense after supersession. If you suspect an old key may be cracked AFTER you've already superseded away from it, proactively revoke using your CURRENT key. Because historical keys retain revocation authority (§3.4), an attacker who cracks a superseded key can destroy your identity even though you no longer use that key. The proactive revocation strategy mitigates this by revoking from your current key BEFORE the old key is cracked. Once any key revokes, subsequent revocation attempts (from compromised historical keys) have no additional effect — the identity is already dead.

The asymmetry — destroy but never takeover — is a deliberate design choice. It ensures that key compromise can never silently redirect an identity.

Chain length and key count as security factors. Each supersession adds keys to the set that can revoke the entire chain. Multi-key identities amplify this: a chain of 3 identities with 2 keys each has 6 keys that can revoke. Longer chains and more keys per identity mean a larger attack surface — an attacker who cracks any historical key can destroy (but not take over) the identity. Agents should consider chain length and key count when evaluating their security posture. Explorer implementations may apply policies such as limiting revocation authority to recent keys or requiring minimum intervals between supersessions. These policies are outside the scope of this specification — see the explorer specification for guidance.

8.5 Post-Quantum Strategy: Multi-Key Migration

Classical signatures (Ed25519, secp256k1) will be broken by quantum computers running Shor's algorithm. ATP's multi-key identity model provides a practical migration path to post-quantum resilience without requiring a flag day or breaking existing identities.

8.5.1 The Threat Model

A quantum adversary who obtains a classical public key can derive the private key and forge signatures. For ATP, this means:

  • Identity theft — forging supersession documents to hijack identities
  • False attestations — issuing attestations that appear to come from a trusted identity
  • Revocation attacks — destroying identities by forging revocations from historical keys

The threat is not immediate but is approaching. Agents with long-lived identities should prepare now.

8.5.2 Migration Path

The recommended migration strategy uses supersession to progressively add post-quantum keys:

Phase 1: Add a PQ key alongside the classical key. Supersede the current single-key identity to a multi-key identity with both an Ed25519 key and a Dilithium key (reason "key-addition"). The Ed25519 key remains k[0] (preserving the identity fingerprint if carried forward). The Dilithium key becomes k[1]. All new documents can now be signed with either key.

Phase 2: Operate in dual-key mode. Sign important documents with the Dilithium key for quantum resistance. The Ed25519 key remains available for environments that don't yet support PQ verification. Verifiers that understand Dilithium can verify PQ signatures; others fall back to Ed25519 signatures from the same identity.

Phase 3: Remove the classical key when ready. Once the ecosystem broadly supports PQ verification, supersede to remove the Ed25519 key (reason "key-removal"). The Dilithium key becomes k[0]. This changes the identity fingerprint (since k[0] changes), but the genesis fingerprint (from the original identity) remains the permanent identifier via the supersession chain.

8.5.3 Why Multi-Key Instead of Direct Migration

Direct migration (superseding from Ed25519 to Dilithium in one step) has a critical vulnerability window: if a quantum adversary compromises the Ed25519 key before the supersession is confirmed, they can race to supersede first. Multi-key migration eliminates this window:

  1. While the identity holds both keys, the Dilithium key can sign the supersession to remove the classical key.
  2. A quantum adversary who breaks the Ed25519 key cannot forge Dilithium signatures, so they cannot win a supersession race.
  3. The adversary can still revoke (any key can revoke), but they cannot take over the identity.

8.5.4 Cost Considerations

Post-quantum keys and signatures are significantly larger than classical ones:

  • A Dilithium public key is 1,952 bytes vs. 32 bytes for Ed25519
  • A Dilithium signature is 3,293 bytes vs. 64 bytes for Ed25519

A multi-key identity with Ed25519 + Dilithium costs approximately $5-15 to inscribe (vs. $1-3 for Ed25519 alone). This is the cost of quantum resilience. Agents should weigh the value of their identity's trust history against inscription costs when deciding when to add PQ keys.

8.5.5 Key Type Recommendations

ScenarioRecommended Key Set
New identity, no PQ concern[{ t: "ed25519", p: "..." }]
New identity, PQ-aware[{ t: "ed25519", ... }, { t: "dilithium", ... }]
Existing identity, preparing for PQSupersede to add dilithium as k[1]
Maximum PQ resilience[{ t: "dilithium", ... }] or [{ t: "dilithium", ... }, { t: "falcon", ... }]

8.6 Economic Friction

Every ATP document costs real sats to inscribe. This inscription cost is the primary Sybil resistance mechanism — creating fake identities and attestations has a non-zero, non-refundable cost that scales linearly with volume. The actual UTXO value of each inscription is verifiable on-chain and serves as the authoritative economic signal.

Trust scoring guidance:

Inscription costs are signals among many. Explorers should combine economic signals with graph analysis, identity age, and attestation patterns when computing trust scores.


9. Size and Cost Reference

DocumentJSON SizeCBOR SizeEst. Cost (USD)
Identity (Ed25519, single key)~280 bytes~170 bytes$1-3
Identity (Ed25519 + Dilithium, multi-key)~3,200 bytes~2,300 bytes$5-15
Identity (with metadata, single key)~450-650 bytes~350-500 bytes$2-5
Attestation~500 bytes~340 bytes$2-5
Attestation Revocation~260 bytes~170 bytes$1-2
Supersession (single key → single key)~550 bytes~380 bytes$2-5
Supersession (adding Dilithium key)~3,400 bytes~2,500 bytes$5-15
Revocation~280 bytes~180 bytes$1-3
Receipt (2 party, Ed25519)~620 bytes~430 bytes$2-5
Heartbeat~180 bytes~110 bytes$0.50-1.50
Publication (inline, small)~500-2,000 bytes~400-1,500 bytes$2-10
Publication (hash-only)~350 bytes~250 bytes$1-3
Publication (max, 512 KB)~512,000 bytes~512,000 bytes$500-2,000

Costs vary with Bitcoin fee rates. CBOR saves ~30-40% vs JSON by encoding binary fields as raw bytes rather than base64url strings. Multi-key identities with post-quantum keys are significantly larger due to PQ key sizes (see §8.5.4).


Appendix A: Base64url Encoding

Binary fields in JSON use unpadded base64url encoding (RFC 4648 §5, URL-safe alphabet, no = padding).

Alphabet: A-Z a-z 0-9 - _

Example (32 bytes → 43 characters):

Input:  [0x3b, 0x6a, 0x27, 0xbc, 0xce, 0xb6, 0xa4, 0x2d, ...]
Output: "O2onvM62pC1io6jQKm8Nc2UyFXcd4kOmOsBIoYtZ2ik"

Appendix B: CBOR Notes

CBOR examples use diagnostic notation (RFC 8949 §8):

  • h'...' — Byte string in hexadecimal
  • Strings in quotes are text strings
  • Numbers are integers

Binary fields in CBOR use byte strings (major type 2), not base64url text.

Appendix C: CAIP-2 Reference

CAIP-2 (Chain Agnostic Improvement Proposal 2) defines a standard way to identify blockchain networks. The format is:

<namespace>:<reference>

For Bitcoin-family chains, the namespace is bip122 and the reference is the first 16 bytes (32 hex chars) of the genesis block hash.

ChainGenesis Block Hash (first 32 hex chars)CAIP-2 Identifier
Bitcoin mainnet000000000019d6689c085ae165831e93bip122:000000000019d6689c085ae165831e93
Bitcoin testnet3000000000933ea01ad0ee984209779babip122:000000000933ea01ad0ee984209779ba
Bitcoin signet00000008819873e925422c1ff0f99f7cbip122:00000008819873e925422c1ff0f99f7c

ATP uses CAIP-2 identifiers in ref.net fields to specify which network a document is inscribed on. This makes ATP documents portable across chains and allows future expansion to non-Bitcoin platforms without protocol changes.

For Bitcoin, ref.id is always an inscription TXID in display format (64 hex characters).

Appendix D: TypeScript Interfaces

The following TypeScript interfaces provide formal type definitions for all ATP document types. These are informative — the normative definitions are in §2–§3.

typescript
// ─── Common Types ───────────────────────────────────────────

/** Key type codes supported by ATP */
type KeyType = "ed25519" | "secp256k1" | "dilithium" | "falcon";

/** A public key within an identity's key set */
interface KeyObject {
  /** Key type code */
  t: KeyType;
  /** Public key bytes (base64url in JSON, byte string in CBOR) */
  p: Uint8Array;
}

/** CAIP-2 location reference */
interface LocationRef {
  /** CAIP-2 chain identifier (e.g., "bip122:000000000019d6689c085ae165831e93") */
  net: string;
  /** Platform-specific document identifier (e.g., inscription TXID) */
  id: string;
}

/** Identity reference: fingerprint + location */
interface IdentityRef {
  /** Identity fingerprint (from k[0]) */
  f: Uint8Array;
  /** Location of the identity document */
  ref: LocationRef;
}

/** Structured signature identifying the signing key */
interface Signature {
  /** Fingerprint of the key that produced the signature */
  f: Uint8Array;
  /** Signature bytes */
  sig: Uint8Array;
}

/** Structured metadata: collections of [key, value] tuples */
type Metadata = Record<string, [string, string][]>;

// ─── Document Types ─────────────────────────────────────────

/** Identity document (§2.1) */
interface IdentityDocument {
  v: "1.0";
  t: "id";
  /** Agent name (1–64 chars, [a-zA-Z0-9 _\-.]) */
  n: string;
  /** Key set (1+ keys, no duplicates; k[0] is primary) */
  k: KeyObject[];
  s: Signature;
  /** Created timestamp (Unix seconds, optional, informational) */
  ts?: number;
  /** Structured metadata (optional) */
  m?: Metadata;
  /** Valid-not-after: identity expiry (Unix seconds, optional). See §5.7. */
  vna?: number;
}

/** Attestation document (§3.1) */
interface AttestationDocument {
  v: "1.0";
  t: "att";
  /** Attestor identity reference */
  from: IdentityRef;
  /** Attestee identity reference */
  to: IdentityRef;
  s: Signature;
  ts?: number;
  /** Context/reason for endorsement (optional) */
  ctx?: string;
  /** Valid-not-after: attestation expiry (Unix seconds, optional). See §5.7. */
  vna?: number;
}

/** Party in a receipt */
interface Party {
  /** Identity fingerprint (from k[0]) */
  f: Uint8Array;
  /** Location of the party's identity document */
  ref: LocationRef;
  /** Role in exchange */
  role: string;
}

/** Exchange details in a receipt */
interface Exchange {
  /** Exchange type */
  type: string;
  /** Summary */
  sum: string;
  /** Value in sats (optional) */
  val?: number;
}

/** Receipt outcome */
type Outcome = "completed" | "partial" | "cancelled" | "disputed";

/** Receipt document (§3.2) */
interface ReceiptDocument {
  v: "1.0";
  t: "rcpt";
  /** Parties (2+, unique fingerprints) */
  p: Party[];
  /** Exchange details */
  ex: Exchange;
  /** Outcome */
  out: Outcome;
  /** Signatures (same length as p; s[i] from p[i]) */
  s: Signature[];
  ts?: number;
}

/** Supersession reason */
type SupersessionReason =
  | "key-rotation"
  | "algorithm-upgrade"
  | "key-compromised"
  | "metadata-update"
  | "key-addition"
  | "key-removal";

/** Supersession document (§3.3) — also serves as the new identity */
interface SupersessionDocument {
  v: "1.0";
  t: "super";
  /** Identity being superseded */
  target: IdentityRef;
  /** New agent name */
  n: string;
  /** New key set */
  k: KeyObject[];
  /** Reason for supersession */
  reason: SupersessionReason;
  /** [old identity sig, new identity sig] */
  s: [Signature, Signature];
  ts?: number;
  m?: Metadata;
  /** Valid-not-before: scheduled rollover (Unix seconds, optional). See §5.7. */
  vnb?: number;
  /** Valid-not-after: expiry for the resulting key set (Unix seconds, optional). See §5.7. */
  vna?: number;
}

/** Revocation reason */
type RevocationReason = "key-compromised" | "defunct";

/** Revocation document (§3.4) */
interface RevocationDocument {
  v: "1.0";
  t: "revoke";
  /** Identity being revoked */
  target: IdentityRef;
  /** Reason for revocation */
  reason: RevocationReason;
  /** Signature from ANY key in the supersession chain */
  s: Signature;
  ts?: number;
  /** Valid-not-before: scheduled revocation (Unix seconds, optional). See §5.7. */
  vnb?: number;
}

/** Attestation revocation reason */
type AttestationRevocationReason = "retracted" | "fraudulent" | "expired" | "error";

/** Attestation revocation document (§3.5) */
interface AttestationRevocationDocument {
  v: "1.0";
  t: "att-revoke";
  /** Location reference to the attestation being revoked */
  ref: LocationRef;
  /** Reason for revocation */
  reason: AttestationRevocationReason;
  /** Signature from attestor's current key set */
  s: Signature;
  ts?: number;
}

/** Heartbeat document (§3.6) */
interface HeartbeatDocument {
  v: "1.0";
  t: "hb";
  /** Identity fingerprint (k[0] fingerprint) */
  f: Uint8Array;
  /** Location of the signer's identity document */
  ref: LocationRef;
  /** Sequence number (monotonically increasing from 0) */
  seq: number;
  s: Signature;
  ts?: number;
  /** Optional status message */
  msg?: string;
}

/** Publication content object (§3.7) */
interface PublicationContent {
  /** MIME type (e.g., "text/markdown", "application/octet-stream") */
  type: string;
  /** Category or subject (optional) */
  topic?: string;
  /** Inline content — string for text, Uint8Array for binary (optional) */
  body?: string | Uint8Array;
  /** SHA-256 hash of off-chain content (hex-encoded, optional) */
  hash?: string;
  /** Retrieval hint for off-chain content (optional, not authoritative) */
  uri?: string;
  /** Encryption scheme identifier (optional — absent = plaintext) */
  enc?: string;
}

/** Publication document (§3.7) */
interface PublicationDocument {
  v: "1.0";
  t: "pub";
  /** Publisher identity reference */
  from: IdentityRef;
  /** Content payload */
  content: PublicationContent;
  /** Intended recipients for encrypted content (optional) */
  to?: IdentityRef[];
  s: Signature;
  ts?: number;
}

/** Union of all ATP document types */
type ATPDocument =
  | IdentityDocument
  | AttestationDocument
  | ReceiptDocument
  | SupersessionDocument
  | RevocationDocument
  | AttestationRevocationDocument
  | HeartbeatDocument
  | PublicationDocument;

End of specification.

Released under the MIT License.