βββ βββ
βββ
βββββββ βββββββββββ βββββββββ ββββββββ βββββββββ
βββ βββββββ βββ βββ βββ βββ βββ βββ
ββββββββ ββββββ βββ βββ βββ βββ βββ βββ
βββ βββ ββββββ βββ βββ βββ βββ ββββ βββ
βββββββββ ββββββ βββ βββ ββββββββ βββββββββ
βββ
βββ
Website | Architecture | FAQ | Terms of Service | Privacy Policy | Support
Airhop is an iOS + Android app (macOS and Windows coming soon) for private, offline-first peer-to-peer communication over Bluetooth mesh networks, with Nostr internet bridging and Cashu ecash payments. Our mission is to make censorship-resistant communication available to anyone: during natural disasters, internet blackouts, mass protests, or any situation where networks are unavailable, surveilled, or shut down.
I built this at a 24-hour hackathon (July 2026) during my final year of undergrad, on top of the foundation of bitchat. It uses the same BLE wire protocol and service UUIDs, meaning Airhop-installed devices can automatically discover and join the same mesh as nearby bitchat-installed devices, relay messages, and exchange DMs with zero setup. It also extends the protocol with Double Ratchet forward secrecy, offline ecash payments, and offline AI (not present in bitchat at the time). Project Trailer / Project Demo
Note
Airhop is an independent side project built and maintained by Areeb Ahmed in his free time. It is not backed by any company or organization, not affiliated with or endorsed by permissionlesstech or the bitchat project, and not an impersonation of any existing app or service.
Warning
Not externally security-reviewed. All code is personally reviewed and run through the security review agent before shipping, but this is not a substitute for a formal audit. Do not rely on its security for sensitive use cases. External audit planned for v1.9.0.
| Category | Feature | Description |
|---|---|---|
| π¬ Messaging | Private DMs | One-on-one end-to-end encrypted messaging |
| Private groups | Fixed-roster encrypted group chats. The creator signs the member list (up to 16) and shares the key over Noise; only listed members can read | |
| Private channels | Invite-only encrypted rooms. A shared key travels in the invite link, so anyone with the link joins and reads; there is no member cap | |
| Public channels | IRC-style group chat rooms anyone nearby can join | |
| Location channels | Public rooms scoped to a geohash cell, from a block to a region, bridged over the internet. Jump to any cell to read a place you are not in | |
| Bulletin board | Signed notices that outlive chat: pin a post to your mesh or location for 1 to 7 days, with urgent flags | |
| π Sharing | Photos & videos | Send photos and videos over the mesh. Photos support PNG, JPEG, GIF and WebP up to 512 KiB; videos support MP4 and MOV up to 1 MiB |
| Voice notes | Send a recorded voice note over the mesh in AAC, 16 kHz mono, up to 512 KiB | |
| Live voice | Hold the mic to talk to people in range, walkie-talkie style; recordings land in chat as voice notes | |
| File transfer | Send any file format over the mesh, including documents and archives, up to 1 MiB per file | |
| Store-and-forward courier | Messages are delivered automatically when a route becomes available, sealed to a one-time prekey for forward secrecy | |
| π Identity | No-account identity | Identity is an Ed25519 key pair stored only on your device |
| Human-readable names | Deterministic usernames derived from your public key | |
| QR contacts | Add a contact by scanning their QR code; carries their public keys, not just an ID | |
| End-to-end encryption | Secure sessions using the Noise XX protocol | |
| Forward secrecy | Double Ratchet protects past messages even if keys are later compromised | |
| Transfer to a new phone | Scan a code on the new phone and your identity, chats and wallet move across the local network; the old phone erases itself | |
| Panic wipe | Triple-tap instantly erases keys and local messages (nuke your account) | |
| πΈοΈ Networking | Bluetooth mesh | Communicate with nearby devices without internet |
| Mesh bridge | Link this area's public #bluetooth chat with another out-of-range Bluetooth crowd over the internet | |
| LAN mesh | Run the whole mesh over a shared Wi-Fi network or a phone hotspot, discovered by mDNS. Works iPhone to Android, unlike Wi-Fi Aware | |
| Wi-Fi Aware | Faster file transfers between two Android devices, or two iPhones. Not across platforms | |
| Multi-hop routing | Messages automatically relay across nearby devices (up to 7 hops) | |
| Relay nodes | Third-party Bitle hardware extends the mesh where nobody stands. Requires an ESP32 board, plus LoRa to link nodes | |
| bitchat compatibility | Airhop nodes communicate directly with bitchat on iOS and Android | |
| π Internet | Internet fallback | DMs and channels keep flowing over Nostr relays when a user moves out of Bluetooth range |
| Geo-relay discovery | Discover location-based channels across 300+ distributed Nostr relays | |
| Internet gateway | Lend your connection to a nearby offline phone so it can still reach the location (geohash) channels | |
| Tor integration | Route Nostr traffic through Tor, with obfs4 and Snowflake bridges where Tor itself is blocked |
| Category | Feature | Description |
|---|---|---|
| π° Payments | Cashu ecash | Pay someone standing next to you over Bluetooth, with no internet on either phone. Coins are stored encrypted on your device, checked against the mintβs signature the moment they arrive (NUT-12), and a payment that never reaches anyone can be taken back. Internet is only needed to top up, cash out, or confirm a received coin is unspent |
| Lightning | Top up from any Lightning wallet, and cash out to any Lightning invoice, through the mint you choose (NUT-04 / NUT-05) | |
| Nutzaps | Pay a Nostr identity over the internet, locked to their key so only they can spend it (NIP-61). Falls back to an encrypted message if they have not set it up | |
| Wallet recovery | Optional 12-word recovery phrase, off by default. Turn it on and a new phone can rebuild your balance by asking your mints which coins they signed (NUT-13 / NUT-09) | |
| π€ AI | Local assistant | On-device inference answers questions with zero network calls, data never leaves your device |
| π Social | AT Protocol | Opt-in bridge to Bluesky, using your Airhop identity |
| ActivityPub | Opt-in bridge to Mastodon, using your Airhop identity |
| Layer | Technology |
|---|---|
| Application Framework | React Native 0.86, Expo SDK 57 (bare workflow) |
| Network Transport | Bluetooth LE mesh (all platforms), LAN over mDNS + TCP, Wi-Fi Aware (Android and iPhone, same platform only), Nostr relay bridge, chosen per message |
| Cryptographic Protocols | Noise XX handshake, Double Ratchet algorithm |
| Cryptographic Library | @noble/curves, @noble/ciphers, @noble/hashes (Cure53 audited) |
| Identity & Signatures | Ed25519 scheme |
| Network Privacy | Arti Tor client, bundled on iOS and Android, with obfs4 and Snowflake bridges |
| Payment System | Cashu ecash via @cashu/cashu-ts: offline transfer, NUT-12 DLEQ verification, NUT-13 recovery phrase, NUT-04 and NUT-05 Lightning, plus NIP-61 Nutzaps |
| State Management | Zustand store, MMKV storage |
| Key Storage | iOS Keychain, Android Keystore |
Airhop chooses a transport per message. Bluetooth is the only one that needs no internet and no network at all, which is why it is the default. The others are used when they are available, and courier is what runs when none of them are.
| Bluetooth LE mesh | LAN (mDNS + TCP) | Wi-Fi Aware | Nostr relays | Courier (store-and-forward) | |
|---|---|---|---|---|---|
| Carries | Channel messages, DMs, files, ecash | Everything Bluetooth carries | DMs and files, when a link exists | DMs and geohash channel messages | Sealed text envelopes, up to 16 KiB each |
| Needs internet | No | No, but everyone must be on one network | No | Yes | No |
| iPhone to Android | Yes | Yes | No | Yes | Yes |
| Range | ~10-30 m indoors, up to ~100 m line of sight, extended by each hop | Wherever the network reaches | ~30 m | Global | Wherever the carrier walks |
| Max hops | 7 | 1 | 1 | 1 | 1 carrier, but unbounded in time |
| Speed | ~18 KiB/s to one peer, ~15 KiB/s to a channel | Network speed, with no radio pacing | Link speed, with no radio pacing | Not used for files | Not used for files |
| Latency per hop | 10-220 ms (randomized to avoid collisions) | Network round trip | n/a | Relay round trip; more over Tor | Whenever the carrier meets the recipient |
Notes on the numbers:
- Text messages (channel and DM) are tiny and effectively instant on any transport. Throughput only matters for files.
- The ~18 KiB/s figure is 467 bytes per fragment sent one every 25 ms. The delay is required: without it the radio drops fragments and the transfer never completes.
- A channel attachment paces at 30 ms instead (~15 KiB/s), since each broadcast fragment requires one radio write per connected peer, increasing airtime usage as the room grows.
- A 1 MiB file (the per-file cap) takes about 56 seconds to one peer. bitchat rejects anything larger as it decodes the packet, so the cap is not ours to raise and chunking above it was dropped. An Airhop-only path stays possible, but it cannot be the default without losing bitchat compatibility.
- LAN needs no such pacing, since the 25 ms delay is a Bluetooth requirement rather than a protocol one. The 467-byte fragment size stays regardless: the receiver reassembles by index and a peer may be relaying to Bluetooth next.
- Wi-Fi Aware doesnβt work across platforms. Apple requires pairing that Android canβt complete, so connections are limited to Android-to-Android (Android 10+) or iPhone-to-iPhone (iOS 26+, paired once).
- Nostr relays carry small signed events, not file bytes. Files can be shared over Nostr only by uploading them to a separate HTTP host and posting a link (NIP-96). Airhop does not do this: that host is a central server that can log, throttle, or take down your files, which is exactly what this app avoids. Attachments therefore travel only over Bluetooth, LAN or Wi-Fi.
- Courier is the fallback when no other transport can reach the recipient. The envelope is sealed to a one-time prekey before it leaves, so the carrier holding it cannot read it, and it is dropped after 24 hours if nobody meets the recipient. Text only: media is never couriered.
Timing intervals:
| Behaviour | Interval | Why |
|---|---|---|
| Presence broadcast | 4 s alone, 15-30 s | Fast while alone so devices meet quickly, then backs off |
| Gossip sync | 15 s | Lets a peer returning from out of range catch up on missed messages |
| Direct peer timeout | 45 s | Handles unreported link drops beyond broadcast |
| Mesh peer timeout | 60 s | Relayed peers get longer, since multi-hop packets arrive late |
| Geohash presence heartbeat | 40-80 s | Randomized so a fixed interval cannot fingerprint the sender |
| Geohash participant window | 5 min | Covers several heartbeats, so a missed round does not drop the peer |
Offline and private messengers generally fall into three categories:
- Internet-only messaging apps that rely on online infrastructure and cannot communicate locally without internet access.
- Radio-based mesh networks that work offline but require dedicated hardware.
- Phone-to-phone mesh apps that use Bluetooth and Wi-Fi on devices people already own.
Airhop belongs to the third category and extends it with a Nostr-based internet layer for long-distance communication when connectivity is available. The table is grouped in that order, starting with the apps most people already measure private messaging against.
| Project | Transport | Encryption | Works offline | Hardware-free | Open source | Platforms |
|---|---|---|---|---|---|---|
| Signal | Centralized servers | Signal protocol | β | β | β | iOS, Android, Desktop |
| Threema | Centralized servers | NaCl + Ibex | β | β | iOS, Android, Desktop | |
| Session | Onion routing (service nodes) | Session protocol | β | β | β | iOS, Android, Desktop |
| White Noise | Nostr relays | MLS (Marmot) | β | β | β | iOS, Android |
| Meshtastic | LoRa radio | AES-256 + Curve25519 PKI | β | β | β | iOS, Android, Web + hardware |
| goTenna | Proprietary sub-GHz radio | AES-256 + ECC-384 PKI | β | β | β | iOS, Android + hardware |
| Bridgefy | Bluetooth + Wi-Fi | Signal (libsignal) | β | β | β | iOS, Android |
| Berty | Bluetooth + mDNS | Scuttlebutt + Ratchet | β | β | β | iOS, Android |
| Briar | Bluetooth + Wi-Fi + Tor | Bramble | β | β | β | Android, Desktop |
| bitchat | Bluetooth + Nostr + Tor | Noise XX | β | β | β | iOS, Android |
| Airhop | Bluetooth + Nostr + mDNS + Wi-Fi + Tor | Noise XX + Double Ratchet | β | β | β | iOS, Android, Desktop, Web, CLI |
Signal and Threema are here as the benchmark rather than as alternatives. Both are excellent at what they do, and neither is trying to work without a network: Signal ties an account to a phone number and Threema to a Threema ID, and both stop entirely when the network does. Airhop is aimed at the moment after that, when there is no network to be excellent on.
git clone https://github.com/areebahmeddd/airhop
cd airhop
npm installCaution
Never run npx expo prebuild. The ios/ and android/ directories are checked in and hand-modified, and prebuild overwrites them.
Xcode setup
- Install Xcode from the Mac App Store, which also installs the iOS Simulator and base build tools
- Open Xcode at least once and let it install any additional required components when prompted
- Go to Xcode then Settings then Locations, and select the most recent version in the Command Line Tools dropdown
- Go to Xcode then Settings then Platforms, click the + icon, and add an iOS runtime if one is not already installed
- Run
bundle installfrom the project root, which installs the CocoaPods versionGemfile.lockpins (CI refuses aPodfile.lockwritten by any other), thenbundle exec pod install --project-directory=iosto install the iOS native dependencies. - Launch a simulator from the device dropdown, then run
npm run ios
The first
npm run iosbuilds the native app from scratch and can take several minutes. Later runs are much faster.
Requires a physical iPhone for BLE mesh testing (the iOS Simulator does not support Bluetooth) and supports iOS 16.0 or later.
Changing a native dependency also changes
ios/Podfile.lock, which pins exact pod versions. Regenerate it withbundle exec pod install --project-directory=iosin the same commit.
If a build fails after changing dependencies, clear the Xcode and CocoaPods caches before trying again:
rm -rf ios/Pods ios/build ~/Library/Developer/Xcode/DerivedData/Airhop-*
bundle exec pod install --project-directory=ios
npm run iosAndroid Studio setup
- Install Android Studio from the official site, which also installs the Android SDK and base build tools
- Open Android Studio at least once and let the setup wizard install any additional required components when prompted
- Click the gear icon and open Settings, then go to Languages & Frameworks then Android SDK
- On the SDK Platforms tab, tick API 34, API 35, and API 36, then click Apply to download them, since a fresh install does not include them
- On the SDK Tools tab, confirm Android SDK Build-Tools, Android SDK Platform-Tools, and Android Emulator are installed
- Copy the SDK path shown at the top of the Android SDK page into an
ANDROID_HOMEenvironment variable, or intoandroid/local.propertiesassdk.dir=<path>, since Gradle looks there for it - Open the virtual device manager: More Actions then Virtual Device Manager from the Welcome screen, or View then Tool Windows then Device Manager if a project is already open
- Click Create Device, choose a Pixel 9 Pro profile, select one of the API levels just installed, then click Finish
- Launch the emulator from the device list, then run
npm run android
The first
npm run androidbuilds the native app from scratch and can take several minutes. Later runs are much faster.
Requires a physical Android device for BLE mesh testing (the Android Emulator does not support Bluetooth) and supports Android 10.0 (API 29) or later.
Changing a native dependency also changes
android/app/gradle.lockfile, which pins exact versions. Regenerate it in the same commit.
If a build fails after changing dependencies, clear the Gradle caches before trying again:
cd android && ./gradlew --stop
rm -rf ../node_modules/expo-modules-core/expo-module-gradle-plugin/{build,.gradle}
./gradlew app:dependencies --write-locks
./gradlew app:assembleDebug -Pkotlin.incremental=false
cd .. && npm run android| Document | Description |
|---|---|
| VISION.md | Why Airhop exists and what it will never compromise on |
| ROADMAP.md | Version targets (v0.5.0 to v2.0.0), and gap analysis |
| ARCHITECTURE.md | System architecture, design decisions, and stack rationale |
| PROTOCOLS.md | Wire format, BLE UUIDs, and protocol specifications |
| BITCHAT.md | Bitchat codebase deep dive and implementation reference |
| PROGRESS.md | Current build, development milestones, and security analysis |
| SECURITY-REVIEW.md | Findings, fixes, accepted risks and upstream gaps |
| GLOSSARY.md | Definitions of technical terms used throughout the documentation |
| CONTRIBUTING.md | Development workflow and pull request guidelines |
| SECURITY.md | Security policy and vulnerability reporting |
| CODE_OF_CONDUCT.md | Community standards and expectations |
Airhop would not exist without the work of the bitchat community. Thank you to everyone who built the foundation this project stands on. Their work is released into the public domain under the Unlicense.
| Person | Contribution |
|---|---|
| jackjackbits | Created bitchat-ios, designed the BLE mesh protocol and wire format |
| callebtc | Lead on bitchat-android, author of the Cashu ecash protocol |
| Nadim Kobeissi | Noise Protocol implementation in bitchat |
| a1denvalu3 | Built the georelays toolkit that produces assets/data/nostr_relays.csv |
Help keep the project going by donating via the website, sponsoring on GitHub, or simply giving this repository a star. What the project needs funding for is published as a funding.json manifest, following the funding.json standard.