An offline, serverless mesh messenger for Android — end-to-end encrypted, no internet, no accounts, no Google Play services.
Your phones talk directly to each other over Wi-Fi Aware and Bluetooth LE, and relay for one another hop by hop.
Knit needs no internet connection, no cell service, no accounts, and no servers. It forms an ad-hoc mesh directly over Wi-Fi Aware (NAN) and Bluetooth LE, running both radios at once. Send a message and it goes to every device in range; each of those re-transmits it onward, so messages leap-frog across phones with no infrastructure at all. Duplicates are discarded, hop-count and TTL bound the flood, and a store-and-forward layer carries what a single flood doesn't reach.
┌─────────┐ Wi-Fi Aware / BLE ┌─────────┐ Wi-Fi Aware / BLE ┌─────────┐
│ Phone A │ ────────────────────► │ Phone B │ ────────────────────► │ Phone C │
└─────────┘ send + relay └─────────┘ relay (dedup) └─────────┘
│ │ │
originates a overhears & re-floods (jittered, delivers — even though
signed, encrypted suppressed) — only carries ciphertext A was never in its range
frame once it can't decrypt
- Two radios, no GMS. Wi-Fi Aware and Bluetooth LE run simultaneously behind one transport seam, with no Google Nearby dependency. A device with only one of the two still meshes over that one.
- End-to-end encrypted, with forward secrecy. Direct and group messages are sealed per-message (AES-256-GCM) and signed (Ed25519), so relays only ever carry ciphertext. Between current builds the keys rotate — a DM session bootstraps X3DH-style from a signed prekey in the peer's profile and rekeys as the conversation turns over, and groups run a sender-key ratchet on top — so recorded traffic stops being readable once its keys age out. Identity keys are hardware-backed and verifiable in person by safety number or QR code. The public Nearby room is plaintext by design: it has nobody in particular to encrypt to.
- Store-and-forward. A message whose recipient isn't in range is held in encrypted custody and re-offered when a path to them appears, so phones that meet only briefly still backfill each other.
- A whole messenger, not a demo. A public Nearby room, 1:1 DMs and group chats; photos, files, voice notes, reactions and @-mentions, with link previews and shared locations as opt-ins; search across every chat; files far too big to attach, handed to a nearby contact over a direct Wi-Fi link; and on-device screening for abusive text and explicit images that never leaves the phone.
- Kilometres of range from a Meshtastic radio, if you want them. Pair a LoRa board over Bluetooth and the Nearby room and your 1:1 messages get a hop measured in kilometres, shared by every phone meshed with yours. It sits idle until you pair one.
- An optional Internet plane, off until you switch it on. When no radio path exists, Knit can park the same sealed frame on a small relay — a spool — that hands it over the next time the other phone connects. See below.
- Reproducible builds. F-Droid rebuilds each release from source and byte-compares it against the APK we publish before distributing it.
| F-Droid | f-droid.org/packages/app.getknit.knit |
| Google Play | play.google.com/store/apps/details?id=app.getknit.knit |
| Signed APK | github.com/getknit/knit/releases |
| Website | getknit.app |
Android 10+ (API 29). Once installed, Knit can hand itself to a nearby phone over Quick Share or Bluetooth — no store and no network required.
Proximity meshing is the product; a spool is the fallback for when nobody is in range. A spool is a scoped, blinded store-and-forward relay. It holds, per conversation scope, a bounded set of end-to-end-sealed frames and a digest over them, streams new arrivals to subscribers, and heals divergence by digest anti-entropy.
┌─────────┐ push ┌────────────┐ event ┌─────────┐
│ Phone A │─────────►│ spool-1 │──────────►│ Phone B │
│ seals │ └────────────┘ │ unions │
│ frame │ push ┌────────────┐ pull │ + opens │
│ once │─────────►│ spool-2 │◄──────────│ │
└─────────┘ └────────────┘ └─────────┘
no spool-to-spool link, ever
- It stays off until you turn it on. Enabling it takes an explicit consent sheet that spells out what a spool can and cannot see, so a fresh install still makes no network calls.
- A spool sees an opaque 32-byte scope id, blob ids, ciphertext, sizes, and timing. It never sees node ids, plaintext, rosters, delivery facts, or which other spools a client uses.
- No spool is load-bearing. Spools never talk to each other; clients multi-home across several and union the results, so overlap is all two members need, and a wiped spool is refilled by any one conversation member.
- The spec is the product.
docs/SPOOL_PROTOCOL.mdis normative, with executable §13 test vectors. Third-party implementations are first-class —knit-spoolships a conformance CLI that validates any live spool over the wire. - Cheap to run. Kotlin + Ktor on JDK 21, CBOR over one WebSocket (
wss://host/spool/v1), SQLite for persistence, idling in ~128–256 MB on the smallest VPS tier. Onedocker composefile brings it up behind Caddy with certificates issued and renewed for you, and the app takes a whole relay — address, token and all — from onegetknit.app/rinvite link, behind a sheet that says what the relay will see.
docker run -p 9470:9470 -v spool-data:/data -e SPOOL_POW_BITS=20 ghcr.io/getknit/knit-spool:0.2.0
gh attestation verify oci://ghcr.io/getknit/knit-spool:0.2.0 --repo getknit/knit-spoolMulti-arch (amd64 + arm64), mirrored on Docker Hub as
getknit/knit-spool.
HOSTING.md covers what a spool needs
from a box; deploy/ has the TLS compose
files.
| Repository | What it is |
|---|---|
| knit | The Android app, plus the normative spool protocol spec — Kotlin, Jetpack Compose, GPL-3.0-or-later |
| knit-spool | The reference spool daemon and conformance suite — Kotlin, Ktor, AGPL-3.0-or-later |
Knit is free and open source, with no ads, no tracking, and no accounts — funded entirely by tips, which buy no special treatment.