From d7ce2f734c059132b10bbf44356c211a3d4fa530 Mon Sep 17 00:00:00 2001 From: Scott Reimers Date: Wed, 13 May 2026 01:20:43 -0400 Subject: [PATCH] docs(design.html): add section 20a Friend-of-Friend Visibility Public-facing architecture description of the FoF post-gating system specified in docs/fof-spec/. Sits between section 20 (Encryption) and section 21 (Delete Propagation). All subsections marked badge-planned. Covers the user-facing 4-level visibility model, V_me primitive, bio-post HPKE distribution, dual-mode operation (public-body+FoF- comments vs FoFClosed), CDN-level comment verification, bucketed padding scheme, revocation/rotation/key-burn lifecycle, PQ-readiness, and the five ship-able layers. Visibility variants table updated with the new FoFClosed row. Disambiguation note added at top of section 20a noting this "vouch" is the cryptographic V_me primitive, distinct from the directory vouches in section 27. Reciprocal disambiguation note added at top of section 27 pointing the other direction. TOC entry added. Co-Authored-By: Claude Opus 4.7 (1M context) --- sessions.md | 28 ++++++++++++ website/design.html | 105 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+) diff --git a/sessions.md b/sessions.md index 5e86a7b..52fe532 100644 --- a/sessions.md +++ b/sessions.md @@ -65,6 +65,34 @@ See `CONTRIBUTING.md` for the protocol. See `AGENTS.md` for the Claude-specific **Stopping point**: commit `b8b38a6` (Layer 1) + new commit for Layer 2 both on branch; not merged. Awaiting Scott. +### Update 2026-05-13 — design.html FoF section (20a) added + +Added a new section `20a. Friend-of-Friend Visibility` to `website/design.html`, sitting between Encryption (20) and Delete Propagation (21). Marked all subsections `badge-planned`. Layer table at the bottom shows ship status per layer. + +**Key writing decisions**: +- Up-front disambiguation note: this section's "vouch" is the cryptographic V_me primitive, distinct from the directory-vouch system in section 27. Symmetric disambiguation note added to section 27 pointing the other direction. +- User-facing 4-level model (Public / Friends-only / FoF / Custom-v2) leads. Crypto primitives follow. +- Mode 1 vs Mode 2 split called out via `card` divs. +- CDN-level verification highlighted as the propagation-DoS resistance story. +- Revocation lifecycle: three `card` blocks — per-post default, V_me rotation, opt-in cascade + key-burn. +- PQ-readiness explicitly addressed (symmetric primitives PQ-safe; Ed25519 → ML-DSA-65 swap path noted). +- Cross-ref to `docs/fof-spec/` for implementation detail. + +**Tables updated**: +- Visibility variants table (in section 20) now has a `FoFClosed` row with overhead + bucketed-audience note. +- New layer-status table at the bottom of section 20a shows the five ship-able layers. + +**Other touched**: +- TOC entry added (`20a. Friend-of-Friend Visibility`). +- `reference_design_index.md` auto-updated by the design-index hook on save. +- Section 27 (Directory) got a reciprocal disambiguation note pointing at section 20a. + +**Files in this commit**: +- `website/design.html` +- `sessions.md` (this entry) + +Branch state: still `docs/fof-spec-layer1-bio-grants`, still unmerged. Implementation can now begin from a coherent public design + internal spec. + ### Update 2026-05-13 — Layer 4 written (rotation + revocation + key lifecycle) Iterative session with Scott. Recap of where the model landed: diff --git a/website/design.html b/website/design.html index 4dccad2..032cda5 100644 --- a/website/design.html +++ b/website/design.html @@ -68,6 +68,7 @@ 18b. Erasure-Coded CDN Replication 19. Sync Protocol 20. Encryption + 20a. Friend-of-Friend Visibility 21. Delete Propagation 22. Social Graph Privacy 23. Multi-Device Identity @@ -1304,6 +1305,7 @@ END PublicNoneUnlimited Encrypted { recipients }~60 bytes per recipient~500 (256KB cap) GroupEncrypted { group_id, epoch, wrapped_cek }~100 bytes totalUnlimited (one CEK wrap for the group) + FoFClosed { pub_post_set, wrap_slots } Planned~154 bytes per admitted V_x, paddedBucketed (8/16/32/64/128/256, then +128 steps)

PostId integrity

@@ -1350,6 +1352,105 @@ END

Different profile versions per circle, encrypted with the circle/group key. A peer sees the profile version for the most-privileged circle they belong to. CircleProfileUpdate (0xB4) wire message. Public profiles can be hidden (public_visible=false strips display_name/bio).

