# Private-media wire migration Private files use the `BitchatFilePacket` TLV shared by iOS and Android. The preferred direct-message wire form encrypts that complete TLV inside the peer's Noise session before BLE fragmentation. ## Wire values and capability - `NoisePayloadType.privateFile` is `0x20`, the value already deployed by the Android client. New sends must use this value. - iOS temporarily accepts `0x09`, which appeared in prerelease builds of the private-media change. Decoders canonicalize it to `privateFile`; they never emit it. - `NoisePayloadType.authenticatedPeerState` is permanently assigned `0x21`. It is emitted after every completed/rekeyed Noise XX session and echoed at most once when the remote state arrives, so message-3/proof reordering over different mesh links converges. This type is part of the protocol security boundary and is not removed when the media migration ends. - The `0x21` payload starts with version `0x01`, followed by one-byte type/length/value fields. Version 1 requires canonical TLV `0x01` (the minimal little-endian `PeerCapabilities` bitfield, 1-8 bytes) and TLV `0x02` (the 32-byte Ed25519 announcement signing key). Duplicate required fields, non-minimal capabilities, malformed lengths, missing fields, and unknown versions are ignored without changing state. Unknown TLVs are skipped. - The public `PeerCapabilities.privateMedia` announce bit is a discovery hint: it starts a Noise handshake, but never selects encrypted sending or creates a pin. A private transfer waits boundedly for the exact session's encrypted `0x21`. A valid bit-8 proof selects Noise `0x20`; a valid no-bit proof or a no-proof timeout reaches the explicit legacy-consent path for an unpinned peer. No timeout automatically sends raw bytes. - An unpinned peer with a stable Noise key but without that capability is eligible for one signed, directed `fileTransfer`, matching the pre-migration wire form used by older iOS and accepted by current Android clients, only after the sender confirms a per-send warning that the file is not end-to-end encrypted and mesh relays can see it. The consent is consumed by that invocation and is never remembered. - A signed announce never creates a pin by itself: an attacker can copy a victim's public Noise key, supply its own Ed25519 key and capability bits, and self-sign an internally consistent announce. Only successfully decrypted `0x21` state pins the authenticated Noise fingerprint and binds the Ed25519 key used by later announces/public messages. A later valid no-bit `0x21` is treated as a downgrade, and raw fallback is blocked even if a caller presents legacy consent. Public no-bit announces cannot overwrite current session-authenticated state. - During migration, both an absent capabilities TLV and an explicit TLV without `privateMedia` are legacy-eligible when that stable fingerprint is not pinned. This supports clients that added capability advertisement before encrypted media. Neither shape bypasses a previously authenticated pin. Older clients decrypt and ignore unknown inner type `0x21`; they do not need to understand it to continue using text or the warned legacy media path. They are never inferred capable merely because the handshake succeeded. Removal gates are independent and must not share an arbitrary calendar date: - Remove the `0x09` receive alias only after every TestFlight/internal build that emitted it has expired and minimum-supported-client policy excludes it. - Remove the signed directed raw `0x22` fallback only after minimum-supported iOS and Android clients emit authenticated bit-8 `0x21` state and the legacy population has aged out. - Nostr kind `1059` compatibility is a separate envelope migration. Its dual publish/removal gate is not evidence that either BLE compatibility shape can be removed. ## Security boundary The encrypted form provides Noise confidentiality and peer authentication. The fallback is signed and its signature is required on receive, so relays cannot forge its sender or contents. It is not confidential: relays can see the raw file TLV. The UI says this explicitly and asks on every send. A peer without a stable Noise key from a verified registry entry cannot use the fallback. Keep it only for the mixed-version migration, and remove it only after minimum-supported Android and iOS releases emit authenticated bit-8 `0x21` state and the legacy population has aged out. Never replace it with an unsigned fallback, persist blanket consent, or send both forms. Incoming clients accept all three migration-era shapes: | Sender | Inbound form | Result | | --- | --- | --- | | Current Android | Noise `0x20` | Decrypt and deliver | | Prerelease iOS | Noise `0x09` | Decrypt, canonicalize, and deliver | | Older client | Signed directed `fileTransfer` | Verify signature and deliver | | Forged/unsigned raw sender | Directed `fileTransfer` | Reject | Panic wipe clears the persistent capability pins together with the rest of the encrypted identity cache. This migration path is mesh-Noise-only (BLE and compatible direct mesh links). Nostr private-media transport is unchanged and remains a follow-up. Nostr inbound paths explicitly ignore `0x21`; do not infer the mesh consent fallback or capability-pin semantics for Nostr delivery. ## Size interoperability iOS bounds inbound file content at 1 MiB and applies the expanded allocation budget only after a large Noise ciphertext authenticates to `0x20` or the temporary `0x09` alias. Ordinary Noise messages retain their 64 KiB limit. Current Android builds cap each reassembly at 256 fragments. Depending on the negotiated BLE packet size and routing overhead, that is roughly 110-120 KiB, well below iOS's absolute inbound ceiling. Private-media v1 therefore runs the actual route-aware BLE fragment planner before both encrypted and consented legacy sends and rejects any plan above 256 fragments with a visible failure. This fragment-count contract, rather than a guessed byte threshold, stays correct as route overhead changes.