Skip to content

A did:plc audit-log verifier in AtprotoTypesVerify - #73

Merged
germ-mark merged 8 commits into
mainfrom
llm/p3-plc-verifier
Oct 19, 2026
Merged

germ-mark merged 8 commits into
mainfrom
llm/p3-plc-verifier

Conversation

@germ-mark

@germ-mark germ-mark commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Adds Atproto.PLC.LogVerifying to AtprotoTypes as the injection seam — mirroring Atproto.Repo.ProofVerifying, so a consumer's engine can name the protocol without linking AtprotoTypesVerify — with Atproto.PLC.LogUnavailable as the refuse-by-default conformer. The real verifier is Atproto.PLC.Verifier in AtprotoTypesVerify.

Given the signed operation log a relay serves, it checks that the genesis operation hashes to the DID (in either genesis format), that every entry's CID is the SHA-256 of its DAG-CBOR bytes, that the live prev chain is unbroken, and that every signature verifies under a rotation key the log itself established — then renders the document the last live operation implies. A did:web document can only be carried as a hint.

AtprotoTypesVerifyMocks gains PLCFixture for building such logs in a consumer's tests.

Notes

  • Three vectors captured from plc.directory (provenance in Tests/AtprotoTypesVerifyTests/Resources/plc/PROVENANCE.md) pin that the verifier reproduces the document the directory serves, for a legacy-genesis and a current-format DID.
  • Every negative case is forged through PLCFixture — one field changed at a time — rather than by pinning bytes.
  • No new dependency. swift build, swift test (176 tests) and swift format lint --recursive --strict Sources Tests are clean.
  • The injection seam is the whole of this change; wiring a consumer's DID read to it is a separate lane.
  • docs/dependency-choices.md gains two notes on primitives that already exist in the dependency graph but cannot be used here: GermCBOR, a value-model CBOR codec (no tags, no floats, and RFC 8949 bytewise key order rather than DAG-CBOR's RFC 7049 length-first), and the swift-bases base64url helper, which accepts all three spellings the signature rule refuses — non-zero trailing bits, = padding, and the standard alphabet.

🤖 Generated with Claude Code

germ-mark and others added 6 commits October 18, 2026 15:44
A consumer's reads layer can name `Atproto.PLC.LogVerifying` from the base
module and inject a verifier. A build that did not link `AtprotoTypesVerify`
injects `Atproto.PLC.LogUnavailable`, which refuses rather than handing back
something that merely looks like a verified document. Mirrors
`Atproto.Repo.ProofVerifying`; the concrete verifier's body lands next.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`DAGCBORValue.parse(json:)` reads JSON strictly and keeps the object's key
order. Hashing does not depend on that order — the encoder sorts canonically —
but rendering does, and one parsed value serves both. `jsonText()` is the
inverse, for fixtures. The three captured directory vectors now re-encode to
the exact bytes their CIDs address.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`Atproto.PLC.Verifier` checks that the genesis hashes to the DID in either
genesis format, that every entry's CID is the SHA-256 of its DAG-CBOR bytes,
that the live `prev` chain is unbroken, and that every signature holds under a
rotation key the operation before it established — then renders the document
the last live operation implies, curve-dependent `@context` and all. The
captured directory vectors verify and reproduce the documents the directory
serves, for both formats.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A real library target, like `RepoFixture`. `unsignedOperation`,
`unsignedLegacyCreate` and `unsignedTombstone` build the operations,
`signed(_:with:sig:)` adds — or substitutes — a signature so a test can forge
exactly one field, and `entry`/`genesisDID`/`cid` derive what a source would
serve. Every refusal reason the verifier has is driven from a log built here
rather than from captured bytes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A `plc_tombstone` terminates the live chain, the document it renders is the
cleared one — the DID's id, the two fixed `@context` entries, and empty
`alsoKnownAs`, `verificationMethod` and `service` — and no live operation may
follow it. The rules landed with the verifier; this enables their two tests and
spells out that the skip for nullified entries comes before the tombstone rule.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A `did:web` document has no log behind it to check, so the most that can be
said about one is that a relay asserted it — which is what `Atproto.PLC.Hint`
is for. The verifier refuses the DID outright, and the test serves a
well-formed log so the DID's method is the only thing that can refuse it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: ec4533c

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@germ-network/atprototypes Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@germ-mark
germ-mark marked this pull request as draft October 19, 2026 01:37
`GermCBOR` in `GermConvenience` exposes a public value tree, which is the test
the external CBOR candidates fail, so this note read as out of date. It still
cannot serve DAG-CBOR: no tags (so no links), no floats, and RFC 8949 bytewise
key order where DAG-CBOR keeps RFC 7049's length-first order.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@germ-mark
germ-mark marked this pull request as ready for review October 19, 2026 01:41
@germ-mark
germ-mark marked this pull request as draft October 19, 2026 06:22
`swift-bases` (already a dependency, for `Base32` and `BaseX`) ships a `Base64`
product with URL-safe support, so this target's own codec looked like a
duplicate. It accepts all three spellings the did:plc signature rule refuses:
non-zero trailing bits (Foundation ignores them), `=` padding (it pads the input
before decoding), and the standard alphabet.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@germ-mark
germ-mark marked this pull request as ready for review October 19, 2026 06:22
@germ-mark
germ-mark merged commit 20900cf into main Oct 19, 2026
7 checks passed
@germ-mark
germ-mark deleted the llm/p3-plc-verifier branch October 19, 2026 06:29
@github-actions github-actions Bot mentioned this pull request Oct 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant