Skip to main content

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.

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.
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 with completeFileProtectionUntilFirstUserAuthentication 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 Noise X 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.
The Noise X pattern provides no forward secrecy. If the recipient’s static key is ever compromised, an attacker who has retained the ciphertext could decrypt it. A prekey-based scheme with per-message forward secrecy is planned future work.

Opaque Addressing

The only routing information on a courier envelope is a 16-byte rotating recipient tag:
// Tag = HMAC(recipientStaticKey, UTCDay)
The tag is computable only by parties who already know the recipient’s static key. It rotates daily, so tags cannot be correlated across days. Couriers learn neither sender, recipient, nor content from the tag alone.

Trust Tiers and Quotas

Deposit quotas are enforced by the courier’s trust tier for the depositing peer:
TierWho qualifiesEnvelopes allowed per depositorPool limit
FavoriteMutual favorites5Unlimited share of 40 total slots
VerifiedAny peer with a signature-verified announce220 of 40 total slots
When the 40-slot pool is full, the oldest verified-tier envelope is evicted first. A verified depositor can never displace favorite-tier mail; if only favorite mail is present and the pool is full, a verified deposit is rejected. Envelopes are capped at 16 KiB and 24 hours; overflow evicts oldest verified-tier mail first.

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
This spray-and-wait approach means mail diffuses through a moving crowd rather than riding a single person all the way to the destination.

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:
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.
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.
Receivers dedup by message ID, so redundant copies (from multiple couriers or the outbox) are harmless. Couriered mail from blocked senders is dropped at decryption time — the courier never knows the message was blocked.

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 a requestSync 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.
Device A                          Device B
   |--- requestSync (GCS filter) -->|
   |<-- missing packets ------------|

Retention Windows

Content typeSync window
Public messages and group messages6 hours (configurable; long enough for a device to walk between partitions and backfill the room)
Fragments and file transfers15 minutes
Prekey bundles24 hours
Board postsUntil each post’s own expiry
Announces60 seconds (stale peers pruned)
The public message cache holds up to 1 000 packets and persists to disk so the room’s recent history survives app restarts and relaunches.

Sync Type Flags

Each requestSync 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 typeBitNotes
Announce0Announces are exempt from the since-cursor: there is at most one per peer
Public message1Main public chat history
Fragment5Short 15-minute window
File transfer7Short 15-minute window
Board post8Long-lived, until each post’s own expiry
Prekey bundle9One per owner, newer replaces older, 24-hour window
Group message10Shares the public-message sync cadence
Rate limiting prevents a single peer from triggering repeated full-store diff passes. Each peer is limited to a maximum of 8 sync responses per 30-second window from any one peer.

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 keyDescription
outbox.queuedA private message entered the outbox (no prompt route)
outbox.resentA retained message was re-sent on a reconnect flush
outbox.deliveredA delivery or read ack cleared a retained message
outbox.droppedA message was dropped (attempt cap, TTL, or overflow)
courier.depositedThis device handed sealed mail to a courier
courier.acceptedThis device accepted sealed mail to carry for a third party
courier.handedOverThis device delivered carried mail over a direct link
courier.remoteHandoverThis device pushed carried mail toward a relayed announce
courier.sprayedThis device split spray copies to another courier
courier.openedCouriered mail addressed to this device was decrypted and delivered
These counters are stored in UserDefaults, never transmitted, and cleared by the panic wipe alongside the stores they describe.

Build docs developers (and LLMs) love