Milestone 1

Early, but no longer only on paper

Two desktop clients hold a conversation through a relay, over a hybrid post-quantum handshake and a real ratchet, and the conversation now outlives the process holding it. This page says what that does and does not include.

Works today

Identity creation with hybrid post-quantum keys, contact exchange and safety-number verification, a real handshake and ratchet, a durable encrypted queue, multi-relay delivery with failover, sessions that survive a restart, and a desktop client driving all of it.

Partly

BLE exists as framing, GATT layout and MTU chunking, verified against a simulated radio. The radio itself sits behind a backend trait that Linux satisfies with BlueZ and each mobile platform will satisfy with native code.

Not yet

No mobile client. No anonymity layer — the DHT, onion circuits and blinded mailbox IDs are Milestone 2, and peer carry's protocol depends on them. No voice, no groups, no maps, and eight of the ten bearers are unimplemented.

Step by step

Milestone 1, as it actually stands

The goal statement was: two devices exchange encrypted text over both internet and BLE, with a working policy engine and no third-party protocol code.

Step State What is there
Wire format & codec Done Frame header, ciphersuite and bearer registries, fragmentation in both modes, mailbox IDs, a compact profile for constrained bearers. Specified byte by byte.
Identity Done Hybrid Ed25519 + ML-DSA-65 keypairs, seed derivation, BIP39 backup, a sigchain with checkpoints and rollback rejection, safety numbers on the PGP word list, and animated QR transport.
Session Done, and reviewed Handshake, ratchet with header encryption and bounded skipped keys, per-device fan-out. Reviewed as a unit, and held by integration tests that drive it adversarially rather than only through the happy path.
Bearer trait + QUIC Done Transport and link traits over capability descriptors, QUIC via quinn. Peer authentication is the session layer's responsibility rather than the transport's, so a relay needs no PKI to be deployable and none is trusted for identity.
Delivery Done Fragmentation and reassembly across every registered bearer, the outbound queue with latency-class priority and backoff, acknowledgements, and the carry pool with quota and bounded replication. The carry protocol waits on blinded mailbox IDs.
Local store Done Per-record AEAD over redb, keyed-hash record identifiers so the file names neither contacts nor conversations, and namespace bound as associated data so a record cannot be lifted between slots.
Policy engine Done Resolution, privacy floor, consent gates, per-contact overrides, and airtime accounting that models ETSI off-time rather than a bare hourly budget. Both invocation paths, with the preference/invariant split enforced by construction.
Bearer: BLE Framing done, radio pending GATT layout, MTU chunking and length framing, verified against a simulated radio that models uncooperative peers as well as well-behaved ones.
Relay node Done Mailbox protocol over QUIC, TTL'd blobs in the encrypted store, destructive collection, background expiry sweep. One process, no external services. Static configuration; DHT placement is Milestone 2.
UniFFI bindings Done Swift, Kotlin and Python generated cleanly, with the Python bindings exercised end to end against the real library — and since widened so the boundary can carry a whole conversation, not just an identity.
Terminal client Done Bearer status with privacy as ink density, airtime budgets, the queue, safety-number verification, and the backup phrase with its consequences. Pure Rust, no system libraries.
Desktop client Done, wired end to end The same four views against the same design tokens, in egui, driving the client engine against a real relay: contacts, sessions, a conversation, and a queue that shows what is actually waiting.
End-to-end tests Done A real relay, a real socket, no stubs. The milestone's goal statement performed rather than asserted crate by crate.
Multi-relay with failover Done Any number of relays, and running with none is a supported state rather than a misconfiguration.
Session persistence Done Ratchets and contacts live in the encrypted store, and an engine opened on an existing store resumes rather than starting over.
Android client Not started The FFI prerequisite is done and verified from a foreign language. What remains is the app: Compose UI, a foreground service for collection, the BLE backend implementation, and Keystore for the device key.

How the session layer is tested

