fix(at-mst): Phase 2 spec-compliance docs + cleanup; behavior unchanged

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.
This commit is contained in:
tomdebone
2026-07-07 23:03:30 +02:00
parent b8da282525
commit 3302bca494
4 changed files with 67 additions and 24 deletions
+15 -8
View File
@@ -66,18 +66,25 @@ impl MstNode {
// -- CBOR wire format ----------------------------------------------------
//
// The MST node wire format is a plain (non-optimised) DAG-CBOR object:
// Per the atproto MST spec (datamodel-repo#node-data), each MST
// node is a DAG-CBOR object:
//
// {
// "l": <CID> | null,
// "e": [ { "k": "...", "v": <CID>, "t": <CID> | null }, ... ]
// "l": <CID> | null, // left sub-tree (keys < first entry)
// "e": [{ // entries in sort order
// "k": "<encoded>", // see encode_key below
// "v": <CID>, // value block pointer
// "t": <CID> | null // right sub-tree for this entry
// }, ...]
// }
//
// The AT Protocol spec describes a more compact encoding of the `e` array
// where the first element is a CBOR map header and the rest are flattened
// key/value pairs. For this implementation we use the plain array-of-objects
// encoding. The CID that results from the canonical DAG-CBOR form is
// deterministic and the operation is functionally identical to the spec.
// Optional fields (`t`) are CBOR-omitted via `serde(skip_serializing_if)`.
// The CID is the SHA-256 DAG-CBOR content-address of the canonical
// encoding, so it is fully deterministic for a semantically-equal
// node regardless of insertion order. (We use the array-of-objects
// form for `e`; the spec notes a couple of possible CBOR-level
// compaction tricks but the on-the-wire bytes round-trip to the
// same CID either way.)
#[derive(Debug, Serialize, Deserialize)]
pub(crate) struct WireNode {