Sync Engine
Resibibo supports opt-in E2E encrypted sync between the mobile app and the Django server.
Overview
Sync is entirely optional. When enabled:
- A keypair is generated on the device
- Financial data is encrypted before leaving the device
- The server stores only encrypted blobs
- Data is decrypted only on the device
Encryption
Key Management
- Algorithm: X25519 (key exchange) + XSalsa20-Poly1305 (symmetric encryption)
- Library (mobile): tweetnacl
- Library (server): PyNaCl (libsodium bindings)
- Key storage: Private key in expo-secure-store (Keychain on iOS, Keystore on Android)
- Public key: Sent to server and stored on User model
Encryption Flow
Sync Flow
Push (Local -> Server)
- Query all records where
syncStatus = 'pending' - Encrypt each record's data with the user's keypair
- Batch POST to
/api/v1/sync/push/with encrypted payloads - On success, update
syncStatus = 'synced'
Pull (Server -> Local)
- GET
/api/v1/sync/pull/?since={last_sync_timestamp} - Decrypt received records with private key
- For each record:
- If local copy doesn't exist: insert
- If local
updated_at< serverupdated_at: update local - If local
updated_at> serverupdated_at: mark asconflict
- Conflicts are presented to the user for manual resolution
Conflict Resolution
Conflicts use a last-write-wins strategy with manual override:
- The
conflicts.tsxscreen shows conflicting records - Users can choose to keep the local version, server version, or merge manually
- Resolution calls
POST /api/v1/sync/resolve/
Auto-Sync Triggers
- App foreground: Syncs when app comes to foreground (if > 5 minutes since last sync)
- Manual: Sync button in Settings
- Sync state is managed with Zustand (
lib/sync/sync-store.ts)
Server Endpoints
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/v1/sync/push/ | Push encrypted records |
| GET | /api/v1/sync/pull/ | Pull records since timestamp |
| GET | /api/v1/sync/status/ | Get sync status |
| POST | /api/v1/sync/resolve/ | Resolve conflicts |
SyncStatus Column
All syncable tables have a syncStatus column:
| Value | Meaning |
|---|---|
pending | Record has local changes not yet synced |
syncing | Currently being synced (transient state) |
synced | Successfully synced with server |
conflict | Server and local versions differ |