Messaging & Transport¶
How a chat is described, joined, routed, and carried. Deep specs:
docs/DESIGN.md, docs/WIRE.md. Source: crates/core/, crates/transport/,
crates/topology/, crates/wire/, crates/crypto/treekem.rs.
Invites (chat descriptors)¶
A chat is a descriptor serialized to a talkrypt://<base32> URI (also a QR):
it encodes version, topology, persistence (ephemeral/persistent),
suite id + params, endpoint(s) (.onion + a client-auth
key), a one-time invite token, the initial channel, a group flag, and an
optional classification marking. A peer
missing the named suite gets a precise error telling them which suite to enable.
The invite token is a one-time pre-shared secret that seeds the session root key, so only descriptor-holders can complete a handshake — the first-contact MITM defense. An optional channel password is mixed into the root via Argon2id and shared out-of-band, so capturing the link alone still can't join.
Handshake¶
A dialer and a responder exchange signed prekeys (ML-DSA-87) and derive the session root from the invite token + authenticated peer fingerprints (HKDF). Wrong root ⇒ different keys ⇒ frames fail to decrypt uniformly (no partial plaintext). Post-handshake, peers compare safety numbers out of band to confirm no MITM.
Topologies¶
| Topology | How it routes | The relay sees |
|---|---|---|
| P2P | every peer hosts its own onion and dials the others (full mesh) | n/a — no relay |
| Hub | one onion relays (IRC-like); members use rotating sender keys | ciphertext only + connection metadata |
| Hybrid | hub for rendezvous/presence; messages flow P2P | rendezvous metadata only |
A non-member RelayHub (relay.rs) can forward opaque ciphertext without ever
holding the group key.
Groups (TreeKEM)¶
Group chats use a TreeKEM continuous group key agreement (treekem.rs,
RFC-9420-inspired with PQ nodes: ML-KEM-1024 + X25519 hybrid keys):
epoch-sequenced Add/Remove/Welcome commits, O(log N) path re-keying per
epoch, and removal forward secrecy (a removed member can't read future
messages). Sender attribution is roster-based. (It's a custom PQ construction,
not MLS-wire-conformant — no MLS interop by design; see docs/CONFORMANCE.md.)
Transports¶
- Tor / Arti onion services (production) — in-process onion via
arti-client tor-hsservice. Ephemeral (fresh keypair per session, never persisted) or persistent (stable.onion, key sealed at rest). Restricted discovery via onion client authorization: only holders of the descriptor's auth key can resolve/reach the service — non-enumerable, doesn't advertise the operator. Censorship circumvention via bridges + pluggable transports (obfs4, Snowflake).- TCP (
tcp.rs) — the default development transport. - Loopback (
loopback.rs) — in-process; full protocol + crypto testing with zero network.
Persistent servers offer keep-alive strategies (always-on daemon,
client-anchored, replicated failover) and keep no metadata logs of who
connected when (docs/DESIGN.md §8).
Wire format¶
Length-prefixed frames bounded by MAX_FRAME (16 MiB) — no allocation on a
hostile length prefix, and the decoder is fuzzed + Kani-proven for bounds
(Security Assurance). A Double-Ratchet message is
header ‖ AEAD(ciphertext) with the header (ratchet public, KEM ciphertext,
counters) bound as AEAD associated data. Full grammar: docs/WIRE.md.
Replay & integrity defenses (summary)¶
- Per-message counter + bounded skipped-key cache → replays rejected.
- AEAD binds the header as AAD → tampering or misrouting fails the open.
- Decrypt-on-clone → a forged frame never corrupts session state.
- Uniform failure → no oracle from why a decrypt failed.