QZMQ: Quantum-Safe ZeroMQ for the Hanzo and Lux Stacks

A hybrid X25519 + ML-KEM-768 transport that drops in where CurveZMQ used to live. PQ by default, two backends, every ZMQ pattern supported.

ZeroMQ is the connective tissue of large parts of our infrastructure — MCP transport between agent runtimes, inference plane links, validator gossip on Lux, MPC coordination, indexer fan-out. Until now, the strongest authenticated encryption mechanism in mainline ZMQ was CurveZMQ, built on Curve25519. Curve25519 is fine today. It is not fine on the day a relevant quantum computer exists, and harvest-now-decrypt-later is already a posture adversaries take seriously.

Today we ship QZMQ — a post-quantum secure transport for ZeroMQ, hybrid X25519 + ML-KEM-768 by default, ML-DSA / Ed25519 for signatures, AEAD via AES-256-GCM or ChaCha20-Poly1305, HKDF for key derivation. It is a drop-in replacement for CurveZMQ across every ZMQ pattern we use.

Why now

The migration window for post-quantum cryptography is not "after Q-Day." It is now. Every encrypted byte on the wire today is a candidate for offline decryption when ML-capable adversaries arrive. Transport-layer security is the first thing you upgrade because it protects everything above it.

We chose ZMQ because we already use it. The choice was not "do we keep ZMQ"; the choice was "do we patch CurveZMQ or do we replace it." We replaced it.

Hybrid by default

QZMQ has three modes:

const (
    ModeHybrid  = iota // X25519 + ML-KEM (default)
    ModePQOnly         // Pure post-quantum
    ModeClassic        // Classical only (for compatibility)
)

The default is hybrid. The classical share gives you defense-in-depth against ML-KEM cryptanalysis surprises. The PQ share gives you defense against quantum attackers. You only lose if both classical and post-quantum fail at the same time, which is the correct property to require.

Pure post-quantum is available for environments that require it (FIPS-aligned profiles, regulated deployments). Classical-only exists for interop testing and is never the default.

Algorithms

  • KEM: ML-KEM-768 and ML-KEM-1024 (NIST FIPS 203, formerly Kyber), plus X25519 for the hybrid share.
  • Signatures: ML-DSA-44 / 65 / 87 (NIST FIPS 204, formerly Dilithium), plus Ed25519 in hybrid mode.
  • AEAD: AES-256-GCM and ChaCha20-Poly1305.
  • KDF: HKDF-SHA256 / HKDF-SHA384.

These are the NIST-standardized choices. No experimental KEMs, no homebrew constructions. The wire protocol pins each suite so a server cannot be talked into negotiating down to a weaker one.

Drop-in across every pattern

Every ZeroMQ pattern is supported with QZMQ on the wire:

  • REQ / REP — request / reply
  • PUB / SUB — publish / subscribe
  • PUSH / PULL — pipeline
  • DEALER / ROUTER — advanced request-reply
  • PAIR — exclusive pair
  • STREAM — raw TCP

If your service speaks CurveZMQ today, switching is mostly a build flag, a key swap, and a configuration string. No application-level changes.

Two backends, one API

QZMQ ships with two backends behind a single API:

  1. Pure Go (CGO_ENABLED=0) using github.com/luxfi/zmq/v4. No C dependency. Builds on every platform Go supports. This is the deployment target for our containers and for anywhere we don't want a libzmq dependency.
  2. CGO (CGO_ENABLED=1) using github.com/pebbe/zmq4 over libzmq. Used for compatibility testing and for workloads where the libzmq router behaves measurably better under load.

Both backends pass the same test suite. Choosing one is a build-flag decision; the application code is unchanged.

# Pure Go
CGO_ENABLED=0 go build ./...

# libzmq (CGO)
CGO_ENABLED=1 go build ./...

Production-shaped from day one

QZMQ ships with:

  • luxfi/log integration for structured logs across socket bind, accept, handshake, key rotation, and shutdown.
  • luxfi/metric Prometheus surface — message counts, byte counts, latency histograms, active connections, all labeled by socket type and direction.
  • A mainnet-shaped test suite covering high-volume stress, network partition simulation, key rotation under load, and memory-leak detection.

Operations endpoints expose Prometheus at :9090/metrics. Dashboards plug into the existing luxfi/metric stack with no changes.

Where it ships first

QZMQ replaces CurveZMQ wherever we run authenticated ZMQ today:

  • Hanzo — MCP transport between agent runtimes, inference plane links, control plane between Hanzo Node and bot runtimes, voice and channel adapters.
  • Lux — validator gossip overlays, MPC node coordination, indexer fan-out, AI subnet inference channels.
  • Hanzo World — agent-to-agent messaging for multi-runtime sessions.

This is one transport across both ecosystems. Same wire format. Same suite. Same audit surface.

Compatibility, not nostalgia

There is no formal compatibility mode with CurveZMQ. A QZMQ peer talks to a QZMQ peer. A CurveZMQ peer continues to talk to a CurveZMQ peer until you upgrade it. This is on purpose. The whole point of moving is to leave the curve behind. We migrate services one at a time; both endpoints upgrade together.

What's next

Roadmap items already in flight:

  • Hardware acceleration via AES-NI / AVX2 paths and Apple MLX for ML-KEM polynomial arithmetic.
  • FIPS 140-3 module certification on the pure-Go AEAD and KDF cores.
  • WebSocket and QUIC transports on top of the same record layer, for browser-side agents.
  • Extended monitoring dashboards and a Grafana pack.

Get QZMQ

Drop-in PQ transport. Two backends. Every pattern. Hybrid by default. The transport you were going to replace anyway is replaced.


Wire format and suite negotiation details in the repo. We treat any deviation between the doc and the deployed binary as a finding.

Read more