ChipChat is a decentralized, peer-to-peer (P2P) chat and voice communication application written in Rust. It utilizes the modern libp2p networking stack and tokio asynchronous runtime to establish direct, authenticated connections between clients. The system handles challenging network topologies using rendezvous peer discovery and interactive hole punching, falling back gracefully to circuit relay nodes when direct communication is impossible.
ChipChat runs on a decentralized architecture where peers coordinate using a hybrid peer discovery and relay infrastructure.
sequenceDiagram
participant Client A
participant Rendezvous Server (VM)
participant Client B
Note over Client A, Client B: Peer Bootstrapping
Client A->>Rendezvous Server (VM): Dial & Identify
Client A->>Rendezvous Server (VM): Register Namespace ("chat")
Client B->>Rendezvous Server (VM): Dial & Identify
Client B->>Rendezvous Server (VM): Register Namespace ("chat")
Note over Client A, Client B: Peer Discovery (Periodic 30s)
Client A->>Rendezvous Server (VM): Discover Peers in "chat"
Rendezvous Server (VM)-->>Client A: Discovered Client B (with Multiaddrs)
Note over Client A, Client B: Connection Establishment
Client A->>Client B: Dial directly
rect rgb(240, 248, 255)
Note over Client A, Client B: If Direct Dial Fails (behind NAT)
Client A->>Client B: Request Direct Connection Utility (DCUTR) Hole-Punch
alt Hole-punch succeeds
Client A<<->>Client B: Direct QUIC / TCP Stream Established
else Hole-punch fails
Client A<<->>Client B: Route via Circuit Relay (STUN/Relay VM)
end
end
Note over Client A, Client B: Communication Sessions
Client A->>Client B: Open Stream (/chat/text/1.0.0 or /chat/audio/1.0.0)
Client A<<-->>Client B: Exchange Text Messages / Raw PCM Audio Frames
- Rendezvous Protocol (
libp2p-rendezvous): A directory service namespace registration protocol. Client instances register their addresses with a central cloud-hosted server (stunbinary) and query it periodically (every 30 seconds) to obtain a live list of other online clients. - Direct Connection Utility for Hole Punching (
libp2p-dcutr): Enables decentralized hole punching. When two peers behind NATs learn of each other, they execute a synchronized connection attempt to establish direct sockets. - Circuit Relay Client/Server (
libp2p-relay): A fallback transport mechanism. If direct hole punching fails (e.g., behind symmetric NATs), the clients establish relay connections using a cloud relay server (stunbinary acting as a relay node) to route traffic securely. - Identity & Identification (
libp2p-identify): Automatically exchanges local P2P keypairs, protocols, and observed external addresses between nodes. - Substream Protocol multiplexing (
libp2p-stream): Dynamically manages custom application-layer streams over existing connection channels.
ChipChat includes a real-time, low-latency duplex voice-calling engine built using cpal (Cross-Platform Audio Library) for raw I/O.
Audio is packetized and transmitted as binary frames over the /chat/audio/1.0.0 protocol. Each packet is formatted as follows:
| Field | Size | Data Type | Description |
|---|---|---|---|
| Sequence Number | 8 Bytes | u64 (Big-Endian) |
Monotonically increasing ID for packet reordering. |
| Sample Rate | 4 Bytes | u32 (Big-Endian) |
The sample rate of the recorded audio (default: 16000 Hz). |
| Payload Length | 4 Bytes | u32 (Big-Endian) |
The size of the sample payload in bytes. |
| Samples | Variable | [u8] |
Raw little-endian f32 PCM mono audio samples. |
To counter network jitter and heterogeneous audio hardware configurations, the audio receiver features:
- Out-of-Order Reordering: Incoming audio packets are temporarily buffered in a
BTreeMapkey-value store, indexed by their sequence number. The playback thread continuously pulls the next contiguous packet (next_seq), dropping frames that arrive too late (beyond a configured threshold ofAUDIO_JITTER_BUFFER_MAX). - Linear Resampler: Local hardware outputs often run at arbitrary rates (e.g., 44100 Hz or 48000 Hz) while the network stream defaults to 16000 Hz. The playback module runs a linear interpolation algorithm to resample incoming floating-point PCM buffers from their source sample rate to the local hardware output rate.
- Headless & File I/O Fallback: If no physical audio input or output hardware is present, ChipChat falls back gracefully:
- Reads inputs from a raw byte file named
audio_input.raw. - Writes incoming playback streams to a file named
audio_output.raw.
- Reads inputs from a raw byte file named
In addition to the default libp2p cryptographic handshake (Noise protocol over QUIC/TCP), ChipChat implements application-level user identities using Ethereum-style cryptography:
- Local Keys: Upon first launch, the client generates a secp256k1 private key saved to
src/user/identity.key(Ethereum format) alongside a standard libp2p cryptographic keypair atsrc/user/libp2p.key. - Ethereum Address: The client derives a 20-byte Ethereum-compatible hex address from the public key, representing the user's secure signature identity.
- Message Verification: The
Messagestruct bundles the payload, sender address, and a signature. The receiver uses local crypto tools to recover the public key from the signed message string (i.e.recover_address_from_msgusingalloy-signer&alloy-primitives) and matches it against the sender's address to prevent impersonation.
.
├── Cargo.toml # Cargo package configuration
├── Cargo.lock # Pinned rust dependencies
├── test.txt # Example serialized signed message representation
└── src/
├── main.rs # Client application entry point (swarm builder, main loop)
├── lib.rs # Library configuration exporting utility modules
├── constants.rs # System parameters (ports, intervals, sample rates, buffers)
├── logs.txt # Swarm runtime logger output file
├── stun/
│ └── main.rs # Rendezvous + Relay server daemon entry point
├── crypto/
│ ├── mod.rs
│ └── message.rs # Message representation and cryptographic verification
├── network/
│ ├── mod.rs
│ ├── behaviour.rs # Swarm custom behavior definitions (gossipsub, dcutr, relay, rendezvous)
│ ├── gossip.rs # Pubsub topics and serialization helpers
│ ├── chat.rs # Direct /chat/text/1.0.0 stream handler and state manager
│ └── audio.rs # Direct /chat/audio/1.0.0 voice-call capture, playback, and resampling
└── user/
├── mod.rs
├── user.rs # User representation and signing functions
├── identity.key # Auto-generated secp256k1 private key
├── libp2p.key # Auto-generated libp2p cryptographic node key
└── names.txt # Local mapping of Peer IDs to user nicknames
System parameters can be adjusted directly in src/constants.rs:
- VM Infrastructure:
VM_IP: The public IP of the coordination server (default:35.223.40.184).VM_LISTENING_PORT: The listening port of the coordination server (default:3478).VM_PEER_ID: The static libp2pPeerIdof the server.
- Tuning:
DISCOVERY_INTERVAL_SECS: Interval in seconds for client discoveries (default:30).IDLE_TIMEOUT_SECS: Connection idle timeout before dropping inactive peers.
- Audio Tuning:
AUDIO_SAMPLE_RATE: Capture sample rate (default:16000).AUDIO_JITTER_BUFFER_MAX: Maximum sequence range for the jitter buffer (default:50frames).AUDIO_FALLBACK_INPUT/AUDIO_FALLBACK_OUTPUT: Fallback files for headless execution.
To compile and run this application, make sure you have the Rust toolchain installed. On Linux platforms, you also need to install the development packages for ALSA (Audio APIs):
# Ubuntu/Debian
sudo apt-get install libasound2-devIf you wish to host your own rendezvous server or deploy to a virtual machine:
cargo run --bin stunNote: Make sure to copy the printed server PeerId on launch and update VM_PEER_ID in src/constants.rs if self-hosting.
To launch the client application:
cargo run --bin FinalProjectOn launch, the client automatically loads/generates its local cryptographic identities, dials the rendezvous server, requests circuit-relay reservations, and prints available console commands.
Upon successful connection, you enter the ChipChat shell. Use the following commands:
cmds: Lists all currently available commands.list: Displays a list of all discovered online peers with their index, Peer ID, and resolved addresses.rename <PeerId> <nickname>: Map a long Peer ID string to a friendly name.chat <index>: Establish a direct P2P text chat session with the peer at the specified list index.call <index>: Initiate a direct P2P voice call with the peer at the specified list index.hangup: End the active call or chat session and return to the main lobby.quit: Write the active peer nickname mappings back tosrc/user/names.txtand shut down the node.