libp2p_mix
June 17, 2026 · View on GitHub
Reference Nim implementation of the LIBP2P-MIX specification (LIP-99) — an anonymous routing protocol for nim-libp2p based on the Sphinx packet format with Single Use Reply Blocks (SURBs), LIONESS payload encryption, cover traffic, and pluggable spam protection.
This package was extracted from nim-libp2p's libp2p/protocols/mix/ tree to
let mix evolve independently. Full extraction history is preserved — every
commit that ever touched mix retains its original author, date, and message,
with PR references rewritten to vacp2p/nim-libp2p#NNNN form so reviewers
can click through to the original threads.
Specifications
This implementation tracks the following Logos LIPs published at lip.logos.co. The specs are the authoritative reference for protocol behaviour, packet formats, and security properties — code follows the spec, not the other way round.
| Spec | LIP | Link |
|---|---|---|
| LIBP2P-MIX — core mix protocol, Sphinx packet construction & handling | 99 | lip.logos.co/ift-ts/raw/mix.html |
| Mix Cover Traffic — constant-rate cover traffic, slot accounting, epoch handling | TBD | lip.logos.co/ift-ts/raw/mix-cover-traffic.html |
| RLN DoS Protection for Mixnet — per-hop RLN proof, membership tree | 144 | lip.logos.co/ift-ts/raw/mix-spam-protection-rln.html |
| Mix DoS Protection — abstract spam-protection interface | TBD | lip.logos.co/ift-ts/raw/mix-dos-protection.html |
Repository layout
libp2p_mix.nim Top-level facade — re-exports the public API
libp2p_mix/ Protocol implementation (23 modules)
├── mix_protocol.nim Core mix protocol (mounts on a libp2p Switch)
├── sphinx.nim Sphinx packet format with LIONESS payload encryption
├── cover_traffic.nim Constant-rate cover-traffic generator
├── exit_layer.nim Exit-node behaviour & dest read framing
├── entry_connection.nim/exit_connection.nim/reply_connection.nim
├── fragmentation.nim Packet fragmentation
├── pool.nim Mix node pool / route selection
├── spam_protection.nim Pluggable spam-protection abstract base
├── delay_strategy.nim Per-hop delay strategies
├── timedcache.nim Replay-cache primitive (vendored from libp2p pubsub)
├── lioness.nim LIONESS block cipher
└── … crypto, mix_node, multiaddr, serialization, etc.
tests/ Unit tests
├── component/ Integration tests using real libp2p switches
└── tools/ Test helpers (vendored from nim-libp2p tests/tools/)
examples/
└── mix_ping.nim End-to-end demo: ping over a 10-node mix network
config.nims Project-wide compiler config (--mm:refc, paths)
tests/config.nims Test-only defines (-d:metrics, libp2p subsystems)
libp2p_mix.nimble Package metadata & nimble tasks
Installation
Add the dependency to your .nimble file:
requires "libp2p_mix"
Or pin a specific revision:
requires "https://github.com/logos-co/nim-libp2p-mix.git#<commit-or-tag>"
Integration
Mounting mix on a libp2p switch:
import libp2p_mix
import libp2p_mix/mix_protocol
import libp2p_mix/mix_node
let mixNodeInfo = initMixNodeInfo(
peerId, multiAddr, mixPubKey, mixPrivKey, libp2pPubKey, libp2pPrivKey
)
let mix = MixProtocol.new(mixNodeInfo, switch).valueOr:
return err("mix init failed: " & error)
# Optional: configure how the exit layer reads payloads for a given proto
mix.registerDestReadBehavior("/your/proto/1.0.0", readLp(maxSize = -1))
# Optional: bootstrap the node pool
for bootstrapNode in bootstrapNodes:
mix.nodePool.add(bootstrapNode)
# Mount and start
switch.mount(mix)
await mix.start()
# Open a mix-routed connection. `toConnection` does NOT send anything yet —
# it returns a libp2p `Connection` whose subsequent writes/reads are wrapped
# into Sphinx packets and routed through the mix overlay. Use it like any
# other libp2p connection.
let conn = mix.toConnection(
MixDestination.init(targetPeerId, targetMultiAddr),
proto = "/your/proto/1.0.0",
MixParameters(expectReply: Opt.some(true), numSurbs: Opt.some(1.byte)),
).valueOr:
return err(error)
# Now write/read as usual — the mix layer handles Sphinx wrapping, routing,
# and (when expectReply is set) collecting the response via SURBs.
await conn.writeLp(requestBytes)
let response = await conn.readLp(maxBytes)
For a complete worked example, see examples/mix_ping.nim.
Pluggable spam protection
Spam protection is a SpamProtection abstract base class
(libp2p_mix/spam_protection). Pass an
implementation at construction time:
let mix = MixProtocol.new(
mixNodeInfo,
switch,
spamProtection = Opt.some(SpamProtection(myImpl)),
)
A reference RLN-based implementation lives in
mix-rln-spam-protection-plugin.
Cover traffic & delay strategies
let ct = ConstantRateCoverTraffic.new(
totalSlots = 10, epochDuration = 10.seconds, useInternalEpochTimer = true
)
let delay = DelayStrategy(
ExponentialDelayStrategy.new(meanDelay = 100, rng = newRng())
)
let mix = MixProtocol.new(
mixNodeInfo, switch,
coverTraffic = Opt.some(CoverTraffic(ct)),
delayStrategy = Opt.some(delay),
)
Building & running
To set up the project:
git clone https://github.com/logos-co/nim-libp2p-mix.git
cd nim-libp2p-mix
make setup # generates nimble.paths
make setup runs nimble setup -l (--localdeps), which runs nimble in
project-local dependency mode and adds --noNimblePath to the generated
nimble.paths.
You can override the NIMBLE_FLAGS variable to pass extra flags to nimble:
make setup NIMBLE_FLAGS="-y" # non-interactive
make setup NIMBLE_FLAGS=--solver:legacy # legacy solver
Tests
nimble test # 14 unit-test files (~143 individual checks)
nimble testComponent # 6 component (integration) tests, ~26 checks
nimble testAll # both
The tests/config.nims enables -d:metrics and several
libp2p_*_metrics defines so tests can assert on metric counters.
Example
nimble example
This compiles examples/mix_ping.nim, which spins up 10 mix nodes locally,
mounts the libp2p Ping protocol on a destination, sends a ping through the
mix network, and waits for the reply via SURBs. Expected output:
INF Ping response received through mix network rtt=41ms…
To build the binary without auto-cleanup:
nim c -d:libp2p_mix_experimental_exit_is_dest -d:metrics -o:mix_ping examples/mix_ping.nim
./mix_ping
Cleaning
make cleanremoves local generated artifacts:nimble.lock,nimbledeps/, andnimble.paths.make clean-nimbledepsonly removesnimbledeps/andnimble.paths, leaving the Nix dependency lock untouched.make refresh-depsforces regeneration of the committednix/deps.nixsnapshot.
Nix
A flake is provided for reproducible dev shells and builds.
nix develop # drops you into a shell with nim 2.2 + nimble
nix build # type-checks libp2p_mix.nim against locked deps
The flake reads nix/deps.nix, which is the committed snapshot of all
pinned transitive dependencies. Refresh it after bumping the libp2p pin in
libp2p_mix.nimble:
make deps # regenerates nix/deps.nix
If you need to force regeneration from a clean intermediate lock file, run:
make refresh-deps
NIMBLE_FLAGS can be passed to make refresh-deps the same way as
make setup; command-line variables are forwarded to the recursive
make deps invocation:
make refresh-deps NIMBLE_FLAGS="-y --solver:legacy"
If this does not work for any reason and you need to start fresh while in the Nix shell
(so after the initial nix develop), run:
make clean
make setup
make refresh-deps
nix build
To quickly check that you are in the nix shell run:
echo "${IN_NIX_SHELL:-not in nix shell}"
$IN_NIX_SHELL is set by both nix develop and nix-shell. Typical
values: impure (default) or pure (when invoked with --pure). Any
non-empty value means you're inside a nix shell. Empty or unset means
you're not.
make deps requires nix-prefetch-git and jq on $PATH. Internally it
generates a fresh nimble.lock, forwards NIMBLE_FLAGS to nimble lock,
and then transforms the lock file via tools/gen-deps.sh.
nimble.lock itself is not committed — it's an intermediate build
artefact regenerated on demand (it lives in .gitignore). The long-lived
pinning artefact for this repo is nix/deps.nix. Downstream consumers
that need an exact dep set should pin libp2p_mix by URL+SHA in their own
.nimble. See logos-co/nim-libp2p-mix#13 for the discussion behind this.
Compile-time flags
| Flag | Purpose |
|---|---|
-d:libp2p_mix_experimental_exit_is_dest | Allow exit nodes to also be the message destination (waku/lightpush usage). Enabled by default in libp2p_mix.nimble. |
-d:metrics | Enable Prometheus-style metric counters (test-time default). |
-d:enable_mix_benchmarks | Compile in benchmark/timing helpers from libp2p_mix/benchmark.nim. |
License
Licensed under either of:
- Apache License 2.0 (LICENSE-APACHEv2)
- MIT license (LICENSE-MIT)
at your option.