15 KiB
Kecalek iOS — Architecture & Features
Version: 0.8.5 Platform: iOS 26+ / Swift 6 Files: 57 Swift source files
Project Structure
Kecalek/
├── KecalekApp.swift # App entry point, tab navigation
├── AppState.swift # Login state, connection monitoring, reconnection
├── Core/
│ ├── ChatClient.swift # Main actor — all server communication & crypto (3400+ lines)
│ ├── KeyStorage.swift # Persistent key storage (RSA, Ed25519, sessions, TOFU)
│ ├── KeychainService.swift # Secure credential storage (biometric auth)
│ └── MessageCache.swift # Encrypted message cache (per-conversation)
├── Crypto/
│ ├── CryptoUtils.swift # AES-256-GCM, HKDF, chain KDF, local encryption
│ ├── DoubleRatchet.swift # Signal Double Ratchet (DM encryption)
│ ├── X3DH.swift # Extended Triple Diffie-Hellman (session init)
│ ├── SenderKeyState.swift # Sender Key chains (group encryption)
│ ├── Ed25519Crypto.swift # Identity key generation & signing
│ ├── X25519Crypto.swift # DH key agreement & Ed25519↔X25519 conversion
│ ├── RSACrypto.swift # RSA-2048 key generation, PKCS#1/PKCS#8
│ ├── KeyEncryption.swift # ECP1 format: PBKDF2 600K + AES-GCM key encryption
│ ├── FieldArithmetic.swift # GF(2^255-19) for Ed25519→X25519 conversion
│ ├── MessagePadding.swift # Bucket-based padding (64B–64KB) for metadata privacy
│ ├── ContactVerification.swift # Fingerprints, safety numbers, QR codes
│ └── CryptoErrors.swift # Error types
├── Network/
│ ├── ConnectionManager.swift # TCP/TLS via Network.framework (actor)
│ └── ProtocolHandler.swift # Newline-delimited JSON encoding/decoding
├── Models/
│ ├── Message.swift # Message, reactions, replies, pins, files, images
│ ├── Conversation.swift # Conversation, members, group detection
│ ├── User.swift # User, UserProfile
│ ├── DeviceBundle.swift # X3DH key bundle per device
│ └── Invitation.swift # Group invitation
├── ViewModels/
│ ├── AuthViewModel.swift # Login, register, pairing, biometrics
│ ├── ChatViewModel.swift # Messages, sending, search, reactions, pins
│ ├── ConversationListVM.swift # Conversations, online users, favorites, avatars
│ ├── ProfileViewModel.swift # Profile editing, avatar upload
│ └── VerificationVM.swift # Safety numbers, QR verification
├── Views/
│ ├── Auth/ # LoginView, RegisterView, PairingView, AuthorizeDeviceView
│ ├── Chat/ # ChatView, MessageBubbleView, MessageInputView, etc.
│ ├── Components/ # CircularAvatarView, ConnectionIndicator, OnlineDotOverlay
│ ├── Conversations/ # ConversationListView, ConversationRowView, NewConversationSheet
│ ├── Groups/ # GroupInfoView, InvitationBanner, CreateGroupSheet
│ ├── Profile/ # ProfileView, EditProfileView
│ └── Verification/ # SafetyNumberView, QRCodeScannerView, VerificationStatusView
└── Utilities/
├── Constants.swift # Version, limits, timeouts, server defaults, crypto params
└── Extensions.swift # Data hex/base64, DateParsing, Dictionary helpers
Architecture
Pattern: MVVM + Actor Isolation
- Views — SwiftUI, declarative UI, bind to
@ObservableViewModels - ViewModels —
@Observable final class, business logic, async operations - ChatClient —
actor, single source of truth for all crypto & network ops - Models — plain
structs withIdentifiable,Codable
Concurrency Model
ChatClientis an actor — all crypto state (keys, sessions, ratchets) is thread-safe- All network calls use
async/await - Real-time notifications via
AsyncStream<ChatNotification>(multiple subscribers) - Background tasks: avatar loading, reconnection, notification listening
Connection Lifecycle
App Launch → Login (RSA challenge-response) → TCP/TLS connected
→ Background listener loop reads messages continuously
→ Notifications broadcast via AsyncStream to all subscribers
→ On disconnect: exponential backoff reconnect (1s → 30s, 5 attempts)
→ On auth failure: immediate logout (keys rotated)
→ On foreground: check connection health, reconnect if stale (>30s)
Encryption (Signal Protocol)
Key Types
| Key | Algorithm | Size | Purpose |
|---|---|---|---|
| RSA | RSA-2048 | 256B | Login authentication (challenge-response) |
| Identity Key (IK) | Ed25519 | 32B | Long-term identity, signs SPK |
| Signed Pre-Key (SPK) | X25519 | 32B | Medium-term, rotated every 7 days |
| One-Time Pre-Keys (OPKs) | X25519 | 32B each | Single-use, batch of 50, replenish at 20 |
| Ratchet Keys | X25519 | 32B | Ephemeral per DH ratchet step |
| Sender Keys | Random | 32B | Per-group, per-sender chain key |
DM Encryption (X3DH + Double Ratchet)
-
Session Init (X3DH):
- Alice computes: DH(IK_A, SPK_B) || DH(EK_A, IK_B) || DH(EK_A, SPK_B) || DH(EK_A, OPK_B)
- HKDF-SHA256 derives 32-byte shared secret
- Double Ratchet initialized
-
Message Encryption (Double Ratchet):
- Root key → chain key → message key (HKDF chain)
- DH ratchet step on each direction change
- AES-256-GCM with derived message key
- AAD: ratchet header (dh_pub, n, pn)
- Max skip: 256 messages
-
Message Format:
plaintext → MessagePadding.pad() → AES-256-GCM encrypt → base64 → JSON
Group Encryption (Sender Keys)
- Each member maintains own sender key chain
- Sender key distributed to all members via pairwise Double Ratchet DMs
- Messages encrypted with AES-256-GCM using derived chain key
- Chain ID = SHA-256(sender_key) for verification
- Max skip: 256 messages per chain
Message Padding
Bucket sizes: 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536 bytes
Format: 0x01 | plaintext | random_padding | pad_length (4B big-endian)
All messages padded to nearest bucket size — prevents metadata analysis of message lengths.
Self-Encryption
- Derived from identity private key via HKDF
- Encrypts own message copies for multi-device access
- Static key — same across all user's devices
Contact Verification (TOFU)
- Fingerprint: Iterated SHA-512 (5200 rounds) over identity key
- Safety Number: 60 digits (12 groups of 5), deterministic ordering by userId
- QR Code: Binary
version(1B) + uid_len(1B) + uid + identity_key(32B) - TOFU Registry: Track first-seen identity keys, alert on change
- Verification Status: unverified → trusted (TOFU) → verified (manual/QR)
Features
Authentication & Accounts
- Registration — email + password + verification code
- Login — RSA challenge-response authentication
- Biometric Login — Face ID / Touch ID via Keychain
- PoW Challenge — SHA-256 proof-of-work during registration surge
- Brute-Force Lockout — exponential backoff (2^n seconds, max 300s)
- Change Username — update display name
- Change Password — re-encrypt RSA + Ed25519 keys with new PBKDF2 password
- Key Rotation — regenerate all keys with grace period for in-flight sessions
- Logout — clean disconnect, clear session
Multi-Device
- Device Pairing — authorize new device via pairing flow
- Device List — view all authorized devices
- Device Removal — revoke device authorization
- Self-Encryption — own messages readable on all devices
Messaging
- Text Messages — encrypted DM and group messages
- Message Replies — reply-to with visual indicator
- Reactions — 6 emoji reactions (👍❤️😂😮😢👎) with toggle
- Message Pinning — pin/unpin with pinned messages sheet
- Message Deletion — soft delete with "Message deleted" indicator
- Message Forwarding — forward to any conversation with source attribution
- Message Search — full-text search with result navigation (prev/next)
- Read Receipts — track who read each message
- Delivery Receipts — sent → delivered → read indicators (checkmarks)
- Incremental Sync — fetch only new messages via
after_ts - Deleted Sync —
get_deleted_sincefor incremental deletion sync - Message Padding — metadata privacy via bucket-based padding
Media & Files
- Image Upload — encrypt + chunked upload (24KB chunks)
- Image Thumbnails — base64 JPEG preview inline
- Image Viewer — full-screen with pinch zoom
- File Upload — any file type with mime detection
- File Download — decrypt + share via system share sheet
- File Icons — type-based system icons (PDF, DOC, ZIP, etc.)
Conversations
- Direct Messages — 1-on-1 encrypted chat
- Group Conversations — multi-member with Sender Keys
- Create Conversation — new DM or group
- Rename Conversation — group rename (creator only)
- Delete Conversation — remove DM or delete group
- Favorites — pin conversations to top with star icon
- Unread Counts — per-conversation badge
- Online Status — real-time presence (green dot)
Group Management
- Add Member — by email
- Remove Member — creator only
- Leave Group — with confirmation
- Group Avatar — upload/change group photo
- Group Rename — change group name
- Group Invitations — accept/decline with banner UI
Profile
- User Profile — username, email, phone, location
- Avatar — upload/change profile photo
- Field Visibility — toggle phone/location visibility
- View Other Profiles — see other users' info (respects visibility)
Contact Verification
- Safety Numbers — 60-digit verification code per contact pair
- Fingerprints — identity key fingerprints
- QR Code Generation — generate scannable verification QR
- QR Code Scanning — camera-based QR scan for verification
- Verification Status — verified (green) / trusted (blue) / unverified (gray)
- TOFU Registry — track identity key first-seen, detect changes
- Shield Icon — verification badge in chat toolbar
Connection & Reliability
- TCP/TLS — Network.framework with optional TLS
- Configurable Server — host, port, TLS toggle in login screen
- Connection Indicator — visual status (disconnected/connecting/connected)
- Auto-Reconnect — exponential backoff (1s → 30s, 5 attempts)
- Background/Foreground Handling — reconnect when returning from background
- Auth Failure Detection — immediate logout on key rotation
Caching & Storage
- Message Cache — encrypted per-conversation cache on disk
- Avatar Cache — disk + in-memory cache
- Conversation Cache — cached list for instant UI
- Session Persistence — Double Ratchet states saved encrypted
- Sender Key Persistence — group key chains saved encrypted
- Device Bundle Cache — 5-minute TTL in-memory
- Keychain Storage — biometric-protected credentials
Network Protocol
Transport
TCP → optional TLS → Newline-delimited JSON (\n terminated)
Message Format
{"type": "send_message", "request_id": "uuid", "conversation_id": "...", "ciphertext": "base64..."}
API Methods (36 endpoints)
Auth: register, register_confirm, login_start, login_finish, change_username, change_password
Keys: get_key_bundle, ensure_prekeys, get_prekey_count, rotate_keys, reset_session
Messaging: send_message, get_messages, delete_message, mark_read, mark_conversation_read, react_message, pin_message, get_pinned_messages, get_deleted_since, forward_message, confirm_delivery, search_messages
Conversations: list_conversations, create_conversation, find_conversation, delete_conversation, rename_conversation, add_member, remove_member, leave_group, accept_invitation, decline_invitation, list_invitations
Profiles: get_profile, update_profile, update_avatar, get_avatar, update_group_avatar, get_group_avatar
Files: upload_file, download_file
Devices: list_devices, remove_device, pairing_start, pairing_wait, authorize_device
Notification Types (17 real-time events)
new_message, messages_read, message_deleted, message_reacted,
message_pinned, message_unpinned, message_delivered,
conversation_created, conversation_renamed, conversation_deleted,
member_added, member_removed, group_invitation,
user_online, user_offline, online_users,
session_reset, keys_updated
Storage Layout
~/Library/Application Support/EncryptedChat/{email}/
├── private.pem # RSA private key (password-protected)
├── public.pem # RSA public key
├── identity_private.bin # Ed25519 private (ECP1: PBKDF2 + AES-GCM)
├── identity_public.bin # Ed25519 public
├── spk_private.bin # Current signed pre-key (X25519)
├── spk_id.txt # SPK ID
├── prevspk_private.bin # Previous SPK (grace period)
├── prevspk_id.txt
├── opk_{id}.bin # One-time pre-keys
├── sessions/
│ └── {userId}_{deviceId}.bin # Double Ratchet state (encrypted)
├── sender_keys/
│ └── {convId}_{senderId}_{deviceId}.bin # Sender Key chain (encrypted)
├── message_cache/
│ └── {convId}.json # Message cache (encrypted)
├── conversations_cache.json # Conversation list cache (encrypted)
├── avatars/
│ └── {convId}.bin # Avatar image data (encrypted)
├── known_identity_keys.bin # TOFU registry (encrypted)
├── verified_contacts.bin # Verified contacts (encrypted)
└── favorites.bin # Favorite conversation IDs (encrypted)
Build
# Xcode build
open /Users/filip/Desktop/kecalek_ios/Kecalek/Kecalek.xcodeproj
# Command-line build
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer \
xcodebuild -scheme Kecalek \
-destination 'platform=iOS Simulator,name=iPhone 17 Pro' \
build
Result: 57 files, 0 errors, 0 warnings.