7.5 KiB
7.5 KiB
Kecalek Android — TODO & Implementation Status
✅ DONE — Infrastructure (Phase 0-3)
Phase 0 — Gradle + Base
- Gradle setup (Tink, Bouncy Castle, Room/SQLCipher, Hilt, Compose, OkHttp)
- Catppuccin Mocha dark theme + Material 3
- Navigation (NavHost, routes)
- Domain models (Message, Conversation, User, MessageReaction, etc.)
- Room entities + DAOs (MessageDao, ConversationDao, UserCacheDao)
- AppDatabase with SQLCipher
Phase 1 — Crypto (11 files)
- AesGcm.kt — AES-256-GCM
- Hkdf.kt — HKDF
- ECP1.kt / KeyEncryption.kt — password-based key encryption
- Ed25519Crypto.kt — Ed25519 sign/verify + Ed→X25519 conversion
- X25519Crypto.kt — X25519 DH
- RSACrypto.kt — RSA-PSS for login
- MessagePadding.kt — bucketed padding 64B–64KB
- X3DH.kt — X3DH initiate/respond
- DoubleRatchet.kt — Double Ratchet encrypt/decrypt/import/export
- SenderKey.kt — group sender key protocol
- ContactVerification.kt — safety numbers (SHA-512 × 5200, 60 digits)
Phase 2 — Network (3 files)
- ConnectionManager.kt — raw TCP, newline-delimited JSON
- ProtocolHandler.kt — request/response + push dispatch
- ServerApi.kt — 50 endpoints
Phase 3 — Core + Repos + DI
- SessionManager.kt — login/register/session persistence
- KeyStorage.kt — encrypted key persistence
- ChatClient.kt — messaging engine (sendDm, decrypt, new_message handler)
- NotificationRouter.kt — routes 18 push types
- MessageRepository.kt — message CRUD + search + reactions
- ConversationRepository.kt — conversation CRUD
- AppModule.kt — AppDatabase singleton
- DatabaseModule.kt — DAO providers
- NetworkModule.kt — placeholder (net classes auto-wired)
- CryptoModule.kt — placeholder (crypto = Kotlin objects)
Phase 4 — Service + Feature UI
- ChatPushService.kt (FCM/push forwarding to NotificationRouter)
- AuthViewModel.kt
- ConversationListViewModel.kt
- ChatViewModel.kt — core (loadMessages, sendMessage, incoming flow)
- ChatScreen.kt
- ConversationListScreen.kt
- Auth screens (login, register)
🔧 READY TO BUILD & TEST
Build
- Build in Android Studio (Ctrl+F9 / Build > Make Project)
- Note: Cannot build from CLI (JAVA_HOME not set on Windows)
- Expected: clean build with no compilation errors
Integration Tests
- Send DM: type message on Android → verify arrives on Python/iOS client
- Receive DM: send from Python/iOS → verify appears on Android
- Self-copy: message I send appears in my own chat view immediately
- X3DH new session: first message to new user triggers X3DH + works correctly
- Group message: send in group conversation → all members receive
- Reaction: add emoji reaction → reflects on all clients
- Pin: pin a message → shows in pinned list
- Delete: delete message → removed from all clients
🟡 TODO — ChatViewModel Secondary Features
Simple API calls (ready to implement)
- deleteMessage(messageId) —
api.deleteMessage()+messageRepository.markDeleted() - reactToMessage(messageId, reaction) —
api.reactMessage()+messageRepository.updateReactions() - pinMessage(messageId) —
api.pinMessage()+messageRepository.updatePinStatus() - markAsRead() —
api.markConversationRead(conversationId) - search(query) —
messageRepository.searchMessages()+ update searchResults in state - nextSearchResult() / prevSearchResult() — navigate currentSearchIndex
- forwardMessage(messageId, targetConversationId) — re-encrypt plaintext for target conv
Complex (need encryption pipeline)
- sendImage(uri) — AES encrypt image → chunked upload via uploadImageStart/Chunk/End
- ImageInfo {fileId, aesKey, iv, thumbnail, filename, size} stored in message
- Payload: {"sender", "text", "image": {"file_id": ..., "key": b64, "iv": b64}, "timestamp"}
- sendFile(uri) — similar to sendImage but with FileInfo
- downloadFile(fileId) — downloadImage() chunks → reassemble → AES decrypt
- Use offset pagination: loop downloadImage(fileId, offset) until complete
🟡 TODO — ChatClient Group Messaging
-
sendGroupMessage() — sender key protocol
- Check if each member has received sender key
- If not: send
_sender_keycontrol message first (DM encrypted per device) - Then encrypt group message with SenderKey.encrypt()
- Track which members have the key in keyStorage or DB
-
Group member add: resend sender key to new member via DM
-
Group member remove: rotate sender key, resend to remaining members
-
Handle
member_added/member_removedpush notifications in ChatClient
🟠 KNOWN ISSUES
✅ FIXED: SQLCipher passphrase (AppModule.kt)
- Was: hardcoded
"TODO_REPLACE_WITH_DERIVED_KEY"passphrase - Now: random 32-byte key generated once, stored in EncryptedSharedPreferences (Android Keystore)
- Migration: if old DB exists without stored passphrase → old DB deleted, new one created
- Note: Could use HKDF-derived key from identity private (like Python), but Android Keystore approach is simpler and equally secure
✅ FIXED: AuthUiState.useTls = true (registration bug)
- Was:
AuthUiState.useTls = true→ registration always tried TLS → SSL error if server is plain TCP - Now:
AuthUiState.useTls = false— consistent with LoginScreen local default - LoginScreen always calls
updateServerConfig(useTls=false)before login anyway - If server requires TLS, user can toggle it in Server Configuration section on LoginScreen
Push notification handler registration
- ChatClient.setupNotificationHandlers() is called from initialize()
- If ChatClient is not initialized (user not logged in), push notifications are silently dropped
- Fix: Check session state before trying to decrypt; store encrypted push and decrypt on next init
Session persistence
- Sessions (DoubleRatchet state) are saved to KeyStorage per device
- If app is reinstalled, all sessions are lost → first message after reinstall needs X3DH
- This is expected behavior but worth testing
📋 PROTOCOL COMPATIBILITY CHECKLIST
sendDm()
- Recipients per-device (not per-user)
- Self-copy: device_id = SELF_DEVICE_ID, ratchet_header = {"self": true}
- X3DH header fields: "ik", "ek", "opk_id" (NOT "ik_pub", "ek_pub")
- Binary encoding: base64 via encodeBinary()/decodeBinary()
- Ratchet header: dh_pub (hex), n, pn via header.toMap()
- Plaintext payload: JSON {"sender", "text", "reply_to", "timestamp"}
- Message padding: MessagePadding.pad() before encrypt, unpad() after decrypt
handleNewMessage() / decryptServerMessage()
- Push field: "sender_id" (NOT "sender_user_id")
- device_entries array: pick by myDeviceId or SELF_DEVICE_ID
- Self-copy detection: ratchet_header.self == true
- Control messages (_sender_key): import silently, don't display
- Flat fallback fields for backward compat
🔮 FUTURE — Nice to Have
- Voice messages — record, encrypt with AES, upload as file
- Disappearing messages — server-side TTL
- Message editing — re-encrypt + server update
- Key rotation — rotate RSA/Ed25519 identity keys
- Device pairing — link second device (pairingStart/pairingSend)
- Contact verification — UI to display safety numbers
- Backup / restore — export encrypted sessions
- Push notification channels — Android notification priorities
- App lock (biometric / PIN)