Verify a record →

badge.blue

A CID-first attestation workflow for AT Protocol records — inline and remote cryptographic signatures with repository-bound replay-attack prevention.

Abstract

This specification defines how any party may attach cryptographic attestations to an AT Protocol record. Attestations travel either inline (ECDSA signatures embedded in the record's signatures array) or remotely (as separate proof records referenced by com.atproto.repo.strongRef). Both forms share a deterministic, repository-bound CID-generation process that makes the signing payload stable, independent of how many signatures have been attached, and non-replayable across repositories.

§ 1Overview

This document specifies a CID-first attestation workflow for records in the AT Protocol. It enables any party to attach cryptographic attestations to records, asserting claims such as authorship verification, content approval, or third-party endorsement.

Attestations take one of two forms:

  • Inline attestations — ECDSA signatures embedded directly in the record's signatures array.
  • Remote attestations — separate proof records, referenced via com.atproto.repo.strongRef, stored in the attestor's repository.

Both forms share a common CID-generation process that deterministically binds the attestation to a specific record, metadata, and repository DID.

Every attestation is bound to a record, a metadata object, and a repository DID. Change any of the three, and the CID — and therefore every signature over it — is invalidated.

§ 2Core concepts

Content identifiers

Every attestation is rooted in a CID — a content-addressed identifier computed from the record, the attestation metadata, and the repository DID. The CID serves as the signing payload for inline attestations, and as the binding reference for remote attestations.

Repository binding

The repository DID is injected into the $sig metadata before CID generation. Consequently, the same record in different repositories produces different CIDs — preventing replay attacks in which a signed record is copied between repositories.

Signatures array

Attestations are stored in a signatures array at the top level of the record. Each entry is either an inline attestation object or a com.atproto.repo.strongRef. The signatures array must be stripped from the record before CID computation, so adding attestations does not change the signing payload.

§ 3CID generation

CID generation is the foundation of the attestation system. Given a record, a metadata object, and a repository DID, an implementation must perform the following procedure in order:

  1. Start with the record as a JSON object. Remove any existing signatures array.
  2. Prepare the metadata object. Remove the cid and signature fields if present.
  3. Add a repository field to the metadata, set to the repository DID.
  4. Insert the prepared metadata into the record as the $sig field.
  5. Serialize the entire object to DAG-CBOR (deterministic CBOR).
  6. Compute a SHA-256 hash over the DAG-CBOR bytes.
  7. Wrap the hash as a CIDv1 with DAG-CBOR codec.

CID parameters

ParameterValue
CID versionCIDv1
CodecDAG-CBOR (0x71)
Hash algorithmSHA-256 (multihash code 0x12)
Hash length32 bytes
String encodingBase32lower (prefix bafy)

Identical record + metadata + repository inputs always produce the same CID. Different repository DIDs produce different CIDs for the same record content.

§ 4The $sig metadata object

$sig is a transient object inserted into the record during CID computation. It is not persisted in the final record — its contents are carried in the attestation entries within the signatures array instead.

$type
Attestation type identifier, e.g. com.example.signature. required
repository
Repository DID housing the record. Added automatically during CID generation. auto
key
Public-key reference (e.g., did:key:z…) for inline attestations. varies
custom
Any additional fields (issuer, issuedAt, purpose, …) are preserved and included in CID calculation. optional

During CID generation, the cid and signature fields are stripped from the metadata before it is inserted as $sig. This lets a single metadata object serve double duty: the full version lives in the signatures array, while the stripped version drives CID computation.

§ 5Inline attestations

An inline attestation embeds ECDSA signature bytes directly in the record. The signer computes the CID and signs the CID bytes with their private key. The signature must be normalized to low-S form before encoding.

Creation workflow

  1. Compute the attestation CID from the record, metadata, and repository DID.
  2. Sign the CID bytes using the private key (ECDSA).
  3. Normalise the signature to low-S form.
  4. Base64-encode the signature bytes (standard alphabet, with padding).
  5. Build the attestation entry with the metadata fields, cid, and signature.
  6. Append the entry to the record's signatures array.
Inline attestation entry
{
  "$type": "com.example.inlineSignature",
  "key": "did:key:zQ3sh...",
  "issuer": "did:plc:issuer123",
  "issuedAt": "2024-01-01T00:00:00.000Z",
  "cid": "bafyrei...",
  "signature": {
    "$bytes": "base64-encoded-normalized-signature"
  }
}

Required fields

$type
Attestation type identifier. required
key
Public-key reference for signature verification. required
cid
The computed attestation CID (base32-encoded CIDv1). auto
signature
Object with $bytes field containing base64-encoded normalized signature. auto

§ 6Remote attestations

A remote attestation creates a separate proof record stored in the attestor's repository. The source record references that proof via a com.atproto.repo.strongRef. This enables third-party attestations without the attestor's private key ever touching the source record.

Creation workflow

  1. Compute the attestation CID from the record, metadata, and source repository DID.
  2. Build the proof record: the metadata object with the computed cid added.
  3. Compute the DAG-CBOR CID of the proof record itself.
  4. Build a com.atproto.repo.strongRef entry with the proof record's AT-URI and CID.
  5. Append the strongRef to the source record's signatures array.
Proof record · stored in the attestor's repository
{
  "$type": "com.example.attestation",
  "issuer": "did:plc:issuer123",
  "purpose": "verification",
  "cid": "bafyrei..."
}
strongRef entry · appended to the source record's signatures
{
  "$type": "com.atproto.repo.strongRef",
  "uri": "at://did:plc:attestor/com.example.attestation/3abc123def",
  "cid": "bafyrei..."
}
$type
com.atproto.repo.strongRef. required
uri
AT-URI pointing to the proof record in the attestor's repository. required
cid
DAG-CBOR CID of the proof record (for content integrity). required

§ 7Multiple attestations

A record may carry any number of attestations in its signatures array. Inline and remote attestations can be freely mixed. Each attestation is independently verifiable and all share the same CID generation logic.

Record with mixed inline and remote attestations
{
  "$type": "app.bsky.feed.post",
  "text": "Hello world!",
  "createdAt": "2024-01-01T00:00:00.000Z",
  "signatures": [
    {
      "$type": "com.example.authorSignature",
      "key": "did:key:zQ3sh...",
      "cid": "bafyrei...",
      "signature": { "$bytes": "..." }
    },
    {
      "$type": "com.atproto.repo.strongRef",
      "uri": "at://did:plc:verifier/com.example.approval/3xyz...",
      "cid": "bafyrei..."
    }
  ]
}

When appending a new attestation to a record that already has signatures, existing attestations should be validated before the new one is added. The signatures array is always stripped before CID computation, so the signing payload is stable regardless of how many attestations are present.

§ 8Verification

Verification iterates over each entry in the signatures array and validates it independently.

Inline attestation verification

  1. Extract the attestation entry from the signatures array.
  2. Rebuild the $sig metadata from the entry: strip cid and signature, then add repository.
  3. Recompute the attestation CID using the record, rebuilt metadata, and repository DID.
  4. Resolve the public key from the key field.
  5. Base64-decode the signature bytes from the $bytes wrapper.
  6. Verify the ECDSA signature against the recomputed CID bytes using the resolved public key.

Remote attestation verification

  1. Extract the com.atproto.repo.strongRef entry from signatures.
  2. Fetch the proof record from the attestor's repository using the AT-URI.
  3. Verify the fetched proof record's DAG-CBOR CID matches the cid in the strongRef.
  4. Rebuild $sig metadata from the proof record: strip cid, add repository.
  5. Recompute the attestation CID using the record, rebuilt metadata, and repository DID.
  6. Verify the recomputed CID matches the cid stored in the proof record.

Verification must use the repository DID where the record is actually stored. If an attacker copies a record to a different repository, the recomputed CID will not match, and verification must fail.

§ 9Security properties

Replay attack prevention

The repository field in $sig metadata binds every attestation to a specific repository DID. Copying a signed record from did:plc:alice to did:plc:mallory invalidates every signature, because the CID changes when the repository DID changes. This is enforced automatically during both creation and verification.

Signature normalization

All ECDSA signatures are normalized to low-S form before encoding. For any ECDSA signature (r, s), if s > n/2 (where n is the curve order), the signature is replaced with (r, n − s). This prevents signature-malleability attacks in which an alternate valid signature is produced without knowing the private key.

Supported key types

CurveUse
P-256 (secp256r1)ECDSA signing and verification
P-384 (secp384r1)ECDSA signing and verification
K-256 (secp256k1)ECDSA signing and verification

Base64 encoding

Signature bytes inside the $bytes wrapper use the standard Base64 alphabet with PKCS #7 padding for encoding. Decoders should accept both padded and unpadded input for maximum compatibility.

Content integrity

Signatures cover CID bytes, not raw record content. Because the CID is a cryptographic hash of the DAG-CBOR-encoded record (including $sig metadata), any modification to the record content, metadata, or repository binding changes the CID and invalidates every signature.

§ 10Quick reference

Signed record — inline form
{
  "$type": "app.bsky.feed.post",
  "text": "Hello world!",
  "createdAt": "2024-01-01T00:00:00.000Z",
  "signatures": [
    {
      "$type": "com.example.inlineSignature",
      "key": "did:key:zQ3shNzMp4oaaQ1gQRz...",
      "issuer": "did:plc:issuer123",
      "issuedAt": "2024-01-01T00:00:00.000Z",
      "cid": "bafyreigw5bqvbz6m3c3zjpqhxwl4nj...",
      "signature": {
        "$bytes": "4pL9s2k7Rm3..."
      }
    }
  ]
}
Signed record — remote form
{
  "$type": "app.bsky.feed.post",
  "text": "Hello world!",
  "createdAt": "2024-01-01T00:00:00.000Z",
  "signatures": [
    {
      "$type": "com.atproto.repo.strongRef",
      "uri": "at://did:plc:attestor/com.example.attestation/3abc...",
      "cid": "bafyreihx7ywnb5ce3f6ol7rv..."
    }
  ]
}
CID-generation input — conceptual shape serialized to DAG-CBOR
{
  "$type": "app.bsky.feed.post",
  "text": "Hello world!",
  "createdAt": "2024-01-01T00:00:00.000Z",
  "$sig": {
    "$type": "com.example.inlineSignature",
    "key": "did:key:zQ3sh...",
    "issuer": "did:plc:issuer123",
    "issuedAt": "2024-01-01T00:00:00.000Z",
    "repository": "did:plc:repo123"
  }
}

Note: signatures is removed, and $sig contains the metadata with repository added and cid/signature removed.

§ 11Interactive example

Walk through a complete remote attestation workflow below. Edit the inputs and click Run to see every intermediate step and the final records that would be written to each repository. The demo performs real DAG-CBOR serialization and SHA-256 hashing in your browser.

i.Compute the attestation CID
1. Strip signatures from the record
2. Prepare metadata — strip cid/signature, add repository
3. Insert metadata as $sig in the record
4. Serialise to DAG-CBOR
5. SHA-256 hash → CIDv1
Attestation CID
ii.Build the proof record
6. Create proof record — metadata + attestation CID
7. Compute proof-record CID — DAG-CBOR → SHA-256 → CIDv1
Proof-record CID
iii.Build the strongRef
8. Create com.atproto.repo.strongRef entry
Output records
Attestor's repository
Source repository