nymChat - Python Messenger

Hello everyone! I’m excited to share nymChat, a python based messaging client that uses Nym’s mixnet via WebSocket. It’s a small start, but the goal is to create the most secure messaging platform on the planet.

You can find the code and README here: GitHub - code-zm/nymChat: Python Messaging Client for secure, asynchronous communication over the Nym Mixnet.

Ensure the nym-client is configured & running before you run the python script.

To send a message, simply run nymChat.py and enter the recipient Nym Client Address.

Some goals I have for this project:

  • client side encryption/decryption
  • usernames + user search
  • groupchats
  • clean front end
  • ios / android apps

Feel free to fork the repo and make any changes you’d like. Also any critiques on what I have so far are much appreciated!

Thanks,
code-zm

9 Likes

hey @code-zm, this is excellent work, thank you for the contribution. Let’s turn your future goals into a roadmap and get you a grant for this project!

How long do you think you’d need to build apps on different platforms with the features you listed above?

  • client side encryption/decryption
  • usernames + user search
  • groupchats
  • clean front end
  • ios / android apps

Keep up the good work :slight_smile:

3 Likes

Hello! I’ve devised a 12 week road map to guide me to release.

Link to roadmap: Blocks And Arrows

3 Likes

Here is the technical diagram, let me know if I am missing anything important!

Link to diagram: Blocks And Arrows

3 Likes

Hey there, cool work! :slight_smile:

As per my post to you in the element chat I think you could do something easy with the address book by parsing something like the incoming SURB sender tag and/or a random nickname assignment pretty easy.

I’d like to hear more re: the key management, since this would be an extra layer of encryption at the app level above the encryption that’s happening at the network level with the client themselves.

Its really cool to see someone writing in Python as well, I’m wondering whether this might be a good opportunity to look into some Rust-Python FFI as we have already done with c++ & Go so you don’t have to rely on having 2 processes running with the standalone binary, but it could all be bundled together. Might this be interesting to you? No worries if not, just thought I’d bring it up.

I’m wondering what your plan is regarding group chats, which I saw you mention in the element chat but I don’t see here.

1 Like

SURBs, User Search, and Key Management

My initial idea was to utilize SURB’s as a “secret chat” feature that replies directly using the SURB instead of a recipient nym address. From my understanding, the initial request must still be sent directly to a nym address. From that point on message routing can be done strictly through SURB’s. This essentially means you have to be doxxed (nym-client address) in order to to communicate through SURBs, which is not good.

A solution could be a centralized server that acts as a registry. Each client will publish a batch of SURBs, an ephemeral public key, and a session ID (sender tag)

For example:
Client A creates and publishes a batch of SURBs to a registry with an associated ephemeral public key + session ID.

Client B could then query this registry using Client A’s session ID and return associated SURBs. This way Client B can initiate conversation with Client A without ever knowing their nym-client address.

As for key management, I am thinking of using ephemeral public keys tied to each batch of SURBs. Each time a batch of SURBs is exhausted, the associated subkey is revoked. The client will then automatically generate new SURBs + subkey and publish them to the registry. The private key will be generated and stored securely on the local machine.

This setup prioritizes anonymity and usability but also introduces the inherent risks of any centralized service.

I am not sure how to implement groupchats yet, I want to focus on the messaging itself first. If anyone has any insights I am all ears.

Thanks,
code-zm

2 Likes

So I really like where this is going, this sort of ‘SURB broadcast / deaddrop’ service! I want to throw a bunch of points in here as its bringing up a few concurrent trains of thought… Let me know if any of these bring up ideas/questions :slight_smile:

What is revealed by a Nym address

This essentially means you have to be doxxed (nym-client address) in order to to communicate through SURBs, which is not good.

Yes the SURB model sort of relies on a client - server model in which you have a client talking to a backend service of some kind you don’t want to doxx yourself too, not necessarily in a situation where your comms/addressing is more peer to peer. It sort of relies on one party (the client) always initiating the connection/request to the other (the server).

Remember that ‘doxxing’ a Nym address only shows which Gateway your client is using as an entry gateway, so overall not a whole lot is being revealed. That said, it is still showing which gateway a client is using, so depending on your threat model could still be undesirable.

Sending SURBs around

Client B could then query this registry using Client A’s session ID and return associated SURBs. This way Client B can initiate conversation with Client A without ever knowing their nym-client address.

You would probably not need to send the SURBs from the server to B to then package and send to A, just have B send their messages to A through the mixnet to the server already encrypted for A, and then the server can just package them and A can decrypt them on the ‘app’ level after they’ve done the Nym Client encryption → Mixnet → NC decryption dance.

MVD

This has the potential to grow into quite a big thing alongside the chat, so I think working out some sort of minimum viable version might be good to contain it a little :slight_smile: That said, I think this is also starting to hone in on an interesting problem that we don’t immediately have a solution for right now, which is how to communicate in a p2p-ish context without having to reveal your Nym address. I also think this might deserve a grant on its own, using the chat as a ‘proof of concept’ that it works. Really interesting stuff.

