# Geohash-Based Location Channels in BitChat: Privacy-Preserving Proximity Messaging

> Discover geohash-based location channels in BitChat for private proximity messaging. Communicate with nearby users securely without revealing exact GPS locations.

- Repository: [permissionlesstech/bitchat](https://github.com/permissionlesstech/bitchat)
- Tags: deep-dive
- Published: 2026-08-03

---

**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)](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Nostr/GeoRelayDirectory.swift). This struct combines two properties:

- **`level`** — A categorical precision indicating geographic scope
- **`geohash`** — 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.

```swift
// 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)](https://github.com/permissionlesstech/bitchat/blob/main/docs/GeohashPresenceSpec.md).

### GeohashPresenceService: Heartbeat Management

The [[`GeohashPresenceService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/GeohashPresenceService.swift)](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/GeohashPresenceService.swift) implementation handles three critical functions:

1. **Scheduled broadcasting** — Throttles presence events to prevent network spam
2. **Event aggregation** — Collects incoming presence beacons from peers
3. **UI publication** — Emits consolidated online counts per channel

```swift
// 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/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/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)](https://github.com/permissionlesstech/bitchat/blob/main/bitchatTests/Services/LocationStateManagerTests.swift) demonstrates complete channel selection flows:

```swift
// 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`](https://github.com/permissionlesstech/bitchat/blob/main/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`](https://github.com/permissionlesstech/bitchat/blob/main/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](https://github.com/permissionlesstech/bitchat/blob/main/docs/GeohashPresenceSpec.md), 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.