Mesh networking

How to build an app like Bitchat (Bluetooth mesh + Nostr)

Bitchat is an open-source messaging app that relays messages between nearby phones over a Bluetooth Low Energy mesh and falls back to Nostr relays over the internet. To build an app like it you need both Bluetooth roles on each phone, a relay protocol with a hop limit and duplicate suppression, end-to-end encryption, and a Nostr client, either written yourself or taken from an SDK that ships those transports.

Learning objectives

After reading this article you will be able to:

  • Describe how Bitchat combines a Bluetooth LE mesh with Nostr relays
  • List the parts you would build for a Bluetooth mesh and Nostr chat app
  • Compare building those parts yourself with using an SDK that ships both transports

What Bitchat is

Bitchat is a peer-to-peer chat app for iOS and macOS, with a separate Android app, from the permissionlesstech project on GitHub. Its README describes a “dual transport architecture”: a local Bluetooth mesh for offline communication, and the Nostr protocol for reaching people over the internet. It has no accounts, no phone numbers, and no central servers of its own.

On the Bluetooth side, phones discover each other automatically and relay messages for one another, up to a maximum of seven hops according to the README. Private messages on the mesh are encrypted with the Noise Protocol Framework. When Bluetooth cannot reach the recipient, a private message falls back to Nostr relays, wrapped in a Bitchat-specific encrypted envelope. When neither path is available, messages are queued until one appears. Location channels, chat rooms keyed by a geohash, run over Nostr only and need the internet.

The iOS and macOS code is released into the public domain. The Android app lives in its own repository under GPL-3.0.

The two transports

Bluetooth Low Energy mesh. Every current phone has a BLE radio, and both iOS and Android let apps advertise, scan, and connect. A phone mesh built on BLE is the app’s own protocol on top of those links, not the Bluetooth SIG’s Bluetooth Mesh standard, so it works on ordinary phones. Range per link is short, and the mesh extends reach by passing messages across several phones.

Nostr. Nostr is an open protocol in which clients publish signed JSON events to relays over WebSockets and subscribe to events that match a filter. NIP-01 defines the basic flow. A relay is an ordinary server that stores and forwards events, and anyone can run one. For a chat app, Nostr gives a way to reach a recipient anywhere with internet access without running your own messaging backend, as long as both sides can reach a common relay.

The combination covers two different situations. Nearby people with no signal reach each other over Bluetooth. Distant people, or the same people once they have a connection, reach each other over Nostr.

The parts you would build

Bitchat’s source shows how much sits between “two radios” and “a chat app”. Building something similar from scratch means writing each of these:

  1. Both Bluetooth roles on each phone. A phone must advertise and accept connections (the peripheral role) as well as scan and connect (the central role), or two phones cannot find each other. On iOS that is Core Bluetooth’s CBPeripheralManager and CBCentralManager; on Android, BluetoothLeAdvertiser, BluetoothGattServer, and the scanner.
  2. A packet format that fits BLE. Each BLE write carries a small payload, so messages need a compact binary format and fragmentation for anything longer.
  3. Relaying. Each phone rebroadcasts messages it has not seen, decrements a hop limit, and drops duplicates by message identifier, so traffic spreads without circling forever.
  4. Store-and-forward. Messages for peers who are out of range wait in a queue and go out when a path appears.
  5. Identity and encryption. Each device needs a long-term key pair, a way for people to check they have the right key, and end-to-end encryption so relaying phones carry content they cannot read.
  6. A Nostr client. WebSocket connections to one or more relays, event signing, subscriptions, and an encrypted envelope for private payloads.
  7. Transport choice. Logic that prefers Bluetooth when the peer is near, falls back to Nostr when it is not, and does not deliver the same message twice when both work.
  8. Platform rules. Background limits on iOS, Bluetooth permissions on Android, and battery cost from scanning and relaying.

Each of these needs testing on real phones, in crowds, and across iOS and Android pairs.

Using an SDK for the same transports

The alternative is to take the transports from a library and write only the app. The Offline Protocol mesh SDK is one such library: in a React Native app it provides Bluetooth LE and Nostr as transports, among others, with multi-hop relay, duplicate suppression, retries, and encryption handled inside the SDK.

The SDK differs from Bitchat’s design in a few ways:

  • Encryption. The SDK encrypts application messages and groups with MLS (RFC 9420) rather than Noise, and each device’s address is derived from an Ed25519 identity key.
  • Nostr envelopes. For version 0.27.0, the SDK seals outgoing Nostr frames into NIP-59 gift wraps with NIP-44 encryption, each signed by a fresh single-use key. A relay still sees subscriptions, timing, and traffic volume.
  • Defaults. In React Native, Bluetooth LE is on by default and Nostr is off. Nostr needs at least one relay URL that the device can reach, so it is an internet path, not a substitute for the local radio.
  • Hop limit. Messages start with a time to live of 8 hops by default in version 0.27.0, and the value can be changed.
import { OfflineProtocol } from '@offline-protocol/mesh-sdk';

const protocol = new OfflineProtocol({
  appId: 'my-chat',
  profile: 'default',
  transports: {
    ble: { enabled: true },
    nostr: { enabled: true, relayUrls: ['wss://relay.example.com'] },
  },
});

Pick relays you trust for the deployment rather than relying on an example public relay.

What to decide before building

  • Who needs to reach whom. If everyone is nearby, Bluetooth alone may be enough. If people are spread out, plan for the internet path and its relays.
  • Licensing. Bitchat’s iOS code is public domain and its Android code is GPL-3.0. The Offline Protocol mesh SDK is free under AGPL-3.0-only, with a commercial license for proprietary apps and App Store distribution. Check what each license asks of your app.
  • Metadata. Encryption hides content, not who is talking. A nearby radio can observe Bluetooth traffic, and a Nostr relay observes timing and volume. Bitchat’s whitepaper discusses what a nearby radio can see; read the equivalent section for any SDK you adopt.

Frequently asked questions

Is there a Bitchat SDK?

Bitchat publishes app source code, not an SDK. Its iOS and macOS code is released into the public domain and its Android code is a separate GPL-3.0 repository, so you can study or reuse it, but you would be adapting an app rather than calling a library built for other apps.

Does an app like Bitchat need a server?

The Bluetooth mesh needs no server at all. The Nostr side needs at least one reachable Nostr relay, which is a server, though anyone can run one and a client can use several.

Sources

Build it with Offline Protocol

The Offline Protocol mesh SDK ships Bluetooth LE and Nostr as transports in one React Native package, with multi-hop relay, MLS encryption, and device identity. The Reticulum and Nostr page covers enabling Nostr, choosing relays, sealing, and what a relay can observe.

Read Reticulum and Nostr