Orinox - Decentralized P2P Chat
Orinox is a single-binary, decentralized peer-to-peer (P2P) chat application written in Rust and powered by libp2p. It features a fullscreen terminal user interface (TUI) built with ratatui, End-to-End Encrypted (E2EE) direct messages, and robust, chunked P2P file transfers with automatic resumption.
+-----------------------------------+
| Orinox TUI |
| [Rooms List] [Chat Area] |
| [Peers List] [Input field] |
+-----------------------------------+
|
v
[Local Chat State]
/ \
/ \
v v
[Storage Engine] [libp2p Swarm]
(.orinox/ JSON/JSONL) / | \
/ | \
[mDNS] [Kademlia] [Gossipsub]
Architecture Overview
Orinox compiles into a single executable that bootstraps a local peer identity, spins up a libp2p swarm composed of multiple cooperative network behaviors, and runs a responsive event loop driven by both network events and user input.
graph TD
subgraph Orinox Node
TUI[Ratatui UI] <--> State[Chat State]
State <--> Storage[JSON/JSONL Storage]
Swarm[libp2p Swarm] <--> State
subgraph libp2p Swarm Behaviours
Gossipsub[Gossipsub Pub/Sub]
mDNS[mDNS Discovery]
Kademlia[Kademlia DHT]
ReqResp[Request-Response JSON]
Identify[Identify Protocol]
RelayDCUtR[Relay Client & DCUtR]
end
end
Gossipsub <-->|Broadcast Message / File Offer| Network((p2p Network))
mDNS <-->|Local Discovery| Network
Kademlia <-->|Peer Routing / Find Peer| Network
ReqResp <-->|Chunked File Transfer| Network
Features
๐ป Fullscreen Terminal User Interface (TUI)
- Built using ratatui and crossterm.
- Left sidebar displays subscribed Rooms (active room highlighted in bold yellow) and active Peers (displaying aliases if set).
- Main view shows a timestamped, scrollable message feed with sender coloring (Green for you, Cyan for others, Red for system events).
- Keyboard navigation:
PageUp/PageDown: Scroll through chat message history.Up/Downarrows: Cycle through terminal command history.Esc: Instantly quit the application.
๐ก๏ธ End-to-End Encrypted (E2EE) Direct Messages
- Private chat over deterministic topic channels (
dm-<peerA>-<peerB>). - Cryptographic key exchange via Diffie-Hellman (ECDH) over X25519 derived from the nodes' Ed25519 peer identities.
- Symmetric payload encryption with ChaCha20Poly1305 authenticated encryption (AEAD).
- Local storage and transit payloads are fully encryptedโonly the recipient can decrypt the message.
๐ Chunked File Sharing & Resumption
- Offer files of any size (up to 1 GiB) via
/send <path>. - Files are assigned a unique, random 32-character hexadecimal
file_idand advertised via Gossipsub. - Downloads are executed via a custom libp2p
request_responseprotocol over 64 KiB chunks. - If a download is interrupted, Orinox automatically aligns to the nearest completed 64 KiB block on disk and resumes from the exact point of interruption.
๐ Network & Routing Stack
- mDNS: Zero-configuration local network peer discovery.
- Kademlia DHT: Distributed routing for connecting peers across different networks/NATs.
- Gossipsub: Pub/Sub broadcasting of room chats, membership updates, and file advertisements.
- Relay Client / DCUtR: Direct connection utilities and relay support for NAT traversal.
- Identify: Self-reporting protocol matching peer versions and supported protocols.
๐ Persistent State Storage
Saved entirely under the .orinox/ folder in the project's working directory:
identity.key: Protobuf-encoded cryptographic Ed25519 keypair.history_*.jsonl: Local chat transcript logs saved in JSON Lines format.rooms.json: Persistent list of joined chat rooms.blocked.json: List of ignored/banned Peer IDs.aliases.json: Assigned names mapping to raw peer IDs.received_files/: Standard directory where completed file transfers are stored.received_files/.tmp_<file_id>: Temporary write locations for active file downloads.
Installation & Setup
Requirements
- Rust Toolchain (2024 Edition compatible, Rust 1.85+)
Building from Source
# Clone the repository
git clone https://github.com/LikhinMN/orinox.git
cd orinox
# Build the release binary
cargo build --release
The compiled binary will be available at ./target/release/orinox.
Quick Start (Local Multi-Node Testing)
Run a local network using separate workspaces or ports:
Node 1 (Bootstrap Node)
Start the first node listening on port 9001 with a custom username:
cargo run -- --port 9001 --name Alice
Node 2 (Connect to Alice)
Start a second node, connect directly to Alice, and discover others:
cargo run -- --port 9002 --connect /ip4/127.0.0.1/tcp/9001 --name Bob
Command Line Options
orinox [OPTIONS]
| Short | Long | Value | Description |
|---|---|---|---|
-p | --port | PORT | Port to listen on (default: 9000) |
-c | --connect | MULTIADDR | Address of a peer to dial (can be specified multiple times) |
-b | --bootnodes | MULTIADDR | Kademlia DHT bootnodes to bootstrap routing table |
-n | --name | STRING | Username displayed in chat (defaults to auto-generated user_<short_id>) |
-l | --log-level | LEVEL | Set output logging level: error, warn, info, debug, trace (default: info) |
-h | --help | - | Print help information |
-V | --version | - | Print version information |
In-Chat Command Reference
Type any command in the input bar at the bottom right of the TUI:
| Command | Usage | Description |
|---|---|---|
/help | /help | List all available in-chat commands and keybindings |
/name | /name <new_name> | Change your local username |
/join | /join <room_name> | Join and switch view to a specific room |
/leave | /leave | Leave the current room and return to the global room |
/rooms | /rooms | List all rooms you have joined |
/current | /current | Display the active room name |
/peers | /peers | Print all currently connected peers in the main log |
/alias | /alias <peer_id> <name> | Assign a friendly name/alias to a Peer ID |
/aliases | /aliases | List all active aliases |
/dm | /dm <peer_id_or_alias> <msg> | Send an End-to-End Encrypted (E2EE) direct message |
/send | /send <file_path> | Offer a file for other peers to download |
/download | /download <file_id_prefix> | Start/resume download of an offered file by ID |
/history | /history [N] | Print the last N messages from the room's persistent history |
/ban | /ban <peer_id_or_alias> [reason] | Block and ignore incoming chat messages from a peer |
/unban | /unban <peer_id_or_alias> | Unblock a banned peer |
/bans | /bans | List all banned peer IDs |
/exit | /exit | Gracefully quit the application |
Protocol & Formats
1. Chat Message Format (Gossipsub JSON)
Messages broadcasted over Gossipsub are serialized as JSON:
{
"username": "Alice",
"message": "Hello world!",
"timestamp": 1782348923000,
"file": null
}
2. Encrypted Direct Message Format (E2EE DM)
DMs are sent to a private topic hash derived from sorting both peer IDs. The payload consists of:
- A
12-byte initialization vector (nonce)prepended to the ciphertext. - The
ChaCha20Poly1305encrypted bytes of the standard Gossipsub JSON message.
3. File Transfer Protocol
- Offered File Announcement: Sent via Gossipsub with
dataset tonulland a uniquefile_idpopulated:{ "username": "Alice", "message": "๐ Offered file: document.pdf", "timestamp": 1782348923000, "file": { "filename": "document.pdf", "size": 142048, "file_id": "8bfa2e12a456...", "data": null } } - Chunk Request/Response: Initiated over libp2p
request_response(JSON serialization):// Request { "file_id": "8bfa2e12a456...", "chunk_index": 2 } // Response { "file_id": "8bfa2e12a456...", "chunk_index": 2, "data": [ ... ], "is_last": false }
Development & Testing
Run verification suites and ensure formatting and clippy lints pass:
# Run unit and integration tests (including crypto & lifecycle)
cargo test
# Check code formatting
cargo fmt --check
# Run lints
cargo clippy -- -D warnings
License
This project is licensed under the terms specified in the Cargo.toml file.