mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-26 17:25:22 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
229a41557e |
+2
-2
@@ -34,7 +34,7 @@ bitchat is designed for private, account-free communication. This policy describ
|
||||
- A panic wipe deletes both stores.
|
||||
|
||||
5. **Recent public mesh messages and notices**
|
||||
- Signed public mesh messages may be kept in a protected local gossip archive for up to 15 minutes so they can cross mesh partitions and survive a short relaunch.
|
||||
- Signed public mesh messages may be kept in a protected local gossip archive for up to 6 hours so they can cross mesh partitions and survive a relaunch.
|
||||
- Public bulletin-board posts and deletion tombstones persist until the post's author-selected expiry, at most seven days. Both stores are bounded and panic-wipeable.
|
||||
- These items are public to the mesh or board where they are posted; they are not confidential messages.
|
||||
|
||||
@@ -114,7 +114,7 @@ No cryptographic system can protect content after a recipient reads, copies, scr
|
||||
- **In-memory chat timelines and active connections:** until the app closes or state is cleared.
|
||||
- **Queued outgoing private messages:** until acknowledged, dropped by bounded policy, or 24 hours, whichever comes first.
|
||||
- **Opaque courier envelopes:** until handed off, evicted by bounded policy, or 24 hours, whichever comes first.
|
||||
- **Recent public mesh gossip:** up to 15 minutes.
|
||||
- **Recent public mesh gossip:** up to 6 hours.
|
||||
- **Public board posts and tombstones:** until expiry, at most seven days.
|
||||
- **Groups, favorites, preferences, identity keys, bookmarks, and media:** until removed by the feature, panic wipe, quota eviction where applicable, or app removal.
|
||||
- **Nostr data:** according to the policies of the relays that receive it.
|
||||
|
||||
@@ -18,7 +18,7 @@ This project is released into the public domain. See the [LICENSE](LICENSE) file
|
||||
- **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
|
||||
- **Privacy First**: No accounts, no phone numbers, no servers. Note that the mesh does use a persistent per-device identifier derived from your identity key — see [the whitepaper](WHITEPAPER.md) on identity and metadata for what a nearby radio can observe
|
||||
- **Private Message End-to-End Encryption**: [Noise Protocol](https://noiseprotocol.org) for mesh, BitChat private envelopes for Nostr fallback
|
||||
- **IRC-Style Commands**: Familiar `/slap`, `/msg`, `/who` style interface
|
||||
- **Universal App**: Native support for iOS and macOS
|
||||
@@ -34,7 +34,7 @@ BitChat uses a **hybrid messaging architecture** with two complementary transpor
|
||||
- **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
|
||||
- **Noise Protocol Encryption**: End-to-end encryption, with forward secrecy for live sessions (store-and-forward mail is sealed without it — see the whitepaper)
|
||||
- **Binary Protocol**: Compact packet format optimized for Bluetooth LE constraints
|
||||
- **Automatic Discovery**: Peer discovery and connection management
|
||||
- **Adaptive Power**: Battery-optimized duty cycling
|
||||
|
||||
+12
-5
@@ -18,7 +18,7 @@ bitchat is a decentralized, peer-to-peer messaging application for secure, priva
|
||||
* **Authentication:** peers are identified by cryptographic keys; announcements are signed and verified.
|
||||
* **Resilience:** the network functions in lossy, low-bandwidth, partitioned environments with churning membership.
|
||||
* **Eventual delivery:** a message to an out-of-range peer should still arrive — relayed by the mesh, carried by a moving person, or resting on an internet relay — within a bounded retention window.
|
||||
* **Ephemerality by default:** no plaintext message content is ever written to disk. Everything the store-and-forward stack persists is either sealed ciphertext or already-public broadcast traffic, and all of it dies with the panic wipe.
|
||||
* **Ephemerality by default:** conversation timelines live in memory only. Everything the store-and-forward stack persists is either sealed ciphertext or already-public broadcast traffic, and all of it dies with the panic wipe. Media is the exception: accepted images and voice notes are written to disk unsealed, protected by the platform's data-protection class rather than by app-layer encryption, and bounded by a storage quota.
|
||||
|
||||
## 2. Architecture Overview
|
||||
|
||||
@@ -36,13 +36,17 @@ Each device holds two long-term key pairs in the Keychain:
|
||||
* a **Curve25519 static key** for Noise key agreement — its SHA-256 fingerprint is the peer's stable identity, and
|
||||
* an **Ed25519 signing key** for packet signatures.
|
||||
|
||||
On the mesh, peers appear under short ephemeral IDs derived per session; favoriting pins the full Noise public key so identity survives across sessions. Mutual favorites also exchange Nostr public keys for the internet path. Optional QR verification binds a nickname to a fingerprint in person.
|
||||
On the mesh, peers appear under a short 8-byte peer ID. That ID is **not ephemeral**: it is the first 8 bytes of the SHA-256 fingerprint of the device's Noise static key, so it is stable across sessions, reboots, and reinstalls that preserve the keychain, and it changes only when the identity itself is replaced by a panic wipe. Favoriting pins the full Noise public key so identity survives across sessions. Mutual favorites also exchange Nostr public keys for the internet path. Optional QR verification binds a nickname to a fingerprint in person.
|
||||
|
||||
Signed announcements additionally carry the nickname, the Noise static public key, and the Ed25519 signing public key in cleartext (§4.5), so a passive receiver in radio range can link a device across time and place regardless of the peer ID. Unlinkable presence is not a property this protocol currently provides; see §9.
|
||||
|
||||
## 4. BLE Mesh Layer
|
||||
|
||||
### 4.1 Packet Format
|
||||
|
||||
A compact binary header (version, type, TTL, timestamp, flags) is followed by an 8-byte sender ID, an optional 8-byte recipient ID, the payload, and an optional Ed25519 signature. Version 2 packets may carry an explicit source route. Signatures exclude the TTL byte so relays can decrement it without invalidating them. Packets other than fragments are padded toward uniform sizes.
|
||||
A compact binary header (version, type, TTL, timestamp, flags) is followed by an 8-byte sender ID, an optional 8-byte recipient ID, the payload, and an optional Ed25519 signature. Version 2 packets may carry an explicit source route. Signatures exclude the TTL byte so relays can decrement it without invalidating them.
|
||||
|
||||
Only `noiseEncrypted` and `noiseHandshake` packets are padded, toward 256/512/1024/2048-byte buckets; every other type — public messages, announcements, board posts, group messages, fragments, files, and voice frames — goes out at its natural length. Padding is PKCS#7-style with pad bytes equal to the pad length, and because that length must fit one byte, a frame needing more than 255 bytes to reach its bucket is emitted unpadded. Payload length is therefore observable for most traffic.
|
||||
|
||||
### 4.2 Flood Control
|
||||
|
||||
@@ -124,11 +128,11 @@ Bare local counters (deposits, handovers, sprays, opens, outbox flushes and drop
|
||||
|
||||
## 8. Security Considerations
|
||||
|
||||
* **Relay nodes** cannot read private traffic; they forward padded, opaque ciphertext.
|
||||
* **Relay nodes** cannot read private traffic; they forward opaque ciphertext. Padding applies to Noise frames only (§4.1), so other packet types relay at their natural length.
|
||||
* **Couriers** are quota-bounded mailbags. A malicious courier can drop mail (redundant copies and deposit retry mitigate this) but cannot read it, link it across days, or amplify it — copy budgets are capped and every envelope is validated against size and lifetime policy on deposit.
|
||||
* **Flooding abuse** is bounded by TTL clamps, deduplication, per-depositor quotas, connect-rate limits, and announce-rate limiting.
|
||||
* **Replay** of public broadcasts is bounded by the 6-hour acceptance window plus deduplication; private payloads are protected by Noise nonces.
|
||||
* **Metadata.** BLE proximity is inherently observable; ephemeral IDs and daily-rotating courier tags limit long-term correlation. Nostr traffic can ride Tor.
|
||||
* **Metadata is the weakest part of this design, and the peer ID does not help.** The 8-byte sender ID in every packet header is derived from a never-rotating key (§3), and announcements publish the static keys and nickname in cleartext, so a passive listener can enumerate participants and follow a device between places. Announcements also carry up to ten direct-neighbor IDs (§4.3), which hands a single sniffer the local adjacency graph. Origin packets leave at the default TTL, so hop distance identifies the originator. Daily-rotating courier tags do limit correlation of carried mail, and Nostr traffic can ride Tor. Addressing the radio-layer exposure is future work (§9).
|
||||
* **No forward secrecy for sealed mail or Nostr private envelopes** (§5.2–5.3) means compromise of a recipient's static key can expose retained ciphertext addressed to that key.
|
||||
|
||||
## 9. Future Work
|
||||
@@ -137,6 +141,9 @@ Bare local counters (deposits, handovers, sprays, opens, outbox flushes and drop
|
||||
* Couriered media beyond the 16 KiB text cap.
|
||||
* Probabilistic relay and edge-of-network TTL boosting for very dense and very sparse graphs.
|
||||
* Multi-hop courier routing informed by encounter history.
|
||||
* **Rotating on-air identity.** Epoch-rotating peer IDs, with static-key disclosure moved inside the encrypted handshake and mutual favorites recognising each other through a tag derived from their shared secret, so presence stops being linkable across sessions (§3, §8).
|
||||
* **Padding for non-Noise packet types**, and closing the gap where a frame needing more than 255 bytes of padding is emitted unpadded (§4.1).
|
||||
* Making the neighbor list in announcements optional, or restricted to authenticated links (§4.3).
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -14,18 +14,22 @@
|
||||
///
|
||||
/// ## Overview
|
||||
/// BitChat's identity system separates concerns across three distinct layers:
|
||||
/// 1. **Ephemeral Identity**: Short-lived, rotatable peer IDs for privacy
|
||||
/// 1. **Network Identity**: the 8-byte peer ID seen on air
|
||||
/// 2. **Cryptographic Identity**: Long-term Noise static keys for security
|
||||
/// 3. **Social Identity**: User-assigned names and trust relationships
|
||||
/// 3. **Social Identity**: assigned names and trust relationships
|
||||
///
|
||||
/// This separation allows users to maintain stable cryptographic identities
|
||||
/// while frequently rotating their network identifiers for privacy.
|
||||
/// The layers are separate concerns, but they are not independent: the network
|
||||
/// identity is *derived* from the cryptographic one, so it does not provide
|
||||
/// unlinkability. Rotating peer IDs would be a change to this model, not a
|
||||
/// description of it — see the note below.
|
||||
///
|
||||
/// ## Three-Layer Architecture
|
||||
///
|
||||
/// ### Layer 1: Ephemeral Identity
|
||||
/// - Random 8-byte peer IDs that rotate periodically
|
||||
/// - Provides network-level privacy and prevents tracking
|
||||
/// ### Layer 1: Network Identity
|
||||
/// - 8-byte peer ID = first 8 bytes of the Noise static key fingerprint
|
||||
/// - **Not ephemeral and not rotating.** It is stable across sessions and
|
||||
/// reboots, and changes only when the underlying identity is replaced by a
|
||||
/// panic wipe. A passive observer can use it to track a device.
|
||||
/// - Changes don't affect cryptographic relationships
|
||||
/// - Includes handshake state tracking
|
||||
///
|
||||
@@ -33,7 +37,7 @@
|
||||
/// - Based on Noise Protocol static key pairs
|
||||
/// - Fingerprint derived from SHA256 of public key
|
||||
/// - Enables end-to-end encryption and authentication
|
||||
/// - Persists across peer ID rotations
|
||||
/// - The root of the peer ID above, and never rotated on a schedule
|
||||
///
|
||||
/// ### Layer 3: Social Identity
|
||||
/// - User-assigned names (petnames) for contacts
|
||||
@@ -44,10 +48,13 @@
|
||||
/// ## Privacy Design
|
||||
/// The model is designed with privacy-first principles:
|
||||
/// - No mandatory persistent storage
|
||||
/// - Optional identity caching with user consent
|
||||
/// - Ephemeral IDs prevent long-term tracking
|
||||
/// - Optional identity caching with explicit consent
|
||||
/// - Social mappings stored locally only
|
||||
///
|
||||
/// It does **not** currently prevent long-term tracking by a passive radio
|
||||
/// observer: the peer ID is stable (Layer 1) and signed announcements carry the
|
||||
/// static keys and nickname in cleartext.
|
||||
///
|
||||
/// ## Trust Model
|
||||
/// Four levels of trust:
|
||||
/// 1. **Unknown**: New or unverified peers
|
||||
@@ -56,17 +63,17 @@
|
||||
/// 4. **Verified**: Cryptographic verification completed
|
||||
///
|
||||
/// ## Identity Resolution
|
||||
/// When a peer rotates their ephemeral ID:
|
||||
/// When a peer's ID changes (a panic wipe on their side, or a future rotation):
|
||||
/// 1. Cryptographic handshake reveals their fingerprint
|
||||
/// 2. System looks up social identity by fingerprint
|
||||
/// 3. UI seamlessly maintains user relationships
|
||||
/// 3. UI seamlessly maintains existing relationships
|
||||
/// 4. Historical messages remain properly attributed
|
||||
///
|
||||
/// ## Conflict Resolution
|
||||
/// Handles edge cases like:
|
||||
/// - Multiple peers claiming same nickname
|
||||
/// - Nickname changes and conflicts
|
||||
/// - Identity rotation during active chats
|
||||
/// - Identity replacement during active chats
|
||||
/// - Network partitions and rejoins
|
||||
///
|
||||
/// ## Usage Example
|
||||
@@ -85,8 +92,12 @@ import BitFoundation
|
||||
|
||||
// MARK: - Three-Layer Identity Model
|
||||
|
||||
/// Represents the ephemeral layer of identity - short-lived peer IDs that provide network privacy.
|
||||
/// These IDs rotate periodically to prevent tracking while maintaining cryptographic relationships.
|
||||
/// Represents the network layer of identity — the peer ID seen on air, plus the
|
||||
/// handshake state tracked against it.
|
||||
///
|
||||
/// Named "ephemeral" for historical reasons; the peer ID is in fact stable,
|
||||
/// being derived from the Noise static key fingerprint. It does not rotate and
|
||||
/// does not prevent tracking.
|
||||
struct EphemeralIdentity {
|
||||
var handshakeState: HandshakeState
|
||||
}
|
||||
@@ -99,8 +110,9 @@ enum HandshakeState {
|
||||
}
|
||||
|
||||
/// Represents the cryptographic layer of identity - the stable Noise Protocol static key pair.
|
||||
/// This identity persists across ephemeral ID rotations and enables secure communication.
|
||||
/// The fingerprint serves as the permanent identifier for a peer's cryptographic identity.
|
||||
/// This identity outlives any change to a peer's network ID and enables secure communication.
|
||||
/// The fingerprint serves as the permanent identifier for a peer's cryptographic identity, and
|
||||
/// its first 8 bytes are the peer ID broadcast on the mesh.
|
||||
struct CryptographicIdentity: Codable {
|
||||
let fingerprint: String // SHA256 of public key
|
||||
let publicKey: Data // Noise static public key
|
||||
|
||||
@@ -38,11 +38,15 @@
|
||||
/// 7. **Decoding**: Binary data parsed back to message objects
|
||||
///
|
||||
/// ## Security Considerations
|
||||
/// - Message padding (to 256/512/1024/2048-byte blocks) obscures actual content length
|
||||
/// - Noise frames are padded (to 256/512/1024/2048-byte blocks) to obscure
|
||||
/// content length; other packet types are not padded, so their payload
|
||||
/// length is observable
|
||||
/// - Randomized relay jitter reduces the traffic-analysis signal; there is no
|
||||
/// cover traffic or per-message timing obfuscation
|
||||
/// - Integration with Noise Protocol for E2E encryption
|
||||
/// - No persistent identifiers in protocol headers
|
||||
/// - The 8-byte sender ID in every header IS a persistent identifier: it is
|
||||
/// derived from the long-lived Noise static key and rotates only on a panic
|
||||
/// wipe. Treat headers as linkable across sessions.
|
||||
///
|
||||
/// ## Message Types
|
||||
/// - **Announce/Leave**: Peer presence notifications
|
||||
|
||||
@@ -28,7 +28,9 @@ Signed announces can expose:
|
||||
- A bounded set of short direct-neighbor identifiers
|
||||
- A coarse rendezvous geohash when the bridge capability is enabled
|
||||
|
||||
The app does not advertise the device's user-assigned name. iOS manages BLE address randomization; bitchat does not attempt to create a stable MAC address. RSSI, timing, traffic volume, and radio fingerprints remain observable to nearby receivers.
|
||||
The app does not advertise the device's assigned name. iOS manages BLE address randomization; bitchat does not attempt to create a stable MAC address.
|
||||
|
||||
That randomization does not deliver the unlinkability it might suggest, because the application layer publishes stable identifiers above it. The 8-byte peer ID in every packet header is the first 8 bytes of the Noise static key fingerprint, so it does not rotate; announces carry the static keys themselves; and the fixed service UUID makes any bitchat device detectable as such by a passive scanner. A receiver in radio range can therefore recognise a specific device across sessions and locations, and detect that the app is in use at all. RSSI, timing, traffic volume, and radio fingerprints remain observable as well.
|
||||
|
||||
Ingress validates announce structure, sender binding, signatures, payload sizes, and freshness. Current-link Noise authentication is required before destructive courier handoff or strict directed delivery. Floods, queues, fragments, ingress work, and per-peer state are bounded.
|
||||
|
||||
@@ -45,7 +47,7 @@ Residual risk: private-message metadata such as timing, radio adjacency, ciphert
|
||||
|
||||
## Public Gossip, Boards, and Media
|
||||
|
||||
- Recent signed public mesh messages are archived in Application Support for up to 15 minutes so gossip sync survives a relaunch and can cross mesh partitions.
|
||||
- Recent signed public mesh messages are archived in Application Support for up to 6 hours so gossip sync survives a relaunch and can cross mesh partitions.
|
||||
- Signed public board posts and tombstones persist until author-selected expiry, at most seven days. Stores are bounded by global and per-author quotas.
|
||||
- Group metadata (name, roster, creator, epoch) persists as protected JSON; group keys live in the keychain until leave/removal/wipe.
|
||||
- Voice notes and images are stored in Application Support. Incoming media has a 100 MB oldest-first quota; outgoing media does not have an equivalent automatic lifetime and remains until cleanup, panic wipe, or app removal. Panic wipe invalidates detached preparation work, cancels active transfers, closes live capture files, and removes the managed media tree before returning.
|
||||
|
||||
@@ -9,7 +9,16 @@
|
||||
import struct Foundation.Data
|
||||
|
||||
/// Provides privacy-preserving message padding to obscure actual content length.
|
||||
/// Uses PKCS#7-style padding with random bytes to prevent traffic analysis.
|
||||
///
|
||||
/// PKCS#7-style: every pad byte equals the pad length, which is what `unpad`
|
||||
/// verifies. The bytes are not random.
|
||||
///
|
||||
/// Two limits are worth knowing before relying on this for traffic analysis
|
||||
/// resistance. Only Noise frames are padded at all (see the outbound packet
|
||||
/// policy); everything else travels at its natural length. And because the pad
|
||||
/// length has to fit in one byte, `pad` declines any request needing more than
|
||||
/// 255 bytes — so a frame far below its target bucket is emitted unpadded
|
||||
/// rather than padded to a smaller bucket.
|
||||
struct MessagePadding {
|
||||
// Standard block sizes for padding
|
||||
static let blockSizes = [256, 512, 1024, 2048]
|
||||
|
||||
Reference in New Issue
Block a user