{
"$type": "site.standard.document",
"path": "/",
"publishedAt": "2026-05-25T15:56:58Z",
"site": "at://did:plc:mkqt76xvfgxuemlwlx6ruc3w/site.standard.publication/3khuwc44c2222",
"textContent": "# [zat](https://zat.dev)\n\nAT Protocol building blocks for zig.\n\n<details>\n<summary><strong>this readme is an ATProto record</strong></summary>\n\n> [view in zat.dev's repository](https://at-me.zzstoatzz.io/view?handle=zat.dev)\n\nzat publishes these docs as [`site.standard.document`](https://standard.site) records, signed by its DID.\n\n</details>\n\n## install\n\nrequires zig 0.16+.\n\n```bash\nzig fetch --save https://tangled.org/zat.dev/zat/archive/main\n```\n\nthen in `build.zig`:\n\n```zig\nconst zat = b.dependency(\"zat\", .{}).module(\"zat\");\nexe.root_module.addImport(\"zat\", zat);\n```\n\n## what's here\n\n<details>\n<summary><strong>string primitives</strong> - parsing and validation for atproto identifiers</summary>\n\n- **Tid** - timestamp identifiers (base32-sortable)\n- **Did** - decentralized identifiers\n- **Handle** - domain-based handles\n- **Nsid** - namespaced identifiers (lexicon types)\n- **Rkey** - record keys\n- **AtUri** - `at://` URIs\n\n```zig\nconst zat = @import(\"zat\");\n\nif (zat.AtUri.parse(uri_string)) |uri| {\n const authority = uri.authority();\n const collection = uri.collection();\n const rkey = uri.rkey();\n}\n```\n\n</details>\n\n<details>\n<summary><strong>identity resolution</strong> - resolve handles and DIDs to documents</summary>\n\n```zig\n// handle → DID\nvar handle_resolver = zat.HandleResolver.init(io, allocator);\ndefer handle_resolver.deinit();\nconst did = try handle_resolver.resolve(zat.Handle.parse(\"bsky.app\").?);\ndefer allocator.free(did);\n\n// DID → document\nvar did_resolver = zat.DidResolver.init(io, allocator);\ndefer did_resolver.deinit();\nvar doc = try did_resolver.resolve(zat.Did.parse(\"did:plc:z72i7hdynmk6r22z27h6tvur\").?);\ndefer doc.deinit();\n\nconst pds = doc.pdsEndpoint(); // \"https://...\"\nconst key = doc.signingKey(); // verification method\n```\n\nsupports did:plc (via plc.directory) and did:web. handle resolution via HTTP well-known and DNS TXT.\n\n</details>\n\n<details>\n<summary><strong>CBOR codec</strong> - DAG-CBOR encoding and decoding</summary>\n\n```zig\n// decode\nconst decoded = try zat.cbor.decode(allocator, bytes);\ndefer decoded.deinit();\n\n// navigate values\nconst text = decoded.value.getStr(\"text\");\nconst cid = decoded.value.getCid(\"data\");\n\n// encode (deterministic key ordering)\nconst encoded = try zat.cbor.encodeAlloc(allocator, value);\ndefer allocator.free(encoded);\n```\n\nfull DAG-CBOR support: maps, arrays, byte strings, text strings, integers, floats, booleans, null, CID tags (tag 42). deterministic encoding with sorted keys for signature verification.\n\n</details>\n\n<details>\n<summary><strong>CAR codec</strong> - Content Addressable aRchive parsing with CID verification</summary>\n\n```zig\n// parse with SHA-256 CID verification (default)\nconst parsed = try zat.car.read(allocator, car_bytes);\ndefer parsed.deinit();\n\nconst root_cid = parsed.roots[0];\nfor (parsed.blocks.items) |block| {\n // block.cid_raw, block.data\n}\n\n// skip verification for trusted local data\nconst fast = try zat.car.readWithOptions(allocator, car_bytes, .{\n .verify_block_hashes = false,\n});\n```\n\nenforces size limits (configurable `max_size`, `max_blocks`) matching indigo's production defaults.\n\n</details>\n\n<details>\n<summary><strong>MST</strong> - Merkle Search Tree</summary>\n\n```zig\nvar tree = zat.mst.Mst.init(allocator);\ndefer tree.deinit();\n\ntry tree.put(allocator, \"app.bsky.feed.post/abc123\", value_cid);\nconst found = tree.get(\"app.bsky.feed.post/abc123\");\ntry tree.delete(allocator, \"app.bsky.feed.post/abc123\");\n\n// compute root CID (serialize → hash → CID)\nconst root = try tree.rootCid(allocator);\n```\n\nthe core data structure of an atproto repo. key layer derived from leading zero bits of SHA-256(key), nodes serialized with prefix compression.\n\n</details>\n\n<details>\n<summary><strong>crypto</strong> - signing, verification, key encoding</summary>\n\n```zig\n// JWT verification\nvar token = try zat.Jwt.parse(allocator, token_string);\ndefer token.deinit();\ntry token.verify(public_key_multibase);\n\n// ECDSA signature verification (P-256 and secp256k1)\ntry zat.jwt.verifySecp256k1(hash, signature, public_key);\ntry zat.jwt.verifyP256(hash, signature, public_key);\n\n// multibase/multicodec key parsing\nconst key_bytes = try zat.multibase.decode(allocator, \"zQ3sh...\");\ndefer allocator.free(key_bytes);\nconst parsed = try zat.multicodec.parsePublicKey(key_bytes);\n// parsed.key_type: .secp256k1 or .p256\n// parsed.raw: 33-byte compressed public key\n```\n\nES256 (P-256) and ES256K (secp256k1) with low-S normalization. RFC 6979 deterministic signing. `did:key` construction and multibase encoding.\n\n</details>\n\n<details>\n<summary><strong>repo verification</strong> - full AT Protocol trust chain</summary>\n\n```zig\nconst result = try zat.verifyRepo(io, allocator, \"pfrazee.com\");\ndefer result.deinit();\n\n// result.did, result.signing_key, result.pds_endpoint\n// result.record_count, result.block_count\n// result.commit_verified (signature check passed)\n// result.root_cid_match (MST rebuild matches commit)\n```\n\ngiven a handle or DID, resolves identity, fetches the repo, parses every CAR block with SHA-256 verification, verifies the commit signature, walks the MST, and rebuilds the tree to verify the root CID.\n\n</details>\n\n<details>\n<summary><strong>firehose client</strong> - raw CBOR event stream from relay</summary>\n\n```zig\nvar client = zat.FirehoseClient.init(io, allocator, .{});\ndefer client.deinit();\n\nconst Handler = struct {\n pub fn onEvent(_: *@This(), event: zat.FirehoseClient.Event) void {\n switch (event.header.type) {\n .commit => {\n // event.body.blocks, event.body.ops, ...\n },\n else => {},\n }\n }\n};\nvar handler: Handler = .{};\ntry client.subscribe(&handler);\n```\n\nconnects to `com.atproto.sync.subscribeRepos` via WebSocket. decodes binary CBOR frames into typed events. round-robin host rotation with backoff.\n\n</details>\n\n<details>\n<summary><strong>jetstream client</strong> - typed JSON event stream</summary>\n\n```zig\nvar client = zat.JetstreamClient.init(io, allocator, .{\n .wanted_collections = &.{\"app.bsky.feed.post\"},\n});\ndefer client.deinit();\n\nconst Handler = struct {\n pub fn onEvent(_: *@This(), event: zat.JetstreamClient.Event) void {\n if (event.commit) |commit| {\n const record = commit.record;\n // process...\n _ = record;\n }\n }\n};\nvar handler: Handler = .{};\ntry client.subscribe(&handler);\n```\n\nconnects to jetstream (bluesky's JSON event stream). typed events, automatic reconnection with cursor tracking, round-robin across community relays.\n\n</details>\n\n<details>\n<summary><strong>xrpc client</strong> - call AT Protocol endpoints</summary>\n\n```zig\nvar client = zat.XrpcClient.init(io, allocator, \"https://bsky.social\");\ndefer client.deinit();\n\nconst nsid = zat.Nsid.parse(\"app.bsky.actor.getProfile\").?;\nvar response = try client.query(nsid, params);\ndefer response.deinit();\n\nif (response.ok()) {\n var json = try response.json();\n defer json.deinit();\n // use json.value\n}\n```\n\n</details>\n\n<details>\n<summary><strong>json helpers</strong> - navigate nested json without verbose if-chains</summary>\n\n```zig\n// runtime paths for one-offs:\nconst uri = zat.json.getString(value, \"embed.external.uri\");\nconst count = zat.json.getInt(value, \"meta.count\");\n\n// comptime extraction for complex structures:\nconst FeedPost = struct {\n uri: []const u8,\n cid: []const u8,\n record: struct {\n text: []const u8 = \"\",\n },\n};\nconst post = try zat.json.extractAt(FeedPost, allocator, value, .{\"post\"});\n```\n\n</details>\n\n## used by\n\n<details>\n<summary><strong>downstream projects</strong> — what's building on zat</summary>\n\n### firehose consumers (Jetstream)\n\n| project | what it does |\n|---|---|\n| [labelz](https://tangled.org/zzstoatzz.io/labelz) | AT Protocol labeler — keyword-matched, secp256k1-signed, served over XRPC. exists to pressure-test zat as an SDK ([live](https://labelz.fly.dev/health)) |\n| [coral](https://tangled.org/zzstoatzz.io/coral) | named-entity recognition over the firehose ([live](https://coral.waow.tech)) |\n| [pollz](https://tangled.org/zzstoatzz.io/pollz) | polls on AT Protocol ([live](https://pollz.waow.tech)) |\n| [typeahead](https://tangled.org/zzstoatzz.io/typeahead) | community actor search ([live](https://typeahead.waow.tech)) |\n| [find-bufo](https://tangled.org/zzstoatzz.io/find-bufo) | bsky bot that quote-posts matching bufo images for opt-in followers |\n\n### CBOR / CAR / MST users\n\n| project | what it does |\n|---|---|\n| [zlay](https://tangled.org/zzstoatzz.io/zlay) | AT Protocol relay — direct PDS crawl, signature validation, inline collection index ([live](https://zlay.waow.tech/_health)) |\n| [atproto-bench](https://tangled.org/zzstoatzz.io/atproto-bench) | cross-SDK relay benchmarks (zig vs go vs rust vs python) |\n| [ken](https://tangled.org/zzstoatzz.io/ken) | semantic search over your atproto repo — vector index of records, search by meaning ([live](https://ken.waow.tech)) |\n\n### identity + XRPC users\n\n| project | what it does |\n|---|---|\n| [music-atmosphere-feed](https://tangled.org/zzstoatzz.io/music-atmosphere-feed) | bsky feed generator for music links — JWT auth for personalized feed |\n| [leaflet-search](https://tangled.org/zzstoatzz.io/leaflet-search) | search across leaflet, pckt, offprint, and other standard.site publishers |\n\n</details>\n\n## benchmarks\n\nzat is benchmarked against Go (indigo), Rust (rsky), and Python (atproto) in [atproto-bench](https://tangled.org/zzstoatzz.io/atproto-bench):\n\n- **decode**: 202k frames/sec (zig) vs 39k (rust) vs 15k (go) — with CID hash verification and full CBOR validation\n- **sig-verify**: 15k–19k verifies/sec across all three — ECDSA is table stakes\n- **trust chain**: full repo verification in ~300ms compute (zig) vs ~410ms (go) vs ~422ms (rust)\n\n## specs\n\nvalidation follows [atproto.com/specs](https://atproto.com/specs/atp). passes the [atproto interop test suite](https://github.com/bluesky-social/atproto-interop-tests) (syntax, crypto, MST vectors).\n\n## versioning\n\npre-1.0 semver:\n- `0.x.0` - new features (backwards compatible)\n- `0.x.y` - bug fixes\n\nbreaking changes bump the minor version and are documented in commit messages.\n\n## license\n\nMIT\n\n---\n\n[devlog](devlog/) · [changelog](CHANGELOG.md)\n",
"title": "zat"
}