Shubham Bhandari 351f8861c7 Bind a verified seal to the nickname it was earned under
A verified key can rename itself onto a nickname the user trusts and keep
drawing the seal beside the new one.

Verification binds a FINGERPRINT, which is right. But the seal is rendered
beside a self-claimed nickname, and nothing binds those two together:

1. Eve announces "ravi" and gets verified in person by someone.
2. Eve announces "medic". The rename is free and silent — PeerManager
   computes `nicknameChanged` only to decide whether to refresh the list.
3. Every device that verified Eve now shows a second trusted-looking medic.

This is the Android mirror of permissionlesstech/bitchat#1708. It is smaller,
because this app has no vouching: there is no transitive trust to launder
onward, only the seal itself to withdraw.

The binding

`SecureIdentityStateManager` records the nickname a key was announcing when it
was verified, in its own store. Deliberately NOT the existing
`cached_fingerprint_nicknames`, which is a LAST SEEN cache overwritten on
every peer-list refresh — comparing against that would always match and catch
nothing.

Pinned on verification and on re-verification (the user just checked this key
again, under whatever name it shows now), cleared on unverify. Never pinned
from `resolvePeerDisplayName`, which falls back to a truncated peerID: that is
an identifier, not a claimed name, and pinning it would drop a seal on the
peer's first real announce.

A missing baseline never suppresses. Peers verified by earlier builds have
none, and dropping their seals on upgrade would teach people to ignore the
signal.

Where the seal comes from

Four surfaces, all gated: the connected peer row and the peer sheet (via
`ChatViewModel.isPeerVerified`), the conversation row, and offline favourite
rows. The last two are checked against the name THEY render rather than a live
announce — an offline favourite shows a name held in the favourites record, so
asking about a "current" name it is not announcing would answer nothing.

`VerificationHandler.isPeerVerified` / `isNoisePublicKeyVerified` have no
callers today and are gated anyway: leaving them ungated would hand the next
caller the answer this change exists to stop giving.

One surface is deliberately half covered

In the fingerprint sheet the seal GLYPH and its green tint are withheld, and
the word "verified" is not. The key genuinely is verified, so saying otherwise
would be false — and leaving the sheet untouched would let someone tap through
from a row whose seal just vanished and be reassured by a green checkmark. The
right fix is a sentence that says both, and a new string has to ship in all 34
locales; machine-translating a security warning is not something to do in
passing. Happy to wire it if someone supplies the wording.

Comparison rules

NFC, then a locale-independent case fold, then NFC again — folding can itself
emit decomposed sequences, and `Locale.ROOT` is not optional or a Turkish
phone would disagree with every other device about whether a peer had renamed.
Recasing your own nickname is not a rename; a fullwidth or Cyrillic look-alike
IS one. A trailing `#abcd` is stripped before comparing, ASCII hex only, since
`Char.isDigit()` accepts fullwidth digits and would truncate a nickname
literally ending in "#ABCD" into something that could match a baseline it
is not.

Tests

`NicknameBindingTest`, 13 cases: the rename attack, the rename-onto-a-
look-alike cases, recasing, combining accents, Turkish dotted I, the hash
suffix and the fullwidth-hex trap, "@" in a nickname, and both fail-open
paths. The logic lives in a pure-Kotlin `NicknameBinding` object with no
Android imports precisely so the security decision is testable without a view.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 16:39:02 +08:00
2026-08-11 19:33:44 +02:00
2026-09-07 14:55:50 +03:00
2025-07-08 20:37:46 +02:00
2025-07-08 20:37:46 +02:00
2026-08-12 01:55:05 +02:00

icon_128x128@2x

bitchat for Android

A decentralized peer-to-peer messaging app with dual transport architecture: local Bluetooth mesh networks for offline communication and internet-based Nostr protocol for global reach. No accounts, no phone numbers, no central servers.

This is the Android implementation of bitchat, fully protocol-compatible with the iOS version for cross-platform mesh communication.

bitchat.free

GitHub Releases

Get it on Google Play

See it in action

Offline mesh conversation Geohash globe picker
Active four-peer Bitchat mesh conversation with an image, voice messages, and text messages Bitchat geohash location picker showing the whole Earth and geohash grid

License

This project is released into the public domain. See the LICENSE file for details.

Features

  • Dual Transport Architecture: Bluetooth LE mesh for offline messaging, Nostr relays for internet-based messaging
  • Location-Based Channels: Geographic chat rooms using geohash coordinates over Nostr relays
  • Intelligent Message Routing: Automatically chooses the best transport, with queuing and retry when a peer is unreachable
  • End-to-End Encryption: Noise Protocol (XX pattern, X25519 + ChaCha20-Poly1305) for private messages over the mesh
  • Decentralized Mesh Network: Automatic peer discovery and multi-hop relay over Bluetooth LE (max 7 hops)
  • Wi-Fi Aware Transport: Higher-bandwidth local mesh on supported devices
  • Channel Chats: Topic-based group messaging with optional password protection (Argon2id + AES-256-GCM)
  • IRC-Style Commands: Familiar /join, /msg, /who style interface
  • Tor Support: Built-in Tor (Arti) for private internet connectivity
  • Emergency Wipe: Triple-tap to instantly clear all data
  • Cross-Platform: Binary protocol compatible with bitchat on iOS and macOS

Technical Architecture

Bluetooth Mesh Network (Offline)

  • Direct peer-to-peer within Bluetooth range, multi-hop relay through nearby devices
  • Noise Protocol sessions with forward secrecy; peer identities derived from static keys
  • Compact binary packet format with fragmentation, TTL routing, and deduplication
  • Adaptive duty cycling and connection limits for battery efficiency
  • Foreground service keeps the mesh alive within Android background execution limits

Nostr Protocol (Internet)

  • Global reach via public relays, geohash-based location channels
  • Private messages fall back to Nostr for mutual favorites when the mesh is unavailable
  • Ephemeral keys per geohash area

Android Stack

  • Kotlin, Jetpack Compose (Material 3), MVVM
  • Coroutines and Flow for all networking and state
  • Core components: MeshForegroundService (persistent connectivity), BluetoothMeshService / WifiAwareMeshService (transports), UnifiedMeshService (transport selection), NoiseSessionManager (encryption sessions), MessageRouter (mesh/Nostr routing with outbox retry)

Building

Requires Android Studio and the Android SDK (API 26+).

git clone https://github.com/permissionlesstech/bitchat-android.git
cd bitchat-android
./gradlew assembleDebug

Install on a connected device:

adb install -r app/build/outputs/apk/debug/app-debug.apk

The app requests Bluetooth, location (required for BLE scanning), and notification permissions at runtime.

Release APKs and the Android App Bundle can be rebuilt byte-for-byte in the pinned Linux container. Maintainers should follow the Android release guide. See Reproducible builds for the build trust model and public GitHub/Google Play verification procedures.

Testing

# Unit tests
./gradlew test

# Lint
./gradlew lint

# Instrumented tests (requires a device or emulator)
./gradlew connectedAndroidTest

Note that BLE mesh behavior is difficult to emulate; protocol and session logic is covered by unit tests, while radio-level behavior needs real devices.

Description
bluetooth mesh chat, IRC vibes
Readme
Languages
Kotlin 86.1%
Java 9.9%
Python 2.5%
Shell 1.1%
Rust 0.3%
Other 0.1%