Guides

Architecture

Komms is a local-first, server-independent messaging system. Every installation is a full peer: it holds its own keys, stores its own history, and can relay for others. No component that carries or stores messages must be operated by the project or any single party. Optional post-pairing rendezvous and native-wake services may accelerate mobile reachability under ADR-0017, but they are neither message transports nor dependencies of the core.

1. Layer model

Applications
Desktop (Tauri)Mobile (iOS / Android)Headless node / relay
kult-ffi
Stable, async, language-neutral API (UniFFI)
kult-node: runtime
Session managerDelivery engineTransport scheduler
kult-protocol
Envelope codecFragmentation / reassemblyGroup state
kult-store
Encrypted SQLite
kult-transport
libp2p (QUIC/TCP, DHT, relay)Proximity (mDNS, BLE)Meshtastic bridgeSneakernet
kult-crypto
PQXDH handshakeDouble RatchetX25519 · Ed25519 · ML-KEM-768 · XChaCha20-Poly1305
Dependency rule: arrows only point downward. kult-crypto has no dependency on any layer above it, so the security core can be audited in isolation.

Dependency rule: arrows only point downward. kult-crypto depends on nothing but primitive crates; kult-transport never sees plaintext; kult-store never sees the network. This makes the crypto core auditable in isolation and keeps the trusted computing base small.

2. Crate responsibilities

Crate Owns Must never
kult-crypto Key generation, PQXDH, Double Ratchet, AEAD, fingerprints. Pure functions + opaque state objects; #![forbid(unsafe_code)]; all secrets zeroize-on-drop. Perform I/O of any kind.
kult-protocol Envelope wire format, fragmentation for small-MTU links, sender-key group fan-out, padding. Touch key material directly (only via kult-crypto handles).
kult-transport The Transport trait and its implementations; peer discovery; link encryption. See plaintext or long-term identity secrets.
kult-store Encrypted persistence of messages, sessions, contacts, queues, media, scheduled work, backups, and sealed local metadata. Perform network I/O or transport scheduling.
kult-node Composition: session lifecycle, delivery engine, multipath scheduling, event bus. Reimplement lower-layer logic.
kult-ffi UniFFI-exposed API and embedded runtime for Kotlin, Swift, and shell consumers. Add behavior beyond kult-node.

3. Message lifecycle

Send path

For a scheduled send, kult-node first stores the destination, text, and absolute UTC not_before value in a separately sealed scheduled-outbox row. Until a tick observes now >= not_before, the steps below do not begin: no ratchet advances and no envelope or transport-visible queue row exists. This is what makes pre-activation edit/cancel safe. Clock rollback keeps the row held; clock advance activates it on the next tick; time-zone changes are display-only.

  1. App calls send(conversation, content) through kult-ffi.
  2. kult-node persists the outbound message locally (kult-store) with state queued: the UI is truthful about delivery, and nothing is lost on crash.
  3. kult-protocol serializes content, pads it to the next size bucket, and hands it to the conversation's ratchet.
  4. kult-crypto advances the sending chain, encrypts with XChaCha20-Poly1305, and encrypts the ratchet header.
  5. kult-protocol wraps ciphertext in a sealed envelope: the only cleartext field is an opaque per-recipient delivery token (see §5). If the selected link's MTU is small (LoRa ≈ 200 B), the envelope is fragmented.
  6. Transport scheduler picks the best available transport(s) for this peer, possibly several in parallel (internet + mesh). Duplicate delivery is fine: envelopes are idempotent by message ID; receivers deduplicate.
  7. On receipt of an encrypted delivery receipt, state advances queued → sent → delivered.

Receive path

Mirror image: transport yields envelope → reassembly → dedup by message ID → ratchet decrypt (tolerating skipped/out-of-order counters within the configured window) → persist → event to app → (optionally) send encrypted receipt.

4. Store-and-forward without servers

Peers are rarely online at the same moment, especially off-grid. Delivery uses three mechanisms, in preference order:

  1. Direct: recipient reachable on some transport now → deliver immediately.
  2. Mailbox relays: any Komms node may volunteer relay capacity. The sender deposits the sealed envelope with one or more relays chosen by the recipient (advertised in their signed prekey bundle, 06: Identity & Trust). Relays store ciphertext-only, TTL-bounded, size-capped queues keyed by delivery token. Users naturally relay for their own contacts (friend-relay model); public volunteer relays are additive, never required.
  3. Mesh flooding / sneakernet: on Meshtastic, envelopes propagate hop-by-hop with the mesh's own store-and-forward; any node that later gains internet can bridge queued envelopes onward. Fully offline, envelopes export as .kkb files; animated message-bundle QR remains planned.

A message may traverse all three; deduplication makes redundancy safe and encouraged.

4.1 Optional reachability and wake acceleration

ADR-0017 defines Sovereign, Private, and Standard modes over the same core. In the optional modes:

  1. ADR-0018 stores a fixed-size encrypted route record under rotating provider- and direction-specific pairwise slots. It supplements post-pairing hints; it never replaces signed DHT/QR first contact or a mailbox.
  2. ADR-0019 lets a sender present a bounded per-contact capability after a direct peer or mailbox accepted the sealed envelope. APNs/FCM receives a static wake shape and no conversation data.
  3. kult-node merges source-scoped expiring hints, lets F4 probe them, and runs one bounded generic collection cycle on wake. Optional-service responses do not authenticate peers or change message delivery state.

Pure derivation and record sealing remain in kult-crypto; bounded encodings remain in kult-protocol; I/O adapters receive only opaque requests in kult-transport; orchestration remains in kult-node. Rendezvous and wake server binaries are outside the client dependency graph. Blackholing every such server must reproduce Sovereign-mode behavior without migration or data loss.

5. What intermediaries see

A relay, DHT node, or mesh repeater carrying Komms envelopes observes only:

  • an opaque, rotating delivery token (unlinkable to the recipient's identity key by anyone but the recipient and, per-message, the sender),
  • a padded ciphertext in one of a small set of standard size buckets,
  • transport-level source of the immediately preceding hop (unavoidable at layer 4).

No sender identity, no recipient identity, no timestamps beyond arrival time, no conversation linkage. This is the sealed sender property; the construction is specified in 04: Cryptography §7.

This paragraph does not describe an enabled optional rendezvous or native-wake service. Their bounded but non-zero metadata surfaces are listed in 02: Threat Model and ADR-0017.

6. Groups

v1 groups use sender keys: each member maintains a per-group sending chain, announced to members over existing pairwise ratchet sessions; a group message is encrypted once and fanned out. Membership changes re-key. This is efficient over constrained links (one ciphertext, not N) and adequate for small-to-medium groups. Large-group semantics (MLS, RFC 9420) are a documented later milestone; see decision record ADR-0003 and 08: Roadmap.

7. Repository layout

komms/
├── Cargo.toml            # workspace
├── crates/
│   ├── kult-crypto/
│   ├── kult-protocol/
│   ├── kult-transport/
│   ├── kult-store/
│   ├── kult-node/
│   ├── kult-ffi/
│   └── kultd/             # daemon + CLI
├── apps/
│   ├── desktop/          # Tauri (M5)
│   ├── android/          # Kotlin shell over kult-ffi (M5)
│   └── ios/              # Swift shell over kult-ffi (M5)
└── docs/                 # this documentation

Build order and per-crate API sketches for implementers: 09: Implementation Guide.

Edit this page on GitHub ↗