Mix implementation in javascript and js-waku

Background and Motivation

In order to make waku-mix available for testing into the public waku network, it would be beneficial if we can implement libp2p-mix in javascript so that developers can start using mix and we can get better feedback of its performance and might help fine-tune and iterate on it. Existing apps using js-waku can also start using mix.

This document analyzes the implementation considerations for libp2p-mix in JavaScript, specifically focusing on browser-based applications and their integration with [js-waku](JavaScript Waku SDK | Waku Documentation"
It is possible some details have been missed which can be looked at and resolved during implementation.

Hoping that it would act as a reference for implementers(both js-mix and js-waku).

Overview

Currently, waku-mix integrates with request-response protocols such as lightpush and Store, which are primarily used by edge nodes."

Majority of apps are generally built using js-waku which would act as edge/light nodes.

The implementation consists of two main components:

  1. libp2p-mix implementation in javascript. Note that complete protocol implementation is not required.
  2. Changes in Waku
    a. lightpush and store implementations to allow publishing using mix.
    b. changes related to discovery

Implementing libp2p-mix in JavaScript

It would be good to refer [nim-mix](https://github.com/vacp2p/mix/tree/main-s2) implementation which is a reference implementation for the protocol.

While we could implement the complete specification, initially js-waku only requires entry-side functionality—specifically packet sending logic and (https://cypherpunks.ca/~iang/pubs/Sphinx_Oakland09.pdf) reply processing.
There is no need to implement intermediate and exit node specific functionalities.
The mix implementation should be designed as a libp2p protocol and transport layer that integrates seamlessly with existing protocols."

The following components would be required to be implemented:

  1. Entry Connection similar to nim
  2. Send functionality which would be invoked while sending messages.
  3. Maintaining a mix node pool and using it to select peers for mix path.
  4. SURB handling at entry node when reply comes back.
  5. No need to implement fragmentation or any other extra functionality as it is expected for user’s of mix to fragment messages to the size that works with mix(current default is 4KB

Note that javascript already seem to have libraries that support sphinx packet encryption (such as sphinx-js or which would mean the cryptography would already be available to be integrated. This should make the implementation quicker and easier.

SURB specific logic

Browser environments have a fundamental limitation: external nodes cannot initiate connections to browsers (unless using WebRTC). This creates challenges for handling mix replies via [Single Use Reply Blocks](https://cypherpunks.ca/~iang/pubs/Sphinx_Oakland09.pdf)." In order to get around this limitation SURB should be generated as mentioned below in browser environments.

When the forward message path is browser(sender) → N1 → N2 → N3 → destination, the SURB reply path should be constructed as N3 → N4 → N1 → browser. A brief illustration below of the same.

The key design principle is that the penultimate node in the reply path must match the first intermediate node from the forward path (N1 in this example). This ensures that N1 can reuse the existing connection established during the forward message transmission when relaying the reply back to the browser, rather than having to establish a new connection.

Updates in js-waku

js-waku functions solely as a sender node, utilizing lightpush and store protocols as a client/service-user. The lightpush and store send APIs should include an optional mixify flag to indicate when mix protocol should be used for packet transmission."

js-waku would require an initialization param which indicates whether mix is to be enabled or not. If mix is enabled the logic of maintaining a mix pool would be required to be implemented i.e as and when peers are discovered along with their ENR’s, they are required to be added to the mix-pool so that mix protocol layer can choose from the pool of nodes randomly while generating a path.

Logic to fragment messages before handing them over to mix layer.

@akshaya - Would love to get your feedback on the mix implementation section specifically.

4 Likes

Similar to SDS, I suspect this is the main “risk” in building this that would increase potential effort required to make it fully interoperable, which is incomplete symmetry between implementations of primitives, especially cryptographic primitives.

Something I tried with nim-sds (though I did not test the performance) was compile one of the nim implementations of a hash function to js and use that in my bloom filter implementation. It seems to come with caveats.

We have not yet reached the point of immediately needing or testing nim<->js interoperability with sds, but I suspect there will be similar challenges there as with mix interoperability. It may warrant some further research into nim<->js interoperability in general…

Hmm, Ideally interoperability should not be an issue if protocol has been spec’d properly. Isn’t the whole point of a protocol spec done so that it can be implemented in any language and not have interop issues? Maybe i am missing something here.

This does seem concerning, if we keep facing interop issues then either we have to improve our specs or add more details that are missing which is causing interop issues.

I just managed to read this post

Good approach with SURB for js-waku, this is what I though of as well when first read about mix.

As I understand, approach with having entry node for send path being same as exit in reply path is a bit of a privacy tradeoff we do due to browser limitations.

If so, we can try to improve it by leveraging mix node pool maintained by js-waku and have different nodes as entry / exit.

1 Like

How so? I don’t think it would be possible to overcome the limitation unless browser also has a listening address which means either probably by using webrtc transport which restricts choise of transport.

In post JS limitation is overcame by reusing connection from N1 to receive reply.
js-waku can work in a way that it will support pool of connections to mix nodes for example {N1, ..., N3} so that we can N1 can be entrance node and N3 can be used for reply.

This is just an idea, I am not sure it fits into mix protocol in general yet.

1 Like

I like the idea, but yea it might be more complex to integrate into mix this way as now js-waku would have 2 types of peers 1 mix pool and second would be pool of nodes to which we have connections and we have to indicate both to mix while building the path and pool size would reduce for selecting the path.

We may have to analyze the impact of choosing this approach vs reusing a connection.

Great write up! Excited to see this shaping up. Just wanted to add a few points that may not be immediately obvious:

  1. SURB handling still requires Sphinx processing logic at entry
    Even in an entry-only implementation, the js-waku nodes must process incoming Sphinx packets. Only after decrypting their own layer can they determine whether the packet contains a reply.

  2. Sphinx packet format differences
    nim-mix slightly modifies Sphinx parameters to embed per-hop libp2p multiaddresses and delays in the header. Just flagging this early. It should be configurable in the js-Sphinx libraries and isn’t a blocker.

  3. Identifying reply packets at N1 (the browser’s upstream node)
    Since the browser maintains a persistent WebSocket connection to N1, N1 must distinguish replies to the browser from other traffic, so it knows to reuse the open browser stream (rather than dialing out as usual).
    This likely requires embedding some identifier in the Sphinx payload for session mapping. While edge nodes often know their path position, this makes N1 — an intermediate hop — path-aware, which is not typical in mixnets. This is somewhat inevitable given browser constraints, but worth documenting.

  4. Changes needed in nim-mix intermediate node behavior
    Currently, nim-mix opens a new stream for each hop by default. Supporting browser replies would require intermediate nodes like N1 to reuse an existing connection to the browser — likely by mapping an identifier in the incoming Sphinx packet to an existing connection (as mentioned in 3. above). This isn’t supported yet and would require changes to the intermediate logic.

  5. N1’s role becomes persistent and potentially sensitive
    Since the browser reuses its connection to N1 for multiple messages, N1 holds long-term state for that browser. If adversarial, N1 gains extended visibility into traffic patterns, enabling correlation over time. This differs from typical mixnets where intermediate nodes are stateless and ephemeral.

  6. Deanonymization risk due to fixed edge nodes
    In mixnet settings, controlling first and last hop in a path enables traffic analysis. Here, the attacker gains similar advantage by controlling N3 (entry on reply) and N1 (last-but-one hop).
    When reply paths reuse N1 and N3, an attacker controlling both can perform traffic analysis with higher success probability.

  • In a standard mixnet with random forward and reply paths: attacker must control 4 nodes ⇒ probability = p^4
  • In the browser design: only N1 and N3 ⇒ probability = p^2
    With p = 0.3, that’s ~9% vs. ~0.8%.
    If the attacker also knows the baseline transmission time between N3, N4, and N1 (without the added mix delays) they can further refine sender–destination correlation. While Sphinx packets’s fixed size and per-hop delays provide some resistance, in practice predictable or low-latency deployments may still enable traffic analysis, especially with persistent connection to N1.
  1. Comparison to non-browser setting (with Mix)
    In non-browser cases, reply paths can fully randomize return path (e.g., destination → N3 → N4 → N5 → sender). Here, to correlate forward and reply paths, the attacker would need to compromise both the sender’s and the the exit’s Mix instance — which is impractical, as the sender’s own Mix instance is assumed to be trusted.

  2. Possible mitigation
    One alternative might be to explore exit == destination, where the final hop (destination) would support Mix directly. This may not always be desirable, but could be a worthwhile tradeoff for stronger anonymity in browser contexts. Since to correlate forward and reply paths, the attacker would need to compromise both the destination’s and N1’s Mix instance

This isn’t a full fledged analysis, just sharing some early thoughts on potential anonymity implications.

1 Like

Thanks a lot! These are very helpful for implementation.

This looks like a better solution to reduce attack probability. But iirc each protocol in a network can only support one mode i.e either exit=destination or not.

WDYT @haelius . If we make waku’s default behaviour for light protocols to use exit=destination model this would help address browser limitation. Anyways in the initial phase we would want all relay nodes (which are inturn waku service nodes to enable mix).

Yes, I think this is a reasonable approach.

From @akshaya’s great analysis above, it does seem though that there is a risk of many hidden complexities in the implementation (slight Sphynx packet differences, required nim-mix changes, js-libp2p risks, etc.). I’d suggest we limit our risk appetite here by first analysing what needs to be done and trying to highlight any possible blockers before jumping into an implementation. I’d also like to see this specified somewhere as a protocol for mix-publishing where publishers can only make outgoing connections. Presumably this affects browsers but also publishers behind a strict NAT?

Agreed, I will do some more analysis and identify list of things to be done and review it with @akshaya.

You mean the decision choices being made due to the limitation that publishers can only establish outgoing connections?
I think this should be part of libp2p Mix spec itself.
WDYT @akshaya

this is a good point, but wondering if DCUtR would be able to solve this. Would require some testing to confirm this.

But iirc each protocol in a network can only support one mode i.e either exit=destination or not.

The current Mix RFC and implementation focus solely on the exit != destination case now. This is working fine for GossipSub as well.
May be we could experiment exit==destination just for the browser use case behind a compile-time flag (e.g., mix_experimental_exit_is_destination ) as @rramos suggested.

You mean the decision choices being made due to the limitation that publishers can only establish outgoing connections?
I think this should be part of libp2p Mix spec itself.

These design choices introduce some deviations from the standard Mix Protocol design though — specifically, intermediate nodes need to maintain state to reuse a stream for replies.
For clarity and incremental design, it may be preferable to document this behavior in a separate spec initially. Once the implementation and security considerations are fully understood, we could integrate it into the main Mix RFC in a future iteration. Thoughts?

That sounds fine because it looks like for Waku light protocols we would go ahead with this approach as of now.

That sounds like a good idea too. We can create a new spec specifically for these scenarios.

Agreed.