SURB requests

We might have to expose some method on the client which would allow for SURBs to be ‘manually’ requested, which wouldn’t necessarily be an issue, but this is not something you can do right now.

A high level example: for the moment, assuming that B has had 100 SURBs sent to it from A (B does not know A’s address), but the reply it wants to send requires 120 SURBs, it will use SURB 100 to request more SURBs from A before sending the full 120SURB reply.

What you would maybe want to do in this instance is something more like have the server / intermediary service keep track of how many SURBs remain per ‘bucket’ (the random alphanumeric string used to differentiate SURBs for a session) and then keep sending requests to keep this ‘topped up’ on its own. This might be jumping the gun a bit though in relation to my next point…

SURB lifetimes

For the moment the SURB is valid indefinitely, but the client will purge its local DB of SURBs that are older than a day on restart. We still have a few features to add to the Mixnet to add some extra security which will lower the validity of the actual SURB though: once we implement proper key rotation and replay protection, then SURBs will only be valid for the length of the key epoch. This length is still to be decided.

This means that you shouldn’t assume a situation in which Client A sends a bunch of SURBs to the server which can be used over the next e.g. day, but instead it will be more of a back-and-forth flow of SURB requests and topups.

As such, you’re possibly adding a few back and forth trips through the Mixnet for a message, if we assume that Client B wants to send a message to Client A but requires more SURBs than A current has stored on the server. Not a problem per se, just something to be aware of.

Potential attack

One thing to bear in mind is that there is the possibility of an (active) attack that the server, assuming it is untrusted, could try and perform against the clients. This would involve consistently requesting more and more SURBs from the clients, then sending all of these through the network at once, in order to try and create a ‘burst’ effect in the traffic and monitor which Gateways exhibit this behaviour, in order to work out which Gateways the clients of the SURBs were connected to. This is not necessarily a problem with this design, more a general active attack which it is good to be aware of here.

1 Like

Nym combined with GNUnet architecture development is a good choice

2 Likes

Update #1 01/12/2025

Nym Directory

The nym directory (server) acts as a remailer / directory, enabling user discovery, groupchats, and messaging without ever revealing nym-client addresses. Clients will still have the option to send messages directly p2p in a “secret chat”.

Database

I’ll be using NoSQL with MongoDB. The database will contain the following:
USERS Collection:
username
public key
sender tag
bio (optional)

GROUPS Collection:
group ID
user list

Custom Methods

The server receives messages from the mixnet which have json data encapsulated into the ‘message’ field to specify certain actions for the server.

send: send a message to a given username, signed.
sendAnon: send a message to a given username unsigned.
sendGroup: sends a message to each user in a given group signed, specified by groupID.
confirm: sends a confirmation, used by clients to determine their request was successful or not.
register: adds a user to the db (given a username, publicKey, and senderTag), replies with confirm or deny.
query: searches db for a given username, replies with confirm.
update: update the db for a specified field, signed, replies with confirm.
createGroup: adds a group to the db, generates a groupID and adds initial client to the list.
sendInvite: sends a message containing a groupID and group name, allowing a user to join.
joinGroup: adds a username to userList of a given group specified by groupID.

How it Works

When registering, clients submit the following information to the server, which is added to the db:
Username, sender tag, public key, and an optional bio.

User Discovery + Messages

Alice wants to send a message to Bob.

  1. Alice sends a ‘query’ containing Bob’s username to the Server.
  2. Server responds to Alice with a confirmation including Bob’s username and bio.
  3. Alice sends a ‘send’ to the server including Bob’s username and the message to send.
  4. The server finds Bob’s senderTag in the local DB and forwards Alice’s message to him using a reply type mixnet message.
  5. Bob receives the message, then sends a ‘send’ to the server including the recipient Alice and the message to send.
  6. The server finds Alice’s senderTag in the DB and forwards Bob’s message to her using a reply type mixnet message.
  7. Repeat steps 3 - 6.

Groupchats

Alice wants to create a groupchat with Bob and Charlie:

  1. Alice sends a ‘createGroup’ to the server.
  2. The server adds a new group to the collection.
  3. Alice sends a ‘sendInvite’ to both Bob and Charlie.
  4. Bob and Charlie receive the invite, then join by sending a ‘joinGroup’ to the server.
  5. The server adds Bob and Charlie to the userList. The userList now contains Alice, Bob, and Charlie.
  6. Alice sends a ‘sendGroup’ to the server, specifying the groupID and the message to send.
  7. The server queries the DB for the given group’s userList.
  8. The server sends the message to Bob and Charlie.

SURB Topups

I’m still not set on how this will work.

The server could keep track of the number of SURBs in each ‘bucket’. When the number of SURBs remaining is healthy, the user state is set to 1. When it reaches a certain threshold, the state is set to 0 and a ‘request’ is sent to the user to send more SURBs. While a user’s state is set to 0, messages intended for them will be placed in a MESSAGES collection, containing the username and a list of messages. When the user sends more SURBs, the state is set back to 1 and the messages in the queue get sent FIFO.

Client Mockups

