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, native-wake, and Private-ingress services may accelerate mobile reachability under ADR-0017, but they are neither message transports nor dependencies of the core.

The internal Rust crates use the kult-* prefix. KULT expands to Komms Ubiquitous Link Transmission: the shared system that carries one protected conversation across applications, cryptography, storage, and interchangeable online or off-grid links.

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 Account/device key generation, certificates and manifests, PQXDH, Double Ratchet, sync sealing, 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, bounded linked-device bundles/events, 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, devices, sync convergence, queues, media, scheduled work, backups, and sealed local metadata. Perform network I/O or transport scheduling.
kult-node Composition: account/device lifecycle, per-device delivery, deterministic sync, 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.

B9 formatting is deliberately a pure kult-node projection rather than a protocol or storage layer. Apps pass exact authenticated source plus optional authenticated semantic ranges and receive bounded block roles and styled text runs. RPC and UniFFI mirror that model; shells create only inert native text primitives. Exact source continues through the ordinary lifecycle below, so old clients, backups, receipts, padding, and carrier selection are unchanged.

C3 editing uses the opposite storage shape while keeping the same layer boundaries: kult-protocol owns canonical content-v1 Edit; kult-node authorizes exact author/content references and derives the deterministic winner; the existing sealed message tables retain immutable originals and edits; RPC and UniFFI expose only strict identifiers and render-safe history; shells refresh an exact target on typed edit events. Edits still traverse the ordinary ratchet or sender-key lane, so no transport can observe the kind and no shell implements ordering or authorization independently. See 18: Authenticated Message Editing.

C4 ephemeral content crosses every layer without moving deletion policy into a shell or relay. kult-protocol owns content-v1 kind 5 and envelope v2's coarse retention field; kult-node binds capabilities, exact deadlines, queueing, expiry-before-work, and terminal events; kult-store owns sealed lifecycle rows, tombstones, media deletion, and KKR6 exclusion; transports only delete sealed envelopes from the authenticated hour bucket. RPC/UniFFI and apps expose typed choices and honest local-only language. See 19: Disappearing Messages and View-Once Attachments.

C5 polls follow the immutable replicated-state shape: kult-protocol owns content-v1 kind 6 create/vote/close frames; kult-node validates the claimed group member and derives fixed-electorate vote heads and final tallies; existing sealed group rows and KKR10 carry the source events; RPC/UniFFI expose typed snapshots; shells render and refresh cards without resolving votes. The sender-key path hides poll content from transports, while authenticated capability intersection keeps old clients off the typed send path. ADR-0029 adds one recipient-specific origin tag to the unchanged shared ciphertext; the node derives the voter from the verified pairwise sender device before applying poll state. See 20: Group Polls and ADR-0029.

C6 authority adds a deliberately separate signed control plane without moving policy into shells. kult-protocol owns content-v1 kind 7, the canonical full authority state, owner-transfer certificates, signed admin requests, and signed poll-moderation closure; kult-crypto owns the four identity-signature domains; kult-node validates transfer-chain ancestry and serializes every mutation at the one current owner; kult-store seals the winning state and consumed request ids separately from legacy group records. RPC/UniFFI expose render-safe roles and typed commands/events. Apps display roles and invoke those commands but never see group secrets, signatures, identity blobs, or chain state. KKR6 introduced authority records and KKR10 carries them forward, while the fresh-identity KKR1KKR7 archive boundary omits groups. See 21: Group Roles, Ownership, and Moderation.

C2 linked devices separate stable account identity from physical endpoint cryptography. kult-crypto owns the offline-root KDA2 proof, independent device certificates, strict-majority transitions, recovery epochs, link transcripts, and sync sealing; kult-protocol owns bounded endpoint bundles and sync events; kult-store seals public account authority, per-endpoint delivery state, sync counters, conflicts, and deterministic ordinary-data winners; kult-node enforces quorum, fail-closed fork/recovery conflict handling, fan-out, capability intersection, convergence, rotation, and recovery. Routine KKR10 and link packages contain no account root. RPC/UniFFI expose opaque ceremony/approval/recovery bytes and strict render-safe device/conflict models; shells compare codes and collect explicit approvals without implementing authority rules. See 22: Linked Devices and ADR-0026.

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 prepares a detached pairwise ratchet candidate and commits that candidate, immutable history, per-device delivery rows, the sealed outbound envelope, and any consumed schedule/reset/capability state through one typed BEGIN IMMEDIATE transition. The UI changes only after the commit receipt; a durable presentation marker requests snapshot resynchronization after a commit-before-notification restart. Group sender/receiver, bounded fan-out, attachment and deferred group/media follow-up use the corresponding typed transitions. The complete inventory records the implemented and excluded boundaries; the quarantined pre-C2 alias bridge, independent review, and physical sudden-power-loss evidence keep ADR-0028 Proposed.
  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. Ordinary v1 envelopes expose only an opaque per-recipient delivery token (see §5). Ephemeral envelope v2 additionally exposes one hour-aligned deletion bucket, authenticated again inside the encrypted content. 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. A transport handoff advances queued → sent; it does not prove receipt. The exact sealed envelope retries on a passive, restart-safe cadence until an encrypted end-to-end receipt advances it to delivered. Fresh user actions bypass that passive work. If no receipt returns within 30 days, the envelope is garbage-collected and retained history becomes delivery failed after 30 days.

