Skip to content

Repository files navigation

Meshchat 🕸

serverless, key-based, small-scale P2P chat

Status: archived experimental project (originally built in high school). I’m keeping it public because I love the idea: censorship-resistant chat with no central server, invite-only trust, and a retro terminal client. Do not use this for real-world secrecy.


What is Meshchat?

Meshchat is a peer-to-peer text chat system. There is no central server.

Each node:

  • creates its own Ed25519 keypair,
  • connects directly to known peers over TCP,
  • gossips information about other peers,
  • and routes messages in a tiny self-organizing “mesh.”

It’s meant for people who care about:

  • sovereignty / freedom tech,
  • talking without depending on any platform,
  • running in a terminal instead of inside some cloud app.

Conceptually: it’s a baby mesh network for trusted friends.


Core features

  • No server, no cloud Every node is both a client and a mini-server. You either start a network, or join an existing one.

  • Invite-only trust model Peers are identified by their public keys, not by usernames or IPs. Trusted keys are stored locally. Unknown keys don’t get full trust automatically.

  • Peer discovery / gossip When a new peer joins, the node that sees them announces that peer to the rest of the network. Nodes also exchange:

    • their current neighbours
    • their known peer list

    This behaves like a tiny DHT-style discovery layer.

  • Key-based identity Every node generates an Ed25519 keypair and keeps the private key locally. Public keys are broadcast to help others identify you.

  • TUI client There’s a text UI for chatting like an oldschool console messenger.

    meshchat TUI screenshot

  • Docker sandbox A minimal Arch Linux Dockerfile exists so you can try the client without messing up your host machine.

  • Light tests already in place There are Python unittests around known network bootstrapping / discovery.

  • Apache 2.0 license You’re explicitly allowed to read, fork, and experiment.


How it basically works

Networking

All peers speak TCP on port 8008 (MESHCHAT_PORT = 8008).

When you:

  • host a network You start listening on 0.0.0.0:8008, accept incoming sockets, and spawn a listener thread for each peer.

  • join an existing network You connect to any known IP in that network, perform the handshake, and bootstrap from there.

# meshchat.Meshchat.join_network(ipa)
# 1. TCP connect to ipa
# 2. Exchange keys / register each other
# 3. Ask for neighbours and known peers
# 4. Start serving yourself

After joining, your node also starts accepting connections, so you become part of the mesh instead of staying a “client-only leaf.”

Identity & keys

Each node owns an Ed25519 keypair.

From util.create_keys() (used by the node layer):

privkey = ed25519.Ed25519PrivateKey.generate()
...
with open('.key/self', 'wb') as f:
    f.write(privbytes)

pubkey = privkey.public_key()
with open('.key/self.pub', 'wb') as f:
    f.write(pubbytes)
  • The private key (.key/self) never leaves your machine.
  • The public key (.key/self.pub) is what other peers know you by.
  • Public keys for other peers are tracked so we know who is allowed to talk to us.

In the code, Node(...) is always told the current set of trusted keys. When someone connects, we check if their key is in that trust set before treating them as a legitimate peer.

Gossip / discovery / neighbour rotation

Each node maintains:

  • self.neighbours: up to MAX_NEIGHBOURS = 4 active neighbours
  • self.known_peers: dict mapping peer_pubkey -> ip
  • self.connections: open sockets (ip -> Node)

When a new peer joins:

  1. We learn their IP and public key.
  2. We add them as a neighbour (possibly rotating out the oldest neighbour if we’re full).
  3. We tell the rest of our neighbours about them using a control message (newpeer).

Control messages are tiny 2-byte codes defined in MESSAGE_CODES:

MESSAGE_CODES = {
    'none':       (0).to_bytes(2, 'big'),
    'neighbours': (1).to_bytes(2, 'big'),
    'newpeer':    (2).to_bytes(2, 'big'),
    'knownpeers': (4).to_bytes(2, 'big'),
}

A typical peer sync looks like:

  • “Send me your neighbours.”
  • “Send me everyone you know.”
  • “FYI, here’s someone new that just appeared.”
  • “Cool, add them to your map.”

So the mesh spreads knowledge of reachable peers over time without a central directory.

Message flow

After handshake, each connection runs a message loop in its own thread:

while True:
    msg = node.receive_message()
    ...
    elif msg == MESSAGE_CODES['newpeer']:
        self._add_newpeer(node)
    else:
        node.dispatcher(msg.decode())

Normal chat messages go through node.dispatcher, which you can bind with set_message_dispatcher(...). System messages like neighbours, knownpeers, and newpeer are handled automatically.

Quick start (dev / demo)

You can build and run the included Dockerfile to get a contained Arch Linux env that launches the client.

docker build -t meshchat .
docker run --network host -it meshchat

Note: --network host is useful so the container can bind/listen on port 8008 and talk to LAN peers directly.


Known limitations / future ideas

This section is intentionally honest. It’s here to show what I’m aware of and what I’d fix next.

  • No NAT traversal Nodes basically assume you can reach each other directly on TCP:8008. This works fine on a LAN, not on the public internet.

  • No forward secrecy / key rotation yet Keys are long-lived Ed25519 pairs. If a private key leaks, that identity is compromised forever. Future step: ephemeral session keys + rotation.

  • Trust bootstrapping is manual You still have to distribute each other’s public keys out-of-band. There’s no key-signature web-of-trust UI yet.

  • Replay / MITM not fully analyzed This is experimental. It has not been audited by a cryptographer. Please assume an attacker on the same network can try fancy stuff.

  • Message routing is local-neighbour only Right now nodes mostly know immediate neighbours and gossip. There is no multi-hop routing logic or store-and-forward for offline peers.

  • Thread-per-connection model Very straightforward to reason about, but not tuned for scale.

  • IPv4 assumed Currently Meshchat binds to "0.0.0.0" and stores ip as strings like "192.168.x.x".

  • Graceful shutdown / timeouts Some sockets get closed on errors or timeouts; threads exit, but cleanup is still a bit rough in places.


Why I built this

I wrote Meshchat as a self-directed exploration of:

  • peer discovery without a central directory,
  • direct encrypted communication between friends,
  • resisting platform lock-in and surveillance,
  • and making a chat tool that still feels like “the old internet” (terminal UI, you own the keys).

This project is where I fell in love with:

  • systems programming / networking,
  • cryptography in practice (Ed25519, key exchange),
  • concurrency (per-connection threads, locks),
  • and decentralised culture.

License

Apache 2.0. Feel free to fork, remix, ritualize, and perform with it.

About

Privacy isn’t optional. It’s a right. An idea for safer, more private communication.

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages