Skip to content

Transport and the mailbox

SSP/1 defines message semantics. It does not define how bytes move.

That is deliberate. A predecessor protocol bound its versioning to a specific peer-to-peer stream identifier, which meant its entire version story lived in a transport string and a change of transport was a change of protocol version. Keeping the two apart lets a mesh choose a transport on operational grounds without renegotiating what the protocol is.

Two roles must exist. They are not substitutes for one another.

Role Purpose
Live transport Direct exchange between nodes that are online at the same time
Mailbox Store-and-forward for nodes that are never online simultaneously

A mesh MAY run either alone, and MUST run at least one. A mesh with only a live transport cannot serve intermittently connected nodes; a mesh with only a mailbox pays store-and-forward latency for peers sitting next to each other.

Requirement Detail
Confidentiality Payloads MUST NOT be readable by any party outside the mesh, including the mailbox operator
Integrity Tampering MUST be detectable
Peer authentication A live binding MUST authenticate the peer’s channel key, and the trust resumption rule then applies. A binding that cannot MUST say so, and event authorship then rests solely on per-event signatures
Framing One request and one response per exchange. A binding claiming realtime mode MUST additionally carry unsolicited one-way bodies
Version negotiation See messages

A binding MUST document which of these it provides and how. A consumer inherits the weakest guarantee in the chain, and cannot reason about that without the statement.

Note the interaction with event signatures: because every event is individually signed, a binding that fails to authenticate its peer degrades confidentiality and availability but not event authorship. That is the property that makes an untrusted intermediary a routing detail.

A mailbox is an asynchronous channel moving opaque payloads between nodes of one mesh. It is the same abstraction the MLS architecture calls a Delivery Service ([RFC 9750]), and where SOP/1 is deployed — which requires a Delivery Service for MLS anyway — one service SHOULD fill both roles rather than running two store-and-forward systems side by side.

Property Requirement
Opacity The operator MUST NOT be able to read payload plaintext
Addressing Payloads are addressed to a site_id within the mesh
Durability An accepted payload MUST be retained until delivered or until a stated retention bound elapses
Retention bound MUST be stated and finite
Delivery At-least-once; duplicates MUST be permitted
Ordering None guaranteed; consumers MUST NOT depend on it
Quota An operator MAY impose one, and MUST reject rather than silently drop

The retention bound is a privacy control as much as an operational one: an unbounded mailbox is an indefinite archive of traffic metadata held by a party outside the trust boundary.

4. What the mailbox operator can and cannot do

Section titled “4. What the mailbox operator can and cannot do”

The operator is inside the availability boundary and outside the confidentiality boundary.

It can withhold, delay, reorder, duplicate and drop payloads, and it observes payload sizes, timing, and which site_id is corresponding with which. Those are real attacks and this contract does not prevent them.

It cannot read payloads, forge events, or forge membership decisions.

The consequence for a consumer: mailbox silence is inconclusive. It never constitutes evidence that no message was sent. Where a decision depends on having seen everything, the consumer needs independent evidence rather than an absence.

Where a binding pads payloads, it SHOULD use a target-length function whose overhead is bounded and whose bucket boundaries are not ad hoc, rather than inventing size classes. Padding reduces length leakage; it does not eliminate traffic analysis, and an implementation SHOULD NOT describe it as doing so.

The default live binding is mutually authenticated QUIC: TLS 1.3 where each endpoint authenticates with an Ed25519 channel key rather than a certificate chain — the raw-public-key shape of [RFC 7250]. This is deliberately the transport family SOP/1 already brings, so a mesh running both layers carries one transport stack.

The channel key is not the identity key. IK_priv may live in a secure element that signs rarely and deliberately; a channel key negotiates connections constantly and may rotate freely. The two are joined by a transport binding record:

TransportBinding = COSE_Sign1(
protected = {
alg : EdDSA,
content type : "sovm/ssp-binding-v1",
kid : site_id (16 bytes)
},
payload = deterministic CBOR {
channel_pub : bstr 32,
generation : unsigned
},
external_aad = mesh_id (16 raw bytes)
)

Rules:

  • The record is signed by the node’s root identity key, so it asserts “this channel key speaks for this site_id” with the same authority as the identity itself.
  • generation is monotonic per site_id; a record supersedes all lower generations for that node. Channel-key rotation is therefore a new record, not a new identity — site_id never changes.
  • Records travel in the sync exchange’s records member, through the mailbox, or in-band during connection establishment. They are self-verifying.

Pairing happens once. Every connection after it is authenticated by the binding, with no human in the loop and no second ceremony:

  1. The binding authenticates the peer’s channel key.
  2. The channel key MUST resolve, through a valid transport binding record, to a site_id whose identity key is in the local trusted-peer keyring.
  3. Any failure — unknown channel key, no valid binding record, kid absent from the keyring, or a resolved identity that differs from the expected peer — is a hard fail: terminate the connection and surface the event.

The hard fail is the security property. An implementation MUST NOT respond to a resolution failure by falling back to a pairing prompt, because a prompt on mismatch is exactly how a machine-in-the-middle converts a detected attack into a second chance. Recovery from a genuine re-provisioning is an explicit, human-initiated re-pair, never an inline dialog.

The mailbox path needs no resumption at all: mailbox traffic carries signed events and self-verifying records, so there is no session trust to resume.

Removal composes cleanly: revoking a node removes its keyring entry, after which step 2 fails for every binding record it ever published — its live connections die with its identity.