Geohash-Based Location Channels in BitChat: Privacy-Preserving Proximity Messaging
Geohash-based location channels in BitChat enable users to communicate with nearby peers by mapping geographic coordinates to discrete, encrypted chat channels without exposing exact GPS positions.
BitChat implements geohash-based location channels as a core mechanism for location-aware, decentralized messaging. The system converts latitude/longitude pairs into compact string identifiers—geohashes—that function as channel addresses on the Nostr protocol. Users join channels corresponding to their physical location at a chosen precision level, enabling regional communication while preserving privacy through deliberate coordinate obfuscation.
How Geohash Channels Work in BitChat
The GeohashChannel Data Model
At the center of BitChat's location system is GeohashChannel, a lightweight value object defined in [bitchat/Nostr/GeoRelayDirectory.swift](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Nostr/GeoRelayDirectory.swift). This struct combines two properties:
level— A categorical precision indicating geographic scopegeohash— The actual geohash string encoding the location
The GeohashChannelLevel enum defines supported precisions in the same file, with each variant specifying a fixed character count (precision) that directly maps to physical area size.
// City-level channel (5-character geohash ≈ 2.4km × 4.8km)
let cityChannel = GeohashChannel(level: .city, geohash: "u4pruy")
// Building-level channel (9-character geohash ≈ 4.8m × 4.8m)
let buildingChannel = GeohashChannel(level: .building, geohash: "u4pruyfku")
Higher precision means smaller physical areas and more specific channels.
Presence Broadcasting Without Privacy Leakage
Ephemeral Nostr Events (Kind 9041)
BitChat uses ephemeral Nostr events to advertise user presence without persistent tracking. Every few seconds, clients broadcast kind 9041 "geohash presence" events containing only the current geohash string—not coordinates, not identity metadata. These events expire quickly, creating a transient footprint.
The formal protocol specification lives in [docs/GeohashPresenceSpec.md](https://github.com/permissionlesstech/bitchat/blob/main/docs/GeohashPresenceSpec.md).
GeohashPresenceService: Heartbeat Management
The [GeohashPresenceService.swift](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/GeohashPresenceService.swift) implementation handles three critical functions:
- Scheduled broadcasting — Throttles presence events to prevent network spam
- Event aggregation — Collects incoming presence beacons from peers
- UI publication — Emits consolidated online counts per channel
// Broadcast presence for selected channel
try NostrProtocol.createGeohashPresenceEvent(
geohash: cityChannel.geohash,
privateKey: myNostrKey
)
// Subscribe to aggregated presence updates
geohashPresenceService.presencePublisher
.sink { presence in
print("🗺️ \(cityChannel.geohash) – online: \(presence.onlineCount)")
}
.store(in: &cancellables)
Only the online count reaches the UI—never individual participant lists unless users explicitly message in the channel.
Activity Tracking and Channel Lifecycle
GeohashChatActivityTracker
The [GeohashChatActivityTracker.swift](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/GeohashChatActivityTracker.swift) component monitors engagement patterns across location channels. It records:
- Message timestamps
- Reaction activity
- User join/leave events
This data drives two UX decisions: highlighting "active" locations in the interface and automatically ceasing presence broadcasts for stale channels—reducing unnecessary network traffic and battery consumption.
Geo-Optimized Relay Selection
GeoRelayDirectory
To minimize latency and distribute load, [GeoRelayDirectory.swift](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Nostr/GeoRelayDirectory.swift) maps geohash prefixes to geographically proximate Nostr relays. Rather than flooding the global relay mesh with location-specific traffic, BitChat targets relays physically close to the hashed region.
This partitioning strategy ensures:
- Lower latency for real-time messaging
- Reduced bandwidth per relay
- Horizontal scalability as user density increases
Channel Integration Throughout the App
Location channels integrate seamlessly with BitChat's broader channel system via the ChannelID enum. The .location(GeohashChannel) case allows unified handling alongside public, private, and direct message channels.
Test coverage in [bitchatTests/Services/LocationStateManagerTests.swift](https://github.com/permissionlesstech/bitchat/blob/main/bitchatTests/Services/LocationStateManagerTests.swift) demonstrates complete channel selection flows:
// Select a location channel through the state manager
locationStateManager.select(.location(cityChannel))
UI components including the people list and map view filter all data—messages, presence, reactions—through the currently selected GeohashChannel.
Privacy and Security Guarantees
BitChat's geohash channel design provides multi-layered privacy protection:
| Mechanism | Protection |
|---|---|
| Coarse precision levels | Exact coordinates never transmitted |
| Ephemeral presence events | No persistent location history on relays |
| Aggregated counts only | Individual identities hidden until explicit messaging |
| Geohash—not GPS—broadcasts | Third parties cannot reverse-engineer precise position |
Even at building-level precision, the geohash represents an area, not a point.
Summary
- GeohashChannel (
GeoRelayDirectory.swift) is the core identifier combining precision level and geohash string - GeohashPresenceService manages ephemeral kind 9041 events for privacy-preserving online indicators
- GeohashChatActivityTracker monitors channel engagement to optimize broadcast behavior
- GeoRelayDirectory routes traffic through geographically optimal relays
- The
.location()channel type integrates with all standard BitChat UI components - Privacy emerges from coarse geohashes, ephemeral events, and aggregated presence data
Frequently Asked Questions
What precision levels does BitChat support for geohash channels?
BitChat defines precision through the GeohashChannelLevel enum in GeoRelayDirectory.swift, with variants like .region, .city, and .building. Each maps to a fixed geohash character count—more characters mean smaller physical areas. The exact level definitions depend on the deployed version, but the architecture supports arbitrary precision granularity.
Can other users see my exact location in a geohash channel?
No. The system broadcasts only your geohash string, which encodes a rectangular area, not GPS coordinates. At city-level precision, a single geohash covers several square kilometers. Other users see only an online count unless you actively send messages revealing your participation.
How does BitChat prevent location channel spam?
Three mechanisms work together: ephemeral events (kind 9041) expire automatically rather than persisting; GeohashPresenceService throttles broadcast frequency; and GeohashChatActivityTracker halts presence broadcasts for inactive channels. Additionally, relay selection in GeoRelayDirectory partitions traffic geographically rather than globally.
Are geohash presence events stored on Nostr relays?
No. Per the Geohash Presence Specification, these events use Nostr's ephemeral event handling (kind 9041). They are transmitted to connected clients but not retained by relays, eliminating persistent location trails.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →