Normative · Part
Transport and the mailbox
23. SSP/1 specifies no transport binding
Section titled “23. SSP/1 specifies no transport binding”SSP/1 defines message semantics. It does not define how bytes move.
That is deliberate. A protocol that binds its versioning to a specific peer-to-peer stream identifier has made a change of transport a change of protocol version: its entire version story lives in a transport string. 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.
24. What a binding MUST supply
Section titled “24. What a binding MUST supply”| 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.
25. The mailbox contract
Section titled “25. The mailbox contract”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.
Section 5.1 of RFC 9750 gives the Delivery Service two functions: message delivery and a directory of initial keying material (KeyPackages). SOP/1 §5.3 relies on KeyPackage delivery to justify its single-credential decision (“the delivery service already links node to group through routing and KeyPackage delivery”). §25.1 carries the message-delivery function. §25.2 carries the KeyPackage directory function — not a new capability, but the half of the claimed role that SSP/1 previously left implicit.
25.1. Message delivery
Section titled “25.1. Message delivery”| 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 | A payload accepted for a currently-trusted recipient MUST be retained until delivered or until a stated retention bound elapses. The trust qualifier is deliberate: a mailbox retains nothing on behalf of a node the mesh has removed or retired |
| 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.
25.2. Key publication
Section titled “25.2. Key publication”A node publishes KeyPackages under its own site_id into a directory co-located
with the §25.1 mailbox. A KeyPackage is a public object — its
SOP/1 §5.3 binding transcript is
COSE_Sign1 under the publisher’s IK_priv, so a fetcher verifies authenticity
against the ik_pub of a currently trusted, not retired peer
(identity §20.4) in its own trusted-peer keyring,
without trusting the directory’s assertion. A retired node’s row still holds its
ik_pub, and a fetcher MUST NOT accept that node’s KeyPackages under it. The §25.1
Opacity row does not apply: there is no plaintext to protect.
| Property | Requirement |
|---|---|
| Authorization | Publication MUST be authenticated by the §28 transport binding; a node MAY publish only under its own site_id |
| Addressing | KeyPackages are addressed to a site_id |
| Read | MUST be authenticated to a current mesh member; consuming for a single-use package; the last_resort package is served on exhaustion of single-use stock and is non-consuming |
| Membership gate | A node the mesh has removed or retired MUST NOT be served, and its stock MUST be purged on removal or retirement — not on the next retention sweep |
| Retention | Governed by supersession and mesh membership, not by a clock. A package is held until replaced by a higher credential_generation, consumed by a fetch, or purged on membership removal. The §25.1 retention bound does not apply to the key store: applying a finite time bound would purge last_resort packages for the offline devices that most need them |
| Operator verification | The operator MUST NOT be required to verify the SOP/1 §5.3 binding transcript; the fetcher performs that check against its own keyring |
26. What the mailbox operator can and cannot do
Section titled “26. 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. With a key store deployed under
§25.2, the operator additionally chooses which KeyPackage it serves within a
site_id and can withhold stock to force last-resort reuse; it still cannot
substitute a KeyPackage across identities.
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.
27. Padding
Section titled “27. Padding”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.
28. Default binding profile
Section titled “28. Default binding profile”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 : ES256, 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. - The record is a chained record,
narrowed to generation-only. The payload above is exhaustive and carries no
predecessormember: a binding is wholly superseded, never appended to, so every binding record is a genesis record and the chain has no link to carry. That is the narrowing chained records §12 permits, stated here on the instantiation’s own page as that section requires. - Substrate §11’s density rule is therefore inapplicable to this chain rather than weakened by it. The rule is stated relative to a record’s predecessor; a binding record has none, so there is nothing on this chain for it to constrain. This is why the narrowing stays inside §12’s bar that an instantiation MUST NOT weaken a verification rule — nothing is relaxed, a premise is simply absent.
generationis monotonic persite_id; a record supersedes all lower generations for that node, and a successor need only exceed its predecessor rather than carry exactly one more. Channel-key rotation is therefore a new record, not a new identity —site_idnever changes.- Records travel in the sync exchange’s
recordsmember, through the mailbox, or in-band during connection establishment. They are self-verifying.
The record’s alg follows the root identity suite; channel_pub stays
bstr 32, and the asymmetry is deliberate. The channel key is an ordinary
software key that rotates freely and is bound to the node by the record’s
signature rather than by its own custody, so the root identity suite — which
reaches the key held in the secure element — does not reach it. Placing the root
on P-256 leaves one curve on the device rather than two, and that is about the
hardware-held key; the channel key does not weaken it, because an SSP/1 node
already carries Curve25519 for the X25519 pairing ephemeral regardless, so the
Ed25519 channel key adds no primitive that is not already present. Whether the
channel key should nonetheless move to P-256 is an open question this
specification does not decide.
29. Trust resumption
Section titled “29. Trust resumption”Pairing happens once. Every connection after it is authenticated by the binding, with no human in the loop and no second ceremony:
- The binding authenticates the peer’s channel key.
- The channel key MUST resolve, through a valid transport binding record, to a
site_idthat is a currently trusted, not retired peer (identity §20.4) in the local trusted-peer keyring. - Any failure — unknown channel key, no valid binding record,
kidabsent from the keyring or resolving only to a retired row, 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. Retirement composes the same way, by a different rule: the entry stays, but step 2 asks for a currently trusted peer, which a verification-only row is not, so step 2 fails for a retired node’s binding records exactly as it does for a removed node’s. It is step 2’s predicate that does the work, not the absence of the row.