~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
128 lines · 5.4 KB Raw History

Regenerating test/fixtures/space-vectors.json

test/fixtures/space-vectors.json holds cross-implementation test vectors for the AT Protocol permissioned data ("spaces") primitives: the LtHash set hash, a commit and its MAC, a record MAC, the STAR archive of a repo, a KEM decapsulation, the MAC headers and a credential signature. They are produced by running the reference implementation, not by transcribing it. pds.js asserts against them so that its primitives stay byte-for-byte compatible with upstream.

Reference pin: bluesky-social/atproto at 704b9e2acd7df78c9a22a275c64ecf8befcf30b7, published as @atproto/space@0.0.0-spaces-alpha-20261105160032. The same SHA is recorded in the fixture's generatedFrom.sha.

Why a worktree

~/code/atproto normally carries uncommitted work on some other branch. Checking out the pin in place would clobber it. Generation therefore happens in a throwaway worktree, and the generator itself lives inside that worktree as an untracked scratch file. Nothing is ever committed to the atproto repo.

Procedure

1. Create the worktree and build the reference's dependencies

cd ~/code/atproto
git worktree add ~/code/atproto-space-ref 704b9e2ac
cd ~/code/atproto-space-ref
pnpm install --frozen-lockfile
pnpm --filter '@atproto/space^...' run build
pnpm --filter '@atproto-labs/rolldown-plugin-bundle-manifest' run build
pnpm --filter '@atproto/oauth-provider-api' run build

packages/space is a registered vitest project, so its own sources run straight from TypeScript. Its workspace dependencies resolve through their dist/ entrypoints and must be built first. The same worktree serves scripts/space-reference/run.sh.

Use pnpm exec, not npx: the repo pins pnpm via devEngines and npx aborts with EBADDEVENGINES.

2. Write the generator

Create packages/space/tests/gen-vectors.test.ts in the worktree. It is a scratch file: untracked, never committed, deleted with the worktree.

Rules the generator must keep:

  • Import the primitives from ../src/index.js (LtHash, RepoCommit, verifyCommit, computeCommitMac, computeRecordMac, formatMacHeader, formatWantMacHeader, serializeRepo, verifyRepoStarFull, encapsulate, decapsulate, createSpaceSigHeaders), P256Keypair from @atproto/crypto, encode from @atproto/lex-cbor, cidForCbor and parseCid from @atproto/lex-data.
  • Never invent CIDs. The two in use are the CIDs of { text: 'hello' } and { text: 'world' }, lifted from the reference's own packages/space/tests/repo-commit.test.ts; the generator re-derives them and asserts they match before writing anything.
  • LtHash inputs: the elements a, app.example.post/3kabcdefghi/<hello cid> and com.atmoboards.thread/self/<world cid>, singly and as the sets [a, b], [b, a] and [a, b, c].
  • The commit is over those two records in at://did:plc:aaaa/space/com.example.type/key1, repo did:plc:bbbb, rev 3lkabcdefgh2x, MACed under a fixed secret of 32 0x07 bytes. Assert verifyCommit and verifyRepoStarFull over the serialized archive before writing, so a fixture upstream itself would reject never reaches disk.
  • The record MAC is over the app.example.post/3kabcdefghi record's uri and CID under the same secret.
  • The KEM vector is one encapsulate against a fresh exportable author key, recording the author's private key, the key share and the shared secret.
  • Fail loudly if VECTORS_OUT or REF_SHA is unset.

The fixture's shape: generatedFrom, lthash (empty, single, sets), commit (space, repo, rev, records[] with valueHex, dataHex, secretHex, macHex, macHeader, starHex), record (uri, cid, secretHex, macHex, macHeader), kem (authorPrivateKeyHex, authorDidKey, keyShare, sharedSecretHex, wantMacHeader) and httpSignature (keyId, headers).

3. Run it

cd ~/code/atproto-space-ref/packages/space
REF_SHA=$(git rev-parse HEAD) \
VECTORS_OUT=~/code/pds.js/test/fixtures/space-vectors.json \
  pnpm exec vitest run tests/gen-vectors.test.ts

4. Verify the fixture

cd ~/code/pds.js
npx vitest run packages/spaces/test

The LtHash, commit, MAC, STAR and KEM suites read the fixture. If the commit's dataHex changes while the LtHash vectors do not, the commit encoding moved upstream; if the LtHash vectors change, every downstream test is suspect, so find out why before committing.

5. Clean up

git -C ~/code/atproto worktree remove --force ~/code/atproto-space-ref

Resyncing with upstream

Regenerating this fixture is how pds.js resyncs with upstream spaces work. When the reference moves, point the worktree at the new revision, rerun the procedure, and let the diff in space-vectors.json tell you exactly which primitive changed on the wire.

Whenever the fixture is regenerated at a new revision, update the pin in docs/superpowers/specs/2026-07-30-permissioned-data-design.md §0 to match the new generatedFrom.sha. The fixture and the spec pin must never disagree.

Non-determinism

The KEM vector and the HTTP signature use fresh keys, so kem.* and httpSignature.* change on every regeneration. They are for verification, not reproduction: given the recorded private key and key share, an implementation must derive the same shared secret, and the recorded signature must verify against keyId. Everything else is deterministic and must not change unless upstream changes.