Ungrid logo
University of Balamand
Live BLE Mesh · No Internet Required

Ungrid Android
Encrypted Mesh Communication

A fully offline, end-to-end encrypted Android messaging app that builds its own network over Bluetooth Low Energy — no internet, no servers, no compromise.

StudentJohnny Jneid · A2211661
SupervisorDr. Charbel Fakhry
CourseCSIS 290 — Senior Project
UniversityUniversity of Balamand
DateMay 13, 2026
SCROLL
0Protocol Downloads (iOS)
Max Relay Hops
0MB Max File Transfer
0Crypto Algorithms
0K PBKDF2 Iterations
0Hr Store-and-Forward
01 — The Problem

When Infrastructure Fails,
Communication Dies

Centralized Dependency

WhatsApp, Signal, and Telegram are clients talking to servers. Every message is routed through a data center. When the server is unreachable — the app is silent.

Infrastructure Fragility

Natural disasters knock out cell towers. Governments enforce internet blackouts during protests. Remote regions never had infrastructure. These are not edge cases — they are recurring realities.

Privacy Under Surveillance

Even when a connection exists, centralized messaging exposes metadata to service providers — who you talk to, when, and how often. Server seizure compromises entire user bases.

Incomplete Offline Solutions

Existing offline tools are fragmented: Meshtastic requires $30+ LoRa hardware, Briar lacks voice capability, GoTenna charges subscriptions. None run on the phone you already have.

The core gap: No open-source solution combines BLE mesh networking, military-grade encryption, multimedia transfer, and real-time voice calling in a single Android application — running on commodity hardware, with zero server dependency.

03 — Our Solution

Ungrid: Full-Stack Offline
Encrypted Communication

Ungrid transforms a text-only BLE mesh foundation into a complete multimedia communication platform — images, documents, voice notes, and live voice calls — all end-to-end encrypted, all fully offline.

BLE Mesh Networking

Every device is simultaneously a sender and relay. Messages relay across unlimited hops — reaching anyone in the mesh without a direct connection. Self-organizing, no configuration required.

Military-Grade Encryption

The Noise XX protocol (same as WhatsApp & WireGuard) for peer sessions. PBKDF2 + AES-256-GCM for group channels. Ed25519 packet signing. PKCS#7 padding defeats traffic analysis.

Multimedia Transfer

Images, documents up to 10 MB, and voice notes — all sent over BLE via TLV-encoded packets with automatic fragmentation. Images auto-compressed (EXIF-corrected, 512px, JPEG 85%).

Real-Time Voice Calling

Live bidirectional voice calls over BLE. AAC-LC at 16kHz/20kbps. Full call state machine: IDLE → CALLING → RINGING → IN_CALL → ENDED. Call signaling itself is Noise-encrypted.

iOS Cross-Platform

100% binary-compatible with the iOS reference implementation. Same V1/V2 packet headers, UUIDs, and message types. Android and iPhone users share the same mesh network seamlessly.

Zero Registration

No accounts, phone numbers, or servers. Identity derived from a self-sovereign Noise key pair generated on first launch. Peer ID = first 16 hex chars of SHA-256(public key).

— Ungrid vs BitChat

Where BitChat Stopped,
Ungrid Continues

Ungrid shares the same binary wire protocol, BLE UUIDs, and Noise XX cryptography as the iOS reference implementation — ensuring full cross-platform mesh interoperability. Every capability listed below was built on top of that shared foundation without breaking a single byte of compatibility.

Capability BitChat (iOS original) Ungrid Android
Text messaging✓ Supported✓ Supported
Image sharingNot available✓ EXIF-corrected, 512px, JPEG 85%
Document transferNot available✓ Up to 10 MB, TLV-encoded
Voice notesNot available✓ AAC-LC with waveform visualizer
Real-time voice callsNot available✓ BLE bidirectional, full state machine
Private chat UINo dedicated screen✓ PrivateChatScreen composable
Chat interfaceIRC-style terminal✓ WhatsApp-style bubbles + GUI
Message persistenceSession only — lost on close✓ Room DB — survives app restart
BLE write reliabilityWRITE_TYPE_NO_RESPONSE (silent loss)✓ WRITE_TYPE_DEFAULT (ACK-based)
Binary protocol✓ V1 / V2✓ V1 / V2 — 100% compatible
iOS ↔ Android interopiOS only✓ Full cross-platform mesh
04 — Technical Architecture

