How BitChat Manages User Identity: A Complete Guide to Its Privacy-First Architecture

BitChat uses a decentralized, key-derived identity system where each device generates X25519 key pairs stored in the iOS/macOS keychain, with periodic peer-ID rotation to prevent long-term tracking.

Every peer-to-peer messaging app faces a critical challenge: proving who you are without revealing more than necessary. The permissionlesstech/bitchat repository solves this through a secure identity layer built on asymmetric cryptography and hardware-backed storage. This article examines how user identity management works in BitChat, from key generation to mesh-wide synchronization.


Core Identity Architecture

BitChat's identity system is implemented across multiple Swift modules in the bitchat/ directory. The design prioritizes local key generation, keychain-backed persistence, and privacy-preserving rotation.

Primary Components

Component File Path Responsibility
SecureIdentityStateManager bitchat/Identity/SecureIdentityStateManager.swift Manages device-local identity lifecycle and keychain interactions
IdentityModels bitchat/Identity/IdentityModels.swift Defines data structures for public/private keys and metadata
BitchatPeer bitchat/Models/BitchatPeer.swift Represents verified remote participants
PeerIdentityStore bitchat/App/PeerIdentityStore.swift Local cache of validated peer identities
GossipSyncManager bitchat/Sync/GossipSyncManager.swift Propagates identity updates across the mesh
RequestSyncManager bitchat/Sync/RequestSyncManager.swift Handles identity packet exchange during peer handshake

Key Generation and Device-Bound Identities

Each BitChat installation generates a fresh X25519 key pair on first launch. The private key never leaves the device.

Local Identity Initialization

import Bitchat.Identity

// Initialize the manager early in app lifecycle
let identityManager = SecureIdentityStateManager.shared

// Generate or rotate identity as needed
identityManager.rotateIdentityIfNeeded()

// Retrieve public key for network advertisement
let myPublicKey = identityManager.currentIdentity.publicKey

The rotateIdentityIfNeeded() method in SecureIdentityStateManager checks key validity and triggers regeneration if the current identity exceeds the rotation threshold. This method is the primary entry point for both initial setup and periodic refreshes.


Keychain-Backed Private Key Storage

BitChat stores private keys using iOS/macOS Secure Enclave protections through the keychain API.

Storage Security Model

  • Accessibility level: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
  • Extraction resistance: Keys are non-exportable and bound to the device Secure Enclave
  • Rotation events: SecureIdentityStateManager monitors keychain state and reloads identity on device unlock

This design prevents key extraction even if the app sandbox is compromised, as the cryptographic operations occur within hardware-isolated environments.


Identity Verification and Peer Trust

Before accepting messages, BitChat nodes perform cryptographic identity verification through signed handshake packets.

Creating a Signed Identity Packet

import Bitchat.Sync
import Bitchat.Models

func makeIdentitySyncPacket() -> RequestSyncPacket {
    let identity = SecureIdentityStateManager.shared.currentIdentity
    
    var packet = RequestSyncPacket()
    packet.identity = IdentityModels(
        publicKey: identity.publicKey,
        createdAt: Date()
    )
    
    // Sign using private key from keychain
    packet.signature = identity.sign(data: packet.encode())
    return packet
}

The RequestSyncPacket structure, defined in the sync layer, encapsulates both identity claims and cryptographic proof. The sign(data:) method performs Ed25519 signatures using the hardware-protected private key.

Verifying Incoming Peer Identities

import Bitchat.Sync
import Bitchat.Models

func handleIncomingSync(_ packet: RequestSyncPacket) {
    // Cryptographic verification
    guard packet.signature.isValid(
        for: packet.encode(),
        withPublicKey: packet.identity.publicKey
    ) else {
        Logger.sync.error("Invalid identity signature — dropping packet")
        return
    }
    
    // Persist verified peer
    let peer = BitchatPeer(
        publicKey: packet.identity.publicKey,
        lastSeen: Date(),
        firstVerified: Date()
    )
    PeerIdentityStore.shared.savePeer(peer)
}

The BitchatPeer model tracks verification timestamp and last seen metadata, enabling trust scoring and stale identity pruning.


Peer-ID Rotation for Privacy Preservation