Feel free to ask any questions! Currently, the codebase is in a private repo. I plan on making it public soon after a few more refinements!

5 Likes

This happens under the hood anyway, on a message trying to be sent. So as soon as your server runs out of SURBs it will request more anyway! There shouldn’t be a need for you to do / implement anything here.

1 Like

This might complicate your model, but what sticks out here is: how will a user, if they have to change client address (e.g. a gateway goes down) be able to update ?

Perfect! Shout out to whoever implemented that!

I’m going to use ECDSA to validate the updates. When registering, clients generate a long term key pair using NIST P-256. The public key gets sent to the server and stored in the local db. For updates, clients will sign the changes using their private key, and the server will verify the signature using the stored public key. This will only be used for validating updates / other methods that are signed, all communications will still rely on MLS.

1 Like

Update #2 01/28/2025

Authentication is working!

When registering, the client generates a key pair and attempts to register by sending a register message containing their username & public key.

The server responds with a ‘challenge’ containing a nonce for the client to sign.

The client signs the nonce with its private key and sends the signature back to the server.

The server validates that the signature came from the specified public key. If successful, the server responds with a ‘success’ message and adds the new user to the database. If it fails, the server responds with a ‘fail’ message specifying the error.

When logging in, the challenge handshake follows the same steps.

Next steps

  1. [CLIENT] Add functionality to track which messages belong to which chat
  2. [CLIENT] Front end work

Alpha Test

Coming early February! I’ll have an exact release date by the end of this week.

4 Likes

Update #3 02/15/25

Alpha test was successful! Thank you to everyone who tried it out!
knockknock

TODO:

  • Implement P2P Chats & Groups

ETA:
2/23/25

5 Likes

This is looking great

3 Likes

Update #4 02/23/25

Secret Chats

Users have the option to start a secret chat with other users. These chats are ephemeral and routed p2p through the mixnet.

High level overview:

  1. alice sends bob a ‘secretChatInvite’ containing her current nym-client address, routed through the NymDirectory.
  2. bob receives the ‘secretChatInvite’ and can ‘accept’ or ‘decline’ it (by doing nothing).
  3. bob accepts, sending back an ‘acceptSecretChat’ message containing his current nym-client address, routed directly to alice’s nym-client address.
  4. alice and bob can now communicate p2p.

Group Chats

Groupchats need further refinement to ensure decentralization is prioritized. Currently, the NymDirectory handles group management and message broadcasting. My new idea is self-hosted groupchats which are standalone instances of a nym-client. Groupchat instances have a db containing all group members’ username, public key, membership (member/admin/owner), and SURB. Group instances forward messages to all members and handle group operations. They communicate with the NymDirectory to validate user identities & to allow user discovery(if public). Users will be able to self-host groups or pay a custodian a fee to do so for them.

This will allow all message routing to be decentralized, leaving the role of the NymDirectory to authorization & user discovery.

Let me know your thoughts!

2 Likes

These chats are ephemeral and routed p2p through the mixnet.

How are you thinking of enforcing the ephemeral nature of it?

alice and bob can now communicate p2p.

I’m wondering how this relates to the original plan of using only SURBs to not have 2 members reveal their nym addresses to each other?

My new idea is self-hosted groupchats which are standalone instances of a nym-client.

What do you mean that the groupchat is an instance of a client?

They communicate with the NymDirectory to validate user identities & to allow user discovery(if public).

I would like to dig more into this NymDirectory: I think you need to look at what sort of architecture to have here since presumably you’d want to keep this hidden to not publicly reveal the nick → mapping. If you’re looking at something like a ‘peer server’, what sort of possibilities for misuse open up?

1 Like

Secret Messages will only be stored in memory instead of the db


The NymDirectory still forwards messages using only SURBs. This ‘secret chat’ is a separate option users have if they don’t want to leave a trace on the NymDirectory.

Normal Chats: client → mixnet → directory → mixnet → client
Secret Chats: client → mixnet → client


It’s basically a mini NymDirectory controlled by the owner of the group. It has it’s own nym-client address. Instead of forwarding messages individual → individual, it broadcasts to all members of the group.
Users can self host on their own machine for ‘free’ or pay us to spin up a server and host it for them. Groups can be public/private and open/invite only. If a group is public, it can be discovered via NymDirectory query. If private, it can only be discovered through knowledge of the nym-client address. If a group is open, anybody can join immediately upon discovery. If a group is invite-only, you have to be invited by an admin / request to join and wait for the admin’s decision.

I’ll add you to the repo. Currently the NymDirectory can see the following:

User info:
username
public key
senderTag (SURB)

Normal Messages:
source / target username

Queries:
source SURB
target username

Secret Messages:
nothing

Group Messages:
nothing

2 Likes

Ok right, so its ‘secret’ from the main directory as the secret chat has its own directory instance, right? With the directory acting as another remote server similar to the SURB relayer?

1 Like

This is very cool, we’re currently starting to look at something like a Nym Libp2p protocol for standardising p2p traffic formats via the Mixnet, but I really like this approach as well.

1 Like