smolmail/reticulum.md

149 lines
14 KiB
Markdown

# Smol Mail Protocol, version 1.2 -- Reticulum transport
**Status: proposal.** Nothing here is implemented. This document is an addendum: version 1.2 is version 1.1 plus these sections, and it edits nothing that came before. §1 to §12 refer to `SPEC.md`, whose sections are unchanged, and the numbering continues from there so this text can fold into that document without renumbering anything.
A mailbox reachable over the Reticulum Network Stack needs no DNS name, no IP address and no fixed topology. It works over LoRa, packet radio, serial links, I2P or TCP, and it keeps working when the internet does not.
- Reticulum replaces the transport of §4. It does not wrap it.
- An address grows a second form. Identities, envelopes, message identifiers, operations, rotation and accept tokens are untouched.
- A server MAY offer either transport or both over one store.
RNS facts cited below were read from Reticulum 1.5.4. Constants in that stack are version-dependent and some are undocumented, so an implementation MUST read them from the stack at runtime rather than compile them from this text, and SHOULD state the version it was tested against. A reader who finds a figure here contradicted by the stack SHOULD believe the stack.
## 13. Reticulum transport
### 13.1 Server destination
A server holds one **Reticulum identity**, persisted, separate from every Smol Mail identity in §2 and never used as one. RNS writes its private key unencrypted, so the file MUST be protected as any long-term server key is: mode 0600, backed up by the operator, never transmitted.
```
RNS.Destination(identity, IN, SINGLE, "smolmail", "server")
```
The full name is `smolmail.server` and the **destination hash** is 16 bytes, derived by RNS from that name and the server identity's public keys. Nothing random enters that derivation, so the hash is stable across restarts for as long as the identity file survives, and it changes if and only if the identity does.
- A server MUST construct its destination from a loaded identity. Passing none makes RNS generate a throwaway identity and append its hex hash as a third aspect, which yields a different address on every start.
- A server that relies on ordinary path discovery MUST announce its destination periodically. A path request is answered from a node's path table, and a path table is populated by announces; paths expire, soonest on access point and roaming interfaces, so the announce interval MUST stay inside the shortest expiry the stack applies. Interfaces rate-limit a destination to roughly one announce an hour, which sets the ceiling; every few hours is enough.
- A server answers a path request for its own destination whether or not it has ever announced, so an unannounced destination is reachable by a client that can reach it directly, or through nodes whose interfaces search for unknown destinations -- access point, gateway, roaming and boundary modes, or recursive path requests. An unlisted mailbox is therefore dependable on one segment and a matter of the operator's configuration beyond it, not a property this protocol can promise.
### 13.2 Addressing
```
smol+rns://alice@8f2c1d0a7b6e5f4c3d2a1b0e9f8c7d6a short form, resolved via the server
smol+rns://alice@8f2c1d0a7b6e5f4c3d2a1b0e9f8c7d6a/mfrggzdfzt... self-certifying, carries its own key
```
The authority is the username of §3 and the server's destination hash as **32 lowercase hexadecimal characters**. Hexadecimal rather than the base32 of §3 because that is how every Reticulum tool prints a destination, and an address is something a user copies from `rnstatus` or an operator's page.
- The scheme is required in both forms. There is no bare `user@host` short form on this transport, because a destination hash is not distinguishable from a hostname by shape.
- A client MUST reject an authority whose host part is not exactly 32 hexadecimal characters, and MUST compare destination hashes in full.
- There is no port. The `:port` suffix of §3 MUST NOT appear.
- Usernames, their normalisation, and the 52-character base32 identity key in the path are exactly as in §3. Fingerprints are of that key and are unaffected.
- A `Reply-To` (§5.7) and a contact imported from a URI or QR code MAY carry a `smol+rns://` address. Every check in §5.7 applies unchanged: the key in the URI MUST equal the payload's `sender`, and it MUST NOT replace a key already bound.
Reticulum has no name resolution -- its resolver is a stub -- so the hash is the address. Naming is out of scope here as it is in RNS itself.
### 13.3 Discovery
A client resolves a destination through Reticulum's own path discovery:
- If no path is known, request one and wait. The default path request timeout is 15 seconds; slow media widen it.
- Recall the identity for the hash, build an `OUT`/`SINGLE` destination from it, then establish a link.
- A client MUST NOT create a link before a path is known. With no path RNS assumes the maximum hop count and the link fails after many minutes instead of promptly.
### 13.4 Server authentication
There is nothing to pin. A destination hash is a 128-bit truncated hash over the server identity's public keys, and Reticulum establishes the link against exactly that identity: the responder signs the link proof with its long-term key, and RNS verifies it. **The address is the pin.** Finding a second identity that answers to a given destination costs about 2^128 trials, the same argument §3 makes for fingerprints.
So §4's pinned and unpinned sessions become one question -- where did this address come from -- and every rule §4 states carries over with the address standing in for the pinned key: `REGISTER`, `FETCH` and `DELETE` require a destination obtained from a trusted channel, `SEND` does not when the sender already holds the recipient's identity key, and `RESOLVE` results from a destination of unknown provenance MUST be marked unverified.
There is consequently nothing for a client to trust out of band and no key to install: §4's pinning step has no counterpart here. A client SHOULD instead record how it learned each address -- out of band, from a URI or QR code, from a `Reply-To` (§5.7), or from somewhere unknown -- and read §8's table by that column, since the address and the server key are now one thing.
### 13.5 Requests
Operations map onto Reticulum's request and response mechanism on a single path, `smolmail/1`, which carries the version tag that the Noise prologue carries on the other transport. RNS transmits a path only as a 16-byte hash, so the tag is free.
```
request → type u8 || body # type from §6, body exactly as in §6.1
response → status u8 || payload # status from §12, payload exactly as in §6.1
```
Every operation body and every response payload in §6.1 is reused byte for byte. Only the `length u32` of §4 is gone, because Reticulum delimits messages itself.
- A server MUST answer every registered request, including with an error status. The default handler policy answers nothing at all, which a client cannot distinguish from a dead link.
- A server MUST bound the request size. The largest legitimate request is the greater of an envelope plus 34 bytes and an `AUTH` carrying a full token set, which is about 32 KiB at the 1024 tokens of §10.
- Requests and responses above the link's maximum data unit become Reticulum resources automatically, in both directions. Nothing in the protocol has to name them.
- A client MUST set its own request timeout. Reticulum's default is derived from the round trip time and covers a packet, not a bulk transfer over a slow link.
- A response status is not a transport outcome. No path, a failed link, a rejected resource and a timeout are local errors, not status codes, and a client MUST NOT report them as one.
### 13.6 Session authentication
The link is the session. `AUTH` is an ordinary request on the same path, and a server keys its session state on the link identifier, discarding it when the link closes. `FETCH` and `DELETE` on a link that has not authenticated return status 4, as on the other transport.
The bodies of `AUTH` (§4) and `REGISTER` (§6.1) do not change. Only the two values those signatures borrow from the Noise handshake are substituted:
```
h = SHA-256("smolmail/1 bind" || destination || link_id) # replaces the Noise handshake hash
server_static = SHA-256("smolmail/1 bind" || destination) # replaces the Noise static key
```
`destination` is the 16-byte hash the client dialled and `link_id` the 16-byte link identifier both ends derive from the link request. Both substitutes are 32 bytes, which keeps the concatenations of §4 and §6.1 as unambiguous as they already are -- neither field is length-prefixed, so a substitute of another width would let one transport's signature be reinterpreted under the other.
What each half buys:
- `destination` binds the signature to this server, so a malicious server cannot replay a client's `AUTH` or `REGISTER` elsewhere. This is what `server_static` does in §6.1.
- `link_id` binds it to this link. RNS derives the identifier from the initiator's per-link ephemeral keys, so a client MUST establish a fresh link per session and MUST NOT reuse one across authentications.
- Unlike the Noise handshake hash, a link identifier is **not** secret: it addresses every packet on the link and every transit node sees it. The argument therefore rests on the signature travelling inside the encrypted link and on the binding being unique per server and link, not on the binding being unguessable.
### 13.7 Sizes and budgets
This is where a mesh differs most. The 768 KiB envelope of §10 is over half an hour on a LoRa link at 391 bytes a second, and the 512 KiB `FETCH` budget suggested in §6.1 is worse. A server reachable over Reticulum SHOULD therefore run much smaller limits. Recommended defaults, all configurable:
```
32 KiB per envelope 32 KiB fetch budget
```
Nothing advertises these and nothing negotiates them. Status 7 and status 6 are the authoritative answer, and a client SHOULD remember a cap it learned that way rather than discover it twice. The `FETCH` budget is the server's alone -- a client never chooses it -- and a server MUST keep §6.1's guarantee of at least one record per response so that no single message can wedge a mailbox shut.
A mailbox reachable over both transports has one queue, and §6.1 requires a record to be returned whole, so an envelope accepted at the 768 KiB cap over TCP is still a half-hour fetch for a mesh client. Nothing breaks -- time is spent -- but an operator whose users are mesh-first SHOULD apply the smaller cap to the whole mailbox rather than per transport.
### 13.8 Abuse control without addresses
The per-IP connection and `SEND` limits of §10 have no analogue. Reticulum gives a server no stable handle on an initiator that does not identify itself, and that is the point: a sender is absent from the wire format by construction (§5.2), and a transport that reintroduced a durable sender identifier would undo it.
- A server MUST NOT require Reticulum link identification for any operation. It proves a Reticulum identity rather than a Smol Mail one, and a durable one is exactly the handle this section says a server must not have.
- A server SHOULD limit requests and bytes per link, and cap concurrent links. Reticulum enforces neither.
- A server SHOULD keep a global `SEND` ceiling in place of the per-peer one.
- The per-accept-token `SEND` limit of §10 is unchanged and becomes the main defence. It never needed a peer identity: a token is issued by the mailbox owner (§5.8), which is exactly the handle this transport still has.
Reticulum's own blackholing reaches identified peers only, so it is unavailable here. Its interface-level ingress control and a server's ability to stop accepting links remain as load-shedding measures.
### 13.9 Privacy over Reticulum
§9 holds, with three amendments.
A server no longer learns a connecting IP address. It learns an ephemeral link identifier and nothing else about who is talking to it, which is strictly better for senders than the transport of §4. It still learns which mailbox each envelope is for, its size, and when it arrived, was fetched and was deleted.
A transit node learns the server's destination hash when a link is requested, and after that only link identifiers, packet sizes and timing. It learns neither the mailbox nor anything about an envelope. Traffic analysis, delivery timing and mailbox size remain unprotected, as in §9.
An announced destination is discoverable network-wide, which is a new exposure for an operator rather than a user; §13.1 leaves announcing optional for that reason. The cost of all this is in §13.8: the operator trades per-IP abuse control for it. Where §9 recommends a Tor onion service for metadata resistance, this transport is a second answer.
### 13.10 Queueing, retries and liveness
- The outbound queue lives in the sending client, as §6 already requires. A client retries when a path becomes available.
- Retrying a `SEND` whose outcome is unknown is safe without any new machinery: the identifier is derived (§5.4) and the server answers a resend with the same identifier and no new message (§10). This matters more here, because Reticulum has many more ways to fail ambiguously than TCP does.
- A client SHOULD tear a link down when it is done rather than hold it open on keepalives, which run for minutes.
## 14. Domain separation labels added in 1.2
```
"smolmail/1 bind" link binding (§13.6)
```
The `smolmail/1` prefix is kept because §11 is a label namespace, not a version list, and every existing label uses it.
## 15. Across transports
One store, one set of username bindings, rotation chains, accept tokens and queues. Identifiers agree because they are derived from the envelope, so the same message delivered by either route deduplicates against itself. A username registered over one transport is reachable over the other, and `AUTH` is per-session, so a client may register over one and authenticate over the other.
A 1.1 client and a 1.2 server interoperate over TCP with no negotiation, because there is nothing to negotiate. The implementation is an adapter around the existing operation dispatch: the same request bodies, the same handlers, the same database. Neither LXMF nor any other Reticulum messaging format is involved.