+ +
+

20a. Friend-of-Friend Visibility Planned

+ +
+ Distinct from directory vouches. The "FoF vouch" described here is a cryptographic primitive for post readership and comment gating (per-persona symmetric key V_me). It is unrelated to the directory vouch system in section 27, which governs discovery-layer trust and bot-ring resistance. The two share vocabulary but operate at different layers. +
+ +

The problem

+

Existing visibility variants gate by explicit recipient lists (Encrypted{recipients}) or named-circle membership (GroupEncrypted). Neither expresses "people who are reachable through my social graph" without leaking the graph itself. FoF visibility fills that gap: posts whose readership emerges from cryptographic reachability through a unilateral vouch graph, with no recipient IDs on the wire and no centrally-computed membership lists.

+ +

User-facing model

+

Authors pick one of four visibility levels at compose time:

+ + + + + + +
LevelReaches
PublicAll readers (unchanged)
Friends-onlyPersonas you have vouched for
Friends-of-FriendsYour vouchees + every vouchee of anyone who vouched for you (emergent FoF)
Custom v2Author-selected subset of held vouch keys
+ +

Core primitives

+
    +
  • V_me: a 32-byte symmetric key owned by each persona. Distributed to everyone the persona vouches for. Anyone holding V_alice can decrypt wrap slots Alice sealed under it.
  • +
  • Keyring: per-persona, holds the persona's own V_me plus every V_x received from vouchers. The union of these is what makes FoF reach emergent: an author wraps a post slot under every V_x they hold, and any reader whose keyring intersects with that set can decrypt.
  • +
  • Wrap slot: an anonymous AEAD ciphertext in the post header, sealed under one V_x. Carries the post's CEK plus a per-slot signing key. No recipient ID visible on the wire.
  • +
  • Prefilter tag: a 2-byte HMAC(V_x, post_id)[:2B] on each slot. Readers precompute tags for keys in their keyring and skip non-matching slots, cutting trial-decrypt cost ~65,000× per post.
  • +
  • pub_post_set: list of all admitted signing pubkeys for a post's FoF set. Inline in the post header, randomly ordered. Allows CDN-level verification of comment signatures without revealing membership identities.
  • +
+ +

Distribution: vouches ride bio posts

+

Vouches are NOT delivered via DM. Instead, the voucher publishes anonymous HPKE-sealed wrappers (one per recipient) inside their bio post. HPKE (RFC 9180) provides recipient anonymity — wrapper ciphertext reveals nothing about the recipient's pubkey. Each wrapper is 48 bytes (32-byte sealed V_me + 16-byte AEAD tag); one shared ephemeral pubkey per batch.

+

Readers auto-scan bio posts of accounts they follow, trial-decrypting each wrapper against each of their personas. Cost is ~60µs per wrapper per persona on mobile — a 200-wrapper bio scanned against 3 personas is ~36 ms. The bio's VouchGrantBatch is padded with dummy wrappers and shuffled on every publish so observers see neither vouch-set size nor change-targets.

+ +

Two modes

+
+

Mode 2: Public body, FoF-gated comments

+

Body is plaintext in the CDN (indexable, cacheable, shardable — unchanged from existing public-post path). Comments are encrypted under a CEK derived from the wrap-slot CEK, so only FoF members can read them. Non-FoF observers see only ciphertext + signature fields. The compose UI exposes this via a new CommentPolicy::FriendsOfFriends variant.

+
+ +
+

Mode 1: FoFClosed (body + comments encrypted)

+

New PostVisibility::FoFClosed variant. Body is encrypted under CEK (same wrap-slot mechanism as comments). Both readership and comment authority emerge from keyring intersection with the post's wrap_slots.

+
+ +

CDN-level comment verification

+