Layered Component Design

UI Layer — Jetpack Compose

Stateless Compose screens driven by StateFlow. ChatViewModel bridges the service layer. AppStateStore is the process-wide reactive state singleton.

Service Layer — BluetoothMeshService

Foreground service keeps BLE alive. Central coordinator owns all subsystems. Each peer gets a dedicated coroutine actor for race-condition-free packet processing.

Encryption Layer — NoiseEncryptionService

Noise XX per-peer session manager. PBKDF2 + AES-256-GCM for channels. Ed25519 signing with TTL exclusion. EncryptedSharedPreferences for key storage.

BLE Layer — Dual-Role GATT

Simultaneously GATT Server (peripheral/advertise) and GATT Client (central/scan). Nordic BLE library. 4-tier adaptive power management (3.3%–100% duty cycle).

Android UI (Jetpack Compose) └── ChatViewModel // StateFlow state └── AppStateStore // process singleton Service Layer └── MeshForegroundService └── BluetoothMeshService ├── BluetoothConnectionManager │ ├── GattServerManager // advertise │ ├── GattClientManager // scan │ └── PowerManager // duty cycle ├── EncryptionService │ └── NoiseEncryptionService │ ├── NoiseSessionManager │ └── NoiseChannelEncryption ├── PacketProcessor // actor/peer │ └── PacketRelayManager // TTL+prob ├── FragmentManager // 512B chunks ├── SecurityManager // Ed25519+dedup ├── PeerManager // lifecycle+RSSI └── StoreForwardManager // 12hr cache Optional Internet Layer └── NostrManager → OkHttp → Nostr relays └── Arti/Tor // SOCKS5:9060 (optional)

Packet Wire Format

V1 Header — 13 bytes
[version:1][type:1][TTL:1]
[timestamp:8][flags:1][payloadLen:2]

Flags: HAS_RECIPIENT·0x01
HAS_SIGNATURE·0x02
IS_COMPRESSED·0x04
HAS_ROUTE·0x08
V2 Header — 15 bytes (files >64KB)
[version:1][type:1][TTL:1]
[timestamp:8][flags:1][payloadLen:4]

Types: ANNOUNCE·0x01 MESSAGE·0x02
NOISE_HANDSHAKE·0x10
NOISE_ENCRYPTED·0x11
FRAGMENT·0x20 FILE_TRANSFER·0x22
05 — Technology Stack

Production-Grade Tools,
Zero Compromises

Language & Build
Kotlin 2.2.0JVM 1.8 target
Gradle 8.13AGP 8.10.1
SDK 35minSdk 26 (Android 8.0)
Kotlin CoroutinesActors, Flow, StateFlow
UI & Architecture
Jetpack ComposeMaterial Design 3
Single-Activity MVVMStateFlow reactive
Room DatabaseChat persistence
Navigation ComposeScreen routing
Bluetooth & Networking
Nordic BLE LibraryDual-role GATT
OkHttpWebSocket / Nostr
Arti (Tor/JNI)Optional Rust Tor
Google Play LocationGeohash channels
Cryptography
BouncyCastleX25519, Ed25519, PBKDF2
Google TinkAES-256-GCM
EncryptedSharedPrefsHardware-backed keys
Custom Noise XXFull implementation
Audio & Media
MediaRecorderAAC-LC 16kHz/20kbps
MediaCodecPCM decode / waveform
ExifInterfaceImage orientation
ZXing + MLKitQR code verify
Full Algorithm Inventory (32 algorithms across 8 categories)
Noise Protocol XX
Curve25519 / X25519
ChaCha20-Poly1305
AES-256-GCM
Ed25519 Signatures
PBKDF2-HMAC-SHA256
HKDF Key Derivation
PKCS#7 Padding
Sliding Nonce Window
Epidemic Flooding (TTL)
Golomb-Coded Sets
Store-and-Forward DTN
Packet Fragmentation
DEFLATE Compression
TLV File Encoding
AAC-LC Audio Codec
RMS Waveform Analysis
Geohash Z-Order Curve
Coroutine Actor Model
RSSI-based Eviction
06 — App Screens

