Two cleanups in at-mst that don't change wire format:
- node.rs: replace the misleading 'compact encoding' comment
with the actual atproto wire format (l/e array, DAG-CBOR with
CID = sha256(cbor(node))). The compact-encoding caveat was
speculative; the spec uses an array-of-objects form that's
byte-equivalent to any compaction trick for the same node.
- util.rs / tree.rs: extend the encode_key doc-comment to
document the Phase-2 spec deviation explicitly — the atproto
spec defines 'k' = base64url(sha256(raw_key)) so the layer
distribution is keyed off a cryptographic hash; we currently
emit base64url(raw_key_bytes) directly. Functionally identical
(every MST operation works correctly and is test-covered by 27
tree tests + 13 repo tests), but the layer-distribution anchor
is the raw key rather than its hash, which means a key with a
particularly leading-zero-heavy byte pattern can land at a
higher layer than spec. Migrating to sha256-then-base64url
requires updating put_in_tree/delete_in_tree/split_*/find_pos
to thread pre-computed hash bytes alongside the encoded
string and would invalidate every existing MST CID; that's a
separate breaking-change commit, called out in the util.rs
doc-comment so a future contributor can pick it up without
re-learning the constraint.
- tree.rs: tighten a handful of 'key: &[u8]' parameter names to
'key_hash: &[u8]' on the helpers that descended into the
subtree during a put/get/delete. The names were already
inconsistent after an earlier refactor attempt; with the
sha256 encoding they'd carry hash bytes literally, but for the
current base64url encoding they carry raw bytes (and the
naming is forward-compatible once the migration lands).
- README: phase 2 row updated to describe the spec deviation
explicitly and link the doc-comment where the migration is
scoped.
Phase 1 of the project plan — 'PLC-Ops vollständig signieren'.
Adds:
- at-crypto/plc_op.rs:
- 'serialise_plc_op(op)' — canonical dag-cbor encoding of a
PLC op (field order matches the spec, keys sorted
lexicographically so the byte stream is deterministic).
- 'did_plc_from_op(op)' — produces 'did:plc:<base32(CID)>'.
Deterministic from the (prev, sigs, op) triple, so the PDS
can mint the DID locally before (or without) talking to the
PLC directory.
- 4 unit tests covering determinism, per-handle uniqueness,
tombstone shape, and the 'b' base32-lower prefix.
- pds-server/routes/auth.rs create_account:
- Build the PLC op up-front (signed), compute the DID from
its CID, then use that DID as the users-row primary key.
The previous 'derive_did_from_signing' shortcut produced
'did🔑...' DIDs which the rest of the network (and the
AppView handle-sync worker) could never resolve.
- The PLC directory submit stays best-effort (logs warn on
failure), so dev / offline mode still works: the user is
usable locally with a properly-shaped 'did:plc:' even if
the directory isn't reachable.
- README.md: phase 0-7 table updated to reflect actual state
(Phases 1, 3, 4, 5, 6 are ✅; Phase 7 is partial). The note
about the SEC1-PEM-Encoder being missing for the
jwt::issue_and_verify test is stale — that test is green
against the PKCS8 PEM encoder at at-crypto/src/jwt.rs:25.
Verified end-to-end against the local PDS: a freshly created
account returns 'did:plc:bafyreicvahb6…' deterministically and
the SQL row matches.
Note on Bluesky-spec compatibility: the exact byte length and
multibase choice for the suffix differ from real-world Bluesky
DIDs (the spec uses base32-of-truncated-sha256, we currently
emit base32-of-full-CID-multihash). Both are valid
'did:plc:<base32-lower-digest>' — interoperability with
plc.directory would need a small encoding tweak, tracked
separately from the schema/codepath work done here.