Each wrap slot is dual-derived: one half yields the shared CEK (read capability), the other yields a per-V_x Ed25519 signing keypair (priv_x sealed inside the slot; the corresponding pub_x published in the post's pub_post_set). Comments declare which pub_x signed them and carry the signature.

+

Propagation nodes verify three things before forwarding a comment:

+
    +
  1. pub_x_index points to a valid entry in pub_post_set.
  2. +
  3. That entry is not in the post's revocation_list.
  4. +
  5. The comment's group_sig validates against that pub_x.
  6. +
+

Any failure → drop, do not forward. This kills the bandwidth-amplification attack that a single admitted-but-malicious FoF member could otherwise mount: their forgeries cannot pass the propagation gate.

+ +

Privacy properties

+
    +
  • Unilateral: vouching is a one-way act, no handshake. The FoF graph forms without bilateral negotiation.
  • +
  • Graph-private: wrap slots carry no recipient IDs. Observers cannot enumerate who can read a post.
  • +
  • Bucketed padding: slot count and body size are deterministically padded to fixed buckets (power-of-2 up to 256, then +128 steps for slots; same shape up to 256 KB then +256 KB steps for bodies). Observers learn the bucket, not the position within it.
  • +
  • Recipient anonymity on vouch distribution: HPKE key privacy ensures bio-post wrappers do not reveal recipients.
  • +
  • Per-post chain pseudonym (accepted tradeoff): the pub_x_index in a comment lets observers correlate "these N comments came through the same chain" within a single post. Cross-post correlation is broken because keys regenerate per post.
  • +
+ +

Revocation & key lifecycle

+

Three complementary mechanisms:

+
+

Per-post comment revocation (default)

+

The author signs a RevocationEntry for a specific pub_x on a specific post. Propagation nodes delete locally-stored comments by that signer, remove the entry from pub_post_set, append to revocation_list, and forward the diff. Retroactive: the mesh self-cleans as the diff sweeps through.

+
+
+

Persona-wide V_me rotation

+

To remove a vouchee, the persona generates V_me_new and issues it to every non-revoked vouchee via the next bio-post batch. Revoked vouchees retain V_me_old. Old posts (sealed under V_me_old) stay readable by anyone who holds the old key — grandfathered by default. The CDN does not auto-cascade revocations.

+

Receivers append new V_me values to their keyring (chain), so newly-issued keys do not invalidate prior ones — the receiver keeps holding the old key for reading historical content.

+
+
+

Opt-in cascade + key burn (advanced)

+

If the author wants to cut off comment authority on old posts after a rotation, they cascade by publishing per-pub_x revocations on each affected post. Local-only own_post_slot_provenance table maps "which pub_x was sealed under which V_me" so the author can target precisely.

+

For the rare case of a leaked V_me, an optional KeyBurnDiff primitive swaps the V_old wrap slot for a V_new wrap slot in-place on a specific post. Scrubs the leaked key from the CDN copy of old posts. Body CEK unchanged.

+
+ +

Performance budget

+

At realistic scale (~500 vouchees, ~500 wrap slots per post), reader-side decryption uses an unlock cache: the first time persona P decrypts a post from author A via key V_x, the (P, V_x) tuple is cached. Subsequent posts from A try that key first — one HMAC + one AEAD attempt in the hot path. Full scan only on cache miss; newly-received V_x triggers a retry sweep over the unreadable-posts table.

+ +

Post-quantum readiness

+

Body encryption, wrap slots, and HKDF/HMAC are all symmetric — PQ-safe. Comment signing uses Ed25519 today; the spec shape is algorithm-agnostic so ML-DSA-65 (~2 KB pubkey, ~3.3 KB signature) can substitute, optionally with a Merkle-commit variant on pub_post_set to keep header size bounded.

+ +

Implementation

+

Full crypto-level byte layouts, data models, wire-format additions, ship criteria, and integration tests are specified in docs/fof-spec/. The implementation is layered for bottom-up shipping:

+ + + + + + + +
LayerScopeStatus
1Vouch primitive (V_x keys, keyring, bio-post HPKE wrappers, scan policy)Planned
2Mode 2: public posts with FoF-gated comments, CDN-level verificationPlanned
3Mode 1: FoFClosed body + wrap slots + anonymous prefilterPlanned
4Rotation, revocation, key lifecycle (grandfather + cascade + key-burn)Planned
5Unlock cache + prefilter optimization (perf-critical at scale)Planned
+
+

21. Delete Propagation

@@ -1578,6 +1679,10 @@ END

The directory is an opt-in convenience layer for discovery and creator protection. It is not node access — losing directory presence does not disconnect anyone from the network or from their existing connections. This asymmetry is load-bearing: humans with mature relationships shrug off directory loss; bots and content thieves depend on it entirely.

+
+ Distinct from FoF cryptographic vouches. The "vouch" described in this section is a directory-layer trust signal governing discovery and bot-ring resistance. It is unrelated to the cryptographic vouch (V_me) in section 20a, which gates post readership and commenting via per-persona symmetric keys. The two share vocabulary but operate at different layers. +
+

Scope

  • Whitelist track — discoverability, vouch-based entry, graph-scoped visibility.