Self-hosted, end-to-end encrypted 1:1 chat app for iOS (Android client WIP), built on Firebase. No central operator reads your messages or media - encryption keys are derived on-device via an offline QR handshake and never leave the device.
Swift
0
18 commits
updated Sep 21, 2026
A self-hosted, end-to-end encrypted 1:1 chat app for iOS, built on Firebase (Auth, Firestore, Storage). No central operator reads your messages or media — encryption keys are derived on-device via an offline QR handshake and never leave the device.
This project is open source (MIT) for anyone who wants to run their own instance. There is no shared/hosted "walkytalky service" — every deployer stands up their own Firebase project and owns their own data.
Identity & access
Messaging
WKWebView with a non-persistent data store) instead of Safari — cookies, cache, and history live only in memory for that one page view and are never written to disk or shared with the system's Safari historyBGTaskScheduler), posting a local notification — always the fixed text "You have a new message," never a preview, since this check never decrypts anything. Deliberately not real push: Push Notifications is one of the capabilities Apple gates behind a paid Apple Developer Program membership, which would contradict this project's free-Apple-ID self-hosting promise below. The tradeoff is real — iOS gives this no guaranteed schedule (opportunistic, can be delayed hours, and is cancelled if the user force-quits the app) — see Known limitationsMedia
UI
conversations/{id}, conversations/{id}/messages/{id}, conversations/{id}/displaynames/{uid}, users/{uid}), Firebase Storage for encrypted media blobs.HMAC<SHA256> since CryptoKit has no PBKDF2 API of its own).firebase/SecurityRules/firestore.rules and storage.rules scope every read/write to real participant membership, verified server-side — never trusting client-supplied claims. See the rules files themselves for the exact logic; they're deliberately readable.If you're building another client against the same backend, these are the exact primitives and byte layouts messages must match to interoperate:
{"conversationId": "<string>", "publicKey": "<base64, raw 32-byte X25519 public key>"}HKDF-SHA256(ECDH(myPrivateKey, peerPublicKey), salt: UTF-8(conversationId), info: UTF-8("walkytalky-conversation-key"), length: 32 bytes)nonce field; the uploaded/stored payload (Storage blob for media, text field for text) is ciphertext || 16-byte GCM tag concatenated, base64-encoded for Firestore fields.Self-hosting means standing up your own Firebase project — there's no separate server to run. Everyone who wants their own instance does this once.
npm install -g firebase-tools)com.withpreet.walkytalky — change this to something you own before building for a real device, since bundle IDs must be unique per Apple Developer team).GoogleService-Info.plist and place it at ios/walkytalky/walkytalky/GoogleService-Info.plist (this path is already gitignored — never commit it).cd firebase
firebase login
Edit .firebaserc and replace the project ID with your own:
{
"projects": {
"default": "your-project-id"
}
}
firebase deploy --only firestore:rules,storage
Read firebase/SecurityRules/firestore.rules and storage.rules first — they're the actual access-control boundary for your data. Don't deploy rules you haven't read.
ios/walkytalky/walkytalky.xcodeproj in Xcode.walkytalky target → Signing & Capabilities → set your own Team and Bundle Identifier (must match what you registered in step 1.5).See Admin workflows below — you'll need at least two signed-in accounts (two devices, or two simulators) and a manually-created conversation doc before there's anything to chat in.
There is no in-app way to add a contact or start a new conversation — this is intentional, not a missing feature (see chat-app-task-list.md for the original design rationale). An admin does the following directly in the Firebase console (Firestore Database tab):
conversations collection with:
{ "participants": ["<uidA>", "<uidB>"] }
conversations/{conversationId}/displaynames/{uid}:
{ "displayId": "AB" }
cd ios/walkytalky
# Build
xcodebuild -project walkytalky.xcodeproj -scheme walkytalky \
-destination 'generic/platform=iOS Simulator' -configuration Debug build
# Run the unit test suite
xcodebuild -project walkytalky.xcodeproj -scheme walkytalky \
-destination 'id=<simulator-udid>' -only-testing:walkytalkyTests test
# Run the UI tests (real tap-through tests against a live Firebase backend —
# these need a booted, freshly-erased simulator to be reliable)
xcodebuild -project walkytalky.xcodeproj -scheme walkytalky \
-destination 'id=<simulator-udid>' -parallel-testing-enabled NO \
-only-testing:walkytalkyUITests test
Project layout:
ios/walkytalky/walkytalky/
Auth/ sign-in, device-conflict handling, auth state
Passcode/ local passcode lock, PBKDF2, lockout
Conversations/ chat list, thread view, messages, display names
Pairing/ X25519 identity, QR generation/scanning, key derivation
Media/ capture, EXIF stripping, resize, encrypt/decrypt, upload/download
Models/ Firestore-mirrored data models, Keychain wrapper
firebase/
SecurityRules/ firestore.rules, storage.rules — read these before deploying
firebase.json, .firebaserc
chat-app-task-list.md is the full build log — every phase, every decision, every bug found and fixed, with dates. It's the most detailed record of why the code looks the way it does.
participants array will break decryption for everyone in it, not just the third person.MIT — see LICENSE.
18 commits
Swift
100.0%
Self-hosted, end-to-end encrypted 1:1 chat app for iOS (Android client WIP), built on Firebase. No central operator reads your messages or media - encryption keys are derived on-device via an offline QR handshake and never leave the device.
Swift
0
18 commits
updated Sep 21, 2026
A self-hosted, end-to-end encrypted 1:1 chat app for iOS, built on Firebase (Auth, Firestore, Storage). No central operator reads your messages or media — encryption keys are derived on-device via an offline QR handshake and never leave the device.
This project is open source (MIT) for anyone who wants to run their own instance. There is no shared/hosted "walkytalky service" — every deployer stands up their own Firebase project and owns their own data.
Identity & access
Messaging
WKWebView with a non-persistent data store) instead of Safari — cookies, cache, and history live only in memory for that one page view and are never written to disk or shared with the system's Safari historyBGTaskScheduler), posting a local notification — always the fixed text "You have a new message," never a preview, since this check never decrypts anything. Deliberately not real push: Push Notifications is one of the capabilities Apple gates behind a paid Apple Developer Program membership, which would contradict this project's free-Apple-ID self-hosting promise below. The tradeoff is real — iOS gives this no guaranteed schedule (opportunistic, can be delayed hours, and is cancelled if the user force-quits the app) — see Known limitationsMedia
UI
conversations/{id}, conversations/{id}/messages/{id}, conversations/{id}/displaynames/{uid}, users/{uid}), Firebase Storage for encrypted media blobs.HMAC<SHA256> since CryptoKit has no PBKDF2 API of its own).firebase/SecurityRules/firestore.rules and storage.rules scope every read/write to real participant membership, verified server-side — never trusting client-supplied claims. See the rules files themselves for the exact logic; they're deliberately readable.If you're building another client against the same backend, these are the exact primitives and byte layouts messages must match to interoperate:
{"conversationId": "<string>", "publicKey": "<base64, raw 32-byte X25519 public key>"}HKDF-SHA256(ECDH(myPrivateKey, peerPublicKey), salt: UTF-8(conversationId), info: UTF-8("walkytalky-conversation-key"), length: 32 bytes)nonce field; the uploaded/stored payload (Storage blob for media, text field for text) is ciphertext || 16-byte GCM tag concatenated, base64-encoded for Firestore fields.Self-hosting means standing up your own Firebase project — there's no separate server to run. Everyone who wants their own instance does this once.
npm install -g firebase-tools)com.withpreet.walkytalky — change this to something you own before building for a real device, since bundle IDs must be unique per Apple Developer team).GoogleService-Info.plist and place it at ios/walkytalky/walkytalky/GoogleService-Info.plist (this path is already gitignored — never commit it).cd firebase
firebase login
Edit .firebaserc and replace the project ID with your own:
{
"projects": {
"default": "your-project-id"
}
}
firebase deploy --only firestore:rules,storage
Read firebase/SecurityRules/firestore.rules and storage.rules first — they're the actual access-control boundary for your data. Don't deploy rules you haven't read.
ios/walkytalky/walkytalky.xcodeproj in Xcode.walkytalky target → Signing & Capabilities → set your own Team and Bundle Identifier (must match what you registered in step 1.5).See Admin workflows below — you'll need at least two signed-in accounts (two devices, or two simulators) and a manually-created conversation doc before there's anything to chat in.
There is no in-app way to add a contact or start a new conversation — this is intentional, not a missing feature (see chat-app-task-list.md for the original design rationale). An admin does the following directly in the Firebase console (Firestore Database tab):
conversations collection with:
{ "participants": ["<uidA>", "<uidB>"] }
conversations/{conversationId}/displaynames/{uid}:
{ "displayId": "AB" }
cd ios/walkytalky
# Build
xcodebuild -project walkytalky.xcodeproj -scheme walkytalky \
-destination 'generic/platform=iOS Simulator' -configuration Debug build
# Run the unit test suite
xcodebuild -project walkytalky.xcodeproj -scheme walkytalky \
-destination 'id=<simulator-udid>' -only-testing:walkytalkyTests test
# Run the UI tests (real tap-through tests against a live Firebase backend —
# these need a booted, freshly-erased simulator to be reliable)
xcodebuild -project walkytalky.xcodeproj -scheme walkytalky \
-destination 'id=<simulator-udid>' -parallel-testing-enabled NO \
-only-testing:walkytalkyUITests test
Project layout:
ios/walkytalky/walkytalky/
Auth/ sign-in, device-conflict handling, auth state
Passcode/ local passcode lock, PBKDF2, lockout
Conversations/ chat list, thread view, messages, display names
Pairing/ X25519 identity, QR generation/scanning, key derivation
Media/ capture, EXIF stripping, resize, encrypt/decrypt, upload/download
Models/ Firestore-mirrored data models, Keychain wrapper
firebase/
SecurityRules/ firestore.rules, storage.rules — read these before deploying
firebase.json, .firebaserc
chat-app-task-list.md is the full build log — every phase, every decision, every bug found and fixed, with dates. It's the most detailed record of why the code looks the way it does.
participants array will break decryption for everyone in it, not just the third person.MIT — see LICENSE.
18 commits
Swift
100.0%