# How BitChat Derives Ephemeral Cryptographic Identities: A Deep Dive into the NostrIdentityBridge

> Discover how BitChat derives ephemeral cryptographic identities using a device-wide seed and HMAC-SHA256. Learn about unlinkable, temporary Nostr keys for secure, session-only communication.

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

---

**BitChat derives ephemeral cryptographic identities deterministically from a single device-wide seed using HMAC-SHA256 with geohash inputs, ensuring each location channel has an unlinkable, temporary Nostr-compatible key pair that is never persisted beyond the session.**

The permissionlesstech/bitchat repository implements a privacy-preserving messaging protocol where users interact through location-based channels without exposing long-term cryptographic identities. Instead of storing static private keys, BitChat generates **ephemeral cryptographic identities** on-the-fly using a deterministic derivation scheme that maintains unlinkability between different geohash cells.

## The Foundation: Device-Wide Seed Generation

Every ephemeral identity in BitChat originates from a single **256-bit random seed** generated once per device. In [`bitchat/Nostr/NostrIdentityBridge.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Nostr/NostrIdentityBridge.swift), the method `getOrCreateDeviceSeed()` creates and persists this seed in the system keychain under the identifier `nostr-device-seed`.

The seed is cached in memory after first retrieval to avoid repeated keychain operations. This cached seed serves as the root secret from which all location-specific identities are mathematically derived. If the device seed is wiped or lost—for example, through a factory reset—all previously derived ephemeral identities become permanently unrecoverable, breaking any linkability across device wipes.

## Per-Location Identity Derivation via HMAC-SHA256

For each geohash location (e.g., `"u4pruydq"`), BitChat creates a unique identity through a deterministic HMAC-SHA256 process implemented in `deriveIdentity(forGeohash:)`. The derivation uses the device seed as the HMAC key and the UTF-8 bytes of the geohash string concatenated with an iteration counter as the message.

The resulting 32-byte digest becomes candidate private-key material for a **secp256k1 Schnorr key pair**. This deterministic approach ensures that the same device always generates the identical ephemeral identity for the same location, enabling message continuity without requiring persistent storage of per-location keys.

### The Derivation Loop and secp256k1 Validation

The candidate bytes produced by the HMAC operation are not guaranteed to form a valid secp256k1 private key. BitChat addresses this through a validation loop:

1. Compute HMAC-SHA256(seed, geohash + counter)
2. Attempt initialization via `NostrIdentity(privateKeyData:)`
3. If the bytes fail secp256k1 validation, increment the counter (0-9) and retry
4. Return the first valid identity encountered

This retry mechanism ensures cryptographic correctness while maintaining deterministic output for any valid geohash input.

### Deterministic Fallback Mechanism

After ten failed attempts, BitChat falls back to a deterministic alternative. The system computes `SHA-256(seed || geohash)`, hashes the result once more, and interprets the final digest as the private key. This fallback guarantees that every geohash maps to exactly one valid identity, eliminating edge cases where HMAC outputs consistently fail validation.

## Caching and Thread Safety

To optimize performance during UI rendering, successfully derived `NostrIdentity` objects are stored in an in-memory dictionary named `derivedIdentityCache`. This cache uses the geohash string as the key and is protected by an `NSLock` to ensure thread-safe access across concurrent operations.

The caching layer eliminates redundant HMAC computations and keychain access when the user rapidly switches between location channels. Since the cache resides solely in memory, it provides no persistence—ephemeral identities vanish when the application terminates.

## Bridge Rendezvous Isolation

BitChat supports bridge rendezvous points for hybrid Bluetooth/Nostr messaging. To prevent correlation attacks between location identities and bridge identities, the system implements `deriveIdentity(forBridgeRendezvous:)` which prepends the static label `"bridge|"` to the cell string before derivation.

This namespace separation ensures that a bridge identity for `"cell-42"` cannot be mathematically linked to a geohash identity, even if the underlying cell identifiers collide. The isolation maintains the unlinkability guarantees across the entire hybrid messaging architecture.

## Technical Implementation in Swift

The following examples demonstrate the identity derivation patterns implemented in [`bitchat/Nostr/NostrIdentityBridge.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Nostr/NostrIdentityBridge.swift) and [`bitchat/Nostr/NostrIdentity.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Nostr/NostrIdentity.swift).

Deriving a per-geohash identity:

```swift
import BitFoundation
import BitChat

let keychain = KeychainManager() // Production keychain wrapper
let bridge = NostrIdentityBridge(keychain: keychain)

do {
    // Derive an identity unique to the geohash "u4pruydq"
    let geoIdentity = try bridge.deriveIdentity(forGeohash: "u4pruydq")
    print("Public key (hex):", geoIdentity.publicKeyHex) // 32-byte x-only
    print("Bech32 npub:", geoIdentity.npub)            // Nostr-compatible
} catch {
    print("Failed to derive identity:", error)
}

```

Deriving a bridge-rendezvous identity:

```swift
let bridgeIdentity = try bridge.deriveIdentity(forBridgeRendezvous: "cell-42")
print("Bridge npub:", bridgeIdentity.npub)

```

Generating the global Nostr identity for relay authentication:

```swift
let globalIdentity = try NostrIdentity.generate()
print("Global npub:", globalIdentity.npub)

```

All methods return a `NostrIdentity` struct wrapping a secp256k1 Schnorr key, providing `schnorrSigningKey()` for event signatures and `npub` for Bech32-encoded public key representation.

## Summary

- BitChat creates **ephemeral cryptographic identities** from a single 256-bit device seed stored in the system keychain under `nostr-device-seed`.
- Location-specific keys are derived via **HMAC-SHA256** using the geohash and an iteration counter, validated against secp256k1 requirements with a ten-attempt retry loop.
- A **deterministic SHA-256 fallback** ensures valid key generation even when HMAC outputs fail validation.
- The `derivedIdentityCache` with `NSLock` provides thread-safe, in-memory caching without persistent storage.
- Bridge rendezvous identities are isolated through a `"bridge|"` namespace prefix, maintaining unlinkability across the hybrid messaging infrastructure.

## Frequently Asked Questions

### How does BitChat ensure ephemeral cryptographic identities are unlinkable across different locations?

BitChat derives each identity deterministically from the device seed combined with a unique geohash input. Since different geohashes produce different HMAC-SHA256 outputs, the resulting secp256k1 key pairs are mathematically unrelated. An observer monitoring multiple location channels cannot correlate the ephemeral public keys (`npub` values) without access to the original device seed.

### What happens if the HMAC-SHA256 derivation produces an invalid secp256k1 private key?

The `deriveIdentity(forGeohash:)` method implements an iteration counter (0-9) that modifies the HMAC input message. If the initial candidate fails `NostrIdentity(privateKeyData:)` validation, the system increments the counter and recomputes. After ten failed attempts, BitChat falls back to `SHA-256(SHA-256(seed || geohash))` to guarantee a valid key.

### Where is the device seed stored and how is it protected?

The 256-bit seed is generated once and persisted in the iOS/macOS keychain under the identifier `nostr-device-seed`, implemented in `NostrIdentityBridge.getOrCreateDeviceSeed()`. The seed is cached in memory during application runtime to minimize keychain access, but it never persists to disk outside the secure enclave storage.

### Can ephemeral identities be recovered after the device seed is lost?

No. Because **ephemeral cryptographic identities** are derived deterministically from the device seed using HMAC-SHA256, losing the seed permanently destroys the ability to regenerate any previously used identities. This property ensures forward secrecy across device resets—new installations generate fresh seeds with no mathematical connection to previous ephemeral identities.