Bitchat’s four-layer store-and-forward stack ensures message delivery even when the recipient isn’t currently reachable. The layers are complementary and engaged in order of cost: the sender’s own outbox tries first on reconnect, couriers carry sealed envelopes through the physical world when the mesh cannot bridge the gap, gossip sync keeps public room history alive across partitions, and Nostr relays cover the case where both devices are offline simultaneously. All persisted state across every layer is wiped by panic mode.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/permissionlesstech/bitchat/llms.txt
Use this file to discover all available pages before exploring further.
Panic mode clears all persisted store-and-forward state: the sealed outbox, all carried courier envelopes, the gossip archive of public messages, and on-device delivery metrics. Nothing from any layer survives a panic wipe.
1. Sender Outbox
When a private message has no prompt route to its recipient — because the peer is not currently reachable over BLE or Nostr — it enters the sender outbox.Retention
Up to 100 messages per peer, each held for 24 hours. Messages are re-sent automatically whenever a reconnect event occurs.
Resend cap
After 8 delivery attempts without an acknowledgment, the message is dropped with a visible failure indicator in the UI.
Sealed at rest
The outbox is persisted to disk sealed under a ChaChaPoly key held only in the Keychain (after-first-unlock, this device only). Plaintext never touches disk — queued mail survives an app kill without storing readable content.
Cleared on ack
A delivery acknowledgment or read receipt from the recipient clears the queued entry immediately. Redundant copies (from couriers and Nostr) are deduped by message ID and are harmless.
Persistence Details
The outbox file is written withcompleteFileProtectionUntilFirstUserAuthentication on iOS, so it cannot be read from disk before the device is first unlocked after a reboot. If the file is unavailable when Bitchat launches (locked device, background restore), the outbox load is deferred and retried when protected data becomes available — messages queued during the locked wake are merged with the durable snapshot rather than lost.
2. Courier System
When neither the BLE mesh nor Nostr can deliver a message promptly, the message is sealed into a courier envelope and handed to nearby peers who may physically travel toward the recipient.Envelope Sealing
Courier envelopes are sealed with the one-way NoiseX pattern using the recipient’s static Curve25519 key. The sender’s identity is authenticated inside the ciphertext. Intermediate couriers see only opaque bytes — they cannot read the content, identify the sender, or identify the recipient.
Opaque Addressing
The only routing information on a courier envelope is a 16-byte rotating recipient tag:Trust Tiers and Quotas
Deposit quotas are enforced by the courier’s trust tier for the depositing peer:| Tier | Who qualifies | Envelopes allowed per depositor | Pool limit |
|---|---|---|---|
| Favorite | Mutual favorites | 5 | Unlimited share of 40 total slots |
| Verified | Any peer with a signature-verified announce | 2 | 20 of 40 total slots |
Spray-and-Wait
Envelopes carry a copy budget that controls how widely they diffuse through the network:- Initial copy budget: 4 (capped at 8)
- When two eligible couriers meet, the carrying courier hands over half its remaining budget (binary spray)
- A courier that has been sprayed records the peer to avoid repeating the transfer
- A carry-only envelope (budget = 1) stays put and is not sprayed further
Deposit and Retry
The sender hands courier envelopes to up to 3 connected peers who may encounter the recipient. Queued messages are re-deposited whenever a new eligible courier connects, until 3 distinct couriers are carrying the message or it expires.Handover
When a courier encounters the recipient:Direct announce (recipient is right here)
Direct announce (recipient is right here)
When a courier receives a verified direct announce from the recipient, matching envelopes are delivered over the live BLE link and removed from the courier’s store.
Relayed announce (recipient heard multi-hop away)
Relayed announce (recipient heard multi-hop away)
When a courier receives a verified relayed announce (the recipient is reachable but not directly connected), a copy is flooded toward the recipient as a directed packet. The carried original stays put. This is throttled to one attempt per envelope per 10 minutes to avoid mesh flooding.
3. Public History Gossip Sync
Public broadcast messages are cached locally and reconciled with peers using a gossip sync protocol based on compact Golomb-coded set (GCS) filters.How It Works
Every ~15 seconds, a device sends its connected peers arequestSync packet containing a GCS filter that encodes the IDs of all the public packets it currently holds. The peer computes the set difference and returns only the packets the requester is missing. This is efficient: the filter is compact (typically a few hundred bytes) and the exchange terminates in a single round-trip for each sync type.
Retention Windows
| Content type | Sync window |
|---|---|
| Public messages and group messages | 6 hours (configurable; long enough for a device to walk between partitions and backfill the room) |
| Fragments and file transfers | 15 minutes |
| Prekey bundles | 24 hours |
| Board posts | Until each post’s own expiry |
| Announces | 60 seconds (stale peers pruned) |
Sync Type Flags
EachrequestSync packet carries a bitfield specifying which content types the filter covers. Old clients ignore bits they don’t recognise, so new content types (board posts, prekey bundles, group messages) are backward-compatible: the round uses a wider bitfield but old clients simply answer with the types they know.
| Sync type | Bit | Notes |
|---|---|---|
| Announce | 0 | Announces are exempt from the since-cursor: there is at most one per peer |
| Public message | 1 | Main public chat history |
| Fragment | 5 | Short 15-minute window |
| File transfer | 7 | Short 15-minute window |
| Board post | 8 | Long-lived, until each post’s own expiry |
| Prekey bundle | 9 | One per owner, newer replaces older, 24-hour window |
| Group message | 10 | Shares the public-message sync cadence |
4. Nostr Mailboxes
For the case where both devices are completely offline at the same time, Bitchat private messages can rest on Nostr relays.- Private envelopes for mutual favorites are published to Nostr relays using a proprietary sealed-sender format (kind 14 inner message, kind 13 seal, kind 1059 outer envelope signed by a one-time key)
- On reconnect, each client re-subscribes with a 24-hour lookback, fetching any envelopes that arrived while it was offline
- Nostr traffic can optionally route over Tor for additional metadata privacy
- This path is available only to mutual favorites who have exchanged Nostr public keys
Bitchat’s Nostr envelope format reuses NIP-17/NIP-59 kind numbers but is not compatible with NIP-17, NIP-44, or NIP-59 and interoperates only with Bitchat clients. The protocol provides no forward secrecy: compromise of the recipient’s static Nostr private key can expose stored envelopes addressed to that key.
Delivery Metrics
Bitchat records bare local counters for store-and-forward activity so delivery behavior can be measured on-device. No identities, message IDs, or timestamps are recorded — only anonymous event tallies.| Event key | Description |
|---|---|
outbox.queued | A private message entered the outbox (no prompt route) |
outbox.resent | A retained message was re-sent on a reconnect flush |
outbox.delivered | A delivery or read ack cleared a retained message |
outbox.dropped | A message was dropped (attempt cap, TTL, or overflow) |
courier.deposited | This device handed sealed mail to a courier |
courier.accepted | This device accepted sealed mail to carry for a third party |
courier.handedOver | This device delivered carried mail over a direct link |
courier.remoteHandover | This device pushed carried mail toward a relayed announce |
courier.sprayed | This device split spray copies to another courier |
courier.opened | Couriered mail addressed to this device was decrypted and delivered |
UserDefaults, never transmitted, and cleared by the panic wipe alongside the stores they describe.