itsgoin/docs/fof-spec/README.md
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

98 lines
8.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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, 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](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 (400500 keys × 400500 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_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.