Sharif T. Issah
← All work
Case study 02Working build · debug releases

Sink

End-to-end encrypted messages relayed phone to phone with no server, account or phone number.

Role
Design, protocol, engine, app and CI
Tests
43 automated engine tests, run in CI
Code
111 Kotlin files across engine, core and feature modules
Source
Public
Stack
Kotlin · Jetpack Compose · Room · Hilt · Nearby Connections · WorkManager · Android Keystore · JCA crypto · Gradle · GitHub Actions

Problem

When connectivity fails, messaging usually fails with it. Sink asks how far people can still talk using only the phones around them, and what that costs in security and reliability.

What I built

  • A routing engine using TTL- and hop-bounded flooding, duplicate suppression, ACK propagation and store-and-forward retry with exponential backoff.
  • A signed, versioned binary wire protocol with a self-certifying HELLO identity handshake.
  • End-to-end encryption using ephemeral ECDH, HKDF-SHA256 and AES-256-GCM, with ECDSA signatures on packets.
  • A hardware-backed device identity: a non-exportable Keystore signing key, with StrongBox where available.
  • Nearby Connections and SMS transports, a foreground service, and a settings screen where each toggle is functionally wired.
  • A real mesh view and honest connectivity states. It shows direct connections only and never fabricates a topology.

Architecture

Repository layout

engine/                pure Kotlin/JVM, no Android dependency
  protocol/            packet format, ids, codecs
  crypto-core/         ECDH · ECDSA · AES-GCM · HKDF (JCA)
  mesh-engine/         RoutingEngine, TransportManager,
                       mesh simulator + tests
app/                   Compose entry point, DI wiring
core/                  common · crypto · database · datastore
                       networking · logging · permissions
feature/               onboarding · home · conversations · chat
                       contacts · discovery · mesh · settings

The engine builds and tests anywhere with a JDK, without the Android SDK. The Android project includes it as a composite build.

Engineering decisions

Flooding over topology-aware routing

Neighbours change as people walk and phones sleep, so keeping a current topology graph is costly. Bounded flooding with a dedup cache degrades gracefully: a lost relay removes one path, not a routing table. This is recorded as a deliberate MVP-scope decision.

Keep the engine free of Android

Routing, crypto and protocol code can be unit-tested on the JVM and exercised by a simulator that stands in for real radios.

Security documents ship inside the app

The security and threat-model documents are bundled as assets and readable from the About screen, so users see the limits as well as the claims.

Block enforcement at the routing layer

Blocked contacts have their keys withheld in the routing layer, not only hidden in the interface.

Current state and limits

  • Traffic analysis is not defended against: packet size, timing and sender/destination ids are visible to relays. This is disclosed in the threat model.
  • First contact relies on trust-on-first-use unless the user verifies a safety number.
  • Published builds are debug-signed. A versioned signed release awaits release-signing setup.
  • Throughput and battery behaviour in large real-world meshes have not been measured.