What Is the BitChat PeerID Derived From?
The BitChat PeerID is deterministically derived from the 32-byte public key of a Curve25519 key-agreement pair, hex-encoded to produce either a 64-character full identifier or a 16-character short representation.
BitChat is a permissionless peer-to-peer messaging protocol where each client requires a unique, cryptographically verifiable identity. According to the permissionlesstech/bitchat source code, the PeerID serves as this identity, but rather than being a random UUID, it is mathematically derived from the user's existing cryptographic keys, enabling deterministic routing and trustless peer discovery.
Cryptographic Foundation of the BitChat PeerID
At the core of every BitChat identity lies a Curve25519 key-agreement pair, the same elliptic curve primitive used for the protocol's X‑ChaCha20‑Poly1305 encryption layer.
The Curve25519 Key Agreement Pair
When a BitChat client initializes, it generates or imports a Curve25519.KeyAgreement.PrivateKey. The corresponding Curve25519.KeyAgreement.PublicKey (32 raw bytes) becomes the seed material for the PeerID. This design binds the network identity directly to the encryption keys, eliminating the need for separate certificate infrastructure.
In localPackages/BitFoundation/Sources/BitFoundation/PeerID.swift, the PeerID struct consumes this public key through its primary initializer:
public init(publicKey: Curve25519.KeyAgreement.PublicKey)
How the PeerID Is Derived from the Public Key
The derivation is a pure function: the same public key bytes always produce the same PeerID, regardless of device or timestamp.
Hex Encoding and Byte Representation
The derivation process follows three deterministic steps:
- Raw byte extraction – The 32-byte raw representation of the
Curve25519.KeyAgreement.PublicKeyis extracted. - Hexadecimal encoding – These bytes are converted to a 64-character hex string (128 hex digits representing 64 bytes of entropy).
- Checksum validation – The resulting string is validated against the protocol's character set constraints.
Short vs. Full Identifier Formats
BitChat maintains two views of the same underlying identity:
- Full ID (
idproperty): The complete 64-character hex string used in protocol headers and cryptographic proofs. - Short ID (
bareproperty): The first 16 characters of the hex string, optimized for UI display and human-readable sharing.
let peerID = PeerID(publicKey: publicKey)
// Full 64-character identifier
print(peerID.id) // e.g., "a3f5c9e2b7d1f4a8e6c2b9d3f7a1e5c9b2d4f6a8c0e2b4d6f8a0c2e4b6d8f0a2c4"
// Short 16-character identifier
print(peerID.bare) // e.g., "a3f5c9e2b7d1f4a8"
Namespaced Prefixes and Protocol Contexts
BitChat supports context-specific PeerID variants by prepending protocol identifiers to the hex string. These prefixes allow the same underlying key to function across different namespaces without collision.
Supported prefixes include:
nostr_– For interoperability with Nostr public keysmesh– For mesh network routing layersnoise– For Noise protocol handshakesgeoDMandgeoChat– For geospatial messaging contexts
For example, initializing a Nostr-compatible PeerID:
let nostrPubKey = try Curve25525519.KeyAgreement.PublicKey(rawRepresentation: nostrKeyData)
let nostrPeerID = PeerID(nostr_: nostrPubKey)
print(nostrPeerID.id) // "nostr_1a2b3c4d5e6f7a8b"
Implementation in the BitChat Source Code
The PeerID implementation resides in localPackages/BitFoundation/Sources/BitFoundation/PeerID.swift. The struct conforms to Equatable, Hashable, and Swift.Sendable, making it safe to store in collections and pass across concurrency boundaries without data races.
Unit tests in localPackages/BitFoundation/Tests/BitFoundationTests/PeerIDTests.swift verify that:
- Identical public keys produce identical PeerIDs
- Hex encoding matches expected byte-to-string conversions
- Prefixes are correctly prepended without altering the underlying key hash
PeerID Rotation and Key Management
When users rotate their cryptographic keys, BitChat handles identity transition through PeerIDRotation, implemented in localPackages/BitFoundation/Sources/BitFoundation/PeerIDRotation.swift.
This utility tracks the transition from an old PeerID to a new one, allowing the protocol to maintain continuity of trust while updating the underlying Curve25519 key pair:
let oldPeer = PeerID(publicKey: oldPublicKey)
let newPeer = PeerID(publicKey: newPublicKey)
let rotation = PeerIDRotation(old: oldPeer, new: newPeer)
print(rotation.description) // "Rotated from <short-old-id> to <short-new-id>"
Summary
- The BitChat PeerID is deterministically derived from the 32-byte Curve25519 public key, not randomly generated.
- Hex encoding produces a 64-character full ID (
id) and a 16-character short ID (bare) for different contexts. - Namespace prefixes like
nostr_allow the same key to function across multiple protocols. - The implementation in
PeerID.swiftis Sendable-conforming, enabling safe concurrent usage across Swift actors. - PeerIDRotation in
PeerIDRotation.swiftmanages identity transitions during key rotation events.
Frequently Asked Questions
Is the BitChat PeerID randomly generated?
No. The PeerID is a deterministic function of your Curve25519 public key. Anyone with access to your public key can compute your exact PeerID, which enables trustless peer discovery but means you cannot change your ID without changing your underlying cryptographic key pair.
Can I recover my BitChat PeerID if I lose my private key?
No. Because the PeerID is derived from the public key, which is mathematically linked to the private key, losing your private key means losing the ability to prove ownership of that PeerID. You must generate a new key pair and derive a new PeerID, then use PeerIDRotation to notify contacts of the transition.
What is the difference between the bare and full PeerID?
The full PeerID (id property) is the complete 64-character hex string used in protocol messages and cryptographic signatures. The bare PeerID (bare property) truncates this to the first 16 characters for UI display and human-readable sharing, reducing visual clutter while maintaining sufficient entropy for casual identification.
How does BitChat handle PeerID rotation?
BitChat handles identity changes through the PeerIDRotation class defined in PeerIDRotation.swift. When you generate a new Curve25519 key pair, you create PeerID instances for both the old and new public keys, then instantiate PeerIDRotation(old:new:) to cryptographically link the two identities. This allows peers to verify that the new ID belongs to the same logical user who previously controlled the old ID.
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 →