bitchat
要成为德、智、体兼优的劳动者,锻炼身体极为重要。身体健康是求学和将来工作之本。运动能治百病,能使人身体健康,头脑敏捷,对学习有促进作用。——吴耕民
https://github.com/permissionlesstech/bitchat
bitchat:当聊天离开账号体系,回到设备之间与地点之间
有些聊天工具的存在感很强,先让你注册、登录、绑定,再慢慢允许你开口说话。bitchat 的气质却完全不一样,它像是把门先打开了,再告诉你:这里没有账号、没有电话号码,也没有持久化身份标识。
它不是在传统中心化消息系统里做一点修补,而是直接把交流这件事拆开重想:一部分交给本地蓝牙 Mesh 网络,让离线通信成为可能;另一部分交给基于互联网的 Nostr 协议,让消息可以从近处走向远方。两条路径并排站着,既不互相排斥,也不试图取代对方,而是一起构成了 bitchat 的双传输架构。
项目地址:https://github.com/permissionlesstech/bitchat
第一眼就很鲜明:去中心化、点对点,还有一点 IRC 的气味
仓库 description 只有一句很短的话:bluetooth mesh chat, IRC vibes。
短,但很传神。
一边是 Bluetooth mesh chat,意味着它关心的是设备与设备之间如何建立联结,尤其是在没有传统网络依赖的前提下;另一边是 IRC vibes,意味着它在交互气质上显然也不是那种把一切都藏在图形界面背后的现代即时通讯产品,它保留了一种更直接、更命令式、更熟悉的聊天感觉。
README 里也确实印证了这一点。它把 bitchat 描述为一个去中心化点对点消息应用,采用双传输架构:本地 Bluetooth mesh 网络负责离线通信,基于互联网的 Nostr 协议负责全球触达。整句话几乎把项目的骨架一次性说透了。
它不是在“没有网络时勉强可用”,而是在认真设计离线通信
很多产品谈“离线”时,语气都像是在描述一种退化模式:没有网络也能凑合发一发。bitchat 的 README 并不是这种态度。
在 Bluetooth Mesh Network 一节里,它把离线层写得相当完整:
- 本地通信依靠 Bluetooth 范围内的直接点对点连接
- 消息可以通过附近设备进行多跳中继,最多 7 hops
- 不需要互联网
- 使用 Noise Protocol 做端到端加密,并提供 forward secrecy
- 使用适配 Bluetooth LE 约束的紧凑二进制协议
- 支持自动发现和连接管理
- 具备面向电池优化的自适应功耗机制
这不是一层“降级选项”,而是一套被认真构建的传输基础设施。尤其是“多跳中继”这一点,会让人立刻意识到它描绘的场景并不只是两台手机面对面聊天,而是一整个由附近设备组成、消息可以继续传递的本地网状结构。
README 里甚至明确写到,这一层可以在灾害场景下完全离线工作。它不依赖互联网,这让 bitchat 的“可沟通性”不只受制于基站和常规网络条件。
但它也没有把自己困在“只适合线下”的边界里
如果只有 Bluetooth mesh,这会是一款很鲜明的本地通信工具;而 bitchat 的另一半,是 Nostr Protocol。
README 给出的 Nostr 层定位同样清楚:
- 通过互联网中继连接全球用户
- 用 geohash 坐标组织地理频道
- 依赖分布式中继网络
- 私聊在 Nostr 回退路径下使用 BitChat private envelopes
- 每个 geohash 区域使用新的加密身份
这一层的存在,让 bitchat 不必在“本地离线”与“全球触达”之间二选一。它更像是在说:离你近的时候,我们可以在附近传播;离你远的时候,我们可以借助互联网中继继续把消息送出去。
这种结构非常像一张会自动变形的网。近处的节点用 Bluetooth 连起来,远处的关系则交给 Nostr relay 延伸。不是非此即彼,而是各自承担自己最擅长的那部分距离。
它对“隐私优先”的表达也很直接
README 在功能列表里写得很明确:No accounts, no phone numbers, no persistent identifiers。
这句话之所以有冲击力,是因为它几乎正面反过来了今天主流聊天产品的登录逻辑。很多通讯应用把身份系统当成一切功能的起点,而 bitchat 则把“不要这些东西”写成自己的基础前提。
这并不意味着它忽视安全。相反,README 对私聊加密的表述非常明确:
- Mesh 私聊使用 Noise Protocol
- Nostr 回退路径使用 BitChat private envelopes
同时,README 还专门强调:BitChat 的 private-envelope 格式是专有的,不兼容 NIP-17、NIP-44 或 NIP-59。它把 Nostr 用作 relay transport,但私有载荷只在 BitChat 客户端之间互通;这些 private payload 被放进 kind-1059 事件里,内容是带 v2: 前缀、基于 BitChat 自身方案的 XChaCha20-Poly1305 构造,而不是 NIP-44 加密。
这段说明很技术,但也很关键。它清楚地划出了边界:bitchat 会使用 Nostr 的中继通道,但并不等于它在私密消息层面直接套用那些标准兼容格式。它的私聊回退机制,是明确的 BitChat 自身实现。
它把“地点”也变成了聊天的一部分
bitchat 的一个很有辨识度的设计,是 Location-Based Channels。
README 里提到,它会通过 geohash 坐标来构建地理聊天房间。在 Channel Types 一节,这一点被展开得很具体。
mesh #bluetooth
这是本地蓝牙 Mesh 频道。
- 传输方式是 Bluetooth Low Energy mesh network
- 作用范围是多跳可达范围内的本地设备
- 不需要互联网
- 使用场景包括离线通信、抗议、灾害、偏远地区
这个频道类型非常“现场化”。它依赖的是附近存在的设备,而不是远端平台。
Location Channels
README 给出了几种示例:
block #dr5rsj7neighborhood #dr5rscountry #dr
这些频道依赖 Nostr over internet,并通过 geohash 精度划分地理范围。README 明确列出了对应粒度:
block:7 个字符,城市街区级别neighborhood:6 个字符,区域或街区级别city:5 个字符,城市级别province:4 个字符,州或省级别region:2 个字符,国家或更大区域
这套设计很有意思,因为它不是简单地把“群聊”换一个名字,而是把“你在哪里”直接纳入了频道结构。聊天房间不只是一组用户关系,也是一块地理范围的切片。这样一来,区域社区讨论、本地活动交流、同地附近的公共对话,就有了一个很自然的组织方式。
私聊路由也不是死板的,它会自己判断怎么走
README 里专门有一节叫 Direct Message Routing,解释私聊消息的传输选择逻辑。
1. Bluetooth First
优先使用 Bluetooth。
前提是与目标对象已经建立了 Noise session。README 给出的解释是:这是最快、也最私密的选项。
2. Nostr Fallback
当 Bluetooth 不可用时,回退到 Nostr。
这种情况下,消息会使用接收方的 Nostr public key,并通过 BitChat 的 app-specific private-envelope encryption 处理,然后走全球 relay 网络转发。
3. Smart Queuing
当 Bluetooth 和 Nostr 都不可用时,消息进入排队状态。
README 说明,消息会先排队,等到传输方式恢复可用后自动送达。
这套路由逻辑看上去很“聪明”,但它并不是那种神秘的黑箱式聪明,而是非常清楚地把优先级和回退逻辑摆了出来。对用户来说,这意味着不需要自己每次手动判断到底该走哪条通道;对系统来说,这意味着“可通信性”本身就是一个动态选择过程。
功能列表里,很多细节都带着很强的使用感
README 的 Features 一节并不只是罗列大词,还包含不少很有画面感的点。
IRC 风格命令
它支持 familiar 的 /slap、/msg、/who 风格接口。
这让 IRC vibes 不只是 description 里的气氛词,而是确实落在交互层里的具体选择。对于熟悉这类命令式聊天习惯的人来说,这会带来一种很直接的亲切感。
Universal App
README 写明它原生支持 iOS 和 macOS。
这意味着它不是只押注某一个苹果端入口,而是同时覆盖了移动端和桌面端。
Emergency Wipe
三击即可立刻清除所有数据。
这是一个非常短、却很有分量的功能描述。它既说明产品确实在考虑紧急场景,也说明“本地数据”这件事在它这里是可以被迅速处置的,而不是只能被动地保留。
Performance Optimizations
README 提到它包含:
- LZ4 消息压缩
- 自适应电池模式
- 优化过的网络处理
这些点虽然没有被长篇展开,但放在一起能看出 bitchat 并不只满足于“功能成立”,它也在关注消息体积、功耗与传输效率这些非常实际的工程问题。
如果你想自己跑起来,它也把 setup 讲得很清楚
README 给出了两种主要的 setup 方式。
Option 1:使用 Xcode
最直接的方式是:
1 | open bitchat.xcodeproj |
如果要进行 signed device build,需要先创建本地忽略配置,并把示例 team ID 替换成自己的 Apple Developer Team ID:
1 | cp Configs/Local.xcconfig.example Configs/Local.xcconfig |
README 还特别说明,Local.xcconfig.example 会基于这个 team ID 推导出唯一的 app 和 App Group identifiers,而 entitlement 文件已经引用了 $(APP_GROUP_ID),因此不需要去修改受跟踪的 project 或 entitlement 文件。
这段说明很实在。它不是让你手动到处改工程设置,而是通过本地配置层把签名相关信息接进去,既保持了仓库本身的整洁,也减轻了接入时的改动成本。
一些命令行检查方式
README 在仓库根目录给出了几个有用命令。
macOS Debug build without signing:
1 | xcodebuild -project bitchat.xcodeproj -scheme "bitchat (macOS)" \ |
完整 SwiftPM 测试套件:
1 | swift test |
iOS 模拟器测试:
1 | xcodebuild -project bitchat.xcodeproj -scheme "bitchat (iOS)" \ |
如果本机没有 iPhone 17,README 还提供了查看已安装 simulator 的方式:
1 | xcodebuild -showdestinations -project bitchat.xcodeproj -scheme "bitchat (iOS)" |
这一整段 setup 内容有一个很舒服的地方:它没有模糊地说“理论上可以构建”,而是把可操作的命令直接写到了台面上。
Option 2:使用 just
README 还提供了另一条更轻便的路径:
1 | brew install just |
并且补充说明:
just build和just run使用当前的bitchat (macOS)scheme- Xcode 输出保存在被忽略的
.DerivedData/目录 - 不会去 patch source、project、configuration 或 entitlement 文件
同时:
just clean只删除.DerivedData/和.build/- 不会调用 Git,也不会恢复 tracked files
- 未提交的工作会被保留
just test运行 SwiftPM suitejust test-ios运行 iPhone 17 simulator suite
这部分很容易让人产生好感,因为它把常见的构建、运行、清理、测试流程整理得很平整,而且明确说明不会乱动你的源码和工程配置。
本地化也被单独照顾到了
README 的 Localization 一节并不长,但信息明确:
- App 本体的本地化位于
bitchat/Localizable.xcstrings - Share extension 的字符串位于
bitchatShareExtension/Localization/Localizable.xcstrings - 键名建议偏向表达意图,例如
app_info.features.offline.title - 优先复用已有 key
- 修改本地化后,可以用 macOS Debug build 命令做编译检查
这说明项目在国际化或多语言支持上,至少已经形成了明确的资源组织方式和命名习惯。
测试部分也很有意思:它在追求可预测、快速,而且尽量无竞争问题
仓库里的 bitchatTests/README.md 专门解释了测试套件的设计。开头第一句就说得很清楚:这套测试使用 in-memory networking harness,目标是让端到端和集成测试具备确定性、速度快、没有 race condition,同时不触碰生产代码。
这句话其实非常有分量,因为它直接说明了测试的设计哲学:不是一边测一边和真实环境互相碰撞,而是先为网络行为建立一个可控、可复现的实验场。
In-Memory Bus
README 介绍了 MockBLEService.swift:
- 全局
registry把peerID映射到MockBLEService实例 adjacency记录模拟链路- 在
setUp()中调用MockBLEService.resetTestBus()清理状态 - 可以通过
simulateConnectedPeer(_:)和simulateDisconnectedPeer(_:)管理拓扑 - 测试中可通过
messageDeliveryHandler观察解码后的BitchatMessage - 也可通过
packetDeliveryHandler观察原始BitchatPacket - 通过线程安全的
seenMessageIDs避免 flooding 或 relay 过程中的重复投递
只看这份说明,就能感受到测试并不是简单 mock 一下函数返回值,而是在模拟整个 BLE 网络中的节点关系、链路变化和消息传播行为。
Broadcast Flooding
README 提到一个标志位:MockBLEService.autoFloodEnabled。
当它为 true 时,公共广播会传播到整个连通分量,忽略 TTL 的可达性限制,但仍然通过去重避免循环。Integration tests 会在 setUp 中启用它,用于模拟大网络广播;E2E tests 则关闭它,以保持路由显式并验证 TTL 行为。
这种区分很细腻。它说明测试作者非常清楚,不同类型测试要验证的不是同一件事:有时要关注广播覆盖,有时要关注路由规则本身。
Rehandshake Flow
README 还解释了 Noise 的 rehandshake 恢复流程:
- 旧的 NACK recovery path 已移除
- 恢复依赖解密失败或状态失步后的 Noise session rehandshake
NoiseSessionManager管理每个 peer 的 session- 解密失败时,先清空本地 session,再发起新的握手
- 对端接受并替换其 session
- 测试
IntegrationTests.testRehandshakeAfterDecryptionFailure会故意破坏密文,引入解密错误,再验证重握手后加解密恢复成功
这一段信息很能体现项目的严肃程度。因为真正有价值的系统,往往不是只在顺风顺水时能跑,而是在 session 错乱、消息损坏、状态失步时,是否有可验证的恢复路径。
连 ViewModel 的拆分方式,也能看出它关注的功能边界
bitchat/ViewModels/Extensions/README.md 很短,但很有效地说明了 ChatViewModel 的模块化方向:
ChatViewModel+Tor.swift:处理 Tor 生命周期事件和通知ChatViewModel+PrivateChat.swift:管理私聊逻辑、媒体传输、图片、语音笔记和文件处理ChatViewModel+Nostr.swift:处理 Nostr 集成、Geohash 频道和 Nostr 身份管理ChatViewModel.swift:保留核心状态、初始化和协调逻辑
这段 README 至少说明了一个事实:在项目结构层面,bitchat 已经把 Tor、私聊媒体能力、Nostr 逻辑等内容拆到了清晰的扩展模块里,而不是糅在一个巨大文件中。哪怕这里只是目录说明,也足以看出它在功能复杂度面前选择的是“分层整理”,不是“硬塞在一起”。
它最迷人的地方,是让“聊天”这件事重新带上空间感和韧性
读完 README 后,会很自然地意识到,bitchat 并不是只想再做一个消息应用。
它把聊天放回了几种非常具体的现实条件中:
- 当网络消失时,附近设备还能不能传递消息
- 当距离扩大时,消息能不能借助互联网继续延伸
- 当位置本身重要时,频道能不能按地理精度生成
- 当私聊需要穿越不同传输层时,路由能不能自动选择
- 当使用者不愿意绑定账号、手机号和持久身份时,系统还能不能成立
这些问题,一旦被放在一起,bitchat 的轮廓就会变得非常清晰。它不是围绕社交平台关系链来组织自己,而是围绕传输、位置、隐私和可达性来组织自己。
从 README 能读出的 bitchat,像一套为“消息总得想办法到达”而生的系统
如果只依据仓库 description 和 README 信息来概括,permissionlesstech/bitchat 至少有这样几个非常明确的面向:
它是一个去中心化点对点消息应用;它把 Bluetooth mesh 离线通信和 Nostr 互联网中继并列为两条互补通道;它支持基于 geohash 的位置频道;它强调无账号、无电话号码、无持久标识的隐私前提;它为私聊提供端到端加密,并设计了 Bluetooth 优先、Nostr 回退、不可用时智能排队的消息路由逻辑;它原生支持 iOS 与 macOS;它同时在构建、测试、本地化和 ViewModel 模块化上呈现出相当清楚的工程组织。
如果说有些聊天工具像繁华城市里的巨型中枢,bitchat 则更像一张会自己找路的网:近时贴地而行,远时借道而去;有网络时继续扩散,没网络时也不轻易沉默。