Long-lived identifiers enable tracking across network sessions. BitChat implements periodic identity rotation as documented in docs/PEER-ID-ROTATION.md.

Rotation Mechanism

import Bitchat.Identity

func evaluateRotation() {
    let manager = SecureIdentityStateManager.shared
    
    // Default rotation interval: 30 days
    guard manager.daysSinceLastRotation > 30 else { return }
    
    // Generate new key pair, archive old for history verification
    manager.rotateIdentityIfNeeded()
    
    // Announce new identity to mesh
    GossipSyncManager.shared.broadcastNewIdentity()
}

Key rotation properties:

  • Conversational continuity: Historic messages remain verifiable using archived public keys
  • Gossip propagation: GossipSyncManager.broadcastNewIdentity() floods the new public key through the mesh
  • Grace period: Old identities remain valid for a configured overlap window to prevent message loss

The rotation strategy balances unlinkability (prevention of long-term correlation) against usability (continuous conversation access).


Mesh-Wide Identity Synchronization

BitChat operates without central servers. Identity state propagates through gossip protocols and on-demand synchronization.

Synchronization Pathways

  1. GossipSyncManager — proactively broadcasts identity changes to connected peers
  2. RequestSyncManager — responds to explicit identity queries from new or reconnecting nodes
  3. PeerIdentityStore — maintains eventually consistent local view of the identity graph

The sync managers operate on eventual consistency: identity updates may propagate through multi-hop paths, with verification occurring at each step using the signed packet format described above.


End-to-End Encryption Integration

Identity management directly feeds the encryption layer. Session keys derive from X25519 key agreement between participant public keys.

import Bitchat.Crypto

func establishSession(with peer: BitchatPeer) throws -> SessionKey {
    let myIdentity = SecureIdentityStateManager.shared.currentIdentity
    
    // X25519 ECDH with hardware-protected private key
    let sharedSecret = try X25519.sharedSecret(
        myPrivateKey: myIdentity.privateKey,
        theirPublicKey: peer.publicKey
    )
    
    // HKDF-SHA256 key derivation
    return HKDF.deriveKey(
        input: sharedSecret,
        context: "bitchat-v1-session",
        length: 32
    )
}

The identity layer's key availability guarantees ensure that decryption is only possible for the intended recipient holding the corresponding private key.


Summary

  • Device-bound keys: Each installation generates unique X25519 pairs with private keys in Secure Enclave
  • Hardware-backed storage: SecureIdentityStateManager uses kSecAttrAccessibleWhenUnlockedThisDeviceOnly keychain items
  • Cryptographic verification: All identity claims carry Ed25519 signatures verified through RequestSyncPacket handling
  • Privacy rotation: 30-day identity rotation prevents long-term tracking while preserving conversation history
  • Decentralized sync: GossipSyncManager and RequestSyncManager propagate identity state without central servers
  • Encryption integration: Identity keys directly enable X25519+HKDF session establishment

Frequently Asked Questions

How does BitChat prevent identity theft if a device is compromised?

The private keys are stored with kSecAttrAccessibleWhenUnlockedThisDeviceOnly and bound to the Secure Enclave. This means keys are unextractable even with full filesystem access—they can only be used for cryptographic operations while the device is unlocked. Additionally, the 30-day rotation window limits the exposure window of any single key.

Can users recover their identity after reinstalling BitChat?

No—this is intentional. The PEER-ID-ROTATION.md design explicitly treats device-bound, non-exportable keys as a security feature. Reinstalling generates a fresh identity, preventing cloud-based key escrow attacks. Conversation history can be restored from peer devices in the mesh, but the cryptographic identity is new.

How does BitChat handle two devices belonging to the same person?

Each device maintains independent identities. The BitchatPeer model does not encode "user" concepts—only device identities. A person using multiple devices appears as multiple distinct peers to the network, preserving the principle that identity equals cryptographic capability.

What happens to messages sent to a rotated identity?

The rotation implementation in SecureIdentityStateManager archives superseded public keys for a configurable grace period (default 7 days). PeerIdentityStore queries this archive during signature verification, allowing recently-rotated identities to remain valid. After the grace period, messages to the old identity are rejected with a sync error triggering re-resolution through the gossip layer.

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 →