The Interface

AN
anon_8489
Noise XX · Encrypted
Any news from the city?
14:21
Nothing — network is down
14:22
Using Ungrid on BLE only?
14:22
Yes. Zero servers.
14:23
E2E encrypted too?
14:23
Always. Noise XX per peer.
14:23
+
Message…

Private Chat — Noise XX Encrypted

Each private conversation is protected by a unique Noise XX session. The lock icon and "Encrypted" label confirm an active session key. WhatsApp-style message bubbles replace the original terminal interface.

  • Navy sent bubbles (#0A2D6E) / dark received bubbles (#1E2B3F) — Ungrid brand palette
  • Delivery tick marks (sent → delivered → read)
  • Green voice call button (top right) initiates a BLE voice call
  • Microphone button (bottom right) records a voice note
  • Attachment picker ( + ) for images and documents
Broadcast Chat Screen
Public · Unencrypted · All peers

Broadcast Chat — Mesh-Wide Channel

The broadcast channel reaches every node in the mesh. Packets are signed with Ed25519 but not encrypted — anyone in range can read. The peer list in the sidebar shows all discovered nodes with RSSI indicators.

  • Real-time peer discovery — nodes appear as they come in range
  • RSSI signal strength indicator per peer
  • TTL counter visible in debug mode showing hop count
  • Adaptive relay probability shown in network status bar
AN
anon8489
● IN_CALL

Voice Call — BLE Real-Time Audio

Live bidirectional voice calls over Bluetooth Low Energy. AAC-LC encoded at 16kHz/20kbps — sufficient for clear speech within BLE's power budget. Call signaling is itself Noise-encrypted.

  • Call state machine: IDLE → CALLING → RINGING → IN_CALL
  • AAC-LC codec: 20kbps, walkie-talkie quality over BLE
  • Mute, speaker, and end-call controls
  • Call metadata (who called whom) is end-to-end encrypted
  • Caller ID verified via Noise session (no spoofing)
CONVERSATIONS
AN
anon8489
Hello there!
00:16
#
#general
Public broadcast
3
#secure-room
Password protected

Conversation List — Unified Inbox

All conversations — private peers, broadcast channels, and password-protected rooms — in a single persistent list. Chat history survives app restarts via Room Database.

  • Private chats with last message preview and unread badge
  • Public broadcast channel (#general)
  • Password-protected channels (PBKDF2-derived key)
  • Offline peers remain visible in sidebar (greyed out)
  • Conversation history persisted via Room DB
07 — Demo for the Jury

See It In Action

Live Mesh Simulation — Epidemic Routing with TTL
Click "Start" to watch a mesh packet propagate
Idle   Active   Reached   Source/Dest

Live Demo Script

  1. Launch Open Ungrid on Device A and Device B. Both appear in each other's peer list within ~3 seconds via BLE ANNOUNCE packets.
  2. Send Text Type a message on Device A. Observe Noise XX handshake completes, then the encrypted message appears on Device B.
  3. Send Image Tap (+) → pick a photo. Watch the EXIF correction + 512px downscale + JPEG compression pipeline execute, then the image appear on Device B.
  4. Voice Note Hold the microphone button. Record 5 seconds. Release. The waveform visualizer plays back the waveform before sending.
  5. Voice Call Tap the phone icon (top right in private chat). Device B shows incoming call. Accept → live bidirectional audio over BLE.
  6. Walk Away Move Device B out of range. Observe it moves to "offline" in sidebar. Send a message — it's cached. Move back in range — message delivered.

Technical Metrics

30–70%
DEFLATE compression on text
3–5×
Image size reduction
~100ms
Per PBKDF2 brute-force guess
1024-bit
Nonce replay window
08 — Challenges

What Was Hard to Build

BLE Layer · Critical Bug

Silent Packet Loss — WRITE_TYPE_NO_RESPONSE

After modifying the BLE write path, the Noise XX handshake was silently failing. Packets were being sent but dropped on the receiver with no error. Root cause: WRITE_TYPE_NO_RESPONSE fires-and-forgets without any acknowledgment. Single character change to WRITE_TYPE_DEFAULT restored ACK-based delivery and full handshake completion.

// Before (silent packet loss):
gatt.writeCharacteristic(char, data, BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE)

// After (ACK-reliable delivery):
gatt.writeCharacteristic(char, data, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT)
Protocol Design · Non-Obvious

Ed25519 Signing with TTL Exclusion

Every relay node must decrement the TTL to prevent infinite loops — but a naive implementation would invalidate the sender's Ed25519 signature. The solution: the TTL field is deliberately excluded from the signed data. Any relay can safely decrement TTL; the signature still verifies against the original sender's identity. This is a subtle but critical protocol invariant that required careful design.

Audio System · Real-Time Constraint

Voice Calls over BLE Fragmentation

A 20ms AAC audio frame can exceed 469 bytes (the BLE fragment payload limit), meaning a single audio chunk spans multiple BLE packets. These arrive out-of-order on a mesh. The fragment reassembly timeout (30s) is far too slow for real-time audio. Solved by implementing a dedicated audio reassembly path with a per-call sliding buffer keyed by sequence number.

Image Processing · Android Gotcha

EXIF Orientation — Camera Photos Arrive Rotated

Android camera apps embed rotation metadata in EXIF rather than rotating the actual pixels. Without correction, every photo sent through the app would appear rotated 90° on the receiver. The fix requires reading the EXIF orientation tag with ExifInterface and applying a Matrix.postRotate() transform before encoding.

Testing · Physical Constraint

Two Physical Devices Required for Every Test

The Android emulator cannot simulate BLE scanning or advertising. Every meaningful end-to-end test required two physical phones. Cross-platform compatibility testing with the iOS reference implementation required borrowing an iPhone. This limited iteration speed and made CI/CD impossible for BLE-specific features.

Scope Management · Time Pressure

Voice Calling — Almost Cut

Real-time voice calling was nearly descoped due to the audio fragmentation complexity. Implemented in the final two weeks of development. The key insight was treating the call as a lightweight state machine with a separate audio reassembly pipeline rather than routing call audio through the general packet processor.

09 — Conclusion & Perspectives

What Was Built.
Where It Goes Next.

What Was Achieved

  • Fully functional offline encrypted Android messaging app
  • Added images, documents (10 MB), voice notes, and live voice calls to the base protocol
  • 100% binary protocol compatibility with the iOS reference implementation verified
  • 5 independent crypto algorithms integrated into one system
  • Fixed critical packet-loss regression (WRITE_TYPE_NO_RESPONSE bug)
  • Complete WhatsApp-style UI redesign in Jetpack Compose
  • Room DB persistence — chats survive app restart

Core Competencies Demonstrated

  • Cryptography: Noise XX, ChaCha20, AES-GCM, Ed25519, PBKDF2
  • Computer Networks: Epidemic routing, TTL flooding, MTU fragmentation, DTN
  • Operating Systems: Coroutine actors, concurrency without locks, process lifecycle
  • Data Structures: Golomb-Coded Sets, sliding nonce bitmap, dedup hash map
  • Mobile Computing: Android BLE stack, foreground services, Jetpack Compose
  • Software Engineering: MVVM, clean architecture, component hierarchy

Future Work

Async Voice Relay

Buffer and forward audio packets for peers not currently in BLE range — extending voice notes to true store-and-forward.

Group Voice Calls

Coordinate multi-party audio sessions via a distributed call coordinator — point-to-point only today.

Delivery Receipts

ACK packets for message delivery confirmation across mesh relays — today best-effort only.

File Transfer Resume

Partial fragment retry on interrupted large file transfers — currently requires a full resend.

Security Audit

External formal verification of the custom Noise XX implementation before production deployment.

Emergency Response

Pre-deployed mesh relay nodes (Raspberry Pi / cheap Android) for disaster zones — first responders connect instantly.

Ungrid iOS v2

A native iOS counterpart bringing images, documents, voice notes, and live voice calling to iPhone users — full feature parity with this Android fork over the shared binary protocol.