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
PeerCapabilitiesobject serialized via itsencoded()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 (
0x20in 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 sequencedecode(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
0x01is 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
AuthenticatedPeerStatePacketuses a versioned TLV structure with fixed version0x01for forward compatibility. - It contains two TLV fields: capabilities (type
0x01, max 8 bytes) and Ed25519 signing public key (type0x02, exactly 32 bytes). - The packet type constant
0x21identifies it within the Noise protocol framing. - Implementation in
bitchat/Protocols/Packets.swiftprovides strict validation throughencode()anddecode(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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →