itsgoin/docs/fof-spec
Scott Reimers 553fbd3a20 docs: Layer 2 — CDN-verified FoF comments (per-V_x keypair)
Replace single per-post priv_post with per-V_x (pub_x, priv_x). Post
header publishes pub_post_set; comments declare pub_x_index; CDN
propagation nodes verify group_sig + identity_sig against named pubkey
before forwarding. Kills bandwidth-amplification DoS from admitted-but-
malicious FoF members.

Dual-derivation wrap slot (read → CEK, sign → priv_x) with shared
structure Layer 3 inherits. Comments encrypted under CEK_comments so
Mode 2 comments are genuinely FoF-read-gated, not just FoF-sign-filtered.

Author-signed revocation diff appended to post; CDN honors per-chain
revocation. Tradeoff: pub_x_index is a per-post voucher-chain pseudonym,
re-randomized across posts. Accepted.

Layer 3 banner added noting wrap-slot structure is now superseded by
Layer 2's canonical form; full reconciliation deferred.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 08:25:40 -04:00
..
layer-1-vouch-primitive.md docs: Layer 1 — HPKE-sealed vouch grants via bio post 2026-04-24 07:38:12 -04:00
layer-2-mode2-fof-comments.md docs: Layer 2 — CDN-verified FoF comments (per-V_x keypair) 2026-04-24 08:25:40 -04:00
layer-3-mode1-fof-closed.md docs: Layer 2 — CDN-verified FoF comments (per-V_x keypair) 2026-04-24 08:25:40 -04:00
layer-4-keypair-rotation.md docs: FoF-gating spec skeleton (hand-off to Opus) 2026-04-23 23:20:56 -04:00
layer-5-prefilter-and-cache.md docs: FoF-gating spec skeleton (hand-off to Opus) 2026-04-23 23:20:56 -04:00
layer-6-revocation.md docs: FoF-gating spec skeleton (hand-off to Opus) 2026-04-23 23:20:56 -04:00
README.md docs: Layer 2 — CDN-verified FoF comments (per-V_x keypair) 2026-04-24 08:25:40 -04:00

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 — Vouch primitive. V_x keys, per-persona keyring storage, epoch tag, HPKE-sealed anonymous-wrapper distribution via the voucher's bio post, scan-on-follow + scan cache, minimal UI. No FoF-gated posts yet.
  2. Layer 2 — 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 — Mode 1: FOF_CLOSED posts. New PostVisibility::FoFClosed variant. Wrap slots, anonymous prefilter tag. Receive-path integration.
  4. Layer 4 — 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 — 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 (400500 keys × 400500 slots).
  6. Layer 6 — 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_x / priv_x: per-V_x ed25519 keypair (Layer 2). priv_x is wrapped in the sign-slot of each V_x; pub_x is published in the post's pub_post_set. Comments reference a pub_x_index so propagation nodes can verify the comment signature without unwrapping. Replaces the earlier single pub_post/priv_post per-post keypair.
  • pub_post_set: list of all pub_x for a post's FoF set, in randomized order. Inline in post header. Comments reference entries by index.
  • revocation_list: author-appended signed entries that tell CDN propagation nodes to drop comments under a named pub_x. Stops compromised voucher-chains at the propagation layer.
  • 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 pub_x_index, group_sig, identity_sig, encrypted ciphertext, and inner vouch_mac (inside the ciphertext). Back-compat via #[serde(default)], same pattern as Phase 2e ref_post_id.
  • Propagation-node accept rule for comments on FoF posts: valid pub_x_index + not in revocation_list + group_sig verifies + identity_sig verifies. Any failure → drop without forwarding. Makes bandwidth-amplification DoS infeasible.
  • 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.
  • Bio post (VisibilityIntent::Profile): Layer 1 adds an optional vouch_grants field carrying an HPKE-sealed per-recipient wrapper batch. Existing bio-post CDN propagation carries vouch distribution — no new control-message type. See Layer 1 for wrapper format and scan policy.

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.