Skip to main content

bitchat-protocol

PyPI Python 3.10+ License: Unlicense

BitChat binary protocol encode/decode for Python.

Implements the wire format from the BitChat mesh networking protocol: binary packet encode/decode, TLV codec for announcement and private message structures, and peer ID derivation utilities.

Installation

pip install bitchat-protocol

Requires Python 3.10+.

Quick Start

from bitchat_protocol import (
    encode, decode,
    BitchatPacket, MessageType,
    encode_announcement, decode_announcement,
    AnnouncementPacket,
    peer_id_from_noise_key,
)

# Encode a broadcast message
import time
packet = BitchatPacket(
    version=1,
    type=int(MessageType.MESSAGE),
    ttl=7,
    timestamp=int(time.time() * 1000),
    flags=0,
    sender_id=bytes.fromhex('abcdef0123456789'),
    payload='Hello, BitChat!'.encode(),
)
wire = encode(packet, padding=True)   # padded for BLE transmission

# Decode from bytes received over BLE or Nostr relay
decoded = decode(wire)
if decoded:
    print('type:', decoded.type)
    print('payload:', decoded.payload.decode())

API

Packet Encode/Decode

encode(packet: BitchatPacket, *, padding: bool = False) -> bytes
decode(data: bytes) -> BitchatPacket | None

decode() returns None (never raises) on invalid or truncated input.

TLV: AnnouncementPacket

encode_announcement(packet: AnnouncementPacket) -> bytes
decode_announcement(data: bytes) -> AnnouncementPacket | None

Decoder is lenient: unknown TLV tags are skipped (forward-compatible).

TLV: PrivateMessagePacket

encode_private_message(packet: PrivateMessagePacket) -> bytes
decode_private_message(data: bytes) -> PrivateMessagePacket | None

Decoder is strict: returns None on any unknown TLV tag.

TLV: RequestSyncPacket

REQUEST_SYNC gossip-sync payloads carrying GCS filter parameters. Unlike the TLVs above, these use 16-bit big-endian lengths.

encode_request_sync(packet: RequestSyncPacket) -> bytes
decode_request_sync(data: bytes, max_accept_bytes: int = MAX_ACCEPT_FILTER_BYTES) -> RequestSyncPacket | None

@dataclass
class RequestSyncPacket:
    p: int                                    # Golomb-Rice parameter (decode accepts 1..=MAX_P = 32)
    m: int                                    # hash range M = N * 2^P (uint32)
    data: bytes                               # GR bitstream bytes (MSB-first)
    types: int | None = None                  # sync-type flags bitmask
    since_timestamp: int | None = None        # only sync packets newer than this (ms, uint64)
    fragment_id_filter: str | None = None     # restrict sync to one fragment ID
from bitchat_protocol import (
    MessageType, RequestSyncPacket,
    encode_request_sync, decode_request_sync, sync_type_flags_from_message_types,
)

wire = encode_request_sync(RequestSyncPacket(
    p=19,
    m=1 << 19,
    data=gcs_filter_bytes,
    types=sync_type_flags_from_message_types([MessageType.ANNOUNCE, MessageType.MESSAGE]),
))
request = decode_request_sync(wire)  # None on any invalid input

The decoder is lenient about unknown tags (forward-compatible — the optional types/since_timestamp/fragment_id_filter TLVs are iOS-side extensions that older decoders skip) and strict about validity: it rejects p outside 1..=MAX_P (32), m = 0, missing required fields, and filter data larger than max_accept_bytes (default MAX_ACCEPT_FILTER_BYTES = 1024, a DoS guard).

Helpers: sync_type_flags_from_message_types(types) / sync_type_flags_to_message_types(flags) convert between list[MessageType] and the flags bitmask.

Peer ID Utilities

peer_id_from_noise_key(noise_public_key: bytes) -> str   # 16-char hex
peer_id_to_bytes(peer_id: str) -> bytes                  # 8 bytes
peer_id_from_bytes(data: bytes) -> str                   # 16-char hex
nostr_geo_dm_peer_id(nostr_pubkey_hex: str) -> str       # "nostr_" + prefix
nostr_geo_chat_peer_id(nostr_pubkey_hex: str) -> str     # "nostr:" + prefix

Utilities

hex_to_bytes(hex_str: str) -> bytes
bytes_to_hex(data: bytes) -> str

Wire Format

v1 Header (14 bytes)

[version:1][type:1][ttl:1][timestamp:8 BE uint64][flags:1][payloadLen:2 BE uint16]
[senderID:8]
[recipientID:8]         — if flags & HAS_RECIPIENT
[payload:payloadLen]
[signature:64]          — if flags & HAS_SIGNATURE

v2 Header (16 bytes)

Same but payloadLen is 4 bytes (BE uint32) and source routing is supported.

Flags Byte

Bit Value Name
0 0x01 HAS_RECIPIENT
1 0x02 HAS_SIGNATURE
2 0x04 IS_COMPRESSED
3 0x08 HAS_ROUTE (v2+ only)
4 0x10 IS_RSR

Running Tests

pip install -e ".[dev]"
pytest

Compatibility

This package implements the same binary wire format as:

  • ios/bitchat/Protocols/BinaryProtocol.swift
  • android/.../BinaryProtocol.kt

Cross-language compatibility is verified against the bitchat-sdk/spec-tests golden fixture suite. Clone that repo as a sibling of this one, then pytest runs all cross-language vectors automatically — without it, the fixture suite silently skips.

License

Unlicense — public domain.

Metadata

Release files for bitchat-protocol 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for bitchat-protocol 0.2.0
File Size Uploaded Provenance Actions
bitchat_protocol-0.2.0.tar.gz 22.2 kB Without provenance Details for bitchat_protocol-0.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for bitchat-protocol 0.2.0
File Interpreter ABI Platform Actions
bitchat_protocol-0.2.0-py3-none-any.whl Python 3 none any Details for bitchat_protocol-0.2.0-py3-none-any.whl

Total release size: 38.8 kB

Release files / bitchat_protocol-0.2.0.tar.gz

Download URL bitchat_protocol-0.2.0.tar.gz
Size 22.2 kB
Tags Source
SHA-256 checksum
How to use checksums
c810a4ad115dc29d9614709b958ae4f850e230c413f32ca2c471eca8d7c64cbb
BLAKE2b-256 checksum
How to use checksums
b80e4b3eea555887fd9759d23b1fb29da01eaf608f20dbe55c803df57d7fbf37
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.2

Release files / bitchat_protocol-0.2.0-py3-none-any.whl

Download URL bitchat_protocol-0.2.0-py3-none-any.whl
Size 16.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
38694ac9c8d9589c243a2fe89d93614842528f099810e2393d82ba18bcf9cc0c
BLAKE2b-256 checksum
How to use checksums
37ed369e11a1b1fcd63728ca13ee9b6179ed93e3de383dc63db18db35c6afc9e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.2

Security for bitchat-protocol 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page