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
PublicEncrypted { recipients }GroupEncrypted { group_id, epoch, wrapped_cek }FoFClosed { pub_post_set, wrap_slots } PlannedDifferent 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).
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.
+ 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.
Authors pick one of four visibility levels at compose time:
+| Level | Reaches |
|---|---|
| Public | All readers (unchanged) |
| Friends-only | Personas you have vouched for |
| Friends-of-Friends | Your vouchees + every vouchee of anyone who vouched for you (emergent FoF) |
| Custom v2 | Author-selected subset of held vouch keys |
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.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.V_x. Carries the post's CEK plus a per-slot signing key. No recipient ID visible on the wire.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.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.
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.
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.
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:
+pub_x_index points to a valid entry in pub_post_set.revocation_list.group_sig validates against that pub_x.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.
+ +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.Three complementary mechanisms:
+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.
V_me rotationTo 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.
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.
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.
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.
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:
| Layer | Scope | Status |
|---|---|---|
| 1 | Vouch primitive (V_x keys, keyring, bio-post HPKE wrappers, scan policy) | Planned |
| 2 | Mode 2: public posts with FoF-gated comments, CDN-level verification | Planned |
| 3 | Mode 1: FoFClosed body + wrap slots + anonymous prefilter | Planned |
| 4 | Rotation, revocation, key lifecycle (grandfather + cascade + key-burn) | Planned |
| 5 | Unlock cache + prefilter optimization (perf-critical at scale) | Planned |
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.
+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.
+