Ghost Chat is a fully off-grid, hardware-based encrypted communication system that operates with zero internet dependency. It physically bypasses all cell towers, ISPs, and Wi-Fi monitoring by transmitting AES-256 encrypted messages across a LoRa radio mesh network using ESP32 microcontrollers.
No server. No cloud. No trace. Just radio.
| Point | Detail |
|---|---|
| What is it? | A complete chat system that works without internet — uses radio waves instead |
| How far? | LoRa radio reaches up to 10 km line-of-sight, 2–5 km through buildings |
| Is it secure? | AES-256 encryption — same grade used by military and banking |
| Who can read messages? | Only people with the correct room password — not even the relay nodes |
| What hardware? | Heltec WiFi LoRa 32 V2 (~₹1500/$15) — fits in a pocket |
| Can it be traced? | No — creates its own private WiFi network, doesn't touch the internet |
| What if someone finds the device? | Panic button (hardware) or duress password (software) wipes everything instantly |
| Storage? | 40KB per room on SPIFFS, or unlimited with MicroSD card module |
The system has two parts that work together:
A Heltec WiFi LoRa 32 V2 microcontroller that:
- Creates a private WiFi hotspot (
Ghost_Net) - Runs a full HTTP web server on
192.168.4.1 - Stores encrypted messages in SPIFFS/SD filesystem
- Broadcasts & relays messages via LoRa radio at 866 MHz
- Shows live status on a built-in OLED display
A native Kotlin Android application featuring:
- AES-256 end-to-end encryption (client-side, before transmission)
- Glassmorphism dark-mode UI with smooth animations
- Voice memos with hardware noise suppression
- Burn-on-read messages, bomb images, emoji reactions
- QR code room sharing, radar visualization, and stealth mode
graph LR
subgraph "Your Phone"
A["Ghost Chat App<br>(AES-256 Encrypt)"]
end
subgraph "Your Node"
B["ESP32 + SX1276<br>WiFi AP: 192.168.4.1"]
end
subgraph "Radio Airwaves"
C(("LoRa 866 MHz<br>~10 km Range"))
end
subgraph "Remote Node"
D["Friend's ESP32"]
end
subgraph "Friend's Phone"
E["Friend's App<br>(AES-256 Decrypt)"]
end
A -- "Private WiFi<br>(HTTP API)" --> B
B -- "Encrypted LoRa<br>Broadcast" --> C
C -- "Zero-Knowledge<br>Relay" --> D
D -- "Private WiFi" --> E
| Feature | Description |
|---|---|
| AES-256 Encryption | All messages encrypted on-device before transmission; ESP32 never sees plaintext |
| Zero-Knowledge Relay | Nodes forward messages for other rooms but cannot decrypt them |
| Duress Password | Entering a special password (pink) silently wipes all data and formats the filesystem |
| Panic Button | Hold the hardware button for 3 seconds → instant full wipe |
| Stealth Calculator Mode | App disguises itself as a calculator — enter your custom PIN (configurable in Settings) to unlock |
| Custom Calculator PIN | Set your own numeric unlock code in Settings → Security (minimum 4 digits, default 1337) |
| Biometric Lock | Fingerprint/face unlock support via Android BiometricPrompt |
| Session Tokens | Each login generates a cryptographic 8-char hex token bound to IP + room hash |
| XSS Sanitization | All user input sanitized server-side to prevent injection attacks |
| Rate Limiting | Max 10 messages per minute per IP to prevent flooding |
| No Cloud, No Logs | Zero telemetry, zero analytics, zero external network calls |
| Feature | Description |
|---|---|
| Text Messages | Standard encrypted text with delivery receipts (✓✓) |
| Image Sharing | Upload & share images through the ESP32's filesystem |
| Voice Memos | Tap-to-record with VOICE_COMMUNICATION audio source for hardware noise suppression |
| Burn-on-Read | Messages marked BURN: are revealed on tap, then auto-destroyed after 3 seconds |
| Bomb Images | View-once images: tap to reveal for 5 seconds, then permanently destroyed |
| Disappearing Messages | EXP: protocol for time-limited messages that fade out |
| Emoji Reactions | Quick-react bar: 👍 ❤️ 😂 😮 😢 🔥 — reactions now display inline as pills below the target bubble (no standalone messages) |
| Reply Threading | Swipe-to-reply with quoted message preview — replies show an inline quote bar inside the bubble with sender name + content preview |
| Message Forwarding | Copy message to clipboard for pasting into another room |
| Message Starring | Star important messages for quick reference |
| Message Deletion | Delete locally or broadcast CMD:DEL to all clients — persists across app restarts |
| Pinned Messages | Pin important messages visible to the whole room |
| Typing Indicator | Real-time animated dots ("user is typing...") with auto-expiry |
| Search & Highlight | Search through message history with highlighted results |
| Feature | Description |
|---|---|
| LoRa Mesh | SX1276 radio at 866 MHz, SF7, 125 kHz bandwidth, 17 dBm TX power |
| Multi-Room | Infinite rooms via password hashing — each room gets isolated storage |
| LRU Room Cache | 5 active rooms cached in RAM; oldest evicted to filesystem on overflow |
| Auto-Trim | Messages auto-trimmed when room exceeds 40KB to prevent filesystem overflow |
| SD Card Support | Toggle #define USE_SD_CARD 1 for expanded storage using any MicroSD |
| OLED Dashboard | 128×64 OLED displays IP, users online, RAM usage, and LoRa RSSI in real-time |
| Captive Portal | DNS spoofing routes all connected devices to the Ghost Chat UI |
| Low Memory Protection | Auto-trims room data when free heap drops below 28KB |
| Online User Tracking | Heartbeat-based user presence (30s timeout) |
| Feature | Description |
|---|---|
| Chat Wallpaper | Subtle 45° dark navy-to-charcoal gradient replaces flat backgrounds — Telegram-style depth |
| ConstraintLayout Bubbles | Message bubbles rebuilt with ConstraintLayout for pixel-perfect timestamp packing and inline metadata |
| Floating Pill Input | Input bar is now a detached rounded pill with margins and elevation — feels like Telegram/WhatsApp |
| Inline Reaction Pills | Emoji reactions render as small floating pills below the message bubble |
| Inline Reply Quotes | Replies show a color-accented quote bar with sender name + preview inside the bubble |
| Glassmorphism Design | Dark frosted-glass UI with gradient mesh backgrounds |
| Message Animations | Telegram-style slide-up + scale-in on new messages |
| Send Button Pulse | Micro-animation bounce on tap |
| Connection Banner | Slide-down/up banner showing online/offline status |
| Online Dot Pulse | Green dot pulses when connection established |
| Animated Typing Dots | Instagram-style cycling dots (0→3, 400ms interval) |
| Reply Bar Animation | Smooth slide-up from bottom with fade-in |
| Fragment Transitions | Slide-up radar, scale-fade settings |
| QR Code Room Sharing | Generate QR with real room credentials for instant invite |
| Radar Visualization | Live LoRa node radar with RSSI-based distance mapping |
| Pull-to-Refresh | Swipe down to force-fetch latest messages |
| Haptic Feedback | System-wide vibration on key interactions |
yukti/
├── 📱 ghost_chat_android/ ← Native Android App (Kotlin)
│ ├── app/
│ │ ├── build.gradle.kts ← Dependencies & SDK config
│ │ └── src/main/
│ │ ├── AndroidManifest.xml ← Permissions & activities
│ │ ├── java/com/ghost/app/
│ │ │ ├── MainActivity.kt ← App entry, login flow
│ │ │ ├── ChatFragment.kt ← Main chat UI (490+ lines)
│ │ │ ├── ChatViewModel.kt ← State management, polling
│ │ │ ├── ChatMessage.kt ← Message data model
│ │ │ ├── MessageAdapter.kt ← RecyclerView adapter
│ │ │ ├── MessageActionSheet.kt ← Long-press context menu
│ │ │ ├── ChatItemAnimator.kt ← Message bubble animations
│ │ │ ├── GhostApi.kt ← HTTP API client
│ │ │ ├── GhostCrypto.kt ← AES-256 encryption
│ │ │ ├── GhostRepository.kt ← Message parsing & storage
│ │ │ ├── SecurePrefs.kt ← Encrypted preferences
│ │ │ ├── RadarFragment.kt ← LoRa radar visualization
│ │ │ ├── LoginFragment.kt ← Room login UI
│ │ │ ├── LoginViewModel.kt ← Login state
│ │ │ ├── SettingsFragment.kt ← Settings & QR generator
│ │ │ ├── StarredMessagesFragment.kt← Starred messages viewer
│ │ │ ├── StealthActivity.kt ← Calculator disguise
│ │ │ ├── BiometricHelper.kt ← Fingerprint/face auth
│ │ │ ├── SwipeReplyCallback.kt ← Swipe-to-reply gesture
│ │ │ ├── NetworkBinder.kt ← WiFi network binding
│ │ │ ├── PanicWipeHelper.kt ← Emergency data wipe
│ │ │ ├── ImageLoader.kt ← Async image loading
│ │ │ └── GlassHelper.kt ← Glassmorphism effects
│ │ └── res/
│ │ ├── anim/ ← 6 animation XMLs
│ │ ├── drawable/ ← 14 drawable XMLs
│ │ ├── layout/ ← 11 layout XMLs
│ │ └── values/ ← Themes, colors, strings
│ ├── build.gradle.kts
│ ├── settings.gradle.kts
│ └── gradle.properties
│
├── 🔌 ghost_chat_v3_sx1276/ ← ESP32 Firmware (C++)
│ └── ghost_chat_v3_sx1276.ino ← Complete firmware (747 lines)
│
├── 📦 Ghost_Chat.apk ← Pre-built debug APK (install directly)
├── 📄 README.md ← This file
└── 📋 Ghost_Chat_Final_Report.docx ← Project documentation
#define USE_SD_CARD 0 // 0 = SPIFFS, 1 = MicroSD card
WIFI_SSID = "Ghost_Net" // Hotspot name
WIFI_PASS = "ghost1234" // Hotspot password
DURESS_PASS = "pink" // Wipes everything silently
ADMIN_PASS = "ghostadmin99" // Admin panel access
NODE_ID = "NODE-A" // LoRa node identity
LORA_FREQ = 866E6 // 866 MHz (India band)
LORA_POWER = 17 // Transmit power in dBm
MAX_ROOM_BYTES = 40000 // 40KB per room storage
MAX_ACTIVE_ROOMS = 5 // Rooms cached in RAMLoRa SX1276: SCK=5, MISO=19, MOSI=27, SS=18, RST=14, DIO0=26
OLED SSD1306: SDA=4, SCL=15, RST=16
Panic Button: GPIO 0 (built-in BOOT button)
SD Card (opt): CS=13 (VSPI)
| Endpoint | Method | Description |
|---|---|---|
/login |
GET | Authenticate with room hash + password → returns session token |
/send |
GET | Send encrypted message (requires tok param) |
/read |
GET | Fetch all messages for current room + typing status + online count |
/typing |
GET | Broadcast typing indicator (auto-expires after 3s) |
/online |
GET | Get count of online users (30s heartbeat window) |
/upload |
POST | Upload image/file to filesystem |
/download |
GET | Download file by name |
/delete_file |
GET | Delete uploaded file |
/pin |
GET | Pin a message for the room |
/getpin |
GET | Get current pinned message |
/clear |
GET | Clear all messages in current room |
/status |
GET | System status (RAM, uptime, WiFi clients, LoRa, filesystem) |
/lorastatus |
GET | LoRa radio status (RSSI, last packet time) |
/ram |
GET | Free heap memory in KB |
/admin |
GET | Admin panel JSON (requires admin password) |
/admin_wipe |
POST | Full system wipe (requires admin password) |
- Arduino IDE 2.x with ESP32 board manager installed
- Android Studio Ladybug+ (for building from source) or just install the APK
- Heltec WiFi LoRa 32 V2 board (or compatible ESP32 + SX1276)
- Libraries:
LoRa,U8g2,WiFi,WebServer,SPIFFS,SD(all available in Library Manager)
- Open
ghost_chat_v3_sx1276/ghost_chat_v3_sx1276.inoin Arduino IDE - Select Board: Heltec WiFi LoRa 32 (V2)
- Edit configuration constants at the top (WiFi name, passwords, frequency)
- Set partition scheme to "Minimal SPIFFS (1.9MB APP with OTA/190KB SPIFFS)" for max app space, or "Default 4MB with SPIFFS" for more storage
- Click Upload → Wait for flash to complete
- The OLED will display
Ghost Chat V4+ IP address when ready
Option A: Install pre-built APK (fastest)
- Transfer
Ghost_Chat.apkto your Android phone - Enable "Install from unknown sources" in Settings
- Tap the APK to install
Option B: Build from source
- Open
ghost_chat_android/folder in Android Studio - Sync Gradle dependencies
- Build → Run on device or
./gradlew assembleDebug
- Power on the ESP32 → OLED shows
Ghost_Netand IP - On your phone, connect to WiFi network
Ghost_Net(password:ghost1234) - Open the Ghost Chat app
- Enter a username, set a room password → this creates an encrypted room
- Share the room password with friends → they join the same encrypted channel
- Start chatting! All messages are AES-256 encrypted end-to-end
┌─────────────────────────────────────────────────────────────┐
│ LoRa Packet Format │
│ GC|<NODE_ID>|<ROOM_HASH>|<ENCRYPTED_PAYLOAD> │
│ │
│ Example: │
│ GC|NODE-A|a3f8c2|U2FsdGVkX1+vup... │
│ │
│ • NODE_ID prevents echo loops (ignores own packets) │
│ • ROOM_HASH routes to correct room (zero-knowledge) │
│ • PAYLOAD is AES-256 ciphertext (node can't read it) │
└─────────────────────────────────────────────────────────────┘
Mesh Relay Logic:
- Node A sends a message → broadcasts via LoRa
- Node B receives the packet → checks if
NODE_IDis different from its own - Node B appends the encrypted payload to the matching room file
- Any phone connected to Node B can now read (and decrypt) the message
- The relay node never knows the content — it only knows the room hash
| Protocol Prefix | Format | Description |
|---|---|---|
| (none) | sender|time|text |
Standard text message |
IMG: |
sender|time|IMG:filename.jpg |
Image attachment |
VOX: |
sender|time|VOX:audio.m4a |
Voice memo |
BURN: |
sender|time|BURN:secret text |
Burn-on-read (auto-destroys) |
CMD:DEL |
sender|time|CMD:DEL:msgId |
Delete message command |
REACT: |
sender|time|REACT:<sha256_hash>§emoji |
Emoji reaction — targets message by deterministic SHA-256 hash |
REPLY: |
sender|time|REPLY:<hash>|<sender>|<preview>§<content> |
Quoted reply — links to target via hash |
EXP: |
sender|time|EXP:epoch|text |
Disappearing message |
###TYPE: |
(appended to /read response) | Typing indicator |
###ONLINE: |
(appended to /read response) | Online user count |
Every message gets a unique 8-character hex ID generated by:
SHA-256( sender + "|" + time + "|" + content ) → first 8 hex chars
This ID is deterministic — the same message content on any device produces the same hash. This solves the distributed systems problem: reactions and replies work perfectly across all nodes in the mesh, even if devices join at different times or receive messages in different order.
| Parameter | Value |
|---|---|
| Max message length | 1,500 characters |
| Max room storage | 40 KB per room (SPIFFS) / Unlimited (SD card) |
| Active rooms in RAM | 5 (LRU eviction to filesystem) |
| Max sessions | 8 concurrent |
| Max tracked users | 10 |
| Rate limit | 10 messages/minute/IP |
| User online timeout | 30 seconds heartbeat |
| Typing indicator timeout | 3 seconds auto-clear |
| LoRa range | ~10 km LOS / ~2-5 km urban |
| LoRa packet max | 200 bytes per broadcast |
| Burn message reveal | 3 seconds (text) / 5 seconds (images) |
| Min voice memo length | 1 second |
| OLED refresh rate | Every 3 seconds |
The firmware already has built-in SD card support — you don't need to buy any extra module if you just want more software-side storage. Here's how:
Edit the firmware constants:
#define MAX_ROOM_BYTES 80000 // 80KB per room
#define MAX_ACTIVE_ROOMS 8 // More rooms in RAM
⚠️ KeepMIN_FREE_HEAPat 28000 to prevent crashes
- Connect any MicroSD module to the VSPI pins (CS = GPIO 13)
- Change
#define USE_SD_CARD 1in the firmware - Re-flash → now uses SD instead of SPIFFS (gigabytes of storage)
The firmware already auto-trims old messages when a room exceeds MAX_ROOM_BYTES. The oldest messages are removed first, keeping the most recent conversation intact.
Hold the BOOT button (GPIO 0) for 3 seconds:
- OLED shows countdown:
HOLD TO WIPE → 3... 2... 1... - All rooms, sessions, users, and files are permanently erased
- SPIFFS is formatted (all data destroyed)
- OLED confirms:
WIPED CLEAN
Enter the duress password (pink by default) as the room password:
- Server responds
WIPEDto the client - All data silently destroyed
- No visual indication to bystanders — looks like a normal failed login
In Settings, toggle "Calculator Disguise":
- App icon changes to a calculator
- App name changes to "Calculator"
- Opening the app shows a fully working calculator (add, subtract, multiply, divide)
- Enter your custom secret PIN +
=to unlock Ghost Chat - Default PIN is
1337— change it in Settings → Security → Calculator Unlock PIN - Minimum 4 digits required; stored in EncryptedSharedPreferences
- App becomes invisible in recent apps
| Version | Changes |
|---|---|
| V1 | Basic ESP32 + React Native prototype |
| V2 | Added LoRa mesh relay, SPIFFS storage |
| V3 | SX1276 chip support, captive portal |
| V4 | Dynamic rooms, SD card support, admin panel |
| V5 | Native Android (Kotlin) rewrite, glassmorphism UI |
| V6 | AES-256 encryption, burn messages, QR sharing, biometrics |
| V7 | Stable message IDs, fixed deletion, radar, mic UX, animations |
| V7.1 | Fixed reaction display, bomb image/text, long-press reliability, 40KB rooms |
| V8.0 | Deterministic SHA-256 message IDs — perfect sync across distributed mesh. ConstraintLayout bubbles with packed timestamps. Inline reaction pills & reply quote bars inside bubbles. Floating pill input with chat wallpaper gradient. Custom calculator PIN configurable in Settings. Protocol additions: REPLY: wire format. 6 bug fixes including duplicate hideReplyBar, continue in forEachIndexed, and missing recording bar views. |
This is an educational/research project. Contributions welcome:
- Fork the repository
- Create a feature branch
- Submit a pull request with description
This project is intended for educational, research, emergency, and off-grid communication purposes only. The developers assume no responsibility for misuse. Operating on unlicensed radio frequencies may be subject to local regulations — check your country's spectrum allocation before deploying. In India, the 866 MHz ISM band is available for low-power devices.
Built with 💀 by Team Ghost
No servers. No internet. No trace.