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
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 KKR1–KKR7 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.
- App calls
send(conversation, content)throughkult-ffi. - 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 IMMEDIATEtransition. 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. - kult-protocol serializes content, pads it to the next size bucket, and hands it to the conversation's ratchet.
- kult-crypto advances the sending chain, encrypts with XChaCha20-Poly1305, and encrypts the ratchet header.
- 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.
- 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.
- 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 todelivered. Fresh user actions bypass that passive work. If no receipt returns within 30 days, the envelope is garbage-collected and retained history becomesdelivery 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:
- Direct: recipient reachable on some transport now → deliver immediately.
- 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.
- 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
.kkbfiles; 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:
- 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.
- 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.
kult-nodemerges 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 documentationBuild order and per-crate API sketches for implementers: 09: Implementation Guide.