atproto utils for zig zat.dev
atproto sdk zig
26
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 100 lines 5.8 kB view raw view rendered
1# verifying the trust chain 2 3since the last devlog (firehose benchmarks), zat picked up a bunch of correctness work — interop test suites, signature fixes, a full MST implementation — and now ties it all together: given a handle, verify everything about a repo from scratch. 4 5## what happened since last time 6 7### correctness first (0.1.8) 8 9we joined the [atproto interop test suite](https://github.com/bluesky-social/atproto-interop-tests). this is bluesky's official cross-implementation test vectors — the same fixtures that the TypeScript SDK, Go SDK, and others validate against. zat now passes all of them: 10 11- **syntax**: 6 types (TID, DID, Handle, NSID, RecordKey, AT-URI), valid + invalid vectors 12- **crypto**: 6 signature verification vectors (P-256 and secp256k1) 13- **MST**: 9 key height vectors, 13 common prefix vectors, 6 commit proof fixtures 14 15this also surfaced two bugs: 16- NSID parser wasn't rejecting TLDs starting with a digit (`1.0.0.127.record` should fail) 17- AT-URI parser wasn't validating its components (authority, collection, rkey) — it was just splitting on `/` 18 19and a spec compliance issue: ECDSA signature verification wasn't rejecting high-S values. atproto requires low-S normalization (BIP-62 style), and we were accepting both. fixed with explicit half-order checks in `verifyP256` and `verifySecp256k1`. 20 21### MST and crypto signing (0.1.9) 22 23the merkle search tree is the core data structure of an atproto repo. each key's tree layer is derived from the leading zero bits of SHA-256(key), and nodes are serialized with prefix compression. `mst.Mst` supports `put`, `get`, `delete`, and `rootCid` (serialize → hash → CID). 24 25alongside that: ECDSA signing (`signSecp256k1`, `signP256` with RFC 6979 deterministic nonces), `did:key` construction, and multibase encoding. these round out the crypto layer — zat can now both sign and verify. 26 27### code organization (0.2.0) 28 2922 files in a flat `src/internal/` was getting unwieldy. we reorganized into domain subdirectories following bluesky's own boundaries (from the [TypeScript SDK](https://github.com/bluesky-social/atproto/tree/main/packages)): 30 31``` 32internal/ 33 syntax/ — tid, did, handle, nsid, rkey, at_uri 34 crypto/ — jwt, multibase, multicodec 35 identity/ — did_document, did_resolver, handle_resolver 36 repo/ — cbor, car, mst, repo_verifier 37 xrpc/ — transport, xrpc, json 38 streaming/ — firehose, jetstream, sync 39 testing/ — interop_tests 40``` 41 42the groupings aren't arbitrary. the TypeScript SDK has `syntax`, `crypto`, `identity`, `repo`, and `xrpc` as distinct packages — `syntax` is pure parsing with zero deps, `identity` handles network resolution, `crypto` is P-256 + K-256, and `repo` contains the MST, CAR, and CBOR together (CBOR isn't a standalone package — it lives with the types that need it). 43 44## the repo verifier 45 46`verifyRepo(allocator, "pfrazee.com")` exercises the entire trust chain in one call: 47 48``` 49handle → DID → DID document → signing key 50 51repo CAR → commit → signature ← verified against key 52 53 MST root CID → walk nodes → rebuild tree → CID match 54``` 55 56the pipeline: 57 581. **resolve handle** — HTTP well-known or DNS TXT → DID string 592. **resolve DID** — did:plc via plc.directory, did:web via .well-known/did.json → DID document 603. **extract signing key** — find the `#atproto` verification method, multibase decode, multicodec parse → key type + raw bytes 614. **extract PDS endpoint** — find the `#atproto_pds` service 625. **fetch repo** — HTTP GET `{pds}/xrpc/com.atproto.sync.getRepo?did={did}` → raw CAR bytes 636. **parse CAR** — extract roots and blocks 647. **find + decode commit** — the root block is the signed commit (DAG-CBOR map with `did`, `version`, `rev`, `data`, `sig`) 658. **verify signature** — strip `sig` from the commit map, re-encode to DAG-CBOR (deterministic key ordering), verify with the signing key 669. **walk MST** — starting from the commit's `data` CID, recursively decode MST nodes with prefix decompression, collect all (key, value_cid) pairs 6710. **rebuild MST** — insert every record into a fresh `mst.Mst`, compute root CID, compare against the commit's `data` CID 68 69if any step fails, you know exactly where the trust chain breaks. 70 71### what this exercises 72 73every major module in zat participates: 74 75| step | modules used | 76|------|-------------| 77| handle resolution | `HandleResolver`, `Handle` | 78| DID resolution | `DidResolver`, `Did`, `DidDocument` | 79| key extraction | `multibase`, `multicodec` | 80| HTTP fetch | `HttpTransport` | 81| repo parsing | `car`, `cbor` | 82| signature verification | `jwt.verifyP256` / `jwt.verifySecp256k1` | 83| MST walk + rebuild | `mst.Mst`, `cbor.Value` | 84 85it's the first feature that crosses all the domain boundaries — identity, crypto, repo, and network all working together. 86 87### the integration tests 88 89two accounts, two PDS backends: 90 91- **zzstoatzz.io** — self-hosted PDS (`pds.zzstoatzz.io`), ~12k records. verifies the self-hosting path works. 92- **pfrazee.com** — bluesky CTO, hosted on `bsky.network`, ~192k records. verifies against the canonical infrastructure. 93 94both use the graceful-catch pattern: if the network isn't available (CI, offline), the test prints a message and passes. when the network is there, it runs the full chain and asserts on the DID and record count. 95 96## what's next 97 98this is the first "full pipeline" feature — it validates that the primitives compose correctly end to end. from here, the natural next steps are incremental: repo diffing (compare two commits), record-level verification (check a specific record's inclusion proof), or sync protocol support. 99 100but following the pattern: we ship when something real needs it, not before.