* Give private DMs an unmistakable visual signature An open DM renders identically to the public room — same view, same green-on-black surface, with a small header name and two orange icons as the only cues. For this audience the cost of misreading "am I in the encrypted DM or the public channel?" is severe: sensitive text typed into the wrong composer. Four presentation-layer cues; no formatter or cache changes: - The composer placeholder states the destination instead of a generic prompt: "message @jack — private" in a DM, "message #mesh — public, nearby" on mesh, "message #9q8yy — public" in a geohash channel. - A persistent lock caption sits above the DM composer. It reads "private · end-to-end encrypted" only once the Noise session is actually secured or verified, and "private conversation" before that — the caption must not overstate encryption mid-handshake. - The DM sheet header carries a faint orange wash (6%), extending the existing orange self-accent to the chrome. - Each private message row is prefixed with a small orange lock glyph (view-layer, hidden from VoiceOver — the caption carries the semantic; the cached AttributedString formatter is untouched). New strings are added source-language (en) only. * Fix geohash-DM caption and placeholder Two carve/review follow-ups: - The privacy caption showed "private conversation" for geohash DMs, implying they are not encrypted — but geohash DMs are NIP-17 gift-wrapped (always end-to-end encrypted), they just carry no Noise session status. Show the encrypted caption for geohash DMs and for secured Noise sessions; the pre-secured wording now applies only while a mesh handshake is still in progress. - The private-chat placeholder prepended "@" to the partner name, which for a geohash DM (whose display name is already "#geohash/@name") produced a doubled "@". The "@" is now added only for mesh nicknames. * Make the DM header orange wash visible in the matrix theme The 6% orange background was chained after .themedSurface(), so in the default matrix theme (whose themedSurface paints an opaque background) the wash sat behind the surface and never rendered — it was only visible in liquid glass. Apply the orange tint before .themedSurface() so it layers in front of the themed background. * Align DM lock glyph across text and media rows; keep header wash visible under glass Media rows in a private conversation now get the same leading lock glyph as text rows, so left edges line up instead of misaligning by the glyph's width. The DM header's orange wash gets a higher opacity under the liquid-glass theme, where themedSurface() adds no opaque backing and 6% orange disappears into the backdrop gradient. Also drops the dead sender != "system" guard in TextMessageView — system messages are routed to systemMessageRow before this view is built. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Remove orphaned content.input.message_placeholder from the string catalog The destination-stating placeholders replaced its last code reference; nothing on the branch resolves this key anymore. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: jack <212554440+jackjackbits@users.noreply.github.com> Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
bitchat
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. It's the side-groupchat.
License
This project is released into the public domain. See the LICENSE file for details.
Features
- Dual Transport Architecture: Bluetooth mesh for offline + Nostr protocol for internet-based messaging
- Location-Based Channels: Geographic chat rooms using geohash coordinates over global Nostr relays
- Intelligent Message Routing: Automatically chooses best transport (Bluetooth → Nostr fallback)
- Decentralized Mesh Network: Automatic peer discovery and multi-hop message relay over Bluetooth LE
- Privacy First: No accounts, no phone numbers, no persistent identifiers
- Private Message End-to-End Encryption: Noise Protocol for mesh, NIP-17 for Nostr
- IRC-Style Commands: Familiar
/slap,/msg,/whostyle interface - Universal App: Native support for iOS and macOS
- Emergency Wipe: Triple-tap to instantly clear all data
- Performance Optimizations: LZ4 message compression, adaptive battery modes, and optimized networking
Technical Architecture
BitChat uses a hybrid messaging architecture with two complementary transport layers:
Bluetooth Mesh Network (Offline)
- Local Communication: Direct peer-to-peer within Bluetooth range
- Multi-hop Relay: Messages route through nearby devices (max 7 hops)
- No Internet Required: Works completely offline in disaster scenarios
- Noise Protocol Encryption: End-to-end encryption with forward secrecy
- Binary Protocol: Compact packet format optimized for Bluetooth LE constraints
- Automatic Discovery: Peer discovery and connection management
- Adaptive Power: Battery-optimized duty cycling
Nostr Protocol (Internet)
- Global Reach: Connect with users worldwide via internet relays
- Location Channels: Geographic chat rooms using geohash coordinates
- 290+ Relay Network: Distributed across the globe for reliability
- NIP-17 Encryption: Gift-wrapped private messages for internet privacy
- Ephemeral Keys: Fresh cryptographic identity per geohash area
Channel Types
mesh #bluetooth
- Transport: Bluetooth Low Energy mesh network
- Scope: Local devices within multi-hop range
- Internet: Not required
- Use Case: Offline communication, protests, disasters, remote areas
Location Channels (block #dr5rsj7, neighborhood #dr5rs, country #dr)
- Transport: Nostr protocol over internet
- Scope: Geographic areas defined by geohash precision
block(7 chars): City block levelneighborhood(6 chars): District/neighborhoodcity(5 chars): City levelprovince(4 chars): State/provinceregion(2 chars): Country/large region
- Internet: Required (connects to Nostr relays)
- Use Case: Location-based community chat, local events, regional discussions
Direct Message Routing
Private messages use intelligent transport selection:
-
Bluetooth First (preferred when available)
- Direct connection with established Noise session
- Fastest and most private option
-
Nostr Fallback (when Bluetooth unavailable)
- Uses recipient's Nostr public key
- NIP-17 gift-wrapping for privacy
- Routes through global relay network
-
Smart Queuing (when neither available)
- Messages queued until transport becomes available
- Automatic delivery when connection established
For detailed protocol documentation, see the Technical Whitepaper.
Setup
Option 1: Using Xcode
cd bitchat
open bitchat.xcodeproj
To run on a device there're a few steps to prepare the code:
- Clone the local configs:
cp Configs/Local.xcconfig.example Configs/Local.xcconfig - Add your Developer Team ID into the newly created
Configs/Local.xcconfig- Bundle ID would be set to
chat.bitchat.<team_id>(unless you set to something else)
- Bundle ID would be set to
- Entitlements need to be updated manually (TODO: Automate):
- Search and replace
group.chat.bitchatwithgroup.<your_bundle_id>(e.g.group.chat.bitchat.ABC123)
- Search and replace
Option 2: Using just
brew install just
Want to try this on macos: just run will set it up and run from source.
Run just clean afterwards to restore things to original state for mobile app building and development.
Localization
- Base app resources live under
bitchat/Localization/Base.lproj/. Add new copy toLocalizable.stringsand plural rules toLocalizable.stringsdict. - Share extension strings are separate in
bitchatShareExtension/Localization/Base.lproj/Localizable.strings. - Prefer keys that describe intent (
app_info.features.offline.title) and reuse existing ones where possible. - Run
xcodebuild -project bitchat.xcodeproj -scheme "bitchat (macOS)" -configuration Debug CODE_SIGNING_ALLOWED=NO buildto compile-check any localization updates.