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

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/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:

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:). The precision parameter determines which hierarchy level the channel occupies.

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/bitchat/Services/GeohashPresenceService.swift) file enforces a strict allowlist:

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

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/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/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/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
// 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

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, 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/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/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.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →