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

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), 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:

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:

// 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), wraps a PeerID alongside additional metadata:

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, it references peerID.id to select the correct transport and destination.

Practical Usage Examples

Creating a Standard Mesh PeerID

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

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.
  • 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). 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.

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 →