# What Is the BitChat PeerID Derived From?

> Discover what the BitChat PeerID is derived from. Learn how it's generated from a Curve25519 public key for seamless peer identification in the BitChat network.

- Repository: [permissionlesstech/bitchat](https://github.com/permissionlesstech/bitchat)
- Tags: internals
- Published: 2026-08-21

---

**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`](https://github.com/permissionlesstech/bitchat/blob/main/localPackages/BitFoundation/Sources/BitFoundation/PeerID.swift), the `PeerID` struct consumes this public key through its primary initializer:

```swift
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:

1. **Raw byte extraction** – The 32-byte raw representation of the `Curve25519.KeyAgreement.PublicKey` is extracted.
2. **Hexadecimal encoding** – These bytes are converted to a 64-character hex string (128 hex digits representing 64 bytes of entropy).
3. **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 (`id` property)**: The complete 64-character hex string used in protocol headers and cryptographic proofs.
- **Short ID (`bare` property)**: The first 16 characters of the hex string, optimized for UI display and human-readable sharing.

```swift
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 keys
- `mesh` – For mesh network routing layers
- `noise` – For Noise protocol handshakes
- `geoDM` and `geoChat` – For geospatial messaging contexts

For example, initializing a Nostr-compatible PeerID:

```swift
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`](https://github.com/permissionlesstech/bitchat/blob/main/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`](https://github.com/permissionlesstech/bitchat/blob/main/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`](https://github.com/permissionlesstech/bitchat/blob/main/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:

```swift
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.swift`](https://github.com/permissionlesstech/bitchat/blob/main/PeerID.swift) is **Sendable-conforming**, enabling safe concurrent usage across Swift actors.
- **PeerIDRotation** in [`PeerIDRotation.swift`](https://github.com/permissionlesstech/bitchat/blob/main/PeerIDRotation.swift) manages 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`](https://github.com/permissionlesstech/bitchat/blob/main/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.