mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-26 23:45:19 +00:00
399cdf95ab078580ad38a01654da8cc9ea329884
9
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
399cdf95ab |
Stop broadcasting the adjacency graph and the authorship marker
Two radio-layer metadata leaks that need no cross-platform agreement, because both only change what this device chooses to emit. **Announces no longer carry the neighbour list.** The TLV held up to ten 8-byte peer IDs, so a *single* passive receiver could reconstruct the local adjacency graph — who is standing next to whom — with no need for several receivers or RSSI trilateration. In a crowd that is the most sensitive thing the radio layer gives away, and unlike the identity keys it is not required for the protocol to work. Backward compatible in both directions: an empty list omits the TLV entirely rather than emitting a zero-length one, the decoder already treats its absence as "no topology offered", and lists from other peers are still parsed so a mixed network behaves sensibly. The cost is source routing. MeshTopologyTracker builds its adjacency map from these lists, so with everyone silent there are no routes to compute and directed traffic floods instead — which is already the documented fallback whenever a route fails. More airtime for directed sends in dense meshes; no correctness change. Left as a TransportConfig constant rather than a user setting because it is a protocol trade-off, not a preference, and flipping it back is one line. **Public broadcasts no longer always originate at the maximum TTL.** `ttl == messageTTLDefault` was a reliable "this device wrote it" marker to any direct listener, which discloses authorship rather than mere presence. Origin TTL is now drawn from 5...7: in a dense graph relays already clamp broadcasts to 5, so an origin emitting 5 is indistinguishable from relayed traffic, and in a sparse chain a 6 could be an origin or one hop from a 7. Signature-safe and needs no agreement: TTL is excluded from the signed bytes (toBinaryDataForSigning zeroes it so relays can decrement), so a peer on any version just sees a smaller starting TTL and relays it normally. The floor is not below the dense-graph clamp, since lower would cost reach without buying ambiguity that clamp does not already provide. Announces deliberately keep the fixed TTL: three link-binding paths read a maximum-TTL announce as "direct link", and an announce's sender ID already identifies the device, so there is nothing to hide and something to break. **Not done here: padding.** Extending padding beyond Noise frames, and fixing the gap where a frame needing over 255 bytes of padding is emitted unpadded, both looked unilateral but are not. `toBinaryDataForSigning` encodes with padding enabled, so the padding bytes are inside the signed material for every signed packet — changing the algorithm changes the signed byte stream and breaks signature verification against any peer that has not changed it identically. That makes it a coordinated wire change; recorded in the privacy assessment and in #1487's open questions rather than attempted. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
c1ce9029d8 |
Keep working when the network is hostile, and make the app verifiable (#1486)
Tier 3 of a protest-hardening review. The BLE mesh is already properly fenced off from network reachability and needs nothing here; every gap is on the internet side, or in how someone gets a build they can trust. Say when Tor is blocked. The bootstrap poll loop simply ended at its 75-second deadline, leaving isStarting true with no further state, so a network that blocks Tor was indistinguishable from a slow one and the UI said "starting tor…" indefinitely. TorManager now exposes bootstrapDidStall and posts .TorBootstrapDidStall, and the app reports that mesh messaging still works while internet delivery is paused. It is cleared on each new start or restart, so a later attempt can report again. Let relays be added by hand. The four built-in relays are well-known clearnet hostnames, which is four names for a censor to block and no recourse short of a new build. NostrRelaySettings persists up to eight additional relays, normalized, .onion accepted, with a settings editor. They join the same target set as the built-ins and are subject to the same activation policy. Removal reconciles against the previous set: the teardown path iterates the current targets, so without that a removed relay's socket and queued sends would linger — covered by a test that fails without it. The merged list is cached rather than recomputed, because allowedRelayList consults it once per candidate URL and would otherwise read UserDefaults inside that loop. Stop stranding people who denied location. The activation gate required location permission or a mutual favorite, but teleporting into a geohash requires neither, so someone with no permission and no favorites could sit in a channel that never connected while Tor and the relays stayed suppressed and nothing said why. Being in a location channel is now a third arm of the gate, in both the activation service and the relay manager's copy of the policy, and leaving the channel closes it again. Stop burning the Tor timeout when Tor is off. GeoRelayDirectory awaited Tor readiness unconditionally, but with the preference off TorManager has been shut down, so every refresh spent the full bootstrap deadline and the directory froze on its cached copy. It now keys on the preference, not on live readiness: Tor wanted but unavailable must still skip the fetch rather than fall back to clearnet. Say what turning Tor off costs. The toggle's copy described it as hiding your IP "for location channels", understating both scope and consequence. It now names private messages too, and while the toggle is off the settings screen states that every relay can see the device IP. Make builds verifiable. There was no release verification of any kind: no signatures, no checksums, no documented procedure. Post-takedown that is the acute gap, because mirrors appear and people install whatever they can find during a shutdown. source-manifest.yml publishes a per-tag SHA-256 manifest with a provenance attestation, self-checking before it publishes, and docs/VERIFYING-A-BUILD.md explains how to verify source and states plainly that compiled builds from anywhere but the App Store cannot be verified. It also records the gaps honestly: no published signing key, no reproducible build, no non-GitHub mirror. docs/TOR-INTEGRATION.md was substantially stale — it documented a torrc, SOCKSPort and ControlPort that the in-process Arti client does not use, and claimed there are no user-visible settings — so it is rewritten, including the deferred gap below. Deferred: no Tor bridges or pluggable transports. arti-client is built without pt-client or bridge-client and bootstraps from stock config, so in a country that blocks Tor outright there is still no circumvention path — only a clear report that there isn't. Closing it needs the Rust features, bridge config through the FFI, and an xcframework rebuild under the pinned toolchain with a provenance update, which is its own change. Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
c079d2ab5d |
Harden what a locked or seized device gives away (#1484)
* Harden what a locked or seized device gives away The realistic compromise for many of the people this app is built for is not interception but a phone taken, and often unlocked under coercion. Content encryption is in good shape; these are the gaps around it. Hide notification previews, by default. Notification content is rendered by the system on the lock screen, so it was readable without unlocking: DM alerts carried the sender's nickname and the full message body, and geohash alerts put the geohash in the title. Alerts now state that a DM, mention, or location-channel activity arrived and withhold the rest until the app is opened. userInfo still carries the routing peer ID and deep link, neither of which the system displays, so taps land where they did. A settings toggle restores full previews for anyone who wants them. Default-on is the deliberate part: a phone face-up on a table should not narrate conversations, and someone who wants previews can say so. Cover the window on willResignActive, so the snapshot iOS stores for the app switcher shows a placeholder rather than an open conversation. Opaque rather than blurred, because blurred large text stays partly legible and the snapshot goes to disk. Added synchronously from a UIKit notification with queue: nil, since the capture follows shortly after and an OperationQueue hop or a SwiftUI state change can lose that race. Panic wipe already deleted snapshots already on disk; this stops new ones from being worth deleting. Bound media by age as well as size. The 100 MB quota only ever considered incoming files, so outgoing media had no lifetime at all and a received photo could outlive its conversation indefinitely. A launch-time sweep now deletes managed media older than seven days, incoming and outgoing, with the same exemptions quota eviction honors: in-flight live captures and files reserved by a delivery or deletion in progress. Make /clear tell the truth on the mesh timeline. It recorded an echo watermark and left the gossip archive on disk for up to 6 hours, so someone who cleared before a police stop had deleted nothing. Clearing now erases the archive too. The watermark still matters: it suppresses pre-clear messages this device hears again from peers. The cost is that the device stops serving recent public backlog until it hears fresh traffic, which is a fair reading of what clearing a timeline means. Documented in PRIVACY_POLICY.md and the privacy assessment, including a new section on what is deliberately NOT addressed: there is still no duress mechanism of any kind (no decoy passphrase, no wipe-on-failed-auth, no app lock), macOS gets no file-protection classes, and media is not sealed at the app layer. The duress question is a product decision as much as an engineering one, since in some jurisdictions destroying data on demand is itself an offence and hiding may protect someone better than destroying, so it is called out rather than guessed at. Three findings from the audit that prompted this work turned out to be already fixed on main and are not included: keychain accessibility is AfterFirstUnlockThisDeviceOnly with a retrying migration, the panic media wipe uses a two-location durable marker transaction, and panic already discards staged share-extension content. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Inject the previews preference instead of reading shared defaults CI caught a real problem this introduced. `NotificationServiceTests` already asserted the full-preview title and body, and both it and the new redaction tests read `NotificationPrivacySettings` from `UserDefaults.standard` in the same process. Whichever ran second depended on the other's cleanup, so it passed locally and failed in CI: XCTAssertEqual failed: ("🔒 new dm") is not equal to ("🔒 DM from Alice") Fixed at the source rather than by ordering or serialization. `NotificationService` now takes a `hidePreviewsProvider`, defaulting to the real preference, so each test states which behavior it asserts. The pre-existing test asks for previews shown and gains a redacted counterpart; the redaction tests no longer touch the shared store. `NotificationPrivacySettings` also gained store-injecting accessors so the default-value and round-trip assertions can use an isolated suite rather than mutating preferences other tests read. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
229a41557e |
docs: correct inaccurate privacy and metadata claims (#1485)
* docs: correct inaccurate privacy and metadata claims Several documented guarantees did not match the implementation. These matter more than ordinary doc drift: someone deciding whether to carry this phone to a protest reads these sentences as the threat model. - Peer IDs were described as "short ephemeral IDs derived per session" that "rotate periodically" and "prevent tracking". They are the first 8 bytes of the Noise static key fingerprint, stable across sessions and reboots, and replaced only by a panic wipe. Corrected in the whitepaper (§3, §8), IdentityModels, and BitchatProtocol, whose header notes claimed "no persistent identifiers in protocol headers" while every header carries exactly one. - "No plaintext message content is ever written to disk" was false for accepted media, which is stored unsealed under the platform's data-protection class. Narrowed to what actually holds. - Padding was described as applying to all packets but fragments. Only noiseEncrypted and noiseHandshake frames are padded; the pad bytes equal the pad length rather than being random; and because that length must fit one byte, a frame needing over 255 bytes of padding is emitted unpadded. Documented in the whitepaper (§4.1) and MessagePadding. - The gossip archive window is 6 hours in production, not the 15 minutes claimed in PRIVACY_POLICY.md and the privacy assessment. The 15-minute figure is the struct default that BLEService overrides. - The privacy assessment credited iOS BLE address randomization without noting that stable app-layer identifiers defeat it. The whitepaper's future-work list now names the changes these corrections imply: rotating on-air identity, padding for non-Noise types, and making the announce neighbor list optional. No behavior change; comments and documentation only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs: correct the same claims in the README The README repeats two of the claims corrected elsewhere in this PR, and it is the document people actually read before deciding to trust the app. - "no persistent identifiers" is the inverse of what the mesh does; it now points at the whitepaper's identity and metadata sections. - "end-to-end encryption with forward secrecy" holds for live Noise sessions but not for sealed store-and-forward mail, which the whitepaper already flags as its main cryptographic trade-off. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
e9275cb3d8 |
Relabel private Nostr envelopes honestly in docs; harden legacy envelope validation (#1480)
Split-out safe half of #1437 (the kind-1402 wire migration stays held for Android coordination). Docs (README/WHITEPAPER/PRIVACY_POLICY/privacy-assessment) now describe the actual proprietary DM construction — kind 1059 gift wrap carrying XChaCha20-Poly1305 with a 24-byte nonce, base64url v2: framing, and an HKDF that borrows the nip44-v2 info label but is not the NIP-44 key schedule — instead of claiming NIP-17/44/59. Hardens the existing legacy inbound path: 64 KiB ciphertext cap before decode (~46x the largest producible legacy envelope), outer kind/recipient-tag/signature binding, tagless kind-13 seal binding, unsigned kind-14 inner binding, inner tags restricted to the two shapes deployed clients emit (verified against every historical iOS release and a fixture frozen from Android production), SecRandomCopyBytes failure now throws, non-UTF-8 plaintext now throws instead of returning empty. Adds frozen cross-platform fixtures with hash-pinned generators; interop-reviewed with no rejection surface for deployed clients. |
||
|
|
cd727c6867 |
Make panic wipe deterministic and device-bound (#1431)
* Make panic wipe deterministic and device-bound * Scope install markers to iOS * Harden panic recovery and service shutdown * Invalidate queued BLE ingress during panic * Harden panic keychain and media cleanup --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: jack <jack@deck.local> |
||
|
|
820c933958 |
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. |
||
|
|
4093ee6733 |
Rebuild Arti from audited source with enforced provenance
Vendored arti.xcframework rebuilt from source (Rust 1.96.0, normalized archive metadata for reproducible hashes). New ARTI-BINARY-PROVENANCE.md records toolchain, rebuild steps, and a SHA256 manifest for every file in the xcframework. A new CI workflow turns that policy into a gate: PRs must keep the binary matching the manifest, and binary changes must ship with source/lockfile/build-script evidence. Also raises TorManager.awaitReady's default timeout from 25s to 75s to match the bootstrap monitor deadline - a shorter wait reported "not ready" while Arti was still legitimately bootstrapping, silently stranding queued relay work. Privacy policy, Tor integration doc, and privacy assessment updated to match the current implementation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
4f1ac30f12 |
Feat/mesh robustness efficiency (#451)
* chat: de-dup private chats across ephemeral/stable IDs; prefer most advanced delivery status\n\n- Fixes LazyVStack duplicate ID warnings and blank row in PM\n- Merges messages by id from ephemeral and Noise-key stores\n- Chooses read > delivered > partiallyDelivered > sent > sending > failed (newer wins on tie)\n- Ensures status icon updates immediately without waiting for another send\n- Adds exhaustive handling for DeliveryStatus in ranking * logging: reduce noisy info logs to debug; keep errors/warnings\n\n- Downgrade routing/ACK/subscription/connect logs to debug\n- Retain security/fingerprint/keychain info logs\n- Keep errors and warnings intact\n\ndocs: add docs/privacy-assessment.md covering BLE privacy, routing TTL/jitter, Nostr E2E gift wraps, ACK throttling, and logging posture --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com> |