NUSV

Syna — technical documentation

Offline-first LAN messenger (Kotlin Multiplatform + Compose Multiplatform). Android · Windows · macOS, fully peer-to-peer, end-to-end encrypted.

01 Stack & targets

ItemDetail
LanguageKotlin (Kotlin Multiplatform + Compose Multiplatform)
AndroidAndroid 9+ (API 28). NDK component (syna_shield.c) for the native anti-hook layer
DesktopWindows 10+ / macOS / Linux (JVM, Java 17+); release DMG targets Apple Silicon
ServerHeadless JVM "fat jar" — runs on Windows / macOS / Linux / Raspberry Pi
LicenseGPL-3.0-only (SPDX headers on every source file; all runtime dependencies Apache-2.0 / MIT)

02 Architecture & code map

Single Gradle module :composeApp with per-platform source sets on top of shared code. Everything transport-, crypto- and shield-related is in commonMain; platform code only adapts system services (keystores, notifications, file pickers, networking sockets).

commonMain/netSynaEngine (connection state machine), Protocol (framing), Peer, ServerChannel, discovery
commonMain/cryptoSynaCrypto (X25519/AES-GCM), IdentityStore contract, KeyPinning
commonMain/shieldShield engine contracts, TOTP (RFC 6238), session & storage key gates, config guard
commonMain/chatChatStore — conversations, outbox, message lifecycle
commonMain/uiCompose UI: Chats / Contacts / Chat / Settings / ShieldLock screens
androidMainMainActivity, Keystore-backed identity/session/TOTP stores, cpp/syna_shield.c NDK bridge, AndroidShieldEngine
desktopMainmain.kt launcher, Desktop* shield/stores, file transfer, plus the whole server package (SynaServer, ServerMain, ServerLauncher, ServerUi)

03 Networking & protocol

Peer discovery (LAN)

Dual-channel discovery over UDP: broadcast + multicast (some routers block broadcast; multicast is the fallback). Every device announces itself every 3 seconds with a heartbeat; a peer is marked offline after 15 seconds without heartbeats. A manual refresh button re-broadcasts on demand for troubleshooting. Android 14+ local-network restrictions and proxy tools with TUN mode are explicitly handled — same-machine traffic is routed via loopback, and cross-device traffic can be restored by bypassing the LAN subnet in the proxy.

Transport

  • Messages and files travel over TCP; encrypted chunks of 64 KB. Low-latency frames (typing, presence) use UDP at 40 KB chunks.
  • ACK-based retransmission: the outbox only drops frames after a confirmed send failure re-enters the queue — failed sends stay queued for reconnect instead of being marked "sent".
  • Cross-version interop: new frame fields carry defaults, so old and new clients interoperate on the same LAN.
  • Group chat (LAN mesh) has no central node; offline members simply miss group traffic until they return (the private server adds persistence, see 06).

04 Crypto & identity

  • Key exchange: X25519; session cipher AES-256-GCM.
  • Identity: per-device long-term key held in the platform keystore — Android: Android Keystore (TEE-backed); desktop: key files with POSIX permissions (NTFS quirks handled).
  • Trust on first use (TOFU) with key pinning — see commonMain/shield/KeyPinning.kt; a changed key triggers the shield flow rather than silent acceptance.
  • Forward secrecy: session keys rotate on every unlock ("process-epoch"), with HMAC-protected in-memory state; message history is encrypted at rest (see 07).
  • Files & images: chunked encrypted transfer over the same channels, with progress bars.

05 The ⋄Mirtazapine Shield

The shield is a real-time security monitor that assumes the device itself may be hostile. Its threat model, detection matrix and rationale are documented in the repository (MIRTAZAPINE_SHIELD.md); it is designed to hold even with the source fully public.

Detection (25+ sources, JVM + native dual-channel)

