# How a Peer Is Identified on the BitChat Mesh Network Using a Peer ID

> Discover how BitChat mesh network uses a unique Peer ID, derived from public keys and prefixes, to identify each peer. Learn about this secure identification method.

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

---

**A BitChat peer is identified by a `PeerID` object that combines a transport-specific prefix with a 16-hexadecimal character bare ID derived from the peer's Noise static public key.**

In the `permissionlesstech/bitchat` repository, the `PeerID` struct serves as the canonical identifier for every participant in the mesh network. Defined in [[`localPackages/BitFoundation/Sources/BitFoundation/PeerID.swift`](https://github.com/permissionlesstech/bitchat/blob/main/localPackages/BitFoundation/Sources/BitFoundation/PeerID.swift)](https://github.com/permissionlesstech/bitchat/blob/main/localPackages/BitFoundation/Sources/BitFoundation/PeerID.swift), this immutable value type encodes routing information and unique identity into a compact string format used throughout the protocol stack.

## Understanding the PeerID Structure

The `PeerID` type is composed of two distinct parts that work together to create a globally unique identifier.

### The Prefix Component (Transport Type)

The **prefix** is an enum value (`PeerID.Prefix`) that indicates how the identifier should be interpreted by the routing layer. According to the source code, valid prefixes include:

- `mesh:` – Regular mesh-only peers communicating over Bluetooth Low Energy.
- `noise:` – Full 64-hexadecimal Noise public keys for cryptographic verification.
- `nostr:` – Short 8-hexadecimal identifiers derived from Nostr keys for "geo-chat" functionality.
- `nostr_` – 16-hexadecimal Nostr keys for "geo-DM" direct messaging.
- `bridge:` – Bridged senders identified by a Nostr public key.
- `group_` – Virtual identifiers for group conversations.
- `empty` – No prefix, reserved for internal system IDs.

### The Bare Component (Unique Identifier)

The **bare** string represents the raw identifier without its prefix. For standard mesh peers, this is a **16-hexadecimal character string** (representing 8 bytes) generated by hashing the peer's 64-byte Noise static public key and extracting the first 16 characters. The `id` property concatenates the prefix's `rawValue` with the bare string to produce the final wire-format identifier.

## Generating PeerIDs from Cryptographic Keys

When a device discovers another node on the mesh, it receives the peer's Noise static public key. The `PeerID` initializer `init(publicKey:)` handles the conversion:

```swift
import BitFoundation

let noisePublicKey: Data = /* 64-byte Noise static public key */
let peerID = PeerID(publicKey: noisePublicKey)

print(peerID.id)  // "abc123def4567890" (16-hex bare ID with mesh: prefix implied)

```

This constructor hashes the provided key material and stores the first 16 hexadecimal characters as the stable, short identifier. This design ensures that the same public key always resolves to the same `PeerID`, enabling persistent peer recognition across app launches.

## Transport-Specific PeerID Variants

BitChat supports multiple transports, and the `PeerID` struct provides convenience initializers for each variant:

**Mesh peers** use the default `init(publicKey:)` which implicitly applies the `mesh:` prefix. **Nostr-based peers** use specialized constructors:

```swift
// Geo-chat user (8-hex short form with nostr: prefix)
let nostrPubKey = "0f1e2d3c4b5a6978..."
let geoChatID = PeerID(nostr: nostrPubKey)
print(geoChatID.id)  // "nostr:0f1e2d3c"

// Bridged sender (bridge: prefix)
let bridgeID = PeerID(bridge: nostrPubKey)
print(bridgeID.id)   // "bridge:..."

```

These prefixes tell the routing layer which transport mechanism to use when delivering messages—whether to broadcast over the Bluetooth mesh, query a Nostr relay, or route through a bridge connection.

## Integration with the BitchatPeer Model

The higher-level `BitchatPeer` model, defined in [[`bitchat/Models/BitchatPeer.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Models/BitchatPeer.swift)](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Models/BitchatPeer.swift), wraps a `PeerID` alongside additional metadata:

```swift
struct BitchatPeer: Equatable {
    let peerID: PeerID          // Unique identifier on the mesh
    let noisePublicKey: Data    // Full 64-byte Noise key for encryption
    let nickname: String
    var isConnected: Bool
    var isReachable: Bool
}

```

Equality and hashing for `BitchatPeer` are determined solely by the `peerID` property. This ensures that a peer is uniquely identified across the entire system regardless of connection state or nickname changes. When the mesh engine routes commands via [`CommandInfo.swift`](https://github.com/permissionlesstech/bitchat/blob/main/CommandInfo.swift), it references `peerID.id` to select the correct transport and destination.

## Practical Usage Examples

### Creating a Standard Mesh PeerID

```swift
import BitFoundation

// Derive ID from discovered Noise key
let discoveredKey: Data = /* 64 bytes from handshake */
let meshPeer = PeerID(publicKey: discoveredKey)
print(meshPeer.id)           // "a1b2c3d4e5f67890"
print(meshPeer.isValid)      // true
print(meshPeer.isShort)      // true (16-hex)

```

### Instantiating a Complete BitchatPeer

```swift
let peer = BitchatPeer(
    peerID: meshPeer,
    noisePublicKey: discoveredKey,
    nickname: "Alice",
    isConnected: true,
    isReachable: true
)

// Display logic uses nickname or falls back to short ID
print(peer.displayName)  // "Alice"

```

## Summary

- **PeerID** is the fundamental identifier for all BitChat mesh participants, defined in [`PeerID.swift`](https://github.com/permissionlesstech/bitchat/blob/main/PeerID.swift).
- Each identifier consists of a **prefix** indicating transport type and a **16-hex bare ID** derived from the Noise static public key.
- The `init(publicKey:)` constructor generates stable short IDs by hashing the 64-byte Noise key and extracting the first 16 hex characters.
- **BitchatPeer** wraps `PeerID` with metadata and uses it for equality comparison, ensuring unique addressing across the network.
- Prefix variants (`mesh:`, `nostr:`, `bridge:`, etc.) enable the same addressing scheme to work across Bluetooth mesh, Nostr relays, and bridge transports.

## Frequently Asked Questions

### What file defines the PeerID structure in BitChat?

The `PeerID` struct is defined in [[`localPackages/BitFoundation/Sources/BitFoundation/PeerID.swift`](https://github.com/permissionlesstech/bitchat/blob/main/localPackages/BitFoundation/Sources/BitFoundation/PeerID.swift)](https://github.com/permissionlesstech/bitchat/blob/main/localPackages/BitFoundation/Sources/BitFoundation/PeerID.swift). This file contains the prefix enum, validation helpers (`isValid`, `isShort`), and convenience initializers for different transport types.

### How long is a standard BitChat peer ID?

A standard mesh peer ID is **16 hexadecimal characters** long (representing 8 bytes). This is generated by taking the first 16 characters of the hash of the peer's 64-byte Noise static public key. When including the prefix (e.g., `nostr:`), the full `id` string is longer.

### What happens if two peers have the same Noise public key?

Because the `PeerID` is deterministically derived from the Noise public key via the `init(publicKey:)` constructor, identical keys produce identical peer IDs. The `BitchatPeer` struct uses this property to establish equality, treating the same key material as the same peer identity regardless of how many times the device is discovered.

### Can a peer have multiple PeerIDs?

Yes. A single physical device may present different `PeerID` values depending on the transport context. For example, the same device might use a `mesh:` ID for Bluetooth connections and a `nostr:` ID when communicating via Nostr relays. The `BitchatPeer` model stores the specific `PeerID` used for the current connection context.