Receive path

Direct transport holds its response while the node consumes a verified introduction or commits the complete encoded envelope to the bounded sealed pending domain. Mailbox collection gives each row an internal settlement handle: the node commits that same PendingStage transition before the transport acknowledges the exact relay lease and row id. Failure before commit leaves the direct request refused or the relay row leased for retry; failure after commit leaves a duplicate that the endpoint absorbs.

A bridge may enqueue an unregistered internet deposit for bounded best-effort mesh transit, but returns a custody refusal because that queue is not a durable mailbox. Only durable endpoint staging or a registered mailbox deposit can advance the sender's next-hop state.

From durable staging, the ordinary path is reassembly → exact duplicate suppression → ratchet decrypt (including the configured skipped/out-of-order window) → one typed receive transition containing plaintext history, replay, receipt ciphertext, and source-row removal → event to the app. The encrypted end-to-end receipt is the only event that advances the sender to delivered.

Live-call path

C7 deliberately does not reuse the durable message lifecycle for media. A bounded CallControl offer/answer/decline/busy/cancel/hangup value travels inside the existing pairwise ratchet, but the node keeps its decoded state transient and excludes it from history, search, backup, and C2 sync. After one authorized physical device wins the answer, kult-transport opens /komms/call/1 only on the observed direct QUIC connection. kult-crypto derives and owns directional per-call media keys plus replay/key-phase state; the shells exchange only bounded Opus packets and render-safe call status.

TCP, relay-only, mailbox, sneakernet, and airtime-budgeted carriers fail the availability check before an offer is emitted. This path has no coordinator or store-and-forward substitute. See 23: Live Audio Calls.

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). A v2 relay returns accepted only after durable commit. It stores TTL-bounded, size-capped, row-bound sealed records under opaque keyed token/client/content indexes. Collection creates bounded idempotent leases and deletes only exact rows acknowledged after endpoint commit. 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. The separate OHTTP relay artifact holds no gateway HPKE key and is not yet a client-selectable path. 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 ordinary Komms envelopes observes:

  • 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), and
  • for a mailbox operator, deposit/collection timing, volume, expiry, quota outcomes, pseudonymous transport clients, random relay row/lease ids, and access correlation within that operator.

For C4 ephemeral traffic, the carrier additionally observes one coarse, hour-aligned retention_until bucket so it can delete sealed work without decrypting it. The exact deadline and whether the content is text or view-once media remain encrypted. The bucket is a bounded deletion hint, not proof that a relay erased every copy.

The envelope encodes no sender identity, recipient identity, exact content deadline beyond the coarse bucket, or conversation identifier. This is the sealed sender content property; it does not hide network addresses, opaque delivery tokens, timing, sizes, volume, or cross-request correlation from every carrier or observer. The construction is specified in 04: Cryptography §7.

Mailbox persistence does not turn these limits into anonymity or operator inability. A running operator controls the process and can inspect live memory, retain traffic metadata, deny, delay, replay, suppress, or discard ciphertext. At rest, the v2 database contains only opaque indexes and row-bound seals when its separate service key is unavailable; possession of both files exposes the relay-visible tokens and envelopes, which remain end-to-end ciphertext.

ADR-0018's client, sealed state, fixed codecs, persistence-free component, and dedicated network/TLS reference-service wrapper are implemented for Beta but are not configured as a default or deployed service. ADR-0019's fixed codecs, sealed per-session state, durable identity-free revoke retries, bounded core collection, pinned direct/Tor client, dedicated APNs/FCM gateway, and hardened deployment artifacts are also implemented for Beta. Direct APNs, Play-only FCM, a separately inspected Google-free artifact, and bounded mobile lifecycle handlers are present; real provider/deployed-gateway and named physical-device qualification remain open. No wake gateway is deployed. Their bounded but non-zero metadata surfaces are listed in 02: Threat Model, ADR-0017, and ADR-0034; the gateway boundary and no-stale-restore procedure are in the native-wake runbook. A fixed one-to-one RFC 9458 relay-side artifact, hardened image, and metadata-stripping tests are also implemented locally. No compatible gateway/client path, deployment, or non-collusion evidence exists; see the OHTTP relay runbook.

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 ↗