1<div align="center">2 <img src="sophaxchat_logo.png" alt="SophaxChat" width="160" />34 <h1>SophaxChat</h1>56 <p><strong>Works offline over Bluetooth mesh. Reaches anyone in the world over Tor. No servers. No phone number. No account.</strong></p>78 <p>9 <img src="https://img.shields.io/badge/Swift-6.2-FA7343?logo=swift&logoColor=white" />10 <img src="https://img.shields.io/badge/iOS-17%2B-000000?logo=apple&logoColor=white" />11 <img src="https://img.shields.io/badge/macOS-14%2B-000000?logo=apple&logoColor=white" />12 <img src="https://img.shields.io/badge/Android-8%2B-3DDC84?logo=android&logoColor=white" />13 <img src="https://img.shields.io/badge/License-MIT-blue" />14 <img src="https://img.shields.io/badge/Status-Alpha-orange" />15 <img src="https://img.shields.io/badge/Cryptography-CryptoKit-34C759?logo=apple" />16 </p>1718 <p>19 <a href="PHILOSOPHY.md">Philosophy</a> •20 <a href="#protocol">Protocol</a> •21 <a href="#cryptography">Cryptography</a> •22 <a href="#features">Features</a> •23 <a href="#architecture">Architecture</a> •24 <a href="#getting-started">Getting Started</a> •25 <a href="#android">Android</a> •26 <a href="#security">Security</a> •27 <a href="#roadmap">Roadmap</a>28 </p>2930 <br />3132 > ⚠️ **Alpha — not yet production-ready.** Cryptographic primitives are sound, but the codebase has not been independently audited. Do not rely on it for life-critical anonymity.3334 <br />3536**[iPhone: build from source](#run-on-iphone)** · **[Mac: works today with a free Apple ID](#run-on-mac-catalyst)** · **[Android](android/README.md)**3738</div>3940---4142<div align="center">4344| | |45|:---:|:---:|46| <img src="screenshots/s1.png" width="280" /> | <img src="screenshots/s2.png" width="280" /> |47| <img src="screenshots/s3.png" width="280" /> | <img src="screenshots/s4.png" width="280" /> |4849<sub>Running on macOS via Xcode — "My Mac (Designed for iPad)"</sub>5051</div>5253---5455## What is SophaxChat?5657SophaxChat is an **open-source, serverless, end-to-end encrypted** messenger for iOS, macOS, and Android.5859**Nearby** — it works over Bluetooth LE and WiFi Direct. No internet, no servers, no pairing. Messages relay across multiple devices automatically (up to 6 hops), so you reach people even when you're not directly connected.6061**Globally** — enable TCP and connect through **[Orbot](https://apps.apple.com/app/orbot/id1609461599)** (Tor). Your traffic is anonymous, your IP is hidden, and you can reach anyone in the world with a `.onion` address. No configuration inside SophaxChat — Orbot handles everything.6263In both modes: **no phone number, no email, no account**. Your identity is a Curve25519 key pair, generated on your device, never transmitted to any server. Every message uses the **Signal Protocol** (X3DH + Double Ratchet with Header Encryption).6465> **Is it still "Anonymous, offline, end-to-end encrypted mesh chat"?**66> Yes — and more. Local mode (BLE/WiFi) is fully offline and anonymous as always. With TCP enabled via Orbot/Tor, it becomes *global*: still anonymous (no account, no phone number), still end-to-end encrypted (same X3DH + Double Ratchet pipeline), still decentralized (no servers — direct peer-to-peer). TCP and Tor support are optional and off by default.6768### Who is SophaxChat for?6970**Disaster responders and field workers** — no internet? Bluetooth and WiFi mesh still work. Devices relay messages across the network automatically, up to 6 hops.7172**Privacy-conscious users** — tired of surrendering your phone number to chat apps. Here, your identity is a cryptographic key pair, generated locally, never shared with any server.7374**Developers and researchers** — the full source is open. Cryptographic primitives (X3DH, Double Ratchet, Header Encryption, Sealed Sender) are implemented in ~3 500 lines of auditable Swift using Apple's CryptoKit.7576### Philosophy7778SophaxChat is built around three principles that cannot be traded away:79801. **No servers, ever.** Servers are honeypots. Every central point is a single point of failure, surveillance, and censorship. SophaxChat communicates directly device-to-device — over Bluetooth, WiFi, and peer-to-peer TCP. The Tor network (via Orbot) provides internet routing without introducing any trusted server.81822. **No identity leakage.** No phone number, no email, no account. Your identity is a Curve25519 key pair, generated on your device, never transmitted to any server. The app doesn't even know your name unless you choose to set one.83843. **Maximum cryptographic protection, not just "good enough".** X3DH gives each session unique key material. Double Ratchet rotates keys every message. Header Encryption hides routing metadata from relay nodes. Sealed Sender hides who is talking to whom. These are not features — they are the baseline.8586Read the full manifesto: [PHILOSOPHY.md](PHILOSOPHY.md).8788### Why does it exist?8990Most messengers need a server and the internet. Mesh messengers like bitchat and Briar work without the internet, but each makes different trade-offs in platforms, protocol and reach. SophaxChat's goal is to combine three things in one app:9192-**No server at all** — devices talk to each other directly, nearby over Bluetooth/WiFi or far away over Tor.93-**No phone number, email or account** — your identity is a key made on your device.94-**The Signal Protocol design** (X3DH, Double Ratchet, sealed sender) for 1:1 chats, and MLS for groups on Apple devices.9596It's meant for situations where the internet is down — a power cut, a disaster, a crowded event — and for people who would rather not hand over a phone number. Until an independent audit has been done, it should not be relied on where being identified would put someone in danger.9798---99100## Protocol101102### 1:1 Session Lifecycle103104```105┌─ Alice ─────────────────────────────────────────────────────────── Bob ─┐106107 [1. DISCOVERY — MultipeerConnectivity (Bluetooth LE / WiFi Direct)]108109 Alice ←──── MPC peer found ────→ Bob110111 [2. HELLO — Immediate key exchange on connection]112113 Alice ──── Hello(PreKeyBundle_A) ────→ Bob114 Alice ←─── Hello(PreKeyBundle_B) ──── Bob115116 [3. SESSION INIT — X3DH key agreement, first encrypted message]117118 Alice computes:119 DH1 = DH(IK_A, SPK_B)120 DH2 = DH(EK_A, IK_B)121 DH3 = DH(EK_A, SPK_B)122 DH4 = DH(EK_A, OPK_B) [if available]123 SK = HKDF(DH1 || DH2 || DH3 || DH4)124125 Alice ──── InitiateSession(EK_A, usedKeyIDs, DR_msg_0) ────→ Bob126 Bob derives SK from his keys127 Bob inits Double Ratchet128129 [4. MESSAGES — Double Ratchet + Header Encryption]130131 Alice ──── SealedSender(encHeader, encBody) ────→ Bob132 Alice ←─── SealedSender(encHeader, encBody) ──── Bob133134 [5. RELAY — If Alice and Bob not directly connected]135136 Alice ──── Relay(TTL=6, target=Bob, msg) ────→ Charlie ────→ Bob137 (TTL=5)138139└─────────────────────────────────────────────────────────────────────────┘140```141142### Group Session Lifecycle143144```145┌─ Alice (creator) ─────────────────────── Bob, Carol ─┐146147 [1. CREATE — Alice generates group and her sender chain]148149 Alice: senderChainKey_A = random(32 bytes)150151 [2. INVITE — Sent via per-peer Double Ratchet channel]152153 Alice ──── GroupInvite(groupID, senderChainKey_A) ────→ Bob154 Alice ──── GroupInvite(groupID, senderChainKey_A) ────→ Carol155156 [3. JOIN — Each member generates their own sender chain]157158 Bob: senderChainKey_B = random(32 bytes)159 Carol: senderChainKey_C = random(32 bytes)160161 Bob ──── SenderKeyDistribution(senderChainKey_B) ────→ Alice, Carol162 Carol ──── SenderKeyDistribution(senderChainKey_C) ────→ Alice, Bob163164 [4. GROUP MESSAGE — Each sender uses their own KDF chain]165166 messageKey_n = HMAC-SHA256(chainKey_n, 0x01)167 chainKey_{n+1} = HMAC-SHA256(chainKey_n, 0x02)168169 Alice ──── GroupMsg(ciphertext, iteration=n) ────→ Bob, Carol170 (broadcast via mesh)171172└──────────────────────────────────────────────────────┘173```174175### Key Properties176177-**Forward secrecy** — each message uses a unique message key. Compromising key `n` reveals nothing about keys `< n`.178-**Break-in recovery** — after a compromise, DH ratchet steps generate fresh key material from new Curve25519 ephemeral keys. Each group member ratchets independently.179-**Header encryption** — ratchet headers (public key, counters) are sealed with rotating header keys. Relay nodes see only opaque ciphertext.180-**Sealed sender** — sender identity is hidden from relay nodes; only the intended recipient can learn who sent the message.181-**Multihop relay** — messages flood through the mesh with TTL=6, deduplicated via LRU cache.182-**Offline queue** — messages queued in memory when no peers are reachable; drained automatically on reconnect.183-**Disappearing messages** — optional `expiresAt` timestamp; messages purged locally every 60 seconds. Supported in both 1:1 and group conversations.184185---186187## Cryptography188189All primitives come from Apple's **[CryptoKit](https://developer.apple.com/documentation/cryptokit)** — hardware-accelerated and independently audited by Apple.190191| Layer | Operation | Algorithm | Key Size |192|---|---|---|---|193| Identity | Signing | Ed25519 | 256-bit |194| Identity | Key agreement | X25519 | 256-bit |195| Session init | Key exchange | X3DH | — |196| Session init | Key derivation | HKDF-SHA256 | 256-bit |197| Messaging | Symmetric ratchet | HMAC-SHA256 | 256-bit |198| Messaging | DH ratchet | X25519 | 256-bit |199| Messaging | AEAD encryption | ChaCha20-Poly1305 | 256-bit |200| Header encryption | AEAD | ChaCha20-Poly1305 | 256-bit |201| Sealed sender | Key derivation | HKDF-SHA256 | 256-bit |202| Sealed sender | AEAD | ChaCha20-Poly1305 | 256-bit |203| Group Sender Keys | KDF chain | HMAC-SHA256 | 256-bit |204| Group encryption | AEAD | ChaCha20-Poly1305 | 256-bit |205| Storage | At-rest encryption | AES-256-GCM | 256-bit |206| Identity verification | Safety numbers | SHA-512 | — |207208### Key Storage209210All private keys and session states are stored in the **iOS Keychain** with `kSecAttrAccessibleWhenUnlockedThisDeviceOnly`:211212- Not backed up to iCloud213- Not transferred to a new device214- Not accessible when the device is locked215- Wiped on device factory reset216217---218219## Features220221### Messaging222223| Feature | 1:1 | Group |224|---|:---:|:---:|225| Text messages | ✅ | ✅ |226| Image sharing | ✅ | ✅ |227| Voice messages (PTT, AAC M4A) | ✅ | ✅ |228| Reply to message (quoted bubble) | ✅ | ✅ |229| Message reactions (6-emoji) | ✅ | ✅ |230| Read receipts (blue tick) | ✅ | ✅ |231| Disappearing messages (30s–7d) | ✅ | ✅ |232| Forward message | ✅ | ✅ |233| Message search | ✅ | ✅ |234| Draft persistence (restored on re-open) | ✅ | ✅ |235236### Groups237238| Feature | Status |239|---|---|240| Create group (multi-peer picker) | ✅ |241| Signal-style Sender Keys (per-member KDF chain) | ✅ |242| MLS (RFC 9420) groups — default on iPhone/iPad/Mac when all members have exchanged MLS keys; otherwise Sender Keys | ✅ |243| MLS on Android | ❌ (Android groups use Sender Keys only) |244| Group invite via Double Ratchet channel | ✅ |245| Sender Key Distribution to all members | ✅ |246| Per-message forward secrecy (per-member) | ✅ |247| Leave group | ✅ |248| Member list sheet | ✅ |249| Group push notifications | ✅ |250| Out-of-order message recovery (skipped key cache, 200 keys/sender) | ✅ |251| Re-keying on member leave (forward secrecy) | ✅ |252253### Security & Privacy254255| Feature | Status |256|---|---|257| X3DH session establishment | ✅ |258| Double Ratchet + Header Encryption | ✅ |259| Sealed sender | ✅ |260| Ed25519 message signatures | ✅ |261| Safety Number verification (QR + manual) | ✅ |262| App lock (Face ID / Touch ID / passcode) | ✅ |263| Auto-lock on background | ✅ |264| App Switcher blur | ✅ |265| AES-256-GCM at-rest message storage | ✅ |266| Block peer | ✅ |267| TOFU key-change warning (Safety Number changed banner) | ✅ |268| Notification content hiding on lock screen | ✅ |269| Clipboard auto-clear (60s after copy) | ✅ |270| Keyboard privacy (autocorrect disabled, no learning) | ✅ |271| Screen recording warning banner | ✅ |272| Screenshot notification toast | ✅ |273| Screenshot prevention (screen appears blank) | ✅ |274| Duress PIN — decoy empty state on coercion | ✅ |275| Custom numeric lock PIN (alternative to biometrics) | ✅ |276| Full encrypted export — identity + messages (`.sxfe`) | ✅ |277| File sharing (arbitrary files ≤ 2 MB) | ✅ |278279### Global Reach (Tor)280281| Feature | Status |282|---|---|283| `.onion` address derived from identity key (no configuration) | ✅ |284| Orbot SOCKS5 support (route through Tor via proxy config) | ✅ |285| Contact Card — QR code + `sophaxchat://` deep link | ✅ |286| QR scanner for adding contacts | ✅ |287| `sophaxchat://add` deep link handling | ✅ |288| Tor onboarding (first-run guide) | ✅ |289290### Local AI Assistant291292| Feature | Status |293|---|---|294| On-device AI (Apple Foundation Models, iOS 26+) | ✅ |295| No server, no API key, no data leaves device | ✅ |296| Conversation memory within session | ✅ |297| Graceful fallback on unsupported devices | ✅ |298299### Network & Transport300301| Feature | Status |302|---|---|303| Bluetooth LE transport (iOS) | ✅ |304| WiFi Direct transport (iOS / Android) | ✅ |305| LAN auto-discovery via mDNS/Bonjour (same WiFi) | ✅ |306| iOS ↔ Android cross-platform messaging (TCP wire protocol) | ✅ |307| TCP internet transport (peer-to-peer, no server) | ✅ |308| Tor / SOCKS5 anonymity (Orbot VPN mode or proxy) | ✅ |309| GMS-free Android (Wi-Fi Direct fallback, no Google Play Services) | ✅ |310| Multihop relay (TTL=6) | ✅ |311| LRU relay deduplication | ✅ |312| Offline message queue | ✅ |313| Rate limiting (20 relays / 10s per peer + 50 / 10s global cap) | ✅ |314| Relay hop indicator in UI | ✅ |315| Typing indicators | ✅ |316| Store-and-forward via relay peers (48h TTL, 300 items) | ✅ |317| macOS Catalyst support | ✅ |318319---320321## Architecture322323```324SophaxChat/325├── Sources/SophaxChatCore/ # Core library — pure Swift, testable326│ ├── Crypto/327│ │ ├── CryptoTypes.swift # Types, constants, error definitions328│ │ ├── KeychainManager.swift # Keychain CRUD for all key material329│ │ ├── IdentityManager.swift # Ed25519 + X25519 identity lifecycle330│ │ ├── PreKeyManager.swift # X3DH prekey pool (SPK + 20 OTPKs)331│ │ ├── X3DH.swift # X3DH sender and receiver implementation332│ │ ├── DoubleRatchet.swift # Double Ratchet + Header Encryption (Signal spec §4.3)333│ │ ├── Keccak.swift # Minimal SHA3-256 (Tor v3 .onion checksum)334│ │ └── OnionAddress.swift # Tor v3 .onion derivation from Ed25519 key335│ ├── Group/336│ │ ├── GroupTypes.swift # GroupInfo, SenderKeyState, GroupCryptoVersion337│ │ ├── MLSGroupManager.swift # MLS actor — wraps mls-rs via UniFFI338│ │ ├── ChatManager+MLS.swift # MLS inbound/outbound handlers + dispatch router339│ │ └── GroupCryptoMigration.swift # Opt-in SKv2 → MLS migration scaffold340│ ├── MLS/341│ │ └── Generated/342│ │ └── sophax_mls.swift # Auto-generated UniFFI Swift bindings (mls-rs)343│ ├── Network/344│ │ ├── NetworkProtocol.swift # Wire message types + WireMessageBuilder345│ │ ├── MeshManager.swift # MultipeerConnectivity P2P transport (BLE/WiFi)346│ │ ├── TCPTransport.swift # Internet TCP transport (SOCKS5/Tor, port 25519)347│ │ └── RelayRouter.swift # Multihop relay with LRU dedup cache348│ ├── Storage/349│ │ ├── MessageStore.swift # AES-256-GCM encrypted at-rest storage350│ │ └── AttachmentStore.swift # Encrypted blob store for images/audio351│ └── ChatManager.swift # High-level coordinator (session + routing)352│353├── rust/ # mls-rs Rust crate (compiled to XCFramework via UniFFI)354│ ├── src/lib.rs # Stateless MLS API: create/add/remove/encrypt/decrypt355│ └── Cargo.toml # mls-rs 0.54, RustCrypto backend (no OpenSSL)356│357├── scripts/358│ └── build_mls_xcframework.sh # Cross-compile Rust → XCFramework for iOS/macOS359│360├── SophaxChat/ # iOS/macOS SwiftUI application361│ ├── App/362│ │ ├── SophaxChatApp.swift # App entry point, security overlays, deep links363│ │ └── AppState.swift # @MainActor observable state, SOCKS5/Tor config364│ └── Views/365│ ├── Onboarding/ # First-run username setup366│ ├── AI/ # Local AI assistant (Apple Foundation Models)367│ ├── Chat/ # Chat list, 1:1 and group bubbles, relay indicator368│ └── Settings/ # Safety number, app lock, Contact Card, Tor setup369│370└── Tests/SophaxChatCoreTests/371 ├── CryptoTests.swift # X3DH symmetry + Double Ratchet correctness372 └── MLSGroupManagerTests.swift # MLS two-party, three-party, removal, epoch tests373```374375> **macOS:** The app runs on macOS 14+ via Catalyst. `AVAudioSession` calls are guarded with `#if !targetEnvironment(macCatalyst)`. Peer discovery works over WiFi on macOS.376377### Message Encryption Layers378379Every message passes through four independent encryption layers before leaving the device:380381```mermaid382sequenceDiagram383 autonumber384 participant A as Alice 📱385 participant R as Relay node 📡386 participant B as Bob 📱387388 Note over A,B: Session setup — once per contact389 A->>B: Hello (PreKeyBundle: IK, SPK, OTPKs, avatar)390 B->>B: X3DH: DH₁⊕DH₂⊕DH₃⊕DH₄ → RootKey → Double Ratchet init391392 Note over A,B: Every message — 4 layers applied on Alice's device393 rect rgb(20, 30, 48)394 Note over A: ① Double Ratchet — new symmetric key every message (forward secrecy)395 Note over A: ② Header Encryption — sequence numbers and ratchet keys sealed396 Note over A: ③ Sealed Sender — Alice's identity hidden from all relay nodes397 Note over A: ④ RelayEnvelope — TTL=6, LRU dedup, multihop routing398 end399400 A->>R: Encrypted blob — relay sees ciphertext + TTL only401 Note over R: Cannot read: sender identity, recipient identity, or content402 R->>B: TTL decremented, rebroadcast unchanged403 B->>B: Unseal Sealed Sender → DR+HE decrypt → plaintext ✓404```405406### Data Flow407408```409SwiftUI View410 │ sendMessage() / sendGroupMessage()411 ▼412AppState (@MainActor)413 │414 ▼415ChatManager ← single coordinator, NSLock session mutex416 │417 ├─ 1:1 path418 │ ├─ buildOutboundWire() ← X3DH init (new session) or DR+HE encrypt419 │ ├─ sealedSenderWrap() ← ECDH(ephemeral, recipientDH) → ChaChaPoly420 │ └─ sendOrQueue()421 │ ├─ tcp.send() if peer connected via TCP/internet422 │ ├─ mesh.send() if peer directly connected (BLE/WiFi)423 │ ├─ mesh.broadcast() relay via RelayEnvelope (TTL=6)424 │ └─ pendingQueue[] offline; drained on reconnect425 │426 ├─ group path427 │ ├─ senderKeyRatchetStep() ← HMAC-SHA256(chainKey, 0x01/0x02)428 │ ├─ ChaChaPoly encrypt ← with per-message key429 │ └─ mesh.broadcast() ← GroupWireMessage to all members430 │431 └─ didReceiveMessage()432 ├─ .hello → store PreKeyBundle, drain queue433 ├─ .initiateSession → X3DH receiver, DR init, decrypt434 ├─ .message → DR+HE decrypt, send ACK435 ├─ .ack / .readReceipt → update message status436 ├─ .relay → process inner or forward (TTL-1)437 ├─ .groupInvite → store group, generate sender chain, distribute SKD438 ├─ .groupMessage → v2: ratchet peer chain; v1: shared key fallback439 └─ .senderKeyDistribution → store peer sender chain state440```441442---443444## Android445446The Android port uses the same wire protocol as iOS. Messages between iOS and Android are fully interoperable — same X3DH + Double Ratchet encryption, same JSON framing over TCP port 25519.447448**Auto-discovery on the same WiFi:** iOS (Bonjour) and Android (NsdManager) both advertise `_sophaxchat._tcp`. They find each other automatically — no manual pairing, no IP addresses.449450**GMS-free:** Works on GrapheneOS, CalyxOS, and any AOSP device. Google Play Services are detected at runtime; if absent, Wi-Fi Direct (`android.net.wifi.p2p.*`) is used instead. No GMS imports in the codebase.451452**Feature parity with iOS:** The Android UI matches the iOS app on all key features — App Lock (BiometricPrompt), unread badges, typing indicators, message long-press menu (copy / delete / block / reply), reply-to-message with quoted preview, Safety Number screen, deep link confirmation dialog, onboarding slides, message reactions (6-emoji picker + reaction pills), disappearing messages (per-conversation timer, auto-cleanup), contact renaming, and per-conversation message search.453454See [`android/README.md`](android/README.md) for build instructions and architecture.455456---457458## Getting Started459460### Requirements461462| Requirement | Version |463|---|---|464| Xcode | 16.0+ |465| iOS deployment target | 17.0+ |466| macOS deployment target | 14.0+ (Catalyst) |467| Physical devices | 2× iPhone (MultipeerConnectivity requires real hardware) |468| XcodeGen | latest (`brew install xcodegen`) |469470> **Note:** The Swift core library (`SophaxChatCore`) builds without Xcode via `swift build`. Physical devices are required to test P2P connectivity; the simulator does not support MultipeerConnectivity peer discovery.471472### Run on iPhone473474```sh475# 1. Clone476git clone https://github.com/richardmetzner/SophaxChat.git477cd SophaxChat478479# 2. Generate Xcode project480brew install xcodegen481xcodegen generate482483# 3. Open in Xcode484open SophaxChat.xcodeproj485```486487In Xcode:4881. Select your **Team** under Signing & Capabilities4892. Plug in your iPhone, select it as the destination4903. **⌘R**491492Repeat on a second device to test messaging.493494### Run on Mac (Catalyst)495496The app runs natively on macOS via Mac Catalyst — no iOS simulator needed.497498```sh499xcodegen generate500open SophaxChat.xcodeproj501```502503In Xcode:5041. Select target **SophaxChat** → tab **General** → under **Deployment Info** enable the **Mac** checkbox → choose **Mac Catalyst**5052. Set destination to **My Mac (Mac Catalyst)** in the toolbar5063. Signing & Capabilities → select your Team (free Apple ID works)5074. **⌘R**508509> **Free Apple ID:** Mac Catalyst apps run on your own Mac without any developer account. No 7-day re-signing limit (unlike iOS sideloading).510511**What works on Mac:**512- Full encrypted messaging (1:1 and group)513- Global reach via TCP + Tor (Orbot for Mac)514- Local AI assistant (macOS 26 + Apple Intelligence)515- QR Contact Cards (share / receive)516- All security features (App Lock uses Touch ID on Mac)517518**What differs on Mac:**519- Peer discovery uses WiFi (no Bluetooth mesh on macOS Catalyst)520- Screen recording warning is disabled (screen recording is normal OS behavior on Mac)521- Camera picker uses the Mac camera522523### Run on iPhone without an Apple Developer account (AltStore)524525You can sideload SophaxChat on iPhone for free using [AltStore](https://altstore.io) — no Apple Developer account required.5265271. Install **AltStore** on your Mac and iPhone ([altstore.io](https://altstore.io))5282. Clone + generate the Xcode project (steps above)5293. In Xcode: `Product → Archive` (select **Any iOS Device** as destination)5304. In the Organizer: **Distribute App → Save for Ad Hoc Deployment** → save the `.ipa`5315. In AltStore on your Mac: drag-and-drop the `.ipa` onto your iPhone5326. AltStore re-signs automatically every 7 days when AltServer is running on your Mac533534> **Free Apple ID limit:** you can have 3 sideloaded apps active at a time. AltStore itself counts as one.535536### Verify the core library (no Xcode needed)537538```sh539swift build540```541542### Tests543544```sh545# Run in Xcode: Product → Test (⌘U)546```547548Tests cover: X3DH key symmetry (with/without OTPK), Double Ratchet bidirectional messaging, out-of-order message delivery, session state persistence, associated data binding, and KDF distinctness.549550---551552## Security553554See [SECURITY.md](SECURITY.md) for the full threat model, cryptographic primitive table, protocol details, and security review findings.555556### What SophaxChat protects against557558| Threat | Protection |559|---|---|560| Network eavesdropping | ChaCha20-Poly1305 E2EE; MPC transport encrypted (`.required`); TCP is a carrier of already-sealed WireMessages |561| MITM / impersonation | Ed25519 signatures on every message; Safety Number verification |562| Replay attacks | Unique nonce per message; message-ID deduplication at storage layer |563| Past message compromise | Forward secrecy via symmetric ratchet (per-message keys) |564| Future message compromise | Break-in recovery via DH ratchet (fresh Curve25519 ephemeral keys) |565| Header metadata leakage | Header Encryption — relay nodes see only opaque ciphertext |566| Sender identity leakage | Sealed sender — sender hidden from relay nodes via ECDH wrapping |567| Data at rest | AES-256-GCM encrypted message and attachment store |568| iCloud backup exfiltration | `kSecAttrAccessibleWhenUnlockedThisDeviceOnly`; keys never backed up |569| App Switcher screenshot | Content blurred on `willResignActive` |570| Unauthorized app access | App lock: Face ID / Touch ID / passcode, auto-lock on background |571| DoS via oversized messages | Attachment ≤ 512 KB; username ≤ 64 chars |572| Prekey exhaustion | OTPK pool auto-replenished when below 50%; SPK rotated every 7 days |573574### Identity verification575576Each user has a **Safety Number** — a 60-digit fingerprint derived from SHA-512 of their identity keys. Both numbers are shown side by side in the verification screen: each party reads their own number aloud to the other. Scan via QR code or compare manually.577578### Known limitations579580See [SECURITY.md § Security Review Findings](SECURITY.md) for full details. Key items:581582| Finding | Severity | Summary |583|---|---|---|584| ~~H-1~~ | ~~HIGH~~ | ~~No group re-keying on member leave~~ — **fixed**: fresh sender chain rotated on every membership change |585| ~~M-1~~ | ~~MEDIUM~~ | ~~Group skipped message keys not cached~~ — **fixed**: bounded cache (200 keys/sender) with auto-eviction |586| M-2 | MEDIUM | Sender Key Distribution may arrive after first messages in high-load scenarios |587| M-3 | MEDIUM | One-time prekey exhaustion window reduces X3DH entropy temporarily |588| ~~M-4~~ | ~~MEDIUM~~ | ~~Notification previews may expose content on lock screen~~ — **fixed**: `hiddenPreviewsBodyPlaceholder` registered |589| ~~A-1~~ | ~~MEDIUM~~ | ~~Sender Key Distribution accepts iteration resets (DoS)~~ — **fixed**: monotonicity check rejects backward iteration |590| ~~A-2~~ | ~~MEDIUM~~ | ~~Inner relay message signature not verified~~ — **fixed**: Ed25519 verification at relay dispatch entry |591| ~~A-3~~ | ~~LOW~~ | ~~Global relay rate limit missing~~ — **fixed**: 50-relay / 10s global cap across all senders |592| ~~A-4~~ | ~~LOW~~ | ~~TCP connections held indefinitely on slow peers~~ — **fixed**: 120s idle timeout per connection, reset on each frame |593| ~~A-5~~ | ~~LOW~~ | ~~Deep link triggers immediate TCP connect~~ — **fixed**: confirmation dialog required on both iOS and Android |594| ~~A-6~~ | ~~LOW~~ | ~~App Lock leaves notifications visible~~ — **fixed**: `removeAllDeliveredNotifications()` on lock |595| ~~S3-A1~~ | ~~HIGH~~ | ~~Android port parsing without bounds check~~ — **fixed**: `toIntOrNull()?.takeIf { it in 1..65535 }` |596| ~~S3-A2~~ | ~~HIGH~~ | ~~Tor v3 onion address validated only by `.endsWith(".onion")`~~ — **fixed**: strict regex `^[a-z2-7]{56}\.onion$` |597| ~~S3-I1~~ | ~~HIGH~~ | ~~X3DH dhConcat not zeroed after key derivation~~ — **fixed**: `initializeMemory(as: UInt8.self, repeating: 0)` |598| ~~S3-A3~~ | ~~MEDIUM~~ | ~~Android app SharedPreferences stored in plaintext~~ — **fixed**: migrated to `EncryptedSharedPreferences` |599| ~~S3-A4~~ | ~~MEDIUM~~ | ~~Double Ratchet message key `mk` not zeroed after use~~ — **fixed**: `mk.fill(0)` after encrypt/decrypt |600| ~~S3-A5~~ | ~~MEDIUM~~ | ~~Android clipboard no auto-clear~~ — **fixed**: 60s delayed clear matching iOS |601| ~~S3-I2~~ | ~~MEDIUM~~ | ~~X3DH dhConcat length not validated~~ — **fixed**: `precondition(count == 96 \|\| count == 128)` |602| ~~S3-A6~~ | ~~LOW~~ | ~~Android exception messages exposed to user~~ — **fixed**: generic user-facing strings, debug-only `Log.e` |603| ~~S3-I3~~ | ~~LOW~~ | ~~`assert()` in IdentityManager stripped in Release builds~~ — **fixed**: replaced with `precondition()` |604605### Responsible Disclosure606607Do not open public issues for security bugs.608609---610611## Roadmap612613### Recently shipped614615- [x] Friendly invite code — `SXPH-XXXX-XXXX` fingerprint in ContactCard + paste-link flow; no server616- [x] Mesh relay via Tor — relay envelopes forwarded over TCP/Tor peers, extends reach beyond local BLE/WiFi617- [x] Avatar sync for group-only contacts — eager push in Hello handler, no DM required618- [x] On-device diagnostic log — rotating local log, voluntary export from Settings; zero telemetry619- [x] Duress mode — custom PIN shows empty decoy app; silently drops incoming messages620- [x] Full export (`.sxfe`) — identity + all messages in one PBKDF2-encrypted file621- [x] Sealed sender for groups — per-member SKv2 fan-out wrapped in ECDH envelope622- [x] File sharing — arbitrary file attachments (≤2 MB) in DMs and groups623624<details>625<summary>Full completed list (50+ items)</summary>626627- [x] X3DH session establishment628- [x] Double Ratchet + Header Encryption629- [x] Sealed sender (hides sender from relay nodes)630- [x] Multihop relay (TTL=6, LRU dedup)631- [x] Offline message queue + store-and-forward (48h TTL, 300 items)632- [x] Disappearing messages (30s–7d, 1:1 and group)633- [x] SPK rotation (7-day) + OTPK replenishment634- [x] Safety Numbers (QR + manual)635- [x] Rate limiting (20 relays/10s per peer, 50/10s global)636- [x] Session initiation deduplication637- [x] Voice messages PTT (AAC M4A, encrypted)638- [x] Image sharing (encrypted, PhotosPicker + camera)639- [x] Reply, forward, search, reactions, read receipts640- [x] App lock (Face ID / Touch ID / passcode + custom PIN)641- [x] Group messaging — Signal-style Sender Keys v2 (per-member KDF chains)642- [x] Group re-keying on member leave643- [x] Group skipped message key cache (200 keys/sender)644- [x] macOS Catalyst support645- [x] TOFU key-change detection646- [x] Channel discovery (nearby groups broadcast)647- [x] Tor global reach — `.onion` v3 from Ed25519 identity key648- [x] Contact Card — QR + `sophaxchat://add` deep link649- [x] Orbot SOCKS5 support (route TCP traffic through Tor proxy)650- [x] Clipboard auto-clear (60s)651- [x] Keyboard privacy (autocorrect disabled)652- [x] Screen recording warning + screenshot toast653- [x] Local AI assistant (Apple Foundation Models, iOS 26+)654- [x] Background BLE operation (`BGAppRefreshTask`)655- [x] Pluggable transport adapter (`MessageTransport` protocol)656- [x] Android feature parity — App Lock, reactions, disappearing messages, safety numbers, search657- [x] PrivacyInfo.xcprivacy (App Store privacy manifest)658659</details>660661### Up next662663- [ ] **TestFlight public beta** — requires an Apple Developer Program membership664- [ ] **Independent third-party security audit** — highest-priority before v1.0. Open a [GitHub Security Advisory](https://github.com/richardmetzner/SophaxChat/security/advisories/new) if you are interested in auditing665- [ ] **DHT peer discovery** — opt-in BitTorrent DHT announce of `.onion` address; no server, no IP leak666- [ ] **Hardware security key binding** — Secure Enclave identity key + FIDO2 external key (post-audit)667668---669670## Contributing671672Pull requests are welcome. Before contributing, read [PHILOSOPHY.md](PHILOSOPHY.md) — contributors agree to its principles by submitting a PR.6736741. Read [SECURITY.md](SECURITY.md) — especially if touching crypto code6752. Open an issue to discuss significant changes before writing code6763. All cryptographic changes require a corresponding test in `CryptoTests.swift`6774. Run `swift build` before submitting678679### Areas where help is especially welcome680681-**UI/UX** — the interface is functional, not polished682-**Tests** — relay dedup, group sender key, session initiation edge cases683-**Localization** — the app currently ships in English only684-**LoRa / audio transport** — implement `MessageTransport` for LoRa radio or near-ultrasonic audio channel (see `Network/MessageTransport.swift`)685686---687688<!-- Support section — hidden until BTC/XMR addresses are configured689## Support690691SophaxChat is free, open-source, and always will be. No ads, no subscriptions, no data collection — ever.692693If you find it useful, you can support continued development anonymously:694695| Currency | Address |696|----------|---------|697| **Bitcoin (BTC)** | `YOUR_BTC_ADDRESS_HERE` |698| **Monero (XMR)** | `YOUR_XMR_ADDRESS_HERE` |699700Monero is recommended — it's privacy-preserving by design, consistent with everything this project stands for.701702You can also ⭐ star the repo, share it, or submit a pull request.703-->704705---706707## Privacy708709SophaxChat collects no data. See [PRIVACY.md](PRIVACY.md) for the full policy (also usable as App Store privacy policy URL).710711---712713## License714715MIT — see [LICENSE](LICENSE).716717```718Copyright (c) 2026 SophaxChat contributors719720Permission is hereby granted, free of charge, to any person obtaining a copy721of this software and associated documentation files (the "Software"), to deal722in the Software without restriction, including without limitation the rights723to use, copy, modify, merge, publish, distribute, sublicense, and/or sell724copies of the Software, and to permit persons to whom the Software is725furnished to do so, subject to the following conditions:726727The above copyright notice and this permission notice shall be included in all728copies or substantial portions of the Software.729```730731---732733<div align="center">734 <sub>Built with ❤️ and paranoia. No servers were harmed in the making of this app.</sub>735</div>