Help us improve
Share bugs, ideas, or general feedback.
From atproto-skills
This skill should be used when the user is implementing, debugging, or reasoning about AT Protocol record attestations per the badge.blue specification — inline (embedded ECDSA signature) or remote (content-addressed strongRef to a proof record) — in Rust, TypeScript, or Go. Triggers on phrases like "sign an atproto record", "inline attestation", "remote attestation", "badge.blue", "attest a record", "record signatures array", "`$sig` metadata", "content CID for signing", "com.atproto.repo.strongRef in signatures", "proof record", "verify an attestation", "attestation CID mismatch", "low-S signature", "ECDSA r‖s", "IEEE P1363", "signature normalization", "did:key signing", "replay protection", "cross-repo replay", "signatures[] append", as well as error strings from the reference crate like `error-atproto-attestation-*`, `UnsupportedKeyType`, `RemoteAttestationCidMismatch`, `SignatureValidationFailed`. Also triggers on dependency/import names `atproto-attestation`, `atproto-identity`, `atproto-client`, `k256`, `p256`, `@noble/curves`, `@ipld/dag-cbor`, `multiformats/cid`, `dcrec/secp256k1`, `go-ipld-prime`, `go-cid`, and references to the reference Rust crate at `/crates/atproto-attestation` in ngerakines.me/atproto-crates. Covers the CID-first signing model (DAG-CBOR of `record + $sig(repository)` → SHA-256 → CIDv1 → sign), low-S normalization for P-256 and K-256, the P-384 normalization gap in the reference crate, the two-CID distinction in remote attestations (content CID inside proof record vs proof record's DAG-CBOR CID inside strongRef), and per-language library choices. Use this skill to implement, port, or audit attestation code in SDKs, tools, or applications. Does NOT cover general atproto record parsing or XRPC invocation (see `atproto-lexicon`), DID/handle resolution internals (see `atproto-identity-resolution`), CAR/MST/commit signing (see `atproto-repository`), OAuth token flows (see `atproto-oauth`), or CID parsing/construction for non-attestation records (see `atproto-cid`).
npx claudepluginhub ngerakines/atproto-skills --plugin atproto-skillsHow this skill is triggered — by the user, by Claude, or both
Slash command
/atproto-skills:atproto-attestationThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
CID-first record attestations as specified at <https://badge.blue/> and implemented in the `atproto-attestation` Rust crate. This skill routes to per-language guides for Rust, TypeScript, and Go, sitting on top of a language-neutral spec in `shared/`.
go/README.mdgo/creating.mdgo/signatures.mdgo/verifying.mdrust/README.mdrust/creating.mdrust/signatures.mdrust/verifying.mdshared/cid-computation.mdshared/divergence-matrix.mdshared/inline-attestation.mdshared/remote-attestation.mdshared/signature-normalization.mdshared/spec.mdshared/test-vectors.mdtypescript/README.mdtypescript/creating.mdtypescript/signatures.mdtypescript/verifying.mdThis skill should be used when the user is working with AT Protocol or DASL CIDs (Content Identifiers) in Rust, TypeScript, or Go — parsing, constructing, validating, verifying, or debugging them. Triggers on phrases like "parse a CID", "compute a CID for this record", "verify a blob CID", "why is this CID invalid", "CID mismatch", "tag 42 in DAG-CBOR", "identity multibase prefix", "the $link format", "base32lower", "digest length error", "what does the b prefix mean", "CIDv1 vs CIDv0", "bafyrei / bafkrei prefix", "dag-cbor codec", "multihash", "BLAKE3 CID", "BDASL". Also triggers on dependency/import names like `atproto-dasl`, `cid`, `multihash-codetable`, `libipld`, `multiformats`, `@ipld/dag-cbor`, `CID.parse`, `CID.create`, `github.com/ipfs/go-cid`, `go-multihash`, or lockfile names `Cargo.toml`, `package.json`, `pnpm-lock.yaml`, `go.mod`, `go.sum`. Covers the strict DASL CID profile (CIDv1 only, codec raw 0x55 or dag-cbor 0x71, hash SHA-256 0x12, 32-byte digest, base32lower string form with 'b' prefix, 36-byte binary form), the BDASL extension permitting BLAKE3 (0x1e) for large-file content, the DAG-CBOR wire form (CBOR tag 42 plus identity multibase 0x00 prefix), and the AT Protocol JSON `{"$link": "..."}` form. Use this skill to implement CID support in SDKs, clients, servers, firehose consumers, repo tooling, or diagnostic scripts in any of the three supported languages; for an unsupported language point at `shared/spec.md` and a reference implementation. Does NOT cover general IPFS / multiformats CID questions (DASL is a strict subset — reject anything outside the allowed constants); for MST tree traversal, CAR inspection, or DAG-CBOR canonicalization beyond the CID tag see `atproto-repository`.
Guides building on AT Protocol (atproto/Bluesky): authoring Lexicons, app views, firehose consumption, DIDs/handles, repositories, records, XRPC endpoints, OAuth.
Signs AI agent actions with cryptographic identity (DIDs, Verifiable Credentials) using Vouch Protocol's shared Rust core. Provides Python, TypeScript, Go SDKs for agent identity, intent attestation, and post-quantum proof support.
Share bugs, ideas, or general feedback.
CID-first record attestations as specified at https://badge.blue/ and implemented in the atproto-attestation Rust crate. This skill routes to per-language guides for Rust, TypeScript, and Go, sitting on top of a language-neutral spec in shared/.
An attestation binds a cryptographic or content-addressed claim to a specific record in a specific repository:
0x71 (dag-cbor), hash 0x12 (SHA-256), 32-byte digest, 36-byte binary form. Computed over a canonical merge of record (without signatures) + $sig metadata (without cid/signature, with repository inserted).record.signatures[] alongside the metadata.signatures[] carries a com.atproto.repo.strongRef pointing at the proof record.Full normative rules: shared/spec.md. Step-by-step CID procedure: shared/cid-computation.md. Inline flow: shared/inline-attestation.md. Remote flow: shared/remote-attestation.md. Signature normalization: shared/signature-normalization.md. Fixtures: shared/test-vectors.md. Cross-language differences: shared/divergence-matrix.md.
Before generating or reviewing any attestation code, determine the target language from project files:
Cargo.toml, *.rs, any mention of atproto-attestation / atproto-identity / k256 / p256 → Rust — read from rust/. The reference crate lives here.package.json, tsconfig.json, *.ts, *.tsx, imports of @noble/curves / @ipld/dag-cbor / multiformats → TypeScript — read from typescript/.go.mod, *.go, imports of github.com/ipfs/go-cid / github.com/ipld/go-ipld-prime / github.com/decred/dcrd/dcrec → Go — read from go/.Prefer the file being edited over the repo root when they disagree.
If multiple languages are present and the task does not point at one unambiguously, ask which one applies. Never mix attestation libraries across languages in generated code.
For an unsupported language (Python, Java, Elixir, Swift, …), point the user at shared/spec.md and the Rust crate as the reference implementation to transliterate from.
For every attestation task:
shared/spec.md for the normative rules (terminology, record shapes, verification order, known gaps).shared/cid-computation.md. Every non-trivial implementation bug in this space is at this step.shared/inline-attestation.md.shared/remote-attestation.md.shared/signature-normalization.md.{lang}/README.md{lang}/creating.md{lang}/verifying.md{lang}/signatures.mdshared/divergence-matrix.md when porting between languages or reviewing cross-stack interop.Always prefer the reference crate (Rust) or the primitive libraries called out in each {lang}/README.md over hand-rolling. Never guess function names — read the relevant file, and if a detail is missing, consult live docs (docs.rs, npm, pkg.go.dev) rather than inventing.
Neither the raw record nor the metadata alone — the ECDSA signature is over the 36-byte binary content CID of a canonical merge:
record' = record without `signatures`
meta' = metadata without `cid` / `signature`, with `repository` inserted
merged = record' ∪ { "$sig": meta' }
content_cid = CIDv1(dag-cbor, SHA-256(DAG-CBOR(merged)))
signature = ECDSA_sign(private_key, content_cid.bytes)
repository participates in the CID but NOT in the stored attestation. This gives replay protection: the same record + metadata signed for did:plc:A produces a different CID than for did:plc:B, so a signature made for one repo cannot be reused in another.
| Trait | Inline | Remote |
|---|---|---|
| Cryptographic | Yes — ECDSA signature in the record | No — integrity by content-address + strongRef |
| Records involved | 1 (subject only) | 2 (subject + proof, usually in different repos) |
signatures[] entry | metadata object with cid and signature.$bytes | com.atproto.repo.strongRef with uri + cid |
| Verifier needs | public key resolution | record resolver (AT-URI fetch) |
| Revocation | immutable once signed | delete proof record → unreachable |
shared/inline-attestation.md and shared/remote-attestation.md cover each in full.
Remote attestations involve two distinct CIDs and confusing them is the most common bug:
| CID | Where it's stored | What it identifies |
|---|---|---|
| Content CID | Inside the proof record's cid field | The signing payload (record + $sig) |
| Proof CID | Inside the strongRef's cid field | The proof record itself (plain DAG-CBOR) |
Always verify both. See shared/remote-attestation.md §two-CIDs.
| Curve | Reference crate (Rust) | TypeScript (@noble/curves) | Go (crypto/ecdsa + dcrec) |
|---|---|---|---|
| P-256 | ✅ full | ✅ full | ✅ full (low-S manual) |
| K-256 | ✅ full | ✅ full | ✅ full (dcrec low-S default) |
| P-384 | ⚠️ normalize_signature returns UnsupportedKeyType | ✅ full, but interop broken | ✅ full, but interop broken |
Interop rule: use P-256 or K-256 only. P-384 does not round-trip through the reference crate. See shared/signature-normalization.md §curve-coverage.
Separate these steps in your code. Keep policy checks (is this issuer authorized? is the attestation fresh?) outside — they're application concerns above the crypto.
Draw from each shared/*.md §common-mistakes and shared/divergence-matrix.md. The high-impact ones:
bafyrei…) instead of the 36-byte binary CID. Silent interop break — implementations that hash cid.bytes don't verify signatures made over the string form.repository in the stored attestation. It's only a transient input to CID computation; it must not appear in signatures[] entries. Reference implementations strip it; hand-rolled ones often don't.signatures from the record before CID computation. Every new signature re-signs a stripped version, so all prior signatures stay valid. Skip the strip and all signatures invalidate each other.cid / signature from metadata before CID computation. These fields are outputs, not inputs. Including them on re-verify produces a different CID.r‖s (64 bytes for P-256/K-256). Convert if needed.signature.$bytes. Spec uses standard base64 (+// + padding). URL-safe (-/_) decodes to different bytes for 62/63 code points.fxamacker/cbor without CoreDetEncOptions (Go), hand-rolled CBOR, or generic CBOR libraries produce different bytes than DAG-CBOR. Use strict DAG-CBOR libraries only.cid? The reference crate does not; the TS and Go guides in this skill do by default. Pass verifyProofCid: false / VerifyProofCid: false to match reference semantics exactly.repository come from at verify time? The DID of the repo you fetched the record from. Hardcoding it or using a stale value silently invalidates every inline signature.signatures stripped, so all prior signatures remain valid when new ones are appended.Prefer these MCP tools when the goal is to compute or validate CIDs as part of a composite workflow rather than teach an implementation how:
lexicon-garden → create_record_cid(record_json) for the underlying DAG-CBOR CID primitive, invoke_xrpc for com.atproto.repo.putRecord / getRecord.atpmcp → create_record_cid, get_record, invoke_xrpc against a local PDS for development.Note: neither MCP currently exposes a badge.blue-specific create_attestation or verify_attestation tool — the content-CID pipeline (record + $sig(repository)) is attestation-specific and must be implemented per-language as described in the guides. The MCPs are useful for the raw CID step inside that pipeline and for publishing the resulting records.
atproto-attestation/
├── SKILL.md # this file — router
├── shared/
│ ├── spec.md # normative attestation rules
│ ├── cid-computation.md # content-CID procedure (bit-exact)
│ ├── inline-attestation.md # inline flow + record shape
│ ├── remote-attestation.md # remote flow + two-CID model
│ ├── signature-normalization.md # low-S rules, P-384 gap
│ ├── test-vectors.md # fixtures catalog
│ └── divergence-matrix.md # cross-language differences
├── rust/
│ ├── README.md # reference crate setup, API map
│ ├── creating.md # create_inline / create_remote / append
│ ├── verifying.md # verify_record + resolver impls
│ └── signatures.md # sign / validate / normalize_signature
├── typescript/
│ ├── README.md # library stack + idioms
│ ├── creating.md # computeContentCid + create flows
│ ├── verifying.md # verifier + resolver impls
│ └── signatures.md # noble curves + low-S
└── go/
├── README.md # stdlib + dcrec + go-ipld-prime
├── creating.md # ComputeContentCID + create flows
├── verifying.md # VerifyRecord + resolvers
└── signatures.md # stdlib ecdsa + dcrec + P1363
shared/spec.md, shared/cid-computation.md, shared/inline-attestation.md, shared/remote-attestation.md, shared/signature-normalization.md, shared/test-vectors.md, shared/divergence-matrix.mdrust/README.md, rust/creating.md, rust/verifying.md, rust/signatures.mdtypescript/README.md, typescript/creating.md, typescript/verifying.md, typescript/signatures.mdgo/README.md, go/creating.md, go/verifying.md, go/signatures.md