Test Chat App is a local-first encrypted chat prototype with a Swift macOS client and a Rust Axum backend. The backend is a zero-knowledge relay over SQLite: it authenticates users, routes direct and group messages, stores encrypted attachments, and never receives plaintext message or file content.
| Path | Purpose |
|---|---|
backend/ |
Rust Axum HTTP/SSE relay, SQLite migrations, backend tests, and maintenance scripts. |
mac-app/ |
Swift Package Manager macOS app, app state, crypto, services, views, and tests. |
docs/ |
Review notes and reproducible flows, including attachment rendering and zero-knowledge audits. |
verify.sh |
Root verification entrypoint; delegates to the AgentLoop verification script. |
Swift macOS app
|-- Keychain: identity private key, X25519 private key, session token
|-- Application Support/ChatApp: account.json, sync-cursors.json
|-- MessageCrypto / GroupCrypto / FileCrypto encrypt before upload
v
HTTP + SSE at http://127.0.0.1:3000
v
Rust Axum backend
|-- auth, registration, keys, messages, groups, attachments, health routes
|-- stores routing metadata, public keys, sessions, and opaque ciphertext
v
SQLite database
The app runtime is fixed to http://127.0.0.1:3000 for the production macOS client services. Live Swift tests can target another backend with CHATAPP_LIVE_BACKEND_URL.
The backend API surface includes GET /health, registration, auth challenge/verify/session, key publication and lookup, direct-message history/inbox/SSE, conversation summaries, group management/messages/SSE, and encrypted attachment upload/download routes.
- Registration creates or loads a local identity key from Keychain, sends only the username and public identity key, and stores local account state under Application Support.
- Sign-in uses a backend challenge and a client-side identity signature; the session token is stored in Keychain.
- Direct messages use
MessageCryptoon the sender device, then the backend stores and relays only ciphertext plus sender, recipient, id, and timestamp metadata. - Groups use
GroupCryptoto encrypt group messages and wrap group key material for members; the backend stores group membership metadata and ciphertext. - Attachments use
FileCryptobefore upload. Filename, MIME type, file key, and size travel in encrypted message descriptors; the backend stores opaque encrypted bytes and routing metadata. - Conversation updates use backend HTTP history endpoints plus SSE live streams.
- macOS 13 or newer for the SwiftUI app target.
- Swift 6 toolchain.
- Rust stable toolchain with Cargo.
bash,curl,sqlite3, and standard Unix command-line tools for scripts and verification.
Backend:
cd backend
cargo build
cargo testmacOS app:
cd mac-app
swift build
swift test
swift run ChatAppRunnerChatAppRunner is the SwiftPM command-line runner. The Xcode scheme and app bundle remain named ChatApp.
To run the macOS client as a real app bundle from Xcode:
open mac-app/ChatApp.xcodeprojSelect the shared ChatApp scheme, select the My Mac destination, and press Run. The app window renders the normal registration, sign-in, or authenticated chat screen.
You can also build the Debug app bundle from the command line:
cd mac-app
xcodebuild -project ChatApp.xcodeproj -scheme ChatApp -configuration Debug -destination 'platform=macOS' buildFor a Release app bundle suitable for local evaluation:
cd mac-app
rm -rf /tmp/chatapp-xcode-release
xcodebuild -project ChatApp.xcodeproj -scheme ChatApp -configuration Release -destination 'platform=macOS' -derivedDataPath /tmp/chatapp-xcode-release build
open /tmp/chatapp-xcode-release/Build/Products/Release/ChatApp.appThe repository gate intentionally skips live backend XCTest cases unless explicitly opted in:
cd mac-app
env -u CHATAPP_LIVE_BACKEND_URL swift testRun the full root verification flow from the repository root:
./verify.shFrom the repository root:
cd backend
cargo run -- --db-path ./backend.sqlite3 --port 3000From another terminal, check that the backend is reachable:
curl -i http://127.0.0.1:3000/healthThe environment-variable equivalent is:
cd backend
DATABASE_PATH=./backend.sqlite3 PORT=3000 cargo runStart the backend on port 3000 first, then run the SwiftPM runner:
cd mac-app
swift run ChatAppRunnerOr open mac-app/ChatApp.xcodeproj, choose the ChatApp scheme and My Mac, then press Run. ChatAppRunner is only the SwiftPM runner command; the Xcode scheme and app bundle remain ChatApp. The app talks to the fixed runtime URL http://127.0.0.1:3000, so start the backend on port 3000 before testing registration, sign-in, or chat messaging against a live relay. The app window should still render its initial route without the backend.
For live Swift tests that need an explicit backend URL:
cd mac-app
CHATAPP_LIVE_BACKEND_URL=http://127.0.0.1:3000 swift testFor the attachment rendered-flow demo:
docs/run-attachments-rendered-flow-demo.shSee also:
docs/zero-knowledge-relay.mddocs/attachments-rendered-flow.mddocs/run-attachments-rendered-flow-demo.sh
| Name | Kind | Applies to | Default | Notes |
|---|---|---|---|---|
DATABASE_PATH |
Environment variable | Backend | backend.sqlite3 |
SQLite file path relative to the backend process current working directory; parent directories are created. |
PORT |
Environment variable | Backend | 3000 |
HTTP/SSE listen port. |
--db-path |
CLI flag | Backend | Overrides DATABASE_PATH |
Example: cargo run -- --db-path /tmp/chat.sqlite3. |
--port |
CLI flag | Backend | Overrides PORT |
Use 3000 for the current macOS app runtime. |
CHATAPP_LIVE_BACKEND_URL |
Environment variable | Swift tests | unset | When set, live E2E XCTest cases run against that backend; when unset, they skip. |
| none | Fixed runtime URL | macOS app | http://127.0.0.1:3000 |
Current HTTP services default to this URL. |
Optional artifact-output environment variables used by task gates are not required for normal local development.
The trusted boundary is the macOS client device. Private identity and X25519 keys live in Keychain; local account and sync cursor files live under the user's Application Support ChatApp directory. The backend is treated as an untrusted zero-knowledge relay: it can see routing metadata, public keys, sessions, timestamps, ids, and ciphertext sizes, but not direct-message plaintext, group-message plaintext, attachment contents, or private keys.
MessageCrypto uses X25519 key agreement, HKDF-SHA256, AES-GCM, and a base64 envelope containing the version, ephemeral public key, and sealed bytes. GroupCrypto uses per-epoch symmetric group keys, member-specific wrapped keys, and AES-GCM message envelopes. FileCrypto uploads AES-GCM encrypted bytes as opaque attachment blobs.
These components enforce the end-to-end encryption boundary before data reaches the Rust backend. The backend database schema intentionally stores ciphertext blobs and routing fields, not plaintext content columns. This protects content, not anonymity or metadata: the backend still sees account identifiers, usernames, message ids, group ids, membership, routing and timing metadata, public identity keys, public prekeys, signatures, valid bearer tokens, and ciphertext sizes. The detailed audit is in docs/zero-knowledge-relay.md.
-
Port 3000 already in use: remove stale orphaned backend processes, then restart the backend.
bash backend/scripts/reap_stale_backends.sh
-
Xcode Run fails with
The local path is "(null)"andLLDB reports that it doesn't exist: themac-apppackage folder was opened instead of the Xcode project. The package workspace auto-generates a scheme also namedChatApp, but it is the library target and has no executable to launch. Openmac-app/ChatApp.xcodeprojand run its sharedChatAppscheme, or use theChatAppRunnerscheme when working from the package workspace. -
App opens with an old account or cursor state: clear the local
ChatAppfiles under the user's Application Support directory. -
App keeps using an old identity, X25519 key, or session: clear the related
com.testchatapp.identitygeneric-password items from Keychain. -
Live Swift tests skip: set
CHATAPP_LIVE_BACKEND_URLto a running backend URL.