Chat and browse with the power of people nearby โ no signal needed
GPL-3.0 ใป Android 8.0+ ใป a fork of
bitchat for Android
No root required ใป No VPN
NOLOSS (noroshi โ the Japanese word for a signal fire) lets you chat with people near you even where there is no cellular signal. Phones link to each other directly over Bluetooth LE, and because every phone quietly acts as a relay, the reach grows with the number of people. There is no server anywhere in the middle.
On top of that, NOLOSS has two features of its own: Invitation and Pay It Forward. Beyond the text of a chat, what this app set out to carry is a person, and the internet itself.
Over the internet, you can invite exactly one person who is far away. They join the same chat area as if they were standing in the room with you. We call the invited person the "Chosen One".
Send an invitation to someone far away
A Chosen One merges two chat areas into one
A Chosen One can accept invitations from more than one person. When they do, the two chat areas merge into one. Chain that, and places far apart end up on the same thread.
The merge survives the Chosen One going offline
If the invitation was made in Distributed mode, the merged areas stay connected even when the person standing between them drops off the network.
One phone with a working connection is enough: it can lend that connection to the people around it. On the receiving end you just use your browser and apps the way you normally would.
A phone with a connection lends it to the phone next to it
Whoever receives it can pass it on again
Hop after hop, one connection spreads outward across the mesh in a bucket brigade.
The part that was hardest to build
On Android, every Wi-Fi Direct group owner is pinned to the address 192.168.49.1. Chain two of those
groups together to relay, and the relay node's upstream address is its own address โ the packet is
delivered locally and the hop dies. The problem is well documented; the known workaround is to switch
between groups, which means a disconnect and a reconnect every time. NOLOSS instead changes how the
upstream is addressed, so the chain forms without either link ever going down.
(The mechanism is written up under Technical details โ The two hardest parts below.)
Note
What can be carried is ordinary web traffic โ the kind browsers use. By design, apps that communicate another way (voice and video calls, online games) may fail to connect: only TCP is relayed, and UDP is not carried. Reading the news and looking at a map work as they normally do.
Invitation and Pay It Forward both sit on top of the same thing: a Bluetooth bucket brigade. Phones running the app find each other on their own, and every one of them quietly becomes a relay. The more people there are, the further it reaches.
No signal, but people nearby are still reachable
When everyone relays, it reaches the people beyond them
NOLOSS is a fork of bitchat, and none of the wire protocol was changed. Someone next to you running bitchat is in the same chat area you are โ public messages and private messages both cross, with neither side installing anything new.
The reach extends past the invitation, too. If a NOLOSS device has merged another chat area over the internet, the people on the far side appear in bitchat's list as ordinary neighbours and conversation works โ with nothing configured on the bitchat side. It never learns that invitations exist; it simply inherits the range.
Not breaking what already worked is the rule this project is built on. Invitation and Pay It Forward sit on top of that, and they are NOLOSS-only: receiving a shared connection requires NOLOSS on both ends.
Coming to Google Play. A closed test is starting soon, and we are looking for people willing to help test it!
NOLOSS ships four tutorials, 19 pages in total, for people opening it for the first time. The collapsible section below reproduces all of them, in the same order and with the same wording the app uses โ so you can see what the app actually says before installing it.
Open all 19 pages (Getting started 4 ใป Invitation 5 ใป How to connect 2 ใป Receiving 3 ใป Sharing 5)
1. What is this app? Even with no signal, you can chat with people nearby by forming a shared "chat area".
2. What is a chat area? A network built entirely by phones. Like a bucket brigade, messages hop from friend to friend, reaching far away.
3. How far does it reach? The more friends join, the bigger the chat area grows. Hiking, concerts, crowded city centersโฆ it works anywhere.
4. A quick setup first We'll enable Bluetooth to connect phones, Location to find friends nearby, Notifications so you never miss a message, and Battery settings so the app keeps running.
1. What is an invitation? If you're online, you can send an invitation to someone far away and bring them into your chat area.
2. The invited person isโฆ We call the invited person the "Chosen One". The Chosen One also needs an internet connection. They join the chat area through the internet and you.
3. Powers of the Chosen One The Chosen One can do more than join from afar. By accepting another invitation from someone else far away, they can merge multiple chat areas together.
4. You were the Chosen One! If you invited them in Distributed mode, the merged chat areas stay connected even when the Chosen One goes offline.
5. How to send an invitation There are two ways. If the Chosen One is nearby, show the QR code on your phone and let them scan it. If they're far away, copy the invitation text and send it by email or SMS for them to paste. Let's get started.
๐๏ธ Maat (distributed) (Recommended)
Connects to the other person through a "detour" on the internet. It works on any kind of connection,
so it links up easily and is hard to break. After chat areas are merged, if the other area is also
connected in distributed mode, the areas stay connected even when the Chosen One goes offline.
Note: photos, voice, and some long messages from the neighboring area cannot travel past the detour.
Nobody along the detour can read your messages, but the fact that you communicated does remain.
๐ Maverick (direct) Your device and theirs connect one-to-one, directly. With no detour it is fast, and it leaves no record of your exchange anywhere else. However, some combinations of connections cannot link up. If it will not connect, try the distributed mode.
1. What is Pay It Forward? A friend who has internet can share their connection with people around them. Your phone gets online by connecting to that friend's signal. No coverage, no data plan โ as long as a friend is nearby, you're fine.
2. How does it reach me? The sharing phone sends out a special Wi-Fi signal. Like a bucket brigade, the signal is passed from friend to friend, reaching far away. It's not fast, but news and maps work just fine.
3. One-time setup Next, a notification will walk you through each step. It takes 2โ3 minutes, and you only ever do it once โ after that everything is automatic. You can stop anytime and pick up where you left off.
1. What is Pay It Forward? It hands your internet connection out to friends in the chat area. Everyone gets online using your data.
2. How does it spread? The data you pay forward travels over Wi-Fi, relayed from friend to friend across the chat area.
3. How does it spread?? It grows like this.
4. Anything to watch out for? Even people right next to you can't receive it unless they use NOLOSS. Traffic from friends is treated just like traffic through a normal Wi-Fi router โ your phone becomes the gateway for everyone. Of course, you can turn it off anytime.
5. What do I need to share? Just a mobile network connection! Your friends are notified that you are paying it forward, and each of them sets up their own phone to receive it.
Open the technical write-up โ architecture, the two hardest parts, hard-won lessons, compatibility, building
Invite exactly one person who is nowhere near you, and drop them into the local chat area as if they were in the room. Two transports. The inviter picks one every time an invitation is created โ there is no silent default โ and the app marks Distributed as the recommended choice:
- Maverick (direct) โ UDP hole punching over global IPv6 with no STUN, no signalling server, no relay of any kind. The invite string is handed to the other person by whatever messenger they already use. Verified on real carrier networks (including one that hands out an IPv6-only APN) and across NAT from a home Wi-Fi.
- Maat (distributed) โ the recommended option, because it also connects where hole punching cannot (IPv4-only CGNAT). It bridges the two devices through public Nostr relays. Payloads are NIP-17 gift-wrapped, and private messages remain end-to-end encrypted underneath, so the relay carries ciphertext it cannot read. Announcements, handshakes, keepalives and acks are posted as ephemeral events (kind 21059) that relays are not supposed to store, which keeps them out of the fetch window. Encrypted control messages still travel in stored events โ a relay cannot tell those apart from human ones โ so the window is reduced, not emptied, of machine traffic.
A device with a working internet connection can lend it to the mesh. Devices without one reach the web through multiple hops of other phones, chaining Wi-Fi Direct links where Bluetooth bandwidth is not enough.
- Roles are decided autonomously per device (
EXIT= has upstream,RELAY= forwards). No election, no coordinator, no two-tier topology โ a leaf is just aRELAYwith no children. - Link selection is demand-driven and hysteretic, with route vectors (not hop counts) to avoid loops, borrowing the lighting discipline from RPL: a relay only raises a Wi-Fi Direct group while it holds a route to the root, and folds it (poisons) when the route is lost.
- Every hop is mutually authenticated and encrypted with
Noise_IK_25519_ChaChaPoly_SHA256, so a device without the app cannot carry a single byte, and an evil-twin access point cannot complete the handshake. - Derived from an earlier prototype by the same author.
Everything is switched off through a single flag file (noloss/Features.kt) rather than deleted, so tracking
upstream stays possible: geohash location channels, Tor, IRC-style commands, media sending, nickname editing,
QR verification UI, and the debug settings sheet. The Phase-2 Nostr "gateway" is likewise dormant behind a flag.
Upstream's Nostr fallback for direct messages is kept, but gated. When two mutual favorites are out of Bluetooth range, DMs travel over Nostr โ in NOLOSS only if the user has explicitly turned it on, off by default, like every other path that touches a third-party server. The feature itself comes from upstream; what NOLOSS adds here is the opt-in gate.
Everything else in this project is an application of something already established โ see On the rest of the
design below, where I say so explicitly. These two took by far the longest, and I could not find an existing
implementation to copy from. If you know of prior work for either, please open an issue and I will link it
here. Both are marked in the source with โ
THE HARD PART so you can find them.
Before building either, I read through the other mesh messengers, the long-range radio mesh networks, the federated chat protocols and the mesh routing stacks. Those comparison notes live in the design log, which is not published.
Multi-hop Wi-Fi Direct via scoped link-local IPv6. On Android every Wi-Fi Direct Group Owner is pinned to
192.168.49.1. Chain two groups together to relay and the relay node's upstream address is its own address โ
the kernel delivers locally and the hop dies. The problem itself is well documented in the literature; the known
fixes are group relaying (the relay node switches between groups, so every switch is a disconnect and a
reconnect), IP subnet negotiation, or connecting one GO to another. NOLOSS instead addresses the upstream as
fe80::xxxx%wlan0 โ a link-local address plus an interface scope. Link-local addresses are per-interface, so
the collision cannot arise, and both links stay up at once: nothing disconnects. I did not find this
approach in prior work; that is not a proof it is new.
โ RadioController.upstreamIsLinkLocal, RadioHost.goLinkLocal, MeshUpstreamProvider.
Multi-hop internet sharing over a BLE mesh. A device with a connection shares it outward across a
Wi-Fi Direct chain, with the BLE mesh acting as the nervous system that decides who becomes an exit and who
becomes a relay. The other mesh messengers I looked at carry messages only, and the mesh-to-internet gateways
I found run the other direction โ they expose a mesh to the internet rather than carrying the internet
into one.
โ ProxyController, noloss/proxy, noloss/radio, noloss/relay.
Links whose substance is a person. Independent local BLE meshes merge into one chat area with zero servers: someone issues an invitation, someone else accepts it, and both area hosts consent. That consent is the link. Chained, it grows a wide-area mesh. Federated chat systems link server to server, mesh radio networks reach the outside through a broker, and other mesh protocols discover their routes automatically โ a topology where human agreement is the edge is one I could not find elsewhere. The closest thing I found in spirit builds a trust web out of explicit invitations, but it does not merge local meshes. Again: if you know of a system that does this, please open an issue โ I would rather link it than claim it.
Deliberate asymmetry. The inviting side may host exactly one guest at a time; the invited side โ the Chosen One, in the app's own vocabulary โ may hold several invitations. The chain therefore grows, but no node can quietly turn into a hub. The limit is a design choice, not an implementation shortcut.
Five findings that cost real device time. Each is marked โ
HARD-WON at the place in the source where it
applies, with the measurements attached.
- Upstream bitchat has two TTL gates, and anything arriving with TTL below 2 never reaches fan-out โ while
upstream itself answers history sync with TTL=0. Our code was correct; the packets died at a gate belonging
to a path we were riding on. Add a new route and you must count the gates on every existing road it
traverses. โ
InetMeshService.onGossipStored - A Nostr relay's fetch window (
limit=100) can be eaten by your own machine traffic. Gift wraps carry a randomised timestamp from the past two days, so the 100 events you get back are a lottery fixed at posting time. Periodic keepalives and acks crowded out human messages: 2 of 12 messages came back, and 207 of 265 returned events were things the receiver discards on sight. Not a Nostr limitation โ a loading mistake. Budget any external resource you write to. โMaatFerry - Android reaps an idle Wi-Fi Direct GO after ~300 seconds and the app cannot prevent it. Polling,
discoverPeers, none of it helps. You must fold before the OS does and relight on demand. A resource owned by the OS has its own time constant, shorter than yours. โRadioController.NO_CLIENT_FOLD_MS - Samsung OneUI promotes your own Wi-Fi Direct group to the default network and hijacks outbound traffic โ
and
bindSocketbinds the socket route but not DNS, so binding fixes half of it and leaves name resolution hijacked. Half-working is the worst failure mode. โInternetOutboundBinder - A BLE stack can die silently while still reporting live connections. Measured: 11m52s with zero inbound
while the app held "2 server + 7 client" unchanged for 17 minutes. Adapter on, scanner running, RSSI reads
12/12 successful โ every plumbing health check lied. Only "did anything actually arrive?" told the truth.
For any ledger, cache, or flag, answer at design time: when this rots, what will I compare it against?
โ
BleLinkLiveness
Everything else here is deliberately grounded in existing, established practice, and I want to be explicit
about that rather than let the sections above imply more originality than exists. The exit/relay lighting
discipline is RPL (RFC 6550) translated; hold-down and poisoning come from the same place. The heartbeat is
RFC 4861 ยง7.3.1 used the way it was meant to be used, and child address self-reporting is ยง7.2.3. Liveness
detection follows BFD (RFC 5880/5882). Graceful handover of an exit is BGP Graceful Shutdown (RFC 8326) plus
RSVP-TE make-before-break (RFC 3209 ยง2.5). Per-hop encryption is a straight application of Noise_IK. The
lighting stagger is 3GPP A3 time-to-trigger. Chat-area merging uses the tombstone pattern that federated chat
protocols use to upgrade a room. Battery gates use AOSP's own config_lowBatteryWarningLevel rather than an
invented number. IPv6
hole punching without STUN is simply what you get when there is no NAT.
The interesting work was mostly in finding out which established answer applied, and in the five lessons above โ not in inventing mechanisms.
Wire compatibility with upstream bitchat is a hard requirement, not a nice-to-have. A NOLOSS user and a bitchat user in the same room can talk to each other. This was measured on real devices against bitchat 1.7.4, not assumed: signed announcements verified on both sides, and public messages, private messages, images and voice notes all crossed. The BLE service UUID is upstream's, unchanged.
The interesting consequence is that an unmodified upstream client inherits the federation for free. Peers reached through an invitation are re-announced onto the BLE mesh, so they land in bitchat's peer list as ordinary neighbours and can be talked to โ bitchat has no concept of an invitation and does not need one. Nothing is configured on that side.
NOLOSS extensions ride along as additive TLV blocks and announce fields that upstream clients ignore
(node capability, route vectors, radio addressing). The binary protocol under protocol/ is unmodified.
The BLE mesh core under mesh/ is treated as near-untouchable and every change there requires an explicit
numbered decision โ but "near-untouchable" is not "untouched": 8 files under mesh/ differ from the fork
point (+174 / โ48 lines), and each carries a modification notice at the top saying what changed and why.
They are additive hooks (capability announcements riding along with the identity announcement, connection
liveness readouts, duplicate-handshake suppression) plus one inherited fragmentation bug fixed in place.
Standard Android toolchain โ no extra steps.
git clone <this repository>
cd NOLOSS
./gradlew assembleDebug # APK per ABI in app/build/outputs/apk/debug/
./gradlew bundleRelease # AAB for Play (requires your own signing config)minSdk 26,targetSdk 36, AGP 8.10.1 / Gradle 8.13, Kotlin 2.2 with Jetpack Compose.- Release builds run R8 with resource shrinking.
- The source namespace is still
com.bitchat.android, deliberately. Only theapplicationIdwas changed (com.iea.noloss), which is what identifies the app on a device and on Play. Renaming the namespace would rewrite thepackageline of every file and make diffing against upstream impractical, so it stays as inherited. It is a fork; the tree says so. - The bundled Tor libraries (
app/src/main/jniLibs/*/libarti_android.so, ~21 MB) come from upstream and are currently unused โFeatures.TORisfalse. They are kept rather than deleted so that tracking upstream stays possible, which is the same rule every disabled feature follows. Build instructions for reproducing them yourself are intools/arti-build/.
The comments in this codebase are dense, in Japanese, and full of references like D-116, s125, ๅฐ้ท32
or ยง5-38. They all point into the same private development log:
D-NNNโ a numbered design decision: what was chosen, why, and which alternatives were rejected and how they failed.sNNN/ใปใใทใงใณNNโ a numbered development session, most of which involved measurements on real devices.ๅฐ้ทNN("landmine NN") โ a numbered entry in a running list of mistakes that were made once and must not be repeated. Where one is cited, the lesson itself is normally spelled out on the spot.ๆฏๆญฃ#N/ๅๆคๆป#N/Nๅทก็ฎ/่ฃ่ถณ(ii)โ a numbered item from an internal re-inspection round. Code here is re-read against the requirements several times before it is considered done, and these tags link a line back to the round that changed it.ยงN-M,ยง8.1a,่จญ่จv2,ไปๆงๆธB,ๅฎ่ฃ ๅฐๅณand similar โ sections of the internal design documents. Comments also name those documents directly:PROJECT_SPEC.md(requirements),DECISION_LOG.md(decisions),RESEARCH_NOTES.md(research),TECH_GUIDE.md(implementation map),HANDOVER.md(session handover), and the two Japanese-titled design documents for internet sharing and area merging.
None of those documents are published. They record device serial numbers, packet captures, and notes from a private development log, so this repository ships the code only. The comments are still worth reading on their own: where a decision matters to the code, the reasoning is written out in place rather than left behind a reference number. Treat a bare reference number as a footnote you are not missing much by skipping.
- Send an invitation directly to someone in a geohash channel
- A third way to connect an invitation โ an anonymous (Majestic) mode routed over Tor
- Better transmission quality for Pay It Forward
No order and no dates are promised.
GPL-3.0. See LICENSE.md.
This project is a derivative work of bitchat-android by permissionlesstech, which is itself the Android port of bitchat. Modified files carry a modification notice at the top of the file, as required by the license.
Note: upstream's README states the project is released into the public domain, while the repository ships GPL-3.0 in
LICENSE.md. NOLOSS follows the license file, and all NOLOSS-authored code is GPL-3.0. This says nothing about upstream's own terms โ if you want to reuse bitchat itself, ask upstream, not us. The choice here binds this fork only.
- permissionlesstech / bitchat-android โ the mesh core this is built on. The hard part was already solved.
- jackjackbits / bitchat โ the original iOS app and the protocol.
- IEA โ author of NOLOSS and of the earlier prototype its internet-sharing engine comes from.




















