Help us improve
Share bugs, ideas, or general feedback.
From atproto-skills
This 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`.
npx claudepluginhub ngerakines/atproto-skills --plugin atproto-skillsHow this skill is triggered — by the user, by Claude, or both
Slash command
/atproto-skills:atproto-cidThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Content Identifiers (CIDs) are the content-addressed hashes that bind every record, blob, and commit in AT Protocol to its exact bytes. This skill routes to per-language guides for Rust, TypeScript, and Go, sitting on top of a single language-neutral spec in `shared/`.
go/README.mdgo/codecs.mdgo/construction.mdgo/parsing.mdrust/README.mdrust/codecs.mdrust/construction.mdrust/parsing.mdscripts/validate_cid.pyshared/binary-layout.mdshared/divergence-matrix.mdshared/spec.mdshared/test-vectors.mdtypescript/README.mdtypescript/codecs.mdtypescript/construction.mdtypescript/parsing.mdThis skill should be used when the user is constructing, parsing, exporting, importing, or signature-validating an AT Protocol user repository in Rust, TypeScript, or Go — the durable per-account record store represented as a Merkle Search Tree inside a signed commit, serialized on the wire as a CAR v1 file. Triggers on phrases like "CAR file", "CAR v1", "car export", "com.atproto.sync.getRepo", "com.atproto.sync.getBlocks", "listMissingBlocks", "subscribeRepos", "repo export", "MST", "Merkle Search Tree", "MST node", "tree entry", "left subtree", "fanout", "prefix compression", "key height", "repo commit", "commit signature", "sig field", "signing bytes", "UnsignedCommit", "commit version 3", "rev TID", "prev commit", "DAG-CBOR", "DRISL", "canonical CBOR", "map key ordering", "tag 42", "block store", "block CID mismatch", "verify repo", "inductive verification", "prevData", "record CID in MST", "at://<did>/<collection>/<rkey>". Also triggers on dependency/import names like `atproto-repo`, `atproto-dasl`, `atproto-record`, `@atproto/repo`, `@atproto/lex-cbor`, `@atproto/lex-data`, `@atproto/crypto`, `indigo/atproto/repo`, `indigo/atproto/repo/mst`, `indigo/atproto/atdata`, `github.com/ipld/go-car`, `go-block-format`, `cbor-gen`, or API names like `LoadRepoFromCAR`, `VerifyCommitSignatureFromCar`, `VerifyCommitMessage`, `readCar`, `readCarWithRoot`, `verifyRepoCar`, `verifyDiffCar`, `signCommit`, `verifyCommitSig`, `Mst::insert_recursive`, `Tree.RootCID`, `MST.create`, `MST.load`, `DataDiff.of`. Use this skill to implement a repo reader, a repo writer, a commit signer/verifier, an MST diff tool, a CAR streaming parser, a firehose consumer, or to debug failures like "CID mismatch", "unknown codec in CAR", "MST node not found", "prefix_len exceeds previous key", "signature invalid", "prev null vs absent", "partial tree", "missing block", or "inverted tree root didn't match prevData". Covers DRISL (canonical DAG-CBOR) encoding rules, CAR v1 byte framing, MST structure (SHA-256 fanout 4, sorted entries, prefix compression, left subtree pointer), commit record fields and signing bytes, signature verification (including the `prev: null` vs omitted cross-implementation divergence), record placement (NSID + rkey), and cross-language interop. Does NOT cover CID parsing/construction (see `atproto-cid`), DID resolution / locating the signing key (see `atproto-identity-resolution`), lexicon-level record validation or XRPC method invocation or typed `$type` record dispatch (see `atproto-lexicon`), or OAuth (see `atproto-oauth`). Bluesky-domain record idioms (richtext facets, embeds) are out of scope for this plugin entirely.
Guides building on AT Protocol (atproto/Bluesky): authoring Lexicons, app views, firehose consumption, DIDs/handles, repositories, records, XRPC endpoints, OAuth.
Guides correct BosStr usage, type parameterization, and string allocation patterns in Jacquard Rust AT Protocol library to prevent common errors.
Share bugs, ideas, or general feedback.
Content Identifiers (CIDs) are the content-addressed hashes that bind every record, blob, and commit in AT Protocol to its exact bytes. This skill routes to per-language guides for Rust, TypeScript, and Go, sitting on top of a single language-neutral spec in shared/.
A DASL CID is always a CIDv1 with codec raw (0x55) or dag-cbor (0x71), hash SHA-256 (0x12), 32-byte digest, base32lower string form with a leading b, 36-byte binary form. The BDASL extension permits BLAKE3 (0x1e) for large-file blobs. Anything outside that set is rejected — DASL is a strict subset of multiformats CIDs, not an alias.
Full normative rules: shared/spec.md. Byte-level diagrams: shared/binary-layout.md. Fixtures: shared/test-vectors.md. Cross-language differences: shared/divergence-matrix.md.
Before generating or reviewing any CID code, determine the target language from project files or the file being edited:
Cargo.toml, *.rs, rust-toolchain.toml, any mention of atproto-dasl / cid / libipld → Rust — read from rust/.package.json, tsconfig.json, *.ts, *.tsx, imports of multiformats / @ipld/dag-cbor → TypeScript — read from typescript/. Also *.js/*.jsx when there is no .ts present in the repo.go.mod, *.go, imports of github.com/ipfs/go-cid → Go — read from go/.Prefer the file being edited over the repo root when they disagree: a .ts client inside a Rust-workspace monorepo still means TypeScript for that task.
If multiple languages are present and the task does not point at one unambiguously, ask which one applies. Never mix CID libraries across languages in generated code.
If an unsupported language is detected (Python, Java, Elixir, Swift, …), point the user at shared/spec.md for the binary format and offer Rust as a reference implementation to transliterate from. Name this out explicitly — it converts a dead end into a partial success.
For every CID task:
shared/spec.md first. It is short and defines the rules your code must enforce.{lang}/parsing.md(codec, digest) → {lang}/construction.md{lang}/codecs.md{lang}/README.mdshared/divergence-matrix.md when porting between languages or reviewing cross-stack interop.Always prefer the official library (atproto-dasl in Rust, multiformats in TypeScript, go-cid in Go) over hand-rolling. Never guess function names — read the relevant {lang}/*.md file, and if a detail is missing, fetch the live docs (docs.rs, npm, pkg.go.dev) rather than inventing.
b<base32lower(36_byte_cid)>
Leading b signals multibase base32lower (RFC 4648, lowercase, no padding). The encoded 36-byte CID comes out to 58 characters, for a total string length of 59. The first seven string characters are fully determined by the CID header, which gives you a fast sniff test:
| Header bytes | Codec meaning | String prefix |
|---|---|---|
01 71 12 20 … | dag-cbor + SHA-256 | bafyrei… |
01 55 12 20 … | raw + SHA-256 | bafkrei… |
01 71 1e 20 … | dag-cbor + BLAKE3 (BDASL only) | see shared/binary-layout.md |
01 55 1e 20 … | raw + BLAKE3 (BDASL only) | see shared/binary-layout.md |
In practice: bafyrei… means record/MST node; bafkrei… means blob. Anything else is not a DASL SHA-256 CID.
36 bytes flat: version(1) || codec(1) || hash_code(1) || digest_length(1) || digest(32). No tag, no length prefix. This is what goes into CAR block frames and what you hash-compare.
Inside DAG-CBOR, a CID is emitted as CBOR tag 42 wrapping a byte string whose first byte is the identity multibase prefix (0x00) followed by the 36 binary CID bytes — 37 bytes total inside the byte string. Encoders in all three languages do this automatically; hand-rolling it is a footgun.
$link formIn AT Protocol JSON (lexicon records, XRPC responses), CIDs appear as:
{"$link": "bafyreihunttf7a3uvtzrgbnyu2rzv24w4zx7xjwqgk4x5w7n5yvq7u7aua"}
A bare string in a CID-typed field is invalid — reject records that inline it.
Keep these two concepts separate in your code — conflating them is the most common bug in a new CID implementation.
Draw from shared/spec.md §7 (validation order — each step is the rejection condition for the byte it inspects), shared/binary-layout.md, and shared/divergence-matrix.md. The high-impact ones:
0x00 identity multibase prefix inside DAG-CBOR tag-42 wrapping — produces a CID that other implementations silently reject.Qm… accepted by permissive libraries (TypeScript and Go in particular). Always re-validate against the DASL subset after parsing.0x70) with dag-cbor (0x71) — one codec byte apart. dag-pb is IPFS-only and is not a DASL codec.z…) or base64 (m…) prefixes — valid multibase CIDs but not DASL. Reject.await sha256.digest(bytes) into a sync helper. Every caller that wants to build a CID from bytes becomes async. See typescript/construction.md.shared/divergence-matrix.md §codec-constants.cid.bytes (property), not cid.toBytes(). Rust and Go use methods. Port-and-paste hazard.{"$link": "..."}, not a bare string. The DASL spec itself does not define a JSON encoding; this is an AT Protocol convention.raw vs dag-cbor? dag-cbor for structured records (the vast majority). raw for opaque binary blobs referenced from records.Prefer these MCP tools to writing new code when the goal is to compute or validate a CID rather than teach an implementation how:
lexicon-garden → create_record_cid(record_json), transmogrify_record, invoke_xrpc (for com.atproto.sync.getBlob and friends).atpmcp → create_record_cid, get_record, transmogrify_record against a local PDS.These are authoritative within the AT Protocol ecosystem. Use them to generate expected values for cross-language test vectors.
For a lightweight local sanity check against the DASL subset (no deps, no network, no hashing), run:
python3 scripts/validate_cid.py <cid-string> [--bdasl]
Exit 0 = valid DASL string, 1 = rejected with reason on stdout, 2 = usage error. Useful for quick CI checks or piping many candidate CIDs through xargs. Does not verify content — it only parses the 4-byte header and digest length against the allowed constants.
atproto-cid/
├── SKILL.md # this file — router
├── shared/
│ ├── spec.md # normative DASL rules
│ ├── binary-layout.md # byte-level diagrams
│ ├── test-vectors.md # fixtures
│ └── divergence-matrix.md # cross-language differences
├── rust/
│ ├── README.md # crate setup, idioms
│ ├── parsing.md # Cid / DaslCid parse paths
│ ├── construction.md # compute_cid, new_v1 paths
│ └── codecs.md # codec constants, BLAKE3
├── typescript/
│ ├── README.md # multiformats + @ipld/dag-cbor setup
│ ├── parsing.md # CID.parse / decode + DASL gate
│ ├── construction.md # async sha256 + CID.createV1
│ └── codecs.md # per-codec packages
├── go/
│ ├── README.md # go-cid + go-multihash setup
│ ├── parsing.md # cid.Decode / Cast + DASL gate
│ ├── construction.md # Prefix.Sum / NewCidV1
│ └── codecs.md # shipped constants (cid.DagCBOR, cid.Raw)
└── scripts/
└── validate_cid.py # stdlib-only DASL subset check
Everything below is reachable from the directory tree above. Listed here for quick grep:
shared/spec.mdshared/binary-layout.mdshared/test-vectors.mdshared/divergence-matrix.mdrust/README.md, rust/parsing.md, rust/construction.md, rust/codecs.mdtypescript/README.md, typescript/parsing.md, typescript/construction.md, typescript/codecs.mdgo/README.md, go/parsing.md, go/construction.md, go/codecs.md