# Friend-of-Friend (FoF) Gating — Implementation Spec **Status**: Skeleton drafted 2026-04-23 by Scott + Lead Claude. Crypto-level byte layouts and algorithm specifics are intentionally left as `TBD — OPUS` markers for Opus to fill in. See each layer file for scope. --- ## What this spec covers FoF-gating is a new post-visibility mechanism that gates readership and commenting by **social reachability** rather than explicit membership lists. It complements, rather than replaces, the existing primitives (`Public`, per-recipient `Encrypted`, per-circle `GroupEncrypted`). The central primitive is the **per-person vouch key** `V_me` — a symmetric key each persona owns, handed to everyone they vouch for. Cryptographic reachability for an author's FoF post emerges from the union of every `V_x` that author holds (own + received-from-vouchers), and every reader whose keyring intersects that set. ### User-facing model Authors pick a post visibility from four levels: | Level | Wrap slots include | Cryptographic reach | |---|---|---| | **Public** | (none) | All readers | | **Friends-only** | `V_me` only | My vouchees | | **Friends-of-Friends** | `V_me` + every `V_x` I hold | My vouchees + every vouchee of anyone who vouched for me (emergent FoF) | | **Custom** | Author-selected subset of held `V_x` | Union of reach through each included key | No centrally-computed membership list. Reach is a function of the wrap-slot set and which keys each reader holds. ### Key design properties - **Unilateral**: vouching is a one-way act (Alice hands out `V_alice`). No bilateral handshake required for the FoF graph to form. - **Graph-private**: wrap slots carry no recipient IDs. Observers cannot enumerate "who can read this post." - **Anonymous prefilter**: each wrap slot carries a 2-byte `HMAC(V_x, post_id)[:2B]` tag. Readers precompute tags for keys in their ring and skip non-matching slots, cutting trial-decrypt cost ~65000× per post. - **Slot count padding**: author pads wrap slots to power-of-2 buckets to blunt vouch-count leak. - **Per-post keypair**: each FoF post generates a fresh `(priv_post, pub_post)`. Leak of one post's `priv_post` bounds blast radius to that post's comments; doesn't expose other posts or the author's identity key. ### Two modes - **Mode 1 — `FOF_CLOSED`**: post body encrypted, comments encrypted. FoF-only readership + writership. - **Mode 2 — `PUBLIC_FOF_COMMENT`**: post body public (indexable, cacheable, shardable), comments can be public OR FoF-encrypted per-comment. --- ## Layering / implementation order Build and ship bottom-up. Each layer is independently shippable and exercised before moving to the next. 1. **[Layer 1](layer-1-vouch-primitive.md) — Vouch primitive.** `V_x` keys, per-persona keyring storage, epoch tag, distribution/exchange mechanism, minimal UI. No posts yet. 2. **[Layer 2](layer-2-mode2-fof-comments.md) — Mode 2: public posts with FoF-gated comments.** Easier implementation path; reuses existing public-post CDN path; extends `CommentPolicy` with a new `GroupMembersOfFoF` variant. 3. **[Layer 3](layer-3-mode1-fof-closed.md) — Mode 1: `FOF_CLOSED` posts.** New `PostVisibility::FoFClosed` variant. Wrap slots, anonymous prefilter tag. Receive-path integration. 4. **[Layer 4](layer-4-keypair-rotation.md) — Per-post keypair rotation.** Graceful `(priv_post', pub_post')` rotation record re-wrapped to current FoF set. Old comments still verifiable under old `pub_post`; new comments require new. 5. **[Layer 5](layer-5-prefilter-and-cache.md) — Unlock cache + prefilter optimization.** Author-direct fast path, winning-V_x-per-author cache, unreadable-posts retry table, re-try-on-new-V_x trigger. Performance-critical at realistic keyring sizes (400–500 keys × 400–500 slots). 6. **[Layer 6](layer-6-revocation.md) — Revocation & rotation cascades.** Deferred; may not be in v1. Drafted as a stub for design review. --- ## Intentionally out of scope (v1) - **First-contact DM gating.** Deferred; Scott has a separate approach to sketch later. - **Spam / abuse mitigation beyond vouch_mac.** Rate limits, PoW, reputation, reporting — tracked as separate work. - **Intra-circle anonymity.** Ring signatures over the vouch set so even other FoF members cannot tell which voucher's chain a comment arrived through. v2. - **Zero-knowledge audience gating.** v2. - **PQ migration** of `priv_post` / identity sigs. The spec shape above is algorithm-agnostic; PQ (ML-DSA ~1.3KB sigs) swaps in without structural change. Tracked separately. --- ## Glossary - **Persona**: a user's posting identity. A device may hold multiple. Each persona has its own `V_me` and its own received-vouches keyring. - **Vouch**: a unilateral act by persona P1 declaring trust in persona P2. P1 gives P2 a copy of `V_P1`. - **Vouch key (`V_x`)**: a symmetric key owned by persona `x`, distributed by `x` to everyone `x` vouches for. `V_me` refers to the current persona's own vouch key. - **Keyring**: for a given persona, the set of vouch keys held = `{V_me}` ∪ `{V_x : x vouched for me}`. - **Wrap slot**: an anonymous ciphertext in a post header, carrying the post's private material encrypted under some `V_x`. Readers trial-decrypt slots whose 2-byte prefilter tag matches an owned key. - **pub_post / priv_post**: per-post ephemeral ed25519 keypair. `priv_post` is wrapped in the post's wrap slots; `pub_post` is in the header plaintext and used for group signature verification on comments. - **Identity key**: persona's long-term ed25519 key, used to sign content as that persona. Distinct from `priv_post`. (In current ItsGoin terms: this is the persona's posting key. Worth naming-alignment review when specifying.) - **Vouch MAC**: `HMAC(V_x, post_id || comment_hash)` — 16B truncated. Identifies which `V_x` a commenter holds. Inside encrypted payload (Mode 1) or alongside plaintext (Mode 2). Used by author strict-mode to verify the commenter is reachable via a known vouch chain. --- ## Integration with existing ItsGoin primitives - **`Circle` + `GroupEncrypted`** remain as-is for named explicit-membership groups. FoF is a separate visibility class, not a replacement. - **`PostVisibility`** gains one new variant (`FoFClosed`, from Layer 3). Mode 2 reuses `PostVisibility::Public` with extended `CommentPolicy`. - **`CommentPolicy`** gains one new variant for Layer 2 (Mode 2 comment gating). - **`InlineComment`** gets optional `group_sig` + `vouch_mac` fields (back-compat via `#[serde(default)]`, same pattern as Phase 2e `ref_post_id`). - **`control::receive_post`** gets new verify-gate branches for `FoFClosed` posts (author_sig + wrap_slots well-formedness) and FoF comments (group_sig verifies against `pub_post` from the referenced parent post). - **Multi-persona**: keyrings are per-persona. Unlock attempts iterate personas; the persona that successfully unlocks is recorded and drives comment-authorship defaults. See Layer 3 for detail. --- ## How to read this spec - Every `TBD — OPUS` marker identifies a crypto specifier / byte layout / test vector that Opus will fill in. - `Lead decisions` blocks capture commitments that came out of the Scott + Lead Claude conversation; these are not open to further design iteration. - `Open questions` blocks flag things the Lead wants Opus's input on before proceeding. Once Opus fills in the crypto layers, the Lead re-reviews + we move to implementation on a per-layer branch schedule.