# Layer 1 — Vouch Primitive **Scope**: Introduce `V_x` vouch keys, per-persona keyring storage, and a distribution/exchange mechanism. No post-gating yet — this layer ships first so the primitive is in place and UI-exercised before any post encryption depends on it. --- ## Goal After Layer 1 ships: - Each persona owns a current `V_me` symmetric key. New personas auto-generate one at creation. - Each persona has a keyring of received vouch keys: `{V_x : x has vouched for me}`. - Users can view who they've vouched for, who has vouched for them, and revoke/rotate `V_me`. - Wire protocol can transfer `V_me` from voucher to vouchee inside an existing encrypted channel. - No post encryption or comment gating depends on Layer 1 yet — that arrives in Layer 2/3. --- ## Lead decisions - **`V_me` is symmetric, not asymmetric.** Every vouchee holds the same `V_me`. This is the property that makes FoF reach emergent (wrap under `V_x`, anyone `x` vouched for decrypts). - **Distribution is unilateral.** Alice vouching for Bob = Alice gives Bob a copy of `V_alice`. No request/accept handshake. Bob can immediately read FoF posts that include `V_alice` as a wrap slot. - **Revocation = rotate `V_me`.** There is no per-vouchee revocation. To un-vouch someone, the persona generates a new `V_me'` and re-distributes to every remaining vouchee. Layer 6 stub. - **Epoch tag is part of the key.** Every `V_x` has an associated `(owner_id, epoch)` so receivers can tell fresh from stale when multiple copies arrive (e.g., if voucher rotated). - **Keyring is per-persona, not per-device.** Multi-persona users have independent keyrings. Layer 3 reader logic iterates personas when trial-decrypting. --- ## Data model ### `vouch_keys_own` table Per-persona, stores the persona's own `V_me` history (current + recent-past for graceful rotation). TBD — OPUS: exact column types, key size in bytes, how many past epochs to retain. ``` vouch_keys_own( persona_id BLOB, epoch INTEGER, key_material BLOB, -- TBD — OPUS: symmetric key bytes (32B for a 256-bit key?) created_at_ms INTEGER, is_current INTEGER, -- 1 for the active V_me, 0 for retained past epochs PRIMARY KEY (persona_id, epoch) ) ``` ### `vouch_keys_received` table Per-persona, stores vouch keys received from other personas (one row per `(owner_id, epoch)` currently held). ``` vouch_keys_received( holder_persona_id BLOB, -- whose keyring this entry belongs to owner_id BLOB, -- the persona who owns (issued) this V_x epoch INTEGER, key_material BLOB, received_at_ms INTEGER, PRIMARY KEY (holder_persona_id, owner_id, epoch) ) ``` Readers compute their keyring as `SELECT key_material FROM vouch_keys_received WHERE holder_persona_id = ? UNION SELECT key_material FROM vouch_keys_own WHERE persona_id = ? AND is_current = 1`. --- ## Key generation TBD — OPUS: specify the primitive. Candidates: - Random 32B from CSPRNG (simplest; `V_me` is pure symmetric secret) - HKDF-derived from persona identity key + epoch counter (allows deterministic re-derivation; couples key rotation to identity key exposure) Lead leaning: **random 32B CSPRNG, stored encrypted at rest alongside identity key material**. Identity-key coupling offers no defensive benefit given the keyring is already indexed per-persona in the same DB. --- ## Wire format for vouch distribution A `VouchGrant` message carries a copy of the voucher's current `V_me` from voucher to vouchee. TBD — OPUS: exact byte layout. Target shape: ``` VouchGrant { voucher_persona_id: NodeId, -- sender's persona epoch: u32, key_material: [u8; 32], -- V_voucher at this epoch issued_at_ms: u64, sig: [u8; 64], -- voucher's identity-key signature over the above } ``` Delivery: wrap `VouchGrant` inside the existing `Direct` (encrypted-to-one-recipient) post primitive. Do not introduce a new top-level control message. Content-type byte distinguishes vouch grants from regular DMs. Recipient's receive-path decodes, verifies signature, inserts into `vouch_keys_received`. TBD — OPUS: decide whether vouch grants should use a reserved visibility intent (`VisibilityIntent::VouchGrant`) for filtering out of the Messages tab, or whether they ride under `Direct` and are suppressed client-side by content-type. --- ## UI / UX Minimum viable surface for Layer 1 ship: - **Persona screen**: "Vouch for someone" button. Picker of contacts. Hands them a `V_me`. - **Persona screen**: "Who has vouched for me" list (reads `vouch_keys_received` grouped by `owner_id`). - **Persona screen**: "People I've vouched for" list (local-only; TBD — OPUS on whether to track this explicitly or derive from DM-sent history — see open question below). - **Settings**: "Rotate my vouch key" button → generates new epoch, queues re-distribution to tracked vouchees. Layer 1 ships without any post/comment behavior change. Vouches are visible in UI but don't gate content yet. --- ## Open questions - **Do we track outbound vouches explicitly?** Option A: local `vouches_issued(recipient_persona_id, epoch_at_grant)` table. Option B: derive from DM-sent history searching for `VouchGrant` payloads. A is simpler + survives DM history loss; B avoids a redundant record. Lead leaning: A. - **Re-distribution on rotation.** When persona rotates `V_me`, do we auto-queue `VouchGrant` DMs to every tracked vouchee, or require user-initiated re-vouch? Lead leaning: auto-queue with a confirmation summary ("rotate will re-send vouch to N people"). - **Key size.** 256-bit symmetric key is standard and matches our ChaCha20-Poly1305 usage. Confirm with Opus there's no reason to prefer 192 or 128 bits. - **Epoch granularity.** Monotonic counter per persona, or wall-clock-based? Counter is simpler; wall-clock aids debugging. Lead leaning: counter. - **Should `V_me` ever be exported in the persona backup bundle?** Losing `V_me` means every FoF post gated under it becomes permanently unreadable for every vouchee. Lead leaning: yes, include in identity export. --- ## Ship criteria for Layer 1 - All personas auto-generate `V_me` at creation. - Users can vouch and receive vouches end-to-end via DM-wrapped `VouchGrant`. - UI shows received and issued vouches per persona. - `V_me` rotation works; re-distribution to tracked vouchees is demonstrated. - No change to post visibility / comment behavior. - Integration test: two personas on two devices, Alice vouches for Bob, Bob's `vouch_keys_received` contains `V_alice` with correct signature.