A terminal peer-to-peer messenger with Perfect Forward Secrecy (PFS) and no central server.
- Decentralized: every node is both a client and a server. There is no single point of failure or censorship.
- PFS via ephemeral Diffie-Hellman (X25519): fresh one-time keys are generated for each connection. Even if the long-term identity key leaks, past sessions stay unreadable.
- Ed25519 authentication: the identity key signs the ephemeral key, preventing MITM attacks.
- Encryption: ChaCha20-Poly1305 with keys derived from the shared secret (HKDF-SHA256).
- Double Ratchet: during a long-lived session, keys rotate periodically through X25519 DH rounds — providing break-in recovery and future secrecy.
- Kademlia DHT: decentralized peer discovery by
peer_idwithout a central bootstrap list. - Terminal: command-line interface, no GUI.
- Internet-ready: listens on a TCP port, connects to other nodes by IP/host, DHT, relay, or UPnP.
run.py main.py
| |
v v
cross-platform cli.py -> network.py -> crypto.py + double_ratchet.py
launcher | | |
PromptSession P2PNode Identity + DoubleRatchet
| |
Contacts + Session DHT (Kademlia)
| |
relay.py nat.py
relay_client.py logo.py
crypto.py— key generation, ephemeral DH, handshake, and symmetric encryption.double_ratchet.py— Double Ratchet (X25519 DH + KDF chain) for forward/future secrecy within a session.dht.py— Kademlia DHT (UDP) for peer discovery and address lookup.network.py— asyncio TCP server/client, handshake, peer exchange, message routing, rooms.relay.py/relay_client.py— self-hosted relay for NAT-to-NAT connections.nat.py— UPnP/NAT-PMP port mapping helpers.cli.py— terminal UI based onprompt_toolkit.main.py— entry point.run.py— cross-platform launcher that creates a virtual environment and installs dependencies.logo.py— ASCII art banner (placeholder ready for the final logo).
The run.py launcher handles everything: it creates .venv, installs dependencies, and runs main.py.
python run.py- Port is picked automatically: first
12345–12354, then a random free port if those are busy. - Default data directory:
data/anon. - The node prints its listening port and fingerprint.
# Alice
python run.py --name alice
# Bob
python run.py --name bob --bootstrap 127.0.0.1:<alice_port>Use the Listening on ...:<port> line from Alice's terminal for --bootstrap.
python run.py --port 12345 --name alice --data-dir data/alice--port— listening port (0for auto).--name— local profile / nickname, defaultanon. Used to builddata/<name>.--data-dir— explicit data directory.--bootstrap— comma-separatedhost:portlist of known TCP peers.--dht-port— Kademlia DHT UDP port (0for auto,-1to disable).--dht-bootstrap— comma-separatedhost:portlist of Kademlia nodes.--relay— address of a public relay for NAT-to-NAT connections.--upnp— try automatic UPnP/NAT-PMP port mapping.--ephemeral— ephemeral mode: identity and contacts are not persisted, keys are wiped on exit.
If you prefer manual setup:
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
python main.py --name aliceConnect manually inside the CLI:
/connect 127.0.0.1:12345
/connect host:port|peer_id— send a connection request to a peer by address orpeer_id./accept <id|nickname|fingerprint>— accept an incoming connection request./decline <id|nickname|fingerprint>— decline an incoming connection request./msg <id|nickname|room_id|room_name> <text>— send a direct message or a room message./chat <id|nickname|room_id|room_name>— enter clean chat mode with a peer or room./back— leave clean chat mode and return to the normal command menu./room create [name]— create a group chat room./room add <room_id|room_name> <peer_id|nickname>— add a peer to a room./room msg <room_id|room_name> <text>— send a message to a room./rooms— list rooms./leave <room_id|room_name>— leave a room./add <peer_id|short_id> <nickname> [address]— save a contact (address is optional; DHT resolves it)./remove <id|nickname|fingerprint>— remove a contact./contacts— list known contacts./peers— list connected peers./help— show help./quitor/exit— exit the application.
/chat gives you a vim-like, distraction-free view for a single peer or room.
> /chat bob
[chat] entered chat with bob (type /back to return)
> hello
> /back
[chat] returned to normal mode
While in chat mode:
- type text and press Enter to send it directly (no
/msgor/room msgprefix); - messages are shown with a timestamp:
[18:12:21] <nickname> text; - only messages for the active peer/room, plus
connected/disconnectedevents, room join/leave notifications, and incoming connection requests, are shown; - you can still answer connection requests with
/acceptor/decline; - other system notifications are suppressed until you type
/back; /quitor/exitstill works to leave the application.
Bob starts the node and Alice sends him a connection request:
# Alice
> /connect 127.0.0.1:12345
[i] connect request sent to BobFingerprint
On Bob's side:
[?] AliceFingerprint wants to connect from 127.0.0.1:52648. /accept AliceFingerprint or /decline AliceFingerprint
> /accept AliceFingerprint
[+] connected AliceFingerprint
After saving a contact, the nickname is shown everywhere:
> /add <bob_fingerprint> bob
> /msg bob hello
[you -> bob] hello
# On the peer side:
[18:12:21] <alice> hello
Each node publishes its TCP address in a Kademlia distributed hash table. You can connect to a peer by peer_id without typing the address.
# Alice — the first node in the network
python run.py --name alice --dht-port 13345
# Bob — joins Alice's DHT
python run.py --name bob --dht-port 13346 --dht-bootstrap 127.0.0.1:13345Inside the CLI:
> /connect <alice_fingerprint>
[i] connect request sent to <alice_fingerprint>
# On Alice's side:
[?] <bob_fingerprint> wants to connect from ... /accept <bob_fingerprint> or /decline ...
> /accept <bob_fingerprint>
[+] connected <bob_fingerprint>
# Now messaging works:
> /msg <bob_fingerprint> hello
For maximum leave-no-trace behavior, run with --ephemeral:
python run.py --ephemeral --dht-port 13345In this mode:
- A fresh identity is generated and not reused across runs.
- Contacts and routes are kept in memory only.
- On exit (
/quitor/exit) the Double Ratchet keys are cleared, the inbox is drained, and the temporary data directory is removed. Ctrl+Cdoes not exit the app; use/quitor/exit.
Rooms are created in memory locally. Messages are fanned out pairwise to each member through its own Double Ratchet session.
> /room create myteam
[+] room created aa02957b (full id: aa02957b...)
> /room add myteam <bob_fingerprint>
> /room add myteam <charlie_fingerprint>
# or /room add myteam <nickname> if the contact was saved with /add
> /room msg myteam hello everyone
> /leave myteam
[+] left room myteam
When a group message is received, the room and its member list are automatically created/updated on the recipient side.
When a member leaves a room, the remaining members receive a notification:
[-] bob left room 'myteam' (aa02957b)
To let someone from outside connect, you need an open port. Three options:
- UPnP (automatic) — if your router supports UPnP IGD:
python run.py --upnp --port 12345On success, the terminal shows Public endpoint: <your_public_ip>:<port>. Share that address with the peer.
-
Manual port forwarding — open a TCP port in your router to the local IP of the machine.
-
Self-hosted relay (VPS) — when both peers are behind NAT/CGNAT. Run
relay.pyon a server with a public IP:
python relay.py --port 20000Both clients connect to the relay:
# Alice
python run.py --name alice --relay <relay_ip>:20000
# Bob
python run.py --name bob --relay <relay_ip>:20000Then Bob can message Alice by fingerprint:
> /msg <alice_fingerprint> hello
The relay only sees encrypted packets — it cannot decrypt messages.
FYM uses the terminal's alternate screen (the same mechanism as vim/nano). While the app is running:
- the previous terminal scrollback is hidden and cannot be swiped back to;
Ctrl+CandCtrl+Dare ignored by the CLI — you must type/quitor/exitto return to the normal terminal buffer;- on exit the alternate screen is restored and the original terminal content reappears.
.
├── main.py # Entry point
├── run.py # Cross-platform launcher
├── cli.py # Terminal UI
├── network.py # P2P node, sessions, rooms, routing
├── crypto.py # Key generation, handshake, session cipher
├── double_ratchet.py # Double Ratchet implementation
├── dht.py # Kademlia DHT node
├── relay.py # Relay server
├── relay_client.py # Relay client
├── nat.py # UPnP helpers
├── logo.py # ASCII art banner
├── requirements.txt # Full pinned dependencies
├── .gitignore # Git ignore rules
└── LICENSE # Project license