bitchat
Applicationpermissionlesstech/bitchat
Decentralized peer-to-peer messaging with Bluetooth mesh and Nostr.
Overview
A decentralized messaging app with dual transport: local Bluetooth mesh for offline communication and Nostr protocol for global reach. No accounts, no central servers, privacy-first with end-to-end encryption. Features location-based channels and IRC-style commands.
README Preview
\n\n## bitchat\n\nA 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.\n\n[bitchat.free](http://bitchat.free)\n\n📲 [App Store](https://apps.apple.com/us/app/bitchat-mesh/id6748219622)\n\n## License\n\nThis project is released into the public domain. See the [LICENSE](LICENSE) file for details.\n\n## Features\n\n- **Dual Transport Architecture**: Bluetooth mesh for offline + Nostr protocol for internet-based messaging\n- **Location-Based Channels**: Geographic chat rooms using geohash coordinates over global Nostr relays\n- **Intelligent Message Routing**: Automatically chooses best transport (Bluetooth → Nostr fallback)\n- **Decentralized Mesh Network**: Automatic peer discovery and multi-hop message relay over Bluetooth LE\n- **Privacy First**: No accounts, no phone numbers, no persistent identifiers\n- **Private Message End-to-End Encryption**: [Noise Protocol](https://noiseprotocol.org) for mesh, NIP-17 for Nostr\n- **IRC-Style Commands**: Familiar `/slap`, `/msg`, `/who` style interface\n- **Universal App**: Native support for iOS and macOS\n- **Emergency Wipe**: Triple-tap to instantly clear all data\n- **Performance Optimizations**: LZ4 message compression, adaptive battery modes, and optimized networking\n\n## [Technical Architecture](https://deepwiki.com/permissionlesstech/bitchat)\n\nBitChat uses a **hybrid messaging architecture** with two complementary transport layers:\n\n### Bluetooth Mesh Network (Offline)\n\n- **Local Communication**: Direct peer-to-peer within Bluetooth range\n- **Multi-hop Relay**: Messages route through nearby devices (max 7 hops)\n- **No Internet Required**: Works completely offline in disaster scenarios\n- **Noise Protocol Encryption**: End-to-end encryption with forward secrecy\n- **Binary Protocol**: Compact packet format
FAQ (4)
TroubleshootingHow to fix bitchat macOS build failing on Xcode 27 with 'ambiguous use of words' or swift-secp256k1 plugin validation error?
Upgrade the pinned 21-DOT-DEV/swift-secp256k1 dependency to **0.24.0 or later** — the maintainer released 0.24.0 specifically to fix the Xcode 27 / Swift 6.4 plugin validation failure, then update Package.resolved and rebuild with xcodebuild -project bitchat.xcodeproj -scheme "bitchat (macOS)" -configuration Debug build.
Root cause: the pinned 0.21.1 fails to compile because its custom var words in UInt256.swift collides with Swift.BinaryInteger.words in Swift 6.4 ('ambiguous use of words'), while 0.23.2 fails xcodebuild's plugin validation because SharedSourcesPlugin uses @available(_PackageDescription 6.0) APIs unavailable in Xcode 27's bundled SwiftPM.
If you cannot upgrade, workaround: vendor swift-secp256k1 0.23.2 locally — copy it to localPackages/swift-secp256k1, delete the Plugins/ directory, remove the .plugin(name: "SharedSourcesPlugin", capability: .buildTool()) target and plugins: ["SharedSourcesPlugin"] from the P256K/ZKP targets in its Package.swift (flatten Sources/Shared/* into both target source dirs), point Package.swift to .package(path: "localPackages/swift-secp256k1"), replace XCRemoteSwiftPackageReference with XCLocalSwiftPackageReference in project.pbxproj, and delete Package.resolved. The build then succeeds with 0 errors.
TroubleshootingHow do I fix the flaky SimulatedMeshTests failure where deliveredFrameCount is 16 instead of at most 12?
The flake is caused by announce-admission logic using wall-clock Date() while the test harness only advances simulated time. On a loaded CI runner, the 0.15 s throttle window can elapse, producing four extra announce frames and making mesh.deliveredFrameCount - baseline rise from 12 to 16. Fix it by removing wall-clock dependence from the simulated mesh: inject/use the test's simulated clock for the announce throttle, or drain the mesh to a quiet state before recording the baseline so wall-clock-driven announce traffic is excluded from the measured delta. The relevant fixes are tracked in PRs #1622/#1649, with #1651 draining mesh traffic before the baseline and bounding the storm budget to the relay behavior under test.
TroubleshootingWhy do large compressed messages sent from Android silently fail to appear on iOS, and how is the payload-size mismatch fixed?
This happens because Android allows up to AppConstants.Protocol.MAX_PAYLOAD_LENGTH = 10 MiB decompressed payload, while iOS rejects frames above FileTransferLimits.maxFramedFileBytes ≈ 1.13 MiB. A highly compressed Android message can therefore expand to 2–5 MiB and be silently dropped by iOS.
Fix:
1. Update the iOS client to a build containing the PR "Accept compressed payloads up to Android's 10 MiB decompressed ceiling," which aligns iOS's inbound decompressed-payload limit with Android.
2. Apply the encode-side enforcement PR that refuses to emit frames above the global expanded-payload ceiling, preventing senders from creating messages that decoders will reject.
3. As a temporary workaround, keep the uncompressed/original payload below approximately 1.13 MiB when sending from Android to older iOS clients.
The 50,000:1 compression-ratio guard is separate and does not prevent this mismatch because iOS allocates based on the wire-declared originalSize before that guard is applied.
TroubleshootingHow do I fix a GitHub Actions workflow token that cannot create or update a pull request?
The default workflow token lacks permission to open the PR. Create it manually by opening the compare URL and selecting automation/georelay-data as the source branch:
https://github.com/permissionlesstech/bitchat/compare/main...automation/georelay-data?expand=1
Then submit the PR for normal review. If the repository still blocks the token, push the unchanged branch to a personal fork and open the cross-fork PR against main. For automated runs, grant the workflow token pull-requests: write (and contents: write when required), or use a PAT/app token with PR creation permission.