Docs Menu

Specs

spec/03-cross-domain.md · shown exactly as it appears in the repository

03 — Cross-Domain Connections

Status: Draft, v0.1. License: CC-BY-4.0 — see LICENSE-DOCS.

Overview

A connection established under one domain may be extended to cover additional domains without creating a second connection. Extending a connection MUST reuse the existing key pair and shared secret, registered a second time under the new domain — generating new key material would make it a second, distinct connection, not an extension of the first. This lets one pairwise relationship serve multiple applications at once (see 00-overview.md for the distinction between a domain and the application implementing it).

Nothing requires two parties to extend their connection to every domain they share. Either party can instead keep a separate connection scoped to one domain, established the ordinary way (02-connections.md) — there's no special mechanism for this, the user can choose which option to take.

The domain field

A connection record holds a domain field that is an array, not a single value — initially containing the one domain it was established under, and gaining one entry each time it is successfully extended to a new domain. The domain array MUST include every domain the connection is currently registered under — it is the authoritative record of which domains the connection spans.

Per-domain display information — a party's name and image, which may differ by domain — is optional; a user is not required to set it per domain. Whenever it is set, it MUST be stored in a cxnDomainInfo record scoped to that (connection, domain) pair, never on the connection record itself. Two domains sharing a connection may display entirely different names for the same underlying relationship.

Extending a connection to a new domain

  1. Requester reuses the connection's existing key pair unchanged. It appends the new domain to its own domain array and creates a cxnDomainInfo record for it with status pending. It sends a connection / cxnDomainRequest message — containing the new domain and its display name/image for that domain — addressed via the connection's existing sendKey, but routed under whichever domain the two parties already share (the peer has not registered anything under the new domain yet, so it cannot yet be reached there). It then registers its own key a second time, now scoped to the new domain, pointing at whichever backend serves that domain for it.
  2. Peer receives the request and simply records it — appending the new domain to its own domain array and creating a cxnDomainInfo record with status pending. An implementation MUST NOT auto-accept or auto-decline the request — extending to a new domain requires the same explicit consent as establishing the connection itself. Accepting or declining is a separate, later action taken by the user.
  3. Peer accepts: registers its own key under the new domain first — safe to do immediately, since the requester already registered there in step 1 — then marks its cxnDomainInfo for the new domain as cxnEstablished and sends a cxnDomainAccepted message back, routed under the new domain itself.
  4. Peer declines: removes the new domain from its own domain array, deletes the cxnDomainInfo record, and sends a cxnDomainDeclined message back, routed the same way the original request was (the domain the two parties already share) — the peer never registered anything under the rejected domain, so it cannot route through it. An implementation MUST fully remove this pending-domain state on decline, the same complete-removal requirement as ending a connection (see "Ending a connection" in 02-connections.md).
  5. Requester receives acceptance: marks its cxnDomainInfo for the new domain as cxnEstablished. No further registration is needed — it already registered in step 1.
  6. Requester receives decline: removes the new domain from its own domain array, deletes the cxnDomainInfo record, and deregisters the key it had proactively registered under that domain in step 1 — the same MUST applies here: nothing is left behind from the declined extension.

Why domain-scoped registration, not a new connection

The router's registry is keyed on (sendKey, domain), not on sendKey alone (see 02-connections.md). The same key value can therefore be registered independently once per domain, each registration pointing at whichever backend and routing information is correct for that domain — which may belong to an entirely different application than the one that first established the connection. Extending a connection to a new domain is a matter of registering an already-existing key a second time, not minting new key material or performing a new handshake.

Invariant: register before you can be addressed

A party MUST register a key under a domain before sending any message routed under that domain — never after. This is why step 3's acceptance registers before sending cxnDomainAccepted (routed under the new domain itself), while step 1's request can register after sending cxnDomainRequest (routed under the domain the parties already share, not the new one) — the rule only binds the sender of a message to the domain that message is actually routed under. The same rule governs the connection handshake in 02-connections.md.