docs: specify response framing and superseded-key retention

This commit is contained in:
randogoth 2026-09-26 10:37:10 +03:00
parent 6c76d1d6e9
commit 9a31607648

30
SPEC.md
View file

@ -54,7 +54,7 @@ A client MUST NOT `REGISTER`, `FETCH` or `DELETE` against a server whose static
**Framing.** Each Noise message carries a 2-byte length prefix and is at most 65535 bytes. Application frames are `length (u32) || type (u8) || body`, split across Noise messages, with a default maximum of 1 MiB. Every field is length-prefixed; nothing on the wire requires text parsing or delimiter scanning. **Framing.** Each Noise message carries a 2-byte length prefix and is at most 65535 bytes. Application frames are `length (u32) || type (u8) || body`, split across Noise messages, with a default maximum of 1 MiB. Every field is length-prefixed; nothing on the wire requires text parsing or delimiter scanning.
**Session authentication.** `FETCH` and `DELETE` require one `AUTH` frame after the handshake: **Session authentication.** `FETCH` and `DELETE` require one `AUTH` frame — an application frame of type `0x00` — after the handshake:
``` ```
username_len u8 || username || identity 32 || signature 64 username_len u8 || username || identity 32 || signature 64
@ -157,6 +157,30 @@ Because `esk` is wiped, a sender cannot decrypt what they sent. To keep a Sent f
`FETCH` has no cursors, flags or server-side read state; a client loops until it returns nothing. `REGISTER` without a certificate binds a free username, or returns code 9; with one, it rebinds an existing username when the chain validates against the currently bound key. `FETCH` has no cursors, flags or server-side read state; a client loops until it returns nothing. `REGISTER` without a certificate binds a free username, or returns code 9; with one, it rebinds an existing username when the chain validates against the currently bound key.
### 6.1 Request and response bodies
A request body is the operation's payload. A response body begins with `status (u8)` from §12; on `0` the operation's payload follows, on anything else an optional UTF-8 reason string. A response reuses the request's type byte. `AUTH` is type `0x00` and its response carries a status and no payload.
```
RESOLVE → username_len u8 || username
← identity 32 || chain_len u8 || cert 136 × chain_len
SEND → envelope
← id 16
FETCH → (empty)
← count u16 || ( id 16 || received_at i64 || env_len u32 || envelope ) × count
DELETE → count u16 || id 16 × count
← removed u16
REGISTER → username_len u8 || username || identity 32
|| token_len u8 || token || cert_len u8 || cert
← (empty)
```
A zero length means the field is absent. `FETCH` returns records oldest first, up to a server byte budget which MUST be smaller than the maximum application frame; 512 KiB against the default 1 MiB frame is a reasonable choice.
## 7. Key rotation ## 7. Key rotation
``` ```
@ -166,6 +190,8 @@ signature = Ed25519 by old_pub over "smolmail/1 rotate" || old_pub || new_pub ||
A server keeps each username's ordered chain of certificates and returns it with every `RESOLVE`. A client holding a stale pinned key walks the chain forward from that key, verifying each link, and accepts the new key silently if the chain terminates at the key `RESOLVE` returned. A broken, absent or over-long chain — maximum 16 links — requires explicit user confirmation. Senders SHOULD also push their certificate to contacts as an ordinary message, so rotation propagates without depending on the old server. A server keeps each username's ordered chain of certificates and returns it with every `RESOLVE`. A client holding a stale pinned key walks the chain forward from that key, verifying each link, and accepts the new key silently if the chain terminates at the key `RESOLVE` returned. A broken, absent or over-long chain — maximum 16 links — requires explicit user confirmation. Senders SHOULD also push their certificate to contacts as an ordinary message, so rotation propagates without depending on the old server.
A server MUST retain every key ever bound to a username, MUST accept `SEND` addressed to any of them, and MUST return messages addressed to any of them on `FETCH`. Without this, mail from a contact who has not yet seen a rotation is addressed to a superseded key and becomes unreachable.
**Rotation is not revocation.** An attacker holding a stolen identity key can rotate to their own key and the chain will validate. Clients MUST surface rotations in the interface rather than applying them invisibly; out-of-band re-verification is the only defence against a compromised identity key. There is no revocation mechanism in version 1. **Rotation is not revocation.** An attacker holding a stolen identity key can rotate to their own key and the chain will validate. Clients MUST surface rotations in the interface rather than applying them invisibly; out-of-band re-verification is the only defence against a compromised identity key. There is no revocation mechanism in version 1.
## 8. Trust model ## 8. Trust model
@ -213,7 +239,7 @@ A conforming server is a single binary over an embedded key-value store.
2 bad version 5 auth failed 8 rate limited 2 bad version 5 auth failed 8 rate limited
``` ```
A response MAY carry a UTF-8 reason string. Clients MUST NOT parse it. A status is the first byte of every response body (§6.1), which MAY be followed by a UTF-8 reason string. Clients MUST NOT parse the reason.
## 13. Out of scope for version 1 ## 13. Out of scope for version 1