BitChat AuthenticatedPeerStatePacket Structure: A Complete Technical Guide to the TLV Wire Format

The AuthenticatedPeerStatePacket is a compact, versioned TLV (type-length-value) binary packet used in BitChat's Noise sessions to exchange peer capabilities and Ed25519 signing public keys, consisting of a 1-byte version (0x01), a capabilities TLV block, and a 32-byte signing key TLV block.

BitChat implements peer authentication over Bluetooth Low Energy (BLE) using a specific binary protocol defined in the permissionlesstech/bitchat repository. Understanding the AuthenticatedPeerStatePacket structure is essential for developers building compatible clients or auditing the cryptographic handshake.

Binary Wire Format Structure

The packet follows a strict canonical encoding designed for forward compatibility. According to the Swift implementation in bitchat/Protocols/Packets.swift (lines 172-199), the wire format uses a fixed version byte followed by two TLV-encoded fields.

Version Field

The first byte is always 0x01, indicating the current wire format version. Decoding fails immediately if this byte does not match, preventing interpretation of future incompatible protocol versions.

Capabilities TLV (Type 0x01)

Next comes the peer capabilities metadata:

  • TLV Type: 0x01
  • Content: A PeerCapabilities object serialized via its encoded() method
  • Constraints: Length-prefixed encoding with a maximum payload of 8 bytes
  • Purpose: Advertises feature support such as private media streaming and group chat participation

Unknown capability bits are ignored during decoding to allow forward-compatible extensions.

Signing Public Key TLV (Type 0x02)

The final mandatory component contains the cryptographic identity:

  • TLV Type: 0x02
  • Content: 32-byte Ed25519 public key
  • Size: Fixed at 32 bytes (0x20 in hex)
  • Purpose: Used to verify the peer's identity signatures during the Noise XX handshake

The complete byte layout appears as:


[0x01]                           // Version byte
[0x01][len][capability-bytes]    // Capabilities TLV (variable length ≤ 8)
[0x02][0x20][32-byte signing key] // Signing key TLV (fixed length)

Packet Type Constants

Within the BitChat protocol hierarchy, this packet carries the type identifier authenticatedPeerState = 0x21. This constant is defined in bitchat/Protocols/BitchatProtocol.swift, where it distinguishes authenticated state payloads from other Noise protocol messages like handshake initiations or data packets.

Swift Implementation Details

The reference implementation resides in bitchat/Protocols/Packets.swift. The AuthenticatedPeerStatePacket struct provides two primary methods:

  • encode() -> Data?: Validates the signing key length and capability size before producing the canonical TLV byte sequence
  • decode(from: Data) -> AuthenticatedPeerStatePacket?: Performs strict validation including version checking, TLV ordering verification, and rejection of duplicate tags

Validation errors (malformed TLVs, non-canonical capability encoding, or incorrect key lengths) result in nil returns, ensuring that only well-formed data affects the session state.

Working with AuthenticatedPeerStatePacket in Code

Encoding an Outgoing Packet

To construct and serialize a packet for transmission over BLE:

import BitFoundation

// Configure capabilities to advertise
let capabilities = PeerCapabilities(
    supportsPrivateMedia: true,
    supportsGroupChat: true
)

// Obtain your 32-byte Ed25519 signing public key
let signingKey: Data = secureIdentityManager.signingPublicKey

// Construct the packet
let statePacket = AuthenticatedPeerStatePacket(
    capabilities: capabilities,
    signingPublicKey: signingKey
)

// Serialize for BLE transmission
guard let encoded = statePacket.encode() else {
    fatalError("Failed to encode AuthenticatedPeerStatePacket")
}
// encoded is now ready for the BLE Noise layer

The encode() method enforces all length constraints before returning the raw bytes.

Decoding a Received Packet

When processing incoming data from the BLE Noise layer:

// payload is Data received from peer
if let packet = AuthenticatedPeerStatePacket.decode(from: payload) {
    let peerCapabilities = packet.capabilities
    let peerSigningKey = packet.signingPublicKey
    
    // Update session state with verified peer information
    sessionStore.updatePeerCapabilities(peerCapabilities, 
                                       signingKey: peerSigningKey)
} else {
    // Invalid version, malformed TLV, or invalid key length
    print("Discarding invalid AuthenticatedPeerStatePacket")
}

The decoder strictly rejects ambiguous or malformed input to prevent state corruption.

Integration and Validation

The packet integrates into BitChat's transport layer through bitchat/Services/BLE/BLENoisePayloadFactory.swift, which wraps the encoded bytes into typed BLE payloads. Upon receipt, bitchat/Services/BLE/BLEPrivateMediaSessionStore.swift manages the lifecycle of authenticated peer states, persisting the verified capabilities and signing keys per session.

Security-critical validation rules include:

  • Version enforcement: Only 0x01 is accepted
  • Canonical encoding: Non-minimal TLV length encodings are rejected
  • Duplicate detection: Repeated TLV tags cause immediate parsing failure
  • Key validation: Signing keys must be exactly 32 bytes

Unit tests in bitchatTests/Protocols/PacketsTests.swift verify these constraints, ensuring that forward-compatible extensions do not compromise security.

Summary

  • The AuthenticatedPeerStatePacket uses a versioned TLV structure with fixed version 0x01 for forward compatibility.
  • It contains two TLV fields: capabilities (type 0x01, max 8 bytes) and Ed25519 signing public key (type 0x02, exactly 32 bytes).
  • The packet type constant 0x21 identifies it within the Noise protocol framing.
  • Implementation in bitchat/Protocols/Packets.swift provides strict validation through encode() and decode(from:) methods.
  • Unknown TLV types are safely ignored, while malformed packets are rejected to prevent session state corruption.

Frequently Asked Questions

What happens if a peer sends an AuthenticatedPeerStatePacket with an unknown version byte?

The decode(from:) method in bitchat/Protocols/Packets.swift immediately returns nil if the version byte is not exactly 0x01. This ensures that clients running older code do not misinterpret future protocol versions, maintaining backward compatibility through explicit version rejection rather than silent failure.

Why are TLV length fields limited to specific sizes in the AuthenticatedPeerStatePacket?

The capabilities field is limited to 8 bytes to constrain the attack surface for denial-of-service via memory exhaustion, while the signing key requires exactly 32 bytes to match Ed25519 public key specifications. These constraints are enforced during both encoding and decoding operations in the Swift implementation.

Can the AuthenticatedPeerStatePacket be extended with new fields without breaking existing clients?

Yes, the TLV design supports forward-compatible extensions. New fields can be added with unique type tags (values greater than 0x02), and existing implementations will ignore unknown TLV types while continuing to process the required version, capabilities, and signing key fields. This allows gradual protocol upgrades across the BitChat network.

Where is the packet type 0x21 actually used in the BitChat codebase?

The constant authenticatedPeerState = 0x21 is defined in bitchat/Protocols/BitchatProtocol.swift and referenced in bitchat/Services/BLE/BLENoisePayloadFactory.swift to tag the payload type when wrapping the encoded AuthenticatedPeerStatePacket for BLE transmission, allowing the receiving end to route the data to the appropriate decoder.

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 →