# Understanding the Geohash Precision Hierarchy in BitChat: Block, Neighborhood, and City Levels Explained

> Explore the BitChat geohash precision hierarchy. Understand how block neighborhood and city levels protect user privacy by broadcasting presence data at region province and city levels.

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

---

**BitChat uses a six-tier geohash precision hierarchy where longer geohash strings represent smaller geographic areas, with only region (2 chars), province (4 chars), and city (5 chars) levels broadcasting presence data to protect user privacy.**

BitChat's location-based messaging system organizes channels using **geohash precision hierarchy** — a system that maps string lengths to real-world geographic scales. This architecture, implemented in the permissionlesstech/bitchat repository, balances location-based discovery with strong privacy protections by restricting presence broadcasts to coarser precision levels.

## How Geohash Precision Levels Map to Geographic Areas

BitChat defines six precision levels in [[`LocationChannel.swift`](https://github.com/permissionlesstech/bitchat/blob/main/LocationChannel.swift)](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Protocols/LocationChannel.swift). Each `GeohashChannelLevel` case maps to a specific character count in the encoded geohash string:

| Precision Level | Geohash Length | Approximate Area |
|-----------------|----------------|------------------|
| **Region** | 2 characters | ~5,000 km² (country-scale) |
| **Province** | 4 characters | ~160 km² (state-scale) |
| **City** | 5 characters | ~40 km² (city-scale) |
| **Neighborhood** | 6 characters | ~5 km² (district) |
| **Block** | 7 characters | ~0.6 km² (city block) |
| **Building** | 8 characters | ~0.08 km² (single building) |

The `GeohashChannelLevel` enum implements this mapping through a computed `precision` property:

```swift
enum GeohashChannelLevel: CaseIterable, Codable, Equatable {
    case building, block, neighborhood, city, province, region

    var precision: Int {
        switch self {
        case .building:      return 8
        case .block:         return 7
        case .neighborhood:  return 6
        case .city:          return 5
        case .province:      return 4
        case .region:        return 2
        }
    }
}

```

Notice that precision values skip `3` — this maintains alignment with standard geohash resolution jumps where each character approximately halves the longitude and latitude ranges.

## Encoding Locations into Geohash Channels

When users create or join location-based channels, BitChat encodes coordinates using [`Geohash.encode(latitude:longitude:precision:)`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Protocols/Geohash.swift). The precision parameter determines which hierarchy level the channel occupies.

```swift
import BitChat

// Encode San Francisco coordinates to different precision levels
let lat = 37.78
let lon = -122.41

// City-level precision (5 characters)
let cityHash = Geohash.encode(
    latitude: lat, 
    longitude: lon, 
    precision: GeohashChannelLevel.city.precision
)
// Result: "9q8yy"

// Block-level precision (7 characters)
let blockHash = Geohash.encode(
    latitude: lat, 
    longitude: lon, 
    precision: GeohashChannelLevel.block.precision
)
// Result: "9q8yym8"

```

The additional characters in `blockHash` compared to `cityHash` narrow the geographic bounding box from approximately 40 km² down to roughly 0.6 km² — a 60× reduction in area coverage.

## Privacy-First Presence Broadcasting Rules

BitChat's **geohash precision hierarchy** directly determines which channels receive automatic presence heartbeats. The [[`GeohashPresenceService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/GeohashPresenceService.swift)](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/GeohashPresenceService.swift) file enforces a strict allowlist:

```swift
private let allowedPrecisions: Set<Int> = [
    GeohashChannelLevel.region.precision,    // 2
    GeohashChannelLevel.province.precision,  // 4
    GeohashChannelLevel.city.precision       // 5
]

```

This means:

- **Region**, **province**, and **city** channels broadcast ephemeral presence (Nostr kind 20001)
- **Neighborhood**, **block**, and **building** channels **never** auto-broadcast presence

The exclusion protects against **location triangulation attacks** — an adversary monitoring neighborhood-level or finer heartbeats could potentially track a user's movements with building-level accuracy.

### Detecting Broadcast Eligibility in Code

```swift
let service = GeohashPresenceService.shared
service.start() // Initializes heartbeat loop

// Check if a channel will broadcast presence
let cityChannel = GeohashChannel(level: .city, geohash: "9q8yy")
let blockChannel = GeohashChannel(level: .block, geohash: "9q8yym8")

service.allowedPrecisions.contains(cityChannel.geohash.count)   // true — broadcasts
service.allowedPrecisions.contains(blockChannel.geohash.count)  // false — silent

```

## UI Behavior Across Precision Levels

BitChat's interface adapts based on whether presence data exists for a given **geohash precision hierarchy** level, as documented in [[`GeohashPresenceSpec.md`](https://github.com/permissionlesstech/bitchat/blob/main/GeohashPresenceSpec.md)](https://github.com/permissionlesstech/bitchat/blob/main/docs/GeohashPresenceSpec.md).

### Participant Count Display

| Precision | Presence Broadcast | UI Display |
|-----------|-------------------|------------|
| Region, Province, City | Yes | Exact count: "42 people" |
| Neighborhood, Block, Building | No | Ambiguous: "[? people]" |

The "`[? people]`" indicator signals to users that BitChat intentionally withholds this information for privacy protection, not due to a technical failure.

### High-Precision Sharing Warnings

When users attempt to share neighborhood-level or finer channels, [[`LocationChannelsSheet.swift`](https://github.com/permissionlesstech/bitchat/blob/main/LocationChannelsSheet.swift)](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Views/LocationChannelsSheet.swift) triggers an operational security warning. This alerts users that sharing a geohash of 6+ characters reveals their approximate location to recipients — a risk not present when sharing city-level or coarser channels.

## Why the Hierarchy Excludes Intermediate Precisions

Two design decisions in BitChat's **geohash precision hierarchy** warrant explanation:

1. **Missing precision value 3**: Standard geohash implementations use character counts that double resolution (roughly) with each step. Precision 3 (~400 km²) falls between province and city scales without clear semantic mapping, so BitChat jumps directly from 4 to 5.

2. **No 1-character geohash**: Single-character geohashes span ~20,000 km² — larger than most continents. This provides minimal location value while creating relay routing inefficiencies, so the hierarchy begins at 2 characters (region).

These omissions keep the precision levels semantically meaningful and technically efficient.

## Testing the Hierarchy Implementation

The [[`GeohashPresenceTests.swift`](https://github.com/permissionlesstech/bitchat/blob/main/GeohashPresenceTests.swift)](https://github.com/permissionlesstech/bitchat/blob/main/bitchatTests/GeohashPresenceTests.swift) suite validates that:

- All six `GeohashChannelLevel` cases map to correct integer precisions
- `allowedPrecisions` contains only 2, 4, and 5
- Channels with geohash lengths 6, 7, or 8 correctly fail presence eligibility checks
- UI components render "[? people]" for high-precision channels

```swift
// Example test pattern from the test suite
func testHighPrecisionChannelsExcluded() {
    let highPrecisions = [6, 7, 8] // neighborhood, block, building
    for length in highPrecisions {
        XCTAssertFalse(
            service.allowedPrecisions.contains(length),
            "Precision \(length) should not broadcast presence"
        )
    }
}

```

## Summary

- **Geohash precision hierarchy** in BitChat uses 2–8 character strings mapped to six geographic scales from region (~5,000 km²) down to building (~0.08 km²)
- The `GeohashChannelLevel` enum in [[`LocationChannel.swift`](https://github.com/permissionlesstech/bitchat/blob/main/LocationChannel.swift)](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Protocols/LocationChannel.swift) defines this mapping with precision values of 2, 4, 5, 6, 7, and 8
- Presence broadcasts are restricted to region (2), province (4), and city (5) via `allowedPrecisions` in [[`GeohashPresenceService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/GeohashPresenceService.swift)](https://github.com/permissionlesstech/bitchat/blob/main/bitchateat/Services/GeohashPresenceService.swift)
- Neighborhood, block, and building channels operate silently — no heartbeats, ambiguous participant counts, and sharing warnings
- This architecture prevents location tracking while maintaining useful proximity-based channel discovery at broader scales

## Frequently Asked Questions

### Why doesn't BitChat broadcast presence for block-level channels?

Block-level geohashes (7 characters, ~0.6 km²) reveal precise location information that could enable stalking or surveillance. By excluding precisions 6, 7, and 8 from [`allowedPrecisions`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/GeohashPresenceService.swift), BitChat ensures users cannot be tracked at neighborhood-scale resolution or finer. The trade-off is that users cannot see real-time participant counts for these channels.

### How does BitChat determine which precision level a geohash belongs to?

The app inspects the string length of any geohash and matches it against the `precision` property of `GeohashChannelLevel`. A 5-character geohash maps to `.city`, a 6-character geohash maps to `.neighborhood`, and so forth. This reverse mapping happens throughout the codebase whenever channel metadata needs interpretation.

### Can users manually override the presence broadcast restrictions?

No. The `allowedPrecisions` set in [[`GeohashPresenceService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/GeohashPresenceService.swift)](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/GeohashPresenceService.swift) is hardcoded as a private constant. Users cannot configure their client to broadcast fine-grained presence, ensuring consistent privacy protection across the network. Attempting to modify this would require forking the codebase and running a non-standard client.

### What happens when I share a building-level channel with someone?

[[`LocationChannelsSheet.swift`](https://github.com/permissionlesstech/bitchat/blob/main/LocationChannelsSheet.swift)](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Views/LocationChannelsSheet.swift) detects geohash lengths ≥ 6 and displays a warning dialog before completing the share action. The warning explains that the recipient will learn your approximate location within ~80 meters. Users must confirm this disclosure before the share proceeds.