eleven / ocho Public

Clone
git clone https://amadan.net/eleven/ocho

Plain git — no account needed to clone.

Download

Download this file

1# πŸ€– Cross-instance relay β€” chatting across the trust boundary
2
3Status: **proposal / for discussion** (no code). This is a design sketch, not a
4commitment. It builds on `docs/threads-and-dms.md` (user keypairs, per-thread
5keys, server-blind key delivery) and deliberately re-opens something that
6document calls out as *"intentionally impossible"* β€” cross-instance contact.
7
8Today each chat is a **fully-isolated instance**: its own Go binary, SQLite DB,
9admin token, and subdomain. A user is a member of exactly one roster; a message
10is fanned out in-process to WebSocket clients on that same server and can't leave
11the box. There is no federation, no server-to-server protocol, no user discovery
12across instances. This doc asks: *could* two people on different instances chat,
13and if so, what would it actually cost?
14
15## The key insight: the crypto is the easy part
16
17E2EE here is about **keys, not servers**. A "user" is an ECDH P-256 public key
18with no inherent instance affiliation; a thread key is AES-GCM, wrapped to each
19member's public key via ECIES; the server only ever holds blobs it can't open,
20and messages are self-describing ciphertext (filename/MIME ride *inside* the
21payload β€” `crypto.js`).
22
23So for Alice@family to DM Bob@friends, the cryptographic need is only:
24
251. they hold each other's **public keys**,
262. someone generates a thread key and **wraps it to both** pubkeys (ECIES), and
273. both can **fetch** the ciphertext + their own wrapped key blob.
28
29None of that requires them to share a server. A relay would see exactly what
30today's server already sees β€” opaque bytes. **The E2EE invariant survives
31untouched**; the server was never trusted with plaintext in the first place. The
32wire format needs no real change: `iv||ciphertext` base64url blobs plus one
33wrapped-key blob per recipient.
34
35What a relay actually costs is **metadata privacy** and the **invite-gated trust
36boundary** β€” not confidentiality. Those, not the encryption, are the design work.
37
38## Three topologies (lightest β†’ heaviest)
39
401. **Client-as-relay (no new server).** Alice is a member of both instances; her
41 browser posts the same ciphertext to both DBs. Keys/discovery bootstrap via an
42 out-of-band invite β€” *exactly today's invite-fragment mechanism*. Cheapest,
43 leaks the least (no new observer), but there's no offline delivery to a peer
44 who isn't co-homed, and the "same human, two identities" model is awkward.
45
462. **Instance-to-instance federation.** Instance A `POST`s ciphertext to an
47 inbound endpoint on instance B. Needs server-to-server auth, a way for A to
48 discover B's address, and each instance to trust the other's routing claims.
49 Distributes metadata (no single chokepoint) but multiplies the number of trust
50 relationships an admin has to reason about.
51
523. **Central relay / mailbox.** A third dumb server both instances push to and
53 poll, addressing messages by recipient public key (or a hash of it). Best UX β€”
54 directory-style discovery and offline delivery β€” but it concentrates routing
55 metadata in one place.
56
57## What you'd actually be trading away
58
59The encryption doesn't weaken, but three real properties do. These are the
60decisions, and they're why this is a *proposal* and not a PR that ships code.
61
62- **Cross-instance metadata stops being non-existent.** Today, isolated instances
63 mean the cross-circle social graph *lives nowhere* β€” no server can observe that
64 a member of a kid's-friends instance also talks to a work contact. Any relay
65 creates a place where `{pubkey X ↔ pubkey Y, time T, size N}` is visible, even
66 with content sealed. This is precisely the property the isolated-instance design
67 exists to provide (see the metadata note in
68 `docs/onboarding-and-home-server.md`). Losing it is the central cost.
69
70- **Invite-gating stops being the anti-abuse mechanism.** Right now "you can only
71 be reached if an admin put you on the roster" *is* the spam/abuse control.
72 Cross-instance reachability punches through it, so it must be replaced by an
73 explicit **contact-request / consent** model: no one can open a cross-instance
74 thread with you until you accept.
75
76- **Governance spans two admins.** A shared thread lives under two rosters with
77 two admins and two removal/rotation policies. Whose rules win? Removing someone
78 from one instance doesn't evict them from a thread hosted (or mirrored) on the
79 other; a thread-key rotation on one side has to propagate across the boundary.
80 Push, too, is per-instance today (APNs topic per instance, subscriptions keyed
81 by token) β€” cross-instance delivery needs the peer/relay to trigger it, adding
82 metadata at the push layer.
83
84## A recommended shape (if we do this)
85
86Frame it honestly: **this is a metadata-and-consent project, not a crypto
87project.** The lightest design that keeps the most of what makes lchat good is
88option 1 stretched slightly:
89
90- A **cross-instance invite** bootstraps a shared thread, reusing the exact
91 key-shipping already built for onboarding: the invite fragment carries the
92 thread key wrapped to the recipient, exchanged out-of-band (so no central
93 directory is introduced, and discovery stays a human act).
94- The thread's ciphertext is **mirrored to both instances** so neither side needs
95 to trust a new server and either instance alone can serve history.
96- A **consent step** gates the first message, replacing roster-gating as the
97 abuse control.
98
99Reach for a central relay (option 3) only if offline delivery and directory-style
100discovery are worth conceding the metadata point β€” a genuine trade, not a default.
101
102## Open questions
103
104- **Identity across instances.** Is a person's cross-instance identity their
105 existing per-instance user keypair, or a separate long-term "contact" key they
106 present to multiple instances? The latter enables real discovery but creates a
107 correlatable global identifier β€” the thing isolation currently prevents.
108- **Where history lives** when the two instances have different retention/deletion
109 and one deletes the shared thread.
110- **Trust display.** How does the UI make it unmistakable that a thread crosses
111 the trust boundary (different admin, different rules) rather than looking like
112 any other DM?
113- **Blast radius on compromise.** A relay or federation endpoint that's reachable
114 is new attack surface; even serving only ciphertext, it becomes a place to
115 correlate the graph. Worth a threat model of its own before any code.
116
117## Non-goals (for this doc)
118
119No implementation, schema, or endpoints are proposed here. Sealed-sender /
120private-membership (hiding the graph even from the relay) is a separate, harder
121problem and explicitly out of scope. This exists to make the trade-offs
122reviewable so a future decision is deliberate, not incremental.
123