mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-27 09:45:22 +00:00
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>
Test Harness Guide
This test suite uses an in-memory networking harness to make end-to-end and integration tests deterministic, fast, and race-free without touching production code.
In-Memory Bus
- File:
bitchatTests/Mocks/MockBLEService.swift - Registry/Adjacency: Global
registrymapspeerIDto aMockBLEServiceinstance;adjacencyrecords simulated links between peers. - Setup: Call
MockBLEService.resetTestBus()insetUp()to clear state between tests. - Topology: Use
simulateConnectedPeer(_:)andsimulateDisconnectedPeer(_:)to add/remove links.connectFullMesh()helpers in tests build larger topologies. - Handlers: Tests can observe data via
messageDeliveryHandler(decodedBitchatMessage) andpacketDeliveryHandler(rawBitchatPacket). - De‑duplication: A thread-safe
seenMessageIDsprevents duplicate deliveries during flooding/relays.
Broadcast Flooding
- Flag:
MockBLEService.autoFloodEnabled - Intent: When
true, public broadcasts propagate across the entire connected component (ignores TTL for reach) while still de‑duping to prevent loops. - Usage: Enabled in Integration tests (
setUp) to simulate large-network broadcast; disabled in E2E tests to keep routing explicit and verify TTL behavior (seePublicChatE2ETests.testZeroTTLNotRelayed).
Rehandshake Flow (Noise)
- Why: The legacy NACK recovery path was removed; recovery now relies on Noise session rehandshake after decrypt failure or desync.
- Manager:
NoiseSessionManagermanages per-peer sessions. - Pattern: On decrypt failure, proactively clear the local session and re-initiate a handshake. The peer accepts and replaces their session.
- Test:
IntegrationTests.testRehandshakeAfterDecryptionFailure- Corrupts ciphertext to induce a decrypt error.
- Calls
removeSession(for:)on the initiator’s manager beforeinitiateHandshake(with:)to avoidalreadyEstablished. - Verifies encrypt/decrypt succeeds post-rehandshake.
Tips
- Determinism: Add small async delays only where handler installation/topology changes could race the first send.
- Scoping: Keep
autoFloodEnabledtoggled only within Integration tests; always reset intearDown()to avoid cross-test contamination. - Direct vs Relay: Private messages target a specific peer when adjacent; otherwise they are surfaced to neighbors for relay and, if known, also delivered to the target.
Quick Start
- Create nodes and connect them:
let svc = MockBLEService(); svc.myPeerID = "PEER1"svc.simulateConnectedPeer("PEER2")
- Observe messages:
svc.messageDeliveryHandler = { msg in /* asserts */ }
- Enable broadcast flooding for Integration suites only:
MockBLEService.autoFloodEnabled = true