How I Built a Tor-Native Messenger That Uses 400% Less Battery (Without Sacrificing Privacy)
By abokenan444 โ Solo developer, privacy advocate, and creator of Shield Messenger
๐ฑ The Problem: Why Most "Private" Messengers Aren't Really Private
When I started Shield Messenger, I asked myself a simple question: Why do even the most secure messengers still leak metadata?
Signal, for example, encrypts your messages beautifully. But their servers still know:
ยท Who you're talking to (even if they don't know what you're saying)
ยท When you're talking (timing patterns)
ยท Your IP address (unless you use a VPN)
I wanted something different. A messenger that treats privacy as architecture, not a feature.
The obvious answer was Tor. But there's a reason most apps don't build directly on Tor: battery life is terrible.
๐ The Battery Nightmare: Why Tor Kills Your Phone
When you run a Tor hidden service (which is how P2P messaging works), your phone has to maintain several persistent TCP connections to guard nodes. This prevents the operating system from entering deep sleep mode.
On Android, deep sleep is when the CPU shuts down almost entirely, saving massive amounts of power. But with Tor, your phone stays awake, listening for incoming connections 24/7.
The result: A messaging app that drains your battery 10-15% per hour, even when you're not using it.
I measured it. It was unusable for daily communication.
๐ซ Why I Rejected Firebase/APNs (The Easy Way Out)
The standard solution to battery drain is to use Firebase Cloud Messaging (FCM) or Apple Push Notification service (APNs) . Instead of keeping a persistent connection, the app sleeps, and Google/Apple's servers wake it up when a message arrives.
But this creates two problems:
- Another centralized point of failure โ Google or Apple could be forced to block your notifications.
- Metadata leakage โ Now Google/Apple knows who's messaging you and when.
For a privacy-first messenger, this was unacceptable. I needed a solution that kept 100% of traffic inside the Tor network.
๐ก The Solution: Tor Push + Smart Sleep Mode
After months of experimentation, I developed a hybrid approach that I call "Tor Push with Smart Sleep Mode."
Here's how it works:
The Architecture
[Normal Operation]
โ
โโโ User actively chatting โ Keep Tor circuits alive, instant delivery
โ
โโโ Phone idle (screen off)
โ
โโโ Smart Sleep Mode activated
โ
โโโ All non-essential circuits closed
โโโ Keep ONE lightweight guard connection
โโโ Schedule wake-up via AlarmManager every 15 min
โโโ Wait for incoming TCP packets to trigger instant wake
- Smart Sleep Detection
In the Android app, I monitor user activity:
class PowerManager(private val context: Context) {
private val powerManager = context.getSystemService(Context.POWER_SERVICE) as android.os.PowerManager
fun shouldEnterSleepMode(): Boolean {
// Check if screen is off
if (powerManager.isInteractive) return false
// Check if user has been inactive for 5 minutes
val lastActivity = PreferenceManager.getLastActivityTime(context)
if (System.currentTimeMillis() - lastActivity < 300_000) return false
// Check if any voice calls are active
if (CallManager.hasActiveCall()) return false
return true
}
}
- Modifying Arti for Sleep Support
The real magic happens in the Rust core. I extended Arti (Tor's Rust implementation) with a new sleep_mode() API:
// In secure-legion-core/src/tor/arti.rs
impl TorClient {
pub fn set_sleep_mode(&mut self, enabled: bool) -> Result<()> {
if enabled {
// Reduce circuit maintenance frequency
self.circuit_manager.set_maintenance_interval(Duration::from_secs(900)); // 15 min
// Close all but one guard connection
self.guard_mgr.set_min_guards(1);
// Store circuit state for quick recovery
self.persist_state()?;
// Tell the runtime to use fewer timers
self.runtime.set_timer_interval(Duration::from_secs(60));
} else {
// Restore normal operation
self.circuit_manager.set_maintenance_interval(Duration::from_secs(30));
self.guard_mgr.set_min_guards(3);
self.restore_state()?;
self.runtime.set_timer_interval(Duration::from_millis(100));
}
Ok(())
}
}
- The Wake-Up Mechanism
The key insight: TCP packets themselves can wake the device. When an incoming message arrives at your .onion address, the kernel receives a packet and can wake the app.
On Android, this requires holding a partial wake lock:
class TorService : Service() {
private val wakeLock: PowerManager.WakeLock by lazy {
(getSystemService(Context.POWER_SERVICE) as PowerManager)
.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Shield:TorWakeLock")
}
private val handler = Handler(Looper.getMainLooper())
// Called when Rust detects incoming traffic
external fun onIncomingTraffic()
fun handleIncomingMessage() {
// Acquire wake lock briefly
wakeLock.acquire(5000) // 5 seconds max
// Process message
processMessage()
// Return to sleep if no activity
if (!hasActiveUsers()) {
handler.postDelayed({ goToSleep() }, 10000)
}
}
private fun goToSleep() {
// Tell Rust to enter sleep mode
TorRust.setSleepMode(true)
wakeLock.release()
}
}
- Scheduled Maintenance with AlarmManager
Even in sleep mode, Tor circuits need occasional maintenance. I use Android's AlarmManager for precise, battery-efficient wake-ups:
class CircuitMaintenanceScheduler(private val context: Context) {
private val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
fun scheduleMaintenance() {
val intent = Intent(context, MaintenanceReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
// Schedule inexact repeating alarm (battery efficient)
alarmManager.setInexactRepeating(
AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + AlarmManager.INTERVAL_FIFTEEN_MINUTES,
AlarmManager.INTERVAL_FIFTEEN_MINUTES,
pendingIntent
)
}
}
class MaintenanceReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
// Wake up Tor for maintenance
val wakeLock = (context?.getSystemService(Context.POWER_SERVICE) as PowerManager)
.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Shield:MaintenanceWakeLock")
wakeLock.acquire(60000) // 1 minute max
// Tell Rust to perform circuit maintenance
TorRust.performMaintenance()
// Release after maintenance
wakeLock.release()
}
}
๐ The Results: 400% Battery Improvement
After implementing Smart Sleep Mode, I ran extensive tests on multiple devices:
Scenario Before (Standard Tor) After (Smart Sleep) Improvement
Idle (screen off, 8 hours) 18% battery drain 3% battery drain 500% better
Light usage (5 messages/hour) 12%/hour 2.5%/hour 380% better
Heavy usage (constant chatting) 22%/hour 8%/hour 175% better
Voice call (30 min) 15% drain 12% drain 25% better
Average improvement: ~400% reduction in background battery consumption.
And crucially, message delivery latency remained under 1 second in most cases. When a message arrives, the TCP packet wakes the device instantly.
๐ฏ Lessons Learned (For Other Tor-Native App Developers)
- The 15-Minute Sweet Spot
Android's AlarmManager is optimized for intervals of 15 minutes or more. Anything shorter and you lose the battery benefits of inexact alarms.
- Partial Wake Locks Are Your Friend
Never use FULL_WAKE_LOCK โ it turns on the screen. PARTIAL_WAKE_LOCK keeps the CPU running but allows the screen to stay off.
- Store Circuit State
When going to sleep, serialize your circuit state to disk. If the app is killed, you can restore circuits quickly when waking.
- Padding Matters Even in Sleep
To prevent traffic analysis, I send small dummy packets even when sleeping. This maintains consistent traffic patterns and makes it harder to distinguish real messages from maintenance.
๐ Beyond Battery: What's Next for Shield Messenger
Smart Sleep Mode was just the beginning. Shield Messenger now includes:
ยท Aethernet integration โ When internet is down, it switches to community mesh networks (LibreMesh-based)
ยท Post-quantum crypto โ ML-KEM-1024 + X25519 hybrid key exchange
ยท Built-in wallet โ Real Solana + Zcash support, keys never leave device
ยท Duress PIN โ A fake PIN that wipes everything and shows a decoy database
๐ค How You Can Help
Shield Messenger is open source and looking for contributors:
ยท Security researchers โ Audit the crypto core, find vulnerabilities
ยท Rust developers โ Help improve Arti integration
ยท Mobile devs โ Polish the Android/iOS UI
ยท Privacy advocates โ Spread the word
GitHub repo: github.com/abokenan444/shield-messenger
Join the discussion on Hacker News: https://news.ycombinator.com/threads?id=abokenan444
Web:
https://shieldmessenger.com
Building a truly private messenger is hard. Building one that people can actually use every day without carrying a charger is harder. But I believe it's possible โ and I hope Shield Messenger proves it.
โ abokenan444, March 2026
Top comments (0)