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:
SecureIdentityStateManagermonitors 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
- GossipSyncManager — proactively broadcasts identity changes to connected peers
- RequestSyncManager — responds to explicit identity queries from new or reconnecting nodes
- 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:
SecureIdentityStateManageruseskSecAttrAccessibleWhenUnlockedThisDeviceOnlykeychain items - Cryptographic verification: All identity claims carry Ed25519 signatures verified through
RequestSyncPackethandling - Privacy rotation: 30-day identity rotation prevents long-term tracking while preserving conversation history
- Decentralized sync:
GossipSyncManagerandRequestSyncManagerpropagate 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →