Repository navigation
How a message crosses the mesh
A Knit message can reach someone your phone has never been in range of, with no router, server or internet connection in between. Two mechanisms make that work. Flooding moves a message quickly through every phone that's connected right now. Catch-up sync fills the gaps later, when a phone that missed the message meets one that has it.
This page explains how the two work together. For a walkthrough without the mechanics, see How a message reaches someone who isn't in range yet on the Knit website.
Checked against Knit 2.11.0-beta.0.
The internet moves data along routes. Routers keep tables of which neighbour leads where, and they update those tables as the network changes. That works as long as the network holds still for longer than the tables take to catch up.
A mesh of phones doesn't hold still. People walk off, and a link between two phones might last a couple of minutes. Knit also has no directory of where anyone is, so there's nothing to route toward.
Knit floods instead. Every phone is a sender, a receiver and a relay. Your phone gives a copy of your message to each neighbour, they do the same, and the phone it's addressed to picks it out. Direct and group messages spread this way too, and end-to-end encryption is what keeps them private along the way. Posts in the Nearby room are plaintext by design.
flowchart LR
A["Your phone"] -->|"hop count 0"| B["Phone B"]
A -->|"hop count 0"| C["Phone C"]
B -->|"hop count 1"| D["Phone D"]
C -.->|"repeat, dropped"| D
D -->|"hop count 2"| E["Recipient"]
Left alone, a flood would never stop. Knit puts three limits on it.
The first limit ends loops. Every message has a unique ID, and a phone remembers the IDs it has handled for 10 minutes. It drops any repeat it hears in that time, and it never relays a message back to a neighbour it already heard it from.
The second limits distance. Each message carries a hop count that starts at zero and goes up by one at every relay. A phone won't relay a message whose count has reached eight, so one flood passes through at most eight relays. Every phone applies that limit itself, which means a modified app can't send a message further by claiming a higher one.
The third handles crowds. If 30 phones hear a message and all repeat it at once, they spend their airtime telling each other what they already know. Each relay waits a random delay of up to 150 milliseconds first. If a second neighbour's copy arrives during the wait, the phone cancels its relay, since the message is already spreading around it. On a sparse mesh no second copy turns up, and the relay goes out.
Relays change the hop count and nothing else. The author's signature covers the message but not the hop count, so it still checks out after any number of hops, and a relay can't alter a message without breaking it. A phone also forwards messages it can't read or verify, like one sealed for someone else or one from a newer version of Knit. An out-of-date phone still helps the mesh.
A flood reaches the phones connected at that moment, and that's rarely everyone. The person you're writing to might be on the other side of a field. A neighbour might have cancelled its relay when the one phone behind it still needed a copy. A small packet sent without a connection might get lost.
Knit covers those cases by having phones remember what they've passed along.
Every Knit phone keeps a copy of the messages it passes along, in its encrypted local database. The copy is the original signed message, so a direct message stays sealed and the phone carrying it can't open it. The carrying phone does see the envelope: the sender's Knit identity, the recipient, the time and the size, and for a group message its member list. What Knit encrypts, and what stays visible covers this in full. Reactions, delivery receipts, group changes and profile updates are carried the same way. Pictures and files travel separately: the message holds a reference to them, and the bytes follow on their own.
| Carrying limit | Value |
|---|---|
| Direct and group messages | 24 hours from when they were sent |
| Nearby room posts | 6 hours from when they were sent |
| Most messages one phone carries | 1,000 |
| Most from one sender, one group, or the Nearby room | 200 each |
A carried message reaches its recipient when some phone holding it meets them before it expires. That can take minutes or hours, depending on who walks where, and the delivery receipt comes back the same way before you see Delivered.
When two phones meet, each needs to learn what the other is missing. Sending everything would work, but that could mean 1,000 messages over a slow radio, and most of the time the two phones already hold the same set. Knit runs a cheap check first and an exact one only if it has to. Distributed databases call this pattern anti-entropy.
The cheap check is a fingerprint of everything a phone is carrying. Knit hashes each message ID and combines the results with XOR into one 64-bit number. The same set of messages gives the same number, whatever order they arrived in, and it stays the same across a restart. XOR also undoes itself, so adding or dropping a message updates the fingerprint in one step.
Phones with Wi-Fi Aware announce their fingerprint in a small message that needs no connection, whenever their set changes and every 30 seconds otherwise. A matching number means the two phones hold the same messages, and neither has to open a connection. That saving matters, because a Wi-Fi Aware connection takes time and radio resources to set up.
When the numbers differ, the exact check runs. Each phone sends the list of message IDs it holds, and the other replies with only the messages missing from that list.
sequenceDiagram
participant A as Phone A (12 messages)
participant B as Phone B (10 messages)
A-->>B: Fingerprint 7f3a...
B-->>A: Fingerprint c21e...
Note over A,B: Numbers differ, so a connection opens
A->>B: IDs of everything I carry
B->>A: IDs of everything I carry
A->>B: The 3 messages you lack
B->>A: The 1 message you lack
Note over A,B: Both now carry 13, and the connection closes
Bluetooth works a little differently, because Knit holds Bluetooth links open. Two linked phones swap ID lists when the link forms and every 60 seconds after that. If they already share a Bluetooth link, the exchange stays on it and no Wi-Fi Aware connection is needed.
A message handed over this way starts a new flood with its hop count back at zero. The limit of eight relays applies to each flood, so over a day of people walking around, a message can pass through far more phones than that.
A phone can't check the signature of someone it has never met, so the sender's profile, which holds their public key, goes ahead of their messages. A newcomer can then verify a whole backlog as it arrives.
Fingerprints match only when two phones hold exactly the same set, so every phone has to let go of the same messages. If each one chose for itself, two phones could never match, and they'd swap the same messages at every meeting.
Knit's rules therefore read only the message itself and the set a phone holds, and never when or where a copy arrived:
- Expiry counts from the time in the signed message, so copies everywhere run out together, give or take the difference between phone clocks.
- When a sender, a group or the Nearby room goes over 200, a phone keeps the newest 200 by send time.
- When a phone reaches its limit of 1,000, it removes the oldest message from whichever sender it holds the most of, so a noisy sender loses their own messages first.
The same reasoning explains two behaviours that can look odd at first. A group message is offered to any phone that lacks it, member or not, and it stays sealed either way. And a delivered direct or group message stays on carrying phones until it expires, because delivery receipts between current versions of Knit are sealed too. Carrying phones can't tell a delivered message from an undelivered one, which keeps who received what private at the cost of some storage.
Flooding and catch-up work the same on any radio. Knit merges its radios into one list of neighbours, and the ID check drops whichever copy arrives second.
| Path | What it carries | How it behaves |
|---|---|---|
| Bluetooth link | Everything, and Knit prefers it when a link is up | Held open to nearby phones |
| Wi-Fi Aware message | Nearby room posts and small public updates to every neighbour, plus short direct messages to their recipient | No connection needed, about 255 bytes each, and a small message can be split across a few |
| Bluetooth advertising page | Nearby room posts and small public updates, to phones in range with no link. Direct messages never use it | Up to 236 bytes, on phones with extended advertising |
| Wi-Fi Aware connection | Catch-up and anything too large for the small paths | Opened when needed and closed afterwards |
The two small paths are best effort. A packet they lose comes back through carrying and catch-up.
A Meshtastic LoRa board and Internet relays add further paths. Both are optional, and both stay off until you turn them on.
- Architecture covers the whole design, including how the radios sit behind one interface.
- Store-and-forward reference has the full carrying rules and the reasoning behind them.
- How Knit uses Bluetooth and Wi-Fi Aware describes the radios from a user's side.
Knit is free and ad-free, funded by its supporters.
☕ Ko-fi ·
💖 GitHub Sponsors ·
Liberapay ·
Other ways to help
Supporting Knit on Ko-fi or GitHub Sponsors comes with Knit Anywhere. Liberapay goes to the developer personally.