Unit coverage of a cryptographic layer is necessary and not sufficient, because a test written alongside the code has no reason to abuse the API the way a stranger will. So the session layer carries integration tests that drive it adversarially — malformed and reordered input, loss and duplication, and the boundaries the design deliberately exposes to parties it does not trust.

Roadmap

After Milestone 1

Ordered by dependency rather than by appeal. Two of these are gated on open questions.

  1. M1 — mostly landed

    Rust core, and text on Linux

    Wire format through to a desktop client that holds a conversation across a restart. BLE's radio and the Android client are what remain.

  2. M2 — next

    The anonymity layer, and the first always-on node

    A DHT with blinded rotating keys, onion circuit construction, and relay mailboxes behind circuits. This is where infrastructure learns nothing becomes true rather than designed. Peer carry lands here because it depends on blinded mailbox IDs, along with device-to-device history sync, Shamir backup, and the Home Assistant add-on — scheduled here because an always-on node is the only way to test carry and relay behaviour against something, making it a development instrument as much as a deliverable.

  3. M3

    Voice

    ICE, SRTP and Opus, with a relay-by-default media path and per-contact direct opt-in. Opus over BLE.

  4. M4

    Platform completion

    iOS, macOS and Windows. iOS background and push is a genuine research task rather than a port — it is open question 1, and it reshapes the delivery layer either way it resolves.

  5. M5 — gated

    Groups

    MLS via openmls, group messaging, then group calls through a non-decrypting forwarding unit with SFrame. Gated on resolving commit ordering over a delay-tolerant network before the milestone begins rather than during it.

  6. M6

    The remaining bearers

    LoRa with the image profile and airtime accounting, the acoustic packet modem in both profiles, NFC tap-to-pair, SMS and MMS with consent gates, Wi-Fi Direct, and QR transfer. Payload adaptation and progressive fidelity land here, since this is where bearers first differ by orders of magnitude in practice.

  7. M7

    Maps

    Offline vector tiles, shared annotations backed by a CRDT, and location sharing with its own retention and revocation rules.

Open questions

Twelve, numbered, each with a deadline

Each is stated as the actual question to be answered rather than as a topic. These are the ones most likely to change the design.

3 · Why would anyone run a relay?

Decentralisation without an incentive model becomes three volunteer servers and a single point of failure. Is a staking or payment mechanism needed, or is altruistic operation plus low resource cost enough? Unresolved, and the question most likely to decide whether the network survives contact with reality. Due before public launch.

4 · How do MLS commits stay ordered across hours-long partitions?

The hardest problem in the design. MLS assumes a delivery service providing consistent commit ordering across all members; there is none here, and two members may sit partitioned behind a LoRa link. A per-group ordering role weakens decentralisation, DHT-based sequencing is unproven, and tolerating forks is something MLS does not natively support. The fallback — pairwise Double Ratchet with sender keys — scales worse but composes with partitions. Due before Milestone 5 begins, not during.

1 · How do we wake an iOS device without handing Apple our metadata?

Push notification services are a centralised chokepoint that sees device tokens and delivery timing, and iOS offers no alternative. Accept encrypted silent push through a bridge holding only an opaque token, or fall back to polling and eat the battery cost? This reshapes the delivery layer either way. Prototyped during M2, resolved before M4.

The rest cover sigchain propagation before the DHT exists, peer carry routing parameters, distributing multi-gigabyte map packs with no CDN, whether AGPL would be acceptable after all, what App Store review makes of a bearer that transmits in the clear, and where a client's device key should come from.

Building

What continuous integration insists on

cargo test --workspace --all-features
cargo clippy --all-targets --all-features -- -D warnings
cargo fmt --all -- --check
cargo metadata --locked

All of the above, plus a --locked check, because for a project like this an unnoticed dependency bump is a supply-chain change rather than a convenience. An advisory scan runs alongside them, and its results are triaged rather than merely counted.

The toolchain version is pinned in the repository rather than in the pipeline, so there is one place to bump it instead of two that can drift.

Run it, read it, do not rely on it

A relay is one command and a client is another. Neither needs the other to start.