mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-24 22:25:18 +00:00
Harden PTT audio and the 1.7.1 release (#1423)
Centralize PTT and voice audio-session ownership, harden courier/bridge/outbox delivery and recovery, correct location and delivery-state races, add privacy/release metadata, and ship reproducible universal Arti slices with Release CI coverage. Validated by the full iOS suite, repeated audio/fragment/performance regressions, BitFoundation tests, strict lint and dead-code analysis, universal iOS Release builds, and iOS/macOS archives.
This commit is contained in:
@@ -18,7 +18,9 @@ The crate declares `rust-version = "1.90"` and uses `arti-client` / `tor-rtcompa
|
||||
|
||||
- `aarch64-apple-ios`
|
||||
- `aarch64-apple-ios-sim`
|
||||
- `x86_64-apple-ios`
|
||||
- `aarch64-apple-darwin`
|
||||
- `x86_64-apple-darwin`
|
||||
|
||||
It builds release static libraries with size-oriented flags (`opt-level=z`, fat LTO, one codegen unit, `panic=abort`, stripped symbols), normalizes static-archive metadata with `xcrun libtool -static -D`, then packages them with `xcodebuild -create-xcframework`.
|
||||
|
||||
@@ -29,30 +31,34 @@ From the repo root:
|
||||
```sh
|
||||
cd localPackages/Arti
|
||||
rustup toolchain install 1.96.0
|
||||
rustup default 1.96.0
|
||||
rustup target add aarch64-apple-ios aarch64-apple-ios-sim aarch64-apple-darwin
|
||||
cargo install cbindgen
|
||||
rustup target add --toolchain 1.96.0 aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-ios aarch64-apple-darwin x86_64-apple-darwin
|
||||
rustup run 1.96.0 cargo install cbindgen --version 0.29.4 --locked
|
||||
./build-ios.sh
|
||||
```
|
||||
|
||||
`build-ios.sh` defaults to the audited `1.96.0` toolchain and refuses a rustc
|
||||
version other than `1.96.0`; it likewise requires cbindgen `0.29.4`. Set
|
||||
`RUST_TOOLCHAIN`, `RUSTC_VERSION`, or `CBINDGEN_VERSION` explicitly only when
|
||||
intentionally updating the binary provenance and hashes below.
|
||||
|
||||
After rebuilding, verify that:
|
||||
|
||||
- `Cargo.lock` changes are intentional and reviewed.
|
||||
- `Frameworks/include/arti.h` still matches the exported FFI functions used by `TorManager`.
|
||||
- `Frameworks/arti.xcframework` contains iOS device, iOS simulator, and macOS arm64 slices.
|
||||
- `Frameworks/arti.xcframework` contains iOS device arm64, universal iOS simulator arm64+x86_64, and universal macOS arm64+x86_64 slices.
|
||||
- The main app still passes iOS tests and the macOS build.
|
||||
|
||||
## Audited Rebuild
|
||||
|
||||
The June 2026 artifact below was rebuilt from source on this host with:
|
||||
The July 2026 artifact below was rebuilt from source on this host with:
|
||||
|
||||
```text
|
||||
rustc 1.96.0 (ac68faa20 2026-05-25)
|
||||
cargo 1.96.0 (30a34c682 2026-05-25)
|
||||
rustup 1.29.0 (28d1352db 2026-03-05)
|
||||
cbindgen 0.29.3
|
||||
Xcode 26.5
|
||||
Build version 17F42
|
||||
cbindgen 0.29.4
|
||||
Xcode 26.6
|
||||
Build version 17F113
|
||||
```
|
||||
|
||||
Rust 1.86.0 was also checked during the audit and no longer builds this lockfile because `typed-index-collections@3.4.0` requires Rust 1.90.0 or newer.
|
||||
@@ -70,13 +76,13 @@ find localPackages/Arti/Frameworks/arti.xcframework -maxdepth 3 -type f -print0
|
||||
Current hashes:
|
||||
|
||||
```text
|
||||
2083d44eafc765db1ffa2691a5c5fabe60b4edbb82b574169ca0c6b98e245e3a localPackages/Arti/Frameworks/arti.xcframework/Info.plist
|
||||
551655904834748c9dc36034fdbc9465e7533aef1e4a6514b4fcc75875b93058 localPackages/Arti/Frameworks/arti.xcframework/ios-arm64-simulator/Headers/arti.h
|
||||
85febff37b751df667a3cab8222de2e1450cefe44b5b62c419adcbce48b9663f localPackages/Arti/Frameworks/arti.xcframework/ios-arm64-simulator/libarti_bitchat.a
|
||||
cac99db408280bbef15cae8ce64c8ccdbf2e8863c205168d59f83fe8ab680f94 localPackages/Arti/Frameworks/arti.xcframework/Info.plist
|
||||
551655904834748c9dc36034fdbc9465e7533aef1e4a6514b4fcc75875b93058 localPackages/Arti/Frameworks/arti.xcframework/ios-arm64/Headers/arti.h
|
||||
fd25ee379d709a794733fc3c052746d1e6f7b25fec23e5f5234008a3434ce879 localPackages/Arti/Frameworks/arti.xcframework/ios-arm64/libarti_bitchat.a
|
||||
551655904834748c9dc36034fdbc9465e7533aef1e4a6514b4fcc75875b93058 localPackages/Arti/Frameworks/arti.xcframework/macos-arm64/Headers/arti.h
|
||||
8c426a41dc3eb76cc3e3e22e3356b9d11dbebdf0a0f248c5ac892e1839352c75 localPackages/Arti/Frameworks/arti.xcframework/macos-arm64/libarti_bitchat.a
|
||||
5461a231a786812e91e7965290031ea3479fdc5c6459553e46988ecafbbc2a3d localPackages/Arti/Frameworks/arti.xcframework/ios-arm64/libarti_bitchat.a
|
||||
551655904834748c9dc36034fdbc9465e7533aef1e4a6514b4fcc75875b93058 localPackages/Arti/Frameworks/arti.xcframework/ios-arm64_x86_64-simulator/Headers/arti.h
|
||||
af8f5f636eb6affb309b3e44f13e48498eb2540c77af44ddcd7fdf9241b1e317 localPackages/Arti/Frameworks/arti.xcframework/ios-arm64_x86_64-simulator/libarti_bitchat.a
|
||||
551655904834748c9dc36034fdbc9465e7533aef1e4a6514b4fcc75875b93058 localPackages/Arti/Frameworks/arti.xcframework/macos-arm64_x86_64/Headers/arti.h
|
||||
7c9afe98227f1767567ddcd4e35d9dfffe70309c302c4dbc9a6c9d6aeefab007 localPackages/Arti/Frameworks/arti.xcframework/macos-arm64_x86_64/libarti_bitchat.a
|
||||
```
|
||||
|
||||
## Review Checklist
|
||||
|
||||
+88
-68
@@ -1,79 +1,99 @@
|
||||
BitChat Privacy Assessment
|
||||
==========================
|
||||
# bitchat Privacy Assessment
|
||||
|
||||
Scope
|
||||
- Mesh transport (BLE) behavior and metadata minimization
|
||||
- Nostr-based private message fallback (gift-wrapped, end-to-end encrypted)
|
||||
- Nostr-backed public geohash channels, presence heartbeats, and location notes
|
||||
- Optional CoreLocation use for geohash channel discovery
|
||||
- Read receipts and delivery acknowledgments
|
||||
- Logging/telemetry posture and controls
|
||||
Last reviewed: July 2026
|
||||
|
||||
Summary
|
||||
- No accounts and no project-operated servers. Mesh traffic is peer-to-peer; Nostr is used for mutual-favorite private fallback and public geohash features.
|
||||
- BLE announces contain only nickname and Noise pubkey. No device name, no plaintext identity beyond what the user broadcasts.
|
||||
- Discovery and flooding incorporate jitter and TTL caps to reduce linkability and propagation radius of encrypted payloads.
|
||||
- UI and storage remain mostly ephemeral; message content is not persisted to disk by default. Minimal local state (e.g., read-receipt IDs, favorites, selected/bookmarked geohashes) is stored for UX and is bounded or user-wipeable.
|
||||
- Logging defaults to conservative levels; debug verbosity is suppressed for release builds. A single env var can raise/lower threshold when needed.
|
||||
## Scope
|
||||
|
||||
BLE Privacy Considerations
|
||||
- Announce content: Unchanged — nickname + Noise public key only.
|
||||
- Local Name: Not used (explicitly disabled). Avoids leaking device/OS identity.
|
||||
- Address: iOS uses BLE MAC randomization; BitChat does not attempt to set static addresses.
|
||||
- Announce jitter: Each announce is delayed by a small random jitter to avoid synchronization-based correlation.
|
||||
- Scanning: Foreground scanning uses “allow duplicates” briefly to improve discovery latency; background uses standard scanning parameters.
|
||||
- RSSI gating: The acceptance threshold adapts to nearby density (approx. -95 to -80 dBm) to reduce long-distance observations in dense areas and improve connectivity in sparse ones.
|
||||
- Fragmentation: Fragments use write-with-response for reliability (less re-broadcast churn = fewer repeated signals).
|
||||
- GATT permissions: Private characteristic disallows .read; we use notify/write/writeWithoutResponse to avoid exposing plaintext attributes over GATT.
|
||||
- BLE discovery, mesh routing, gossip sync, private delivery, and courier behavior
|
||||
- Nostr private fallback, bridge courier drops, mesh bridging, geohash channels, and notices
|
||||
- CoreLocation and reverse geocoding
|
||||
- Local persistence, panic wipe, logging, and App Store privacy manifests
|
||||
|
||||
Mesh Routing and Multi-hop Limits
|
||||
- Encrypted relays permitted with random per-hop delay (small jitter) to smooth floods.
|
||||
- TTL cap: Encrypted payloads are capped at 2 hops, limiting metadata spread and path reconstruction risk while enabling close-range relays.
|
||||
The user-facing contract is `PRIVACY_POLICY.md`. This document records implementation-level behavior and residual risks that should be re-audited when storage or transport semantics change.
|
||||
|
||||
Nostr Private Messaging Fallback
|
||||
- Usage criteria: Only attempted for mutual favorites or where a Nostr key has been exchanged (stored in favorites).
|
||||
- Payload confidentiality: Messages embed a BitChat Noise-encrypted packet inside a NIP-17 gift wrap; relays see only random-looking ciphertext.
|
||||
- Timestamp handling: Gift wraps add small randomized offsets to reduce exact timing correlation.
|
||||
- Read/delivery acks: Also encapsulated in gift wraps, preserving content secrecy and minimizing metadata.
|
||||
- Relay policy variance: Some relays apply “web-of-trust” policies and may reject events; BitChat tolerates partial delivery and still prefers mesh when available.
|
||||
## Current Posture
|
||||
|
||||
Location Channels and Geohash Public Chats
|
||||
- Location permission: Optional when-in-use CoreLocation access computes local geohash channel options. Exact coordinates are held in memory only and are not included in BitChat or Nostr payloads.
|
||||
- Local state: Selected channel, teleported geohashes, bookmarks, and bookmark display names are stored in `UserDefaults`; the panic action clears location presence state along with identity/session state.
|
||||
- Geohash precision: User-selected channels can range from region-level to building-level. Public geohash messages and location notes expose the selected geohash tag to relays and participants.
|
||||
- Presence minimization: Automatic presence heartbeats are restricted to low-precision region/province/city geohashes and use randomized timing.
|
||||
- Per-geohash identities: Public geohash Nostr identities are derived from a device seed stored in the keychain, reducing cross-channel linkability compared with a single stable public key.
|
||||
- Relay metadata: Relays can observe event kind, geohash tag, public key, timestamp, and network metadata. Content in public geohash channels is intentionally public to that channel.
|
||||
- The project operates no account system, analytics pipeline, advertising SDK, or project-owned messaging backend.
|
||||
- Mesh transport is peer-to-peer. Optional internet features use third-party Nostr relays and can expose public content, coarse geohashes, timing, relay, and network metadata.
|
||||
- Private payloads are end-to-end encrypted, but public mesh, board, bridge, and geohash content is intentionally visible to its participants.
|
||||
- Local storage is bounded where practical and included in panic wipe, but it is not wholly ephemeral. The app persists the stores listed below.
|
||||
- The app and share extension each bundle a privacy manifest declaring their actual required-reason API use.
|
||||
|
||||
Read Receipts and Delivery Acks
|
||||
- Routing policy: Prefer mesh if Noise session established; otherwise use Nostr when mapping exists.
|
||||
- Throttling: Nostr READ acks are queued and rate-limited (~3/s) to prevent relay rate limits during backlogs.
|
||||
- Coalescing (optional future): When entering a chat with many unread, only send READ for the latest message, marking older as read locally to reduce metadata.
|
||||
## BLE Discovery and Metadata
|
||||
|
||||
Data Retention and State
|
||||
- Messages: Ephemeral in-memory only; history is bounded per chat and trimmed.
|
||||
- Read-receipt IDs: Stored in `UserDefaults` for UX continuity; periodically pruned to IDs present in memory.
|
||||
- Favorites: Noise and optional Nostr keys with petnames; can be wiped via panic action.
|
||||
- Location channels: Exact coordinates are not persisted by BitChat. Selected/bookmarked geohashes, teleport flags, and bookmark display names persist locally until removed, panic-wiped, or the app is deleted.
|
||||
- Geohash identities: Device seed is stored in the keychain and used to derive per-geohash Nostr identities deterministically.
|
||||
- Relay persistence: Public geohash events, location notes, and encrypted gift wraps may be retained by relays according to each relay's policy.
|
||||
- Panic: Triple-tap clears keys, sessions, cached state, and disconnects transports.
|
||||
Signed announces can expose:
|
||||
|
||||
Logging and Telemetry
|
||||
- Centralized `SecureLogger` filters potential secrets and uses OSLog privacy markers.
|
||||
- Default level: `info`; release builds suppress debug. Developers can set `BITCHAT_LOG_LEVEL=debug|info|warning|error|fault`.
|
||||
- Transport routing, ACK sends, subscribe/connect noise were downgraded from info→debug.
|
||||
- OS/system errors (e.g., transient WebSocket disconnects) may still appear in system logs; BitChat avoids re-logging those unless actionable.
|
||||
- Nickname, persistent Noise public key, and Ed25519 signing public key
|
||||
- Capability flags
|
||||
- A bounded set of short direct-neighbor identifiers
|
||||
- A coarse rendezvous geohash when the bridge capability is enabled
|
||||
|
||||
Residual Risks and Mitigations
|
||||
- RF fingerprinting: BLE presence is observable at the RF layer; mitigated by minimal announce content and platform MAC randomization.
|
||||
- Timing correlation: Announce/relay jitter reduces but does not eliminate timing analysis. Avoids synchronized bursts.
|
||||
- Relay metadata: Nostr relays can see that an account posts gift wraps; content remains end-to-end encrypted. Favor mesh path when in range.
|
||||
- Geohash inference: Public location-channel tags reveal approximate area. Mitigated by explicit channel selection, low-precision automatic presence, and per-geohash identities.
|
||||
- Bookmark persistence: Locally stored geohash bookmarks may reveal places of interest on a seized/unlocked device. Mitigated by panic wipe and local-only storage.
|
||||
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.
|
||||
|
||||
Recommendations (Next)
|
||||
- Add optional coalesced READ behavior for large backlogs.
|
||||
- Expose a “low-visibility mode” to reduce scanning aggressiveness in sensitive contexts.
|
||||
- Allow user-configurable Nostr relay set with a “private relays only” toggle.
|
||||
- Add a user-facing precision warning before posting in block/building-level geohash channels.
|
||||
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.
|
||||
|
||||
## Private Messaging and Courier Delivery
|
||||
|
||||
- Direct mesh sessions use Noise XX with X25519, ChaCha20-Poly1305, and SHA-256.
|
||||
- Undelivered outgoing private messages remain in a bounded, ChaChaPoly-sealed outbox for at most 24 hours. Its key is stored in the keychain.
|
||||
- Physical couriers store opaque Noise-sealed envelopes, not plaintext. Deposits have trust-tier quotas, per-depositor caps, a global cap, and at most a 24-hour lifetime.
|
||||
- Spray-and-wait copies are bounded and progress cannot be replenished by replaying a deposit.
|
||||
- Delivery status advances only after real transport admission or explicit relay acceptance; late failure cannot downgrade a delivered/read message.
|
||||
- Panic wipe deletes the outbox, courier mail, keys, dedup state, and active transport state.
|
||||
|
||||
Residual risk: private-message metadata such as timing, radio adjacency, ciphertext size, rotating recipient tags, and relay connections remains observable. A compromised recipient device can disclose plaintext.
|
||||
|
||||
## 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.
|
||||
- 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.
|
||||
|
||||
Public archives contain content already intended for public mesh/board distribution, but a seized unlocked device can reveal it. Group metadata and media can reveal relationships or content even when the in-memory chat timeline has gone away.
|
||||
|
||||
## Nostr and Mesh Bridge
|
||||
|
||||
- NIP-17/NIP-44 v2 private fallback protects plaintext with secp256k1 key agreement, HKDF-SHA256, and XChaCha20-Poly1305. Relays still see event and network metadata.
|
||||
- Bridge courier drops use a throwaway publisher key, an opaque Noise-sealed envelope, and a day-rotating recipient tag. Only a party already holding the recipient's Noise static key can compute candidate tags.
|
||||
- Relay publication is considered successful only after an explicit NIP-20 `OK true` from at least one target relay. Rejected, disconnected, timed-out, or merely socket-written events stay retryable.
|
||||
- When mesh bridge is enabled, public mesh messages not marked “nearby only” are signed under a per-cell Nostr identity and published to a neighborhood rendezvous geohash. Presence and public bridge traffic therefore expose a coarse area to relays and participants.
|
||||
- A bridge gateway can carry signed bridge/location events and opaque courier drops for nearby mesh-only peers. It cannot validly publish a neighbor's radio-only message because the author must first sign the bridge event.
|
||||
|
||||
Residual risk: Nostr relay retention and logging are outside project control. Public events may be copied indefinitely. Timing, coarse location, and participation can be correlated even when content is encrypted or per-cell identities are used.
|
||||
|
||||
## Location
|
||||
|
||||
- When-in-use CoreLocation access computes geohash choices and bridge cells. Permission revocation stops live sampling and releases subscriptions.
|
||||
- Exact coordinates are not persisted by bitchat or placed into mesh/Nostr payloads.
|
||||
- Selected/bookmarked geohashes, teleport flags, and display names persist in local preferences; a fine geohash can identify a small area.
|
||||
- Friendly place names use `CLGeocoder.reverseGeocodeLocation`. Apple may process the supplied location under its own privacy terms, so this operation is not accurately described as wholly on-device.
|
||||
- Automatic presence is limited to lower-precision geohashes; precise posts occur through user-selected channels, notes, notices, or the bridge behavior presented in the UI.
|
||||
|
||||
## Logging and Telemetry
|
||||
|
||||
- `SecureLogger` uses OSLog privacy markers and filters likely secrets. Release builds suppress debug verbosity.
|
||||
- No project analytics or telemetry endpoint exists.
|
||||
- Apple system logs, Nostr relays, network providers, and nearby radios can still observe operational metadata outside the project's logging layer.
|
||||
|
||||
## Privacy Manifests
|
||||
|
||||
`bitchat/PrivacyInfo.xcprivacy` declares:
|
||||
|
||||
- UserDefaults: `CA92.1` for app-only preferences and `1C8F.1` for the shared app group
|
||||
- File timestamps/metadata: `C617.1` for app-container files and `3B52.1` for user-granted files
|
||||
- System boot time: `35F9.1` for elapsed-time deadlines and timers
|
||||
|
||||
`bitchatShareExtension/PrivacyInfo.xcprivacy` declares app-group UserDefaults reason `1C8F.1`. Both manifests declare no tracking domains and no data collection by the app developer. They must remain bundled in their respective executable bundles.
|
||||
|
||||
## Panic Wipe Coverage
|
||||
|
||||
The panic action clears identity/session state, preferences, location state, groups, prekeys, outbox mail, courier mail, bridge dedup state, gossip archive, board data, managed media, and active subscriptions/transports. New persistent stores must add an explicit wipe hook and a regression test.
|
||||
|
||||
## Release Review Checklist
|
||||
|
||||
- Reconcile every new Application Support, UserDefaults, keychain, cache, or relay write with this assessment and `PRIVACY_POLICY.md`.
|
||||
- Re-scan required-reason APIs and validate both bundled `PrivacyInfo.xcprivacy` files before archive submission.
|
||||
- Verify panic wipe reaches any newly added persistent store.
|
||||
- Treat geohash precision, bridge-cell changes, new relay tags, and announce fields as privacy-surface changes.
|
||||
- Re-run real-device Bluetooth, background/locked-device recovery, location revocation, and audio-route checks; simulators cannot validate the physical side of those behaviors.
|
||||
|
||||
Reference in New Issue
Block a user