mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 03:05:19 +00:00
d499c3e4158a296a07032c78e6a9d6fb8d80fa71
31
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d499c3e415 |
Isolate tests from the developer's real login keychain (#1413)
* Isolate tests from the developer's real login keychain Every test run prompted for the login keychain password (repeatedly, since the xctest runner's code signature changes each build, so "Always Allow" can never stick) and silently deleted the developer's real Nostr identity via panic-mode tests. Two holes let tests reach the real keychain: - NostrIdentityBridge() defaulted to the real KeychainManager. Tests inject mocks, but app-side constructions with no injection point (LocationNotesManager's static bridge, GeohashPresenceService, BoardManager, AppRuntime) read the real chat.bitchat.nostr item when exercised under test. - clearAllAssociations() used raw SecItem* calls that bypassed the injected keychain entirely, so panicClearAllData tests wiped the real Nostr identity items on every run. Fix: centralize FavoritesPersistenceService's test-guarded in-memory default as KeychainManager.makeDefault() and use it for all default keychain parameters, and add deleteAll(service:) to KeychainManagerProtocol so clearAllAssociations() goes through the injected keychain like every other operation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Share one in-memory test keychain per process (Codex P2) A fresh store per makeDefault() call diverges from production, where separate default-constructed bridges share chat.bitchat.nostr — BoardManager's publish and NIP-09 delete paths would derive different geohash identities under test. PreviewKeychainManager gains a lock since the shared instance is reached from arbitrary threads. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
ef848857b7 |
Remove dead code found by full Periphery audit; add scan config + advisory CI (#1410)
Periphery 3.7.4 audit of both schemes (macOS + iOS, intersected so platform-specific code is never touched), with test targets indexed and the share extension built. 277 dead declarations removed or demoted: dead forwarding wrappers (ChatViewModel+Nostr/+PrivateChat), removed- feature remnants (autocomplete command suggestions, back-swipe tuning, MediaSendError, GeohashParticipantTracker), unused Tor dormancy bindings, assign-only properties, unused parameters (renamed to _), and redundant public accessibility. 13 orphaned localization keys deleted across all 29 locales (old pre-#1392 location-notes UI, app_info warnings). Two real tests were flagged as unused because they never ran: Swift Testing methods missing @Test (NostrProtocolTests. testAckRoundTripNIP44V2_Delivered, NotificationStreamAssemblerTests. testAssemblesCompressedLargeFrame). Re-armed both; they pass. Deliberately kept, now recorded in .periphery.baseline.json: iOS-only code invisible to the CI macOS scan, C FFI signatures, keep-alive NWPathMonitor reference, InboundEventKey.eventID (dedup semantics), wifiBulk capability bit (reserved for Wi-Fi bulk work, used by BitFoundation package tests), and the String secureClear cluster (exercised by package tests). New: .periphery.yml config and an advisory Dead Code CI job (mirrors the SwiftLint precedent from #1361) that fails on findings not in the committed baseline. Verified: full macOS app suite, BitFoundation (119) and BitLogger (13) package tests green; periphery scan --strict exits clean. Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
78a291ab77 |
Public-mesh push-to-talk: signed live voice bursts in the mesh channel (#1406)
* Live push-to-talk voice for DMs: stream while you talk, voice note as fallback Holding the mic in a DM now streams AAC frames live over the Noise session (walkie-talkie style, ~0.5s mouth-to-ear at one hop) while recording the same audio as a normal voice note. On release the note ships through the existing fileTransfer pipeline; receivers that heard the live stream absorb it silently into the same bubble (matched by the burst ID embedded in the file name), so reliability comes for free and nobody sees duplicates. Protocol: - NoisePayloadType.voiceFrame = 0x08 carrying VoiceBurstPacket (burstID + seq + START/data/END/CANCELED, length-prefixed AAC frames) - 210-byte burst-content budget keeps each Noise packet inside the 256-byte padding bucket: one BLE frame, never the fragment scheduler - fire-and-forget: frames are dropped (never queued) without an established session; live is only offered when the peer is mesh-reachable Receive: - ChatLiveVoiceCoordinator assembles bursts (jitter-ordered, 0.5s gap skip, 3s idle end, flood/size caps), persists progressively as ADTS .aac so even a partial burst is a replayable bubble - live autoplay only when the conversation is on screen, app active, and the new app-info "live voice messages" toggle is on (also gates live sending) - one-playback-at-a-time via a shared ExclusivePlayback slot Capture: - PTTCaptureEngine taps AVAudioEngine, dual-encodes: live AAC frames + the finalized .m4a (same 16kHz/mono/16kbps settings as VoiceRecorder) - VoiceRecordingViewModel now drives a pluggable VoiceCaptureSession; the composer HUD shows a pulsing LIVE treatment when streaming Includes the push-to-talk design doc, 6 new localization keys across all 29 locales, and unit tests for framing, packetizer budget, ADTS output, codec round-trip, and the assembly/absorb lifecycle. Public-mesh PTT (MessageType 0x29) lands separately on top of this. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Public-mesh push-to-talk: signed live voice bursts in the mesh channel Extends live PTT from DMs to the public mesh timeline. Holding the mic in the mesh channel now broadcasts the burst live as signed voiceFrame packets (MessageType 0x29) while the finalized voice note still ships on release — new clients hear you as you speak and absorb the note silently into the live bubble; old clients (and late joiners) keep receiving the note exactly as before, so mixed-version meshes lose nothing. Wire/relay: - MessageType.voiceFrame = 0x29: ephemeral signed broadcast, never gossip-synced (SyncTypeFlags maps it to no bit), never padded (padding to the 512 block would push every ~490-byte signed packet into fragmentation) - RelayController treats voiceFrame like media fragments: dense-graph TTL clamp contains the sustained ~15 pkt/s per-talker stream, tight 8-25 ms jitter keeps multi-hop latency inside the receiver's 350 ms jitter buffer - inbound gate mirrors public messages: broadcast-only, 30 s freshness cap, packet signature verified against the claimed sender's announce before any audio reaches the UI App: - ChatLiveVoiceCoordinator gains burst scopes: public bubbles land in the mesh timeline, autoplay only while that timeline is on screen, and the finalized-note absorb is scope-bound (a public note can't replace a DM burst or vice versa) - floor courtesy: while someone talks live in the public channel the composer mic tints red and pulses, with an accessibility value naming the talker ("%@ is speaking", localized in all 29 locales); holding still works — a decentralized mesh has no floor arbiter, the tint just discourages talk-over Tests: relay policy (sparse cap + dense clamp), public bubble + talker indicator lifecycle, note absorption into the mesh store, and scope-binding rejection; full suite green (1382 tests). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * PTT follow-ups from review + field test: peer-ID normalization, toggle gates inbound, drop-path diagnostics Codex review fixes (#1403): - makeVoiceCaptureSession normalizes the selected peer with toShort() before the reachability/session checks and binds the send target to that same routing ID — a conversation selected under the stable 64-hex Noise key no longer silently falls back to a classic note while the short-ID session is established - the live-voice toggle now gates inbound bursts too: off means classic-notes-only in both directions (no live bubble, partial file, or early notification; the finalized note still arrives), with a test Field-test diagnostics (first device run: DM frames decrypted but no bubble appeared, with no log evidence of which guard dropped them): - coordinator logs undecodable frames (size + hex prefix) and blocked drops - makeAssembly logs directory/file-handle failures instead of returning nil silently - PTTLiveVoiceSession logs capture start and finish (packet/frame/duration counts); PTTCaptureEngine logs engine start success/failure with the input format; BLEService.sendVoiceFrame logs no-session drops Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Fix iPhone live-capture failure: dead input unit (AURemoteIO -10851, 0 Hz) Field testing showed the phone's live capture failing at mic enable with AURemoteIO -10851 and an input format of 0 Hz / 2 ch — an input unit bound to an earlier (playback-only or settling) audio session. The Mac, which has no session lifecycle, captured fine, which is why public bursts from the Mac worked while phone-side sends degraded from working (first hold) to sporadic to dead across holds. Three layers of defense: - PTTCaptureEngine recreates its AVAudioEngine on every start(), after the session is configured, so the input unit binds to the session that is active now; a dead input (0 Hz or 0 channels) is now a distinct, logged error instead of a silent setup failure - PTTLiveVoiceSession retries the capture start once after a 150 ms route-settle pause - VoiceRecordingViewModel falls back to the classic VoiceRecorder within the same hold if the live engine still cannot start — a route glitch now costs the live stream, never the voice note Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Blue mic when the hold will stream live The mic button now shows readiness at a glance — and doubles as a build marker for device testing: - blue: holding will stream live (DM peer reachable with an established Noise session, or the public mesh channel) - accent (orange in DMs): holding records a classic voice note (no session yet, peer unreachable, or live voice toggled off) - red states unchanged (recording, floor busy) Refactors capture-backend selection into a single liveVoiceTarget() so the indicator and makeVoiceCaptureSession can never disagree. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Revert the blue live-ready mic to the normal accent color The build-verification marker did its job; idle mic color goes back to the accent. The LIVE recording HUD remains the signal for whether a hold is streaming. Keeps the liveVoiceTarget() refactor so backend selection stays in one place. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Leave a trace on every mic press and every inbound-frame drop Field testing read "tap does nothing" as breakage: the mic start is async (permission check + engine spin-up), so releasing before recording begins has always been a silent cancel — for classic voice notes too. Every press now logs which backend it chose and, for quick presses, that it released before recording started. Also logs the two remaining silent drops: inbound voice frames rejected by the live-voice toggle (the one unlogged guard left in the receive path) and the classic-note fallback now includes the toggle state. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Fix mic hold dying instantly in DMs: sheet swipe gesture starved the composer Field logs showed every DM mic hold ending 3-10 ms after it began, on both platforms, while public-channel holds worked — the private sheet wraps its entire content (composer included) in a high-priority swipe-right-to-leave DragGesture, and a high-priority ancestor drag cancels the mic button's press-and-hold within milliseconds. Same starvation mechanism as the DM image-reveal bug (#1402), hitting a drag instead of a tap. The swipe-to-leave gesture now lives on the message list only, so the composer's gestures (mic hold, text field, buttons) are out of its reach and the swipe still works where users actually swipe. Also stops touching the capture engine when a hold cancels before the engine ever started: probing inputNode on a never-started engine instantiates its input unit against whatever session is active and spams benign-but-alarming AURemoteIO -10851 errors into field logs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Reorder app info sheet: usage first, then settings, then reference New section order: HOW TO USE, then the adjustable bits (appearance, voice, network), then the reference material (features, privacy, symbols legend). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * App info: flow HOW TO USE into one paragraph; "list" and "person" wording The six how-to-use bullets now read as a single comma-separated paragraph (same instruction strings, legacy bullet prefix stripped at render). Two wording updates across all 29 locales: the people icon opens the "list" (not "sidebar"), and you tap a "person's" name (not a "peer's") to start a DM. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
bbe5e1ef4e |
Fix reachability-gate races flagged by Codex on #1389 (#1394)
P1: TorManager.shutdownCompletely() resets didStart asynchronously (after Arti has actually stopped, up to ~5s later). A brief offline->online flap could call startIfNeeded() inside that window; the guard on didStart dropped it, and nothing reevaluated afterwards, so Tor stayed down while activationAllowed was true. Track shutdowns in flight and record a deferred start, honored when the last shutdown finishes (still gated on allowAutoStart/foreground at that point). P2: NWPathReachabilityMonitor.ingest() cancelled and rescheduled the flush a full debounce interval from "now" on every observation, even duplicates (e.g. interface detail changes while still unsatisfied). ReachabilityDebounce already preserves the original pending.since, so schedule the flush for the remaining time to the true deadline instead of restarting the window. Tests: debounce deadline preservation (pure) + a monitor-level timing test that a mid-window duplicate does not postpone the offline commit. Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
81a10f73f0 |
Private groups: creator-managed encrypted group chat over the mesh (#1383)
* Add capability bits to announce TLV Announces now carry an optional capabilities TLV (0x05): a little-endian bitfield with named bits for upcoming features (prekeys, wifiBulk, gateway, groups, board, vouch, meshDiagnostics). Old clients skip the unknown TLV; peers without it decode as nil so features can distinguish "legacy peer" from "advertises nothing". PeerCapabilities lives in BitFoundation with a minimal-length encoding that preserves unknown bits for forward compatibility. Peer capabilities are stored in the BLE peer registry on verified announce and exposed via BLEService.peerCapabilities(_:). The local advertisement set is empty until each feature ships its bit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Private groups: creator-managed encrypted group chat over the mesh Small encrypted crews (hard cap 16) between public broadcast and 1:1 DMs: Protocol - MessageType.groupMessage = 0x25: broadcast packets with a cleartext 16-byte group ID + epoch, ChaCha20-Poly1305 ciphertext (epoch bound as AEAD AAD), inner Ed25519 sender signature over "bitchat-group-msg-v1"|groupID|messageID|timestamp|content - NoisePayloadType.groupInvite = 0x06 / .groupKeyUpdate = 0x07: creator-signed group state (key, epoch, roster) 1:1 over Noise; signature over "bitchat-group-v1"|groupID|epoch|key-hash|roster-hash and the Noise session peer must BE the creator - SyncTypeFlags bit 10 (groupMessage): variable-length LE bitfield widens 1 -> 2 bytes inside the length-prefixed REQUEST_SYNC TLV; old clients ignore unknown bits and answer with types they know - PeerCapabilities.localSupported now advertises .groups Storage - GroupStore: symmetric keys in the keychain, roster/name/epoch as protected JSON in Application Support; wiped in panicClearAllData() Behavior - Non-members relay 0x25 like any broadcast but cannot read it; group messages join gossip-sync backfill with the public-message window - Receivers drop wrong-epoch envelopes, bad sender signatures, and senders missing from the creator-signed roster - Fire-and-flood delivery (no per-member acks in v1) UI - Groups open as chat windows through the private-chat sheet (virtual "group_" peer IDs); groups section in the people sheet; /group create/invite/remove/leave/list commands; invitees get a system message + notification and the group appears in their people sheet Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Private groups: fix TLV truncation, roster downgrade, removal notice, block, media, signable bytes Addresses the Codex review and adversarial-review findings on #1383: - TLV encoding now throws GroupTLVError.valueTooLong instead of clamping to 65535 and truncating, so an oversize group message fails to seal and surfaces send_failed rather than shipping ciphertext recipients drop. - Roster nicknames truncate on a Character boundary (never mid-scalar), so a multi-byte nickname can no longer make the whole signed roster undecodable. - Invites now bump the epoch (rotate the key) like removals, giving every roster change a strictly-increasing epoch so out-of-order invite states no longer last-writer-wins a just-added member back out. - Removing a member now sends them a creator-signed roster-without-them under a throwaway all-zero key (never the rotated key), so their client deactivates the group and surfaces "removed" instead of going silently dark. - /block is enforced in the group receive path: a blocked member's messages are dropped from display and notifications, consistent with every other inbound path. - Media affordances are disabled in group chats (both computed sites) so the composer can't strand a media placeholder that never sends; media-in-groups is a documented v2 item. - Creator signature now covers the group name and the sender signature covers the epoch (wire-format-affecting; needs Android parity before ship). - Explicit isGroup guard in markPrivateMessagesAsRead so read/delivered receipts can never leak into group conversations under a future refactor. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
87910541ef |
Prekey bundles: forward-secret async first contact for courier mail (#1381)
* Add capability bits to announce TLV Announces now carry an optional capabilities TLV (0x05): a little-endian bitfield with named bits for upcoming features (prekeys, wifiBulk, gateway, groups, board, vouch, meshDiagnostics). Old clients skip the unknown TLV; peers without it decode as nil so features can distinguish "legacy peer" from "advertises nothing". PeerCapabilities lives in BitFoundation with a minimal-length encoding that preserves unknown bits for forward compatibility. Peer capabilities are stored in the BLE peer registry on verified announce and exposed via BLEService.peerCapabilities(_:). The local advertisement set is empty until each feature ships its bit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Prekey bundles: forward-secret async first contact for courier mail Courier envelopes were sealed with one-way Noise X to the recipient's long-lived static key, so a later compromise of that key exposed every envelope captured in transit. This adds one-time prekey bundles: - PrekeyBundle (MessageType 0x24): 8 one-time Curve25519 public prekeys bound to the owner's Noise static key by an Ed25519 signature over "bitchat-prekey-bundle-v1" canonical bytes; gossiped mesh-wide on its own 60s sync round (SyncTypeFlags bit 9, 200-peer cap, 24h freshness) and verified against the announce-bound signing key before caching. - Sealed envelope v2: Noise X where the responder static is the one-time prekey, prologue "bitchat-prekey-v1" || prekeyID. Sender identity rides encrypted inside and is authenticated exactly like v1 (blocked-sender check included). CourierEnvelope gains an optional prekeyID TLV that v1 decoders skip as unknown. - Local prekeys live in the Keychain; consumed privates survive a 48h grace window for spray-and-wait redeliveries, then are deleted (the forward-secrecy clock starts at deletion). The batch tops back up and re-gossips when unconsumed count drops below 3, and everything is wiped in panic mode. - Routing: courier sealing picks a cached verified bundle when one exists (one prekey per message, reused across deposit retries), with the advertised .prekeys capability as a veto for on-mesh peers, and falls back to static sealing otherwise. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Prekeys: authenticate bundle packets, fix consume-republish, deflake CI Fixes the prekey-bundle PR review + CI failure: - CI root cause: the receive queue (mesh.message) is concurrent, so a gossiped prekey bundle can be processed before the announce that binds its owner's signing key. The old handler dropped such bundles outright, so under CI parallel load the bundle was permanently lost and the cache/gossip tests flaked (verifiedBundleEntersGossipStore, prekeySealedMailTravelsViaCourierAndOpens). Bundles that arrive before their binding are now retained per-owner (bounded) and re-attempted when the verified announce lands, atomically to avoid a check-then-act race. - Authenticate the OUTER prekey-bundle packet (Codex P2 / review MEDIUM): require senderID == PeerID(bundle.noiseStaticPublicKey) and verify the packet's Ed25519 signature (covers senderID + timestamp) against the owner's bound signing key, in addition to the inner bundle signature. Stops replay under a fresh timestamp / fake senderID. - Key the gossip prekey-bundle store/dedup by the bundle's authenticated identity (noiseStaticPublicKey), not the unauthenticated packet senderID, so one valid bundle sprayed under many fabricated sender IDs can't multiply entries and exhaust the 200-owner cap. - Bump published-bundle generatedAt strictly on consume (Codex P1): consuming a prekey shrinks the published bundle, so it now republishes with a strictly newer generatedAt and re-gossips, so peers replace the cached copy and stop assigning the consumed ID before its 48h grace. - Guard the panic/clear detached Application Support tree-deletes behind TestEnvironment.isRunningTests: the SPM test process shares that tree, so the wipe could land mid-test and flake file-dependent tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Update sync tests for prekeyBundle as bit 9 / default sync round Prekeys makes bit 9 (prekeyBundle) a known SyncTypeFlags bit and enables a prekey sync round by default. That broke tests authored by other PRs that assumed bit 9 was phantom or that only their own sync round fires: - SyncTypeFlags(Board)Tests: move the "unknown bits" probes to bits 10+ (0xFE -> 0xFC / 0xFD), since bit 9 is now assigned. - GossipSync(Board)Tests + GossipSyncManagerTests: disable the prekey sync round in configs that run maintenance (as they already do for message/ fragment/fileTransfer), so they isolate the behavior under test. Full app suite (1301 tests) green locally via SPM. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
f9032cf2b9 |
Add mesh diagnostics: /ping, /trace, and topology map (#1377)
* Add mesh diagnostics: /ping, /trace, and topology map - New protocol types ping=0x26 / pong=0x27 (9-byte payload: 8-byte nonce + origin TTL) with per-peer inbound rate limiting (5 per 10s) - /ping @name reports RTT and hop count, 10s timeout - /trace @name prints the estimated path from gossiped directNeighbors - Topology map sheet (circular Canvas layout) reachable from App Info - Ping/pong ride the deterministic directed-relay path like DMs - Tests: payload round-trip, hop-count math, command output, edge normalization, layout Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Fix CI media-wipe race, per-link ping rate limiting, and /ping output routing Three fixes for PR #1377 review: 1. CI flake (sendImage_privateChatProcessesAndTransfersImage): the panicClearAllData / clearCurrentPublicTimeline detached utility-priority tasks delete the real ~/Library/Application Support/files tree, which the test process shares. The wipe fires at a nondeterministic time and raced the sendImage test's JPEG in files/images/outgoing (write then re-read), so prepareImagePacket threw and the test timed out. Both wipes are now skipped under tests (existing TestEnvironment.isRunningTests pattern); this also stops test runs from deleting the developer's real media. 2. Codex P1: ping packets are unsigned, so keying the pong rate limiter on packet.senderID let one connected peer rotate forged sender IDs to bypass the 5-per-10s budget. The limiter now keys on the ingress link (the directly connected peer that delivered the packet); the pong still goes to the claimed sender. Regression test proves rotating senders over one link exhaust one budget (fails 10 vs 5 pongs on the old code). 3. Codex P2: /ping output arrived up to 10s later and was routed from selectedPrivateChatPeer at callback time, misrouting the result after a chat switch. The origin conversation is now captured when the command is issued (CommandOutputDestination) and deferred output is routed there: a DM result lands in the origin chat's history even if deselected, and a mesh-timeline result pins to #mesh instead of the active channel. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * App Info: move NETWORK section under HOW TO USE and uppercase NETWORK/SYMBOLS headers Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Conform DiagnosticsMockContext to sendPublicMessage CommandContextProvider gained sendPublicMessage (Cashu /pay, #1376) after this branch forked, so the diagnostics test mock no longer conformed once main was merged in. Add the no-op stub. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
d4f0c49787 |
Gateway mode: opt-in mesh↔Nostr uplink for geohash channels (#1384)
* Add capability bits to announce TLV Announces now carry an optional capabilities TLV (0x05): a little-endian bitfield with named bits for upcoming features (prekeys, wifiBulk, gateway, groups, board, vouch, meshDiagnostics). Old clients skip the unknown TLV; peers without it decode as nil so features can distinguish "legacy peer" from "advertises nothing". PeerCapabilities lives in BitFoundation with a minimal-length encoding that preserves unknown bits for forward compatibility. Peer capabilities are stored in the BLE peer registry on verified announce and exposed via BLEService.peerCapabilities(_:). The local advertisement set is empty until each feature ships its bit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Gateway mode: opt-in mesh↔Nostr uplink for geohash channels An opt-in "internet gateway" toggle lets one connected phone bridge the local geohash channel for mesh-only peers: signed kind-20000 events ride a new nostrCarrier (0x28) packet — directed to the gateway for uplink, broadcast with TTL for downlink — with Schnorr verification at every hop, CourierStore-style quotas, and explicit loop-prevention rules. - BitFoundation: MessageType.nostrCarrier = 0x28 - NostrCarrierPacket: 2-byte-length TLV codec (direction, geohash, signed event JSON), 16 KiB cap, tolerant decoder - GatewayService: closure-injected policy layer — verify gates (sig, kind, #g tag, age, size), uplink quotas (10/min/depositor rate limit, offline queue of 20 total / 5 per depositor, drop-oldest, flush on reconnect), downlink budget (30/min, bounded drop-oldest backlog), bounded loop-prevention ID sets - BLEService: runtime capability bits (advertise .gateway only while the toggle is on, re-announce on change), signed directed uplink sends, carrier ingress with depositor signature verification - Mesh-only senders uplink automatically from sendGeohash when no relay is connected and a reachable peer advertises .gateway; once-per- channel "sent via mesh gateway" notice - UI: gateway toggle beside the Tor toggle, globe header indicator, VoiceOver labels, xcstrings entries Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Gateway: harden downlink freshness, uplink verify ordering, and drain Fixes the confirmed downlink/uplink defects from the PR #1384 review + Codex findings: - Downlink age + #g gate (Codex P2 / review #1): rebroadcastRelayEvent now drops events outside the same freshness window receivers enforce and whose #g tag mismatches the carrier geohash, BEFORE spending any budget — so a 1h/200-event channel-resubscribe backfill no longer burns the 30/min BLE budget on events every receiver drops. - Rate-limit + dedup before Schnorr (review #2): handleUplinkDeposit now runs cheap structural checks + carried-ID dedup + rate-token consume before isValidSignature(), so a replay flood is bounded by cheap work instead of unbounded main-actor verifies. - Quota-dropped deposits not rendered (review #3): enqueueUplink reports acceptance and injectInbound only fires for events actually published/queued, ending the local-timeline divergence. - Drain timer + mark-after-send (Codex P2 / review #4): a burst beyond budget now arms a timer to drain when the window frees; rebroadcast IDs are marked only after an event is actually sent, so overflow- dropped events stay retryable. - Symmetric publish path (review #5): the gateway publish closure now refuses when no geo relay is known, matching the local send path instead of publishing dead traffic to default relays. - Loop-rule doc (review #7): softened to reflect that rule 3 is a call-site convention with unit-tested backstops; added tests for the publishedEventIDs backstop, downlink freshness/mismatch, drain timer, and quota-drop non-injection. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Gateway: stop self-echo of uplinked events onto the mesh Every event a gateway uplinks to the relays comes back through its own geohash subscription. `rebroadcastRelayEvent` deduped against `meshBroadcastEventIDs`, `rebroadcastEventIDs`, and `pendingDownlinks`, but not `publishedEventIDs` — so an event this gateway just published was downlink-rebroadcast onto the same mesh it originated from, doubling BLE airtime per uplinked message and able to starve the 30/min downlink budget on a busy channel (device-confirmed, filed on #1384). Fix: also skip the downlink rebroadcast when the event id is in `publishedEventIDs`. That set is already the bounded (drop-oldest, capacity maxTrackedEventIDs) loop-rule-2 uplink cache, populated only by `publish()`, so genuine inbound-from-internet events (never published here) still rebroadcast normally. Reconciles cleanly with the existing loop-prevention sets — no new state. Adds a GatewayServiceTests case asserting an uplinked event that echoes back via the subscription is not rebroadcast, while a genuine inbound event still is. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
70229f0be1 |
Originate v2 source routes and wire fragmentIdFilter targeted resync (#1378)
* Originate v2 source routes and wire fragmentIdFilter targeted resync Part A — source-route origination policy: - Gate route application (BLESourceRouteOriginationPolicy): only packets we author, directed at a single peer, with TTL headroom, whose recipient is not directly connected. Relays no longer attach routes to (and re-sign) packets they merely forward. - Version-gate paths: MeshTopologyTracker records the highest protocol version observed per peer; BFS routes require every intermediate hop and the recipient to be v2-observed, capped at 4 intermediate hops. - Degrade on failure: BLESourceRouteFailureCache marks a routed send that sees no inbound traffic from the recipient within 10s as failed and floods for 60s before retrying routes. Part B — REQUEST_SYNC fragmentIdFilter (TLV 0x06): - Requester: BLEFragmentAssemblyBuffer reports stalled broadcast reassemblies (no new fragment for 5s, retried at most every 10s); the maintenance pass sends a types=fragment REQUEST_SYNC naming the stalled 8-byte fragment stream IDs to each connected peer. - Responder: GossipSyncManager restricts the fragment diff to exactly the named streams, bypassing the since-cursor while the GCS filter still excludes pieces the requester holds; RSR/TTL-0/rate-limit semantics unchanged and REQUEST_SYNC stays link-local. - Bounds: at most 60 IDs per request (60*17-1 = 1019 bytes <= the 1024-byte decoder cap); oversized 0x06 values are ignored, not fatal. Docs: SOURCE_ROUTING.md gains the iOS origination policy (§8); REQUEST_SYNC_MANAGER.md documents 0x05/0x06 as implemented. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Fix stall-clock refresh on duplicates and overflow suppression in fragment resync Two fixes to stalledBroadcastFragmentIDs bookkeeping in BLEFragmentAssemblyBuffer: - Duplicate fragments no longer reset the stall clock. Fragment packets bypass the packet deduplicator, so relayed duplicates of an already-held index arriving every few seconds kept lastFragmentAt fresh and suppressed the targeted REQUEST_SYNC indefinitely. Now lastFragmentAt only updates when the index is new (actual progress). - Only the streams that will actually be encoded on the wire are rate-limited. Previously every stalled candidate got lastResyncRequestAt set, but encodeFragmentIdFilter serializes at most RequestSyncPacket.maxFragmentIdFilterCount (60) IDs, so overflow streams were suppressed for retryAfter without ever being requested. Selection now caps at that shared constant, oldest stall first, so overflow stays eligible and rotates fairly on the next pass. Tests: duplicates arriving periodically still trigger the stall report; 70 stalled streams yield the 60 oldest on the first pass and the remaining 10 on the next. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
60be88a4f5 |
Geohash bulletin board: persistent signed notices over mesh sync (#1379)
* Add geohash bulletin board: persistent signed notices over mesh sync
New MessageType 0x23 carries TLV-encoded board posts and tombstones,
self-signed with the author's Ed25519 key ("bitchat-board-v1" /
"bitchat-board-del-v1" domains) so notices verify without the author
present. BoardStore persists raw signed packets under Application
Support/board/ (200 posts, 5 per author, oldest evicted; expiry sweep;
tombstones retained until the deleted post's original expiry) and is
wiped on panic.
Board packets join gossip sync as bit 8 of the existing variable-length
types bitfield (a second byte old decoders already accept and ignore),
with a 60s round and its own capacity, served straight from the board
store so retention has one owner. Posts relay like broadcasts; urgent
posts get the announce-class TTL cap.
UI: a pin button in the header opens the board for the current channel
(geohash board, or mesh-local board), with urgent-pinned newest-first
listing, compose with urgent toggle and 1/3/7-day expiry, and
swipe-delete on own posts. Geohash posts also publish one-way as
Nostr kind-1 location notes when relays are reachable.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Board: bound orphan tombstones and reject future-dated posts at ingest
Two hardening fixes from Codex review of the geohash bulletin board:
- Orphan tombstones (P1): retention was derived solely from the
sender-chosen deletedAt, so self-signed tombstones for unseen post IDs
with far-future deletedAt persisted and re-entered sync unboundedly.
Retention is now also clamped to receive time (now + 7d + 1h skew --
no post can outlive that), and orphans are capped at 100 globally and
5 per author key with oldest-received evicted first. Matched
tombstones and disk restores keep their existing behavior.
- Future-dated posts (P2): ingest only checked expiresAt > now, letting
posts dated years ahead sort above honest posts and squat the 200
global slots without ever pruning. The single ingest chokepoint
(radio, sync, and disk restore all funnel through it) now rejects
createdAt > now + 1h skew and expiresAt > now + 7d + 1h skew; the
decoder's span rule is unchanged.
Adds tests for the skew boundary, far-future expiry, receive-time
tombstone clamping, orphan caps/eviction, and matched-tombstone
exemption.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: jack <jackjackbits@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
201dbac49a |
docs: reconcile protocol docstrings with implementation (#1374)
- BitchatProtocol.swift: stop advertising "timing obfuscation prevents traffic analysis" — what exists is randomized relay jitter (RelayController, 10-220 ms) and PKCS#7-style padding to 256/512/1024/2048-byte blocks (MessagePadding); there is no cover traffic or per-message timing obfuscation. Also update the stale Message Types list (Delivery/Read are Noise payloads, no Version negotiation type; add CourierEnvelope/RequestSync/FileTransfer). - MessageType.swift: header said "6 essential" types; the enum has 9 cases. WHITEPAPER.md needed no changes: the #1372 rewrite already replaced the old Bloom-filter and MessageRetryService claims, and its numbers (dedup 1000/5min, jitter, outbox 100/peer 24h 8 attempts, courier 16 KiB/24h/40-20-5-2 quotas, spray 4/8, gossip 1000/15s/6h) all match the code. Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
5fdc15d5af |
Add capability bits to announce TLV (#1375)
Announces now carry an optional capabilities TLV (0x05): a little-endian bitfield with named bits for upcoming features (prekeys, wifiBulk, gateway, groups, board, vouch, meshDiagnostics). Old clients skip the unknown TLV; peers without it decode as nil so features can distinguish "legacy peer" from "advertises nothing". PeerCapabilities lives in BitFoundation with a minimal-length encoding that preserves unknown bits for forward compatibility. Peer capabilities are stored in the BLE peer registry on verified announce and exposed via BLEService.peerCapabilities(_:). The local advertisement set is empty until each feature ships its bit. Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
7341696280 |
Expand store-and-forward: open couriers, spray-and-wait, persistent outbox, 6h public history (#1372)
Store-and-forward previously delivered to an out-of-range peer only if a
mutual favorite happened to be connected at send time and later met the
recipient directly, and everything except courier envelopes died with the
app process. This closes those gaps end to end:
- Persist the MessageRouter outbox to disk, sealed with a ChaChaPoly key
held only in the Keychain (no plaintext at rest); queued private
messages now survive an app kill and flush on next launch.
- Deposit retry: queued messages are re-deposited whenever a new eligible
courier connects, tracked per message so the same courier is never
double-burned, until 3 distinct couriers carry it or it expires.
- Tiered open couriering: signature-verified strangers can now carry mail
(2 envelopes/depositor into a 20-slot pool) alongside mutual favorites
(5 each); overflow evicts verified-tier mail before favorites'.
- Spray-and-wait: envelopes carry a copy budget (4, capped 8, new TLV,
wire-compatible with old clients); couriers split half their remaining
budget with each newly encountered courier so mail diffuses through a
moving crowd.
- Remote handover: a verified relayed announce now floods a copy toward
the multi-hop recipient (directed-relay treatment, 10-min per-envelope
cooldown) while the carried original stays put for a direct encounter.
- Public history: gossip-sync window for whole public messages widened
from 15 min to 6 h, matched on the receive-acceptance side, and the
message store persists to disk so devices bridge partitions and
restarts ("town crier").
- Privacy-safe local delivery counters (bare tallies, log-only) so the
store-and-forward stack is measurable on-device.
- Panic wipe now also clears the sealed outbox, gossip archive, and
counters.
- Rewrite WHITEPAPER.md to describe the app as implemented (Noise XX/X,
actual flood control, courier system, gossip sync, Nostr path); the old
document described a bloom filter, three fragment types, and a
MessageRetryService that don't exist.
1037 macOS tests pass (17 new); iOS builds.
Co-authored-by: jack <jackjackbits@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
75da63c9d7 |
Fix favorites end-to-end: peer-list duplicates, Nostr sync, /fav key corruption (v1.5.4) (#1367)
* Friend-courier store-and-forward: mutual favorites carry sealed messages to offline peers When a private message has no reachable transport, the router now seals it to the recipient's Noise static key (new one-way Noise X pattern) and hands the envelope to up to three connected mutual favorites. Couriers store the opaque ciphertext under strict quotas (20 total, 5 per depositor, 16 KiB, 24 h) and hand it over when the recipient's announce matches a rotating HMAC recipient tag; the recipient opens it and the message flows through the normal private-message pipeline, so dedup and delivery acks just work. - CourierEnvelope TLV + courierEnvelope (0x04) message type in BitFoundation - Noise X one-way pattern reusing the existing handshake machinery, domain-separated by a courier prologue; sender identity authenticated via the ss DH (no forward secrecy - documented tradeoff) - CourierStore with eviction, file persistence, and panic-wipe integration - Rotating recipient tags (HMAC over epoch day) so carried envelopes don't correlate for observers who don't already know the recipient's key - New "carried" delivery status with figure.walk glyph; header indicator while carrying mail for others - Three-node end-to-end test ferrying packets through real BLEService instances, plus codec/crypto/store/router suites (986 tests green) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Fix courier handoff verification and directed sends * Authenticate courier deposits by ingress peer * Gate courier handover on direct announces and isolate store test Envelopes are removed from the courier store optimistically, so releasing them on a relayed (multi-hop) announce risks losing carried mail to a speculative flood that never reaches the recipient. Handover now also requires the announce to have arrived directly (full TTL), i.e. an actual encounter with a live link; regression test builds a relayed copy of a genuinely signed announce (TTL is excluded from announce signatures). Also make CourierStore's on-disk location injectable so the persistence test round-trips through a temp directory instead of wiping the real Application Support store, and reattach BLEAnnounceHandler's doc comment to the class it describes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Use Xcode-bundled Swift in CI instead of a standalone toolchain The unpinned setup-swift action installs Swift 6.1, which refuses the SDK on runner images that have rolled to Xcode 26.5 ("this SDK is not supported by the compiler"). Jobs passed or failed depending on which image they landed on. The Xcode-bundled toolchain always matches the image's SDK, and matches local development. Cache keys now include the toolchain version so artifacts from one compiler are never restored into builds with another. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Drop couriered mail from blocked senders at envelope open The UI-layer block check (isPeerBlocked in the transport event coordinator) resolves a fingerprint from the live session or peer list, but a couriered message arrives precisely when its sender is absent — no session, no registry entry — so the check failed open and a blocked identity's mail was delivered anyway. Gate in openCourierEnvelope, where the sealed sender's full static key is in hand. End-to-end test ferries a full deposit→carry→handover round and verifies the envelope from a blocked sender never reaches the delegate (confirmed failing without the gate). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Fix favorites end-to-end: peer-list dedup, Nostr sync, /fav key corruption - UnifiedPeerService: dedup offline favorites against mesh peers by noise key. Phase 2 compared a 64-hex noise-key PeerID against 16-hex mesh IDs (never equal), leaving only a nickname+isConnected heuristic — a mutual favorite that was reachable-but-not-connected or renamed rendered twice, and a same-nick stranger could suppress a favorite entirely. - Nostr inbound: intercept [FAVORITED]/[UNFAVORITED] markers in the live PM handler so they update theyFavoritedUs instead of rendering as chat text; mutual favorites can now form over Nostr. Delete the dead favorite-aware PM variant and ChatNostrCoordinator.handleFavoriteNotification (unwired, parsed a stale FAVORITE:TRUE|… format no sender emits). - NostrTransport.isPeerReachable: match short form regardless of incoming ID width — toggling an offline favorite (addressed by 64-hex noise key) was silently dropped with no reachable transport. - BLEService.sendPrivateMessage: normalize recipient to the short ID like sendFilePrivate, so a 64-hex target hits the existing Noise session instead of initiating a handshake with a 32-byte wire recipient ID. - /fav, /unfav: stop writing Data(hexString: peerID.id) — the 8-byte routing ID for mesh peers — into the favorites store as a "noise key", and stop double-sending the favorite notification; delegate to toggleFavorite with a proper state check. - FavoritesPersistenceService.updatePeerFavoritedUs: keep the stored nickname when the caller passes the "Unknown" placeholder. - Bump marketing version to 1.5.4. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Route DMs to mutual favorites via Nostr when a mesh-keyed peer goes offline Field-tested on device: with a DM window opened while the peer was on mesh (conversation keyed by the short 16-hex ID), walking out of range and sending failed instantly with "peer not reachable" even though the header showed the peer as Nostr-reachable (mutual favorite, npub known). sendPrivateMessage derived the favorites key as Data(hexString: peerID.id) — for a short mesh ID that is the 8-byte routing ID, never the noise key — so the mutual-favorite/Nostr-key checks always came up empty and the send failed before reaching MessageRouter. Conversations keyed by the full 64-hex noise-key ID (opened from the offline favorite row) were unaffected, which is why later tests appeared to work. Resolve the noise key properly (peerID.noiseKey, then the unified peer row, then the favorites store by derived short ID) and add a regression test for the mesh-keyed-peer-goes-offline case. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Label Nostr DMs from favorites with their stored nickname Field-tested: a DM delivered over the Nostr fallback rendered as "anon#678e" instead of the sender's name. The inbound handler named the sender via displayNameForNostrPubkey, which only knows geohash-scoped names — even though the pipeline had already resolved the sender's noise key (the conversation is keyed by it). When the conversation key carries a noise key, prefer the favorite's stored nickname; geohash DMs (nostr_ keys) keep the anon geo name. This also stops an inbound Nostr [FAVORITED] from overwriting the stored nickname with the anon fallback, since the same name feeds updatePeerFavoritedUs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Fix courier path for offline favorites addressed by noise-key IDs Two Codex review findings, both the same ID-width confusion this PR targets, in the courier flow: - CourierDirectory.favoritesBacked resolved recipients only via getFavoriteStatus(forPeerID:), which requires a short 16-hex ID — offline favorites are addressed by the full 64-hex noise-key ID, so attemptCourierDeposit silently bailed for exactly the peers couriers exist to serve. The 64-hex ID now yields its own key directly. - openCourierEnvelope emitted the derived short mesh ID even when the sender has no live mesh identity, landing couriered mail in an unresolvable short-ID thread labeled "Unknown". Absent senders now emit the full noise-key ID so the message joins the stable favorite conversation; present senders keep the live short-ID thread. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
b31a63ce37 |
Burn down SwiftLint advisory violations from 109 to 4 (#1362)
Mechanical style fixes across the enabled rule set, mostly via swiftlint --fix (trailing_comma, comma, colon, trailing_newline, comment_spacing, unused_closure_parameter, unneeded_break_in_switch, opening_brace) plus hand fixes: - non_optional_string_data_conversion (45): .data(using: .utf8)! and ?? Data() fallbacks replaced with the non-optional Data(_.utf8), including two production sites (NIP-44 HKDF info constant and the announce canonicalization context/nickname bytes — byte-identical output, only the impossible-nil handling is gone). - switch_case_alignment: LocationChannel had a misindented closing brace; also repaired an --fix artifact in BLEService's .none case. - redundant_string_enum_value: TrustLevel raw values equal to the case names (encoded form unchanged). - unused_optional_binding: let _ = binds replaced with != nil / is Bool. - static_over_final_class: PreviewView.layerClass. - Resolved the BinaryProtocolTests TODO by documenting that 8-byte recipient ID truncation is the fixed wire-field size, not a bug. The 4 remaining violations are all todo markers for a shared test-helpers module (tracked in #1088) and one Reuse note. Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
af954b05ea |
Liquid Glass theme with in-app appearance switcher (#1337)
* Centralize UI theme colors into semantic ThemePalette tokens Introduce AppTheme/ThemePalette (Utils/Theme.swift) with an environment key and @ThemedPalette property wrapper, persisted via @AppStorage. Views now resolve background/primary/secondary/accentBlue/alertRed/ divider tokens from the environment instead of computing colors inline, removing the backgroundColor/textColor/secondaryTextColor prop-drilling through the header, composer, and people-sheet hierarchy. Matrix theme output is pixel-identical; this is groundwork for a user-selectable theme switcher. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Add liquid glass theme with in-app appearance switcher Add a .liquidGlass AppTheme alongside the matrix terminal theme, selectable from a one-line APPEARANCE row in the app info sheet (persisted via @AppStorage, applies live). Liquid glass renders system fonts and colors over a subtle gradient backdrop, with the header and composer floating as Liquid Glass panels (real .glassEffect() on iOS/macOS 26, compiler-gated with an ultraThinMaterial fallback for older SDKs). The message list scrolls underneath the chrome via safe-area insets, and all sheets share the same backdrop and surface language. The matrix theme is unchanged. Details: - Theme is threaded through ChatMessageFormatter so message AttributedStrings switch font design per theme; the per-message format cache gains a variant key so themes never serve each other's cached strings - New palette tokens: accent (interactive tint) and locationAccent (geohash green), replacing scattered hardcoded greens/blues in the voice note, waveform, verification, and people-sheet views - Header controls get full-height tap targets and the people count becomes a real Button (previously a tap gesture on the cluster) - CommandSuggestionsView renders nothing when empty instead of a zero-height view that pushed the composer input off-center - New appearance strings localized for all 29 catalog languages Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
cf3b85f65c |
Compile all logging out of release builds
Production builds previously still emitted info/warning/error entries via os_log (content private-redacted, but entries, categories, and timing metadata were visible, and message strings were constructed). For a privacy-first app the right posture is silence: every SecureLogger wrapper and both cores are now gated behind #if DEBUG, so release builds construct no log strings and emit nothing. Debug builds are unchanged (public formatting, level threshold via BITCHAT_LOG_LEVEL). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
4b287f7490 |
Stop paying for filtered log messages; sample per-event hot-path logs
SecureLogger's public wrappers evaluated their message autoclosure before the level check inside log(), so every filtered debug message across the codebase still paid for string interpolation - hundreds of call sites on the per-packet/per-event hot paths. The wrappers now guard the level first, and debug() compiles out of release builds entirely. Regression tests verify filtered messages are never constructed. The two heaviest per-event debug logs (NostrRelayManager inbound events, ChatNostrCoordinator geo events) are now sampled every 100th with a running count, so debug-enabled dev builds stop emitting hundreds of lines per minute in busy geohashes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
c60eff2c11 | Move additional files/tests to BitFoundation (#1102) | ||
|
|
4cfcefcda6 |
BitFoundation module to centralize shared components (#1089)
* Run local packages’ tests as well on CI * BitFoundation module to centralize shared components |
||
|
|
3fc64f6168 |
feat: Implement Request Sync Manager (V2 Sync) (#965)
* feat: Implement Request Sync Manager (V2 Sync) - Add RequestSyncManager to track and attribute sync requests - Update BitchatPacket and BinaryProtocol to support IS_RSR flag (0x10) - Update RequestSyncPacket with new TLV fields (sinceTimestamp, fragmentIdFilter) - Update GossipSyncManager to use unicast sync requests and mark responses as RSR - Update BLEService to enforce timestamp validation for normal packets and exempt valid RSRs - Add documentation for the new sync manager mechanism * fix: Resolve compilation errors in V2 Sync implementation - Remove duplicate restartGossipManager in BLEService - Add missing TransportConfig constants for sync - Add 'sync' log category to BitLogger - Add missing BitLogger import in GossipSyncManager * fix: Update tests for V2 Sync changes - Add requestSyncManager parameter to GossipSyncManager init in tests - Implement getConnectedPeers stub in RecordingDelegate - Remove unused variable warning in SubscriptionRateLimitTests --------- Co-authored-by: a1denvalu3 <> Co-authored-by: jack <212554440+jackjackbits@users.noreply.github.com> |
||
|
|
ec54877140 |
Remove dormant wake attempt on Tor foreground restart
Arti's dormant/wake FFI functions are no-op stubs that don't actually implement dormant mode. The app was wasting 2.5-12 seconds trying to wake from a dormant state that doesn't exist before falling back to a full restart. This change: - Removes wakeFromDormant() call and function - Simplifies goDormantOnBackground() to just mark state as not ready - Goes straight to restartArti() on foreground, eliminating the delay Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> |
||
|
|
23d63ab4df |
Replace C Tor with Rust Arti for Tor integration
- Replace C Tor (0.4.8.21) with Rust Arti (1.9.0/arti-client 0.38) - 70% smaller binary: 21MB xcframework vs 67MB (6.9MB vs 14MB per slice) - Memory-safe Rust implementation with modern async (tokio) - Same SOCKS5 proxy interface at 127.0.0.1:39050 for drop-in compatibility - FFI wrapper (arti-bitchat crate) with C-compatible exports - Swift TorManager maintains identical public API - Aggressive size optimization: opt-level=z, lto=fat, panic=abort, strip - Supports iOS device, iOS simulator (Apple Silicon), and macOS Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> |
||
|
|
7b9ffe464a |
Add Tor build script to repository for reproducibility
Include the build-minimal.sh script alongside BITCHAT_TOR.md so future rebuilds can be done directly from the bitchat repo. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> |
||
|
|
5a87ee3e62 |
Update Tor to 0.4.8.21 with aggressive size optimization
- Update Tor from 0.4.8.17 to 0.4.8.21 - Update OpenSSL from 3.5.1 to 3.6.0 - Aggressive OpenSSL trimming to reduce binary size: - Remove post-quantum crypto (ML-DSA, ML-KEM, SLH-DSA, LMS) - Remove legacy ciphers (DES, RC2, RC4, RC5, IDEA, SEED, etc.) - Remove unused hashes (MD4, MDC2, Whirlpool, RIPEMD160) - Remove Chinese standards (SM2, SM3, SM4) - Remove certificate features (CMP, CT, RFC3779) - Remove GOST, binary EC curves, and other unused features - Add --disable-module-pow to Tor configure - Add -Wl,-dead_strip linker flag Binary size reduction: - iOS arm64: 17 MB → 14.2 MB (-16%) - iOS simulator: 16 MB → 13.8 MB (-14%) - macOS arm64: 16 MB → 13.8 MB (-14%) Build script at ~/Documents/vibe/Tor.framework-build/build-minimal.sh Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> |
||
|
|
aca44f9f55 |
Extract sanitization logic into an extensions + add tests (#827)
Co-authored-by: jack <212554440+jackjackbits@users.noreply.github.com> |
||
|
|
eb35608fa1 | Remove unused helpers and add cross-platform logging fallbacks (#811) | ||
|
|
4563c22d2d | Fix hidden source of deadlock (#794) | ||
|
|
3a35b3acc2 |
Modularization: Extract Tor into a separate module (#602)
* Extract Tor into a separate module * Add Tor package as a dependency for iOS & macOS targets * Move `tor-nolzma.xcframework` inside Tor * Remove `libz` from Frameworks as its linked in Tor * Remove stray `.gitkeep` from macOS target membership * Fix missing import and access control for modularized Tor - Add import Tor to NetworkActivationService - Make TorManager.shutdownCompletely() public for external access * Fix tor-nolzma.xcframework structure for Xcode builds - Add missing Info.plist files to all framework slices - Restructure macOS framework to use deep bundle format (Versions/) - Keep iOS frameworks as shallow bundles (standard for iOS) This fixes the Xcode build errors while maintaining SPM compatibility. * Remove stale xcframework references from Xcode project Xcode cleaned up old direct references to tor-nolzma.xcframework since it's now managed internally by the Tor Swift package. --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com> |
||
|
|
347ce5ece4 |
Modularization: Extract SecureLogger into a separate module (#600)
* Extract SecureLogger into a separate module * Add BitLogger package as a dependency for iOS & macOS targets |