Root / Magisk / Xposed / Zygisk / Shamiko / LSPosed / Riru / EdXposed / TaiChi; Frida injection (paths, ports 27042/27043, /proc/self/maps, threads, TracerPid); emulators; ADB; MDM takeover; credential changes; VPN/proxy changes; CA-certificate changes and ARP spoofing (LAN MITM); SSID fingerprint; rapid background switching; accessibility abuse; monitoring apps (signature-learning blacklist that catches renamed/repackaged apps); screen capture/recording (API 34+); screen mirroring; SELinux; clock tamper; weak lock; IME changes; USB attach/detach; suspicious executable modules (partition whitelist + non-JIT memfd); system proxy; device identity changes; downgrade attempts; JVM -javaagent injection; remote-control processes.

Native layer (Android)

NDK C bridge (syna_shield.c): syscall-direct I/O defeats GOT/PLT/LD_PRELOAD interception; own-code-segment memory-vs-disk hashing plus export-entry self-verification defeat inline hooks; libc entry verification.

Active countermeasures

  • Operation-triggered integrity probing on the decrypt path; watchdog ring (3 threads monitoring each other); honeypot data pollution (decoy messages).
  • Severity-graded full-screen lock; TOTP (RFC 6238) two-factor unlock with otpauth:// seed import; biometric unlock (BIOMETRIC_STRONG only).
  • Dual-factor disable — the shield cannot be turned off without biometrics + TOTP; brute-force protection: 5 failures releases the key and triggers the self-destruct protocol, with exponential cooldown.
  • Self-destruct wipes local chats and files on critical compromise; honeypot fake-lock (real key release + triple verification) for injection threats.
  • Heartbeat gate: fail-closed — a stalled detector refuses to decrypt. VersionCode downgrade defense; HMAC-signed settings.
  • APK signature-fingerprint verification (anti-repackaging) and dex hash self-verification at startup.

06 Private server

  • Headless syna-server.jar built by :composeApp:serverFatJar; launch with java -jar (JRE 17 suffices).
  • Plain TCP listener on port 45880; join from the app via Contacts → Join Server → address:port + password. The channel is derived from the shared password.
  • Server groups persist history to history.jsonl and survive restarts.
  • NAT traversal: map 45880 with any tunnel client — frp (type = tcp, local_port = 45880), ngrok tcp 45880, or a Tailscale node IP.

07 Data & persistence

  • LAN chats persist locally as encrypted JSONL (AES-GCM; Android keys live in the hardware Keystore, files at mode 0600) and survive restarts.
  • Data-level key gate: the storage key is wrapped by a biometric-authenticated Keystore key — without authentication, newly stored data is unreadable.
  • Memory cleared while locked and after 60 s in background; notifications and clipboard content are suppressed while locked.
  • Audit: hash-chained + AES-GCM encrypted event timeline persisted across restarts.
  • Desktop settings are stored in JVM user preferences (multiple instances under one OS account share one identity).

08 Test matrix

./gradlew :composeApp:desktopTest runs 88 tests covering: crypto, protocol, loopback chat, group mesh, burn-after-reading, temp chat, offline outbox, server join + chat + history + burn, ACK retransmission, group admin, file transfer, process-epoch forward secrecy, a 6-engine message storm, and the shield gate · watchdog · honeypot · brute-force · TOTP · TOTP-cancel · TOFU · module whitelist · memfd cases.

09 Building from source

Prerequisites: JDK 17+ and the Android SDK (for the Android target only).

# Run the desktop app
./gradlew :composeApp:run

# Installers
./gradlew :composeApp:packageDmg    # macOS .dmg
./gradlew :composeApp:packageMsi    # Windows .msi (Windows + WiX required)
./gradlew :composeApp:packageDeb    # Linux .deb

# Headless server jar (all platforms)
./gradlew :composeApp:serverFatJar  # -> build/server/syna-server.jar

# Android
./gradlew :composeApp:assembleDebug # debug APK
./gradlew :composeApp:assembleRelease # release APK (needs signing config in local.properties)

# Tests
./gradlew :composeApp:desktopTest

CI (GitHub Actions, .github/workflows/build.yml) runs the test suite, builds the Android APK, the server jar and the macOS DMG on every push to main; tag pushes also publish a GitHub Release with all artifacts. Dependency license audit output lives in tools/license-audit/.