From a70e68c7bd75604e72afe8815b6596f2fe10317a Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Wed, 13 May 2026 11:19:44 +0000 Subject: [PATCH] Max/smolmix docs (#6716) * Smolmix documentation * Add smolmix docs: landing page, tutorials, and developer page links * Add Exit Gateway services page (NR vs IPR) and link from existing docs * Update auto-generated command and API outputs * Reorg of tutorials and architecture pages * License information + remove TODO from docs.rs visibile comment + reorg readme * Add versions file for doc-wide versioning * Relative -> absolute links * Relative -> absolute links * Update license + add old tutorial code as examples * Streamline smolmix docs * Clippy * Clean up doc comments * Last pass * Add larger file download to list * set new versions * Clippy * Remove blake pin from docs + add version range to root Cargo.toml * Format example logging * Remove crate blocked component * Loose whitespace * Add doc verification script for inline mdx * Formatting * Components regen * Reorg + tighten text * Voicing cohesion pass + remove bloated examples * Voicing cont. * Reduce max download size * Small suggested clarifications * Max/docs voicing consistency (#6769) * Reduce max download size * voicing consistency across docs * New landing order w smolmix * Tweaks * Final tweaks --- .github/workflows/cd-docs.yml | 3 + .github/workflows/ci-docs.yml | 3 + .gitignore | 1 + Cargo.lock | 2 +- Cargo.toml | 2 +- common/ip-packet-requests/src/lib.rs | 1 + documentation/.gitignore | 1 + .../docs/components/code-verified.tsx | 33 +- .../docs/components/crates-paused.tsx | 13 - .../docs/components/landing-page.tsx | 32 +- .../docs/components/lewes-pending.tsx | 57 + documentation/docs/components/mix-fetch.tsx | 197 +- .../api-scraping-outputs/nodes-count.json | 6 +- .../outputs/api-scraping-outputs/time-now.md | 2 +- .../node-api-check-query-help.md | 2 +- .../outputs/command-outputs/nym-api-help.md | 6 +- .../nym-node-cli-install-help.md | 3 +- .../outputs/command-outputs/nym-node-help.md | 6 +- .../command-outputs/nym-node-run-help.md | 211 +- .../outputs/command-outputs/nymvisor-help.md | 3 +- .../docs/components/version-banner.tsx | 15 + documentation/docs/components/versions.ts | 31 + documentation/docs/next.config.js | 36 +- documentation/docs/pages/_meta.json | 16 +- .../docs/pages/apis/explorer-api.mdx | 2 +- documentation/docs/pages/apis/ns-api.mdx | 2 +- .../docs/pages/developers/_meta.json | 21 +- .../pages/developers/archive/nym-connect.mdx | 10 +- .../docs/pages/developers/browsers.mdx | 43 - documentation/docs/pages/developers/chain.md | 8 +- .../docs/pages/developers/chain/cli-wallet.md | 4 +- .../pages/developers/chain/ledger-live.md | 4 +- .../docs/pages/developers/chain/rpc-node.md | 6 +- .../docs/pages/developers/clients/socks5.mdx | 4 +- .../developers/clients/socks5/config.mdx | 2 +- .../pages/developers/clients/socks5/setup.mdx | 2 +- .../pages/developers/clients/socks5/usage.mdx | 2 +- .../developers/clients/webassembly-client.mdx | 19 +- .../pages/developers/clients/websocket.md | 2 +- .../developers/clients/websocket/config.md | 2 +- .../developers/clients/websocket/examples.md | 6 +- .../developers/clients/websocket/usage.md | 12 +- .../developers/concepts/message-queue.mdx | 6 +- documentation/docs/pages/developers/index.mdx | 33 +- .../docs/pages/developers/integrations.mdx | 53 +- .../docs/pages/developers/mix-fetch.mdx | 130 + .../docs/pages/developers/native.mdx | 63 - .../docs/pages/developers/nymvpncli.mdx | 4 +- documentation/docs/pages/developers/rust.mdx | 45 +- .../pages/developers/rust/client-pool.mdx | 14 +- .../developers/rust/client-pool/tutorial.mdx | 73 +- .../docs/pages/developers/rust/ffi.mdx | 4 +- .../docs/pages/developers/rust/importing.mdx | 30 +- .../docs/pages/developers/rust/mixnet.mdx | 2 +- .../pages/developers/rust/mixnet/examples.mdx | 2 +- .../pages/developers/rust/mixnet/tutorial.mdx | 70 +- .../docs/pages/developers/rust/stream.mdx | 15 +- .../developers/rust/stream/architecture.mdx | 4 +- .../pages/developers/rust/stream/tutorial.mdx | 44 +- .../docs/pages/developers/rust/tcpproxy.mdx | 226 +- .../docs/pages/developers/rust/tour.mdx | 4 +- .../docs/pages/developers/smolmix.mdx | 130 + documentation/docs/pages/developers/tools.mdx | 4 +- .../developers/tools/diagnostic-tool.mdx | 40 +- .../pages/developers/tools/nym-cli/usage.mdx | 4 +- .../developers/tools/standalone-tcpproxy.mdx | 4 +- .../docs/pages/developers/typescript.mdx | 68 +- .../typescript/bundling/esbuild.mdx | 17 +- .../typescript/bundling/webpack.mdx | 11 +- .../typescript/examples/cosmos-kit.mdx | 15 +- .../typescript/examples/mix-fetch.mdx | 32 +- .../developers/typescript/examples/mixnet.mdx | 2 +- .../typescript/playground/cosmos-kit.mdx | 4 +- .../typescript/playground/mixfetch.mdx | 2 +- .../typescript/playground/traffic.mdx | 2 +- .../docs/pages/network/cryptography.md | 2 +- .../docs/pages/network/cryptography/sphinx.md | 4 +- .../pages/network/cryptography/zk-nym.mdx | 12 +- .../cryptography/zk-nym/double-spend-prot.mdx | 8 +- .../cryptography/zk-nym/rerandomise.mdx | 4 +- .../cryptography/zk-nym/unlinkability.mdx | 8 +- .../cryptography/zk-nym/zk-nym-overview.mdx | 8 +- documentation/docs/pages/network/dvpn-mode.md | 4 +- .../docs/pages/network/dvpn-mode/protocol.mdx | 12 +- documentation/docs/pages/network/index.md | 4 +- .../docs/pages/network/infrastructure.md | 4 +- .../pages/network/infrastructure/_meta.json | 3 +- .../network/infrastructure/exit-services.mdx | 84 + .../network/infrastructure/nym-nodes.mdx | 4 +- .../docs/pages/network/mixnet-mode.mdx | 6 +- .../network/mixnet-mode/anonymous-replies.mdx | 4 +- .../network/mixnet-mode/cover-traffic.md | 4 +- .../docs/pages/network/mixnet-mode/loopix.md | 4 +- .../docs/pages/network/mixnet-mode/mixing.mdx | 6 +- documentation/docs/pages/network/overview.md | 2 +- .../pages/network/overview/choosing-a-mode.md | 2 +- .../pages/network/overview/comparisons.md | 2 +- .../docs/pages/network/reference/acks.mdx | 8 +- .../docs/pages/network/reference/epochs.md | 6 +- documentation/docs/public/llms-full.txt | 2480 +++++++++-------- documentation/docs/public/llms.txt | 23 +- documentation/scripts/verify-doc-versions.sh | 62 + .../nym-sdk/examples/builder_with_storage.rs | 13 +- sdk/rust/nym-sdk/examples/client_pool.rs | 19 +- sdk/rust/nym-sdk/examples/simple.rs | 20 +- .../examples/stream_simple_read_write.rs | 26 +- sdk/rust/nym-sdk/examples/surb_reply.rs | 22 +- smolmix/README.md | 23 +- smolmix/core/Cargo.toml | 4 +- smolmix/core/README.md | 19 - smolmix/core/examples/tcp.rs | 38 +- smolmix/core/examples/tcp_download.rs | 175 ++ smolmix/core/examples/udp.rs | 32 +- smolmix/core/examples/websocket.rs | 36 +- smolmix/core/src/ARCHITECTURE.md | 67 +- smolmix/core/src/bridge.rs | 26 +- smolmix/core/src/device.rs | 17 +- smolmix/core/src/error.rs | 3 +- smolmix/core/src/lib.rs | 8 +- smolmix/core/src/tunnel.rs | 81 +- 120 files changed, 3043 insertions(+), 2346 deletions(-) delete mode 100644 documentation/docs/components/crates-paused.tsx create mode 100644 documentation/docs/components/lewes-pending.tsx create mode 100644 documentation/docs/components/version-banner.tsx create mode 100644 documentation/docs/components/versions.ts delete mode 100644 documentation/docs/pages/developers/browsers.mdx create mode 100644 documentation/docs/pages/developers/mix-fetch.mdx delete mode 100644 documentation/docs/pages/developers/native.mdx create mode 100644 documentation/docs/pages/developers/smolmix.mdx create mode 100644 documentation/docs/pages/network/infrastructure/exit-services.mdx create mode 100755 documentation/scripts/verify-doc-versions.sh delete mode 100644 smolmix/core/README.md create mode 100644 smolmix/core/examples/tcp_download.rs diff --git a/.github/workflows/cd-docs.yml b/.github/workflows/cd-docs.yml index 98995e6986..bdc3724563 100644 --- a/.github/workflows/cd-docs.yml +++ b/.github/workflows/cd-docs.yml @@ -37,6 +37,9 @@ jobs: command: build args: --workspace --release + - name: Verify doc versions + run: ${{ github.workspace }}/documentation/scripts/verify-doc-versions.sh + working-directory: ${{ github.workspace }} - name: Install project dependencies run: pnpm i - name: Generate llms-full.txt diff --git a/.github/workflows/ci-docs.yml b/.github/workflows/ci-docs.yml index 50b168b82d..07660f5578 100644 --- a/.github/workflows/ci-docs.yml +++ b/.github/workflows/ci-docs.yml @@ -61,6 +61,9 @@ jobs: cd ${{ github.workspace }}/sdk/typescript/packages/sdk && typedoc --skipErrorChecking cd ${{ github.workspace }}/sdk/typescript/packages/mix-fetch && typedoc --skipErrorChecking + - name: Verify doc versions + run: ${{ github.workspace }}/documentation/scripts/verify-doc-versions.sh + working-directory: ${{ github.workspace }} - name: Install project dependencies run: pnpm i - name: Generate llms-full.txt diff --git a/.gitignore b/.gitignore index 1178ece5d5..72c468a3fd 100644 --- a/.gitignore +++ b/.gitignore @@ -78,3 +78,4 @@ CLAUDE.md /notes /target-otel +test-tutorials/ diff --git a/Cargo.lock b/Cargo.lock index 99019e079c..2d24747ef7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11301,7 +11301,7 @@ dependencies = [ [[package]] name = "smolmix" -version = "0.0.1" +version = "1.20.4" dependencies = [ "futures", "hickory-proto", diff --git a/Cargo.toml b/Cargo.toml index 14ab79e00a..9d3c2121da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -229,7 +229,7 @@ base85rs = "0.1.3" bincode = "1.3.3" bip39 = { version = "2.0.0", features = ["zeroize"] } bitvec = "1.0.0" -blake3 = "1.7.0" +blake3 = ">=1.7, <1.8.4" # blake3 1.8.4+ requires digest 0.11; workspace is on 0.10 bloomfilter = "3.0.1" bs58 = "0.5.1" bytecodec = "0.4.15" diff --git a/common/ip-packet-requests/src/lib.rs b/common/ip-packet-requests/src/lib.rs index 7468c3580a..770fcaa933 100644 --- a/common/ip-packet-requests/src/lib.rs +++ b/common/ip-packet-requests/src/lib.rs @@ -32,6 +32,7 @@ mod tests { fn stream_transport_threshold_is_consistent() { assert_eq!(MAX_NON_STREAM_VERSION, 8); assert_eq!(SPHINX_STREAM_VERSION_THRESHOLD, 9); + const _: () = assert!(SPHINX_STREAM_VERSION_THRESHOLD > MAX_NON_STREAM_VERSION); } } diff --git a/documentation/.gitignore b/documentation/.gitignore index 4329a0d6c0..cf1c85310b 100644 --- a/documentation/.gitignore +++ b/documentation/.gitignore @@ -1,2 +1,3 @@ todo.md scripts/generate-api +RELEASE_TASKS.md diff --git a/documentation/docs/components/code-verified.tsx b/documentation/docs/components/code-verified.tsx index f3390c6668..1742160b4c 100644 --- a/documentation/docs/components/code-verified.tsx +++ b/documentation/docs/components/code-verified.tsx @@ -1,22 +1,33 @@ import { Callout } from "nextra/components"; +import { NYM_SDK_VERSION, SMOLMIX_VERSION } from "./versions"; -const COMMIT_SHORT = "97068b2"; -const COMMIT_FULL = "97068b2aa"; -const EXAMPLES_URL = - "https://github.com/nymtech/nym/tree/develop/sdk/rust/nym-sdk/examples"; +const VERSIONS: Record = { + "nym-sdk": NYM_SDK_VERSION, + smolmix: SMOLMIX_VERSION, +}; -export const CodeVerified = () => ( +const EXAMPLES_URLS: Record = { + "nym-sdk": + "https://github.com/nymtech/nym/tree/develop/sdk/rust/nym-sdk/examples", + smolmix: + "https://github.com/nymtech/nym/tree/develop/smolmix/core/examples", +}; + +interface CodeVerifiedProps { + /** Crate name to display. Defaults to "nym-sdk". */ + crate?: keyof typeof VERSIONS; +} + +export const CodeVerified = ({ crate: crateName = "nym-sdk" }: CodeVerifiedProps) => ( - Code verified against commit{" "} + Code verified against{" "} + {crateName} v{VERSIONS[crateName]}. If the API has changed + since then, check the{" "} - {COMMIT_SHORT} - - . If the API has changed since then, check the{" "} - examples in the repo {" "} for the latest usage. diff --git a/documentation/docs/components/crates-paused.tsx b/documentation/docs/components/crates-paused.tsx deleted file mode 100644 index 1194a5222e..0000000000 --- a/documentation/docs/components/crates-paused.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { Callout } from "nextra/components"; - -const CRATES_VERSION = "1.20.4"; -const INSTALL_PATH = "/developers/rust/importing"; - -export const CratesPaused = () => ( - - Crate publication is paused. The crates.io release (v - {CRATES_VERSION}) doesn't include the Stream module or other recent work. - Publication resumes with the Lewes Protocol. Import from Git for now — see{" "} - Installation. - -); diff --git a/documentation/docs/components/landing-page.tsx b/documentation/docs/components/landing-page.tsx index 9df5a95192..c1013c6618 100644 --- a/documentation/docs/components/landing-page.tsx +++ b/documentation/docs/components/landing-page.tsx @@ -11,8 +11,6 @@ const asciiStyle: React.CSSProperties = { margin: 0, }; -// ── Animation components ── - const randomRow = () => Math.floor(Math.random() * 3); const randomPath = () => [randomRow(), randomRow(), randomRow()]; @@ -243,8 +241,6 @@ const ApiAnimation = () => { ); }; -// ── Section data ── - const sections = [ { title: "Network", @@ -291,17 +287,29 @@ const AnimationBlock = ({ type }: { type: string }) => { const sdks = [ { - name: "Rust", + name: "Rust SDK", description: - "Native SDK with async Mixnet client, streams, and TcpProxy modules.", + "Async Mixnet client with AsyncRead/AsyncWrite streams over the Mixnet.", href: "/developers/rust", }, { - name: "TypeScript", + name: "smolmix", description: - "Browser-based SDK with fetch API replacement and message-based WebSocket transport.", + "TCP/UDP tunnel over the Mixnet. Userspace smoltcp stack exposing AsyncRead/AsyncWrite TcpStream and UdpSocket types.", + href: "/developers/smolmix", + }, + { + name: "TypeScript SDK", + description: + "Browser-side Mixnet Client for raw messaging via WebSocket, plus Nyx smart contract bindings.", href: "/developers/typescript", }, + { + name: "mix-fetch", + description: + "fetch()-compatible API that routes HTTP(S) requests through the Mixnet. Browsers and Node.js.", + href: "/developers/mix-fetch", + }, ]; export const LandingPage = () => { @@ -309,7 +317,6 @@ export const LandingPage = () => {
- {/* ── Section cards ── */}
{ ))}
- {/* ── SDKs ── */}
{ padding: 0, }} > - SDKs + Libraries

{ lineHeight: 1.6, }} > - Integrate Mixnet privacy into your application with our Rust and - TypeScript SDKs. + Rust and TypeScript libraries for Mixnet integration.

@@ -466,7 +471,6 @@ export const LandingPage = () => {
- {/* ── Links ── */}
+ * + * + */ + +type Variant = "latency" | "cryptography" | "acks"; + +interface LewesPendingProps { + variant: Variant; +} + +interface VariantEntry { + type: "info" | "warning"; + body: ReactNode; +} + +const VARIANTS: Record = { + latency: { + type: "info", + body: "Updated latency measurements will be published after the Lewes Protocol release.", + }, + cryptography: { + type: "info", + body: ( + <> + Cryptographic details on this page will be updated for the Lewes + Protocol release. For the current algorithm overview, see the{" "} + + Nym Trust Center: Cryptography + + . + + ), + }, + acks: { + type: "warning", + body: "The upcoming Lewes Protocol release will introduce changes to how acknowledgements are handled. The current hop-by-hop ACK mechanism described above may be revised as part of broader protocol improvements. Details will be documented here once the changes are finalised.", + }, +}; + +export const LewesPending = ({ variant }: LewesPendingProps) => { + const { type, body } = VARIANTS[variant]; + return {body}; +}; diff --git a/documentation/docs/components/mix-fetch.tsx b/documentation/docs/components/mix-fetch.tsx index a754ba7f62..0fb6298e37 100644 --- a/documentation/docs/components/mix-fetch.tsx +++ b/documentation/docs/components/mix-fetch.tsx @@ -153,115 +153,116 @@ export const MixFetch = () => { error: `Error: ${errorMsg}`, }; const statusColor: Record = { - idle: "gray", + idle: "#9e9e9e", starting: "orange", - ready: "green", - error: "red", + ready: "#85E89D", + error: "#ff6b6b", }; return ( -
- {/* --- Start MixFetch Section --- */} - - - + {status === "starting" && } + + {statusText[status]} + + + + {/* --- Fetch Controls (disabled until ready) --- */} + - Start MixFetch - - {status === "starting" && } - - {statusText[status]} - + {/* Single fetch */} + + setUrl(e.target.value)} + size="small" + /> + + + {busy && ( + + + + )} + {html && ( + <> + + Response + + + + {html} + + + + )} + + {/* Concurrent fetch demo */} + + Concurrent Requests + + + + + {concurrentBusy && ( + + + + )} + {concurrentResults.length > 0 && ( + + {concurrentResults.map((result, i) => ( + + {result} + + ))} + + )} + - {/* --- Fetch Controls (disabled until ready) --- */} - - {/* Single fetch */} - - setUrl(e.target.value)} - /> - - - {busy && ( - - - - )} - {html && ( - <> - - Response - - - - {html} - - - - )} - - {/* Concurrent fetch demo */} - - Concurrent Requests - - - - - {concurrentBusy && ( - - - - )} - {concurrentResults.length > 0 && ( - - {concurrentResults.map((result, i) => ( - - {result} - - ))} - - )} - - {/* --- Log Panel --- */} {logs.length > 0 && ( Log {logs.map((entry, i) => ( @@ -276,6 +277,6 @@ export const MixFetch = () => { ))} )} -
+ ); }; diff --git a/documentation/docs/components/outputs/api-scraping-outputs/nodes-count.json b/documentation/docs/components/outputs/api-scraping-outputs/nodes-count.json index a1c7683698..cc5266cf38 100644 --- a/documentation/docs/components/outputs/api-scraping-outputs/nodes-count.json +++ b/documentation/docs/components/outputs/api-scraping-outputs/nodes-count.json @@ -1,6 +1,6 @@ { - "nodes": 745, + "nodes": 743, "locations": 77, - "mixnodes": 264, - "exit_gateways": 474 + "mixnodes": 258, + "exit_gateways": 477 } diff --git a/documentation/docs/components/outputs/api-scraping-outputs/time-now.md b/documentation/docs/components/outputs/api-scraping-outputs/time-now.md index 47c9becae5..dd38066ac5 100644 --- a/documentation/docs/components/outputs/api-scraping-outputs/time-now.md +++ b/documentation/docs/components/outputs/api-scraping-outputs/time-now.md @@ -1 +1 @@ -Wednesday, May 6th 2026, 11:11:33 UTC +Wednesday, May 13th 2026, 10:37:10 UTC diff --git a/documentation/docs/components/outputs/command-outputs/node-api-check-query-help.md b/documentation/docs/components/outputs/command-outputs/node-api-check-query-help.md index bfa068ddb0..999f3d4a6f 100644 --- a/documentation/docs/components/outputs/command-outputs/node-api-check-query-help.md +++ b/documentation/docs/components/outputs/command-outputs/node-api-check-query-help.md @@ -11,7 +11,7 @@ options: --no_routing_history Display node stats without routing history --no_verloc_metrics Display node stats without verloc metrics -m, --markdown Display results in markdown format - -o [OUTPUT], --output [OUTPUT] + -o, --output [OUTPUT] Save results to file (in current dir or supply with path without filename) ``` diff --git a/documentation/docs/components/outputs/command-outputs/nym-api-help.md b/documentation/docs/components/outputs/command-outputs/nym-api-help.md index da2cedcfe5..a00d8c810a 100644 --- a/documentation/docs/components/outputs/command-outputs/nym-api-help.md +++ b/documentation/docs/components/outputs/command-outputs/nym-api-help.md @@ -8,8 +8,10 @@ Commands: help Print this message or the help of the given subcommand(s) Options: - -c, --config-env-file Path pointing to an env file that configures the Nym API [env: NYMAPI_CONFIG_ENV_FILE_ARG=] - --no-banner A no-op flag included for consistency with other binaries (and compatibility with nymvisor, oops) [env: NYMAPI_NO_BANNER_ARG=] + -c, --config-env-file Path pointing to an env file that configures the Nym API [env: + NYMAPI_CONFIG_ENV_FILE_ARG=] + --no-banner A no-op flag included for consistency with other binaries (and compatibility with + nymvisor, oops) [env: NYMAPI_NO_BANNER_ARG=] -h, --help Print help -V, --version Print version ``` diff --git a/documentation/docs/components/outputs/command-outputs/nym-node-cli-install-help.md b/documentation/docs/components/outputs/command-outputs/nym-node-cli-install-help.md index 3451f94b20..d6fb736bed 100644 --- a/documentation/docs/components/outputs/command-outputs/nym-node-cli-install-help.md +++ b/documentation/docs/components/outputs/command-outputs/nym-node-cli-install-help.md @@ -13,8 +13,7 @@ usage: nym-node-cli install [-h] [-V] [-d BRANCH] [-v] options: -h, --help show this help message and exit -V, --version show program's version number and exit - -d BRANCH, --dev BRANCH - Define github branch (default: develop) + -d, --dev BRANCH Define github branch (default: develop) -v, --verbose Show full error tracebacks --mode {mixnode,entry-gateway,exit-gateway} Node mode: 'mixnode', 'entry-gateway', or 'exit- diff --git a/documentation/docs/components/outputs/command-outputs/nym-node-help.md b/documentation/docs/components/outputs/command-outputs/nym-node-help.md index 7eaa0fdaa3..6f47c76202 100644 --- a/documentation/docs/components/outputs/command-outputs/nym-node-help.md +++ b/documentation/docs/components/outputs/command-outputs/nym-node-help.md @@ -8,11 +8,13 @@ Commands: migrate Attempt to migrate an existing mixnode or gateway into a nym-node run Start this nym-node sign Use identity key of this node to sign provided message - unsafe-reset-sphinx-keys UNSAFE: reset existing sphinx keys and attempt to generate fresh one for the current network state + unsafe-reset-sphinx-keys UNSAFE: reset existing sphinx keys and attempt to generate fresh one for the current network + state help Print this message or the help of the given subcommand(s) Options: - -c, --config-env-file Path pointing to an env file that configures the nym-node and overrides any preconfigured values [env: NYMNODE_CONFIG_ENV_FILE_ARG=] + -c, --config-env-file Path pointing to an env file that configures the nym-node and overrides any + preconfigured values [env: NYMNODE_CONFIG_ENV_FILE_ARG=] --no-banner Flag used for disabling the printed banner in tty [env: NYMNODE_NO_BANNER=] -h, --help Print help -V, --version Print version diff --git a/documentation/docs/components/outputs/command-outputs/nym-node-run-help.md b/documentation/docs/components/outputs/command-outputs/nym-node-run-help.md index 6774992a11..298c919400 100644 --- a/documentation/docs/components/outputs/command-outputs/nym-node-run-help.md +++ b/documentation/docs/components/outputs/command-outputs/nym-node-run-help.md @@ -4,81 +4,138 @@ Start this nym-node Usage: nym-node run [OPTIONS] Options: - --id Id of the nym-node to use [env: NYMNODE_ID=] [default: default-nym-node] - --config-file Path to a configuration file of this node [env: NYMNODE_CONFIG=] - --accept-operator-terms-and-conditions Explicitly specify whether you agree with the terms and conditions of a nym node operator as defined at - [env: NYMNODE_ACCEPT_OPERATOR_TERMS=] - --deny-init Forbid a new node from being initialised if configuration file for the provided specification doesn't already exist [env: - NYMNODE_DENY_INIT=] - --init-only If this is a brand new nym-node, specify whether it should only be initialised without actually running the subprocesses [env: - NYMNODE_INIT_ONLY=] - --local Flag specifying this node will be running in a local setting [env: NYMNODE_LOCAL=] - --mode [...] Specifies the current mode(s) of this nym-node [env: NYMNODE_MODE=] [possible values: mixnode, entry-gateway, exit-gateway, - exit-providers-only] - --modes Specifies the current mode(s) of this nym-node as a single flag [env: NYMNODE_MODES=] [possible values: mixnode, entry-gateway, - exit-gateway, exit-providers-only] - -w, --write-changes If this node has been initialised before, specify whether to write any new changes to the config file [env: - NYMNODE_WRITE_CONFIG_CHANGES=] - --bonding-information-output Specify output file for bonding information of this nym-node, i.e. its encoded keys. NOTE: the required bonding information is still a - subject to change and this argument should be treated only as a preview of future features [env: NYMNODE_BONDING_INFORMATION_OUTPUT=] - -o, --output Specify the output format of the bonding information (`text` or `json`) [env: NYMNODE_OUTPUT=] [default: text] [possible values: text, - json] - --public-ips Comma separated list of public ip addresses that will be announced to the nym-api and subsequently to the clients. In nearly all - circumstances, it's going to be identical to the address you're going to use for bonding [env: NYMNODE_PUBLIC_IPS=] - --hostname Optional hostname associated with this gateway that will be announced to the nym-api and subsequently to the clients [env: - NYMNODE_HOSTNAME=] - --location Optional **physical** location of this node's server. Either full country name (e.g. 'Poland'), two-letter alpha2 (e.g. 'PL'), - three-letter alpha3 (e.g. 'POL') or three-digit numeric-3 (e.g. '616') can be provided [env: NYMNODE_LOCATION=] - --http-bind-address Socket address this node will use for binding its http API. default: `[::]:8080` [env: NYMNODE_HTTP_BIND_ADDRESS=] - --landing-page-assets-path Path to assets directory of custom landing page of this node [env: NYMNODE_HTTP_LANDING_ASSETS=] - --http-access-token An optional bearer token for accessing certain http endpoints. Currently only used for prometheus metrics [env: - NYMNODE_HTTP_ACCESS_TOKEN=] - --expose-system-info Specify whether basic system information should be exposed. default: true [env: NYMNODE_HTTP_EXPOSE_SYSTEM_INFO=] [possible values: - true, false] - --expose-system-hardware Specify whether basic system hardware information should be exposed. default: true [env: NYMNODE_HTTP_EXPOSE_SYSTEM_HARDWARE=] - [possible values: true, false] - --expose-crypto-hardware Specify whether detailed system crypto hardware information should be exposed. default: true [env: - NYMNODE_HTTP_EXPOSE_CRYPTO_HARDWARE=] [possible values: true, false] - --mixnet-bind-address Address this node will bind to for listening for mixnet packets default: `[::]:1789` [env: NYMNODE_MIXNET_BIND_ADDRESS=] - --mixnet-announce-port If applicable, custom port announced in the self-described API that other clients and nodes will use. Useful when the node is behind a - proxy [env: NYMNODE_MIXNET_ANNOUNCE_PORT=] - --nym-api-urls Addresses to nym APIs from which the node gets the view of the network [env: NYMNODE_NYM_APIS=] - --nyxd-urls Addresses to nyxd chain endpoint which the node will use for chain interactions [env: NYMNODE_NYXD=] - --enable-console-logging Specify whether running statistics of this node should be logged to the console [env: NYMNODE_ENABLE_CONSOLE_LOGGING=] [possible - values: true, false] - --wireguard-enabled Specifies whether the wireguard service is enabled on this node [env: NYMNODE_WG_ENABLED=] [possible values: true, false] - --wireguard-bind-address Socket address this node will use for binding its wireguard interface. default: `[::]:51822` [env: NYMNODE_WG_BIND_ADDRESS=] - --wireguard-tunnel-announced-port Tunnel port announced to external clients wishing to connect to the wireguard interface. Useful in the instances where the node is - behind a proxy [env: NYMNODE_WG_ANNOUNCED_PORT=] - --wireguard-private-network-prefix The prefix denoting the maximum number of the clients that can be connected via Wireguard. The maximum value for IPv4 is 32 and for - IPv6 is 128 [env: NYMNODE_WG_PRIVATE_NETWORK_PREFIX=] - --wireguard-userspace Use userspace implementation of WireGuard (wireguard-go) instead of kernel module. Useful in containerized environments without kernel - WireGuard support [env: NYMNODE_WG_USERSPACE=] [possible values: true, false] - --verloc-bind-address Socket address this node will use for binding its verloc API. default: `[::]:1790` [env: NYMNODE_VERLOC_BIND_ADDRESS=] - --verloc-announce-port If applicable, custom port announced in the self-described API that other clients and nodes will use. Useful when the node is behind a - proxy [env: NYMNODE_VERLOC_ANNOUNCE_PORT=] - --entry-bind-address Socket address this node will use for binding its client websocket API. default: `[::]:9000` [env: NYMNODE_ENTRY_BIND_ADDRESS=] - --announce-ws-port Custom announced port for listening for websocket client traffic. If unspecified, the value from the `bind_address` will be used - instead [env: NYMNODE_ENTRY_ANNOUNCE_WS_PORT=] - --announce-wss-port If applicable, announced port for listening for secure websocket client traffic [env: NYMNODE_ENTRY_ANNOUNCE_WSS_PORT=] - --enforce-zk-nyms Indicates whether this gateway is accepting only coconut credentials for accessing the mixnet or if it also accepts non-paying clients - [env: NYMNODE_ENFORCE_ZK_NYMS=] [possible values: true, false] - --mnemonic Custom cosmos wallet mnemonic used for zk-nym redemption. If no value is provided, a fresh mnemonic is going to be generated [env: - NYMNODE_MNEMONIC=] - --upgrade-mode-attestation-url Endpoint to query to retrieve current upgrade mode attestation. This argument should never be set outside testnets and local networks - [env: NYMNODE_UPGRADE_MODE_ATTESTATION_URL=] - --upgrade-mode-attester-public-key Expected public key of the entity signing the published attestation. This argument should never be set outside testnets and local - networks [env: NYMNODE_UPGRADE_MODE_ATTESTER_PUBKEY=] - --upstream-exit-policy-url Specifies the url for an upstream source of the exit policy used by this node [env: NYMNODE_UPSTREAM_EXIT_POLICY=] - --open-proxy Specifies whether this exit node should run in 'open-proxy' mode and thus would attempt to resolve **ANY** request it receives [env: - NYMNODE_OPEN_PROXY=] [possible values: true, false] - --lp-control-bind-address Bind address for the TCP LP control traffic. default: `[::]:41264` [env: NYMNODE_LP_CONTROL_BIND_ADDRESS=] - --lp-control-announce-port Custom announced port for listening for the TCP LP control traffic. If unspecified, the value from the `lp_control_bind_address` will - be used instead [env: NYMNODE_LP_CONTROL_ANNOUNCE_PORT=] - --lp-data-bind-address Bind address for the UDP LP data traffic. default: `[::]:51264` [env: NYMNODE_LP_DATA_BIND_ADDRESS=] - --lp-data-announce-port Custom announced port for listening for the UDP LP data traffic. If unspecified, the value from the `lp_data_bind_address` will be - used instead [env: NYMNODE_LP_DATA_ANNOUNCE_PORT=] - --lp-use-mock-ecash Use mock ecash manager for LP testing. WARNING: Only use this for local testing! Never enable in production. When enabled, the LP - listener will accept any credential without blockchain verification [env: NYMNODE_LP_USE_MOCK_ECASH=] [possible values: true, false] - -h, --help Print help + --id + Id of the nym-node to use [env: NYMNODE_ID=] [default: default-nym-node] + --config-file + Path to a configuration file of this node [env: NYMNODE_CONFIG=] + --accept-operator-terms-and-conditions + Explicitly specify whether you agree with the terms and conditions of a nym node operator as defined at + [env: NYMNODE_ACCEPT_OPERATOR_TERMS=] + --deny-init + Forbid a new node from being initialised if configuration file for the provided specification doesn't already + exist [env: NYMNODE_DENY_INIT=] + --init-only + If this is a brand new nym-node, specify whether it should only be initialised without actually running the + subprocesses [env: NYMNODE_INIT_ONLY=] + --local + Flag specifying this node will be running in a local setting [env: NYMNODE_LOCAL=] + --mode [...] + Specifies the current mode(s) of this nym-node [env: NYMNODE_MODE=] [possible values: mixnode, entry-gateway, + exit-gateway, exit-providers-only] + --modes + Specifies the current mode(s) of this nym-node as a single flag [env: NYMNODE_MODES=] [possible values: mixnode, + entry-gateway, exit-gateway, exit-providers-only] + -w, --write-changes + If this node has been initialised before, specify whether to write any new changes to the config file [env: + NYMNODE_WRITE_CONFIG_CHANGES=] + --bonding-information-output + Specify output file for bonding information of this nym-node, i.e. its encoded keys. NOTE: the required bonding + information is still a subject to change and this argument should be treated only as a preview of future features + [env: NYMNODE_BONDING_INFORMATION_OUTPUT=] + -o, --output + Specify the output format of the bonding information (`text` or `json`) [env: NYMNODE_OUTPUT=] [default: text] + [possible values: text, json] + --public-ips + Comma separated list of public ip addresses that will be announced to the nym-api and subsequently to the clients. + In nearly all circumstances, it's going to be identical to the address you're going to use for bonding [env: + NYMNODE_PUBLIC_IPS=] + --hostname + Optional hostname associated with this gateway that will be announced to the nym-api and subsequently to the + clients [env: NYMNODE_HOSTNAME=] + --location + Optional **physical** location of this node's server. Either full country name (e.g. 'Poland'), two-letter alpha2 + (e.g. 'PL'), three-letter alpha3 (e.g. 'POL') or three-digit numeric-3 (e.g. '616') can be provided [env: + NYMNODE_LOCATION=] + --http-bind-address + Socket address this node will use for binding its http API. default: `[::]:8080` [env: NYMNODE_HTTP_BIND_ADDRESS=] + --landing-page-assets-path + Path to assets directory of custom landing page of this node [env: NYMNODE_HTTP_LANDING_ASSETS=] + --http-access-token + An optional bearer token for accessing certain http endpoints. Currently only used for prometheus metrics [env: + NYMNODE_HTTP_ACCESS_TOKEN=] + --expose-system-info + Specify whether basic system information should be exposed. default: true [env: NYMNODE_HTTP_EXPOSE_SYSTEM_INFO=] + [possible values: true, false] + --expose-system-hardware + Specify whether basic system hardware information should be exposed. default: true [env: + NYMNODE_HTTP_EXPOSE_SYSTEM_HARDWARE=] [possible values: true, false] + --expose-crypto-hardware + Specify whether detailed system crypto hardware information should be exposed. default: true [env: + NYMNODE_HTTP_EXPOSE_CRYPTO_HARDWARE=] [possible values: true, false] + --mixnet-bind-address + Address this node will bind to for listening for mixnet packets default: `[::]:1789` [env: + NYMNODE_MIXNET_BIND_ADDRESS=] + --mixnet-announce-port + If applicable, custom port announced in the self-described API that other clients and nodes will use. Useful when + the node is behind a proxy [env: NYMNODE_MIXNET_ANNOUNCE_PORT=] + --nym-api-urls + Addresses to nym APIs from which the node gets the view of the network [env: NYMNODE_NYM_APIS=] + --nyxd-urls + Addresses to nyxd chain endpoint which the node will use for chain interactions [env: NYMNODE_NYXD=] + --enable-console-logging + Specify whether running statistics of this node should be logged to the console [env: + NYMNODE_ENABLE_CONSOLE_LOGGING=] [possible values: true, false] + --wireguard-enabled + Specifies whether the wireguard service is enabled on this node [env: NYMNODE_WG_ENABLED=] [possible values: true, + false] + --wireguard-bind-address + Socket address this node will use for binding its wireguard interface. default: `[::]:51822` [env: + NYMNODE_WG_BIND_ADDRESS=] + --wireguard-tunnel-announced-port + Tunnel port announced to external clients wishing to connect to the wireguard interface. Useful in the instances + where the node is behind a proxy [env: NYMNODE_WG_ANNOUNCED_PORT=] + --wireguard-private-network-prefix + The prefix denoting the maximum number of the clients that can be connected via Wireguard. The maximum value for + IPv4 is 32 and for IPv6 is 128 [env: NYMNODE_WG_PRIVATE_NETWORK_PREFIX=] + --wireguard-userspace + Use userspace implementation of WireGuard (wireguard-go) instead of kernel module. Useful in containerized + environments without kernel WireGuard support [env: NYMNODE_WG_USERSPACE=] [possible values: true, false] + --verloc-bind-address + Socket address this node will use for binding its verloc API. default: `[::]:1790` [env: + NYMNODE_VERLOC_BIND_ADDRESS=] + --verloc-announce-port + If applicable, custom port announced in the self-described API that other clients and nodes will use. Useful when + the node is behind a proxy [env: NYMNODE_VERLOC_ANNOUNCE_PORT=] + --entry-bind-address + Socket address this node will use for binding its client websocket API. default: `[::]:9000` [env: + NYMNODE_ENTRY_BIND_ADDRESS=] + --announce-ws-port + Custom announced port for listening for websocket client traffic. If unspecified, the value from the + `bind_address` will be used instead [env: NYMNODE_ENTRY_ANNOUNCE_WS_PORT=] + --announce-wss-port + If applicable, announced port for listening for secure websocket client traffic [env: + NYMNODE_ENTRY_ANNOUNCE_WSS_PORT=] + --enforce-zk-nyms + Indicates whether this gateway is accepting only coconut credentials for accessing the mixnet or if it also + accepts non-paying clients [env: NYMNODE_ENFORCE_ZK_NYMS=] [possible values: true, false] + --mnemonic + Custom cosmos wallet mnemonic used for zk-nym redemption. If no value is provided, a fresh mnemonic is going to be + generated [env: NYMNODE_MNEMONIC=] + --upgrade-mode-attestation-url + Endpoint to query to retrieve current upgrade mode attestation. This argument should never be set outside testnets + and local networks [env: NYMNODE_UPGRADE_MODE_ATTESTATION_URL=] + --upgrade-mode-attester-public-key + Expected public key of the entity signing the published attestation. This argument should never be set outside + testnets and local networks [env: NYMNODE_UPGRADE_MODE_ATTESTER_PUBKEY=] + --upstream-exit-policy-url + Specifies the url for an upstream source of the exit policy used by this node [env: NYMNODE_UPSTREAM_EXIT_POLICY=] + --open-proxy + Specifies whether this exit node should run in 'open-proxy' mode and thus would attempt to resolve **ANY** request + it receives [env: NYMNODE_OPEN_PROXY=] [possible values: true, false] + --lp-control-bind-address + Bind address for the TCP LP control traffic. default: `[::]:41264` [env: NYMNODE_LP_CONTROL_BIND_ADDRESS=] + --lp-control-announce-port + Custom announced port for listening for the TCP LP control traffic. If unspecified, the value from the + `lp_control_bind_address` will be used instead [env: NYMNODE_LP_CONTROL_ANNOUNCE_PORT=] + --lp-data-bind-address + Bind address for the UDP LP data traffic. default: `[::]:51264` [env: NYMNODE_LP_DATA_BIND_ADDRESS=] + --lp-data-announce-port + Custom announced port for listening for the UDP LP data traffic. If unspecified, the value from the + `lp_data_bind_address` will be used instead [env: NYMNODE_LP_DATA_ANNOUNCE_PORT=] + --lp-use-mock-ecash + Use mock ecash manager for LP testing. WARNING: Only use this for local testing! Never enable in production. When + enabled, the LP listener will accept any credential without blockchain verification [env: + NYMNODE_LP_USE_MOCK_ECASH=] [possible values: true, false] + -h, --help + Print help ``` diff --git a/documentation/docs/components/outputs/command-outputs/nymvisor-help.md b/documentation/docs/components/outputs/command-outputs/nymvisor-help.md index e3c23ff77d..ce0399ac28 100644 --- a/documentation/docs/components/outputs/command-outputs/nymvisor-help.md +++ b/documentation/docs/components/outputs/command-outputs/nymvisor-help.md @@ -11,7 +11,8 @@ Commands: help Print this message or the help of the given subcommand(s) Options: - -c, --config-env-file Path pointing to an env file that configures the nymvisor and overrides any preconfigured values + -c, --config-env-file Path pointing to an env file that configures the nymvisor and overrides any + preconfigured values -h, --help Print help -V, --version Print version ``` diff --git a/documentation/docs/components/version-banner.tsx b/documentation/docs/components/version-banner.tsx new file mode 100644 index 0000000000..26b14d6e5b --- /dev/null +++ b/documentation/docs/components/version-banner.tsx @@ -0,0 +1,15 @@ +import { Callout } from "nextra/components"; +import { NYM_SDK_VERSION } from "./versions"; + +const INSTALL_PATH = "/developers/rust/importing"; + +export const VersionBanner = () => ( + + Code examples target v{NYM_SDK_VERSION} of the Nym crates + on{" "} + + crates.io + + . See Installation for setup instructions. + +); diff --git a/documentation/docs/components/versions.ts b/documentation/docs/components/versions.ts new file mode 100644 index 0000000000..67646aacce --- /dev/null +++ b/documentation/docs/components/versions.ts @@ -0,0 +1,31 @@ +/** + * Centralised version constants for documentation. + * + * Components (VersionBanner, CodeVerified, etc.) import from here so there is a + * single place to update on each release. + * + * Fenced code blocks still hardcode versions for copy-paste friendliness. + * When bumping versions here, also update the Cargo.toml snippets in: + * + * pages/developers/rust/importing.mdx + * pages/developers/rust/tcpproxy.mdx + * pages/developers/rust/mixnet/tutorial.mdx + * pages/developers/rust/stream/tutorial.mdx + * pages/developers/rust/client-pool/tutorial.mdx + * public/llms.txt + * + * RUST_MSRV is imported directly by all pages that display the Rust version, + * so no manual file edits are needed for MSRV bumps: + * + * pages/developers/smolmix.mdx + * pages/developers/rust/importing.mdx + */ + +// nym-sdk / nym-bin-common / nym-network-defaults (Rust SDK crates) +export const NYM_SDK_VERSION = "1.21.0"; + +// smolmix standalone crate +export const SMOLMIX_VERSION = "1.21.0"; + +// Minimum supported Rust version (matches workspace rust-version in root Cargo.toml) +export const RUST_MSRV = "1.87"; diff --git a/documentation/docs/next.config.js b/documentation/docs/next.config.js index 45e93e4089..1b1ace3068 100644 --- a/documentation/docs/next.config.js +++ b/documentation/docs/next.config.js @@ -1178,13 +1178,46 @@ const config = { basePath: false, }, + // Docs reorg: language-based sidebar + // Deleted routing pages → merged into integrations + { + source: "/docs/developers/native", + destination: "/docs/developers/integrations", + permanent: true, + basePath: false, + }, + { + source: "/docs/developers/browsers", + destination: "/docs/developers/integrations", + permanent: true, + basePath: false, + }, + // --- Directory index redirects (directories without index pages) --- + { + source: "/docs/developers/concepts", + destination: "/docs/developers/concepts/message-queue", + permanent: false, + basePath: false, + }, { source: "/docs/developers/typescript/bundling", destination: "/docs/developers/typescript/bundling/bundling", permanent: false, basePath: false, }, + { + source: "/docs/developers/typescript/api/mix-fetch", + destination: "/docs/developers/typescript/api/mix-fetch/globals", + permanent: false, + basePath: false, + }, + { + source: "/docs/developers/typescript/api/sdk", + destination: "/docs/developers/typescript/api/sdk/globals", + permanent: false, + basePath: false, + }, { source: "/docs/developers/typescript/examples", destination: "/docs/developers/typescript/examples/mix-fetch", @@ -1231,7 +1264,8 @@ const config = { }, { source: "/docs/apis/ns-api/ns-api-run-deploy", - destination: "/docs/operators/performance-and-testing/ns-api-deployment", + destination: + "/docs/operators/performance-and-testing/ns-api-deployment", permanent: true, basePath: false, }, diff --git a/documentation/docs/pages/_meta.json b/documentation/docs/pages/_meta.json index b3b6f66d1d..fc9040cc2c 100644 --- a/documentation/docs/pages/_meta.json +++ b/documentation/docs/pages/_meta.json @@ -8,21 +8,7 @@ }, "developers": { "title": "Developers", - "type": "menu", - "items": { - "developers": { - "title": "Integrations", - "href": "/developers/integrations" - }, - "rust": { - "title": "Rust SDK", - "href": "/developers/rust" - }, - "typescript": { - "title": "Typescript SDK", - "href": "/developers/typescript" - } - } + "type": "page" }, "operators": { "title": "Operators", diff --git a/documentation/docs/pages/apis/explorer-api.mdx b/documentation/docs/pages/apis/explorer-api.mdx index 77237bbd97..59b99d2ea1 100644 --- a/documentation/docs/pages/apis/explorer-api.mdx +++ b/documentation/docs/pages/apis/explorer-api.mdx @@ -1,6 +1,6 @@ --- title: "Explorer API (Deprecated)" -description: "Legacy Explorer API reference for the Nym Mixnet Explorer. Deprecated in favor of the Node Status API." +description: "Legacy Explorer API reference for the Nym Mixnet Explorer. Deprecated in favour of the Node Status API." schemaType: "TechArticle" section: "APIs" lastUpdated: "2026-03-15" diff --git a/documentation/docs/pages/apis/ns-api.mdx b/documentation/docs/pages/apis/ns-api.mdx index 619f640b3e..f97878c64c 100644 --- a/documentation/docs/pages/apis/ns-api.mdx +++ b/documentation/docs/pages/apis/ns-api.mdx @@ -19,7 +19,7 @@ The Node Status API serves information about individual `nym-node` instances in - **Mixnet summaries**: active set composition, role distribution, network health -If you're building a service that makes heavy use of this API, consider [running your own instance](/operators/performance-and-testing/ns-api-deployment) to distribute load and promote a robust network of downstream services. +If you're building a service that makes heavy use of this API, consider [running your own instance](/operators/performance-and-testing/ns-api-deployment) to distribute load across the network. ## Quick examples diff --git a/documentation/docs/pages/developers/_meta.json b/documentation/docs/pages/developers/_meta.json index 63a7809c49..f49822eeb5 100644 --- a/documentation/docs/pages/developers/_meta.json +++ b/documentation/docs/pages/developers/_meta.json @@ -1,19 +1,22 @@ { - "index": "Introduction", + "index": "Overview", + "integrations": "Choosing an Approach", + "concepts": "Key Concepts", + "--": { "type": "separator", - "title": "Mixnet Integrations" + "title": "Rust" }, - "integrations": "Overview - Start Here!", - "native": "Native Apps", - "browsers": "Browser Apps", - "concepts": "Nym-Specific Concepts for Integrations", + "smolmix": "smolmix", + "rust": "nym-sdk", + "-": { "type": "separator", - "title": "SDKs" + "title": "TypeScript" }, - "rust": "Rust SDK", - "typescript": "Typescript SDK", + "mix-fetch": "mix-fetch", + "typescript": "TypeScript SDK", + "---": { "type": "separator", "title": "Extras" diff --git a/documentation/docs/pages/developers/archive/nym-connect.mdx b/documentation/docs/pages/developers/archive/nym-connect.mdx index 024eff6ab5..e4b2da93c2 100644 --- a/documentation/docs/pages/developers/archive/nym-connect.mdx +++ b/documentation/docs/pages/developers/archive/nym-connect.mdx @@ -4,7 +4,7 @@ import { Callout } from 'nextra/components' NymConnect is no longer maintained as of early 2024. Nym is developing a new client called [NymVPN](https://nymvpn.com), an application routing all user traffic through the Mixnet. -If you want to route traffic through SOCKS5, use the maintained [Nym Socks5 Client](../clients/socks5/setup). +If you want to route traffic through SOCKS5, use the maintained [Nym Socks5 Client](/developers/clients/socks5/setup). In case you want to run deprecated NymConnect, follow these steps: @@ -29,7 +29,7 @@ Here are some examples of applications which will work behind Socks5 proxy (`nym To download Electrum visit the [official webpage](https://electrum.org/#download). To connect to the Mixnet follow these steps: -1. Start and connect NymConnect (or [`nym-socks5-client`](../clients/socks5/setup)) +1. Start and connect NymConnect (or [`nym-socks5-client`](/developers/clients/socks5/setup)) 2. Start your Electrum Bitcoin wallet 3. Go to: *Tools* -> *Network* -> *Proxy* 4. Set *Use proxy* to ✅, choose `SOCKS5` from the drop-down and add (copy-paste) the values from your NymConnect application @@ -41,7 +41,7 @@ To download Electrum visit the [official webpage](https://electrum.org/#download To download Monero wallet visit [getmonero.org](https://www.getmonero.org/downloads/). To connect to the Mixnet follow these steps: -1. Start and connect NymConnect (or [`nym-socks5-client`](../clients/socks5/setup)) +1. Start and connect NymConnect (or [`nym-socks5-client`](/developers/clients/socks5/setup)) 2. Start your Monero wallet 3. Go to: *Settings* -> *Interface* -> *Socks5 proxy* -> Add values: IP address `127.0.0.1`, Port `1080` (the values copied from NymConnect) 5. Now your Monero wallet runs through the Mixnet and it will be connected only if your NymConnect or `nym-socks5-client` are connected. @@ -51,7 +51,7 @@ To download Monero wallet visit [getmonero.org](https://www.getmonero.org/downlo To download Element (chat client for Matrix) visit [element.io](https://element.io/download). To connect to the Mixnet follow these steps: -1. Start and connect NymConnect (or [`nym-socks5-client`](../clients/socks5/setup)) +1. Start and connect NymConnect (or [`nym-socks5-client`](/developers/clients/socks5/setup)) 2. Start `element-desktop` with `--proxy-server` argument: **Linux** @@ -70,7 +70,7 @@ To make the start of Element over NymConnect simplier, you can add this command ### Telegram via NymConnect -1. Start and connect NymConnect (or [`nym-socks5-client`](../clients/socks5/setup)) +1. Start and connect NymConnect (or [`nym-socks5-client`](/developers/clients/socks5/setup)) 2. Start your Telegram chat application 3. Open the Telegram proxy settings. - Linux: *Settings* -> *Advanced* -> *Connection type* -> *Use custom proxy* diff --git a/documentation/docs/pages/developers/browsers.mdx b/documentation/docs/pages/developers/browsers.mdx deleted file mode 100644 index e61cacd694..0000000000 --- a/documentation/docs/pages/developers/browsers.mdx +++ /dev/null @@ -1,43 +0,0 @@ ---- -title: "Browser-Based App Integration" -description: "Build privacy-preserving browser apps with mixFetch and the Nym WASM SDK. Route HTTP requests and messages through the mixnet from the browser." -schemaType: "TechArticle" -section: "Developers" -lastUpdated: "2026-04-07" ---- - -import { Callout } from 'nextra/components'; - -# Browser-Based Apps - -Browsers are a restricted environment: communication is limited to WebSockets, Web Transport, and WebRTC; mixed content policies enforce HTTPS-only; and there is no access to the filesystem or system calls. The main obstacle for routing traffic through the Mixnet is the lack of access to browser TLS negotiation or the CA certificate store from JavaScript. - -Two integration options are available, both delivered as packages bundled into your web application. - -![](/images/developers/nym-browser-arch.png) - -## mixFetch - -A drop-in replacement for the browser `fetch` API that makes HTTP(S) requests via Exit Gateways using the SOCKS Network Requester. It ships with an embedded CA certificate store to establish a TLS session between `mixFetch` and the remote host, creating a secure channel from the browser to the destination over the Mixnet. - -Internally, `mixFetch` uses the WASM client. - -- [Docs](./typescript#mixfetch) -- [Example](./typescript/playground/mixfetch) - - -`mixFetch` currently supports a maximum of 10 concurrent in-flight requests. `mixFetchv2`, which will function as a general-purpose userspace IP stack, is in development. - - -## WASM Client - -Constructs Sphinx packets and cover traffic in WASM, sent over a WebSocket to the Entry Gateway. Responses arrive the same way. - -This operates in messaging mode only (text or binary payloads) and does not currently support IP packet routing via the Exit Gateway IPR or any stream-like API. For HTTP(S) requests, use `mixFetch`. - -Standard browser CSP and mixed content restrictions (HTTPS only) apply to the WebSocket connection, including in embedded WebViews. - -The client runs in a web worker to keep the UI thread free. - -- [Docs](./typescript#mixnet-client) -- [Example](./typescript/playground/traffic) diff --git a/documentation/docs/pages/developers/chain.md b/documentation/docs/pages/developers/chain.md index e99085cbba..683438a5e6 100644 --- a/documentation/docs/pages/developers/chain.md +++ b/documentation/docs/pages/developers/chain.md @@ -13,11 +13,11 @@ There are two options for interacting with the blockchain to send tokens or inte * `nyxd` binary ## Nym-CLI tool (recommended in most cases) -The `nym-cli` tool is a binary offering a simple interface for interacting with deployed smart contract (for instance, bonding and unbonding a Mix Node from the CLI), as well as creating and managing accounts and keypairs, sending tokens, and querying the blockchain. +The `nym-cli` tool is a binary offering an interface for interacting with deployed smart contracts (e.g. bonding and unbonding a Mix Node from the CLI), creating and managing accounts and keypairs, sending tokens, and querying the blockchain. -Instructions on how to do so can be found on the [`nym-cli` docs page](./tools/nym-cli) +See the [`nym-cli` docs page](./tools/nym-cli) for instructions. ## Nyxd binary -The `nyxd` binary, although more complex to compile and use, offers the full range of commands availiable to users of CosmosSDK chains. Use this if you are (e.g.) wanting to perform more granular queries about transactions from the CLI. +The `nyxd` binary, although harder to compile and use, offers the full range of commands available to users of CosmosSDK chains. Use this when you need more granular queries about transactions from the CLI. -You can use the instructions on how to do this on from the [`gaiad` docs page](https://hub.cosmos.network/main/delegators/delegator-guide-cli.html#querying-the-state). +The [`gaiad` docs page](https://hub.cosmos.network/main/delegators/delegator-guide-cli.html#querying-the-state) covers how to do this. diff --git a/documentation/docs/pages/developers/chain/cli-wallet.md b/documentation/docs/pages/developers/chain/cli-wallet.md index 9b7d25eb91..9ce94d9478 100644 --- a/documentation/docs/pages/developers/chain/cli-wallet.md +++ b/documentation/docs/pages/developers/chain/cli-wallet.md @@ -1,6 +1,6 @@ # CLI Wallet -If you have already read our [validator setup and maintenance documentation](../../operators/nodes/validator-setup) you will have seen that we compile and use the `nyxd` binary primarily for our validators. This binary can however be used for many other tasks, such as creating and using keypairs for wallets, or automated setups that require the signing and broadcasting of transactions. +If you have read our [validator setup and maintenance documentation](../../operators/nodes/validator-setup), you will have seen that we compile and use the `nyxd` binary primarily for our validators. This binary can also be used for other tasks, such as creating and using keypairs for wallets, or automated setups that require the signing and broadcasting of transactions. ### Using `nyxd` binary as a CLI wallet -You can use the `nyxd` as a minimal CLI wallet if you want to set up an account (or multiple accounts). Just compile the binary as per the documentation, **stopping after** the [building your validator](../../operators/nodes/validator-setup#building-your-validator) step is complete. You can then run `nyxd keys --help` to see how you can set up and store different keypairs with which to interact with the Nyx blockchain. +You can use `nyxd` as a minimal CLI wallet if you want to set up one or more accounts. Compile the binary as per the documentation, stopping after the [building your validator](../../operators/nodes/validator-setup#building-your-validator) step is complete. Then run `nyxd keys --help` to see how to set up and store keypairs for interacting with the Nyx blockchain. diff --git a/documentation/docs/pages/developers/chain/ledger-live.md b/documentation/docs/pages/developers/chain/ledger-live.md index dd7f933c2a..a320dccd46 100644 --- a/documentation/docs/pages/developers/chain/ledger-live.md +++ b/documentation/docs/pages/developers/chain/ledger-live.md @@ -1,6 +1,6 @@ # Ledger Live Support -Use the following instructions to interact with the Nyx blockchain - either with deployed smart contracts, or just to send tokens - using your Ledger device to sign transactions. +Use the following instructions to interact with the Nyx blockchain (either with deployed smart contracts, or to send tokens) using your Ledger device to sign transactions. ## Prerequisites * Download and install [Ledger Live](https://www.ledger.com/ledger-live). @@ -8,7 +8,7 @@ Use the following instructions to interact with the Nyx blockchain - either with ## Prepare your Ledger App * Plug in your Ledger device -* Install the `Cosmos (ATOM)` app by following the instructions [here](https://hub.cosmos.network/main/resources/ledger.html). This app allows you to interact with **any** Cosmos SDK chain - you can manage your ATOM, OSMOSIS, NYM tokens, etc. +* Install the `Cosmos (ATOM)` app by following the instructions [here](https://hub.cosmos.network/main/resources/ledger.html). This app works with any Cosmos SDK chain, so you can manage your ATOM, OSMOSIS, NYM tokens, etc. * On the device, navigate to the Cosmos app and open it ## Create a keypair diff --git a/documentation/docs/pages/developers/chain/rpc-node.md b/documentation/docs/pages/developers/chain/rpc-node.md index 091edcd56e..67d915615c 100644 --- a/documentation/docs/pages/developers/chain/rpc-node.md +++ b/documentation/docs/pages/developers/chain/rpc-node.md @@ -8,10 +8,10 @@ lastUpdated: "2026-02-01" # RPC Nodes -RPC Nodes (which might otherwise be referred to as 'Lite Nodes' or just 'Full Nodes') differ from Validators in that they hold a copy of the Nyx blockchain, but do **not** participate in consensus / block-production. +RPC Nodes (sometimes called 'Lite Nodes' or 'Full Nodes') differ from Validators in that they hold a copy of the Nyx blockchain but do **not** participate in consensus / block-production. -You may want to set up an RPC Node for querying the blockchain, or in order to have an endpoint that your app can use to send transactions. +You may want to set up an RPC Node for querying the blockchain, or to provide an endpoint that your app can use to send transactions. -In order to set up an RPC Node, simply follow the instructions to set up a [Validator](../../operators/nodes/validator-setup), but **exclude the `nyxd tx staking create-validator` command**. +To set up an RPC Node, follow the instructions to set up a [Validator](../../operators/nodes/validator-setup), but **exclude the `nyxd tx staking create-validator` command**. If you want to fast-sync your node, check out the Polkachu snapshot and their other [resources](https://polkachu.com/seeds/nym). diff --git a/documentation/docs/pages/developers/clients/socks5.mdx b/documentation/docs/pages/developers/clients/socks5.mdx index bcc1a6458d..e0a70f0164 100644 --- a/documentation/docs/pages/developers/clients/socks5.mdx +++ b/documentation/docs/pages/developers/clients/socks5.mdx @@ -1,6 +1,6 @@ # Socks5 Client (Standalone) -> This client can also be utilised via the [Rust SDK](../rust). +> This client can also be utilised via the [Rust SDK](/developers/rust). Many existing applications are able to use either the SOCKS4, SOCKS4A, or SOCKS5 proxy protocols. If you want to send such an application's traffic through the mixnet, you can use the `nym-socks5-client` to bounce network traffic through the Nym network, like this: @@ -47,4 +47,4 @@ The `nym-socks5-client` allows you to do the following from your local machine: * Chop up the TCP stream into multiple Sphinx packets, assigning sequence numbers to them, while leaving the TCP connection open for more data * Send the Sphinx packets through the Nym Network. Packets are shuffled and mixed as they transit the mixnet. -The `nym-node` then reassembles the original TCP stream using the packets' sequence numbers, and make the intended request. It will then chop up the response into Sphinx packets and send them back through the mixnet to your `nym-socks5-client`. The application will then receive its data, without even noticing that it wasn't talking to a "normal" SOCKS5 proxy! +The `nym-node` then reassembles the original TCP stream using the packets' sequence numbers, and makes the intended request. It chops up the response into Sphinx packets and sends them back through the mixnet to your `nym-socks5-client`. The application receives its data without noticing that it wasn't talking to a "normal" SOCKS5 proxy. diff --git a/documentation/docs/pages/developers/clients/socks5/config.mdx b/documentation/docs/pages/developers/clients/socks5/config.mdx index a70404c9c6..0a9bffdd65 100644 --- a/documentation/docs/pages/developers/clients/socks5/config.mdx +++ b/documentation/docs/pages/developers/clients/socks5/config.mdx @@ -21,7 +21,7 @@ tree $HOME//.nym/socks5-clients/docs-example The `config.toml` file contains client configuration options, while the two `pem` files contain client key information. -The generated files contain the client name, public/private keypairs, and gateway address. The name `` in the example above is just a local identifier so that you can name your clients. +The generated files contain the client name, public/private keypairs, and gateway address. The name `` in the example above is a local identifier so that you can name your clients. ## Configuring your client for Docker By default, the native client listens to host `127.0.0.1`. However this can be an issue if you wish to run a client in a Dockerized environment, where it can be convenenient to listen on a different host such as `0.0.0.0`. diff --git a/documentation/docs/pages/developers/clients/socks5/setup.mdx b/documentation/docs/pages/developers/clients/socks5/setup.mdx index f62388eb06..355bbae215 100644 --- a/documentation/docs/pages/developers/clients/socks5/setup.mdx +++ b/documentation/docs/pages/developers/clients/socks5/setup.mdx @@ -4,7 +4,7 @@ If you are using OSX or a Debian-based operating system, you can download the `nym-socks5-client` binary from our [Github releases page](https://github.com/nymtech/nym/releases). -If you are using a different operating system, or want to build from source, simply use `cargo build --release` from the root of the Nym monorepo. +If you are using a different operating system, or want to build from source, run `cargo build --release` from the root of the Nym monorepo. ## Viewing command help You can check that your binaries are properly compiled with: diff --git a/documentation/docs/pages/developers/clients/socks5/usage.mdx b/documentation/docs/pages/developers/clients/socks5/usage.mdx index e4bc0442d9..22730d6c30 100644 --- a/documentation/docs/pages/developers/clients/socks5/usage.mdx +++ b/documentation/docs/pages/developers/clients/socks5/usage.mdx @@ -57,4 +57,4 @@ Adding `--no-banner` startup flag will prevent Nym banner being printed even if **build-info** -A `build-info` command prints the build information like commit hash, rust version, binary version just like what command `--version` does. However, you can also specify an `--output=json` flag that will format the whole output as a json, making it an order of magnitude easier to parse. +A `build-info` command prints the build information (commit hash, rust version, binary version), the same as `--version`. You can also specify an `--output=json` flag that formats the whole output as JSON, which is much easier to parse. diff --git a/documentation/docs/pages/developers/clients/webassembly-client.mdx b/documentation/docs/pages/developers/clients/webassembly-client.mdx index 9c7241c1ac..e84f0aa211 100644 --- a/documentation/docs/pages/developers/clients/webassembly-client.mdx +++ b/documentation/docs/pages/developers/clients/webassembly-client.mdx @@ -1,22 +1,23 @@ # Webassembly Client ## Overview -The Nym webassembly client allows any webassembly-capable runtime to build and send Sphinx packets to the Nym network, for uses in edge computing and browser-based applications. +The Nym webassembly client lets any webassembly-capable runtime build and send Sphinx packets to the Nym network, for use in edge computing and browser-based applications. -This is currently packaged and distributed for ease of use via the [Nym Typescript SDK library](../typescript). **We imagine most developers will use this client via the SDK for ease.** +It is packaged and distributed via the [Nym Typescript SDK library](/developers/typescript). Most developers consume it through the SDK. -The webassembly client allows for the easy creation of Sphinx packets from within mobile apps and browser-based client-side apps (including Electron or similar). +The client supports building Sphinx packets from mobile apps and browser-based client-side apps (including Electron or similar). ## Building apps with Webassembly Client -Check out the [Typescript SDK docs](../typescript) for examples of usage. +See the [Typescript SDK docs](/developers/typescript) for examples of usage. + +## Think about what you're sending -## Think about what you're sending! import { Callout } from 'nextra/components' - - Think about what information your app sends. That goes for whatever you put into your Sphinx packet messages as well as what your app's environment may leak. + + Think about what information your app sends. That covers whatever you put into your Sphinx packet messages as well as what your app's environment may leak. -Whenever you write client apps using HTML/JavaScript, we recommend that you **do not load external resources from CDNs**. Webapp developers do this all the time, to save load time for common resources, or just for convenience. But when you're writing privacy apps it's better not to make these kinds of requests. Pack everything locally. +When writing client apps in HTML/JavaScript, we recommend you **do not load external resources from CDNs**. Webapp developers often do this to save load time for common resources, or for convenience. For privacy apps it's better not to make these kinds of requests. Pack everything locally. -If you use only local resources within your Electron app or your browser extensions, explicitly encoding request data in a Sphinx packet does protect you from the normal leakage that gets sent in a browser HTTP request. [There's a lot of stuff that leaks when you make an HTTP request from a browser window](https://panopticlick.eff.org/). Luckily, all that metadata and request leakage doesn't happen in Nym, because you're choosing very explicitly what to encode into Sphinx packets, instead of sending a whole browser environment by default. +If you use only local resources within your Electron app or your browser extensions, encoding request data in a Sphinx packet protects you from the normal leakage that gets sent in a browser HTTP request. [A lot of metadata leaks when you make an HTTP request from a browser window](https://panopticlick.eff.org/). That leakage doesn't happen in Nym, because you control what gets encoded into Sphinx packets, rather than sending a whole browser environment by default. diff --git a/documentation/docs/pages/developers/clients/websocket.md b/documentation/docs/pages/developers/clients/websocket.md index 2214c1a108..e2c3e9a7c7 100644 --- a/documentation/docs/pages/developers/clients/websocket.md +++ b/documentation/docs/pages/developers/clients/websocket.md @@ -20,7 +20,7 @@ cargo build --release -p nym-client The binary will be at `target/release/nym-client`. -## Initialize and run +## Initialise and run ```bash # Create a new client identity diff --git a/documentation/docs/pages/developers/clients/websocket/config.md b/documentation/docs/pages/developers/clients/websocket/config.md index 8bd6d8bedb..883fe7275d 100644 --- a/documentation/docs/pages/developers/clients/websocket/config.md +++ b/documentation/docs/pages/developers/clients/websocket/config.md @@ -37,7 +37,7 @@ tree $HOME//.nym/clients/example-client The `config.toml` file contains client configuration options, while the two `pem` files contain client key information. -The generated files contain the client name, public/private keypairs, and gateway address. The name `` in the example above is just a local identifier so that you can name your clients. +The generated files contain the client name, public/private keypairs, and gateway address. The name `` in the example above is a local identifier so that you can name your clients. ### Configuring your client for Docker By default, the native client listens to host `127.0.0.1`. However this can be an issue if you wish to run a client in a Dockerized environment, where it can be convenenient to listen on a different host such as `0.0.0.0`. diff --git a/documentation/docs/pages/developers/clients/websocket/examples.md b/documentation/docs/pages/developers/clients/websocket/examples.md index c06b1707d6..2705ace056 100644 --- a/documentation/docs/pages/developers/clients/websocket/examples.md +++ b/documentation/docs/pages/developers/clients/websocket/examples.md @@ -10,8 +10,4 @@ All of these code examples will do the following: * wait for confirmation that the message hit the native client * wait to receive messages from other Nym apps -By varying the message content, you can easily build sophisticated service provider apps. For example, instead of printing the response received from the mixnet, your service provider might take some action on behalf of the user - perhaps initiating a network request, a blockchain transaction, or writing to a local data store. - - +By varying the message content, you can build service provider apps. For example, instead of printing the response received from the mixnet, your service provider might take some action on behalf of the user (initiating a network request, a blockchain transaction, or writing to a local data store). diff --git a/documentation/docs/pages/developers/clients/websocket/usage.md b/documentation/docs/pages/developers/clients/websocket/usage.md index 9f60a48e49..5219f9611a 100644 --- a/documentation/docs/pages/developers/clients/websocket/usage.md +++ b/documentation/docs/pages/developers/clients/websocket/usage.md @@ -4,7 +4,7 @@ The Nym native client exposes a websocket interface that your code connects to. Once you have a websocket connection, interacting with the client involves piping messages down the socket and listening for incoming messages. ## Message Requests -There are a number of message types that you can send up the websocket as defined [here](https://github.com/nymtech/nym/blob/master/clients/native/websocket-requests/src/responses.rs#L48). +The message types you can send up the websocket are defined [here](https://github.com/nymtech/nym/blob/master/clients/native/websocket-requests/src/responses.rs#L48). ### Getting your own address When you start your app, it is best practice to ask the native client to tell you what your own address is (from the generated configuration files . If you are running a service, you need to do this in order to know what address to give others. In a client-side piece of code you can also use this as a test to make sure your websocket connection is running smoothly. To do this, send: @@ -26,7 +26,7 @@ You'll receive a response of the format: See [here](https://github.com/nymtech/nym/blob/93cc281abc2cc951023b51746fa6f2ead1f56c46/clients/native/examples/python-examples/websocket/textsend.py#L16C9-L16C9) for an example of this being used. -> Note that all the pieces of native client example code begin with printing the selfAddress. Examples exist for Rust, Go, Javascript, and Python. +> All native client example code begins with printing the selfAddress. Examples exist for Rust, Go, Javascript, and Python. ### Sending text If you want to send text information through the mixnet, format a message like this one and poke it into the websocket: @@ -52,11 +52,11 @@ In some applications, e.g. where people are chatting with friends who they know, } ``` -**If that fits your security model, good. However, will probably be the case that you want to send anonymous replies using Single Use Reply Blocks (SURBs)**. +If that fits your security model, good. Otherwise, send anonymous replies using Single Use Reply Blocks (SURBs). -You can read more about SURBs [here](/network/mixnet-mode/anonymous-replies) but in short they are ways for the receiver of this message to anonymously reply to you - the sender - **without them having to know your client address**. +More on SURBs in the [anonymous replies docs](/network/mixnet-mode/anonymous-replies). In short, they let the receiver of a message reply to you without needing to know your client address. -Your client will send along a number of `replySurbs` to the recipient of the message. These are pre-addressed Sphinx packets that the recipient can write to the payload of (i.e. write response data to), but not view the final destination of. If the recipient is unable to fit the response data into the bucket of SURBs sent to it, it will use a SURB to request more SURBs be sent to it from your client. +Your client will send some `replySurbs` along to the recipient of the message. These are pre-addressed Sphinx packets that the recipient can write response data into, but cannot see the final destination of. If the recipient cannot fit its response into the bucket of SURBs sent to it, it will use a SURB to request more SURBs from your client. ```json { @@ -69,7 +69,7 @@ Your client will send along a number of `replySurbs` to the recipient of the mes See ['Replying to SURB Messages'](#replying-to-surb-messages) below for an example of how to deal with incoming messages that have SURBs attached. -Deciding on the amount of SURBs to generate and send along with outgoing messages depends on the expected size of the reply. You might want to send a lot of SURBs in order to make sure you get your response as quickly as possible (but accept the minor additional latency when sending, as your client has to generate and encrypt the packets), or you might just send a few (e.g. 20) and then if your response requires more SURBs, send them along, accepting the additional latency in getting your response. +How many SURBs to generate and send with outgoing messages depends on the expected size of the reply. Send many SURBs to get the response as quickly as possible (accepting minor additional latency at send time, since your client has to generate and encrypt the packets), or send a few (e.g. 20) and let the recipient request more if needed (accepting additional latency in receiving the response). ### Sending binary data You can also send bytes instead of JSON. For that you have to send a binary websocket frame containing a binary encoded diff --git a/documentation/docs/pages/developers/concepts/message-queue.mdx b/documentation/docs/pages/developers/concepts/message-queue.mdx index c93dc5bf3c..1e1d4c66c5 100644 --- a/documentation/docs/pages/developers/concepts/message-queue.mdx +++ b/documentation/docs/pages/developers/concepts/message-queue.mdx @@ -11,7 +11,7 @@ import { Callout } from 'nextra/components' # Message Queue - Although useful for understanding how the Nym Client works internally, this information is only of practical use if you are using the [`Mixnet`](../rust/mixnet) module of the Rust SDK and interacting with the client at a low level. Most of this is abstracted away by the [`Stream`](../rust/stream) module (`AsyncRead + AsyncWrite` channels) and the [`TcpProxy`](../rust/tcpproxy) module (TCP tunnelling with message ordering). + Useful for understanding how the Nym Client works internally, but only of practical interest if you are using the [`Mixnet`](/developers/rust/mixnet) module of the Rust SDK and interacting with the client at a low level. The [`Stream`](/developers/rust/stream) module (`AsyncRead + AsyncWrite` channels) abstracts most of this away. ## Sphinx Packet Streams @@ -85,6 +85,6 @@ sequenceDiagram When passing a message to a client (however you do it, either piping messages from an app to a standalone client or via one of the `send` functions exposed by the SDKs), you are **putting that message into the queue** to be source encrypted and sent in the future, in order to ensure that traffic leaving the client does so in a manner that to an external observer is uniform / does not create any 'burst' or change in traffic timings that could aid traffic analysis. ## Note on Client Shutdown -Accidentally dropping a client before your message has been sent is something that is possible and should be avoided (see the [troubleshooting guide](../rust/mixnet/troubleshooting) for more on this) but is easy to avoid simply by remembering to: +Accidentally dropping a client before your message has been sent is possible and should be avoided (see the [troubleshooting guide](/developers/rust/mixnet/troubleshooting) for more on this). To avoid it: - keep your client process alive, even if you are not expecting a reply to your message -- (in the case of the SDKs) properly disconnecting your client in order to make sure that the message queue is flushed of Sphinx packets with actual payloads. +- (with the SDKs) disconnect your client properly so that the message queue is flushed of Sphinx packets with real payloads. diff --git a/documentation/docs/pages/developers/index.mdx b/documentation/docs/pages/developers/index.mdx index 846afb616a..40bdcfa3a1 100644 --- a/documentation/docs/pages/developers/index.mdx +++ b/documentation/docs/pages/developers/index.mdx @@ -1,27 +1,30 @@ --- -title: "Nym Developer Portal: SDKs & Tools" -description: "Developer documentation for building privacy-enhanced applications on the Nym mixnet. Covers Rust SDK, TypeScript SDK, blockchain interaction & CLI tools." +title: "Overview" +description: "Developer documentation index for the Nym mixnet: Rust and TypeScript SDKs, smolmix, mix-fetch, chain interaction, and CLI tools." schemaType: "TechArticle" section: "Developers" -lastUpdated: "2026-02-01" +lastUpdated: "2026-05-12" --- -# Developer Documentation +# Overview -Build applications that protect user metadata using the Nym Mixnet. This section covers SDK integration, blockchain interaction, and developer tools. +This section covers the SDKs, standalone crates, blockchain interaction, and developer tools for building on the Nym mixnet. -## Where to start +## Start here -**Choosing an integration approach:** read [Integrations](/developers/integrations) to understand the architectural trade-offs (native SDK vs proxy vs mixFetch), then pick your path: +If you're new, read **[Choosing an Approach](/developers/integrations)** first. It maps your runtime (native vs browser vs mobile) and your architecture (end-to-end vs proxy) onto the right crate/library. -- **[Rust SDK](/developers/rust):** full-featured SDK with message passing, `AsyncRead`/`AsyncWrite` streams, and client pooling. Start with the [Tour](/developers/rust/tour). -- **[TypeScript SDK](/developers/typescript):** browser and Node.js SDK for mixFetch, Mixnet client, and smart contract interaction. -- **[Standalone Clients](/developers/clients):** language-agnostic SOCKS5 and WebSocket proxies for piping traffic through the Mixnet without an SDK. +## Crates/Libraries -## Blockchain interaction +| Crate/library | Language | Use it for | +|---|---|---| +| [`nym-sdk`](/developers/rust) | Rust | E2E messaging, `AsyncRead`/`AsyncWrite` streams, client pooling. Start with the [Tour](/developers/rust/tour). | +| [`smolmix`](/developers/smolmix) | Rust | `TcpStream` and `UdpSocket` over the Mixnet via a userspace IP stack. Compatible with `tokio-rustls`, `hyper`, `tungstenite`. | +| [`mix-fetch`](/developers/mix-fetch) | TypeScript | `fetch()`-compatible API for browser HTTP(S) requests over the Mixnet. | +| [TypeScript SDK](/developers/typescript) | TypeScript | Browser-side Mixnet Client (raw messaging) and Nyx Smart Contracts. | +| [Standalone Clients](/developers/clients) | Language-agnostic | SOCKS5 and WebSocket binaries for piping traffic through the Mixnet without an SDK. | -The Nym Network is coordinated by the [Nyx blockchain](/network/infrastructure/nyx). To query chain state, submit transactions, or interact with smart contracts, see [Chain Interaction](/developers/chain). +## Other sections -## API reference - -Auto-generated API specs for Nym infrastructure endpoints are in the [APIs section](/apis/introduction). +- **[Chain interaction](/developers/chain)**: query Nyx state, submit transactions, and call Nym smart contracts. +- **[APIs](/apis/introduction)**: auto-generated reference for Nym infrastructure HTTP endpoints. diff --git a/documentation/docs/pages/developers/integrations.mdx b/documentation/docs/pages/developers/integrations.mdx index 713d5cd2a1..64aea1ed93 100644 --- a/documentation/docs/pages/developers/integrations.mdx +++ b/documentation/docs/pages/developers/integrations.mdx @@ -1,23 +1,42 @@ --- -title: "Integrating With Nym" -description: "Choose an integration path for sending application traffic through the Nym mixnet, depending on your runtime environment and architecture." +title: "Choosing an Approach" +description: "Decide which Nym integration path fits your project. Compare nym-sdk, smolmix, mix-fetch, and the TypeScript SDK by runtime environment and architecture." schemaType: "TechArticle" section: "Developers" -lastUpdated: "2026-04-07" +lastUpdated: "2026-05-12" --- import { Callout } from 'nextra/components'; -# Integrating With Nym +# Choosing an Approach -Any application that integrates with Nym sends its traffic through the Mixnet via a Nym client. The right integration path depends on two factors: **environment** and **architecture**. +Any application that integrates with Nym sends its traffic through the Mixnet via a Nym client. The right product depends on two factors: your **environment** (where your code runs) and your **architecture** (whether you control both sides of the communication). + +## At a glance + +| | **End-to-end** (both sides run Nym) | **Proxy mode** (Nym → clearnet exit) | +|---|---|---| +| **Rust** (native / desktop / CLI) | [`nym-sdk`](/developers/rust) (Stream, Mixnet, Client Pool) | [`smolmix`](/developers/smolmix) (TCP / UDP) · [`nym-sdk`](/developers/rust) SOCKS client | +| **TypeScript** (browser) | [TypeScript SDK](/developers/typescript) (WASM Mixnet Client, messaging only) | [`mix-fetch`](/developers/mix-fetch) (HTTP) | +| **Mobile** (iOS / Android) | _untested; see [Mobile note](#mobile-untested-ground) below_ | _untested; see [Mobile note](#mobile-untested-ground) below_ | ## Environment Different runtimes have different transport constraints: a browser cannot open raw sockets or access the filesystem, while a desktop app can. -- **Native / Desktop**: full access to system networking and persistent storage. Use the [Rust SDK](./rust). -- **Browser**: restricted to WebSockets, Web Transport, and `fetch`, with HTTPS-only mixed content rules and no filesystem access. Use the [TypeScript SDK](./typescript). +- **Native / Desktop / CLI**: full access to system networking and persistent storage. Use [`nym-sdk`](/developers/rust) (the Rust SDK) for E2E messaging or byte streams, or [`smolmix`](/developers/smolmix) for TCP/UDP socket-shaped access in proxy mode. +- **Browser**: restricted to WebSockets, Web Transport, and `fetch`; HTTPS-only mixed-content rules; no filesystem access. Use [`mix-fetch`](/developers/mix-fetch) for HTTP(S) requests, or the [TypeScript SDK](/developers/typescript)'s WASM Mixnet Client for raw message passing. + +### Mobile (untested ground) + +There is no first-party mobile SDK, and we have not yet shipped tested mobile builds. The Rust crates *should* cross-compile to mobile targets. This is the path we'd take if starting today: + +- Build [`nym-sdk`](/developers/rust) or [`smolmix`](/developers/smolmix) for the target triple (`aarch64-apple-ios`, `aarch64-linux-android`, etc.). +- Generate FFI bindings with [`uniffi`](https://mozilla.github.io/uniffi-rs/) and call them from Swift or Kotlin. The existing [Rust SDK FFI](/developers/rust/ffi) (Go, C/C++) is a useful reference for the binding shape. +- Run the tokio reactor on a dedicated background thread. iOS in particular has strong opinions about backgrounding and `libdispatch` interaction. +- Plan for TLS / cert-store integration up front. On mobile you'll typically want to call out to platform TLS (`SecTrust` on iOS, Android `NetworkSecurityConfig`) rather than embed a CA bundle. + +If you try this and hit (or solve) blockers, we'd like to hear about it. Drop a note in the [Nym chat](https://nymtech.net/community) or open an issue on [GitHub](https://github.com/nymtech/nym). ## Architecture @@ -25,10 +44,24 @@ The second factor is whether you control both sides of the communication. **End-to-end (E2E)**: both sides run Nym clients. All traffic stays Sphinx-encrypted the entire way. Appropriate for peer-to-peer setups or any case where you control both endpoints. -**Proxy**: only the client side runs Nym. Traffic exits the Mixnet at an Exit Gateway and continues to the destination as normal internet traffic. Appropriate when connecting to third-party services (blockchain RPCs, external APIs) that you do not control. +![](/images/developers/nym-arch-client-to-client.png) + +**Proxy**: only the client side runs Nym. Traffic exits the Mixnet at an Exit Gateway and continues to the destination over the public internet. The mixnet anonymises the sender; payload protection (TLS, Noise, etc.) is your application's job, as on a direct connection. Appropriate when connecting to third-party services such as blockchain RPCs or external APIs. + +![](/images/developers/nym-arch-ip-routing.png) -In proxy mode, the last hop from Exit Gateway to the remote host travels as standard internet traffic. This is weaker than E2E against a global passive adversary, but still provides timing obfuscation and sender-receiver unlinkability. +Once traffic leaves the Exit Gateway, it travels over the public internet to the destination, exactly like any other server-initiated connection. The mixnet anonymises the sender but does not encrypt the payload past the gateway. Use TLS or another application-layer cipher as you would on a direct connection. See [Exit Gateway Services](/network/infrastructure/exit-services) for what the exit can and cannot observe. -See the [Native / Desktop](./native) and [Browser](./browsers) pages for the specific modules available in each environment. +**Browser apps**: both proxy and E2E modes work slightly differently in a browser setting. The Nym client runs as a WASM blob inside a Web Worker, and your application communicates with it via JS bindings rather than direct function calls. The mixnet behaviour is identical; the integration shape differs. + +![](/images/developers/nym-browser-arch.png) + +## Where to go next + +- **Rust, E2E messaging or byte streams**: [`nym-sdk`](/developers/rust) +- **Rust, TCP/UDP socket replacements**: [`smolmix`](/developers/smolmix) +- **Browser, HTTP(S) requests**: [`mix-fetch`](/developers/mix-fetch) +- **Browser, raw mixnet messaging or Nyx smart contracts**: [TypeScript SDK](/developers/typescript) +- **Background on Sphinx, gateways, and the mixnet itself**: [Key Concepts](/developers/concepts) diff --git a/documentation/docs/pages/developers/mix-fetch.mdx b/documentation/docs/pages/developers/mix-fetch.mdx new file mode 100644 index 0000000000..9aa7223679 --- /dev/null +++ b/documentation/docs/pages/developers/mix-fetch.mdx @@ -0,0 +1,130 @@ +--- +title: "mix-fetch: fetch() Over the Nym Mixnet" +description: "Package providing a fetch()-compatible API that routes HTTP(S) requests through the Nym mixnet via a Network Requester. Available for browsers and Node.js." +schemaType: "TechArticle" +section: "Developers" +lastUpdated: "2026-05-12" +--- + +import { Callout } from 'nextra/components' + +# mix-fetch + +`mix-fetch` is a replacement for [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) that routes HTTP(S) requests through the Nym mixnet. The call signature is identical; underneath, the request is tunnelled through a WASM Nym client to a Network Requester (a Nym service provider, typically operated by an Exit Gateway), which decodes a SOCKS5-shaped connect request and opens a TCP connection to the destination. TLS runs end-to-end between the WASM bundle and the destination server. + +Available for browsers and Node.js, with the WASM core shared between both. + +```text +┌────────────────────────────────────────────────────────────────────┐ +│ Your app (browser or Node.js) │ +│ └─ mixFetch('https://...') (fetch() replacement) │ +│ └─ Go-WASM HTTP/TLS client (embedded Mozilla CA bundle) │ +│ └─ Rust-WASM SOCKS5 framing + Nym mixnet transport │ +│ └─ WebSocket → entry gateway │ +│ └─ mixnet (Sphinx, 3 mix hops by default) │ +│ └─ Network Requester decodes SOCKS5 │ +│ request, opens TCP to dest │ +│ └─ destination server │ +│ (TLS handshake here) │ +└────────────────────────────────────────────────────────────────────┘ +``` + +Two WASM modules sit in the bundle: a Go module that handles HTTP and TLS (Go's `crypto/tls` compiled to WASM, with an embedded Mozilla root CA list), and a Rust module that handles SOCKS5 request framing and the Nym mixnet client itself. They communicate via JS bindings. + +Because TLS terminates at the destination (not at the Network Requester or any node before it), every hop from the entry gateway onwards only sees TLS ciphertext for HTTPS targets. This is the same trust model as a normal HTTPS request through a SOCKS proxy. + + +The "SOCKS5" framing here is Nym's binary `Socks5Request` format wrapped in Sphinx packets, not RFC 1928 SOCKS5 over a TCP socket. The Network Requester decodes it on the mixnet side and proxies onwards as regular TCP. + + +## Runtime and platform support + +### Browser + +The WASM core runs in a Web Worker and needs: +- WebSocket support, for the entry-gateway connection +- WebAssembly +- A CSP that permits `wss://` connections and `worker-src 'self'` (or `blob:` for the `*-full-fat` variants, which load workers as inline blobs) + +Mixed-content rules apply: target URLs must be HTTPS. + +### Node.js + +The same WASM core runs in a `worker_threads` worker. The `ws` package polyfills `WebSocket`, and a Node-flavoured `comlink` adapter (`mix-fetch-node/src/node-adapter.ts`) bridges `worker_threads` to the same Worker-like API surface. + +## Installation + +### Browser variants + +| Variant | Package | When to use | +|---|---|---| +| ESM | `@nymproject/mix-fetch` | Modern project, you can configure your bundler | +| ESM full-fat | `@nymproject/mix-fetch-full-fat` | Modern project, can't configure your bundler | +| CommonJS | `@nymproject/mix-fetch-commonjs` | Legacy project, you can configure your bundler | +| CommonJS full-fat | `@nymproject/mix-fetch-commonjs-full-fat` | Legacy project, can't configure your bundler | + +### Node.js variant + +| Variant | Package | When to use | +|---|---|---| +| CommonJS | `@nymproject/mix-fetch-node-commonjs` | Node.js (currently the only published Node variant) | + +The standard browser variants need your bundler to handle WASM and web workers (see [Bundling](/developers/typescript/bundling)). The `*-full-fat` variants inline both as Base64 so no bundler configuration is needed. + + +The `*-full-fat` variants are large (~18 MB), since they inline ~10 MB of WASM (Go runtime + Rust core) and the web-worker source as Base64. Prefer a standard variant if bundle size matters. + + +```bash +# Browser +npm install @nymproject/mix-fetch-full-fat + +# Node.js +npm install @nymproject/mix-fetch-node-commonjs +``` + + +`mixFetch` caps concurrent connections at **10 per destination host** (Go `http.Transport`'s `MaxConnsPerHost`, see `wasm/mix-fetch/go-mix-conn/internal/mixfetch/mixfetch.go:214`). Keep-alive is disabled, so each request opens a fresh TCP connection through the mixnet; extra concurrent requests to the same host queue until a slot frees. Different hosts are independent. + + +## Playground and examples + +See the [interactive playground](/developers/typescript/playground/mixfetch) for a working `mixFetch` example you can run in the browser. + +The first call bootstraps the WASM Nym client (gateway handshake, key generation, cover traffic). Subsequent calls reuse the active client; the Rust side holds it in a `OnceLock` singleton, so there is one client per page (or per Node process). + +## When to use mix-fetch + +| | mix-fetch | WASM Mixnet Client | smolmix | Plain fetch (no mixnet) | +|---|---|---|---|---| +| **Runtime** | Browser, Node.js | Browser | Native (Rust) | Anywhere | +| **Architecture** | Proxy (Network Requester → destination) | E2E (both sides Nym) | Proxy | Direct | +| **API shape** | `fetch()` replacement | Send/recv text or binary messages | `TcpStream` / `UdpSocket` | `fetch()` | +| **HTTP support** | Yes | No | Yes (via `hyper` over `TcpStream`) | Yes | +| **Sender unlinkability** | Strong (mixnet) | Strong (mixnet) | Strong (mixnet) | None | +| **Concurrency** | 10 per host | Unlimited | Unlimited | Unlimited | + +## Security model + + +Use HTTPS targets. Plaintext HTTP requests are visible to the Network Requester and to any router between it and the destination. + + +### What's protected + +| Segment | Mixnet encryption | What's visible | +|---|---|---| +| App → entry gateway | Sphinx (layered) over a WebSocket | Entry gateway sees your IP, not the destination | +| Inside the mixnet | Sphinx (layered) | Each node only knows previous / next hop | +| Network Requester | Sphinx removed; SOCKS5 connect request decoded | The Requester sees destination hostname + port; payload is application-layer TLS | +| Network Requester → destination | None (regular TCP) | TLS handshake + ciphertext (with HTTPS targets); cleartext (with HTTP targets) | + +### Why mix-fetch ships its own CA store + +The browser's TLS stack and CA store aren't accessible from JavaScript or from a WASM SOCKS client; on Node, the TLS stack lives outside the Web Worker that hosts the mixnet client. `mix-fetch` therefore performs TLS itself, inside the WASM bundle, against the destination server. The bundle ships with an embedded Mozilla root CA list (refreshed from [curl.se's bundle](https://curl.se/docs/caextract.html), verified by SHA-256 in `wasm/mix-fetch/go-mix-conn/scripts/update-root-certs.sh`) and an in-WASM TLS implementation (Go's `crypto/tls`, configured at `wasm/mix-fetch/go-mix-conn/internal/sslhelpers/ssl_helper.go`). The mixnet path sees encrypted TLS ciphertext, not plaintext. + +`mix-fetch` handles the TLS layer for you: HTTPS targets are protected end-to-end between the WASM bundle and the destination, as if a browser had initiated the TLS handshake directly. Plaintext HTTP targets remain visible to the Network Requester and to any router beyond it. See [Exit Gateway Services](/network/infrastructure/exit-services) for what the exit can and cannot observe. + +## API reference + +Generated reference: [typedoc output](/developers/typescript/api/mix-fetch). diff --git a/documentation/docs/pages/developers/native.mdx b/documentation/docs/pages/developers/native.mdx deleted file mode 100644 index 499c65fb95..0000000000 --- a/documentation/docs/pages/developers/native.mdx +++ /dev/null @@ -1,63 +0,0 @@ ---- -title: "Native and Desktop App Integration" -description: "Integrate privacy into native desktop apps and CLIs using the Nym Rust SDK. Choose between end-to-end mixnet messaging or TCP proxy approaches." -schemaType: "TechArticle" -section: "Developers" -lastUpdated: "2026-03-15" ---- - -import { Callout } from 'nextra/components'; - -# Native / Desktop Apps - -Desktop apps and CLIs integrate via the [Rust SDK](./rust), with two broad approaches: embedding Nym clients on both sides of the communication (E2E), or using the Mixnet as a proxy to reach external services. - -## Option 1: Mixnet End-To-End -Both sides of your app run Nym clients. All traffic stays Sphinx-encrypted the entire way. Works for peer-to-peer setups or any case where you control both ends. - -![](/images/developers/nym-arch-client-to-client.png) - -### Stream Module -The [Stream module](./rust/stream) provides `AsyncRead + AsyncWrite` byte streams multiplexed over the mixnet, the closest analogue to TCP sockets. - -- [docs](./rust/stream) -- [tutorial](./rust/stream/tutorial) - -### Mixnet & Client Pool Modules -The [Mixnet module](./rust/mixnet) exposes the raw message API and `MixnetClient`. The [Client Pool](./rust/client-pool) maintains pre-connected clients for bursty workloads. These are appropriate when you need full control over the communication model. - -- [docs](./rust/mixnet) -- [tutorial](./rust/mixnet/tutorial) - -### TcpProxy Module (Unmaintained) - - -**This module is unmaintained.** Use the [Stream module](./rust/stream) for new projects. Existing users should plan to migrate when possible. - - -Exposes localhost TCP sockets that proxy traffic through the mixnet. - -- [docs](./rust/tcpproxy) - -## Option 2: Mixnet-As-Proxy -For cases where you only control the client side and need to reach a third-party service such as a blockchain RPC or remote API. - -![](/images/developers/nym-arch-ip-routing.png) - - - -### Security Considerations - -Traffic is Sphinx-encrypted until the Exit Gateway, where it's unwrapped into HTTPS (Network Requester) or raw IP (IP Packet Router). The last hop to the remote host **travels as normal internet traffic**. - -Weaker than E2E against a global passive adversary, but you still get timing obfuscation and sender-receiver unlinkability between your client and the remote service. - - -### SOCKS Client -Applications that support SOCKS4, 4a, or 5 can use the Socks Client exposed by the Mixnet module. Traffic is routed through the Exit Gateway's Network Requester, which uses SURBs to reply to the sender anonymously. - -- [docs](./rust/mixnet) - - -Development is in progress to allow for this proxy method from native Rust, C, and Go without requiring a separate SOCKS client. Stay tuned. - diff --git a/documentation/docs/pages/developers/nymvpncli.mdx b/documentation/docs/pages/developers/nymvpncli.mdx index 7c15cfe88d..318d103a2b 100644 --- a/documentation/docs/pages/developers/nymvpncli.mdx +++ b/documentation/docs/pages/developers/nymvpncli.mdx @@ -74,7 +74,7 @@ yay -S gcc make protobuf base-devel clang apt install gcc make protobuf-compiler pkconfig libdbus-1-dev build-essential clang ``` - + Older Debian/Ubuntu versions need to manually install `protobuf-compiler` >= v3.21.12 @@ -169,7 +169,7 @@ nym-vpnc device get You can fund your VPN usage directly from your own wallet instead of going through the NymVPN account system. You deposit `$NYM` into the ticketbook smart contract and receive zk-nym ticketbooks that authenticate you on the network. - + If you already have an account stored in `nym-vpnc`, you must remove it first with `nym-vpnc account forget` before setting a new mnemonic. diff --git a/documentation/docs/pages/developers/rust.mdx b/documentation/docs/pages/developers/rust.mdx index 93affb53ae..04229f6175 100644 --- a/documentation/docs/pages/developers/rust.mdx +++ b/documentation/docs/pages/developers/rust.mdx @@ -1,34 +1,53 @@ --- -title: "Nym Rust SDK: Privacy Apps for the Mixnet" -description: "Rust SDK reference for building privacy applications on the Nym mixnet. Covers the Mixnet client, Stream multiplexing, Client Pool, and code examples." +title: "nym-sdk: Rust SDK for the Nym Mixnet" +description: "Rust SDK reference for building privacy applications on the Nym mixnet. Covers the Mixnet client, Stream multiplexing, Client Pool, FFI bindings, and code examples." schemaType: "TechArticle" section: "Developers" -lastUpdated: "2026-03-13" +lastUpdated: "2026-05-12" --- -# Rust SDK +# nym-sdk import { Callout } from 'nextra/components' -import { CratesPaused } from '../../components/crates-paused' -All modules share a common `MixnetClient` that manages gateway connections, Sphinx packet encryption, routing, and cover traffic. +`nym-sdk` is the Rust SDK for the Nym mixnet. All modules share a common `MixnetClient` that manages gateway connections, Sphinx encryption, and cover traffic. + +```text +┌──────────────────────────────────────────────────────────────┐ +│ Your Rust app (alice) │ +│ └─ MixnetClient (Sphinx layering, cover traffic) │ +│ └─ WebSocket to entry gateway │ +│ └─ Nym mixnet (entry → 3 mix layers → exit) │ +│ └─ MixnetClient (bob) │ +│ └─ Your Rust app (bob) │ +└──────────────────────────────────────────────────────────────┘ +``` + +Both sides run a `MixnetClient`. Sphinx encryption protects every hop end-to-end; neither gateway nor any mix node can link sender to receiver. Full API reference: [**docs.rs/nym-sdk**](https://docs.rs/nym-sdk/latest/nym_sdk/) - - For an overview of what the SDK can do, see the **[Tour](./rust/tour)**. For setup instructions, see [Installation](./rust/importing). ## Modules -- **[Stream](./rust/stream)**: multiplexed `AsyncRead + AsyncWrite` byte streams over the Mixnet. **If you're used to TCP sockets, start here.** +| Module | What it does | Status | +|---|---|---| +| [**Stream**](./rust/stream) | Multiplexed `AsyncRead + AsyncWrite` byte streams over the Mixnet, the closest analogue to TCP sockets. | Recommended | +| [**Mixnet**](./rust/mixnet) | Raw message payloads, independently routed, no connections or ordering. Full control over the communication model. | Stable | +| [**Client Pool**](./rust/client-pool) | Keeps ready-to-use `MixnetClient` instances warm for bursty workloads. | Stable | +| [**TcpProxy**](./rust/tcpproxy) | TCP socket proxying with session management and message ordering. | Deprecated | +| [**FFI**](./rust/ffi) | Go and C/C++ bindings. | Stable | -- **[Mixnet](./rust/mixnet)**: raw message payloads, independently routed, no connections or ordering. Use this when you want full control over the communication model. + +**TcpProxy is deprecated.** Use the [Stream module](./rust/stream) for new projects. + -- **[Client Pool](./rust/client-pool)**: keeps ready-to-use `MixnetClient` instances warm for bursty workloads. +## Proxy-mode crates -- **[TcpProxy](./rust/tcpproxy)** *(deprecated)*: TCP socket proxying with session management and message ordering. Use Stream for new projects. +For proxy-mode integrations (reaching third-party services through an Exit Gateway), see also: -- **[FFI](./rust/ffi)**: Go and C/C++ bindings. +- [**`smolmix`**](/developers/smolmix): `TcpStream` and `UdpSocket` over the Mixnet via a userspace IP stack. Compatible with `tokio-rustls`, `hyper`, `tokio-tungstenite`, and the rest of the async Rust ecosystem. +- [**SOCKS Client**](./rust/mixnet): SOCKS4/4a/5 proxy via the Exit Gateway's Network Requester. Works with any SOCKS-capable application without code changes. diff --git a/documentation/docs/pages/developers/rust/client-pool.mdx b/documentation/docs/pages/developers/rust/client-pool.mdx index 311bd147be..a6644584b8 100644 --- a/documentation/docs/pages/developers/rust/client-pool.mdx +++ b/documentation/docs/pages/developers/rust/client-pool.mdx @@ -10,7 +10,7 @@ lastUpdated: "2026-03-15" import { Callout } from 'nextra/components' -The `ClientPool` maintains a configurable number of connected ephemeral `MixnetClient` instances, ready for immediate use. This eliminates the connection latency that comes with creating a new client on each request: the gateway handshake, key generation, and topology fetch all happen ahead of time. +The `ClientPool` keeps a configurable number of `MixnetClient` instances pre-connected in a background loop, so callers don't pay the gateway handshake, key generation, and topology fetch cost on the hot path. ## How it works @@ -26,11 +26,11 @@ flowchart LR BG -->|"pool < reserve? create another"| P ``` -1. **Create** the pool with a target reserve size: `ClientPool::new(5)` -2. **Start** the background loop: `pool.start()`. It immediately begins connecting clients -3. **Pop** a client when needed: `pool.get_mixnet_client()` returns `Some(client)` or `None` if the pool is empty -4. **Use** the client normally: send messages, open streams, etc. -5. **Disconnect** the client when done. The background loop notices the pool is below reserve and creates a replacement +1. Create the pool with a target reserve size: `ClientPool::new(5)`. +2. Start the background loop: `pool.start()`. It immediately begins connecting clients. +3. Pop a client when needed: `pool.get_mixnet_client()` returns `Some(client)` or `None` if the pool is empty. +4. Use the client normally: send messages, open streams. +5. Disconnect the client when done. The background loop notices the pool is below reserve and creates a replacement. Clients are **consumed, not returned**. The pool creates new ones to maintain the reserve. If the pool is empty, you can fall back to `MixnetClient::connect_new()` (slower, but keeps things working). @@ -45,7 +45,7 @@ use nym_sdk::client_pool::ClientPool; use nym_network_defaults::setup_env; #[tokio::main] -async fn main() -> anyhow::Result<()> { +async fn main() -> Result<(), Box> { nym_bin_common::logging::setup_tracing_logger(); // Load mainnet network defaults into env vars (required by ClientPool) setup_env(None::); diff --git a/documentation/docs/pages/developers/rust/client-pool/tutorial.mdx b/documentation/docs/pages/developers/rust/client-pool/tutorial.mdx index 525636c226..727a07928d 100644 --- a/documentation/docs/pages/developers/rust/client-pool/tutorial.mdx +++ b/documentation/docs/pages/developers/rust/client-pool/tutorial.mdx @@ -3,15 +3,16 @@ title: "Client Pool Tutorial: Handle Bursty Traffic" description: "Step-by-step Rust tutorial to use Nym ClientPool for handling bursts of concurrent mixnet operations without blocking on client creation." schemaType: "HowTo" section: "Developers" -lastUpdated: "2026-03-26" +lastUpdated: "2026-04-17" --- # Tutorial: Handle Bursty Traffic with Client Pool import { Callout } from 'nextra/components' import { CodeVerified } from '../../../../components/code-verified' +import { RUST_MSRV } from '../../../../components/versions' -In this tutorial you'll build a program that uses `ClientPool` to handle bursts of concurrent Mixnet operations without blocking on client creation. You'll see how the pool pre-creates clients in the background, how to pop them under load, and what happens when demand exceeds supply. +A program that uses `ClientPool` to absorb bursts of concurrent Mixnet operations without paying client-creation latency on the hot path. The pool pre-creates clients in the background; tasks pop them under load; the tutorial also walks through what happens when demand outruns supply. ## What you'll learn @@ -25,7 +26,7 @@ In this tutorial you'll build a program that uses `ClientPool` to handle bursts ## Prerequisites -- Rust toolchain (1.70+) +- Rust toolchain ({RUST_MSRV}+) - A working internet connection ## Step 1: Set up the project @@ -39,11 +40,10 @@ Add dependencies to `Cargo.toml`: ```toml [dependencies] -nym-sdk = { git = "https://github.com/nymtech/nym", rev = "97068b2" } -nym-network-defaults = { git = "https://github.com/nymtech/nym", rev = "97068b2" } -nym-bin-common = { git = "https://github.com/nymtech/nym", rev = "97068b2", features = ["basic_tracing"] } +nym-sdk = "1.21.0" +nym-network-defaults = "1.21.0" +nym-bin-common = { version = "1.21.0", features = ["basic_tracing"] } tokio = { version = "1", features = ["full"] } -blake3 = "=1.7.0" # required pin — see https://nymtech.net/docs/developers/rust/importing ``` ## Step 2: Create and start the pool @@ -75,7 +75,7 @@ async fn main() { pool_bg.start().await.unwrap(); }); - println!("Pool started — waiting for clients to connect..."); + println!("Pool started, waiting for clients to connect..."); tokio::time::sleep(Duration::from_secs(15)).await; // Check how many are ready @@ -106,14 +106,14 @@ When you call `get_mixnet_client()`, the pool removes a client and returns it. T c } None => { - // Pool is empty — fall back to creating one on the fly. + // Pool is empty; fall back to creating one on the fly. // This is slower but keeps things working. println!("Task {i}: pool empty, creating client on the fly..."); nym_sdk::mixnet::MixnetClient::connect_new().await.unwrap() } }; - // Do something with the client — here, send a message to ourselves + // Do something with the client. Here, send a message to ourselves. let addr = *client.nym_address(); client .send_plain_message(addr, format!("hello from task {i}")) @@ -132,7 +132,7 @@ When you call `get_mixnet_client()`, the pool removes a client and returns it. T } } - // Disconnect when done — the pool will create a replacement + // Disconnect when done; the pool will create a replacement. client.disconnect().await; println!("Task {i}: done"); }); @@ -177,7 +177,7 @@ RUST_LOG=info cargo run You'll see output like: ``` -Pool started — waiting for clients to connect... +Pool started, waiting for clients to connect... Pool has 3 clients ready Task 1: got client 8gk4Y...@2xU4d... from pool Task 2: got client F3qR7...@9nK2m... from pool @@ -231,20 +231,33 @@ async fn main() { setup_env(None::); let pool = ClientPool::new(3); - let pool_bg = pool.clone(); - tokio::spawn(async move { pool_bg.start().await.unwrap() }); - println!("Waiting for pool to fill..."); + let pool_bg = pool.clone(); + tokio::spawn(async move { + pool_bg.start().await.unwrap(); + }); + + println!("Pool started, waiting for clients to connect..."); tokio::time::sleep(Duration::from_secs(15)).await; - println!("Pool has {} clients", pool.get_client_count().await); + + let count = pool.get_client_count().await; + println!("Pool has {count} clients ready"); let mut handles = vec![]; + for i in 1..=3 { let pool = pool.clone(); - handles.push(tokio::spawn(async move { + + let handle = tokio::spawn(async move { let mut client = match pool.get_mixnet_client().await { - Some(c) => c, - None => nym_sdk::mixnet::MixnetClient::connect_new().await.unwrap(), + Some(c) => { + println!("Task {i}: got client {} from pool", c.nym_address()); + c + } + None => { + println!("Task {i}: pool empty, creating client on the fly..."); + nym_sdk::mixnet::MixnetClient::connect_new().await.unwrap() + } }; let addr = *client.nym_address(); @@ -254,24 +267,34 @@ async fn main() { .unwrap(); if let Some(msgs) = client.wait_for_messages().await { - for msg in msgs.iter().filter(|m| !m.message.is_empty()) { - println!("Task {i}: {}", String::from_utf8_lossy(&msg.message)); + for msg in msgs { + if !msg.message.is_empty() { + println!( + "Task {i}: received {:?}", + String::from_utf8_lossy(&msg.message) + ); + } } } client.disconnect().await; - })); + println!("Task {i}: done"); + }); + + handles.push(handle); } for h in handles { h.await.unwrap(); } - println!("Waiting for replenishment..."); + println!("\nWaiting for pool to replenish..."); tokio::time::sleep(Duration::from_secs(15)).await; - println!("Pool has {} clients", pool.get_client_count().await); + + let count = pool.get_client_count().await; + println!("Pool has {count} clients ready again"); pool.disconnect_pool().await; - println!("Done"); + println!("Pool shut down"); } ``` diff --git a/documentation/docs/pages/developers/rust/ffi.mdx b/documentation/docs/pages/developers/rust/ffi.mdx index e32071eaa2..6bc44b2ccf 100644 --- a/documentation/docs/pages/developers/rust/ffi.mdx +++ b/documentation/docs/pages/developers/rust/ffi.mdx @@ -19,7 +19,7 @@ ffi └── shared # Shared Rust implementation ``` -Core logic lives in `shared/` and is imported into language-specific wrappers. The shared layer handles thread safety and ensures client operations run on blocking threads on the Rust side of the FFI boundary. +Core logic lives in `shared/` and is imported into language-specific wrappers. The shared layer handles thread safety and runs client operations on blocking threads on the Rust side of the FFI boundary. ## What's exposed @@ -36,7 +36,7 @@ The TcpProxy module is deprecated. For new projects, use the [Stream module](./s ## Quick example (Go) ```go -// Initialize an ephemeral client +// Initialise an ephemeral client bindings.InitEphemeral() // Get our Nym address diff --git a/documentation/docs/pages/developers/rust/importing.mdx b/documentation/docs/pages/developers/rust/importing.mdx index dd9c95c976..3cc34ab082 100644 --- a/documentation/docs/pages/developers/rust/importing.mdx +++ b/documentation/docs/pages/developers/rust/importing.mdx @@ -3,27 +3,24 @@ title: "Install the Nym Rust SDK" description: "Add nym-sdk to your Rust project from Git or crates.io. Covers version requirements, minimum Rust version, and current feature gate status." schemaType: "TechArticle" section: "Developers" -lastUpdated: "2026-03-27" +lastUpdated: "2026-04-17" --- # Installation import { Callout } from 'nextra/components'; -import { CratesPaused } from '../../../components/crates-paused' - - +import { RUST_MSRV } from '../../../components/versions' ```toml [dependencies] -nym-sdk = { git = "https://github.com/nymtech/nym", rev = "97068b2" } -blake3 = "=1.7.0" # pin to avoid a transitive dependency conflict — see note below +nym-sdk = "1.21.0" ``` - -**Temporary pin required.** You must pin `blake3 = "=1.7.0"` in your `Cargo.toml` to avoid a build failure caused by a transitive `digest` version conflict. This will be resolved in a future SDK release. - +**Minimum Rust version:** {RUST_MSRV}+ -You can also track a branch if you want the latest changes: +### From Git + +You can also import directly from Git if you want unreleased changes: ```toml # development branch (latest changes, may be unstable) @@ -33,19 +30,6 @@ nym-sdk = { git = "https://github.com/nymtech/nym", branch = "develop" } nym-sdk = { git = "https://github.com/nymtech/nym", branch = "master" } ``` -**Minimum Rust version:** 1.70+ - -### crates.io (older API only) - -If you don't need the Stream module or other recent additions, you can still use the published crate: - -```toml -[dependencies] -nym-sdk = "1.20.4" -``` - -This version includes the Mixnet message API, Client Pool, and TcpProxy modules. - **No feature gates yet.** Importing `nym-sdk` pulls in everything (mixnet, tcp_proxy, client_pool, etc.) and their full dependency trees. Cargo feature flags are planned. diff --git a/documentation/docs/pages/developers/rust/mixnet.mdx b/documentation/docs/pages/developers/rust/mixnet.mdx index 812a428ca6..8614a6a839 100644 --- a/documentation/docs/pages/developers/rust/mixnet.mdx +++ b/documentation/docs/pages/developers/rust/mixnet.mdx @@ -10,7 +10,7 @@ lastUpdated: "2026-03-13" import { Callout } from 'nextra/components'; -The `mixnet` module is the core of the Nym SDK. It provides [`MixnetClient`](https://docs.rs/nym-sdk/latest/nym_sdk/mixnet/struct.MixnetClient.html) for connecting to the Nym Mixnet, sending messages through Sphinx packet encryption and 5-hop routing, and receiving reconstructed messages on the other side. +The `mixnet` module provides [`MixnetClient`](https://docs.rs/nym-sdk/latest/nym_sdk/mixnet/struct.MixnetClient.html) for connecting to the Nym Mixnet, sending messages through Sphinx packet encryption and 5-hop routing, and receiving reconstructed messages on the other side. Messages are individually routed through the Mixnet with no guaranteed ordering or persistent connections. If you want familiar socket-like I/O (`read`/`write`), use the [Stream module](./stream) instead. See the [Tour](./tour) for how the two approaches compare. diff --git a/documentation/docs/pages/developers/rust/mixnet/examples.mdx b/documentation/docs/pages/developers/rust/mixnet/examples.mdx index 66282a9413..cc46a51090 100644 --- a/documentation/docs/pages/developers/rust/mixnet/examples.mdx +++ b/documentation/docs/pages/developers/rust/mixnet/examples.mdx @@ -23,7 +23,7 @@ cargo run --example | Parallel Send/Receive | [`parallel_sending_and_receiving.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/parallel_sending_and_receiving.rs) | Using `split_sender()` for concurrent tasks | | Sandbox Testnet | [`sandbox.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/sandbox.rs) | Connecting to the Sandbox testnet instead of mainnet | | Bandwidth Credential | [`bandwidth.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/bandwidth.rs) | Acquiring a bandwidth credential for paid mixnet access | -| Custom Topology | [`custom_topology_provider.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/custom_topology_provider.rs) | Implementing the `TopologyProvider` trait to filter or customize node selection | +| Custom Topology | [`custom_topology_provider.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/custom_topology_provider.rs) | Implementing the `TopologyProvider` trait to filter or customise node selection | | Overwrite Topology | [`manually_overwrite_topology.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/manually_overwrite_topology.rs) | Manually constructing a topology with hardcoded nodes | | Control Requests | [`control_requests.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/control_requests.rs) | Sending service provider control requests (health, version, binary info) | | Custom Storage | [`manually_handle_storage.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/manually_handle_storage.rs) | Implementing custom storage backends for keys, gateways, and credentials | diff --git a/documentation/docs/pages/developers/rust/mixnet/tutorial.mdx b/documentation/docs/pages/developers/rust/mixnet/tutorial.mdx index 0453266998..4221cb36f7 100644 --- a/documentation/docs/pages/developers/rust/mixnet/tutorial.mdx +++ b/documentation/docs/pages/developers/rust/mixnet/tutorial.mdx @@ -3,17 +3,18 @@ title: "Mixnet Tutorial: Send Your First Private Message" description: "Step-by-step Rust tutorial to connect to the Nym mixnet, send and receive messages, reply anonymously with SURBs, and persist client identity." schemaType: "HowTo" section: "Developers" -lastUpdated: "2026-03-26" +lastUpdated: "2026-04-17" --- # Tutorial: Send Your First Private Message import { Callout } from 'nextra/components' import { CodeVerified } from '../../../../components/code-verified' +import { RUST_MSRV } from '../../../../components/versions' -By the end of this tutorial you'll have a working program that sends a Sphinx-encrypted message to itself through the Nym Mixnet, receives it, and replies anonymously using SURBs. The later sections cover persistent identity and concurrent send/receive. +A program that sends a Sphinx-encrypted message to itself through the Nym Mixnet, receives it, and replies anonymously using SURBs. Later sections cover persistent identity and concurrent send/receive. -**You'll need:** Rust 1.70+ and an internet connection (clients connect to the live Mixnet). +Requires Rust {RUST_MSRV}+ and an internet connection (clients connect to the live Mixnet). @@ -28,10 +29,9 @@ Add dependencies to `Cargo.toml`: ```toml [dependencies] -nym-sdk = { git = "https://github.com/nymtech/nym", rev = "97068b2" } -nym-bin-common = { git = "https://github.com/nymtech/nym", rev = "97068b2", features = ["basic_tracing"] } +nym-sdk = "1.21.0" +nym-bin-common = { version = "1.21.0", features = ["basic_tracing"] } tokio = { version = "1", features = ["full"] } -blake3 = "=1.7.0" # required pin — see https://nymtech.net/docs/developers/rust/importing ``` ## Step 2: Connect and send @@ -45,21 +45,21 @@ use nym_sdk::mixnet::{self, MixnetMessageSender}; async fn main() { nym_bin_common::logging::setup_tracing_logger(); - // connect_new() creates an ephemeral client — keys are generated in + // connect_new() creates an ephemeral client; keys are generated in // memory and discarded on disconnect. let mut client = mixnet::MixnetClient::connect_new().await.unwrap(); let our_address = client.nym_address(); println!("Connected: {our_address}"); // The message is Sphinx-encrypted and mixed across 5 nodes. - // send_plain_message only blocks until the message is queued — + // send_plain_message only blocks until the message is queued; // encryption and mixing happen in background tasks. client .send_plain_message(*our_address, "hello from the mixnet!") .await .unwrap(); - println!("Sent — waiting for arrival..."); + println!("Sent, waiting for arrival..."); ``` @@ -70,7 +70,7 @@ async fn main() { ```rust // wait_for_messages() returns the next batch of incoming messages. - // Filter empty messages — these are SURB replenishment requests. + // Filter empty messages: these are SURB replenishment requests. let message = loop { if let Some(msgs) = client.wait_for_messages().await { if let Some(msg) = msgs.into_iter().find(|m| !m.message.is_empty()) { @@ -89,7 +89,7 @@ Every message includes a `sender_tag`, an opaque `AnonymousSenderTag` that lets ```rust let sender_tag = message.sender_tag.expect("should have sender tag"); - // send_reply uses the SURB — the sender's address is never revealed. + // send_reply uses the SURB; the sender's address is never revealed. client.send_reply(sender_tag, "hello back, anonymously!").await.unwrap(); let reply = loop { @@ -114,7 +114,7 @@ RUST_LOG=info cargo run ``` Connected: 8gk4Y...@2xU4d... -Sent — waiting for arrival... +Sent, waiting for arrival... Received: hello from the mixnet! Reply: hello back, anonymously! ``` @@ -140,21 +140,38 @@ async fn main() { .await .unwrap(); - println!("Address: {}", client.nym_address()); + let our_address = client.nym_address(); + println!("Address: {our_address}"); - // Same API as before — send, receive, reply. + // Same API as before: send, receive, SURB reply. client - .send_plain_message(*client.nym_address(), "persistent identity!") + .send_plain_message(*our_address, "hello from persistent client!") .await .unwrap(); + println!("Sent, waiting for arrival..."); - if let Some(msgs) = client.wait_for_messages().await { - for m in msgs.into_iter().filter(|m| !m.message.is_empty()) { - println!("Received: {}", String::from_utf8_lossy(&m.message)); + let message = loop { + if let Some(msgs) = client.wait_for_messages().await { + if let Some(msg) = msgs.into_iter().find(|m| !m.message.is_empty()) { + break msg; + } } - } + }; + println!("Received: {}", String::from_utf8_lossy(&message.message)); - // Always disconnect for clean shutdown — background tasks need to be + let sender_tag = message.sender_tag.expect("should have sender tag"); + client.send_reply(sender_tag, "anonymous reply!").await.unwrap(); + + let reply = loop { + if let Some(msgs) = client.wait_for_messages().await { + if let Some(msg) = msgs.into_iter().find(|m| !m.message.is_empty()) { + break msg; + } + } + }; + println!("Reply: {}", String::from_utf8_lossy(&reply.message)); + + // Always disconnect for clean shutdown; background tasks need to be // stopped and state files flushed. client.disconnect().await; } @@ -184,7 +201,7 @@ async fn main() { // split_sender() returns a clone-able MixnetClientSender. let sender = client.split_sender(); - // Spawn a receiver — the original client implements futures::Stream. + // Spawn a receiver: the original client implements futures::Stream. let rx = tokio::spawn(async move { if let Some(msg) = client.next().await { println!("Received: {}", String::from_utf8_lossy(&msg.message)); @@ -231,7 +248,7 @@ async fn main() { .send_plain_message(*our_address, "hello from the mixnet!") .await .unwrap(); - println!("Sent — waiting for arrival..."); + println!("Sent, waiting for arrival..."); let message = loop { if let Some(msgs) = client.wait_for_messages().await { @@ -270,6 +287,7 @@ async fn main() { nym_bin_common::logging::setup_tracing_logger(); let paths = StoragePaths::new_from_dir("./my-client-data").unwrap(); + let mut client = mixnet::MixnetClientBuilder::new_with_default_storage(paths) .await .unwrap() @@ -280,13 +298,13 @@ async fn main() { .unwrap(); let our_address = client.nym_address(); - println!("Connected: {our_address}"); + println!("Address: {our_address}"); client - .send_plain_message(*our_address, "hello from the mixnet!") + .send_plain_message(*our_address, "hello from persistent client!") .await .unwrap(); - println!("Sent — waiting for arrival..."); + println!("Sent, waiting for arrival..."); let message = loop { if let Some(msgs) = client.wait_for_messages().await { @@ -298,7 +316,7 @@ async fn main() { println!("Received: {}", String::from_utf8_lossy(&message.message)); let sender_tag = message.sender_tag.expect("should have sender tag"); - client.send_reply(sender_tag, "hello back, anonymously!").await.unwrap(); + client.send_reply(sender_tag, "anonymous reply!").await.unwrap(); let reply = loop { if let Some(msgs) = client.wait_for_messages().await { diff --git a/documentation/docs/pages/developers/rust/stream.mdx b/documentation/docs/pages/developers/rust/stream.mdx index 38d8d7e10a..1c3057c51e 100644 --- a/documentation/docs/pages/developers/rust/stream.mdx +++ b/documentation/docs/pages/developers/rust/stream.mdx @@ -9,13 +9,10 @@ lastUpdated: "2026-03-15" # Stream Module import { Callout } from 'nextra/components' -import { CratesPaused } from '../../../components/crates-paused' - - The Mixnet is fundamentally message-based: no persistent connections, no guaranteed ordering, no TCP. The default [message API](./mixnet) works at this level, sending individual payloads independently through Mix Nodes. This is effective for privacy but unlike how most networking code is structured. -The **Stream module** bridges the gap by providing persistent, bidirectional byte channels that behave like TCP sockets. Each `MixnetStream` implements [`AsyncRead`](https://docs.rs/tokio/latest/tokio/io/trait.AsyncRead.html) and [`AsyncWrite`](https://docs.rs/tokio/latest/tokio/io/trait.AsyncWrite.html), so `tokio::io::copy`, codecs, `BufReader`/`BufWriter`, and any other async I/O consumer work without modification. **If you're coming from socket-based networking, start here.** +The Stream module bridges the gap by providing persistent, bidirectional byte channels that behave like TCP sockets. Each `MixnetStream` implements [`AsyncRead`](https://docs.rs/tokio/latest/tokio/io/trait.AsyncRead.html) and [`AsyncWrite`](https://docs.rs/tokio/latest/tokio/io/trait.AsyncWrite.html), so `tokio::io::copy`, codecs, `BufReader`/`BufWriter`, and any other async I/O consumer work without modification. All streams are multiplexed over a single `MixnetClient`. A background router task reads a small header on each incoming message and dispatches the payload to the correct stream by ID, so multiple concurrent streams require no additional connections or gateways. @@ -23,10 +20,10 @@ All streams are multiplexed over a single `MixnetClient`. A background router ta The two sides of a stream connection follow a client/server pattern: -1. **Opener** calls `client.open_stream(recipient, surbs)`. This generates a random `StreamId`, registers the stream locally, and sends an `Open` message through the Mixnet. -2. **Listener** calls `listener.accept()`, which blocks until an `Open` arrives, registers the new stream, and returns a `MixnetStream` ready for reading and writing. +1. The opener calls `client.open_stream(recipient, surbs)`. This generates a random `StreamId`, registers the stream locally, and sends an `Open` message through the Mixnet. +2. The listener calls `listener.accept()`, which blocks until an `Open` arrives, registers the new stream, and returns a `MixnetStream` ready for reading and writing. 3. Both sides read and write using standard `AsyncRead`/`AsyncWrite`. Bytes are wrapped in a 16-byte LP frame header (stream ID, message type, sequence number), routed through the Mixnet, and demultiplexed on arrival. -4. **Cleanup** happens on `drop`. The stream deregisters from the local router. No close message is sent over the wire, since a close could race ahead of in-flight data. +4. On drop, the stream deregisters from the local router. No close message is sent over the wire, since a close could race ahead of in-flight data. ```text ┌─────────────────────────────────────────────────────────┐ @@ -94,7 +91,7 @@ async fn main() { .expect("timed out") .expect("listener closed"); - // Send data and read it back — just like a TCP socket + // Send data and read it back, just like a TCP socket outbound.write_all(b"hello from sender").await.unwrap(); outbound.flush().await.unwrap(); @@ -139,7 +136,7 @@ The receiver replies via **reply SURBs** (Single Use Reply Blocks) and never lea | **Status** | Stable | New | Deprecated | -**Streams and messages are mutually exclusive.** Once you call `open_stream()` or `listener()`, the message-based API (`send_plain_message`, `wait_for_messages`) is permanently disabled on that client. This is a one-way transition: there is no switching back without disconnecting and reconnecting. See the [`stream_mode_guard.rs` example](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/stream_mode_guard.rs) for details. +Streams and messages are mutually exclusive. Once you call `open_stream()` or `listener()`, the message-based API (`send_plain_message`, `wait_for_messages`) is permanently disabled on that client. This is a one-way transition: no switching back without disconnecting and reconnecting. See the [`stream_mode_guard.rs` example](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/stream_mode_guard.rs) for details. ## Next steps diff --git a/documentation/docs/pages/developers/rust/stream/architecture.mdx b/documentation/docs/pages/developers/rust/stream/architecture.mdx index 205eee370e..63541aaf15 100644 --- a/documentation/docs/pages/developers/rust/stream/architecture.mdx +++ b/documentation/docs/pages/developers/rust/stream/architecture.mdx @@ -80,7 +80,7 @@ There is no switching back without disconnecting and creating a new client. ## Known limitations -**Sequence-based reordering.** The Mixnet does not guarantee message ordering at the transport level, but each stream write includes a `sequence_num` in the LP frame header. The receiver maintains a per-stream reorder buffer (BTreeMap keyed by sequence number) that buffers out-of-order messages and drains them in sequence. This means protocols that depend on byte ordering (HTTP, TLS, protobuf) work correctly over streams. +The Mixnet does not guarantee message ordering at the transport level, but each stream write includes a `sequence_num` in the LP frame header. The receiver maintains a per-stream reorder buffer (BTreeMap keyed by sequence number) that buffers out-of-order messages and drains them in sequence, so protocols that depend on byte ordering (HTTP, TLS, protobuf) work correctly over streams. - **Buffer cap:** 256 messages per stream. If the buffer fills (e.g. a large gap in sequence numbers), the receiver skips ahead to the lowest buffered sequence. - **Duplicates:** messages with a sequence number below the next expected are dropped. @@ -89,4 +89,4 @@ There is no switching back without disconnecting and creating a new client. ## Internal details -For the full implementation details (router task, `StreamMap`, `PollSender` usage, base-client type rationale), see the `ARCHITECTURE.md` file next to the module source code. This will also be available on [docs.rs](https://docs.rs/nym-sdk/latest/nym_sdk/) once crate publication resumes with the Lewes Protocol. +For the full implementation details (router task, `StreamMap`, `PollSender` usage, base-client type rationale), see the `ARCHITECTURE.md` file next to the module source code, or the [docs.rs](https://docs.rs/nym-sdk/latest/nym_sdk/) API reference. diff --git a/documentation/docs/pages/developers/rust/stream/tutorial.mdx b/documentation/docs/pages/developers/rust/stream/tutorial.mdx index a9ec02f722..a79df667d1 100644 --- a/documentation/docs/pages/developers/rust/stream/tutorial.mdx +++ b/documentation/docs/pages/developers/rust/stream/tutorial.mdx @@ -3,15 +3,16 @@ title: "Stream Tutorial: Build a Private Echo Server" description: "Step-by-step Rust tutorial to build an echo server and client communicating through the Nym mixnet using AsyncRead and AsyncWrite streams." schemaType: "HowTo" section: "Developers" -lastUpdated: "2026-03-26" +lastUpdated: "2026-04-17" --- # Tutorial: Build a Private Echo Server import { Callout } from 'nextra/components' import { CodeVerified } from '../../../../components/code-verified' +import { RUST_MSRV } from '../../../../components/versions' -In this tutorial you'll build two programs: a server that listens for incoming streams and echoes back whatever it receives, and a client that opens a stream, sends data, and reads the echo. Both communicate through the Nym Mixnet using `AsyncRead` and `AsyncWrite`, just like TCP sockets. +Two programs: a server that listens for incoming streams and echoes back what it receives, and a client that opens a stream, writes data, and reads the echo. Both communicate through the Nym Mixnet using `AsyncRead` and `AsyncWrite`, like a TCP socket pair. ## What you'll learn @@ -25,7 +26,7 @@ In this tutorial you'll build two programs: a server that listens for incoming s ## Prerequisites -- Rust toolchain (1.70+) +- Rust toolchain ({RUST_MSRV}+) - A working internet connection (clients connect to the live Nym Mixnet) ## Step 1: Set up the project @@ -40,11 +41,10 @@ Add dependencies to `Cargo.toml`: ```toml [dependencies] -nym-sdk = { git = "https://github.com/nymtech/nym", rev = "97068b2" } -nym-bin-common = { git = "https://github.com/nymtech/nym", rev = "97068b2", features = ["basic_tracing"] } +nym-sdk = "1.21.0" +nym-bin-common = { version = "1.21.0", features = ["basic_tracing"] } tokio = { version = "1", features = ["full"] } rand = "0.8" -blake3 = "=1.7.0" # required pin — see https://nymtech.net/docs/developers/rust/importing ``` ## Step 2: Build the echo server @@ -65,7 +65,7 @@ async fn main() { let mut client = mixnet::MixnetClient::connect_new().await.unwrap(); println!("Echo server listening at: {}", client.nym_address()); - // Create a listener — this activates stream mode. + // Create a listener; this activates stream mode. // From this point, message-based methods are disabled. let mut listener = client.listener().unwrap(); @@ -88,7 +88,7 @@ async fn main() { loop { let n = match stream.read(&mut buf).await { - Ok(0) => break, // EOF — stream closed + Ok(0) => break, // EOF, stream closed Ok(n) => n, Err(e) => { eprintln!("Stream {stream_id} read error: {e}"); @@ -114,7 +114,7 @@ async fn main() { ``` -**`listener()` can only be called once per client.** It takes exclusive ownership of the inbound message channel. A second call returns `Error::ListenerAlreadyTaken`. +`listener()` can only be called once per client. It takes exclusive ownership of the inbound message channel; a second call returns `Error::ListenerAlreadyTaken`. ## Step 3: Build the client @@ -151,13 +151,13 @@ async fn main() { println!("Stream opened: {}", stream.id()); // Give the Open message time to traverse the mixnet and reach the server. - // open_stream() returns immediately after sending — it doesn't wait for + // open_stream() returns immediately after sending; it doesn't wait for // the server to accept. Writing too soon risks the data arriving before // the Open, which the server would drop. tokio::time::sleep(Duration::from_secs(5)).await; // Send three payloads of different sizes and verify the echo. - // Random bytes show that streams are binary-safe — not just text. + // Random bytes show that streams are binary-safe, not just text. let sizes = [320, 25_000, 1280]; for (i, &size) in sizes.iter().enumerate() { @@ -243,7 +243,7 @@ Stream 12345678 closed 5. On arrival, the router reads the `LpFrameKind` to identify it as stream traffic, decodes the header, finds the matching stream by ID, and delivers the raw payload to `read()`. -6. The inbound stream replies via **reply SURBs**, the same anonymous reply mechanism as the message API, applied transparently. The server never learns the client's Nym address. +6. The inbound stream replies via reply SURBs, the same anonymous reply mechanism as the message API. The server never learns the client's Nym address. 7. When a stream is dropped, it deregisters from the local router. No close message is sent over the wire, since a close could race ahead of in-flight data. @@ -279,7 +279,10 @@ async fn main() { loop { let mut stream = match listener.accept().await { Some(s) => s, - None => break, + None => { + println!("Listener closed"); + break; + } }; let stream_id = stream.id(); @@ -287,16 +290,27 @@ async fn main() { tokio::spawn(async move { let mut buf = vec![0u8; 32_000]; + loop { let n = match stream.read(&mut buf).await { - Ok(0) | Err(_) => break, + Ok(0) => break, Ok(n) => n, + Err(e) => { + eprintln!("Stream {stream_id} read error: {e}"); + break; + } }; - if let Err(_) = stream.write_all(&buf[..n]).await { + + let data = &buf[..n]; + println!("Stream {stream_id} received {n} bytes"); + + if let Err(e) = stream.write_all(data).await { + eprintln!("Stream {stream_id} write error: {e}"); break; } stream.flush().await.unwrap(); } + println!("Stream {stream_id} closed"); }); } diff --git a/documentation/docs/pages/developers/rust/tcpproxy.mdx b/documentation/docs/pages/developers/rust/tcpproxy.mdx index e25154b474..a3ec084fa7 100644 --- a/documentation/docs/pages/developers/rust/tcpproxy.mdx +++ b/documentation/docs/pages/developers/rust/tcpproxy.mdx @@ -1,26 +1,22 @@ --- -title: "Nym TcpProxy: Route TCP via the Mixnet" -description: "Route TCP traffic through the Nym mixnet using the TcpProxy Rust module. Covers architecture, single and multi-connection patterns, and troubleshooting." +title: "Nym TcpProxy: Route TCP via the Mixnet (Deprecated)" +description: "Route TCP traffic through the Nym mixnet using the TcpProxy Rust module. Deprecated in favour of the Stream module." schemaType: "TechArticle" section: "Developers" -lastUpdated: "2026-03-27" +lastUpdated: "2026-04-17" --- # TcpProxy Module + import { Callout } from 'nextra/components'; -import { CodeVerified } from '../../../components/code-verified' - **This module is unmaintained.** The TcpProxy is no longer actively developed in favour of the [Stream module](./stream), which provides `AsyncRead + AsyncWrite` streams directly over the Mixnet without the TCP socket overhead. Existing users should plan to migrate to streams when possible. The TcpProxy will continue to work but will not receive new features or bug fixes. +This module is unmaintained. The TcpProxy is no longer actively developed in favour of the [Stream module](/developers/rust/stream), which provides `AsyncRead + AsyncWrite` channels directly over the Mixnet without the localhost TCP socket layer. Existing users should plan to migrate. The module will continue to work but will not receive new features or bug fixes. -The Stream module offers the same key benefit (familiar I/O patterns on top of the Mixnet) with a simpler API. Streams multiplex connections on a single client, eliminate the localhost socket overhead, and now include sequence-based message reordering. There is no remaining reason to choose TcpProxy over Streams for new projects. +`NymProxyClient` and `NymProxyServer` proxy TCP traffic through the Mixnet. Both run in a background thread and expose a configurable `localhost` socket that callers read and write to like any other TCP connection. The Stream module replaces this pattern with multiplexed channels on a single client and no localhost socket layer. ---- - -`NymProxyClient` and `NymProxyServer` proxy TCP traffic through the Mixnet. Both run in a background thread and expose a configurable `localhost` socket that you read and write to like any other TCP connection. - -> Non-Rust/Go developers who want to experiment with this module can start with the [standalone binaries](../tools/standalone-tcpproxy). +> Non-Rust/Go developers can use the [standalone binaries](/developers/tools/standalone-tcpproxy) instead. ## Examples @@ -36,149 +32,13 @@ cargo run --example tcp_proxy_multistream ## API reference -- [API reference on docs.rs](https://docs.rs/nym-sdk/latest/nym_sdk/tcp_proxy/): architecture overview, client/server examples, and type documentation - -## Tutorial - - - -Set up the project: - -```sh -cargo init nym-tcp-proxy -cd nym-tcp-proxy -rm src/main.rs -``` - -Add dependencies to `Cargo.toml`: - -```toml -[dependencies] -nym-sdk = { git = "https://github.com/nymtech/nym", rev = "97068b2" } -nym-network-defaults = { git = "https://github.com/nymtech/nym", rev = "97068b2" } -nym-bin-common = { git = "https://github.com/nymtech/nym", rev = "97068b2", features = ["basic_tracing"] } -tokio = { version = "1", features = ["full"] } -anyhow = "1" -blake3 = "=1.7.0" # required pin — see https://nymtech.net/docs/developers/rust/importing - -[[bin]] -name = "proxy_server" -path = "src/bin/proxy_server.rs" - -[[bin]] -name = "proxy_client" -path = "src/bin/proxy_client.rs" -``` - -### Server - -The server connects to the Mixnet and forwards incoming traffic to a local TCP service (e.g. a web server on port 8000). - -```rust -use nym_sdk::tcp_proxy::NymProxyServer; - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - nym_bin_common::logging::setup_tracing_logger(); - - let mut server = NymProxyServer::new( - "127.0.0.1:8000", // upstream address (host:port) - "./proxy-server-config", // config directory for persistent keys - None, // env file (None = mainnet) - None, // gateway (None = auto-select) - ).await?; - - println!("Proxy server address: {}", server.nym_address()); - server.run_with_shutdown().await?; - Ok(()) -} -``` - -### Client - -The client opens a localhost TCP socket and tunnels all traffic through the Mixnet to the server. - -```rust -use nym_sdk::tcp_proxy::NymProxyClient; -use nym_sdk::mixnet::Recipient; -use nym_network_defaults::setup_env; -use tokio::io::{AsyncReadExt, AsyncWriteExt}; -use tokio::net::TcpStream; - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - nym_bin_common::logging::setup_tracing_logger(); - // Load mainnet network defaults into env vars (required by NymProxyClient's internal ClientPool) - setup_env(None::); - - let server_addr: Recipient = std::env::args() - .nth(1).expect("Usage: proxy_client ") - .parse()?; - - let client = NymProxyClient::new( - server_addr, - "127.0.0.1", // listen host - "8070", // listen port - 60, // close timeout (seconds) - None, // env file (None = mainnet) - 1, // client pool size - ).await?; - - let proxy = tokio::spawn(async move { client.run().await }); - - // Wait for the pool to create a client and the proxy to be ready. - // The first startup takes ~10-15s while the client connects to the Mixnet. - println!("Waiting for proxy to be ready..."); - tokio::time::sleep(std::time::Duration::from_secs(15)).await; - - let mut stream = TcpStream::connect("127.0.0.1:8070").await?; - stream.write_all(b"GET / HTTP/1.0\r\nHost: localhost\r\n\r\n").await?; - - let mut response = Vec::new(); - stream.read_to_end(&mut response).await?; - println!("Response:\n{}", String::from_utf8_lossy(&response)); - - drop(stream); - proxy.abort(); - Ok(()) -} -``` - -### Run it - -Start an upstream TCP service (e.g. a simple HTTP server): - -```sh -python3 -m http.server 8000 -``` - -In a second terminal, start the proxy server: - -```sh -RUST_LOG=info cargo run --bin proxy_server -``` - -Copy the Nym address it prints, then in a third terminal: - -```sh -RUST_LOG=info cargo run --bin proxy_client -- -``` - -The response will take 30–60 seconds to arrive as it traverses the Mixnet in both directions. +[`docs.rs/nym-sdk/tcp_proxy`](https://docs.rs/nym-sdk/latest/nym_sdk/tcp_proxy/) covers types, methods, and the full client/server walkthrough. ## Architecture -Each sub-module handles Nym clients differently: -- **`NymProxyClient`** relies on the [Client Pool](./client-pool) to create clients and keep a reserve. If incoming TCP connections outpace the pool, it creates an ephemeral client per connection. One client maps to one TCP connection. -- **`NymProxyServer`** has a single Nym client with a persistent identity. +`NymProxyClient` uses a [Client Pool](/developers/rust/client-pool) with one client per incoming TCP connection; if the pool runs dry it falls back to creating clients on demand. `NymProxyServer` runs a single Nym client with a persistent identity. -### Sessions & message ordering - -Messages are wrapped in a session ID per connection, with individual messages given an incrementing message ID. Once all messages are sent, the client sends a `Close` message to notify the server that there are no more outbound messages for this session. - -> Session management and message IDs are necessary since *the Mixnet guarantees message delivery but not message ordering*: in the case of trying to e.g. send gRPC protobuf through the Mixnet, ordering is required so that a buffer is not split across Sphinx packet payloads, and that the 2nd half of the frame is not passed upstream to the parser before the 1st half. - -The key data structure: +Each TCP connection is wrapped in a session ID; messages within a session carry an incrementing message ID, and a final `Close` message signals that no more outbound bytes are coming. Session and message IDs are necessary because the Mixnet guarantees delivery but not ordering, and ordering matters whenever a parser cares about frame boundaries (gRPC over protobuf, HTTP, TLS). ```rust pub struct ProxiedMessage { @@ -188,68 +48,4 @@ pub struct ProxiedMessage { } ``` -### Full request/response flow - -```mermaid ---- -config: - theme: neo-dark - layout: elk ---- -sequenceDiagram - box Local Machine - participant Client Process - participant NymProxyClient - end - Client Process->>NymProxyClient: Request bytes - NymProxyClient->>NymProxyClient: New session - NymProxyClient->>Entry Gateway: Sphinx Packets: Message 1 - Entry Gateway-->>NymProxyClient: Acks - NymProxyClient->>Entry Gateway: Sphinx Packets: Message 2 - Entry Gateway-->>NymProxyClient: Acks - NymProxyClient->>Entry Gateway: Sphinx Packets: Close Message - Entry Gateway-->>NymProxyClient: Acks - - Entry Gateway-->>Mix Nodes: All Packets, Acks, etc - Note right of Mix Nodes: We are omitting the 3 hops etc for brevity here - Mix Nodes-->> Exit Gateway: All Packets, Acks, etc - - Exit Gateway->>NymProxyServer: Sphinx Packets: Message 2 - NymProxyServer-->>Exit Gateway: Acks - loop Message Buffer - NymProxyServer->>NymProxyServer: Wait for Message 1 - Exit Gateway->>NymProxyServer: Sphinx Packets: Message 1 - NymProxyServer-->>Exit Gateway: Acks - NymProxyServer->>NymProxyServer: Message Received: trigger upstream send - end - Note right of NymProxyServer: Note this happens **per session** - NymProxyServer->>Upstream Process: Reconstructed request bytes - Upstream Process->>Upstream Process: Do something with request - Exit Gateway->>NymProxyServer: Sphinx Packets: Close Message - NymProxyServer-->>Exit Gateway: Acks - NymProxyServer->>NymProxyServer: Trigger Client timeout start for session - Upstream Process->>NymProxyServer: Response bytes - NymProxyServer->>NymProxyServer: Write to provided SURB payloads - NymProxyServer->>Exit Gateway: Anonymous replies - box Remote Host - participant NymProxyServer - participant Upstream Process - end - - Entry Gateway->>NymProxyClient: Sphinx Packets: Reply Message 2 - NymProxyClient-->Entry Gateway: Ack - Loop Message Buffer: - NymProxyClient->>NymProxyClient: Wait for Message 1 - Entry Gateway->>NymProxyClient: Sphinx Packets: Message 1 - NymProxyClient-->>Entry Gateway: Acks - NymProxyClient->>NymProxyClient: Message Received: trigger send - NymProxyClient->>Client Process: Response bytes - end - Note right of NymProxyClient: Note this happens **per session** -``` - -## Troubleshooting - -### Lots of `duplicate fragment received` messages - -`WARN` level logs about duplicate fragments are caused by Mixnet-level packet retransmission, where both the original and the retransmitted copy arrive at the destination. This is expected behaviour, not a bug in the client or TcpProxy module. +For the full request/response sequence diagram, see the [module source](https://github.com/nymtech/nym/tree/develop/sdk/rust/nym-sdk/src/tcp_proxy) or the [docs.rs](https://docs.rs/nym-sdk/latest/nym_sdk/tcp_proxy/) entry. diff --git a/documentation/docs/pages/developers/rust/tour.mdx b/documentation/docs/pages/developers/rust/tour.mdx index 6f23e20fe1..9616f0a0d5 100644 --- a/documentation/docs/pages/developers/rust/tour.mdx +++ b/documentation/docs/pages/developers/rust/tour.mdx @@ -5,7 +5,7 @@ import { Callout } from 'nextra/components' A quick walkthrough of the most important things you can do with `nym-sdk`. Each section shows working code and links to the module that covers it in depth. -**The Mixnet is not like regular internet networking** — there are no persistent connections, no guaranteed message ordering, and no TCP underneath. At its core, the Mixnet is a message-based anonymity network: you send individual payloads that are Sphinx-encrypted, mixed through multiple nodes, and independently reconstructed at the destination. +The Mixnet is not like regular internet networking. There are no persistent connections, no guaranteed message ordering, and no TCP underneath. At its core, the Mixnet is a message-based anonymity network: you send individual payloads that are Sphinx-encrypted, mixed through multiple nodes, and independently reconstructed at the destination. The raw [message API](./mixnet) therefore works differently from what most developers expect. The [Stream module](./stream) bridges this gap by providing `AsyncRead + AsyncWrite` byte streams on top of the Mixnet. If you are coming from socket-based networking, start with streams. @@ -80,7 +80,7 @@ async fn main() { // Receiver accepts it let mut inc = listener.accept().await.unwrap(); - // Standard tokio I/O — write, flush, read + // Standard tokio I/O: write, flush, read out.write_all(b"hello stream").await.unwrap(); out.flush().await.unwrap(); diff --git a/documentation/docs/pages/developers/smolmix.mdx b/documentation/docs/pages/developers/smolmix.mdx new file mode 100644 index 0000000000..90f4da65c3 --- /dev/null +++ b/documentation/docs/pages/developers/smolmix.mdx @@ -0,0 +1,130 @@ +--- +title: "smolmix: TCP/UDP Over the Nym Mixnet" +description: "A userspace IP tunnel that provides standard TcpStream and UdpSocket types over the Nym mixnet. Compatible with the async tokio Rust ecosystem." +schemaType: "TechArticle" +section: "Developers" +lastUpdated: "2026-04-29" +--- + +# smolmix + +import { Callout } from 'nextra/components' +import { RUST_MSRV } from '../../components/versions' + +`smolmix` is a TCP/UDP tunnel over the Nym mixnet. It uses a userspace network stack [`smoltcp`](https://docs.rs/smoltcp/latest/smoltcp/) to provide `TcpStream` and `UdpSocket` types that work with the async [`tokio`](https://docs.rs/tokio) Rust ecosystem e.g. [`tokio-rustls`](https://docs.rs/tokio-rustls), [`hyper`](https://docs.rs/hyper), [`tokio-tungstenite`](https://docs.rs/tokio-tungstenite), etc. + +The `TcpStream` type implements tokio's `AsyncRead`/`AsyncWrite` traits and `UdpSocket` provides `send_to`/`recv_from` for datagrams. + +```text +┌──────────────────────────────────────────────────────────────┐ +│ Your application (TLS, HTTP, WebSocket, DNS, etc.) │ +│ └─ smolmix::TcpStream / UdpSocket │ +│ └─ smoltcp (userspace TCP/IP) │ +│ └─ Nym mixnet → IPR exit gateway → internet │ +└──────────────────────────────────────────────────────────────┘ +``` + +Traffic exits the mixnet at an [IPR (Internet Packet Router)](/network/infrastructure/exit-services#ip-packet-router) exit gateway. The exit IP is the gateway's, not yours. + +## Runtime and platform support +### Other runtimes +`smolmix` currently requires `tokio`. The internal pipeline is tokio-based: the bridge task that shuttles packets to the mixnet, the Nym SDK's `MixnetClient`, and the [`tokio-smoltcp`](https://docs.rs/tokio-smoltcp) reactor that drives the userspace TCP/IP stack all run on the tokio runtime. + +This means `smolmix` is not directly compatible with alternative async runtimes like [`smol`](https://docs.rs/smol) or [`async-std`](https://docs.rs/async-std). If you need to use `smolmix` from another runtime, the [`async-compat`](https://docs.rs/async-compat) crate can bridge the gap. + +### Non-native `smolmix` +A WASM version of `smolmix` is planned for a future release. + +## Installation + +Add `smolmix` to your `Cargo.toml`: + +```toml +[dependencies] +smolmix = "1.21.0" +nym-bin-common = { version = "1.21.0", features = ["basic_tracing"] } +tokio = { version = "1", features = ["rt-multi-thread", "macros"] } +``` + +`tokio` is a transitive dependency of smolmix, but you need to enable `rt-multi-thread` (the default runtime spun up by `#[tokio::main]`) and `macros` (for the `#[tokio::main]` attribute itself). + +`nym-bin-common` is optional but recommended: it sets up [`tracing`](https://docs.rs/tracing) logging so you can see mixnet connection progress. + +**Minimum Rust version:** {RUST_MSRV}+ + +### From Git + +For unreleased changes, import directly from the repository: + +```toml +smolmix = { git = "https://github.com/nymtech/nym", branch = "develop" } +``` + +## When to use smolmix + +| | smolmix | Stream module | mixFetch | SOCKS client | +|---|---|---|---|---| +| **Layer** | Transport (TCP/UDP) | Message (multiplexed streams) | HTTP | TCP (SOCKS proxy) | +| **Controls both sides?** | No (proxy mode) | Yes (E2E) | No (proxy mode) | No (proxy mode) | +| **API** | `TcpStream`, `UdpSocket` | `AsyncRead + AsyncWrite` | `fetch()` replacement | SOCKS4/5 protocol | +| **Composability** | Full: TLS, HTTP, WebSocket, DNS, etc. stack on top | Byte streams only | HTTP(S) only | Application-dependent | +| **Best for** | Reaching external services from Rust with standard networking | Peer-to-peer / E2E protocols between Nym clients | Browser HTTP requests | Legacy apps with SOCKS support | + +## Security model + + +Traffic is Sphinx-encrypted inside the mixnet. Past the Exit Gateway, it travels over the public internet to the destination, the same as any other server-initiated connection. Protect the payload at the application layer with TLS ([`rustls`](https://docs.rs/rustls)), Noise Protocol ([`snow`](https://docs.rs/snow)), or equivalent, as you would on a direct connection. + + +### What's protected + +| Segment | Mixnet encryption | What's visible | +|---|---|---| +| Your machine → mixnet entry | Sphinx (layered) | Entry gateway sees your IP but not the destination | +| Inside the mixnet (entry + 3 mix layers + exit) | Sphinx (layered) | Each node only knows prev/next hop | +| Exit gateway (IPR) | Sphinx removed, raw IP packet exposed | IPR sees destination IP + port. Payload depends on your application layer (see below). | +| IPR → remote host | None (Sphinx is mixnet-only) | Remote host sees IPR's IP, not yours | + +The Sphinx encryption is the **mixnet transport layer**: it protects packets as they traverse the mix nodes. At the exit gateway, the Sphinx layers are stripped and the original IP packet is forwarded to the destination. This is analogous to how a Tor exit node or VPN endpoint unwraps its tunnel. + +What's inside that IP packet is up to you. If you connect with TLS (as in the [TCP example](https://github.com/nymtech/nym/blob/develop/smolmix/core/examples/tcp.rs)), the IPR sees encrypted TLS ciphertext going to a destination IP: it knows where but not what. If you send plaintext HTTP, the IPR can read the full request and response. + +### Trust boundaries + +- You trust the mixnet to provide unlinkability between sender and receiver. This is enforced cryptographically by the Sphinx packet format and mixing. +- You trust the IPR exit gateway in the same way you trust a VPN exit or Tor exit node: it can inspect your raw IP packets. The difference is that the IPR doesn't know who is sending the traffic (the mixnet hides your identity). +- **Application-layer encryption closes the gap.** TLS, Noise Protocol, or any authenticated encryption keeps the payload as ciphertext to the IPR. It can see destination IP and port, but not payload content. + +### Comparison with other privacy tools + +| | smolmix | Tor | VPN | +|---|---|---|---| +| **Exit node sees traffic?** | Yes (encrypt it) | Yes (encrypt it) | Yes (encrypt it) | +| **Exit node knows sender?** | No (mixnet hides identity) | No (onion routing) | Yes (VPN provider knows) | +| **Timing analysis resistance** | Strong (mixing, cover traffic) | Weak (low-latency) | None | +| **UDP support** | Yes | No (TCP only) | Yes | + +## Examples + +Runnable examples in [`smolmix/core/examples/`](https://github.com/nymtech/nym/tree/develop/smolmix/core/examples). Each is self-contained; read the `//!` doc comments at the top of each file for a walkthrough. + +```sh +cargo run -p smolmix --example +``` + +All examples accept `--ipr
` to target a specific exit node (pass a `Recipient` address to `Tunnel::builder().ipr_address()`). + +| Example | Source | What it demonstrates | +|---|---|---| +| UDP | [`udp.rs`](https://github.com/nymtech/nym/blob/develop/smolmix/core/examples/udp.rs) | DNS lookup via [`hickory-proto`](https://docs.rs/hickory-proto), sending a raw UDP query to `1.1.1.1:53` through the mixnet. Runs a clearnet [`hickory-resolver`](https://docs.rs/hickory-resolver) lookup alongside it to compare resolved IPs and latency | +| TCP | [`tcp.rs`](https://github.com/nymtech/nym/blob/develop/smolmix/core/examples/tcp.rs) | HTTPS request via [`hyper`](https://docs.rs/hyper) + [`tokio-rustls`](https://docs.rs/tokio-rustls). Fetches Cloudflare's `/cdn-cgi/trace` to show that the exit IP differs from clearnet | +| WebSocket | [`websocket.rs`](https://github.com/nymtech/nym/blob/develop/smolmix/core/examples/websocket.rs) | WebSocket echo against `ws.postman-echo.com` via [`tokio-tungstenite`](https://docs.rs/tokio-tungstenite) + [`tokio-rustls`](https://docs.rs/tokio-rustls). Runs the same stack over clearnet first, so the only thing that changes between runs is the underlying TCP stream | +| TCP download | [`tcp_download.rs`](https://github.com/nymtech/nym/blob/develop/smolmix/core/examples/tcp_download.rs) | DNS-over-mixnet + multi-request HTTP/1.1 download over a single keep-alive connection, the full real-world pattern | + +## Architecture + +The internal stack (smoltcp reactor, device adapter, bridge task, packet flow) is documented in [`ARCHITECTURE.md`](https://github.com/nymtech/nym/blob/develop/smolmix/core/src/ARCHITECTURE.md). This is also the crate-level documentation on docs.rs. + +## API reference + +Full API documentation is available on [docs.rs/smolmix](https://docs.rs/smolmix). diff --git a/documentation/docs/pages/developers/tools.mdx b/documentation/docs/pages/developers/tools.mdx index 07cf771203..838632e50c 100644 --- a/documentation/docs/pages/developers/tools.mdx +++ b/documentation/docs/pages/developers/tools.mdx @@ -8,10 +8,10 @@ lastUpdated: "2026-02-01" # Tools -Standalone binaries for development and testing that don't require an SDK — download or compile them and use them directly. +Standalone binaries for development and testing that don't require an SDK. Download or compile them and use them directly. | Tool | Use case | |---|---| | [nym-cli](./tools/nym-cli) | Command-line interface for interacting with the Nyx blockchain: querying state, submitting transactions, managing keys. An easier-to-use wrapper around `nyxd`. | | [Diagnostic Tool](./tools/diagnostic-tool) | Network diagnostic utility for troubleshooting connectivity issues. | -| [Standalone TcpProxy](./tools/standalone-tcpproxy) | Pre-built binaries of the TcpProxy client and server for proxying TCP traffic through the Mixnet. Note: the TcpProxy module is unmaintained; use the [Stream module](./rust/stream) for new projects. | +| [Standalone TcpProxy](./tools/standalone-tcpproxy) | Pre-built binaries of the TcpProxy client and server for proxying TCP traffic through the Mixnet. The TcpProxy module is unmaintained; use the [Stream module](./rust/stream) for new projects. | diff --git a/documentation/docs/pages/developers/tools/diagnostic-tool.mdx b/documentation/docs/pages/developers/tools/diagnostic-tool.mdx index dec7b67cf2..930c5fcab7 100644 --- a/documentation/docs/pages/developers/tools/diagnostic-tool.mdx +++ b/documentation/docs/pages/developers/tools/diagnostic-tool.mdx @@ -2,9 +2,9 @@ import { Steps } from 'nextra/components'; # Diagnostic Tool -The Diagnostic Tool is a standalone binary designed to perform various network tests, including DNS, HTTP, and gateway connectivity tests. This tool helps diagnose connectivity issues and provides insights into network performance. +The Diagnostic Tool is a standalone binary that runs network tests (DNS, HTTP, gateway connectivity) to diagnose connectivity issues and report on network performance. -It’s also possible to run it within the daemon with the same CLI interface. +It can also be run from within the daemon with the same CLI interface. ## Download Binary @@ -36,7 +36,7 @@ chmod +x ./* ## CLI Usage -The Diagnostic Tool can be executed from the command line interface (CLI). Below are the usage instructions and options available. Read in the chapter [*Tests Performed*](#tests-performed) about the purpose and outcome of these commands. +The Diagnostic Tool runs from the command line. The usage instructions and options follow. See [Tests Performed](#tests-performed) for the purpose and outcome of each command. ### Command Syntax @@ -99,36 +99,36 @@ The Diagnostic Tool runs the following tests: ### 1. DNS Test -- **Purpose**: To check the resolution DNS availability. -- **Process**: We try to resolve all the domain names present in a given nym network environment with different DNS configurations -- **Output**: Displays the resolved IP address and the time taken for the resolution. +- **Purpose**: Check DNS resolution availability. +- **Process**: Resolve all domain names present in the given Nym network environment with different DNS configurations. +- **Output**: Resolved IP address and time taken for resolution. ### 2. HTTP Test -- **Purpose**: To verify the accessibility of the NymVPN API. -- **Process**: The tool query the `health` endpoint as well as the `nodes/described` endpoint. -- **Output**: Displays the response of the `health` endpoint, the time skew and the number of nodes in the network (sanity check) +- **Purpose**: Verify accessibility of the NymVPN API. +- **Process**: Query the `health` and `nodes/described` endpoints. +- **Output**: Response from the `health` endpoint, time skew, and number of nodes in the network (sanity check). ### 3. Gateway Test -- **Purpose**: To check the connectivity to a given gateway. -- **Process**: The tool fetches information about the gateway, then establishes a TCP connection, upgrades it to WS and sends a request -- **Output**: Display the gateway reported information, the status of the connections and the WS response. +- **Purpose**: Check connectivity to a given gateway. +- **Process**: Fetch information about the gateway, establish a TCP connection, upgrade it to WS, and send a request. +- **Output**: Gateway-reported information, connection status, and WS response. ### 4. Registration Test -- **Purpose:** To check the correctness of the registration process. -- **Process:** The tool tries to build a mixnet client to the provided gateway and then tries to register to the entry authenticator -- **Output:** Display the status of the different steps -- **Caveat:** This test requires a credential to be spent, which is why it is available as a separate command only +- **Purpose:** Check the registration process. +- **Process:** Build a mixnet client to the provided gateway and register with the entry authenticator. +- **Output:** Status of each step. +- **Caveat:** This test spends a credential, which is why it lives in a separate command. ### 5. Wireguard Test -- **Purpose:** To check the soundness of a wireguard connection -- **Process:** The tool uses the registration data from the previous step to establish a wireguard connection and ping an IP. -- **Output:** Display the ping RTTs and any error that might have happened +- **Purpose:** Check the soundness of a wireguard connection. +- **Process:** Use the registration data from the previous step to establish a wireguard connection and ping an IP. +- **Output:** Ping RTTs and any errors. ## Reports -Reports are logged in a JSON format and also returned by the commands for a future use \ No newline at end of file +Reports are logged in JSON format and also returned by the commands for later use. \ No newline at end of file diff --git a/documentation/docs/pages/developers/tools/nym-cli/usage.mdx b/documentation/docs/pages/developers/tools/nym-cli/usage.mdx index 6dced9fd92..eac5574545 100644 --- a/documentation/docs/pages/developers/tools/nym-cli/usage.mdx +++ b/documentation/docs/pages/developers/tools/nym-cli/usage.mdx @@ -4,11 +4,11 @@ The `nym-cli` binary can be built by running `cargo build --release` in the `nym/tools/nym-cli` directory. ## Usage -See the [commands](commands.mdx) page for an overview of all command options. +See the [commands](/developers/tools/nym-cli/commands) page for an overview of all command options. ## Staking on someone's behalf (for custodians) -There is a limitation the staking address can only perform the following actions (and are visible via the Nym Wallet: +The staking address can only perform the following actions (visible via the Nym Wallet): - Bond on the gateway's or Mix Node's behalf. - Delegate or Un-delegate (to a Mix Node in order to begin receiving rewards) diff --git a/documentation/docs/pages/developers/tools/standalone-tcpproxy.mdx b/documentation/docs/pages/developers/tools/standalone-tcpproxy.mdx index 89372e7b34..5406a794ec 100644 --- a/documentation/docs/pages/developers/tools/standalone-tcpproxy.mdx +++ b/documentation/docs/pages/developers/tools/standalone-tcpproxy.mdx @@ -6,9 +6,9 @@ import { Callout } from 'nextra/components' **Deprecated.** The TcpProxy module is no longer actively developed. The [Stream module](/developers/rust/stream) provides the same functionality (familiar `AsyncRead`/`AsyncWrite` I/O over the Mixnet) with a simpler API, multiplexed connections, and sequence-based message reordering. Use Streams for new projects. -Standalone versions of the `TcpProxyClient` and `TcpProxyServer` [sdk module](../rust/tcpproxy) can be found [here](https://github.com/nymtech/standalone-tcp-proxies/tree/main). +Standalone versions of the `TcpProxyClient` and `TcpProxyServer` [sdk module](/developers/rust/tcpproxy) can be found [here](https://github.com/nymtech/standalone-tcp-proxies/tree/main). -These might be an easy way for developers to start proxying their traffic throught the mixnet and understanding the sort of latency they should expect, and whether their application can currently tolerate it. They might also prove useful for server setups where several components are being run via init scripts, and the addition of a separate process is acceptable. +These can be a quick way to start proxying traffic through the mixnet, see what latency to expect, and check whether your application can tolerate it. They are also useful for server setups where several components run via init scripts and an extra process is acceptable. ## Build ```shell diff --git a/documentation/docs/pages/developers/typescript.mdx b/documentation/docs/pages/developers/typescript.mdx index d2d8df68d4..5dbe63a500 100644 --- a/documentation/docs/pages/developers/typescript.mdx +++ b/documentation/docs/pages/developers/typescript.mdx @@ -1,9 +1,9 @@ --- -title: "Nym TypeScript SDK: Privacy for Web Apps" -description: "TypeScript SDK for integrating web apps with the Nym mixnet. Covers mixFetch, Mixnet Client, Smart Contracts, and Cosmos Kit with live playground examples." +title: "TypeScript SDK: Mixnet Messaging & Smart Contracts" +description: "TypeScript SDK for integrating web apps with the Nym mixnet. Covers the Mixnet Client (messaging) and Nym Smart Contracts. For HTTP requests, see mix-fetch." schemaType: "TechArticle" section: "Developers" -lastUpdated: "2026-03-13" +lastUpdated: "2026-05-12" --- import { Callout } from 'nextra/components' @@ -12,7 +12,23 @@ import { NPMLink } from '../../components/npm'; # TypeScript SDK -The TypeScript SDK lets you build browser-based applications that communicate through the Nym mixnet. Import SDK packages via NPM as you would any other TypeScript library. +The TypeScript SDK lets you build browser-based applications that communicate through the Nym mixnet. + +```text +┌──────────────────────────────────────────────────────────────┐ +│ Your browser app │ +│ └─ Nym Mixnet Client (WASM, runs in a Web Worker) │ +│ └─ WebSocket to entry gateway │ +│ └─ Nym mixnet (entry → 3 mix layers → exit) │ +│ └─ Peer MixnetClient (e.g. nym-sdk) │ +└──────────────────────────────────────────────────────────────┘ +``` + +The Mixnet Client operates in messaging mode (text or binary payloads) and runs in a Web Worker to keep the UI thread free. For HTTP requests, use [`mix-fetch`](/developers/mix-fetch) instead. + + +For an HTTP-over-mixnet `fetch()` replacement, see [**mix-fetch**](/developers/mix-fetch). This page covers the **Mixnet Client** (send / receive raw messages) and **Smart Contracts** (interact with the Nyx chain). + The Nym Mixnet routes traffic through multiple nodes with no persistent connections or guaranteed ordering. The SDK abstracts the complexity, but understanding the [underlying model](/developers/rust/tour) helps when debugging. @@ -23,21 +39,6 @@ The Nym Mixnet routes traffic through multiple nodes with no persistent connecti - - - **mixFetch** - - - A drop-in replacement for [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) - that sends HTTP requests over the Nym mixnet - - -
-
-
- -
-
**Mixnet Client** @@ -82,12 +83,6 @@ All `*-full-fat` variants have large bundle sizes because they include WASM and ## Installation -### mixFetch - -```bash -npm install @nymproject/mix-fetch-full-fat -``` - ### Mixnet Client ```bash @@ -103,30 +98,11 @@ npm install @nymproject/contract-clients @cosmjs/cosmwasm-stargate @cosmjs/proto ### Install everything ```bash -npm install @nymproject/contract-clients @cosmjs/cosmwasm-stargate @cosmjs/proto-signing @nymproject/sdk-full-fat @nymproject/mix-fetch-full-fat +npm install @nymproject/contract-clients @cosmjs/cosmwasm-stargate @cosmjs/proto-signing @nymproject/sdk-full-fat ``` ## Quick start -### mixFetch - -Use [`mixFetch`](https://www.npmjs.com/package/@nymproject/mix-fetch) as a drop-in replacement for `fetch` to send HTTP requests over the mixnet: - -```ts -import { mixFetch } from '@nymproject/mix-fetch'; - -// HTTP GET -const response = await mixFetch('https://nym.com'); -const html = await response.text(); - -// HTTP POST -const apiResponse = await mixFetch('https://api.example.com', { - method: 'POST', - body: JSON.stringify({ foo: 'bar' }), - headers: { 'Content-Type': 'application/json' } -}); -``` - ### Mixnet Client Create a [`Mixnet Client`](https://www.npmjs.com/package/@nymproject/sdk) to send and receive messages through the mixnet: @@ -184,4 +160,4 @@ console.log(`Tx Hash = ${result.transactionHash}`); - **[Step-by-step examples](./typescript/examples):** Full working projects for each package - **[Live playground](./typescript/playground):** Try the SDK in your browser - **[Bundling](./typescript/bundling):** Configure Webpack or ESBuild for WASM and web workers -- **[TypeDoc reference](./typescript/api):** generated reference for all packages +- **[TypeDoc reference](./typescript/api):** Generated reference for all packages diff --git a/documentation/docs/pages/developers/typescript/bundling/esbuild.mdx b/documentation/docs/pages/developers/typescript/bundling/esbuild.mdx index 5ff64081cf..4873564737 100644 --- a/documentation/docs/pages/developers/typescript/bundling/esbuild.mdx +++ b/documentation/docs/pages/developers/typescript/bundling/esbuild.mdx @@ -2,16 +2,18 @@ import { Callout } from 'nextra/components'; # Troubleshooting bundling with ESbuild -If you've been following the steps outlined in the Examples section, your development environment should be configured as follows: +If you have followed the steps in the Examples section, your development environment should be configured as follows. #### Environment Setup -Begin by creating a directory and configuring your application environment: -Create your directory and set-up your app environment: +Create your directory and set up your app environment: + ```bash npm create vite@latest ``` -During the environment setup, choose React and subsequently opt for Typescript if you want your application to function smoothly following this tutorial. Next, navigate to your application directory and run the following commands: + +Choose React, then Typescript. Navigate to your application directory and run: + ```bash cd < YOUR_APP > npm i @@ -20,12 +22,13 @@ npm run dev ##### Installation Install the required package: + ```bash npm install @nymproject/< PACKAGE_NAME > ``` - - Remember that the CosmosKit example will require you to make use of polyfills. + + The CosmosKit example requires polyfills. -By implementing the provided code for the various components in the step-by-step examples section, you should be able to set-up and run your application without encountering any bundling challenges! +With the code from the step-by-step examples section in place, the application should run without bundling errors. diff --git a/documentation/docs/pages/developers/typescript/bundling/webpack.mdx b/documentation/docs/pages/developers/typescript/bundling/webpack.mdx index 1b5803f90c..f355f9f021 100644 --- a/documentation/docs/pages/developers/typescript/bundling/webpack.mdx +++ b/documentation/docs/pages/developers/typescript/bundling/webpack.mdx @@ -19,18 +19,19 @@ For any project using Webpack, you´ll need the following rule in your `webpack. #### General cases -If you wish to use Webpack for your app with the code provided in the step-by-step examples section, you'll need to: +To use Webpack with the code from the step-by-step examples section: ```bash npx create-react-app nymapp --template typescript cd nymapp ``` -You'll then need to install the needed dependencies, head to your app's `App.tsx` file and paste the code provided in the step-by-step section. + +Install the required dependencies, then paste the code from the step-by-step section into your app's `App.tsx`. #### Contract client - - Using webpack, the `Contract client` for querying or executing might need polyfills. As create-react-app doesn´t allow you access to the Webpack config without ejecting, you'll overwrite it as follow: + + With webpack, the `Contract client` for querying or executing may need polyfills. Since create-react-app doesn't expose the Webpack config without ejecting, override it as follows: ##### Install contract-clients dependencies @@ -38,7 +39,7 @@ You'll then need to install the needed dependencies, head to your app's `App.tsx npm install @nymproject/contract-clients @cosmjs/cosmwasm-stargate @cosmjs/proto-signing ``` -Head to you app's `App.tsx` file and replace the code by the one provided in the step-by-step examples section. +In your app's `App.tsx`, replace the existing code with the code from the step-by-step examples section. ##### Polyfilling diff --git a/documentation/docs/pages/developers/typescript/examples/cosmos-kit.mdx b/documentation/docs/pages/developers/typescript/examples/cosmos-kit.mdx index c33f16d971..666f5e2887 100644 --- a/documentation/docs/pages/developers/typescript/examples/cosmos-kit.mdx +++ b/documentation/docs/pages/developers/typescript/examples/cosmos-kit.mdx @@ -2,21 +2,22 @@ import { Callout } from 'nextra/components' # Cosmos Kit -The wonderful people of Cosmology have made some [fantastic components](https://cosmoskit.com/) that can be used with -Nym. These include: +Cosmology's [components](https://cosmoskit.com/) work with Nym. They cover: -- Using the wallets such as Keplr, Cosmostation and others from your React application; -- Using the [Ledger hardware wallet](https://docs.cosmoskit.com/integrating-wallets/ledger) from your browser; -- Any wallet that supports [Wallet Connect v2.0](https://docs.cosmoskit.com/integrating-wallets/adding-new-wallets); +- Wallets such as Keplr, Cosmostation, and others from your React application +- The [Ledger hardware wallet](https://docs.cosmoskit.com/integrating-wallets/ledger) from your browser +- Any wallet that supports [Wallet Connect v2.0](https://docs.cosmoskit.com/integrating-wallets/adding-new-wallets) ##### Environment Setup -Begin by creating a directory and configuring your application environment: + +Create a new project with Vite: ```bash npm create vite@latest ``` -During the environment setup, choose React and subsequently opt for Typescript if you want your application to function smoothly following this tutorial. Next, navigate to your application directory and run the following commands: +Choose React, then Typescript. Navigate to your application directory and run: + ```bash cd < YOUR_APP > npm i diff --git a/documentation/docs/pages/developers/typescript/examples/mix-fetch.mdx b/documentation/docs/pages/developers/typescript/examples/mix-fetch.mdx index 961df307ec..5aaf9d792a 100644 --- a/documentation/docs/pages/developers/typescript/examples/mix-fetch.mdx +++ b/documentation/docs/pages/developers/typescript/examples/mix-fetch.mdx @@ -1,6 +1,6 @@ --- title: "mixFetch Example: Private HTTP Requests" -description: "Replace browser fetch with mixFetch to route HTTP requests through the Nym mixnet. Covers setup, CA certificates, WSS gateways, and usage examples." +description: "Replace browser fetch with mixFetch to route HTTP requests through the Nym mixnet. Covers setup, CA certificates, TLS configuration, and usage examples." schemaType: "TechArticle" section: "Developers" lastUpdated: "2026-03-15" @@ -14,13 +14,9 @@ An easy way to secure parts or all of your web app is to replace calls to [`fetc Things to be aware of: -- CA certificates in `mixFetch` are periodically updated. If you get a certificate error, the root certificate you need might not be valid yet. [Send a PR](https://github.com/nymtech/nym/pulls) if you need changes to the certificates. -- If you are using `mixFetch` in a web app with HTTPS, you will need to use a gateway that has Secure Websockets (WSS) to avoid a [mixed content](https://developer.mozilla.org/en-US/docs/Web/Security/Mixed_content) error. -- `mixFetch` supports concurrent requests (up to 10) to the same or different URLs. - - -Right now Gateways are not required to run a Secure Websocket (WSS) listener, so only a subset of nodes running in Gateway mode have configured their nodes to do so. You need to select a Gateway that has WSS from [Harbourmaster](https://harbourmaster.nymtech.net/). - +- **CA certificates** are bundled into the WASM binary at build time. They're updated with each SDK release, so if you hit a certificate error, update to the latest `@nymproject/mix-fetch-full-fat` version. +- **HTTPS and WSS.** When serving your app over HTTPS, the mixnet connection must also use Secure WebSockets to avoid a [mixed content](https://developer.mozilla.org/en-US/docs/Web/Security/Mixed_content) error. Set `forceTls: true` in your `SetupMixFetchOps` config (see below) and the SDK will automatically select a WSS-capable gateway. +- `mixFetch` supports **concurrent requests** (up to 10) to the same or different URLs. ## Environment Setup @@ -41,30 +37,34 @@ npm run dev ## Installation ```bash -npm install @nymproject/mix-fetch-full-fat +npm install @nymproject/mix-fetch-full-fat @mui/material @emotion/react @emotion/styled ``` +The MUI packages are used by the example UI below. If you only need `mixFetch` itself, install only `@nymproject/mix-fetch-full-fat`. + ## Configuration ```ts import type { SetupMixFetchOps } from '@nymproject/mix-fetch-full-fat'; const mixFetchOptions: SetupMixFetchOps = { - clientId: "docs-mixfetch-demo", + clientId: "my-app", preferredGateway: "q2A2cbooyC16YJzvdYaSMH9X3cSiieZNtfBr8cE8Fi1", mixFetchOverride: { requestTimeoutMs: 60_000, }, - forceTls: true, // force WSS + forceTls: true, // use Secure WebSockets (required when serving over HTTPS) }; ``` +`preferredGateway` is optional. If omitted, the SDK auto-selects a gateway. You can pin a specific one via [Harbourmaster](https://harbourmaster.nymtech.net/). + ## Full Example -This example shows explicit initialization via `createMixFetch`, single URL fetch, and concurrent requests. Results appear both in the UI and in a visible log panel. +This example shows explicit initialisation via `createMixFetch`, single URL fetch, and concurrent requests. Results appear both in the UI and in a visible log panel. -For this example we use the `full-fat` version of the ESM SDK. If you use the unbundled ESM variant, make sure your [bundler configuration](../bundling/bundling) copies the WASM and web worker files to the output bundle. +For this example we use the `full-fat` version of the ESM SDK. If you use the unbundled ESM variant, make sure your [bundler configuration](/developers/typescript/bundling/bundling) copies the WASM and web worker files to the output bundle. ```tsx @@ -138,7 +138,7 @@ export const MixFetch = () => { setLogs((prev) => [...prev, { timestamp, message, level }]); }; - // Initialize MixFetch explicitly via createMixFetch + // Initialise MixFetch explicitly via createMixFetch const handleStart = async () => { try { setStatus("starting"); @@ -155,7 +155,7 @@ export const MixFetch = () => { } }; - // Single URL fetch — reuses the existing MixFetch singleton + // Single URL fetch (reuses the existing MixFetch singleton) const handleFetch = async () => { try { setBusy(true); @@ -229,7 +229,7 @@ export const MixFetch = () => { - {/* Fetch controls — disabled until MixFetch is ready */} + {/* Fetch controls (disabled until MixFetch is ready) */} {/* Single fetch */} diff --git a/documentation/docs/pages/developers/typescript/examples/mixnet.mdx b/documentation/docs/pages/developers/typescript/examples/mixnet.mdx index ef3e292fab..6d0810c4f6 100644 --- a/documentation/docs/pages/developers/typescript/examples/mixnet.mdx +++ b/documentation/docs/pages/developers/typescript/examples/mixnet.mdx @@ -43,7 +43,7 @@ npm install @nymproject/sdk-full-fat This example creates a Mixnet client, connects to a gateway, and provides a UI for sending and receiving messages through the mixnet. -For this example we use the `full-fat` version of the ESM SDK. If you use the unbundled ESM variant, make sure your [bundler configuration](../bundling/bundling) copies the WASM and web worker files to the output bundle. +For this example we use the `full-fat` version of the ESM SDK. If you use the unbundled ESM variant, make sure your [bundler configuration](/developers/typescript/bundling/bundling) copies the WASM and web worker files to the output bundle. ```ts copy filename="App.tsx" diff --git a/documentation/docs/pages/developers/typescript/playground/cosmos-kit.mdx b/documentation/docs/pages/developers/typescript/playground/cosmos-kit.mdx index 4498cfc72f..4a6b1d8ccd 100644 --- a/documentation/docs/pages/developers/typescript/playground/cosmos-kit.mdx +++ b/documentation/docs/pages/developers/typescript/playground/cosmos-kit.mdx @@ -12,7 +12,7 @@ Sign a transaction using [CosmosKit](https://cosmoskit.com/) wallet adapters. Th - + No transactions will be broadcast. You will only be signing a transaction. @@ -27,6 +27,6 @@ If you are using a Ledger hardware wallet: ## How this works -The component uses CosmosKit to manage wallet connections across different wallet providers (Keplr, Ledger). When you click Sign, it constructs a message, requests your wallet to sign it, and displays the signature hash. This demonstrates how to integrate Cosmos-compatible wallets into a web application that interacts with the Nyx blockchain. +The component uses CosmosKit to manage wallet connections across different wallet providers (Keplr, Ledger). When you click Sign, it constructs a message, requests your wallet to sign it, and displays the signature hash. The same pattern works for any web application that interacts with the Nyx blockchain. diff --git a/documentation/docs/pages/developers/typescript/playground/mixfetch.mdx b/documentation/docs/pages/developers/typescript/playground/mixfetch.mdx index b32c60f24d..f75896a00b 100644 --- a/documentation/docs/pages/developers/typescript/playground/mixfetch.mdx +++ b/documentation/docs/pages/developers/typescript/playground/mixfetch.mdx @@ -17,6 +17,6 @@ Fetch a URL through the Nym Mixnet. This demo creates a mixFetch client in your ## How this works -The component calls `createMixFetch()` to initialize a Mixnet client in the browser, then uses the returned `mixFetch()` function as a drop-in replacement for `window.fetch()`. The request is routed through the Mixnet to a Network Requester, which makes the HTTP request on your behalf and returns the response anonymously. +The component calls `createMixFetch()` to initialise a Mixnet client in the browser, then uses the returned `mixFetch()` function as a replacement for `window.fetch()`. The request is routed through the Mixnet to a Network Requester, which makes the HTTP request on your behalf and returns the response anonymously. diff --git a/documentation/docs/pages/developers/typescript/playground/traffic.mdx b/documentation/docs/pages/developers/typescript/playground/traffic.mdx index 56170d16a2..de0a194396 100644 --- a/documentation/docs/pages/developers/typescript/playground/traffic.mdx +++ b/documentation/docs/pages/developers/typescript/playground/traffic.mdx @@ -7,7 +7,7 @@ import FormattedTrafficExampleCode from '../../../../code-examples/sdk/typescrip Send and receive messages through the Nym Mixnet directly in your browser. This demo creates a Mixnet client, connects to the network, and lets you send a message to yourself (or any Nym address) to see the full round-trip. -**Try it:** Click Connect, wait for the client to initialize, then send a message. You'll see it arrive back through the Mixnet after traversing 5 hops. +**Try it:** Click Connect, wait for the client to initialise, then send a message. You'll see it arrive back through the Mixnet after traversing 5 hops. Open your browser's console to see the connection and send/receive logging for this example. diff --git a/documentation/docs/pages/network/cryptography.md b/documentation/docs/pages/network/cryptography.md index 0ecc00757c..ec5b7345bc 100644 --- a/documentation/docs/pages/network/cryptography.md +++ b/documentation/docs/pages/network/cryptography.md @@ -12,6 +12,6 @@ The Nym Network relies on several cryptographic systems working together. This s ## What's covered -[Sphinx Packets](/network/cryptography/sphinx) explains the packet format that enables layered encryption and anonymous routing. Each Sphinx packet contains routing information encrypted in layers, where each hop can only decrypt its own layer. +[Sphinx Packets](/network/cryptography/sphinx) explains the packet format used for layered encryption and anonymous routing. Each Sphinx packet contains routing information encrypted in layers, where each hop can only decrypt its own layer. [zk-nyms](/network/cryptography/zk-nym) covers the anonymous credential system that separates payment from usage. This is how you can pay for network access without that payment being linkable to your activity. diff --git a/documentation/docs/pages/network/cryptography/sphinx.md b/documentation/docs/pages/network/cryptography/sphinx.md index 579050e11c..8608806311 100644 --- a/documentation/docs/pages/network/cryptography/sphinx.md +++ b/documentation/docs/pages/network/cryptography/sphinx.md @@ -8,7 +8,7 @@ lastUpdated: "2026-03-15" # Sphinx -Sphinx is the cryptographic packet format used for all mixnet traffic. It provides layered encryption where each hop can only decrypt its own routing information, ensuring that no single node knows both the source and destination of a packet. +Sphinx is the cryptographic packet format used for all mixnet traffic. It provides layered encryption where each hop can only decrypt its own routing information, so no single node knows both the source and destination of a packet. ## How Sphinx works @@ -20,7 +20,7 @@ At each hop, the node uses its private key to decrypt its layer, revealing the a All Sphinx packets have a fixed payload size of 2048 bytes. This uniformity is critical: if packets varied in size, nodes could infer their position in the route or correlate packets by size. -The packet contains a header with encrypted routing information for each hop, HMACs to verify integrity at each layer, and the encrypted payload. The header uses a clever "onion" structure where processing at each hop reveals only the next hop's information while maintaining constant size through padding. +The packet contains a header with encrypted routing information for each hop, HMACs to verify integrity at each layer, and the encrypted payload. The header uses an "onion" structure where processing at each hop reveals only the next hop's information while maintaining constant size through padding. ## Integrity verification diff --git a/documentation/docs/pages/network/cryptography/zk-nym.mdx b/documentation/docs/pages/network/cryptography/zk-nym.mdx index 3febcdaebe..22ac756831 100644 --- a/documentation/docs/pages/network/cryptography/zk-nym.mdx +++ b/documentation/docs/pages/network/cryptography/zk-nym.mdx @@ -2,9 +2,9 @@ The zk-nym scheme enables the creation and use of unlinkable, rerandomisable anonymous access credentials that are 'spent' with Gateways in order to anonymously prove that someone has paid for Mixnet access. This implementation incorporates elements of both the [Coconut Credential](https://arxiv.org/pdf/1802.07344) and [Offline Ecash](https://arxiv.org/pdf/2303.08221) schemes. -As outlined in the [overview](./zk-nym/zk-nym-overview) on the next page, zk-nyms allow for users to pay for Mixnet access in a manner that is **unlinkable to their payment account**; even with pseudonymous cryptocurrencies or fiat. This solves one of the fundamental privacy problems with the majority of VPNs and dVPNs in production today: the linkability of a user's session with their payment information, which can in the majority of cases be easily used to deanonymise them, either at the behest of an authority or by the service operators themselves. +As outlined in the [overview](./zk-nym/zk-nym-overview) on the next page, zk-nyms allow users to pay for Mixnet access in a way that is **unlinkable to their payment account**, even with pseudonymous cryptocurrencies or fiat. This solves one of the fundamental privacy problems with most VPNs and dVPNs in production today: the linkability of a user's session with their payment information, which can in most cases be used to deanonymise them, either at the behest of an authority or by the service operators themselves. -> The current zk-nym scheme is non-generic in that it is only used for gating Mixnet access. A generic scheme based on zk-nyms is being actively researched in order to facilitate more generic and customisable anonymous credentials for other applications and services. +> The current zk-nym scheme is non-generic in that it is only used for gating Mixnet access. A generic scheme based on zk-nyms is being actively researched, to support more generic and customisable anonymous credentials for other applications and services. ## Motivations Most of the time, when we build system security, we think of _who_ questions: @@ -25,7 +25,7 @@ The zk-nym scheme allows for this move to take place. Credentials are generated ### Re-randomisation vs pseudonymity We stand on the shoulders of giants. Ten years ago, Bitcoin showed the way forward by allowing people to control resource access without recourse to _who_ questions. Rather, in Bitcoin and succeeding blockchains, a private key proves a _right to use_. -But as we can now see, private keys in blockchain systems act only as a minor barrier to finding out _who_ is accessing resources. A Bitcoin or Ethereum private key is effectively a long-lived pseudonym which is easily traceable through successive transactions. +But as we can now see, private keys in blockchain systems act only as a minor barrier to finding out _who_ is accessing resources. A Bitcoin or Ethereum private key is a long-lived pseudonym that is easily traceable through successive transactions. **zk-nyms allows us to build truly private systems rather than pseudonymous ones.** @@ -38,8 +38,8 @@ Let's say you have a `message` with the content `This credential controls X` in 2. _Re-randomizable signatures_ - take a signature, and generate a brand new signature that is valid for the same underlying message `This credential controls X`. The new bitstring in the re-randomized signature is equivalent to the original signature but not linkable to it. So a user can generate multiple zk-nyms from a single credential source, unlinkable to any previous "shown" zk-nym. But the underlying content of the re-randomized credential is the same (including for things like double-spend protection). This once again protects the user against the signer, because the signer can't trace the signed message that they gave back to the user when it is presented. It also protects the user against the relying party that accepts the signed credential. The user can generate multiple re-randomized credentials repeatedly, and although the underlying message is the same in all cases, there's no way of tracking them by watching the user present the same credential multiple times. -3. _Selective disclosure of attributes_ - allows someone with the public key to verify some, but not all, parts of a message. So you could for instance selectively reveal parts of a signed message to some people, but not to others. This is a very powerful property of the scheme which is to be explored more in future work, potentially leading to diverse applications: voting systems, anonymous currency, privacy-friendly KYC systems, etc. +3. _Selective disclosure of attributes_ - allows someone with the public key to verify some, but not all, parts of a message. So you could for instance selectively reveal parts of a signed message to some people, but not to others. This property of the scheme is to be explored more in future work, with potential applications including voting systems, anonymous currency, and privacy-friendly KYC systems. -4. _[Threshold issuance](https://en.wikipedia.org/wiki/Threshold_cryptosystem)_ - allows signature generation to be split up across multiple nodes and decentralized, so that either all signers need to sign (_n of n_ where _n_ is the number of signers) or only a threshold number of signers need to sign a message (_t of n_ where _t_ is the threshold value). +4. _[Threshold issuance](https://en.wikipedia.org/wiki/Threshold_cryptosystem)_ - allows signature generation to be split up across multiple nodes and decentralised, so that either all signers need to sign (_n of n_ where _n_ is the number of signers) or only a threshold number of signers need to sign a message (_t of n_ where _t_ is the threshold value). -Taken together, these properties provide privacy for applications when it comes to generating and using signatures for cryptographic claims. If you compare it to existing tech, the closest analogy in conventional systems is a decentralized, privacy-preserving [JWT](https://jwt.io/). +Taken together, these properties provide privacy for applications when generating and using signatures for cryptographic claims. The closest analogy in conventional systems is a decentralised, privacy-preserving [JWT](https://jwt.io/). diff --git a/documentation/docs/pages/network/cryptography/zk-nym/double-spend-prot.mdx b/documentation/docs/pages/network/cryptography/zk-nym/double-spend-prot.mdx index 53b76db403..f66c20652f 100644 --- a/documentation/docs/pages/network/cryptography/zk-nym/double-spend-prot.mdx +++ b/documentation/docs/pages/network/cryptography/zk-nym/double-spend-prot.mdx @@ -10,7 +10,7 @@ Double spend protection in the context of zk-nym is a balancing act between spee ## Offline Approach: Pros & Cons The advantages of the offline approach are manifold: - Immediate access to the Nym network upon zk-nym submission, eliminating any delays in service provisioning until payments are deposited and verified as would occur in the online approach. -- Alleviates performance strain on ingress Gateways and Quorum members, serving as a more efficient method compared to the online counterpart. By moving computationally intense work to the Quorum, this means that Gateway nodes are able to be run on less powerful machines, meaning more operators can more easily run them (and cover their costs) and thus increase the overall number and spread of Gateways around the globe. +- Reduces load on ingress Gateways and Quorum members compared to the online approach. Moving the compute-heavy work to the Quorum means Gateway nodes can run on less capable machines, so more operators can run them (and cover their costs), increasing the overall number and spread of Gateways around the globe. - Moreover, the offline approach can circumvent the potential issue of overwhelming the blockchain with the serial numbers of spent coins. However, the offline approach introduces certain limitations. @@ -18,10 +18,10 @@ However, the offline approach introduces certain limitations. - Any potential repercussions against double spenders can only be implemented once the user requests a new credential for their zk-nym Generator (aka they have to 'top up' and buy more bandwidth allowance), assuming they haven't altered their identifier (the Bech32 address). An exploitable scenario arises from these limitations: -- A malicious user purchases bandwidth and aggregates a valid zk-nym credential in the standard way, worth $10 of crypto/fiat. Subsequently, the malicious user proceeds to sell the credential to 100 users for $1 each, allowing each user to generate zk-nym tickets of 100MB from this **valid** credential. Under the offline approach, entry nodes forego double-spending checks; so long as the clients all used different ingress Gateways, all 100 users could access the network without obtaining a subscription. As bandwidth consumption is tracked locally between client and ingress node, and each zk-nym ticket is rerandomised, there is no way that ingress Gateways would know that the zk-credential used by the client has been shared with other parties. This loophole highlights the need for stringent measures to counter such potential abuses within the system, without creating either speed bottlenecks (in the case of the Online model) or impacting the anonymity of the system. We can, however, mitigate this problem without doing either of these things. +- A malicious user purchases bandwidth and aggregates a valid zk-nym credential in the standard way, worth $10 of crypto/fiat. The malicious user then sells the credential to 100 users for $1 each, allowing each user to generate zk-nym tickets of 100MB from this **valid** credential. Under the offline approach, entry nodes skip double-spending checks; so long as the clients all used different ingress Gateways, all 100 users could access the network without obtaining a subscription. As bandwidth consumption is tracked locally between client and ingress node, and each zk-nym ticket is rerandomised, there is no way that ingress Gateways would know that the zk-credential used by the client has been shared with other parties. This loophole calls for measures to counter such abuses without creating either speed bottlenecks (as in the Online model) or harming the anonymity of the system. We can mitigate this problem without doing either. ## Solution to Offline Double Spending -To efficiently prevent the fraudulent use of tickets within the Nym network, a two-tiered solution is in place that combines (1) the immediate detection of double-spending attempts at the level of individuals ingress Gateways and (2) subsequent identification and blacklisting of offending clients at the Quorum level. +To prevent fraudulent use of tickets within the Nym network, a two-tiered solution combines (1) immediate detection of double-spending attempts at individual ingress Gateways and (2) subsequent identification and blacklisting of offending clients at the Quorum level. ### Entry Node Implementation: Real-Time Ticket Validation Each spent zk-nym ticket contains as an attribute a unique serial number, which is revealed in plaintext to the respective ingress Gateway. Each Gateway has a copy of a [Bloom Filter](https://www.geeksforgeeks.org/bloom-filters-introduction-and-python-implementation/) - on receiving a ticket, it will check against its copy of a local database to check whether this serial number has already been seen. If so, it rejects the ticket as being double-spent and the client's connection request is rejected. If not, it will add the serial number to its local DB. @@ -30,7 +30,7 @@ Each spent zk-nym ticket contains as an attribute a unique serial number, which Each Gateway will periodically share their serial numbers with the Quorum and refresh their copy of the Bloom Filters from the Quorum, in order to refresh the global list shared by all ingress Gateways and the Quorum. See the step below for more on this. -> Crucially, ingress Gateways refrain from extensive computations to identify the original ticket owner, and avoids broadcasting information about the double-spending attempt to other ingress Gateways. The entry node is also not involved in any global blacklisting process of the clients. The sole purpose of this check is to swiftly identify any attempts at double-spending and add the seen ticket's serial number to the local DB cache. +> Crucially, ingress Gateways do not perform extensive computations to identify the original ticket owner, and do not broadcast information about the double-spending attempt to other ingress Gateways. The entry node is also not involved in any global blacklisting of clients. The sole purpose of this check is to quickly identify double-spending attempts and add the seen ticket's serial number to the local DB cache. ### Nym-API Implementation: Blacklisting and Penalties for Double-Spenders All Gateways periodically forward the collected tickets to the Quorum, enabling them to pinpoint and blacklist any clients who double spend. Upon receiving the tickets, the Quorum appends all the incoming serial numbers to the global list of spend zk-nym serial numbers and proceed with the identification process for any malicious users engaging in double-spending. diff --git a/documentation/docs/pages/network/cryptography/zk-nym/rerandomise.mdx b/documentation/docs/pages/network/cryptography/zk-nym/rerandomise.mdx index 797bc02d6b..da4a505e41 100644 --- a/documentation/docs/pages/network/cryptography/zk-nym/rerandomise.mdx +++ b/documentation/docs/pages/network/cryptography/zk-nym/rerandomise.mdx @@ -9,9 +9,9 @@ Each ticket will not be valid for the entire amount of data that the ticketbook ## Why a 'ticketbook', not individual 'tickets', and why not spend them all at once? -This is to account for the need for a client to change their ingress Gateway, either because the Gateway itself has gone down / is not offering the required bandwidth, or because a user might simply want to split their traffic across multiple Gateways for extra privacy. +This is to account for the need for a client to change their ingress Gateway, either because the Gateway itself has gone down or is not offering the required bandwidth, or because a user might want to split their traffic across multiple Gateways for extra privacy. -Clients are therefore not tied to a particular Gateway they have spent their entire subscription with. If an ingress Gateway goes down, or the client simply wants to use a different one, remaining tickets can be spent with any other Gateway. +Clients are therefore not tied to a particular Gateway they have spent their entire subscription with. If an ingress Gateway goes down, or the client wants to use a different one, remaining tickets can be spent with any other Gateway. Going back to the `nym-cli` tool to illustrate this; we can generate multiple unlinkable tickets from a single ticketbook aggregated from PSCs: diff --git a/documentation/docs/pages/network/cryptography/zk-nym/unlinkability.mdx b/documentation/docs/pages/network/cryptography/zk-nym/unlinkability.mdx index 3714a64255..2454ef4a3a 100644 --- a/documentation/docs/pages/network/cryptography/zk-nym/unlinkability.mdx +++ b/documentation/docs/pages/network/cryptography/zk-nym/unlinkability.mdx @@ -2,13 +2,13 @@ import { Callout } from 'nextra/components' # Unlinkability -Each time a credential is requested by an ingress Gateway to prove that a client has purchased data to send through the Mixnet the Requester's device will produce a ticket. This is a rerandomised value that is able to be verified as being legitimate (in that it was created by a valid root ticketbook) but **not linked to any other tickets**, either previously generated or to be generated in the future. This feature also allows for a single ticketbook to allow access to be split across multiple ingress Gateways / connections and [incrementally spent](./rerandomise) over time. +Each time a credential is requested by an ingress Gateway to prove that a client has purchased data to send through the Mixnet, the Requester's device produces a ticket. This is a rerandomised value that can be verified as legitimate (in that it was created by a valid root ticketbook) but is **not linked to any other tickets**, either previously generated or to be generated in the future. This also allows a single ticketbook to be split across multiple ingress Gateways or connections and [incrementally spent](./rerandomise) over time. The functionality included in the following code block examples were added to the [nym-cli tool](/developers/tools/nym-cli) for illustrative purposes only: this is not necessarily how credentials will be accessed in the future. -The numbers used in this high level overview are for illustration purposes only. The figures used in production will potentially vary. Note that individual zkNym sizes will be uniform across the Network. +The numbers used in this high level overview are for illustration only. The figures used in production may vary. Individual zkNym sizes are uniform across the Network. @@ -23,7 +23,7 @@ PAYMENT FOR TICKET 3: VfZAuVRRHekQYMvFevNAZmPPuwMAfEhTBY8TXatBysbrNXAg8euEGPpJvdbhNfQSznBb9nRSeBUSVoNTToSA6Uj5dXmJ7oE2rCB439DarLMWHWYfQNhw6yhWJhcg6bt7ebBYTs3vVeQgSB5kYuifzJF4QQmK6uJyTNPvpV1J6V8M32PBkGT3JpVB3GUGZiksETf7TaF9wAhMo2QAMxw5ZvaQVve5ea7Mane6cfb2Gx69SRff5zDfEQvKqKnyyZje4SGZgWUeHWVLhRjg4KMTJ3JcsHxEqj2k5qeGeyBbgzcuEtCpYvaytsz7nuZGJsT4Z87gB5Zq4NGuDmekuN977eRJvua2dASNWeHiAzVyvnS7ARN5cdUjjYKYiWgHaYrHGsv26WTDeiu4U3sdJMrLHGFY5ihX7f8sTZqD6Wx5AWjQNbEtKaVHymDogfLcwGCC42gQ2yhKfPUaWJ8H4yMB65YBDXGjATaUzcDmJcZKx8g31j2uTVNSFUesd5CRNEEcTNW7cSFFCishCD3T4eV9SuyZyEXAZ48pazPzc1BysBNHEXQNUEtEAZTKmpghC2pihhfDub6LnMJPo9DDdhCULCbcWbGAPc1vPekPaWvk7wrUTGwp5xoNUhQLW3MeJzMvrMSsqLdursCKB4h4Tk272WCStCPQwAKMYoxjWvMzxoUTTWCkhLKHruMtsehRnai4vhu13jbui6ji1F389gfazm4ctth2s4Yw3H3SaPtRETBfZNvZ7n5UV1MD6Q3qin92gT65iqXEi4zRN3woYcK6ZehiSvgUksdEFAUSxNMgNXKtHEYDS6kA37tn5JdBa2Ex2jLudFfhg6JBM226ZKyj65o6feYPgbJAR3jMCmQRHe6DSFb4aH895EowNMjfGUhwhmnbYB1djp7iFXxPP7575NAerhxEQ1WFnxTfoX7pu1Vc9YZb5priCAVbATCaDkECJsdedM45Vx96Jc6E5NWqD98RhMsPimVJkSfYJmRxH9qugica6WonFFb2YLvXYyhoBA1VHBcRqZJ5KHitS5AegYSoYprUfubMzcYo2hGVEQkGKAsFq6jZgCsbJoGLXt3No317vcowB5f3hqT9FjASHAzW2j8uJ9RRzX7XtrPhArwx4EyPgYzrvgG7xcenoSgQt8poa7aYky56eZTKHVUZgUEt6St32MjcivMvmNdWiAHHDc2ZxzTJHgeuCckX7n19vQ3XNLuXv9oGKNNCi8kHnT4tUnnGXNAWXWuyBgZKWUL8u3y41iW6dLYK3Pw5zfpKZTrq3q3bTLJRN5LnnUuFVnWsC3SNqa6VAAvhTGR9PzxLk8C6HeLP2AsYPpqeQwbaL3Ks6tvPdob3tQPWRBGL4uiKtNZ23tRYZGZLYFWZK7psRSZg5AETejKxztVzAuYovpVUiDq71o331tjqWWV1SzWT13Rd1uwz6nHtsjgao2863YaizKARcYr1j9MKtNfDs483yho6i7tbCRR9M4CPLqdiKEaRyVC1FP4F3sejA6nZTuAA35JWUzX6BBj7wgdypMLdMmmtcCZm3bRrF3GvJJs67U8JWRc6dnoGUDaD7rUu ``` -Now lets generate another ticket to spend either topping up once the previous one's data allowance has been used, or with another Gateway. Notice that the `ticket-index` is the same: this is generated from the same aggregated credential as the one above! +Generating another ticket, to spend either when topping up after the previous one's data allowance has been used or with another Gateway. The `ticket-index` is the same: this is generated from the same aggregated credential as the one above. ```sh ❯ ./nym-cli ecash generate-ticket --credential-storage storage.db --provider 6qidVK21zpHD298jdDa1RRpbRozP29ENVyqcSbm6hQrG --ticket-index=3 @@ -36,4 +36,4 @@ PAYMENT FOR TICKET 3: Vev3SmwWtH5vbnejX5Zzc1EcxXAgveqHpKNN8arxXaWLhFcEpdcZ6n7qr3NrQUNURWsK2AsUiX8aSiGSjMPEY3iDE3aDYnjYERVow8RKUmQiYSKvz7v9cEJxt97JAHBfu9WYNHXTnLFSJwWuFtBdzY5dzPdzGckFenGCysa1ZBHGADHChDVXKoPHXxpn5qyJxmi48coUQDptR64QgkCeQ8RRZ396Lxw2NKFSjqavCMMDVm3g1rW7cYyPanBhkoAUzPU9KXX1rtmhD6F9gV89mGZ8fm7ByDuKuYU28seLQ7GkVKkhNeRW9XxbjSiyscTnMUzJ24R5VbSdr141BaquUHezdUTzmA2EjAtcyyiVrCMV13cc96CRbMXENP2soUzckFnh1qPnrfKCvX4JYkztq7UgPT2mZEnSTDW4C6Z2NVCNBPNLqUSYrU4id8Jzcp1mBxqJjdYcQ7P5fWJbT5Q9NAq44PCgfXpsUkNoj35QVQvKXKLb5oNGqnua5YC1WBPcENcpS7ZPWpk2hwe8VK4gNgnwQtWH2RPmWbvBREAV97vS1vKNHJyry9sD2PiMJGSmBnb1bKsGxR9UQN3YvRsdGHzyJHzAMTzxbFJBqMPmxjSHJR4UdwzhB81Ludu1RAffTvecWFxmWH5bNymCQjw3wey7Uequcxgyy8KAWYDzvHGwCZQbHQXghsYREiqquZWaa8hX3iTNBFUtEk8PRVT78MoFNdeBWNjsLr8zyZ5EGnf4kqmw3a91g5p5vywf6e3LgMu19VHjPSNtKMNXiatkPEVjsCuCppmV4sB7FsdKKWcMUSWLsdmrDBg9PStHr7NaJRzLL5E91gvysmB36Nob9cHeHSZj3wM4NVVjFfZeRqQf4bi7ahfXjeeBetgDpqx7JcbU6tTN4JpcGUpp7fp4MhTq7MeVQMLweGUVLqewKgAGzCvEmrK6dzLd3U1P9vkAAVZ3cCAKUywnHGxoxDeEfexP1g1EqJLtKNZVKPf7hSMWqGhoQ36K7y5GnyZ5YhQ7jcDME9orm5w4StoxoDdCPcjbakKG7UaTHuhd7tU1mUffXcEvVerkXoQK9SEaKvGks21RBhW86aHUzJWVbkiDzdaqjJWbmzLV8FKvNxNyzucoH2rq8LiHRMZfV1H3SkVSa4j2Ktw7ZGoQfdj8DgekxXSR2nHPfhybzKYXTBqFo2ACisxkjR4rXr9Xo6eYywQhQ1MP6aYgYCAXFGHPoFf7kx7Jns5sWvHRBdaMF65zeFF2m5NDuMWETtLgFfsyNgR84vfSqTfzj2gsUykRei7q9N4LKmiDwBALTAEcTvZpLtXBjc8JaB9PUeBw7DoSiSK376sGrQ9F6ZGTngXACNz1TbvYhtau4bDa6KC2Qn7wmoyrphpn7TtM1jdwGBxLcaEEWZKQHvWVfTyL2itjqnrcAZkxYdCj56oQYwpWfKQk3zJEUA6SYHqyJjaLNVK6u25j7969EWjdpTsJ8qSsZgXi3T7dQqiwintZbUUUKRq7egN1SGVnA6Wup91uKrYUWEWMqVu4g8ipmRsLD9iXHHr3yA21Cka7pqk1FxR9BFTAnkk1 ``` -These are both generated by the _same_ underlying ticketbook and used in a way that they cannot be tied to each other. An ingress Gateway might (for instance) get 100 connection requests from 100 Nym clients, each validated with a ticket. It has no way of knowing whether these are all from the same single subscription, or 100 different ones. +These are both generated by the same underlying ticketbook and used in a way that they cannot be tied to each other. An ingress Gateway might (for instance) get 100 connection requests from 100 Nym clients, each validated with a ticket. It has no way of knowing whether these are all from the same single subscription, or 100 different ones. diff --git a/documentation/docs/pages/network/cryptography/zk-nym/zk-nym-overview.mdx b/documentation/docs/pages/network/cryptography/zk-nym/zk-nym-overview.mdx index 793c5a9281..da4b3cdd35 100644 --- a/documentation/docs/pages/network/cryptography/zk-nym/zk-nym-overview.mdx +++ b/documentation/docs/pages/network/cryptography/zk-nym/zk-nym-overview.mdx @@ -3,7 +3,7 @@ import { Callout } from 'nextra/components' # Generating and using zk-nym anonymous credentials - zk-nyms are already used in production by [NymVPN](https://nymvpn.com) to unlink subscription payments from network activity. The entire credential lifecycle described on this page (key generation, issuance, spending) happens transparently within the NymVPN application. SDK integrations currently connect to the Mixnet without requiring credentials. + zk-nyms are already used in production by [NymVPN](https://nymvpn.com) to unlink subscription payments from network activity. The entire credential lifecycle described on this page (key generation, issuance, spending) happens inside the NymVPN application without user involvement. SDK integrations currently connect to the Mixnet without requiring credentials. Generation of zk-nyms involves the following actors / pieces of infrastructure: @@ -16,13 +16,13 @@ Generation happens in 3 distinct stages: - Issue credential - Generate unlinkable zk-nyms for Nym Network access -From the Requester's perspective this happens transparently, producing an unlinkable, rerandomisable anonymous proof-of-payment credential (a zk-nym) that grants Mixnet access without linking usage to payment information. A single credential can be split into multiple smaller zk-nyms, so a Requester purchases bandwidth in bulk and spends it incrementally across different ingress Gateways as needed. +From the Requester's perspective this happens without user involvement, producing an unlinkable, rerandomisable anonymous proof-of-payment credential (a zk-nym) that grants Mixnet access without linking usage to payment information. A single credential can be split into multiple smaller zk-nyms, so a Requester purchases bandwidth in bulk and spends it incrementally across different ingress Gateways as needed. ## Key Generation & Payment -- First, a Cosmos [Bech32 address](https://docs.cosmos.network/sdk/latest/guides/reference/bech32#performance-address-caching) is created for the Requester. This is used to identify themselves when interacting with the OrderAPI via signed authentication tokens. **This is the only identity that the OrderAPI is able to see, and is not able to link this to the zk-nyms that will be generated.** This identity never leaves the Requester's device and there is no email or any personal details needed for signup. If a Requester is simply 'topping up' their subscription, the creation of the address is skipped as it already exists. +- First, a Cosmos [Bech32 address](https://docs.cosmos.network/sdk/latest/guides/reference/bech32#performance-address-caching) is created for the Requester. This is used to identify themselves when interacting with the OrderAPI via signed authentication tokens. **This is the only identity the OrderAPI sees, and it cannot link this to the zk-nyms that will be generated.** This identity never leaves the Requester's device and there is no email or personal details needed for signup. If a Requester is 'topping up' their subscription, the creation of the address is skipped as it already exists. - The Requester also generates an ed25519 keypair: this is used to identify and authenticate them in the case of using zk-nyms across several devices as an individual user. However, **this is never used in the clear**: these keys are used as private attribute values within generated credentials which are verified via zero-knowledge and not publicly exposed. -- The Requester can then interact with various payment backends to pay for their zk-nyms with crypto, fiat options, or natively with NYM tokens. +- The Requester can then interact with payment backends to pay for their zk-nyms with crypto, fiat options, or natively with NYM tokens. - Payment options will trigger the OrderAPI. This will: - Create a swap for `` to `NYM` tokens. - Deposit these tokens with the NymAPI Quorum via a CosmWasm smart contract deployed on the Nyx blockchain. diff --git a/documentation/docs/pages/network/dvpn-mode.md b/documentation/docs/pages/network/dvpn-mode.md index 52454d84ce..e93c8cc70b 100644 --- a/documentation/docs/pages/network/dvpn-mode.md +++ b/documentation/docs/pages/network/dvpn-mode.md @@ -1,6 +1,6 @@ --- title: "dVPN Mode" -description: "How Nym's decentralized VPN mode routes traffic through two independent gateways, splitting trust so no single operator sees both your identity and destination." +description: "How Nym's decentralised VPN mode routes traffic through two independent gateways, splitting trust so no single operator sees both your identity and destination." schemaType: "TechArticle" section: "Network" lastUpdated: "2026-03-15" @@ -8,7 +8,7 @@ lastUpdated: "2026-03-15" # dVPN Mode -dVPN mode is a 2-hop decentralized VPN available through [NymVPN](https://nymvpn.com). Traffic is routed through two independent gateways rather than a single VPN provider's server, so no single operator ever sees both who you are and what you're doing. +dVPN mode is a 2-hop decentralised VPN available through [NymVPN](https://nymvpn.com). Traffic is routed through two independent gateways rather than a single VPN provider's server, so no single operator ever sees both who you are and what you're doing. ## How it works diff --git a/documentation/docs/pages/network/dvpn-mode/protocol.mdx b/documentation/docs/pages/network/dvpn-mode/protocol.mdx index 92cd9f9bf2..047137cb75 100644 --- a/documentation/docs/pages/network/dvpn-mode/protocol.mdx +++ b/documentation/docs/pages/network/dvpn-mode/protocol.mdx @@ -1,6 +1,6 @@ --- title: "dVPN Protocol Stack and Encryption" -description: "Technical details of Nym dVPN mode's protocol layers: nested WireGuard tunnels, split-knowledge architecture, and packet format tradeoffs." +description: "Technical details of Nym dVPN mode's protocol layers: nested WireGuard tunnels, split-knowledge architecture, and packet format trade-offs." schemaType: "TechArticle" section: "Network" lastUpdated: "2026-04-07" @@ -8,11 +8,9 @@ lastUpdated: "2026-04-07" # dVPN Protocol -import { Callout } from 'nextra/components' +import { LewesPending } from '../../../components/lewes-pending' - -Cryptographic details on this page will be updated for the Lewes Protocol release. For the current algorithm overview, see the [Nym Trust Center: Cryptography](https://nym.com/trust-center/cryptography). - + This page covers the technical details of dVPN mode's protocol stack and encryption. @@ -40,7 +38,7 @@ Both tunnels use standard WireGuard cryptography: Curve25519 for key exchange, C ## Packet format -dVPN mode uses standard WireGuard packet framing: packets are not padded to a uniform size. Packet sizes may vary and could in principle leak information about content types (video streams have different size patterns than text messages). This is a deliberate tradeoff: uniform padding would add overhead and reduce throughput, which conflicts with dVPN mode's goal of low-latency, high-throughput connectivity. For uniform packet sizes, use [mixnet mode](/network/mixnet-mode), which wraps all traffic in fixed-size Sphinx packets. +dVPN mode uses standard WireGuard packet framing: packets are not padded to a uniform size. Packet sizes may vary and could in principle leak information about content types (video streams have different size patterns than text messages). This is a deliberate trade-off: uniform padding would add overhead and reduce throughput, which conflicts with dVPN mode's goal of low-latency, high-throughput connectivity. For uniform packet sizes, use [mixnet mode](/network/mixnet-mode), which wraps all traffic in fixed-size Sphinx packets. ## Connection lifecycle @@ -60,4 +58,6 @@ Replay protection comes from WireGuard's counter-based mechanism and from zk-nym dVPN mode shares infrastructure with mixnet mode. Both use the same Entry and Exit Gateways and the same credential system. The difference is in how traffic is handled: mixnet mode routes through three additional Mix Node layers with delays and cover traffic using fixed-size [Sphinx packets](/network/cryptography/sphinx), while dVPN mode routes directly between gateways using WireGuard. The two modes are distinguishable at the protocol level due to their different packet formats and traffic patterns. +In anonymous (5-hop) mode, NymVPN routes traffic through the full mixnet to the Exit Gateway's [IP Packet Router](/network/infrastructure/exit-services#ip-packet-router), which tunnels raw IP packets to the internet. See [Exit Gateway Services](/network/infrastructure/exit-services) for how the IPR and Network Requester work. + This shared infrastructure means improvements to Gateways and credentials benefit both modes. diff --git a/documentation/docs/pages/network/index.md b/documentation/docs/pages/network/index.md index 102056f7e9..ee3fb02e4d 100644 --- a/documentation/docs/pages/network/index.md +++ b/documentation/docs/pages/network/index.md @@ -8,7 +8,7 @@ lastUpdated: "2026-02-11" # The Nym Network -The Nym Network is decentralized privacy infrastructure that protects against **network-level** surveillance. Unlike tools that focus on encrypting message content, Nym protects the metadata surrounding communication: who talks to whom, when, how often, and how much. This metadata is sufficient for observers to map relationships and build behavioural profiles even without access to any message content. See [The Privacy Problem](/network/overview/privacy-problem) for a fuller treatment. +The Nym Network is decentralised privacy infrastructure that protects against **network-level** surveillance. Unlike tools that focus on encrypting message content, Nym protects the metadata surrounding communication: who talks to whom, when, how often, and how much. This metadata is sufficient for observers to map relationships and build behavioural profiles even without access to any message content. See [The Privacy Problem](/network/overview/privacy-problem) for a fuller treatment. Nym offers two operating modes with different privacy/performance trade-offs, both available through [NymVPN](https://nymvpn.com). Developers can also integrate Mixnet mode directly via the [Nym SDKs](/developers). See [Choosing a Mode](/network/overview/choosing-a-mode) for guidance on which fits a given threat model. @@ -26,7 +26,7 @@ The [Nym SDKs](/developers) allow developers to embed mixnet functionality direc ## Paying for privacy without losing it -A fundamental weakness of traditional VPNs is that payment records can deanonymize users, since most providers link sessions to account IDs. Nym addresses this with **zk-nyms**: zero-knowledge anonymous credentials that prove payment without revealing any other information. Each credential covers a small chunk of bandwidth and is unlinkable to any other. +A fundamental weakness of traditional VPNs is that payment records can deanonymise users, since most providers link sessions to account IDs. Nym addresses this with **zk-nyms**: zero-knowledge anonymous credentials that prove payment without revealing any other information. Each credential covers a small chunk of bandwidth and is unlinkable to any other. When you pay for NymVPN, your payment is converted into a credential that can be split and re-randomized. Each Gateway connection uses a fresh, unlinkable proof; the Gateway verifies that you have paid without learning who you are. Your subscription cannot be linked to your network activity, even by infrastructure operators. diff --git a/documentation/docs/pages/network/infrastructure.md b/documentation/docs/pages/network/infrastructure.md index 479cf22027..c44e7cabe7 100644 --- a/documentation/docs/pages/network/infrastructure.md +++ b/documentation/docs/pages/network/infrastructure.md @@ -1,6 +1,6 @@ --- title: "Nym Network Infrastructure" -description: "Overview of the Nym Network's decentralized infrastructure: independently operated nodes coordinated by the Nyx blockchain for routing, key management, and credential issuance." +description: "Overview of the Nym Network's decentralised infrastructure: independently operated nodes coordinated by the Nyx blockchain for routing, key management, and credential issuance." schemaType: "TechArticle" section: "Network" lastUpdated: "2026-03-15" @@ -8,7 +8,7 @@ lastUpdated: "2026-03-15" # Infrastructure -The Nym Network runs on decentralized infrastructure: a set of independently operated nodes coordinated by the Nyx blockchain, where no single party controls routing, key management, or credential issuance. +The Nym Network runs on decentralised infrastructure: a set of independently operated nodes coordinated by the Nyx blockchain, where no single party controls routing, key management, or credential issuance. ## In this section diff --git a/documentation/docs/pages/network/infrastructure/_meta.json b/documentation/docs/pages/network/infrastructure/_meta.json index d8114f2306..5a13c8a080 100644 --- a/documentation/docs/pages/network/infrastructure/_meta.json +++ b/documentation/docs/pages/network/infrastructure/_meta.json @@ -1,4 +1,5 @@ { "nyx": "Nyx Blockchain", - "nym-nodes": "Nym Nodes" + "nym-nodes": "Nym Nodes", + "exit-services": "Exit Gateway Services" } diff --git a/documentation/docs/pages/network/infrastructure/exit-services.mdx b/documentation/docs/pages/network/infrastructure/exit-services.mdx new file mode 100644 index 0000000000..73b2c62c6a --- /dev/null +++ b/documentation/docs/pages/network/infrastructure/exit-services.mdx @@ -0,0 +1,84 @@ +--- +title: "Exit Gateway Services: Network Requester & IP Packet Router" +description: "The two proxy services running on Nym Exit Gateways: the Network Requester (SOCKS proxy) and the IP Packet Router (raw IP tunneling). How they work, what they see, and who uses them." +schemaType: "TechArticle" +section: "Network" +lastUpdated: "2026-04-15" +--- + +# Exit Gateway Services + +import { Callout } from 'nextra/components' + +Exit Gateways are where traffic leaves the Nym network and reaches the wider internet. Each Exit Gateway runs two distinct proxy services that handle different kinds of outbound traffic: + +- **Network Requester (NR)**, an application-layer SOCKS proxy +- **IP Packet Router (IPR)**, a raw IP tunnel with address allocation + +Both services run on every Exit Gateway. Which one handles your traffic depends on how you connect. + +## Network Requester + +The Network Requester is a SOCKS4/4a/5 proxy. Clients send SOCKS-formatted requests through the mixnet, and the NR makes the corresponding connection on their behalf: resolving hostnames, opening TCP connections, and relaying data. + +```text +Client → Entry Gateway → Mixnodes1..3 → Exit Gateway (NR) → SOCKS connect → destination + ← relay response ← +``` + +Because it operates at the application layer, the NR: +- Resolves DNS on behalf of the client (the client sends hostnames, not IPs) +- Opens individual TCP connections per SOCKS request +- Can enforce allow/deny lists on destination hosts and ports +- Sees the destination hostname and port, but not the contents if TLS is used + +**Used by:** the [SDK's SOCKS client](/developers/rust/mixnet), [standalone SOCKS5 client](/developers/clients/socks5), and [mixFetch](/developers/mix-fetch) (which wraps SOCKS requests in a browser-friendly `fetch` API). + +## IP Packet Router + +The IP Packet Router operates at the IP layer. Instead of proxying individual connections, it allocates a virtual IP address to the client and routes raw IP packets between the client and the internet, functioning as a tunnel endpoint. + +```text +Client → Entry Gateway → Mixnodes1..3 → Exit Gateway (IPR) → raw IP packets → destination + ← raw IP packets ← +``` + +On connection, the IPR: +1. Allocates an IPv4/IPv6 address pair to the client +2. Accepts raw IP packets (TCP, UDP, or any IP protocol) from the client via the mixnet +3. Sends them to the internet from the gateway's own IP address +4. Routes response packets back through the mixnet to the client + +Because it operates at the IP layer, the IPR: +- Does not resolve DNS; the client handles its own DNS (either via clearnet or by sending DNS queries as UDP packets through the tunnel) +- Handles any IP protocol: TCP, UDP, ICMP, etc. +- Sees raw IP packets, including destination IPs and ports +- Does not see contents if the client uses TLS or another encryption layer + + +In both services, traffic between the Exit Gateway and the destination travels over the public internet, exactly as it would from any other server. The mixnet protects sender anonymity (the destination sees the gateway's IP, not yours), but does not encrypt the payload past the gateway. Use TLS or another application-layer cipher to protect payload confidentiality, just as you would on a direct connection. + + +**Used by:** [NymVPN anonymous mode](/network/dvpn-mode/protocol) (5-hop mixnet routing to the IPR), and [`smolmix`](/developers/smolmix) (programmatic `TcpStream`/`UdpSocket` access to the IPR via the Rust SDK). + +## Comparison + +| | Network Requester | IP Packet Router | +|---|---|---| +| **Layer** | Application (SOCKS) | IP (raw packets) | +| **Protocols** | TCP only | TCP, UDP, any IP protocol | +| **DNS** | Resolved by the NR | Client resolves its own | +| **Client gets** | Proxied connections | An allocated IP address | +| **Connection model** | Per-request | Persistent tunnel | +| **Used by** | SDK SOCKS client, mixFetch | NymVPN (anonymous mode), smolmix | + +## Trust model + +Both services share the same fundamental trust property: **the Exit Gateway can see destinations but not senders.** The mixnet's layered encryption ensures that the Exit Gateway cannot determine who sent a given packet; it only knows where it's going. + +Specifically, the Exit Gateway: +- **Can see:** destination IP/hostname, destination port, unencrypted payload content, traffic volume and timing at the exit hop +- **Cannot see:** the sender's IP address, the sender's Nym address, which Entry Gateway the traffic entered through +- **Cannot determine:** the linkage between different requests from the same sender (unless the payload itself contains identifying information) + +The sender's identity is protected by the mixnet's 5-hop routing, Sphinx encryption, cover traffic, and packet mixing. The Exit Gateway is the last hop: it decrypts the final Sphinx layer and sees the destination, but the chain of Mix Nodes between Entry and Exit has destroyed any timing or ordering correlation. diff --git a/documentation/docs/pages/network/infrastructure/nym-nodes.mdx b/documentation/docs/pages/network/infrastructure/nym-nodes.mdx index 59cfbe2bf5..0228b8083d 100644 --- a/documentation/docs/pages/network/infrastructure/nym-nodes.mdx +++ b/documentation/docs/pages/network/infrastructure/nym-nodes.mdx @@ -16,11 +16,11 @@ To run a node, see the [Operator Documentation](/operators/introduction). **Mix Nodes** form the three mixing layers that provide core privacy. They receive Sphinx packets, remove one encryption layer, verify integrity, apply a random delay, and forward to the next hop. Mix Nodes cannot determine their position in the route and cannot link incoming packets to outgoing packets. -**Exit Gateways** handle traffic leaving the mixnet. They communicate with external internet services on behalf of users and return responses through the network. Exit Gateways can see destination addresses but cannot identify the original sender. +**Exit Gateways** handle traffic leaving the mixnet. They run two proxy services: the [Network Requester](/network/infrastructure/exit-services#network-requester) (a SOCKS proxy for application-layer requests) and the [IP Packet Router](/network/infrastructure/exit-services#ip-packet-router) (a raw IP tunnel used by NymVPN and smolmix). Exit Gateways can see destination addresses but cannot identify the original sender. See [Exit Gateway Services](/network/infrastructure/exit-services) for details. ## Unified binary -The various components were originally separate binaries but have been consolidated into a single `nym-node` binary where the role is specified at runtime. This simplifies operation and makes configuration consistent across roles. +These components were originally separate binaries but have been consolidated into a single `nym-node` binary where the role is specified at runtime. This simplifies operation and makes configuration consistent across roles. In the future, nodes will automatically switch modes based on network conditions. Operators won't need to manually set whether a node is a Gateway or Mix Node; the network will assign modes dynamically each epoch. diff --git a/documentation/docs/pages/network/mixnet-mode.mdx b/documentation/docs/pages/network/mixnet-mode.mdx index c103ea7828..c8070f361b 100644 --- a/documentation/docs/pages/network/mixnet-mode.mdx +++ b/documentation/docs/pages/network/mixnet-mode.mdx @@ -8,7 +8,7 @@ lastUpdated: "2026-03-15" # Mixnet Mode -import { Callout } from 'nextra/components' +import { LewesPending } from '../../components/lewes-pending' Mixnet mode routes traffic through 5 hops: an Entry Gateway, three layers of Mix Nodes, and an Exit Gateway. Each mixing layer adds random delays, reorders packets, and injects cover traffic. Available through [NymVPN](https://nymvpn.com) and the [Nym SDKs](/developers). @@ -32,9 +32,7 @@ Each Mix Node strips one layer of [Sphinx](/network/cryptography/sphinx) encrypt The three mixing layers add additional latency. This is acceptable for messaging, file transfers, and most API calls, but unsuitable for real-time applications like video calling. For those, [dVPN mode](/network/dvpn-mode) is more appropriate. - -Updated latency measurements will be published after the Lewes Protocol release. - + ## Further reading diff --git a/documentation/docs/pages/network/mixnet-mode/anonymous-replies.mdx b/documentation/docs/pages/network/mixnet-mode/anonymous-replies.mdx index 2988841e92..209a23e30d 100644 --- a/documentation/docs/pages/network/mixnet-mode/anonymous-replies.mdx +++ b/documentation/docs/pages/network/mixnet-mode/anonymous-replies.mdx @@ -28,7 +28,7 @@ SURB validity is tied to key rotation. Node keys rotate on an odd/even schedule ## SURB replenishment -If Bob's reply is larger than the available SURBs can carry, he uses one SURB to request more. Alice receives the request, generates additional SURBs, and sends them to Bob. This adds round-trip latency but ensures conversations can continue regardless of reply size. +If Bob's reply is larger than the available SURBs can carry, he uses one SURB to request more. Alice receives the request, generates additional SURBs, and sends them to Bob. This adds round-trip latency but lets conversations continue regardless of reply size. ```mermaid --- @@ -53,7 +53,7 @@ sequenceDiagram ## Sender tags -For sessions with multiple messages, Alice includes a randomly generated sender tag with her SURBs. This helps Bob organize SURBs from multiple conversations without revealing anything about Alice's identity; the tag is random and unlinkable to her address. +For sessions with multiple messages, Alice includes a randomly generated sender tag with her SURBs. This helps Bob organise SURBs from multiple conversations without revealing anything about Alice's identity; the tag is random and unlinkable to her address. ## Security considerations diff --git a/documentation/docs/pages/network/mixnet-mode/cover-traffic.md b/documentation/docs/pages/network/mixnet-mode/cover-traffic.md index ecb33efc79..2ee27cfc2d 100644 --- a/documentation/docs/pages/network/mixnet-mode/cover-traffic.md +++ b/documentation/docs/pages/network/mixnet-mode/cover-traffic.md @@ -43,7 +43,7 @@ Mix nodes also generate their own cover traffic, ensuring minimum traffic levels Traffic follows a Poisson process with a configurable rate parameter. Inter-packet times are exponentially distributed: random, but with a known average rate. This distribution provides maximum entropy (uncertainty) for a given mean rate, which translates to optimal privacy properties. -## Tradeoffs +## Trade-offs More cover traffic provides better unobservability but uses more bandwidth and, when zk-nyms are enabled, more credential value. Less cover traffic reduces costs but may allow some inference about activity patterns. @@ -51,4 +51,4 @@ The default parameters balance privacy and resource usage. Applications with hei ## What cover traffic defeats -Cover traffic prevents volume analysis (how much you communicate), timing analysis (when you communicate), and behavioral profiling (your communication patterns over time). Combined with packet mixing, it ensures that even an adversary watching the entire network cannot learn about your communication behavior with currently known methods. +Cover traffic prevents volume analysis (how much you communicate), timing analysis (when you communicate), and behavioural profiling (your communication patterns over time). Combined with packet mixing, this means that even an adversary watching the entire network cannot learn about your communication behaviour with currently known methods. diff --git a/documentation/docs/pages/network/mixnet-mode/loopix.md b/documentation/docs/pages/network/mixnet-mode/loopix.md index 1aace31e4a..3369acb1d1 100644 --- a/documentation/docs/pages/network/mixnet-mode/loopix.md +++ b/documentation/docs/pages/network/mixnet-mode/loopix.md @@ -8,7 +8,7 @@ lastUpdated: "2026-03-15" # Loopix Design -The Nym mixnet is based on the [Loopix](https://arxiv.org/pdf/1703.00536) design, with modifications for decentralized operation and economic incentives. +The Nym mixnet is based on the [Loopix](https://arxiv.org/pdf/1703.00536) design, with modifications for decentralised operation and economic incentives. ## The insight @@ -38,7 +38,7 @@ Loop traffic ensures minimum anonymity even when few users are active, hides whe ## Nym's modifications -The Nym implementation extends Loopix in several ways: replacing the trusted directory server with the Nyx blockchain for decentralized topology management, incentivising node operation with NYM token rewards rather than relying on volunteers, and adding zk-nyms for privacy-preserving payment, which the original academic design did not address. +The Nym implementation extends Loopix in several ways: replacing the trusted directory server with the Nyx blockchain for decentralised topology management, incentivising node operation with NYM token rewards rather than relying on volunteers, and adding zk-nyms for privacy-preserving payment, which the original academic design did not address. ## Security guarantees diff --git a/documentation/docs/pages/network/mixnet-mode/mixing.mdx b/documentation/docs/pages/network/mixnet-mode/mixing.mdx index 1cf9890c73..f1ddd6b000 100644 --- a/documentation/docs/pages/network/mixnet-mode/mixing.mdx +++ b/documentation/docs/pages/network/mixnet-mode/mixing.mdx @@ -8,7 +8,7 @@ lastUpdated: "2026-03-15" # Packet Mixing -import { Callout } from 'nextra/components' +import { LewesPending } from '../../../components/lewes-pending' Packet mixing breaks timing correlations by adding random delays at each Mix Node. It's the core mechanism that prevents traffic analysis. @@ -50,9 +50,7 @@ With three Mix Node layers, each applying random delays, the overall effect is t These delays account for the additional latency of mixnet mode relative to dVPN mode. - -Updated latency measurements will be published after the Lewes Protocol release. - + ## Combined with cover traffic diff --git a/documentation/docs/pages/network/overview.md b/documentation/docs/pages/network/overview.md index 45f76d5005..99b43c974f 100644 --- a/documentation/docs/pages/network/overview.md +++ b/documentation/docs/pages/network/overview.md @@ -18,4 +18,4 @@ The Nym Network is a privacy infrastructure that protects metadata: not just mes ## Network Components -All traffic-routing infrastructure runs on [Nym Nodes](/network/infrastructure/nym-nodes), a single binary that operators configure to serve as an Entry Gateway, Mix Node, or Exit Gateway depending on their setup. Network coordination, token bonding, and the distributed credential system all live on the [Nyx blockchain](/network/infrastructure/nyx), a Cosmos SDK chain whose on-chain topology registry eliminates the need for a centralised directory server. +All traffic-routing infrastructure runs on [Nym Nodes](/network/infrastructure/nym-nodes), a single binary that operators configure to serve as an Entry Gateway, Mix Node, or Exit Gateway depending on their setup. Network coordination, token bonding, and the distributed credential system all live on the [Nyx blockchain](/network/infrastructure/nyx), a Cosmos SDK chain whose on-chain topology registry removes the need for a centralised directory server. diff --git a/documentation/docs/pages/network/overview/choosing-a-mode.md b/documentation/docs/pages/network/overview/choosing-a-mode.md index b29caa332a..9523b3c1aa 100644 --- a/documentation/docs/pages/network/overview/choosing-a-mode.md +++ b/documentation/docs/pages/network/overview/choosing-a-mode.md @@ -28,7 +28,7 @@ Both modes run on the same Nym infrastructure but defend against different threa - Latency matters: browsing, streaming, downloads, video calls - Your concern is ISPs, advertisers, and websites tracking you, not nation-state surveillance -- You want decentralized trust and payment privacy without the overhead of mixing +- You want decentralised trust and payment privacy without the overhead of mixing ## Use Mixnet mode when diff --git a/documentation/docs/pages/network/overview/comparisons.md b/documentation/docs/pages/network/overview/comparisons.md index 5fec66afbe..e27e503200 100644 --- a/documentation/docs/pages/network/overview/comparisons.md +++ b/documentation/docs/pages/network/overview/comparisons.md @@ -24,7 +24,7 @@ Nym's mixnet mode goes further by adding timing obfuscation and cover traffic, w Nym's mixnet addresses this by adding random delays at each Mix Node to break timing correlations, cover traffic so observers can't tell when real communication is occurring, per-packet routing rather than Tor's per-session circuits (so there's no long-lived path to observe), and a blockchain-based topology instead of Tor's centralised directory authority. -The tradeoff is latency: Tor is faster because it doesn't add mixing delays, so it may be a better fit for general browsing where timing protection isn't needed. Nym's mixnet is designed for threat models where the adversary can perform traffic analysis. +The trade-off is latency: Tor is faster because it doesn't add mixing delays, so it may be a better fit for general browsing where timing protection isn't needed. Nym's mixnet is designed for threat models where the adversary can perform traffic analysis. ## Nym vs I2P diff --git a/documentation/docs/pages/network/reference/acks.mdx b/documentation/docs/pages/network/reference/acks.mdx index b35a4be1be..1a4a490600 100644 --- a/documentation/docs/pages/network/reference/acks.mdx +++ b/documentation/docs/pages/network/reference/acks.mdx @@ -6,7 +6,7 @@ section: "Network" lastUpdated: "2026-03-15" --- -import { Callout } from 'nextra/components' +import { LewesPending } from '../../../components/lewes-pending' # Acknowledgements @@ -28,8 +28,6 @@ Acknowledgements operate hop-by-hop between adjacent nodes. They confirm that pa ## Implementation -This is handled entirely by the Nym binaries. Developers and operators don't need to implement or configure acknowledgements; the system handles packet loss transparently. +This is handled entirely by the Nym binaries. Developers and operators don't need to implement or configure acknowledgements; the system handles packet loss without any application involvement. - -**Lewes Protocol:** The upcoming Lewes release will introduce changes to how acknowledgements are handled. The current hop-by-hop ACK mechanism described above may be revised as part of broader protocol improvements. Details will be documented here once the changes are finalised. - + diff --git a/documentation/docs/pages/network/reference/epochs.md b/documentation/docs/pages/network/reference/epochs.md index 0c7e55d442..7375c6c7bd 100644 --- a/documentation/docs/pages/network/reference/epochs.md +++ b/documentation/docs/pages/network/reference/epochs.md @@ -1,6 +1,6 @@ --- title: "Epochs in the Nym Network" -description: "How epochs organize time in the Nym Network: reward distribution, topology reshuffling, SURB validity windows, and future automatic role assignment." +description: "How epochs organise time in the Nym Network: reward distribution, topology reshuffling, SURB validity windows, and future automatic role assignment." schemaType: "TechArticle" section: "Network" lastUpdated: "2026-03-15" @@ -8,11 +8,11 @@ lastUpdated: "2026-03-15" # Epochs -Time in the Nym Network is organized into epochs: discrete periods during which certain network operations occur. The current epoch length is one hour. +Time in the Nym Network is organised into epochs: discrete periods during which certain network operations occur. The current epoch length is one hour. ## What happens at epoch boundaries -**Reward distribution** calculates performance metrics for each node and distributes NYM token rewards based on routing reliability and uptime, ensuring that nodes successfully forwarding packets earn more than those with poor performance. +**Reward distribution** calculates performance metrics for each node and distributes NYM token rewards based on routing reliability and uptime, so that nodes successfully forwarding packets earn more than those with poor performance. **Topology rerandomization** shuffles the arrangement of nodes in each layer. This prevents long-term route prediction attacks and limits the damage from any compromised nodes. Nodes may also enter or leave the active set based on uptime monitoring and stake changes. diff --git a/documentation/docs/public/llms-full.txt b/documentation/docs/public/llms-full.txt index 4c38206c07..34147b560c 100644 --- a/documentation/docs/public/llms-full.txt +++ b/documentation/docs/public/llms-full.txt @@ -1,8 +1,8 @@ # Nym Documentation @version: 1.20.4 -@generated: 2026-03-26 -@pages: 150 +@generated: 2026-05-11 +@pages: 148 @source: https://github.com/nymtech/nym/tree/develop/documentation/docs --- @@ -13,51 +13,33 @@ url: https://nym.com/docs/network # The Nym Network -The Nym Network is decentralized privacy infrastructure that protects against **network-level** surveillance. It does this by protecting message *metadata*—who is communicating with whom, when, how often, and how much—from being able to be captured. +The Nym Network is decentralized privacy infrastructure that protects against **network-level** surveillance. Unlike tools that focus on encrypting message content, Nym protects the metadata surrounding communication: who talks to whom, when, how often, and how much. This metadata is sufficient for observers to map relationships and build behavioural profiles even without access to any message content. See [The Privacy Problem](/network/overview/privacy-problem) for a fuller treatment. -## The problem with metadata - -When you send data across the internet, observers can see that communication has occurred in the form of the source and destination IP addresses of internet packets, the timing and frequency of transmissions, packet sizes, and other bits of information that over time can be used to build up inferences about the type of [device/browser you're using](https://browserleaks.com/ip), [your connection](https://browserleaks.com/tcp), and ultimately who you are. These observers include your ISP, internet infrastructure providers, governments, and large corporations. - -Even when sending encrypted content (e.g. using messaging apps like Signal or SimpleX, or encrypted email providers), metadata can identify users by allowing observers to build up inferences and build behavioral profiles. Advances in machine learning in recent years has made these attacks increasingly practical, and spawned an entire industry dedicated to the capture and analysis of internet traffic. - -## How Nym solves this - -Every person and usecase has a different threat model - journalists in highly adversarial environments might be happy to accept higher latency and lower throughput when their safety is on the line, whereas your average user might just want to be 'private enough' to not be leaking everything they do to an ISP, passive government surveillance, or a centralised VPN provider. - -As such, there are two 'modes' for sending traffic through Nym, each serving different needs. There are also two different ways to access the network: +Nym offers two operating modes with different privacy/performance trade-offs, both available through [NymVPN](https://nymvpn.com). Developers can also integrate Mixnet mode directly via the [Nym SDKs](/developers). See [Choosing a Mode](/network/overview/choosing-a-mode) for guidance on which fits a given threat model. ### NymVPN [NymVPN](https://nymvpn.com) is a subscription-based application that provides access to both modes: -- **dVPN mode** routes traffic through 2 hops using WireGuard with enhanced layer encryption—fast enough for browsing and streaming while still providing strong privacy against typical adversaries. -- **Mixnet mode** routes traffic through 5 hops with packet mixing, timing delays, and cover traffic, providing maximum privacy against sophisticated adversaries capable of observing the entire network. In the Mixnet, every packet is the same size, each hop only sees the next destination, packets are delayed and reordered to destroy timing patterns, and a constant stream of 'dummy' packets hides when real communication is occurring. +- **dVPN mode** routes traffic through 2 hops using WireGuard with enhanced layer encryption. Fast enough for browsing and streaming, with strong privacy against typical adversaries. +- **Mixnet mode** routes traffic through 5 hops with packet mixing, timing delays, and cover traffic. Every packet is the same size, each hop only sees the next destination, and a constant stream of dummy packets hides when real communication is occurring. Designed for privacy against adversaries capable of observing the entire network. Both modes use the same underlying infrastructure. ### Developer SDKs -Developers can integrate mixnet functionality directly into applications using the [Nym SDKs](/developers). This provides the same privacy guarantees as NymVPN's mixnet mode and is currently free for development and testing. The SDKs do **not** provide access to dVPN mode, which is currently specific to the NymVPN application. +The [Nym SDKs](/developers) allow developers to embed mixnet functionality directly into applications, with the same privacy guarantees as NymVPN's Mixnet mode. SDK usage is currently free for development and testing. The SDKs do **not** provide access to dVPN mode. ## Paying for privacy without losing it -A fundamental problem with VPNs and privacy services is that payment information can easily deanonymize users (e.g. most VPNs will link a user's session to their account ID). Nym solves this with **zk-nyms**—zero-knowledge anonymous credentials that allow you to prove you've paid for a subscription without revealing **anything else** about you. Each are used for small chunks of bandwidth, and are unlinkable to each other. +A fundamental weakness of traditional VPNs is that payment records can deanonymize users, since most providers link sessions to account IDs. Nym addresses this with **zk-nyms**: zero-knowledge anonymous credentials that prove payment without revealing any other information. Each credential covers a small chunk of bandwidth and is unlinkable to any other. -When you pay for NymVPN access, your payment is converted to a cryptographic credential that can be split and re-randomized. Each time you connect to a new Gateway node (for example, you switch which server you want your connection to be partially routed through), you present a fresh, unlinkable proof. Gateways verify payment validity without learning your identity, and **your subscription cannot be linked to your network activity, even by infrastructure operators**. +When you pay for NymVPN, your payment is converted into a credential that can be split and re-randomized. Each Gateway connection uses a fresh, unlinkable proof; the Gateway verifies that you have paid without learning who you are. Your subscription cannot be linked to your network activity, even by infrastructure operators. -## Documentation structure +## Further reading -This documentation covers the network architecture and protocols: -- [Overview](/network/overview): high-level concepts. -- [dVPN Mode](/network/dvpn-mode): more detail about the protocol and traffic flow of dVPN mode. -- [Mixnet Mode](/network/mixnet-mode): more detail about the protocol and traffic flow of Mixnet mode. -- [Cryptography](/network/cryptography): covers the underlying primitives (including zk-nyms). -- [Infrastructure](/network/infrastructure): blockchain and node architecture. -- [Reference](/network/reference): technical specifications. - -For building applications and integrating existing apps with the Mixnet, see the [Developer Documentation](/developers). - -If you wish to take part in the network as a Node Operator, see the [Operator Documentation](/operators/introduction). +- **Network architecture:** [Overview](/network/overview) · [dVPN Mode](/network/dvpn-mode) · [Mixnet Mode](/network/mixnet-mode) · [Cryptography](/network/cryptography) · [Infrastructure](/network/infrastructure) · [Reference](/network/reference) +- **Application development:** [Developer documentation](/developers) +- **Node operation:** [Operator documentation](/operators/introduction) --- title: Nym Network Architecture: How the Mixnet Works @@ -67,51 +49,33 @@ url: https://nym.com/docs/network # The Nym Network -The Nym Network is decentralized privacy infrastructure that protects against **network-level** surveillance. It does this by protecting message *metadata*—who is communicating with whom, when, how often, and how much—from being able to be captured. +The Nym Network is decentralized privacy infrastructure that protects against **network-level** surveillance. Unlike tools that focus on encrypting message content, Nym protects the metadata surrounding communication: who talks to whom, when, how often, and how much. This metadata is sufficient for observers to map relationships and build behavioural profiles even without access to any message content. See [The Privacy Problem](/network/overview/privacy-problem) for a fuller treatment. -## The problem with metadata - -When you send data across the internet, observers can see that communication has occurred in the form of the source and destination IP addresses of internet packets, the timing and frequency of transmissions, packet sizes, and other bits of information that over time can be used to build up inferences about the type of [device/browser you're using](https://browserleaks.com/ip), [your connection](https://browserleaks.com/tcp), and ultimately who you are. These observers include your ISP, internet infrastructure providers, governments, and large corporations. - -Even when sending encrypted content (e.g. using messaging apps like Signal or SimpleX, or encrypted email providers), metadata can identify users by allowing observers to build up inferences and build behavioral profiles. Advances in machine learning in recent years has made these attacks increasingly practical, and spawned an entire industry dedicated to the capture and analysis of internet traffic. - -## How Nym solves this - -Every person and usecase has a different threat model - journalists in highly adversarial environments might be happy to accept higher latency and lower throughput when their safety is on the line, whereas your average user might just want to be 'private enough' to not be leaking everything they do to an ISP, passive government surveillance, or a centralised VPN provider. - -As such, there are two 'modes' for sending traffic through Nym, each serving different needs. There are also two different ways to access the network: +Nym offers two operating modes with different privacy/performance trade-offs, both available through [NymVPN](https://nymvpn.com). Developers can also integrate Mixnet mode directly via the [Nym SDKs](/developers). See [Choosing a Mode](/network/overview/choosing-a-mode) for guidance on which fits a given threat model. ### NymVPN [NymVPN](https://nymvpn.com) is a subscription-based application that provides access to both modes: -- **dVPN mode** routes traffic through 2 hops using WireGuard with enhanced layer encryption—fast enough for browsing and streaming while still providing strong privacy against typical adversaries. -- **Mixnet mode** routes traffic through 5 hops with packet mixing, timing delays, and cover traffic, providing maximum privacy against sophisticated adversaries capable of observing the entire network. In the Mixnet, every packet is the same size, each hop only sees the next destination, packets are delayed and reordered to destroy timing patterns, and a constant stream of 'dummy' packets hides when real communication is occurring. +- **dVPN mode** routes traffic through 2 hops using WireGuard with enhanced layer encryption. Fast enough for browsing and streaming, with strong privacy against typical adversaries. +- **Mixnet mode** routes traffic through 5 hops with packet mixing, timing delays, and cover traffic. Every packet is the same size, each hop only sees the next destination, and a constant stream of dummy packets hides when real communication is occurring. Designed for privacy against adversaries capable of observing the entire network. Both modes use the same underlying infrastructure. ### Developer SDKs -Developers can integrate mixnet functionality directly into applications using the [Nym SDKs](/developers). This provides the same privacy guarantees as NymVPN's mixnet mode and is currently free for development and testing. The SDKs do **not** provide access to dVPN mode, which is currently specific to the NymVPN application. +The [Nym SDKs](/developers) allow developers to embed mixnet functionality directly into applications, with the same privacy guarantees as NymVPN's Mixnet mode. SDK usage is currently free for development and testing. The SDKs do **not** provide access to dVPN mode. ## Paying for privacy without losing it -A fundamental problem with VPNs and privacy services is that payment information can easily deanonymize users (e.g. most VPNs will link a user's session to their account ID). Nym solves this with **zk-nyms**—zero-knowledge anonymous credentials that allow you to prove you've paid for a subscription without revealing **anything else** about you. Each are used for small chunks of bandwidth, and are unlinkable to each other. +A fundamental weakness of traditional VPNs is that payment records can deanonymize users, since most providers link sessions to account IDs. Nym addresses this with **zk-nyms**: zero-knowledge anonymous credentials that prove payment without revealing any other information. Each credential covers a small chunk of bandwidth and is unlinkable to any other. -When you pay for NymVPN access, your payment is converted to a cryptographic credential that can be split and re-randomized. Each time you connect to a new Gateway node (for example, you switch which server you want your connection to be partially routed through), you present a fresh, unlinkable proof. Gateways verify payment validity without learning your identity, and **your subscription cannot be linked to your network activity, even by infrastructure operators**. +When you pay for NymVPN, your payment is converted into a credential that can be split and re-randomized. Each Gateway connection uses a fresh, unlinkable proof; the Gateway verifies that you have paid without learning who you are. Your subscription cannot be linked to your network activity, even by infrastructure operators. -## Documentation structure +## Further reading -This documentation covers the network architecture and protocols: -- [Overview](/network/overview): high-level concepts. -- [dVPN Mode](/network/dvpn-mode): more detail about the protocol and traffic flow of dVPN mode. -- [Mixnet Mode](/network/mixnet-mode): more detail about the protocol and traffic flow of Mixnet mode. -- [Cryptography](/network/cryptography): covers the underlying primitives (including zk-nyms). -- [Infrastructure](/network/infrastructure): blockchain and node architecture. -- [Reference](/network/reference): technical specifications. - -For building applications and integrating existing apps with the Mixnet, see the [Developer Documentation](/developers). - -If you wish to take part in the network as a Node Operator, see the [Operator Documentation](/operators/introduction). +- **Network architecture:** [Overview](/network/overview) · [dVPN Mode](/network/dvpn-mode) · [Mixnet Mode](/network/mixnet-mode) · [Cryptography](/network/cryptography) · [Infrastructure](/network/infrastructure) · [Reference](/network/reference) +- **Application development:** [Developer documentation](/developers) +- **Node operation:** [Operator documentation](/operators/introduction) --- title: Nym Network Overview @@ -121,15 +85,17 @@ url: https://nym.com/docs/network/overview # Overview -The Nym Network is a privacy infrastructure that protects metadata — not just message content, but who is talking to whom, when, and how often. This section explains what the network does, why it exists, and how it compares to other approaches. +The Nym Network is a privacy infrastructure that protects metadata: not just message content, but who is talking to whom, when, and how often. This section explains what the network does, why it exists, and how it compares to other approaches. ## In this section -- [The Privacy Problem](/network/overview/privacy-problem) — what metadata is, why it matters, and what adversary models Nym is designed against -- [Two Modes: dVPN & Mixnet](/network/overview/two-modes) — how the two modes differ in architecture and privacy guarantees -- [Choosing a Mode](/network/overview/choosing-a-mode) — guidance on which mode fits your use case -- [Network Components](/network/overview/network-components) — Entry Gateways, Mix Nodes, Exit Gateways, and the Nyx blockchain -- [Nym vs Other Systems](/network/overview/comparisons) — how Nym compares to VPNs, Tor, I2P, and E2EE +- [The Privacy Problem](/network/overview/privacy-problem): what metadata is, why it matters, and what adversary models Nym is designed against +- [Choosing a Mode](/network/overview/choosing-a-mode): how dVPN and Mixnet mode differ, and guidance on which fits your use case +- [Nym vs Other Systems](/network/overview/comparisons): how Nym compares to VPNs, Tor, I2P, and E2EE + +## Network Components + +All traffic-routing infrastructure runs on [Nym Nodes](/network/infrastructure/nym-nodes), a single binary that operators configure to serve as an Entry Gateway, Mix Node, or Exit Gateway depending on their setup. Network coordination, token bonding, and the distributed credential system all live on the [Nyx blockchain](/network/infrastructure/nyx), a Cosmos SDK chain whose on-chain topology registry eliminates the need for a centralised directory server. --- title: The Privacy Problem: Why Metadata Matters @@ -139,94 +105,45 @@ url: https://nym.com/docs/network/overview/privacy-problem # The Privacy Problem -## Metadata is the message +## Metadata -When you communicate over the internet, you can think of two types of information being transmitted: -- The **content** is the actual message, file, or data being sent. In the context of a messaging app, this is the contents of your message. In the context of something lower level, like an HTTP packet, this is the encrypted payload of the packet itself. -- The **metadata** is information about the communication itself, some of which can be gathered immediately, such as HTTP packets have headers which show the sending and receiving IP addresses (revealing which devices are communicating), timestamps, packet sizes hinting at what type of content and what connection type (e.g. the different [Maximum Transmission Units of different media](https://en.wikipedia.org/wiki/Maximum_transmission_unit#MTUs_for_common_media)), and some which is gathered over time, by finding patterns in large amounts of traffic, such as frequency patterns indicating how often parties interact. +When you communicate over the internet, two types of information are in play: +- The **content** is the actual message, file, or data being sent. +- The **metadata** is everything else: who is talking to whom, when, from where, and how often. Some metadata is visible in every packet (source/destination IPs, timestamps, sizes), whereas other metadata only emerges from patterns over time: interaction frequency, session durations, and behavioural fingerprints that can identify users across sessions. See [Maximum Transmission Units](https://en.wikipedia.org/wiki/Maximum_transmission_unit#MTUs_for_common_media) for one example of what packet sizes reveal. -Traditional encryption like TLS and end-to-end-encryption (E2EE) protect content - this is what is often the [focus of media attention](https://wire.com/en/blog/whatsapp-end-to-end-encryption-risks). However, most solutions either don't protect from metadata analysis, or falsely purport to do so. +TLS and end-to-end encryption protect content, which is often the [focus of media attention](https://wire.com/en/blog/whatsapp-end-to-end-encryption-risks). However, most solutions don't protect metadata at all, and some falsely claim to. -Even without reading a single message, metadata alone is enough to reconstruct who you talk to, when, how often, and from where — which is why intelligence agencies treat it as seriously as content. As former NSA Director Michael Hayden put it: ["We kill people based on metadata."](https://committees.parliament.uk/writtenevidence/36962/html/) +Metadata alone is enough to reconstruct who you talk to, when, and from where. Intelligence agencies know this; as former NSA Director Michael Hayden put it, ["We kill people based on metadata."](https://committees.parliament.uk/writtenevidence/36962/html/) ## The adversary models -When using the **Mixnet mode** the Nym Network is designed to protect against **Global Passive Adversaries**—entities capable of observing traffic across the entire network simultaneously. This includes nation-state intelligence agencies, large corporations with extensive network infrastructure, ISPs, and collaborative adversaries sharing data. +**Mixnet mode** is designed to protect against **Global Passive Adversaries**: entities that can observe traffic across the entire network at once, such as nation-state level agencies, large corporations with broad network infrastructure, ISPs, or any combination sharing data. -The assumption is that these adversaries can monitor all entry and exit points, correlate timing across the network, apply machine learning to traffic patterns, and conduct long-term statistical analysis. When Tor was first deployed in 2002, such attacks were considered science fiction. They are now documented reality. +The assumption is worst-case: the adversary monitors all entry and exit points, correlates timing, applies machine learning to traffic patterns, and runs long-term statistical analysis. When Tor launched in 2002, this was considered unrealistic - machine learning and the increase in computation power have made this unfortunately more of a potential reality today. -**dVPN mode** offers reduced protections against E2E surveillance and timing analysis, but still offers similar protections to Tor whilst offering increased speeds. +**dVPN mode** does not defend against timing analysis, but it splits trust across two independent operators and removes payment linkability, which already addresses the biggest weaknesses of traditional VPNs. -## Why traditional solutions fall short - -**VPNs** concentrate trust in a single provider who can see all your traffic movements, can be legally or financially compelled to log, and whose payment systems (in most cases) link your account directly to your usage — so a VPN provider can be turned into a surveillance tool with a single court order or compromise. - -**Tor** was designed in an era when global passive adversaries were considered unrealistic. It routes traffic through three relays with onion encryption, but packets flow through without delays or cover traffic, which means an adversary who can observe both ends of a circuit can correlate timing to deanonymise users. These [correlation attacks](https://www.usenix.org/conference/usenixsecurity14/technical-sessions/presentation/johnson) were once theoretical — they are now [documented in practice](https://www.vice.com/en/article/timing-attack-tor-deanonymization/). - -## Nym's approach - -**dVPN mode** splits trust across two independent operators rather than concentrating it in one, and uses [zk-nym credentials](/network/cryptography/zk-nym) so that payment cannot be linked to usage — addressing the two biggest weaknesses of traditional VPNs. - -**Mixnet mode** goes further by adding packet mixing (reordering traffic to break timing correlation), cover traffic (a constant stream of dummy packets that hides when real communication is occurring), and uniform Sphinx packet sizes (preventing content-type fingerprinting) — addressing the timing analysis weakness that Tor and dVPN mode share. - ---- -title: Two Modes: dVPN and Mixnet -description: How NymVPN's two operating modes differ: dVPN mode for fast 2-hop routing, and Mixnet mode for 5-hop traffic mixing with timing obfuscation and cover traffic. -url: https://nym.com/docs/network/overview/two-modes ---- - -# Two Modes: dVPN and Mixnet - -NymVPN has two modes, each using the same underlying network infrastructure but handling traffic very differently. - -## dVPN mode - -dVPN mode routes traffic through 2 hops—an Entry Gateway and an Exit Gateway. Traffic flows from your device to the Entry Gateway, then to the Exit Gateway, then to the destination. - -``` -User --> Entry Gateway --> Exit Gateway --> Internet -``` - -This mode uses [AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/), a WireGuard fork that adds traffic obfuscation to help evade some forms of protocol detection. It creates a tunnel between you and the Entry Gateway, which then creates another tunnel to the Exit Gateway. - -dVPN mode hides your IP from destination servers and splits knowledge between two independent operators—the Entry Gateway knows your IP but not your destination, while the Exit Gateway knows your destination but not your IP. However, it does not add timing delays or cover traffic. A sophisticated adversary observing both gateways could potentially correlate entry and exit timing. - -See [Choosing a Mode](/network/overview/choosing-a-mode) for when to use dVPN vs Mixnet. - -## Mixnet mode - -Mixnet mode routes traffic through 5 hops—an Entry Gateway, three layers of Mix Nodes, and an Exit Gateway. Each Mix Node adds a random delay and mixes your traffic with other packets passing through. - -``` -User --> Entry --> Mix L1 --> Mix L2 --> Mix L3 --> Exit --> Internet - | | | - delay delay delay - + + + - mixing mixing mixing -``` - -Beyond the additional hops, Mixnet mode generates constant cover traffic—dummy packets indistinguishable from real ones. This hides not just who you're communicating with, but when you're communicating. - -Latency is higher, typically 200-500ms additional, due to the mixing delays, but this is what makes timing correlation attacks impractical even for adversaries watching the entire network. - -For practical guidance on when to use each mode — and how developers access the network via SDKs — see [Choosing a Mode](/network/overview/choosing-a-mode). +For a comparison with VPNs, Tor, and I2P, see [Nym vs Other Systems](/network/overview/comparisons). For help picking a mode, see [Choosing a Mode](/network/overview/choosing-a-mode). --- title: Choosing Between dVPN and Mixnet Mode -description: When to use NymVPN's dVPN mode for low-latency browsing versus Mixnet mode for metadata protection against sophisticated adversaries. +description: When to use NymVPN's dVPN mode for low-latency browsing versus Mixnet mode for metadata protection against traffic analysis. url: https://nym.com/docs/network/overview/choosing-a-mode --- # Choosing a Mode -Both dVPN and Mixnet mode run on the same Nym infrastructure but protect against different things — dVPN keeps your IP hidden from destinations and splits trust across two operators, while Mixnet mode goes further by trying to make your traffic patterns invisible even to someone watching the entire network. +Both modes run on the same Nym infrastructure but defend against different threat models. dVPN mode hides your IP and splits trust across two operators, and Mixnet mode additionally protects traffic patterns against adversaries capable of observing the entire network. + +**dVPN mode** routes through 2 hops (Entry Gateway + Exit Gateway) connected via [AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/), a WireGuard fork with traffic obfuscation to evade protocol-level detection. Latency is low, but there is no protection against timing analysis. + +**Mixnet mode** routes through 5 hops (Entry Gateway, three Mix Node layers, Exit Gateway). Each Mix Node adds a random delay and mixes packets with those of other users. Combined with continuous cover traffic, this makes timing correlation impractical even for an adversary watching the entire network. ## Quick comparison | | dVPN Mode | Mixnet Mode | |---|---|---| | **Hops** | 2 (Entry + Exit Gateway) | 5 (Entry + 3 Mix Nodes + Exit) | -| **Additional latency** | 50–150ms | 200–500ms | | **Timing protection** | No | Yes (random delays per hop) | | **Cover traffic** | No | Yes (constant dummy packets) | | **Protects against** | ISPs, websites, advertisers, passive observers | Global passive adversaries, timing correlation, traffic analysis | @@ -234,75 +151,33 @@ Both dVPN and Mixnet mode run on the same Nym infrastructure but protect against ## Use dVPN mode when -- You need low latency for browsing, streaming, or downloads -- Your adversaries are typical: ISPs monitoring traffic, websites tracking location, advertisers building profiles -- Speed matters more than protection against sophisticated traffic analysis -- You want the decentralization and payment privacy benefits of Nym without the latency cost of mixing +- Latency matters: browsing, streaming, downloads, video calls +- Your concern is ISPs, advertisers, and websites tracking you, not nation-state surveillance +- You want decentralized trust and payment privacy without the overhead of mixing ## Use Mixnet mode when -- Metadata protection is critical: journalism, activism, whistleblowing, legal consultations -- You face sophisticated adversaries who might monitor network traffic across multiple points -- You are willing to accept higher latency (200–500ms) for stronger privacy guarantees -- You need unlinkability and unobservability, not just IP hiding +- Metadata exposure is dangerous: journalism, activism, whistleblowing, legal work +- Your adversary might be watching traffic across multiple network points +- Added latency is an acceptable trade for unlinkability and unobservability ## For developers -Developers using the [Nym SDKs](/developers) have access to **Mixnet mode only**—dVPN mode is specific to the NymVPN application. +The [Nym SDKs](/developers) only expose **Mixnet mode**. dVPN mode is specific to the NymVPN application. -There are two integration models available via the SDKs: +There are two integration models: -**As a proxy** (traffic exits to the internet): +**Proxy** (traffic exits to the internet, analogous to Tor's exit relay model): ``` Your App --> Entry --> Mix Nodes --> Exit --> Internet ``` -**End-to-end** (traffic stays within the Mixnet): +**End-to-end** (Sphinx-encrypted the entire way, traffic stays within the Mixnet): ``` Your App --> Entry --> Mix Nodes --> Exit --> Nym Client ``` -The proxy model uses the Mixnet similarly to Tor's exit relay model, whereas the end-to-end model sends Sphinx packets the entire way. See the [integration overview](/developers/integrations) for more detail on choosing between these approaches. - ---- -title: Nym Network Components -description: Architecture of the Nym Network: Entry Gateways, Mix Nodes, Exit Gateways, the Nyx blockchain, and the Nym API for credential issuance. -url: https://nym.com/docs/network/overview/network-components ---- - -# Network Components - -The Nym Network is made up of traffic-routing nodes, a Cosmos SDK blockchain for coordination, and an API layer that handles credential issuance. - -## Nym Nodes - -All traffic-routing infrastructure runs on **Nym Nodes** — a single binary that can operate as an Entry Gateway, Mix Node, or Exit Gateway depending on configuration. - -**Entry Gateways** are the user's first point of contact. They accept client connections via WebSocket, verify zk-nym credentials to confirm payment, and store messages for clients that go offline (up to 24 hours). Entry Gateways know the client's IP address but cannot see message contents or final destinations. They will either create tunnels to Exit Gateways (dVPN mode) or forward Sphinx packets to the first layer of Mix Nodes (in Mixnet mode). - -**Mix Nodes** form the three mixing layers that provide core privacy. They receive Sphinx packets, remove one encryption layer, verify integrity, apply a random delay, and forward to the next hop. Mix Nodes cannot determine their position in the route and cannot link incoming packets to outgoing packets. - -**Exit Gateways** handle traffic leaving the network. They communicate with external internet services on behalf of users and return responses through the network (dVPN and NymVPN mode), or forward Sphinx packets to receipient Nym Clients (SDK Mixnet mode). Like Tor exit nodes, they can see destination addresses but cannot identify the original sender. - -## Nyx Blockchain - -Nyx is a Cosmos SDK blockchain that provides coordination services. It maintains the topology registry—the list of active nodes and their public keys—eliminating the need for a centralized directory server. It manages NYM token staking and distributes rewards to node operators. It also hosts the CosmWasm smart contracts that coordinate the node rewarding and credential system. - -The blockchain is secured by validators using proof-of-stake consensus. Having the topology on-chain prevents the attacks that plague peer-to-peer directory systems. - -## Nym API - -Nyx validators operate **Nym API** [instances](/apis/nym-api) which provide cached blockchain state. A subset of these also form the "Quorum", handling credential issuance—generating the partial blind signatures that form [zk-nyms](/network/cryptography/zk-nym)- and zk-nym validation. - -Credential generation relies on threshold cryptography. No single member can issue credentials alone, and the system remains functional even if some members are offline. This distributes trust across multiple independent parties. See the [zk-nym docs](/network/cryptography/zk-nym) for more on this. - -## Decentralization properties - -The architecture aims to ensure no single point of compromise: -- Entry Gateways know your IP, but not your activity -- Mix Nodes process your packets but can't trace them -- Exit Gateways see destinations but not sources -- Nyx is decentralized via its validator set, and each member of the Quorum generates partial credentials which are unlinkable to anything +See the [integration overview](/developers/integrations) for guidance on choosing between them. --- title: Nym vs VPNs, Tor, I2P, and E2EE @@ -316,31 +191,31 @@ There are several existing approaches to network privacy, each with different as ## Nym vs VPNs -A traditional VPN creates an encrypted tunnel between your device and a VPN server, hiding your IP from destination websites and encrypting traffic from local observers like your ISP. The fundamental limitation is that the VPN provider itself can see all your traffic — every site you visit, when you visit it, how long you stay — and can log this voluntarily or be compelled to by legal process, with your payment information linking your account directly to your activity. +A traditional VPN creates an encrypted tunnel between your device and a VPN server, hiding your IP from destination websites and encrypting traffic from local observers like your ISP. The fundamental limitation is that the VPN provider itself sees all your traffic (every site you visit, when you visit it, how long you stay) and can log this voluntarily or be compelled to by legal process, with your payment information linking your account directly to your activity. Nym's dVPN mode splits this trust across two independent operators so that the Entry Gateway knows your IP but not your destination, the Exit Gateway knows your destination but not your IP, and neither can build a complete picture. Payment is handled through [zk-nyms](/network/cryptography/zk-nym), making subscriptions unlinkable to activity. -Nym's mixnet mode goes further by adding timing obfuscation and cover traffic, which no traditional VPN offers — see [Mixnet Mode](/network/mixnet-mode) for how this works. +Nym's mixnet mode goes further by adding timing obfuscation and cover traffic, which no traditional VPN offers; see [Mixnet Mode](/network/mixnet-mode) for details. ## Nym vs Tor -[Tor](https://www.torproject.org/) is the best-known anonymous overlay network, routing traffic through three relays using [onion encryption](https://spec.torproject.org/tor-spec/relay-cells.html) so that no single relay sees both source and destination. It was designed in an era when global passive adversaries were considered unrealistic, and its [architecture](https://2019.www.torproject.org/about/overview.html.en) reflects that — packets flow through without delays and there is no cover traffic, which means an adversary watching both ends of a circuit can [correlate timing](https://spec.torproject.org/tor-spec/threat-model.html) to deanonymise users. +[Tor](https://www.torproject.org/) is the best-known anonymous overlay network, routing traffic through three relays using Onion encryption so that no single relay sees both source and destination. It was designed in an era when global passive adversaries were considered unrealistic, and its [architecture](https://2019.www.torproject.org/about/overview.html.en) reflects that: packets flow through without delays and there is no cover traffic, which means an adversary watching both ends of a circuit can try and correlate timing to deanonymise users. Nym's mixnet addresses this by adding random delays at each Mix Node to break timing correlations, cover traffic so observers can't tell when real communication is occurring, per-packet routing rather than Tor's per-session circuits (so there's no long-lived path to observe), and a blockchain-based topology instead of Tor's centralised directory authority. -The tradeoff is latency — Tor is faster because it doesn't add mixing delays, so it may be a better fit for general browsing where timing protection isn't needed. Nym's mixnet is designed for situations where the adversary is sophisticated enough to perform traffic analysis. +The tradeoff is latency: Tor is faster because it doesn't add mixing delays, so it may be a better fit for general browsing where timing protection isn't needed. Nym's mixnet is designed for threat models where the adversary can perform traffic analysis. ## Nym vs I2P -[I2P](https://geti2p.net/) replaces Tor's centralised directory authority with a [distributed hash table](https://geti2p.net/en/docs/how/network-database), which improves decentralisation but introduces its own attack surface — DHT-based routing is vulnerable to eclipse attacks and Sybil attacks on the routing table. Like Tor, I2P provides no timing protection, so packets flow without delays or cover traffic. +[I2P](https://geti2p.net/) replaces Tor's centralised directory authority with a [distributed hash table](https://geti2p.net/en/docs/how/network-database), which improves decentralisation but introduces its own attack surface: DHT-based routing is vulnerable to eclipse attacks and Sybil attacks on the routing table. Like Tor, I2P provides no timing protection, so packets flow without delays or cover traffic. Nym uses a blockchain-based topology registry rather than a DHT, which avoids the known attack vectors around DHT-based routing (e.g. eclipse attacks, Sybil attacks on the routing table). The mixing and cover traffic on top of that address the timing analysis gap that I2P shares with Tor. ## Nym vs end-to-end encryption -End-to-end encryption systems like [Signal](https://signal.org/docs/) encrypt messages on your device so that only the recipient can decrypt them, and the server never sees the content. But E2EE does nothing for metadata — the server still sees who you communicate with, when, how often, and how much, which on its own is enough to map relationships and infer sensitive activity. +End-to-end encryption systems like [Signal](https://signal.org/docs/) encrypt messages on your device so that only the recipient can decrypt them, and the server never sees the content. But E2EE does nothing for metadata: the server still sees who you communicate with, when, how often, and how much, which on its own is enough to map relationships and infer sensitive activity. -Nym and E2EE are complementary — E2EE protects message content, Nym protects the metadata around it (who, when, how much). Using Signal over the Nym mixnet, for instance, would protect both what you're saying and the fact that you're saying it. +Nym and E2EE are complementary: E2EE protects message content, Nym protects the metadata around it (who, when, how much). Using Signal over the Nym mixnet, for instance, would protect both message content and the communication metadata around it. For a practical breakdown of when to use dVPN vs Mixnet mode, see [Choosing a Mode](/network/overview/choosing-a-mode). @@ -360,95 +235,93 @@ url: https://nym.com/docs/network/dvpn-mode # dVPN Mode -dVPN mode is a 2-hop decentralized VPN available through [NymVPN](https://nymvpn.com) — traffic is routed through two independent gateways rather than a single VPN provider's server, so no single operator ever sees both who you are and what you're doing. +dVPN mode is a 2-hop decentralized VPN available through [NymVPN](https://nymvpn.com). Traffic is routed through two independent gateways rather than a single VPN provider's server, so no single operator ever sees both who you are and what you're doing. ## How it works -Unlike traditional VPNs that route traffic through a single provider's server, dVPN mode routes traffic through two independent nodes operated by different parties. - ``` User --> Entry Gateway --> Exit Gateway --> Internet ``` -Your traffic is encrypted in layers—a tunnel inside a tunnel. The outer layer is encrypted to the Entry Gateway, and the inner layer is encrypted to the Exit Gateway. The Entry Gateway strips the outer layer and forwards the still-encrypted packet. The Exit Gateway strips the inner layer and sends it to the destination. Responses follow the reverse path. - -This "onion" model means neither gateway ever sees both your identity and your destination simultaneously. The Entry Gateway knows your IP address but cannot see your destination or message contents. The Exit Gateway knows your destination but cannot see your IP address. +Your device wraps each packet in two layers of encryption, one per gateway. The Entry Gateway strips the outer layer and forwards a packet it cannot read; the Exit Gateway strips the inner layer and sends the plaintext request to the destination. Responses follow the reverse path. The Entry Gateway therefore knows your IP address but not the destination, while the Exit Gateway knows the destination but not the sender. ## Privacy guarantees -dVPN mode hides your IP from destination servers and splits trust across two operators, but it does not add timing obfuscation or cover traffic — packets are forwarded immediately without delay, which means a sophisticated adversary observing both your Entry and Exit Gateways could correlate timing to link your requests. For protection against that kind of adversary, see [Mixnet Mode](/network/mixnet-mode). +dVPN mode hides your IP from destination servers and splits trust across two operators. It does not add timing obfuscation or cover traffic. Packets are forwarded immediately, so an adversary watching both gateways could still correlate timing to link your requests. If you need protection against traffic analysis, see [Mixnet Mode](/network/mixnet-mode). ## Performance -Latency is typically 50-150ms additional, comparable to traditional VPNs, since WireGuard handles encryption and reconnection without much overhead. - -For help deciding between dVPN and Mixnet mode, see [Choosing a Mode](/network/overview/choosing-a-mode). +Added latency is comparable to traditional VPNs, and WireGuard keeps cryptographic overhead low, so browsing, streaming, and downloads are not noticeably affected. ## Technical details -- [dVPN Protocol](/network/dvpn-mode/protocol) — protocol stack and encryption details -- [Censorship Resistance](/network/dvpn-mode/censorship-resistance) — AmneziaWG and DPI evasion +- [dVPN Protocol](/network/dvpn-mode/protocol): protocol stack and encryption details +- [Censorship Resistance](/network/dvpn-mode/censorship-resistance): AmneziaWG and DPI evasion ## Further reading -- [Introducing AmneziaWG for NymVPN](https://nym.com/blog/introducing-amneziawg-for-nymvpn) — censorship resistance -- [What Is a Double VPN?](https://nym.com/blog/double-vpn) — multi-hop privacy explained -- [Building a Decentralized WireGuard VPN](https://nym.com/blog/building-decentralized-wireguard-vpn) — architecture decisions -- [What is NymVPN?](https://nym.com/blog/what-is-nymvpn) — general overview +- [Introducing AmneziaWG for NymVPN](https://nym.com/blog/introducing-amneziawg-for-nymvpn): censorship resistance +- [What Is a Double VPN?](https://nym.com/blog/double-vpn): multi-hop privacy explained +- [Building a Decentralized WireGuard VPN](https://nym.com/blog/building-decentralized-wireguard-vpn): architecture decisions +- [What is NymVPN?](https://nym.com/blog/what-is-nymvpn): general overview --- title: dVPN Protocol Stack and Encryption -description: Technical details of Nym dVPN mode's protocol layers, including WireGuard tunnels, AES-GCM-SIV-256 layer encryption, and packet format tradeoffs. +description: Technical details of Nym dVPN mode's protocol layers: nested WireGuard tunnels, split-knowledge architecture, and packet format tradeoffs. url: https://nym.com/docs/network/dvpn-mode/protocol --- # dVPN Protocol +Cryptographic details on this page will be updated for the Lewes Protocol release. For the current algorithm overview, see the [Nym Trust Center: Cryptography](https://nym.com/trust-center/cryptography). + This page covers the technical details of dVPN mode's protocol stack and encryption. ## Protocol layers -dVPN mode combines WireGuard with additional layer encryption. The client-to-Entry Gateway connection uses WireGuard, providing fast handshakes, efficient encryption, and graceful reconnection. The Entry-to-Exit Gateway connection adds another encryption layer using AES-GCM-SIV-256. +dVPN mode uses two nested WireGuard tunnels. The client establishes an inner tunnel to the Exit Gateway and an outer tunnel to the Entry Gateway, where the inner tunnel is created first and the outer tunnel encapsulates it. ``` +-----------------------------------------+ | Application Data | +-----------------------------------------+ -| Layer Encryption (Entry -> Exit) | +| Inner WireGuard tunnel (Client → Exit) | +-----------------------------------------+ -| WireGuard (Client -> Entry) | +| Outer WireGuard tunnel (Client → Entry)| +-----------------------------------------+ | UDP/IP | +-----------------------------------------+ ``` +The Entry Gateway decrypts only the outer tunnel and forwards the inner tunnel, still fully encrypted, to the Exit Gateway. The Exit Gateway decrypts the inner tunnel and forwards traffic to its destination. Because the Entry Gateway never holds keys for the inner tunnel, it is cryptographically excluded from the payload. + ## Encryption -The WireGuard layer uses Curve25519 for key exchange, ChaCha20-Poly1305 for symmetric encryption, and BLAKE2s for hashing. This provides 256-bit security with modern, well-audited primitives. - -The inner layer uses AES-GCM-SIV-256, an authenticated encryption scheme with nonce-misuse resistance. Even if a nonce is accidentally reused, the scheme degrades gracefully rather than catastrophically. Keys are derived through ECDH between the client and Exit Gateway, with separate keys for each direction. +Both tunnels use standard WireGuard cryptography: Curve25519 for key exchange, ChaCha20-Poly1305 for authenticated encryption, and BLAKE2s for hashing. Each tunnel derives independent session keys, providing 256-bit security with modern, well-audited primitives. ## Packet format -dVPN mode uses standard WireGuard packet framing — packets are not padded to a uniform size. This means packet sizes may vary and could in principle leak information about content types (video streams have different size patterns than text messages). This is a tradeoff: uniform padding would add overhead and reduce throughput, which conflicts with dVPN mode's goal of low-latency, high-throughput connectivity. For uniform packet sizes, use [mixnet mode](/network/mixnet-mode), which wraps all traffic in fixed-size Sphinx packets. +dVPN mode uses standard WireGuard packet framing: packets are not padded to a uniform size. Packet sizes may vary and could in principle leak information about content types (video streams have different size patterns than text messages). This is a deliberate tradeoff: uniform padding would add overhead and reduce throughput, which conflicts with dVPN mode's goal of low-latency, high-throughput connectivity. For uniform packet sizes, use [mixnet mode](/network/mixnet-mode), which wraps all traffic in fixed-size Sphinx packets. ## Connection lifecycle -When connecting, the client first selects Entry and Exit Gateways based on latency, location preference, or random selection. It then presents a zk-nym credential to the Entry Gateway for anonymous authentication. The credential proves payment without revealing identity—it's re-randomized for each connection and cannot be linked to previous usage. +When connecting, the client first selects Entry and Exit Gateways based on latency, location preference, or random selection. It then presents a zk-nym credential to the Entry Gateway for anonymous authentication. The credential proves payment without revealing identity; it is re-randomized for each connection and cannot be linked to previous usage. -Once authenticated, the client establishes a WireGuard tunnel to the Entry Gateway, which establishes a link to the Exit Gateway. Traffic then flows through both hops until the session ends. +Once authenticated, the client establishes both WireGuard tunnels: first the inner tunnel keyed with the Exit Gateway, then the outer tunnel keyed with the Entry Gateway. Traffic flows through both hops until the session ends. ## Security properties -The protocol provides forward secrecy—new session keys are derived for each connection, so compromising long-term keys doesn't expose past sessions. WireGuard's key rotation provides additional forward secrecy within sessions. +The protocol provides forward secrecy: new session keys are derived for each connection, so compromising long-term keys does not expose past sessions. WireGuard's key rotation provides additional forward secrecy within sessions. -The split-knowledge architecture ensures the Entry Gateway knows your IP but not your destinations or payload content, while the Exit Gateway knows your destinations but not your IP. Neither can correlate the two. +The nested-tunnel architecture enforces split knowledge. The Entry Gateway knows your IP but cannot decrypt the inner tunnel, so it sees neither your destinations nor your payload. The Exit Gateway decrypts the inner tunnel and sees your destinations but never learns your IP. Neither gateway can correlate the two. Replay protection comes from WireGuard's counter-based mechanism and from zk-nym serial numbers that prevent credential reuse. ## Relationship to mixnet mode -dVPN mode shares infrastructure with mixnet mode. Both use the same Entry and Exit Gateways and the same credential system. The difference is in how traffic is handled: mixnet mode routes through three additional Mix Node layers with delays and cover traffic using fixed-size Sphinx packets, while dVPN mode routes directly between gateways using WireGuard. The two modes are distinguishable at the protocol level due to their different packet formats and traffic patterns. +dVPN mode shares infrastructure with mixnet mode. Both use the same Entry and Exit Gateways and the same credential system. The difference is in how traffic is handled: mixnet mode routes through three additional Mix Node layers with delays and cover traffic using fixed-size [Sphinx packets](/network/cryptography/sphinx), while dVPN mode routes directly between gateways using WireGuard. The two modes are distinguishable at the protocol level due to their different packet formats and traffic patterns. + +In anonymous (5-hop) mode, NymVPN routes traffic through the full mixnet to the Exit Gateway's [IP Packet Router](/network/infrastructure/exit-services#ip-packet-router), which tunnels raw IP packets to the internet. See [Exit Gateway Services](/network/infrastructure/exit-services) for how the IPR and Network Requester work. This shared infrastructure means improvements to Gateways and credentials benefit both modes. @@ -466,23 +339,23 @@ dVPN mode incorporates several techniques to help users connect in restrictive n Deep Packet Inspection (DPI) systems deployed by ISPs and governments can identify VPN protocols by their handshake patterns, packet sizes, and timing characteristics. Standard WireGuard, for instance, has a recognisable handshake initiation pattern that DPI rules can match against. Once identified, connections can be throttled or blocked entirely. -This is not a theoretical concern — countries including China, Russia, Iran, and others actively deploy DPI to restrict VPN usage. +This is not a theoretical concern: countries including China, Russia, Iran, and others actively deploy DPI to restrict VPN usage. ## AmneziaWG dVPN mode uses [AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/), a fork of WireGuard that adds obfuscation techniques to make the protocol harder to fingerprint. -AmneziaWG modifies the WireGuard handshake by introducing decoy packets before the handshake initiation. These decoy packets disrupt DPI rules that rely on matching the standard WireGuard handshake sequence. The actual WireGuard protocol behaviour is preserved — the modifications sit around the handshake rather than replacing it, so all of WireGuard's security properties (Curve25519 key exchange, ChaCha20-Poly1305 encryption, forward secrecy) remain intact. +AmneziaWG modifies the WireGuard handshake by introducing decoy packets before the handshake initiation. These decoy packets disrupt DPI rules that rely on matching the standard WireGuard handshake sequence. The actual WireGuard protocol behaviour is preserved; the modifications sit around the handshake rather than replacing it, so all of WireGuard's security properties (Curve25519 key exchange, ChaCha20-Poly1305 encryption, forward secrecy) remain intact. ## Limitations -AmneziaWG raises the bar for censors relying on simple protocol fingerprinting, but it doesn't help against deeper analysis — statistical fingerprinting of packet timing and sizes, IP-based blocking of known Gateway addresses, or active probing where the censor sends packets to suspected VPN servers to confirm their identity. +AmneziaWG raises the bar for censors relying on simple protocol fingerprinting, but it doesn't help against deeper analysis: statistical fingerprinting of packet timing and sizes, IP-based blocking of known Gateway addresses, or active probing where the censor sends packets to suspected VPN servers to confirm their identity. ## QUIC transport mode QUIC transport mode wraps the WireGuard/AmneziaWG connection inside a [QUIC](https://datatracker.ietf.org/doc/html/rfc9000) layer, so the traffic looks like standard HTTPS/HTTP3 to DPI systems rather than a VPN tunnel. Since QUIC is now used by a significant portion of regular web traffic (over 30% of Cloudflare's traffic in 2023 was HTTP/3 over QUIC), blocking it outright would break large parts of the web for everyone, making it an unattractive target for censors. -QUIC transport applies to the Entry Gateway connection only (the first hop). Not all Gateways support it yet — enabling QUIC in the NymVPN app will filter the Gateway list to those that do. Because the QUIC wrapper adds overhead, it can reduce speeds slightly, so it's worth leaving disabled unless you're in a censored environment or having connectivity issues. +QUIC transport applies to the Entry Gateway connection only (the first hop). Not all Gateways support it yet; enabling QUIC in the NymVPN app will filter the Gateway list to those that do. Because the QUIC wrapper adds overhead, it can reduce speeds slightly, so it's worth leaving disabled unless you're in a censored environment or having connectivity issues. ## Stealth API Connect @@ -490,7 +363,7 @@ Even if a user can establish a VPN tunnel, censors can also block access to the ## Limitations -These techniques are layered — AmneziaWG obfuscates the handshake, QUIC disguises the tunnel as regular web traffic, and Stealth API Connect protects the initial API discovery. Together they cover several common censorship methods, but none of them are guarantees. Censorship resistance is an ongoing arms race, and new techniques will be documented here as they ship. +These techniques are layered: AmneziaWG obfuscates the handshake, QUIC disguises the tunnel as regular web traffic, and Stealth API Connect protects the initial API discovery. Together they cover several common censorship methods, but none of them are guarantees. Censorship resistance is an ongoing arms race, and new techniques will be documented here as they ship. ## Further reading @@ -509,41 +382,35 @@ url: https://nym.com/docs/network/mixnet-mode # Mixnet Mode -Mixnet mode routes traffic through 5 hops — an Entry Gateway, three layers of Mix Nodes, and an Exit Gateway — with random delays, packet reordering, and cover traffic at each mixing layer. It is available through [NymVPN](https://nymvpn.com) and the [Nym SDKs](/developers). +Mixnet mode routes traffic through 5 hops: an Entry Gateway, three layers of Mix Nodes, and an Exit Gateway. Each mixing layer adds random delays, reorders packets, and injects cover traffic. Available through [NymVPN](https://nymvpn.com) and the [Nym SDKs](/developers). ## How it works -Traffic passes through five hops: an Entry Gateway, three layers of Mix Nodes, and an Exit Gateway. Each Mix Node adds a random delay before forwarding, mixing your packets with others passing through. - ``` User --> Entry --> Mix L1 --> Mix L2 --> Mix L3 --> Exit --> Internet | | | delay delay delay ``` -Beyond the additional hops, mixnet mode generates constant cover traffic—dummy packets indistinguishable from real ones. Your client continuously sends packets into the network whether or not you're actively communicating. Real messages are slotted into this stream of cover traffic. - -The client constructs Sphinx packets with layered encryption. Each layer contains routing information for one hop plus the inner encrypted packet. As the packet travels through the network, each node removes its layer to learn the next destination, but cannot see the final destination or payload content. +Each Mix Node strips one layer of [Sphinx](/network/cryptography/sphinx) encryption to learn the next hop, holds the packet for a random delay, then forwards it. No node ever sees both the origin and the final destination. The client also continuously sends [cover traffic](/network/mixnet-mode/cover-traffic) - dummy packets cryptographically indistinguishable from real ones - so an observer sees a constant stream of identical packets regardless of whether any real communication is taking place. ## Privacy properties -The combination of mixing, delays, and cover traffic gives the mixnet three properties that simpler systems like VPNs and Tor don't have: - -- **Unlinkability**: an observer watching a Mix Node cannot correlate incoming packets with outgoing ones, cannot connect successive packets from the same user, and cannot link activity across different sessions — the random delays and reordering destroy the timing signal that makes this possible in other networks. -- **Unobservability**: because your client sends a constant stream of cover traffic whether or not you're actually communicating, an observer cannot tell when real communication is occurring, how much of the traffic is real versus dummy, or even whether a given user is active at all. -- **Resistance to traffic analysis**: uniform Sphinx packet sizes prevent content-type fingerprinting, per-packet routing means there are no long-lived circuits to observe (unlike Tor), and the mixing delays mean that even an adversary watching the entire network cannot correlate entry and exit timing. +- **Unlinkability**: the random delays and reordering at each Mix Node destroy the timing signal an observer would need to correlate incoming and outgoing packets, or to connect successive packets from the same user. See [Packet Mixing](/network/mixnet-mode/mixing). +- **Unobservability**: because cover traffic is constant, an observer cannot determine when a user is active or what fraction of the traffic is real. See [Cover Traffic](/network/mixnet-mode/cover-traffic). +- **Resistance to traffic analysis**: uniform Sphinx packet sizes prevent content-type fingerprinting, and per-packet routing eliminates the long-lived circuits that make other anonymity networks susceptible to end-to-end correlation. See [Traffic Flow](/network/mixnet-mode/traffic-flow). ## Performance -Latency is higher than dVPN mode, typically 200-500ms additional, due to the mixing delays at each of the three Mix Node layers. This is the cost of timing obfuscation. For most messaging applications, this latency is acceptable. For real-time applications like video calls, dVPN mode may be more appropriate. +The three mixing layers add additional latency. This is acceptable for messaging, file transfers, and most API calls, but unsuitable for real-time applications like video calling. For those, [dVPN mode](/network/dvpn-mode) is more appropriate. -For help deciding between dVPN and Mixnet mode, see [Choosing a Mode](/network/overview/choosing-a-mode). +Updated latency measurements will be published after the Lewes Protocol release. ## Further reading The following pages cover mixnet internals in detail: -- [Loopix Design](/network/mixnet-mode/loopix) explains the academic foundation +- [Loopix Design](/network/mixnet-mode/loopix) explains the academic foundation of Nym's Mixnet design - [Traffic Flow](/network/mixnet-mode/traffic-flow) shows the packet journey with diagrams - [Cover Traffic](/network/mixnet-mode/cover-traffic) explains how dummy packets provide unobservability - [Packet Mixing](/network/mixnet-mode/mixing) covers timing delays and their importance @@ -557,11 +424,11 @@ url: https://nym.com/docs/network/mixnet-mode/loopix # Loopix Design -The Nym mixnet is based on the [Loopix](https://arxiv.org/pdf/1703.00536) academic design, with modifications for decentralized operation and economic incentives. +The Nym mixnet is based on the [Loopix](https://arxiv.org/pdf/1703.00536) design, with modifications for decentralized operation and economic incentives. ## The insight -Traditional mixnets focus on hiding "who messages whom"—but this alone is insufficient. Adversaries observing message volume and timing over time can still infer private information. If you always message the same friend at the same time, patterns emerge. If you go silent when traveling, that's information too. +Traditional mixnets focus on hiding "who messages whom," but this alone is insufficient, as adversaries observing message volume and timing over time can still infer private information. If you always message the same friend at the same time, patterns emerge. If you go silent when traveling, that's information too. Loopix was designed to provide both **unlinkability** (hiding who talks to whom) and **unobservability** (hiding when and how much communication occurs). The name comes from its use of "loop" cover traffic that circulates through the network. @@ -573,7 +440,7 @@ This structure prevents observations about which paths are used together and lim ## Continuous-time mixing -Unlike batch mixnets that collect messages and release them periodically, Loopix uses continuous-time mixing. Each message is delayed independently according to an exponential distribution, then forwarded as soon as its delay expires. +Unlike batch mixnets that collect messages and release them periodically, Loopix uses continuous-time mixing, where each message is delayed independently according to an exponential distribution and then forwarded as soon as its delay expires. This approach offers optimal anonymity for a given mean latency. The exponential distribution has a key property: if two messages arrive at different times, they have equal probability of leaving in either order. An adversary watching input and output timing gains no information about which input became which output. @@ -581,19 +448,19 @@ Continuous mixing also means lower latency overall since messages don't wait for ## Cover traffic loops -Connected clients and nodes continuously generate dummy packets that travel in loops through the network back to the sender. These packets are indistinguishable from real traffic—same size, same encryption, same timing distribution. +Connected clients and nodes continuously generate dummy packets that travel in loops through the network back to the sender. These packets are indistinguishable from real traffic: same size, same encryption, same timing distribution. -Loop traffic ensures minimum anonymity even when few users are active. It hides when real communication starts and stops. And it can detect active attacks: if your loop packets don't return, something is interfering with the network. +Loop traffic ensures minimum anonymity even when few users are active, hides when real communication starts and stops, and enables detection of active attacks (if loop packets fail to return, a network fault or active interference is likely). ## Nym's modifications -The Nym implementation extends Loopix in several ways. The original design assumed a trusted directory server; Nym uses the Nyx blockchain for decentralized topology management. The original relied on volunteers; Nym provides NYM token rewards to ensure sustainable operation. And Nym adds zk-nyms for privacy-preserving payment—something the original academic design didn't address. +The Nym implementation extends Loopix in several ways: replacing the trusted directory server with the Nyx blockchain for decentralized topology management, incentivising node operation with NYM token rewards rather than relying on volunteers, and adding zk-nyms for privacy-preserving payment, which the original academic design did not address. ## Security guarantees -The combination of continuous-time mixing and cover traffic provides provable guarantees. The anonymity set—the set of users who could have sent a given message—grows unboundedly over time. Even messages with short delays have large anonymity sets because of the exponential distribution. +The combination of continuous-time mixing and cover traffic provides provable guarantees. The anonymity set (the set of users who could have sent a given message) grows unboundedly over time. Even messages with short delays have large anonymity sets because of the exponential distribution. -An adversary observing the entire network cannot determine who is communicating with whom. They cannot tell when real communication is occurring. And statistical analysis provides no advantage because the traffic patterns are designed to be indistinguishable from random. +An adversary observing the entire network cannot determine who is communicating with whom, cannot tell when real communication is occurring, and gains no advantage from statistical analysis because the traffic patterns are designed to be indistinguishable from random. For the full formal analysis, see the [Loopix paper](https://arxiv.org/pdf/1703.00536) and the [Nym Whitepaper](https://nym.com/nym-whitepaper.pdf). @@ -610,13 +477,13 @@ This describes the 5-hop mixnet flow. For the 2-hop dVPN mode, see [dVPN Protoco ## Overview -The Nym mixnet uses source routing—the sender chooses the complete route before sending. This means the sender constructs a Sphinx packet with layered encryption, where each layer contains routing information for one hop. +The Nym mixnet uses source routing: the sender chooses the complete route before sending. This means the sender constructs a Sphinx packet with layered encryption, where each layer contains routing information for one hop. ## Client to Entry Gateway -When you connect, your Nym client registers with a particular Entry Gateway. This becomes part of your Nym address and is where your incoming messages are delivered. +On connection, the Nym client registers with a particular Entry Gateway. This Gateway becomes part of the client's Nym address and is where incoming messages are delivered. -The client continuously sends packets to the Entry Gateway over a WebSocket connection. This stream includes both real messages and cover traffic at a constant rate. When you have data to send, it's encrypted as Sphinx packets and slotted into the stream. When you don't, cover packets flow instead. +The client continuously sends packets to the Entry Gateway over a WebSocket connection. This stream includes both real messages and cover traffic at a constant rate. When the application has data to send, the client encrypts it as Sphinx packets and slots them into the stream. When there is no data, cover packets flow instead. ```mermaid sequenceDiagram @@ -736,11 +603,11 @@ url: https://nym.com/docs/network/mixnet-mode/cover-traffic # Cover Traffic -Cover traffic is dummy packets that hide when real communication is occurring. It's a fundamental mechanism for achieving **unobservability**. +Cover traffic consists of dummy packets that hide when real communication is occurring, providing unobservability: an adversary cannot determine whether a user is actively communicating. ## The problem -Even with perfect encryption and mixing, traffic analysis can reveal information. An adversary can see how much data you're sending, when you're sending it, and detect patterns over time. Regular silence followed by bursts of activity reveals your schedule. Consistent traffic volumes to certain destinations reveal ongoing relationships. +Even with perfect encryption and mixing, traffic analysis can reveal information. An adversary can see how much data you're sending, when you're sending it, and detect patterns over time. Regular silence followed by bursts of activity reveals your schedule, and consistent traffic volumes to certain destinations reveal ongoing relationships. ## The solution @@ -759,17 +626,17 @@ Time --------------------------------------> Constant rate (activity hidden) ``` -The cover packets are real Sphinx packets with valid encryption—just empty payloads. They travel through the network exactly like real packets, get mixed at each hop, and are discarded at their destination. No node along the way can tell whether a packet contains real data or is cover traffic. +The cover packets are real Sphinx packets with valid encryption, just with empty payloads. They travel through the network exactly like real packets, get mixed at each hop, and are discarded at their destination. No node along the way can tell whether a packet contains real data or is cover traffic. ## Loop traffic -Cover packets follow complete routes through the network back to the sender. These "loops" serve multiple purposes: they test that network routes are functioning, they provide traffic for mixing with others' cover traffic, and they can detect active attacks. If your loop packets stop returning, something is wrong. +Cover packets follow complete routes through the network back to the sender. These "loops" serve multiple purposes: they provide traffic for mixing with others' cover traffic and they can detect active attacks. If loop packets stop returning, a network fault or active interference is likely. Mix nodes also generate their own cover traffic, ensuring minimum traffic levels even when few users are active. This provides baseline anonymity guarantees regardless of network load. ## How it's generated -Traffic follows a Poisson process with a configurable rate parameter. Inter-packet times are exponentially distributed—random, but with a known average rate. This distribution provides maximum entropy (uncertainty) for a given mean rate, which translates to optimal privacy properties. +Traffic follows a Poisson process with a configurable rate parameter. Inter-packet times are exponentially distributed: random, but with a known average rate. This distribution provides maximum entropy (uncertainty) for a given mean rate, which translates to optimal privacy properties. ## Tradeoffs @@ -779,7 +646,7 @@ The default parameters balance privacy and resource usage. Applications with hei ## What cover traffic defeats -Cover traffic prevents volume analysis (how much you communicate), timing analysis (when you communicate), presence detection (whether you're online), and behavioral profiling (your communication patterns over time). Combined with packet mixing, it ensures that even an adversary watching the entire network learns nothing about your communication behavior. +Cover traffic prevents volume analysis (how much you communicate), timing analysis (when you communicate), and behavioral profiling (your communication patterns over time). Combined with packet mixing, it ensures that even an adversary watching the entire network cannot learn about your communication behavior with currently known methods. --- title: Packet Mixing and Random Delays @@ -793,11 +660,11 @@ Packet mixing breaks timing correlations by adding random delays at each Mix Nod ## The problem -Without mixing, an observer watching a node could correlate inputs and outputs. A packet arriving at time t₀ and a packet leaving at time t₀ + δ are obviously related. Even with encryption hiding contents, the timing relationship reveals which input became which output. +Without mixing, an observer watching a node could correlate inputs and outputs. If packets leave on a FIFO (First In First Out) basis, even with encryption hiding contents, the timing relationship reveals which input became which output. ## The solution -Each Mix Node adds a random delay before forwarding. Packets don't flow through in order—they're held for variable times and released in a different sequence than they arrived. An observer sees packets going in and packets coming out, but cannot match them. +Each Mix Node adds a random delay before forwarding. Packets don't flow through in order; they're held for variable times and released in a different sequence than they arrived. An observer sees packets going in and packets coming out, but cannot match them. ``` Input sequence: A B C D E @@ -813,25 +680,27 @@ The delays follow an exponential distribution. This choice is mathematically opt ## Why exponential delays -The exponential distribution is "memoryless"—the probability of a packet leaving in the next moment doesn't depend on how long it's already waited. This means the adversary cannot narrow down possibilities by noting how long packets have been in the node. +The exponential distribution is memoryless: the probability of a packet leaving in the next moment does not depend on how long it has already waited, so an adversary cannot narrow down possibilities by noting how long packets have been in the node. -Any other delay distribution leaks information. Fixed delays would let adversaries match arrivals to departures by timing. Uniform distributions would create windows where matches become more likely. The exponential distribution maximizes uncertainty. +Any other delay distribution leaks information; fixed delays would let adversaries match arrivals to departures by timing, and uniform distributions would create windows where matches become more likely. ## Continuous vs batch mixing Older mixnet designs collected packets into batches and shuffled them before release. This has problems: latency is unpredictable since you wait for batches to fill, bandwidth is inefficient due to bursty traffic, and the anonymity set is limited to the batch size. -Continuous-time mixing processes each packet independently. Latency is predictable (the mean delay is configurable). Bandwidth is used efficiently. And the anonymity set is unbounded—it includes all packets that have ever passed through, weighted by time. +Continuous-time mixing processes each packet independently. Latency is predictable (the mean delay is configurable), bandwidth is used efficiently, and the anonymity set is unbounded: it includes all packets that have ever passed through, weighted by time. ## The aggregate effect With three Mix Node layers, each applying random delays, the overall effect is thorough reordering. Packets entering the mixnet in sequence exit in a completely different order. The timing relationship between sending and receiving is destroyed. -This is why mixnet mode has higher latency than dVPN mode. The delays are the price of timing protection. Mean delays of 50-100ms per hop add up to 150-300ms average across three layers—noticeable, but worth it for the privacy gain. +These delays account for the additional latency of mixnet mode relative to dVPN mode. + +Updated latency measurements will be published after the Lewes Protocol release. ## Combined with cover traffic -Mixing and cover traffic work together. Cover traffic ensures there's always packets to mix, even during low activity. Mixing ensures that real and cover packets become interleaved and indistinguishable. Neither mechanism alone is sufficient—together they provide both unlinkability and unobservability. +Mixing and cover traffic are complementary. Cover traffic ensures there are always packets to mix, even during low activity, while mixing ensures that real and cover packets become interleaved and indistinguishable. Together they provide both unlinkability and unobservability. --- title: Anonymous Replies with SURBs @@ -845,13 +714,13 @@ SURBs (Single Use Reply Blocks) enable anonymous bidirectional communication. A ## The problem -In a typical mixnet scenario, Alice sends a message to Bob and wants a reply. If Bob sends directly to Alice's Nym address, he learns it. This defeats the purpose of anonymous communication—Bob now knows Alice's identity for future contact. +In a typical mixnet scenario, Alice sends a message to Bob and wants a reply, but if Bob sends directly to Alice's Nym address, he learns it. This defeats the purpose of anonymous communication; Bob now knows Alice's identity for future contact, and due to how Nym's [addressing scheme](/network/reference/addressing.md) works, this means that Bob knows which Gateway node Alice's client is using. ## How SURBs work -Alice creates SURBs—encrypted routing headers—and includes them with her message to Bob. Each SURB contains a complete route back to Alice, encrypted so that Bob cannot read it. Bob attaches his reply to a SURB and sends the resulting packet into the mixnet. It travels through the encoded route and arrives at Alice, but Bob never learns where it went. +Alice creates SURBs (encrypted routing headers) and includes them with her message to Bob. Each SURB contains a complete route back to Alice, encrypted so that Bob cannot read it. Bob attaches his reply to a SURB and sends the resulting packet into the mixnet. It travels through the encoded route and arrives at Alice, but Bob never learns where it went. -A SURB contains the address of the first hop (Alice's Entry Gateway), encrypted routing headers for the path back to Alice, and a key to encrypt the reply payload. The routing headers are layered like a Sphinx packet—each hop can only see the next destination. +A SURB contains the address of the first hop (Alice's Entry Gateway), encrypted routing headers for the path back to Alice, and a key to encrypt the reply payload. The routing headers are layered like a Sphinx packet; each hop can only see the next destination. ## Single use @@ -886,15 +755,15 @@ sequenceDiagram ## Sender tags -For sessions with multiple messages, Alice includes a randomly generated sender tag with her SURBs. This helps Bob organize SURBs from multiple conversations without revealing anything about Alice's identity—the tag is random and unlinkable to her address. +For sessions with multiple messages, Alice includes a randomly generated sender tag with her SURBs. This helps Bob organize SURBs from multiple conversations without revealing anything about Alice's identity; the tag is random and unlinkable to her address. ## Security considerations -There's a known attack where a malicious receiver hoards SURBs and sends them all back simultaneously, attempting to correlate traffic patterns at the sender's Gateway. This attack requires active participation (not just passive observation), costs money once zk-nyms are enabled, and provides limited information even if successful. It's not a passive surveillance technique—the attacker must be specifically targeting you and willing to spend resources. +There's a known attack where a malicious receiver hoards SURBs and sends them all back simultaneously, attempting to correlate traffic patterns at the sender's Gateway. This attack requires active participation (not just passive observation), and provides limited information even if successful. It's not a passive surveillance technique; the attacker must be specifically targeting you and willing to spend resources. ## Comparison to Tor onion addresses -Tor's onion addresses allow indefinite replies but require the recipient to run a hidden service. SURBs are single-use but require no service—they're generated on-demand per message. SURBs also benefit from the mixnet's timing protection, which onion addresses don't have. +Tor's onion addresses allow indefinite replies but require the recipient to run a hidden service. SURBs are single-use but require no service; they're generated on-demand per message. SURBs also benefit from the mixnet's timing protection, which onion addresses don't have. --- title: Nym Network Cryptography @@ -906,125 +775,37 @@ url: https://nym.com/docs/network/cryptography The Nym Network relies on several cryptographic systems working together. This section covers the algorithms, packet formats, and credential systems that provide privacy guarantees. -## Defense in depth - -There isn't a single cryptographic scheme protecting traffic — transport encryption secures connections between nodes, Sphinx packets add per-hop encryption so each node only learns where to forward rather than the full route, the payload itself is encrypted end-to-end, and zk-nyms keep payment separate from usage. - ## What's covered -[Encryption Standards](/network/cryptography/encryption-standards) documents the specific algorithms used throughout the network—Curve25519 for key exchange, AES and ChaCha20 for symmetric encryption, Lioness for wide-block encryption in Sphinx payloads. - [Sphinx Packets](/network/cryptography/sphinx) explains the packet format that enables layered encryption and anonymous routing. Each Sphinx packet contains routing information encrypted in layers, where each hop can only decrypt its own layer. [zk-nyms](/network/cryptography/zk-nym) covers the anonymous credential system that separates payment from usage. This is how you can pay for network access without that payment being linkable to your activity. ---- -title: Encryption Standards Used in Nym -description: Cryptographic algorithms used across the Nym Network: Curve25519 key exchange, ChaCha20-Poly1305, AES-GCM-SIV, Lioness wide-block encryption, Noise protocol, and post-quantum KEM. -url: https://nym.com/docs/network/cryptography/encryption-standards ---- - -# Encryption Standards - -This page documents the cryptographic algorithms used throughout the Nym Network. - -## Key exchange - -All key exchanges use **Curve25519** via X25519. This elliptic curve provides 128-bit security with fast, constant-time implementations and compact 32-byte keys. Nym uses it for Sphinx packet key derivation (ECDH with each hop), Gateway authentication, WireGuard tunnel handshakes, and session key establishment. - -Digital signatures use **Ed25519**, the signature scheme built on Curve25519. Node identity keys, client authentication, and QUIC TLS certificate verification all use Ed25519 signatures. - -## Authenticated encryption - -**ChaCha20-Poly1305** is the primary authenticated encryption scheme. It encrypts all WireGuard data packets in dVPN mode (via the `boringtun` and `wireguard-go` implementations), and is used in the Noise protocol handshakes and the OutFox packet format. It provides 256-bit security with authentication and performs well on devices without AES hardware acceleration. - -**AES-GCM-SIV-256** is used for Gateway-client shared key encryption (protocol version 3+). The SIV (Synthetic Initialization Vector) construction degrades gracefully if a nonce is accidentally reused — important in distributed systems where nonce management is harder. - -**AES-CTR-128** is used in Sphinx header encryption, where the stream cipher combines with blinding factors to create the layered encryption that each mix node peels away. - -## Node authentication - -The **Noise protocol** framework (via the `snow` crate) provides authenticated key exchange between nodes. Two cipher suites are in use: - -- `Noise_XKpsk3_25519_AESGCM_SHA256` -- `Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s` - -These provide mutual authentication, forward secrecy, and resistance to key-compromise impersonation. - -## Wide-block encryption - -**Lioness** is a wide-block cipher used for Sphinx packet payloads. It's constructed from ChaCha20 and BLAKE2, encrypting the entire payload as a single block. This property is essential for Sphinx: modifying any part of the payload invalidates the entire payload, preventing certain manipulation attacks. - -The Lioness implementation is part of the external [`sphinx-packet`](https://github.com/nymtech/sphinx) crate used by Nym. - -## Hashing - -**BLAKE2** variants are used in the WireGuard Noise handshake (BLAKE2s) and in Lioness payload encryption (BLAKE2b via the sphinx-packet crate). - -**BLAKE3** is used for modern key derivation in the KKT protocol and data observatory components. - -**SHA-256** and **SHA-512** appear where compatibility with Cosmos SDK, HKDF, and standard tooling is required. - -## Key derivation - -**HKDF** (HMAC-based Key Derivation Function, RFC 5869) derives session keys from shared secrets. Both HKDF-SHA-256 and HKDF-SHA-512 variants are used, with HKDF-SHA-512 as the primary variant for `DerivationMaterial` in the SDK. - -**Argon2** is used for password-based key derivation when protecting locally stored keys and credentials. - -## Wallet cryptography - -**Secp256k1** (via the `k256` crate) and **ECDSA** handle transaction signing and key management for the Nyx blockchain, consistent with Cosmos SDK conventions. **BIP32** hierarchical deterministic key derivation supports hardware wallet integration via Ledger. - -## zk-nym cryptography - -The credential system uses **BLS12-381**, a pairing-friendly elliptic curve that enables threshold signatures, signature aggregation, and zero-knowledge proofs. The Nym API Quorum uses BLS for distributed key generation and threshold blind signatures. - -**Pedersen commitments** hide attribute values in credentials while allowing verification. **Zero-knowledge proofs** enable selective disclosure — proving properties about credentials without revealing the credentials themselves. - -## Post-quantum cryptography (in progress) - -The classical algorithms used today (Curve25519, BLS12-381) would be vulnerable to a sufficiently powerful quantum computer. Work is underway in the **KKT** (Key KEM Transport) module to add hybrid post-quantum key encapsulation using two NIST-standardised or finalist algorithms: - -- **ML-KEM** (formerly CRYSTALS-Kyber) — a lattice-based KEM, now a NIST standard (FIPS 203) -- **Classic McEliece** — a code-based KEM with decades of cryptanalysis behind it - -Both are available via the `libcrux` cryptographic library. The hybrid construction pairs these with classical X25519, so the system remains secure even if one primitive is broken. Post-quantum support will ship as part of the Lewes Protocol, which is currently in development. - -## References - -- [Sphinx paper](https://cypherpunks.ca/~iang/pubs/Sphinx_Oakland09.pdf) — Original Sphinx specification -- [Coconut paper](https://arxiv.org/pdf/1802.07344) — Credential scheme foundation -- [Offline Ecash paper](https://arxiv.org/pdf/2303.08221) — Compact ecash construction -- [WireGuard protocol](https://www.wireguard.com/protocol/) — dVPN tunnel specification -- [Noise protocol](http://www.noiseprotocol.org/) — Authenticated key exchange framework -- [Nym Whitepaper](https://nym.com/nym-whitepaper.pdf) — Full protocol description -- [Nym Trust Center: Cryptography](https://nym.com/trust-center/cryptography) — Up-to-date cryptographic overview - --- title: Sphinx Packet Format description: How Sphinx packets provide layered encryption for anonymous mixnet routing, with fixed-size payloads, per-hop key derivation, and integrity verification via HMACs. url: https://nym.com/docs/network/cryptography/sphinx --- -# Sphinx Packets +# Sphinx Sphinx is the cryptographic packet format used for all mixnet traffic. It provides layered encryption where each hop can only decrypt its own routing information, ensuring that no single node knows both the source and destination of a packet. ## How Sphinx works -When a client sends a message through the mixnet, it constructs a Sphinx packet with multiple encryption layers—one for each hop in the route. The outermost layer is encrypted for the first hop (Entry Gateway), the next layer for the second hop (Mix Node Layer 1), and so on until the innermost layer contains the actual payload encrypted for the recipient. +When a client sends a message through the mixnet, it constructs a Sphinx packet with multiple encryption layers, one for each hop in the route. The outermost layer is encrypted for the first hop (Entry Gateway), the next layer for the second hop (Mix Node Layer 1), and so on until the innermost layer contains the actual payload encrypted for the recipient. At each hop, the node uses its private key to decrypt its layer, revealing the address of the next hop and a new Sphinx packet to forward. The node cannot see any other routing information or the payload contents. ## Packet structure -All Sphinx packets have a fixed payload size of 2048 bytes. This uniformity is critical—if packets varied in size, nodes could infer their position in the route or correlate packets by size. +All Sphinx packets have a fixed payload size of 2048 bytes. This uniformity is critical: if packets varied in size, nodes could infer their position in the route or correlate packets by size. The packet contains a header with encrypted routing information for each hop, HMACs to verify integrity at each layer, and the encrypted payload. The header uses a clever "onion" structure where processing at each hop reveals only the next hop's information while maintaining constant size through padding. ## Integrity verification -Each layer includes an HMAC (Hash-based Message Authentication Code) that the receiving node verifies before processing. This prevents malicious nodes from modifying packet contents en route. If the HMAC doesn't match, the packet is dropped. +Each layer includes an HMAC (Hash-based Message Authentication Code) that the receiving node verifies before processing, which prevents malicious nodes from modifying packet contents en route. If the HMAC doesn't match, the packet is dropped. The payload uses Lioness wide-block encryption, which means any modification to any part of the payload invalidates the entire payload. This prevents bit-flipping attacks where an adversary might try to modify specific bytes. @@ -1044,9 +825,9 @@ Nym uses the [`sphinx-packet`](https://github.com/nymtech/sphinx) crate for core ## References -- [Sphinx paper](https://cypherpunks.ca/~iang/pubs/Sphinx_Oakland09.pdf) — Original specification and security proofs -- [Elle Mouton's Sphinx explainer](https://ellemouton.com/posts/sphinx/) — Detailed walkthrough of packet construction -- [Nym Whitepaper §4](https://nym.com/nym-whitepaper.pdf) — Sphinx in the context of Nym +- [Sphinx paper](https://cypherpunks.ca/~iang/pubs/Sphinx_Oakland09.pdf): Original specification and security proofs +- [Elle Mouton's Sphinx explainer](https://ellemouton.com/posts/sphinx/): Detailed walkthrough of packet construction +- [Nym Whitepaper §4](https://nym.com/nym-whitepaper.pdf): Sphinx in the context of Nym --- title: What are zk-nyms? @@ -1071,7 +852,7 @@ However, _who_ is not necessarily a question we want to be asking when designing - Does the entity taking this action have a right to do _X_? -This allows a different kind of security. Many of the computer systems we talk to every day don't need to know _who we are_, they only need to know if the entity kicking off a request has the _right to use_ the system. +This allows a different kind of security. Most networked services do not need to know _who_ is making a request, only whether the requester has the _right to use_ the system. The zk-nym scheme allows for this move to take place. Credentials are generated cooperatively by decentralised, trustless systems, and once the credentials are generated, they can be _re-randomized_; entirely new credentials, which no one has ever seen before, can be presented to the ingress point of the Nym Network, and validated without being linkable back to the signatures produced by the Quorum of credential signers used to generate them, or any credentials previously used by an entity wanting access. These properties allow zk-nyms to act as something like cryptographic bearer tokens generated by decentralised systems. The tokens can be mutated so that they are not traceable, but still verified with the original permissions intact. @@ -1097,7 +878,7 @@ Let's say you have a `message` with the content `This credential controls X` in 4. _[Threshold issuance](https://en.wikipedia.org/wiki/Threshold_cryptosystem)_ - allows signature generation to be split up across multiple nodes and decentralized, so that either all signers need to sign (_n of n_ where _n_ is the number of signers) or only a threshold number of signers need to sign a message (_t of n_ where _t_ is the threshold value). -Taken together, these properties provide privacy for applications when it comes to generating and using signatures for cryptographic claims. If you compare it to existing tech, you might think of it as a sort of supercharged decentralized privacy-friendly [JWT](https://jwt.io/). +Taken together, these properties provide privacy for applications when it comes to generating and using signatures for cryptographic claims. If you compare it to existing tech, the closest analogy in conventional systems is a decentralized, privacy-preserving [JWT](https://jwt.io/). --- title: Generating and using zk-nym anonymous credentials @@ -1106,14 +887,7 @@ url: https://nym.com/docs/network/cryptography/zk-nym/zk-nym-overview # Generating and using zk-nym anonymous credentials - The first use-case of zk-nyms is for anonymously proving the right to use the Nym mixnet for privacy. - - The Nym mixnet is - at the time of publication - free for everyone. However, soon™ it will be required for each connecting client to present a valid credential - a zk-nym - to their ingress Gateway to access the Mixnet. - - Accessing zk-nym credentials will vary depending on use: - - Individual developers building on the mixnet will be able to get zk-nym credentials via something like a faucet. - - Larger application integrations will have their own 'under the hood' credential generation and distribution scheme to generate access credentials on behalf of their users automatically. - - NymVPN users will have a variety of payment methods available to them. The vast majority, if not all of the steps outlined on this page, will happen under the hood from their perspective. _More on this soon_. + zk-nyms are already used in production by [NymVPN](https://nymvpn.com) to unlink subscription payments from network activity. The entire credential lifecycle described on this page (key generation, issuance, spending) happens transparently within the NymVPN application. SDK integrations currently connect to the Mixnet without requiring credentials. Generation of zk-nyms involves the following actors / pieces of infrastructure: - **Requester needing a zk-nym** for example a single user using the NymVPN app, or a company purchasing zk-nyms to distribute to their app users, in the instance of an app integrating a Mixnet client via one of the SDKs. The Requester is represented by a Bech32 address on the Nyx blockchain. @@ -1125,10 +899,10 @@ Generation happens in 3 distinct stages: - Issue credential - Generate unlinkable zk-nyms for Nym Network access -From the perspective of the Requester most of this happens under the hood, but results in the creation and usage of an **unlinkable, rerandomisable anonymous proof-of-payment credential** - a zk-nym - with which to access the Mixnet without fear of doxxing themselves via linking app usage and payment information. The user experience is further enhanced by the fact that a single credential can be split into multiple small zk-nyms, meaning that a Requester may buy a large chunk of bandwidth but 'spend' this in the form of multiple zk-nyms with different ingress Gateways. Whilst this happens under the hood, what it affords the Requester is an ease of experience in that they have to 'top up' their bandwidth less and are able to chop and change ingress points to the Nym Network as they see fit, akin to the UX of most modern day VPNs and dVPNs. +From the Requester's perspective this happens transparently, producing an unlinkable, rerandomisable anonymous proof-of-payment credential (a zk-nym) that grants Mixnet access without linking usage to payment information. A single credential can be split into multiple smaller zk-nyms, so a Requester purchases bandwidth in bulk and spends it incrementally across different ingress Gateways as needed. ## Key Generation & Payment -- First, a Cosmos [Bech32 address](https://docs.cosmos.network/main/build/spec/addresses/bech32) is created for the Requester. This is used to identify themselves when interacting with the OrderAPI via signed authentication tokens. **This is the only identity that the OrderAPI is able to see, and is not able to link this to the zk-nyms that will be generated.** This identity never leaves the Requester's device and there is no email or any personal details needed for signup. If a Requester is simply 'topping up' their subscription, the creation of the address is skipped as it already exists. +- First, a Cosmos [Bech32 address](https://docs.cosmos.network/sdk/latest/guides/reference/bech32#performance-address-caching) is created for the Requester. This is used to identify themselves when interacting with the OrderAPI via signed authentication tokens. **This is the only identity that the OrderAPI is able to see, and is not able to link this to the zk-nyms that will be generated.** This identity never leaves the Requester's device and there is no email or any personal details needed for signup. If a Requester is simply 'topping up' their subscription, the creation of the address is skipped as it already exists. - The Requester also generates an ed25519 keypair: this is used to identify and authenticate them in the case of using zk-nyms across several devices as an individual user. However, **this is never used in the clear**: these keys are used as private attribute values within generated credentials which are verified via zero-knowledge and not publicly exposed. - The Requester can then interact with various payment backends to pay for their zk-nyms with crypto, fiat options, or natively with NYM tokens. @@ -1166,14 +940,12 @@ url: https://nym.com/docs/network/cryptography/zk-nym/rerandomise Each ticket will not be valid for the entire amount of data that the ticketbook aggregated from the PSCs is; if the aggregated ticketbook is worth (e.g.) 10GB of Mixnet data, each ticket will be worth far less (e.g. 100MB). This amount will be globally uniform in order to avoid situations where differently sized tickets allow for patterns to emerge. - The functionality included in the following code block examples were added to the [nym-cli tool](/developers/tools/nym-cli) for illustrative purposes only: this is not necessarily how credentials will be accessed in the future. - - The numbers used in this high level overview are for illustration purposes only. The figures used in production will potentially vary. Note that individual ticket sizes will be uniform across the Network. + The `nym-cli` examples below are for illustration only and do not reflect how credentials are accessed in production. The specific figures (ticket counts, bandwidth amounts) are illustrative; production values may differ, though individual ticket sizes are uniform across the network. ## Why a 'ticketbook', not individual 'tickets', and why not spend them all at once? This is to account for the need for a client to change their ingress Gateway, either because the Gateway itself has gone down / is not offering the required bandwidth, or because a user might simply want to split their traffic across multiple Gateways for extra privacy. -This means that clients are not tied to particular Gateways they have 'spent' their entire subscription amount with; if the ingress Gateway goes down, or the client simply wishes to use another ingress Gateway, the user has multiple other tickets they can use that account for their remaining purchased bandwidth. +Clients are therefore not tied to a particular Gateway they have spent their entire subscription with. If an ingress Gateway goes down, or the client simply wants to use a different one, remaining tickets can be spent with any other Gateway. Going back to the `nym-cli` tool to illustrate this; we can generate multiple unlinkable tickets from a single ticketbook aggregated from PSCs: @@ -1298,12 +1070,12 @@ url: https://nym.com/docs/network/infrastructure # Infrastructure -The Nym Network runs on decentralized infrastructure — a set of independently operated nodes coordinated by the Nyx blockchain. No single party controls routing, key management, or credential issuance. +The Nym Network runs on decentralized infrastructure: a set of independently operated nodes coordinated by the Nyx blockchain, where no single party controls routing, key management, or credential issuance. ## In this section -- [Nyx Blockchain](/network/infrastructure/nyx) — the Cosmos SDK chain that maintains the node registry, manages token economics, and hosts the smart contracts for credentials and rewards -- [Nym Nodes](/network/infrastructure/nym-nodes) — the unified `nym-node` binary that operates as Entry Gateways, Mix Nodes, or Exit Gateways depending on network demand +- [Nyx Blockchain](/network/infrastructure/nyx): the Cosmos SDK chain that maintains the node registry, manages token economics, and hosts the smart contracts for credentials and rewards +- [Nym Nodes](/network/infrastructure/nym-nodes): the unified `nym-node` binary that operates as Entry Gateways, Mix Nodes, or Exit Gateways depending on network demand --- title: Nyx Blockchain @@ -1319,9 +1091,9 @@ To interact with the chain, see [Interacting with Nyx](/developers/chain). To ru ## Role in the network -The blockchain serves several functions. It maintains the **topology registry**—the list of active nodes and their public keys. This eliminates the need for a centralized directory server and prevents attacks that plague peer-to-peer directory systems. +The blockchain serves several functions, including maintaining the **topology registry**: the list of active nodes and their public keys. This eliminates the need for a centralized directory server and prevents attacks that plague peer-to-peer directory systems. -It manages **token economics**. The NYM token is a native token of the chain, used for staking, rewards, and credential payments. Validators secure the chain via proof-of-stake consensus. +It manages **token economics**, where the NYM token is a native token of the chain, used for staking, rewards, and credential payments. Validators secure the chain via proof-of-stake consensus. And it hosts **smart contracts** for mixnet coordination and the zk-nym credential system. @@ -1333,7 +1105,7 @@ Nyx Validators run the `nyxd` binary to maintain the blockchain. They process tr For setup instructions, see the [Nym API Operator Guide](/operators/nodes/validator-setup/nym-api). -The Nym API is operated by a subset of validators forming the "Quorum." This group performs network monitoring—sending test packets through the mixnet and calculating reliability scores for nodes. More critically, it handles credential issuance, generating the partial blind signatures that form zk-nyms. +The Nym API is operated by a subset of validators forming the "Quorum." This group performs network monitoring: sending test packets through the mixnet and calculating reliability scores for nodes. More critically, it handles credential issuance, generating the partial blind signatures that form zk-nyms. The Quorum uses threshold cryptography. No single member can issue credentials alone. The system remains functional even if some members are offline. This distributes trust across multiple independent parties. @@ -1354,23 +1126,23 @@ url: https://nym.com/docs/network/infrastructure/nym-nodes # Nym Nodes -All traffic-routing infrastructure runs on the `nym-node` binary. This unified binary operates in different modes—Entry Gateway, Mix Node, or Exit Gateway—simplifying deployment and enabling future dynamic role assignment. +All traffic-routing infrastructure runs on the `nym-node` binary. This unified binary operates in different modes (Entry Gateway, Mix Node, or Exit Gateway), simplifying deployment and enabling future dynamic role assignment. To run a node, see the [Operator Documentation](/operators/introduction). ## Node modes -**Entry Gateways** are the user's first point of contact with the network. They accept WebSocket connections from clients, verify zk-nym credentials to confirm payment, and store messages for clients that go offline (up to 24 hours). Entry Gateways know the client's IP address but cannot see message contents or final destinations. +**Entry Gateways** are the user's first point of contact with the network. They accept WebSocket connections from clients, verify zk-nym credentials to confirm payment, and store messages for clients that go offline. Entry Gateways know the client's IP address but cannot see message contents or final destinations. **Mix Nodes** form the three mixing layers that provide core privacy. They receive Sphinx packets, remove one encryption layer, verify integrity, apply a random delay, and forward to the next hop. Mix Nodes cannot determine their position in the route and cannot link incoming packets to outgoing packets. -**Exit Gateways** handle traffic leaving the mixnet. They communicate with external internet services on behalf of users and return responses through the network. Exit Gateways can see destination addresses but cannot identify the original sender. +**Exit Gateways** handle traffic leaving the mixnet. They run two proxy services: the [Network Requester](/network/infrastructure/exit-services#network-requester) (a SOCKS proxy for application-layer requests) and the [IP Packet Router](/network/infrastructure/exit-services#ip-packet-router) (a raw IP tunnel used by NymVPN and smolmix). Exit Gateways can see destination addresses but cannot identify the original sender. See [Exit Gateway Services](/network/infrastructure/exit-services) for details. ## Unified binary -The various components were originally separate binaries. They've been consolidated into a single `nym-node` binary where the role is specified at runtime. This simplifies operation and makes configuration consistent across roles. +The various components were originally separate binaries but have been consolidated into a single `nym-node` binary where the role is specified at runtime. This simplifies operation and makes configuration consistent across roles. -In the future, nodes will automatically switch modes based on network conditions. Operators won't need to manually set whether a node is a Gateway or Mix Node—the network will assign modes dynamically each epoch. +In the future, nodes will automatically switch modes based on network conditions. Operators won't need to manually set whether a node is a Gateway or Mix Node; the network will assign modes dynamically each epoch. ## Nym clients @@ -1384,6 +1156,85 @@ Client types include native Rust clients, WASM clients for browsers, the SOCKS5 The current deployment includes {stats.nodes} active nodes across {stats.locations} countries, operated by independent parties worldwide. This includes {stats.mixnodes} Mix Nodes and {stats.exit_gateways} Exit Gateways. Running a node requires meeting minimum hardware specifications, bonding NYM tokens as collateral, and maintaining high uptime for rewards. +--- +title: Exit Gateway Services: Network Requester & IP Packet Router +description: The two proxy services running on Nym Exit Gateways, the Network Requester (SOCKS proxy) and the IP Packet Router (raw IP tunneling), how they work, what they see, and who uses them. +url: https://nym.com/docs/network/infrastructure/exit-services +--- + +# Exit Gateway Services + +Exit Gateways are where traffic leaves the Nym network and reaches the wider internet. Each Exit Gateway runs two distinct proxy services that handle different kinds of outbound traffic: + +- **Network Requester (NR)**, an application-layer SOCKS proxy +- **IP Packet Router (IPR)**, a raw IP tunnel with address allocation + +Both services run on every Exit Gateway. Which one handles your traffic depends on how you connect. + +## Network Requester + +The Network Requester is a SOCKS4/4a/5 proxy. Clients send SOCKS-formatted requests through the mixnet, and the NR makes the corresponding connection on their behalf: resolving hostnames, opening TCP connections, and relaying data. + +```text +Client → mixnet → Exit Gateway (NR) → SOCKS connect → destination + ← relay response ← +``` + +Because it operates at the application layer, the NR: +- Resolves DNS on behalf of the client (the client sends hostnames, not IPs) +- Opens individual TCP connections per SOCKS request +- Can enforce allow/deny lists on destination hosts and ports +- Sees the destination hostname and port, but not the contents if TLS is used + +**Used by:** the [SDK's SOCKS client](/developers/rust/mixnet), [standalone SOCKS5 client](/developers/clients/socks5), and [mixFetch](/developers/typescript#mixfetch) (which wraps SOCKS requests in a browser-friendly `fetch` API). + +## IP Packet Router + +The IP Packet Router operates at the IP layer. Instead of proxying individual connections, it allocates a virtual IP address to the client and routes raw IP packets between the client and the internet, functioning as a tunnel endpoint. + +```text +Client → mixnet → Exit Gateway (IPR) → raw IP packets → destination + ← raw IP packets ← +``` + +On connection, the IPR: +1. Allocates an IPv4/IPv6 address pair to the client +2. Accepts raw IP packets (TCP, UDP, or any IP protocol) from the client via the mixnet +3. Sends them to the internet from the gateway's own IP address +4. Routes response packets back through the mixnet to the client + +Because it operates at the IP layer, the IPR: +- Does not resolve DNS; the client handles its own DNS (either via clearnet or by sending DNS queries as UDP packets through the tunnel) +- Handles any IP protocol, not just TCP: UDP, ICMP, etc. +- Sees raw IP packets, including destination IPs and ports +- Does not see contents if the client uses TLS or another encryption layer + +In both services, traffic between the Exit Gateway and the destination travels as **normal internet traffic**. The mixnet protects sender anonymity (the destination sees the gateway's IP, not yours), but does not encrypt the final hop. Use TLS or another end-to-end encryption layer to protect payload confidentiality. + +**Used by:** [NymVPN anonymous mode](/network/dvpn-mode/protocol) (5-hop mixnet routing to the IPR), and [`smolmix`](/developers/smolmix) (programmatic `TcpStream`/`UdpSocket` access to the IPR via the Rust SDK). + +## Comparison + +| | Network Requester | IP Packet Router | +|---|---|---| +| **Layer** | Application (SOCKS) | IP (raw packets) | +| **Protocols** | TCP only | TCP, UDP, any IP protocol | +| **DNS** | Resolved by the NR | Client resolves its own | +| **Client gets** | Proxied connections | An allocated IP address | +| **Connection model** | Per-request | Persistent tunnel | +| **Used by** | SDK SOCKS client, mixFetch | NymVPN (anonymous mode), smolmix | + +## Trust model + +Both services share the same fundamental trust property: **the Exit Gateway can see destinations but not senders.** The mixnet's layered encryption ensures that the Exit Gateway cannot determine who sent a given packet; it only knows where it's going. + +Specifically, the Exit Gateway: +- **Can see:** destination IP/hostname, destination port, unencrypted payload content, traffic volume and timing at the exit hop +- **Cannot see:** the sender's IP address, the sender's Nym address, which Entry Gateway the traffic entered through +- **Cannot determine:** the linkage between different requests from the same sender (unless the payload itself contains identifying information) + +The sender's identity is protected by the mixnet's 5-hop routing, Sphinx encryption, cover traffic, and packet mixing. The Exit Gateway is the last hop: it decrypts the final Sphinx layer and sees the destination, but the chain of Mix Nodes between Entry and Exit has destroyed any timing or ordering correlation. + --- title: Nym Network Reference description: Technical specifications and protocol details for the Nym Network: addressing format, epoch timing, and the hop-by-hop acknowledgement system. @@ -1396,9 +1247,9 @@ Technical specifications and protocol details that apply across the Nym Network ## In this section -- [Addressing](/network/reference/addressing) — the `identity.encryption@gateway` address format and how routing works -- [Epochs](/network/reference/epochs) — time divisions in the network, reward distribution, and topology reshuffling -- [Acknowledgements](/network/reference/acks) — the hop-by-hop packet delivery confirmation system +- [Addressing](/network/reference/addressing): the `identity.encryption@gateway` address format and how routing works +- [Epochs](/network/reference/epochs): time divisions in the network, reward distribution, and topology reshuffling +- [Acknowledgements](/network/reference/acks): the hop-by-hop packet delivery confirmation system --- title: Nym Network Addressing @@ -1418,7 +1269,7 @@ A Nym address has three parts separated by dots and an @ symbol: .@ ``` -The **identity key** identifies the client for routing purposes. It's derived from the client's Ed25519 keypair and base58-encoded for readability. +The **identity key** identifies the client for routing purposes and is derived from the client's Ed25519 keypair and base58-encoded for readability. The **encryption key** is the public key used to encrypt the final layer of Sphinx packets destined for this client. Only the client holding the corresponding private key can decrypt messages addressed to them. @@ -1448,11 +1299,11 @@ url: https://nym.com/docs/network/reference/epochs # Epochs -Time in the Nym Network is organized into epochs—discrete periods during which certain network operations occur. The current epoch length is one hour. +Time in the Nym Network is organized into epochs: discrete periods during which certain network operations occur. The current epoch length is one hour. ## What happens at epoch boundaries -**Reward distribution** calculates performance metrics for each node and distributes NYM token rewards based on routing reliability and uptime. Nodes that successfully forward packets earn more than those with poor performance. +**Reward distribution** calculates performance metrics for each node and distributes NYM token rewards based on routing reliability and uptime, ensuring that nodes successfully forwarding packets earn more than those with poor performance. **Topology rerandomization** shuffles the arrangement of nodes in each layer. This prevents long-term route prediction attacks and limits the damage from any compromised nodes. Nodes may also enter or leave the active set based on uptime monitoring and stake changes. @@ -1462,7 +1313,7 @@ In upcoming releases, epochs will trigger automatic role assignment. Nodes will ## SURB validity -SURBs are tied to key rotation cycles. Node keys rotate on an odd/even schedule with a default validity of 24 epochs. A SURB remains usable for `(validity_epochs + 1) * epoch_duration` — roughly 25 hours at the current 1-hour epoch. After that, the routing keys it was built with are no longer accepted by the network. Clients automatically purge stale SURBs and request fresh ones. +SURBs are tied to key rotation cycles. Node keys rotate on an odd/even schedule with a default validity of 24 epochs. A SURB remains usable for `(validity_epochs + 1) * epoch_duration`, roughly 25 hours at the current 1-hour epoch. After that, the routing keys it was built with are no longer accepted by the network. Clients automatically purge stale SURBs and request fresh ones. ## Querying epoch information @@ -1486,7 +1337,7 @@ This happens automatically at each hop. If a client sends 100 packets to a Gatew ## Why it matters -Network conditions can cause packet loss—congestion, temporary failures, connectivity issues. Without acks and retransmission, lost packets would mean lost messages. The acknowledgement system ensures reliable delivery despite imperfect network conditions. +Network conditions can cause packet loss: congestion, temporary failures, connectivity issues. Without acks and retransmission, lost packets would mean lost messages. The acknowledgement system ensures reliable delivery despite imperfect network conditions. ## Scope @@ -1494,7 +1345,9 @@ Acknowledgements operate hop-by-hop between adjacent nodes. They confirm that pa ## Implementation -This is handled entirely by the Nym binaries. Developers and operators don't need to implement or configure acknowledgements—the system handles packet loss transparently. +This is handled entirely by the Nym binaries. Developers and operators don't need to implement or configure acknowledgements; the system handles packet loss transparently. + +**Lewes Protocol:** The upcoming Lewes release will introduce changes to how acknowledgements are handled. The current hop-by-hop ACK mechanism described above may be revised as part of broader protocol improvements. Details will be documented here once the changes are finalised. --- title: Licensing @@ -1548,15 +1401,16 @@ Build applications that protect user metadata using the Nym Mixnet. This section ## Where to start -**Choosing an integration approach** — read [Integrations](./integrations) to understand the architectural trade-offs (native SDK vs proxy vs mixFetch), then pick your path: +**Choosing an integration approach:** read [Integrations](/developers/integrations) to understand the architectural trade-offs (native SDK vs proxy vs mixFetch), then pick your path: -- **[Rust SDK](./rust)** — full-featured SDK with message passing, `AsyncRead`/`AsyncWrite` streams, and client pooling. Start with the [Tour](./rust/tour). -- **[TypeScript SDK](./typescript)** — browser and Node.js SDK for mixFetch, Mixnet client, and smart contract interaction. -- **[Standalone Clients](./clients)** — language-agnostic SOCKS5 and WebSocket proxies for piping traffic through the Mixnet without an SDK. +- **[Rust SDK](/developers/rust):** full-featured SDK with message passing, `AsyncRead`/`AsyncWrite` streams, and client pooling. Start with the [Tour](/developers/rust/tour). +- **[`smolmix`](/developers/smolmix):** standalone crate providing `TcpStream` and `UdpSocket` over the Mixnet via a userspace IP stack. Drop-in compatible with `tokio-rustls`, `hyper`, `tungstenite`, and the rest of the async Rust ecosystem. Also serves as the core for companion crates that plug into specific frameworks (e.g. hyper connectors, DNS resolvers). +- **[TypeScript SDK](/developers/typescript):** browser and Node.js SDK for mixFetch, Mixnet client, and smart contract interaction. +- **[Standalone Clients](/developers/clients):** language-agnostic SOCKS5 and WebSocket proxies for piping traffic through the Mixnet without an SDK. ## Blockchain interaction -The Nym Network is coordinated by the [Nyx blockchain](/network/infrastructure/nyx). To query chain state, submit transactions, or interact with smart contracts, see [Chain Interaction](./chain). +The Nym Network is coordinated by the [Nyx blockchain](/network/infrastructure/nyx). To query chain state, submit transactions, or interact with smart contracts, see [Chain Interaction](/developers/chain). ## API reference @@ -1574,15 +1428,16 @@ Build applications that protect user metadata using the Nym Mixnet. This section ## Where to start -**Choosing an integration approach** — read [Integrations](./integrations) to understand the architectural trade-offs (native SDK vs proxy vs mixFetch), then pick your path: +**Choosing an integration approach:** read [Integrations](/developers/integrations) to understand the architectural trade-offs (native SDK vs proxy vs mixFetch), then pick your path: -- **[Rust SDK](./rust)** — full-featured SDK with message passing, `AsyncRead`/`AsyncWrite` streams, and client pooling. Start with the [Tour](./rust/tour). -- **[TypeScript SDK](./typescript)** — browser and Node.js SDK for mixFetch, Mixnet client, and smart contract interaction. -- **[Standalone Clients](./clients)** — language-agnostic SOCKS5 and WebSocket proxies for piping traffic through the Mixnet without an SDK. +- **[Rust SDK](/developers/rust):** full-featured SDK with message passing, `AsyncRead`/`AsyncWrite` streams, and client pooling. Start with the [Tour](/developers/rust/tour). +- **[`smolmix`](/developers/smolmix):** standalone crate providing `TcpStream` and `UdpSocket` over the Mixnet via a userspace IP stack. Drop-in compatible with `tokio-rustls`, `hyper`, `tungstenite`, and the rest of the async Rust ecosystem. Also serves as the core for companion crates that plug into specific frameworks (e.g. hyper connectors, DNS resolvers). +- **[TypeScript SDK](/developers/typescript):** browser and Node.js SDK for mixFetch, Mixnet client, and smart contract interaction. +- **[Standalone Clients](/developers/clients):** language-agnostic SOCKS5 and WebSocket proxies for piping traffic through the Mixnet without an SDK. ## Blockchain interaction -The Nym Network is coordinated by the [Nyx blockchain](/network/infrastructure/nyx). To query chain state, submit transactions, or interact with smart contracts, see [Chain Interaction](./chain). +The Nym Network is coordinated by the [Nyx blockchain](/network/infrastructure/nyx). To query chain state, submit transactions, or interact with smart contracts, see [Chain Interaction](/developers/chain). ## API reference @@ -1590,19 +1445,32 @@ Auto-generated API specs for Nym infrastructure endpoints are in the [APIs secti --- title: Integrating With Nym +description: Choose an integration path for sending application traffic through the Nym mixnet, depending on your runtime environment and architecture. url: https://nym.com/docs/developers/integrations --- # Integrating With Nym -Any application that wants to integrate with Nym involves sending its application traffic through the Mixnet using one of the available Nym Clients. There is no single solution for this, as different environments offer different access and transport options (e.g. if operating in a web browser, you do not have access to syscalls or sockets, have to deal with content security policies, etc). -As such, we have several solutions available for developers to choose from depending on the **environment** their application is expected to run in: native apps which are running on a desktop, or webapps running in a browser. +Any application that integrates with Nym sends its traffic through the Mixnet via a Nym client. The right integration path depends on two factors: **environment** and **architecture**. - The list of current options available to developers to do not cover all environments and setups - we are working on expanding this list and approaching more general solutions, but there is no one-size-fits-all approach when dealing with rerouting network traffic. +## Environment -Integration options are then further subdivided by app **architecture**; whether the application interacts with remote hosts on the public internet running independently of the app (e.g. public blockchain RPC endpoints, third-party APIs) or whether app developers have some control over the versions of the software being run on both sides of an interaction (e.g. peer to peer apps running the same software version, or client-server architectures which are running software written by the same team). +Different runtimes have different transport constraints: a browser cannot open raw sockets or access the filesystem, while a desktop app can. - This is because of the different security considerations each option offers. These are detailed in the following pages. +- **Native / Desktop**: full access to system networking and persistent storage. Use the [Rust SDK](/developers/rust) or [`smolmix`](/developers/smolmix). +- **Browser**: restricted to WebSockets, Web Transport, and `fetch`, with HTTPS-only mixed content rules and no filesystem access. Use the [TypeScript SDK](/developers/typescript). + +## Architecture + +The second factor is whether you control both sides of the communication. + +**End-to-end (E2E)**: both sides run Nym clients. All traffic stays Sphinx-encrypted the entire way. Appropriate for peer-to-peer setups or any case where you control both endpoints. + +**Proxy**: only the client side runs Nym. Traffic exits the Mixnet at an Exit Gateway and continues to the destination as normal internet traffic. Appropriate when connecting to third-party services (blockchain RPCs, external APIs) that you do not control. For Rust, [`smolmix`](/developers/smolmix) provides `TcpStream` and `UdpSocket` types that work as drop-in replacements for their tokio equivalents. + +In proxy mode, the last hop from Exit Gateway to the remote host travels as standard internet traffic. This is weaker than E2E against a global passive adversary, but still provides timing obfuscation and sender-receiver unlinkability. + +See the [Native / Desktop](/developers/native) and [Browser](/developers/browsers) pages for the specific modules available in each environment. --- title: Native and Desktop App Integration @@ -1612,52 +1480,36 @@ url: https://nym.com/docs/developers/native # Native / Desktop Apps -Developers wanting to integrate into desktop apps and CLIs can use our [Rust SDK](./rust). There are two broad approaches to using the Mixnet (E2E or as a proxy), with different modules suited for each. +Desktop apps and CLIs integrate with two broad approaches: embedding Nym clients on both sides of the communication (E2E), or using the Mixnet as a proxy to reach external services. -## Option 1: Mixnet End-To-End -Embed Nym Clients in both sides of your app and have them send all network traffic through the Mixnet: a peer-to-peer setup, or a client and server where you control both sides. +## Mixnet End-To-End +Both sides of your app run Nym clients. All traffic stays Sphinx-encrypted the entire way. Works for peer-to-peer setups or any case where you control both ends. ![](/images/developers/nym-arch-client-to-client.png) -### Stream Module -The [Stream module](./rust/stream) provides `AsyncRead + AsyncWrite` byte streams multiplexed over the mixnet. If you're used to working with TCP sockets, this is the closest analog — open a stream, read and write bytes. +| SDK Module | What it does | Status | Links | +|---|---|---|---| +| **Stream** | `AsyncRead + AsyncWrite` byte streams multiplexed over the mixnet, the closest analogue to TCP sockets | Recommended | [docs](/developers/rust/stream) · [tutorial](/developers/rust/stream/tutorial) | +| **Mixnet** | Raw message API and `MixnetClient` for full control over the communication model | Stable | [docs](/developers/rust/mixnet) · [tutorial](/developers/rust/mixnet/tutorial) | +| **Client Pool** | Pre-connected client pool for bursty workloads | Stable | [docs](/developers/rust/client-pool) | +| **TcpProxy** | Localhost TCP sockets that proxy traffic through the mixnet | Unmaintained | [docs](/developers/rust/tcpproxy) | -- [docs](./rust/stream) -- [tutorial](./rust/stream/tutorial) +**TcpProxy is unmaintained.** Use the [Stream module](/developers/rust/stream) for new projects. -### Mixnet & Client Pool Modules -The [Mixnet module](./rust/mixnet) exposes the raw message-based API and `MixnetClient`. The [Client Pool](./rust/client-pool) pre-creates clients in the background for bursty traffic patterns. - -Use these when you need full control over the communication model, or when you're building custom connection logic on top of the raw message API. - -- [docs](./rust/mixnet) -- [tutorial](./rust/mixnet/tutorial) - -### TcpProxy Module (Unmaintained) - -**This module is unmaintained.** Use the [Stream module](./rust/stream) for new projects. Existing users should plan to migrate when possible. - -A pair of abstractions that expose localhost TCP sockets for proxying traffic through the mixnet. - -- [docs](./rust/tcpproxy) - -## Option 2: Mixnet-As-Proxy -For developers who can only control the client side, and need to communicate with a 3rd party service such as a public blockchain RPC or a remote host they do not control. +## Mixnet-As-Proxy +For cases where you only control the client side and need to reach a third-party service such as a blockchain RPC or remote API. ![](/images/developers/nym-arch-ip-routing.png) -### Security Considerations +Traffic is Sphinx-encrypted until the Exit Gateway, where it's unwrapped into HTTPS ([Network Requester](/network/infrastructure/exit-services#network-requester)) or raw IP ([IP Packet Router](/network/infrastructure/exit-services#ip-packet-router)). The last hop to the remote host **travels as normal internet traffic**. Use TLS or another encryption layer to protect the final hop. -Since traffic is only packaged as Sphinx until it gets to the Exit Gateway, where it is unwrapped into either HTTPS packets (by a Network Requester) or IP packets (by an IP Packet Router), the last hop between the Gateway and the remote host **travels as normal internet traffic**. +| Standalone Crate | What it does | Links | +|---|---|---| +| **`smolmix`** | Userspace IP tunnel providing `TcpStream` and `UdpSocket` over the mixnet, compatible with the entire async Rust ecosystem. Also serves as the core for companion crates that plug into specific frameworks (e.g. hyper connectors, DNS resolvers) | [docs](/developers/smolmix) | -This option has fewer protections than the E2E option against a global passive adversary, but still grants you timing obfuscation and sender-receiver unlinkability between your client software and whatever service it is interacting with. - -### SOCKS Client -Developers with apps that support SOCKS4, 4a, or 5 can use the Socks Client exposed by the Mixnet module. This uses the Network Requester service of the chosen Exit Gateway to interact with the remote host via the chosen SOCKS proxy protocol. The Network Requester uses SURBs to anonymously reply to the original sender. - -- [docs](./rust/mixnet) - -Development is in progress to allow for this proxy method from native Rust, C, and Go without requiring a separate SOCKS client. Stay tuned. +| SDK Module | What it does | Links | +|---|---|---| +| **SOCKS Client** | SOCKS4/4a/5 proxy via the Exit Gateway's Network Requester. Works with any SOCKS-capable application without code changes, just point it at the local proxy | [docs](/developers/rust/mixnet) | --- title: Browser-Based App Integration @@ -1666,43 +1518,21 @@ url: https://nym.com/docs/developers/browsers --- # Browser-Based Apps -Browsers are a very restricted environment to work in, with limited options for external communications (websockets, Web Transport API, WebRTC), mixed content restrictions (HTTPS-only), and no access to the file system or any syscalls. These aside, the main issue when trying to capture traffic and send it via a different transport - such as the Mixnet - is the lack of access to browser TLS negotiation from JS or the CA certificate store. -This means that the functionality offered by our current browser-based solutions are quite restricted / specific. There are currently two options for interacting with the Mixnet from the browser: `mixFetch`, and the WASM SDK. +Browsers are a restricted environment: communication is limited to WebSockets, Web Transport, and WebRTC; mixed content policies enforce HTTPS-only; and there is no access to the filesystem or system calls. The main obstacle for routing traffic through the Mixnet is the lack of access to browser TLS negotiation or the CA certificate store from JavaScript. + +Two integration options are available, both delivered as packages bundled into your web application. ![](/images/developers/nym-browser-arch.png) -Both `mixFetch` and the WASM client are delivered to the client bundled into a web application. +| Module | What it does | Links | +|---|---|---| +| **mixFetch** | Drop-in `fetch` replacement. Routes HTTP(S) requests via Exit Gateways with an embedded CA store for browser-to-destination TLS over the Mixnet | [docs](/developers/typescript#mixfetch) · [example](/developers/typescript/playground/mixfetch) | +| **WASM Client** | Sphinx packets and cover traffic in WASM, sent over WebSocket to the Entry Gateway. Messaging mode only (text/binary payloads), runs in a web worker | [docs](/developers/typescript#mixnet-client) · [example](/developers/typescript/playground/traffic) | -## mixFetch -Drop-in replacement for browser's `fetch` API that makes HTTP(S) requests via Exit Gateways using the SOCKS Network Requester. +`mixFetch` currently supports a maximum of 10 concurrent in-flight requests. `mixFetchv2`, which will function as a general-purpose userspace IP stack, is in development. -Uses an embedded CA certificate store to establish TLS session between `mixFetch` and the remote host, creating a client-host secure channel from the browser to the host over the Mixnet. - -Internally it uses the WASM client. - -- [docs](./typescript#mixfetch) -- [example](./typescript/playground/mixfetch) - - ### Current Limitations of `mixFetch` - - `mixFetch` can currently only perform 10 concurrent requests (i.e. in-flight requests where a request has been sent to a remote endpoint, but no result has been recieved). - - `mixFetchv2` - which will act more like a general-purpose userspace IP stack - is currently in development. - - It is shipped with a pre-bundled CA store. - -## WASM Client -Makes Sphinx packets and cover traffic using WASM and sent over a Websocket to the Entry Gateway and receive responses. - -This only works in messaging mode (i.e. messages sent either as text or binary data), and currently doesn’t support making IP packets that are routed to the Internet by an Exit Gateway IPR, nor does it currently expose any stream-like API. If you want to send HTTP(S) requests, use `mixFetch`. - -Note that the limitations of CSPs and Mixed Content restrictions (i.e HTTPS only) apply to the Websocket connection as normal in browsers or embedded WebViews. - -Runs in a web worker to leave UI thread free for the user. - -- [docs](./typescript#mixnet-client) -- [example](./typescript/playground/traffic) +The WASM Client does not support IP packet routing (IPR) or stream-like APIs. For HTTP(S) requests from the browser, use `mixFetch`. Standard browser CSP and mixed content restrictions (HTTPS only) apply to the WebSocket connection. --- title: Nym Client Message Queue and Cover Traffic @@ -1712,7 +1542,7 @@ url: https://nym.com/docs/developers/concepts/message-queue # Message Queue - Although good to understand how the Nym Client works under the hood, this information is only of practical use if you're using the [`Mixnet`](../rust/mixnet) module of the Rust SDK and interacting with the client at a low level. Most of this is abstracted away by the [`Stream`](../rust/stream) module (`AsyncRead + AsyncWrite` channels) and the [`TcpProxy`](../rust/tcpproxy) module (TCP tunnelling with message ordering). + Although useful for understanding how the Nym Client works internally, this information is only of practical use if you are using the [`Mixnet`](/developers/rust/mixnet) module of the Rust SDK and interacting with the client at a low level. Most of this is abstracted away by the [`Stream`](/developers/rust/stream) module (`AsyncRead + AsyncWrite` channels) and the [`TcpProxy`](/developers/rust/tcpproxy) module (TCP tunnelling with message ordering). ## Sphinx Packet Streams Clients, once connected to the Mixnet, **are always sending traffic into the Mixnet**; as well as the packets that you as a developer are sending from your application logic, they send [cover traffic](/network/mixnet-mode/cover-traffic) at a constant rate defined by a Poisson process. This is part of the network's mitigation of timing attacks. @@ -1783,10 +1613,136 @@ sequenceDiagram When passing a message to a client (however you do it, either piping messages from an app to a standalone client or via one of the `send` functions exposed by the SDKs), you are **putting that message into the queue** to be source encrypted and sent in the future, in order to ensure that traffic leaving the client does so in a manner that to an external observer is uniform / does not create any 'burst' or change in traffic timings that could aid traffic analysis. ## Note on Client Shutdown -Accidentally dropping a client before your message has been sent is something that is possible and should be avoided (see the [troubleshooting guide](../rust/mixnet/troubleshooting) for more on this) but is easy to avoid simply by remembering to: +Accidentally dropping a client before your message has been sent is something that is possible and should be avoided (see the [troubleshooting guide](/developers/rust/mixnet/troubleshooting) for more on this) but is easy to avoid simply by remembering to: - keep your client process alive, even if you are not expecting a reply to your message - (in the case of the SDKs) properly disconnecting your client in order to make sure that the message queue is flushed of Sphinx packets with actual payloads. +--- +title: smolmix: TCP/UDP Over the Nym Mixnet +description: A userspace IP tunnel that provides standard TcpStream and UdpSocket types over the Nym mixnet. Drop-in compatible with async tokio Rust ecosystem. +url: https://nym.com/docs/developers/smolmix +--- + +# smolmix + +`smolmix` is a TCP/UDP tunnel over the Nym mixnet. It uses a userspace network stack [`smoltcp`](https://docs.rs/smoltcp/latest/smoltcp/) to provide `TcpStream` and `UdpSocket` types that work with the async [`tokio`](https://docs.rs/tokio) Rust ecosystem e.g. [`tokio-rustls`](https://docs.rs/tokio-rustls), [`hyper`](https://docs.rs/hyper), [`tokio-tungstenite`](https://docs.rs/tokio-tungstenite), etc. + +The `TcpStream` type implements tokio's `AsyncRead`/`AsyncWrite` traits and `UdpSocket` provides `send_to`/`recv_from` for datagrams. + +```text +┌──────────────────────────────────────────────────────────────┐ +│ Your application (TLS, HTTP, WebSocket, DNS, etc.) │ +│ └─ smolmix::TcpStream / UdpSocket │ +│ └─ smoltcp (userspace TCP/IP) │ +│ └─ Nym mixnet → IPR exit gateway → internet │ +└──────────────────────────────────────────────────────────────┘ +``` + +Traffic exits the mixnet at an [IPR (Internet Packet Router)](/network/infrastructure/exit-services#ip-packet-router) exit gateway. The exit IP is the gateway's, not yours. + +## Runtime and platform support +### Other runtimes +`smolmix` currently requires `tokio`. The internal pipeline is tokio-based: the bridge task that shuttles packets to the mixnet, the Nym SDK's `MixnetClient`, and the [`tokio-smoltcp`](https://docs.rs/tokio-smoltcp) reactor that drives the userspace TCP/IP stack all run on the tokio runtime. + +This means `smolmix` is not compatible with alternative async runtimes like [`smol`](https://docs.rs/smol) or [`async-std`](https://docs.rs/async-std) out of the box. If you need to use `smolmix` from another runtime, the [`async-compat`](https://docs.rs/async-compat) crate can bridge the gap. + +### Non-native `smolmix` +A WASM version of `smolmix` is planned for a future release. + +## Installation + +Add `smolmix` to your `Cargo.toml`: + +```toml +[dependencies] +smolmix = "1.21.0" +nym-bin-common = { version = "1.21.0", features = ["basic_tracing"] } +tokio = { version = "1", features = ["rt-multi-thread", "macros"] } +``` + +`tokio` is a transitive dependency of smolmix, but you need to enable `rt-multi-thread` (smolmix spawns multiple tasks internally) and `macros` (for `#[tokio::main]`). + +`nym-bin-common` is optional but recommended: it sets up [`tracing`](https://docs.rs/tracing) logging so you can see mixnet connection progress. + +**Minimum Rust version:** {RUST_MSRV}+ + +### From Git + +For unreleased changes, import directly from the repository: + +```toml +smolmix = { git = "https://github.com/nymtech/nym", branch = "develop" } +``` + +## When to use smolmix + +| | smolmix | Stream module | mixFetch | SOCKS client | +|---|---|---|---|---| +| **Layer** | Transport (TCP/UDP) | Message (multiplexed streams) | HTTP | TCP (SOCKS proxy) | +| **Controls both sides?** | No (proxy mode) | Yes (E2E) | No (proxy mode) | No (proxy mode) | +| **API** | `TcpStream`, `UdpSocket` | `AsyncRead + AsyncWrite` | `fetch()` drop-in | SOCKS4/5 protocol | +| **Composability** | Full: TLS, HTTP, WebSocket, DNS, etc. stack on top | Byte streams only | HTTP(S) only | Application-dependent | +| **Best for** | Reaching external services from Rust with standard networking | Peer-to-peer / E2E protocols between Nym clients | Browser HTTP requests | Legacy apps with SOCKS support | + +## Security model + +Traffic is Sphinx-encrypted inside the mixnet, but between the exit gateway and the remote host it travels as **normal internet traffic**. Always encrypt the final hop with TLS ([`rustls`](https://docs.rs/rustls)), Noise Protocol ([`snow`](https://docs.rs/snow)), etc. + +### What's protected + +| Segment | Mixnet encryption | What's visible | +|---|---|---| +| Your machine → mixnet entry | Sphinx (layered) | Entry gateway sees your IP but not the destination | +| Inside the mixnet (entry + 3 mix layers + exit) | Sphinx (layered) | Each node only knows prev/next hop | +| Exit gateway (IPR) | Sphinx removed, raw IP packet exposed | IPR sees destination IP + port. Payload depends on your application layer (see below). | +| IPR → remote host | None (Sphinx is mixnet-only) | Remote host sees IPR's IP, not yours | + +The Sphinx encryption is the **mixnet transport layer**: it protects packets as they traverse the mix nodes. At the exit gateway, the Sphinx layers are stripped and the original IP packet is forwarded to the destination. This is analogous to how a Tor exit node or VPN endpoint unwraps its tunnel. + +**What's inside that IP packet is entirely up to you.** If you connect with TLS (as in the [TCP example](https://github.com/nymtech/nym/blob/develop/smolmix/core/examples/tcp.rs)), the IPR sees encrypted TLS ciphertext going to a destination IP: it knows *where* but not *what*. If you send plaintext HTTP, the IPR can read the full request and response. + +### Trust boundaries + +- **You trust the mixnet** to provide unlinkability between sender and receiver. This is enforced cryptographically by the Sphinx packet format and mixing. +- **You trust the IPR exit gateway** in the same way you trust a VPN exit or Tor exit node: it can inspect your raw IP packets. The difference is that the IPR doesn't know *who* is sending the traffic (the mixnet hides your identity). +- **Application-layer encryption closes the gap.** TLS, Noise Protocol, or any authenticated encryption ensures the IPR only sees ciphertext. It can see destination IP and port, but not payload content. + +### Comparison with other privacy tools + +| | smolmix | Tor | VPN | +|---|---|---|---| +| **Exit node sees traffic?** | Yes (encrypt it) | Yes (encrypt it) | Yes (encrypt it) | +| **Exit node knows sender?** | No (mixnet hides identity) | No (onion routing) | Yes (VPN provider knows) | +| **Timing analysis resistance** | Strong (mixing, cover traffic) | Weak (low-latency) | None | +| **UDP support** | Yes | No (TCP only) | Yes | + +## Examples + +Runnable examples in [`smolmix/core/examples/`](https://github.com/nymtech/nym/tree/develop/smolmix/core/examples). Each is self-contained; read the `//!` doc comments at the top of each file for a walkthrough. + +```sh +cargo run -p smolmix --example +``` + +All examples accept `--ipr
` to target a specific exit node (pass a `Recipient` address to `Tunnel::builder().ipr_address()`). + +| Example | Source | What it demonstrates | +|---|---|---| +| UDP | [`udp.rs`](https://github.com/nymtech/nym/blob/develop/smolmix/core/examples/udp.rs) | DNS lookup via [`hickory-proto`](https://docs.rs/hickory-proto), sending a raw UDP query to `1.1.1.1:53` through the mixnet | +| TCP | [`tcp.rs`](https://github.com/nymtech/nym/blob/develop/smolmix/core/examples/tcp.rs) | HTTPS request via [`hyper`](https://docs.rs/hyper) + [`tokio-rustls`](https://docs.rs/tokio-rustls). Fetches Cloudflare's `/cdn-cgi/trace` to show that the exit IP differs from clearnet | +| WebSocket | [`websocket.rs`](https://github.com/nymtech/nym/blob/develop/smolmix/core/examples/websocket.rs) | WebSocket echo via [`tokio-tungstenite`](https://docs.rs/tokio-tungstenite) + [`tokio-rustls`](https://docs.rs/tokio-rustls). Full TCP → TLS → WebSocket stack composing over smolmix | +| UDP multi | [`udp_multi.rs`](https://github.com/nymtech/nym/blob/develop/smolmix/core/examples/udp_multi.rs) | Multiple DNS lookups with timeout handling + NTP time sync, all over mixnet UDP | +| TCP download | [`tcp_download.rs`](https://github.com/nymtech/nym/blob/develop/smolmix/core/examples/tcp_download.rs) | DNS-over-mixnet + multi-request HTTP/1.1 download over a single keep-alive connection, the full real-world pattern | +| WebSocket multi | [`websocket_multi.rs`](https://github.com/nymtech/nym/blob/develop/smolmix/core/examples/websocket_multi.rs) | Burst echo (varying message sizes) + idle survival probes with clearnet baseline. Sustained multi-message WebSocket usage over the mixnet | + +## Architecture + +The internal stack (smoltcp reactor, device adapter, bridge task, packet flow) is documented in [`ARCHITECTURE.md`](https://github.com/nymtech/nym/blob/develop/smolmix/core/src/ARCHITECTURE.md). This is also the crate-level documentation on docs.rs. + +## API reference + +Full API documentation is available on [docs.rs/smolmix](https://docs.rs/smolmix). + --- title: Nym Rust SDK: Privacy Apps for the Mixnet description: Rust SDK reference for building privacy applications on the Nym mixnet. Covers the Mixnet client, Stream multiplexing, Client Pool, and code examples. @@ -1795,25 +1751,23 @@ url: https://nym.com/docs/developers/rust # Rust SDK -The Rust SDK provides high-level abstractions for building privacy-preserving applications on the Nym Mixnet. All modules share a common `MixnetClient` that handles gateway connections, Sphinx packet encryption, routing, and cover traffic under the hood. +All modules share a common `MixnetClient` that manages gateway connections, Sphinx packet encryption, routing, and cover traffic. -Full API reference, architecture documentation, and type details are available on [**docs.rs/nym-sdk**](https://docs.rs/nym-sdk/latest/nym_sdk/). +Full API reference: [**docs.rs/nym-sdk**](https://docs.rs/nym-sdk/latest/nym_sdk/) + +For an overview of what the SDK can do, see the **[Tour](./rust/tour)**. For setup instructions, see [Installation](./rust/importing). ## Modules -- **[Mixnet](./rust/mixnet)** — Core client for sending and receiving individual message payloads through the Mixnet. This is the Mixnet's native communication model — no connections, no ordering, just individually routed payloads. +- **[Stream](./rust/stream)**: multiplexed `AsyncRead + AsyncWrite` byte streams over the Mixnet. **If you're used to TCP sockets, start here.** -- **[Stream](./rust/stream)** — Multiplexed `AsyncRead + AsyncWrite` byte streams over the Mixnet. This is the abstraction layer that bridges the gap between the Mixnet's message-based model and familiar socket-based networking. **If you're used to TCP sockets, start here.** +- **[Mixnet](./rust/mixnet)**: raw message payloads, independently routed, no connections or ordering. Use this when you want full control over the communication model. -- **[TcpProxy](./rust/tcpproxy)** *(deprecated)* — TCP socket proxying through the Mixnet with session management and message ordering. For new projects, use the Stream module instead. +- **[Client Pool](./rust/client-pool)**: keeps ready-to-use `MixnetClient` instances warm for bursty workloads. -- **[Client Pool](./rust/client-pool)** — A connection pool that maintains ready-to-use `MixnetClient` instances for high-throughput applications. +- **[TcpProxy](./rust/tcpproxy)** *(deprecated)*: TCP socket proxying with session management and message ordering. Use Stream for new projects. -- **[FFI](./rust/ffi)** — Foreign function interface bindings for using the SDK from Go and C/C++. - -## Getting started - -New to the SDK? Start with the **[Tour](./rust/tour)** for a quick overview of what you can do, then see [Installation](./rust/importing) for how to add `nym-sdk` to your project. +- **[FFI](./rust/ffi)**: Go and C/C++ bindings. --- title: Tour of the Rust SDK @@ -1824,9 +1778,9 @@ url: https://nym.com/docs/developers/rust/tour A quick walkthrough of the most important things you can do with `nym-sdk`. Each section shows working code and links to the module that covers it in depth. -**The Mixnet is not like regular internet networking.** There are no persistent connections, no guaranteed message ordering, and no TCP underneath. At its core, the Mixnet is a message-based anonymity network — you send individual payloads that are Sphinx-encrypted, mixed through multiple nodes, and independently reconstructed at the destination. +**The Mixnet is not like regular internet networking**: there are no persistent connections, no guaranteed message ordering, and no TCP underneath. At its core, the Mixnet is a message-based anonymity network: you send individual payloads that are Sphinx-encrypted, mixed through multiple nodes, and independently reconstructed at the destination. -This means the raw [message API](./mixnet) works differently from what most developers expect. To bridge that gap, we've built the [Stream module](./stream) — an abstraction layer that gives you familiar `AsyncRead + AsyncWrite` byte streams on top of the Mixnet. **If you're coming from socket-based networking, start with streams.** +The raw [message API](./mixnet) therefore works differently from what most developers expect. The [Stream module](./stream) bridges this gap by providing `AsyncRead + AsyncWrite` byte streams on top of the Mixnet. If you are coming from socket-based networking, start with streams. ## Send a raw message payload @@ -1861,11 +1815,11 @@ async fn main() { The message is Sphinx-encrypted, mixed across 5 nodes, and reconstructed on arrival. The whole round trip takes a few seconds. -**Next:** [Mixnet module](./mixnet) | [Tutorial: Send Your First Private Message](./mixnet/tutorial) +Next: [Mixnet module](./mixnet) | [Tutorial: Send Your First Private Message](./mixnet/tutorial) ## Reply anonymously with SURBs -Every received message carries a `sender_tag` — an opaque token that lets you reply **without knowing the sender's Nym address**. Replies travel back through pre-built Single Use Reply Blocks (SURBs): +Every received message carries a `sender_tag`, an opaque token that lets you reply **without knowing the sender's Nym address**. Replies travel back through pre-built Single Use Reply Blocks (SURBs): ```rust // After receiving a message... @@ -1873,11 +1827,11 @@ let tag = received_msg.sender_tag.expect("message includes sender tag"); client.send_reply(tag, "anonymous reply!").await.unwrap(); ``` -The replying side never learns where the reply is going. This is the foundation of anonymous communication on the Mixnet. +The replying side never learns where the reply is going, enabling anonymous communication without mutual identity disclosure. ## Open a bidirectional stream -If you're used to working with TCP sockets, this is where you'll feel at home. The [Stream module](./stream) provides persistent, bidirectional byte channels that implement tokio's `AsyncRead + AsyncWrite` — so any code that works with sockets works with `MixnetStream`: +If you're used to working with TCP sockets, this is where you'll feel at home. The [Stream module](./stream) provides persistent, bidirectional byte channels that implement tokio's `AsyncRead + AsyncWrite`, so any code that works with sockets works with `MixnetStream`: ```rust use nym_sdk::mixnet; @@ -1898,7 +1852,7 @@ async fn main() { // Receiver accepts it let mut inc = listener.accept().await.unwrap(); - // Standard tokio I/O — write, flush, read + // Standard tokio I/O: write, flush, read out.write_all(b"hello stream").await.unwrap(); out.flush().await.unwrap(); @@ -1915,7 +1869,7 @@ async fn main() { Activating stream mode (by calling `listener()` or `open_stream()`) disables message-based methods like `send_plain_message()` and `wait_for_messages()`. A single client operates in one mode at a time. -**Next:** [Stream module](./stream) | [Tutorial: Build a Private Echo Server](./stream/tutorial) +Next: [Stream module](./stream) | [Tutorial: Build a Private Echo Server](./stream/tutorial) ## Use a client pool for bursty traffic @@ -1942,9 +1896,9 @@ async fn main() { } ``` -Clients are **consumed, not returned** — the pool creates replacements automatically. +Clients are consumed, not returned; the pool creates replacements automatically. -**Next:** [Client Pool module](./client-pool) | [Tutorial: Handle Bursty Traffic](./client-pool/tutorial) +Next: [Client Pool module](./client-pool) | [Tutorial: Handle Bursty Traffic](./client-pool/tutorial) ## Persist your identity @@ -1973,28 +1927,30 @@ println!("Persistent address: {}", client.nym_address()); ## Where to go next -- **[Installation](./importing)** — Add `nym-sdk` to your project -- **[Mixnet Tutorial](./mixnet/tutorial)** — Full walkthrough: send, receive, reply with SURBs -- **[Stream Tutorial](./stream/tutorial)** — Build a private echo server -- **[Client Pool Tutorial](./client-pool/tutorial)** — Handle bursty traffic -- **[API Reference on docs.rs](https://docs.rs/nym-sdk/latest/nym_sdk/)** — Type details, method signatures, architecture docs +- [Installation](./importing): add `nym-sdk` to your project +- [Mixnet Tutorial](./mixnet/tutorial): send, receive, and reply with SURBs +- [Stream Tutorial](./stream/tutorial): build a private echo server +- [Client Pool Tutorial](./client-pool/tutorial): handle bursty traffic +- [API Reference on docs.rs](https://docs.rs/nym-sdk/latest/nym_sdk/): type details, method signatures, architecture docs --- title: Install the Nym Rust SDK -description: Add nym-sdk to your Rust project from crates.io or Git. Covers version requirements, minimum Rust version, and current feature gate status. +description: Add nym-sdk to your Rust project from Git or crates.io. Covers version requirements, minimum Rust version, and current feature gate status. url: https://nym.com/docs/developers/rust/importing --- # Installation -The `nym-sdk` crate is available on [crates.io](https://crates.io/crates/nym-sdk): - ```toml [dependencies] -nym-sdk = "1.20.4" +nym-sdk = "1.21.0" ``` -You can also import directly from the Git repository if you need unreleased changes: +**Minimum Rust version:** {RUST_MSRV}+ + +### From Git + +You can also import directly from Git if you want unreleased changes: ```toml # development branch (latest changes, may be unstable) @@ -2004,9 +1960,7 @@ nym-sdk = { git = "https://github.com/nymtech/nym", branch = "develop" } nym-sdk = { git = "https://github.com/nymtech/nym", branch = "master" } ``` -**Minimum Rust version:** 1.70+ - -**Feature gates are not yet implemented.** Importing `nym-sdk` currently pulls in all modules (mixnet, tcp_proxy, client_pool, etc.) and their full dependency trees. Work is planned to gate modules behind Cargo feature flags so you can import only what you need. +**No feature gates yet.** Importing `nym-sdk` pulls in everything (mixnet, tcp_proxy, client_pool, etc.) and their full dependency trees. Cargo feature flags are planned. --- title: Nym Rust SDK: Mixnet Messaging Module @@ -2018,13 +1972,13 @@ url: https://nym.com/docs/developers/rust/mixnet The `mixnet` module is the core of the Nym SDK. It provides [`MixnetClient`](https://docs.rs/nym-sdk/latest/nym_sdk/mixnet/struct.MixnetClient.html) for connecting to the Nym Mixnet, sending messages through Sphinx packet encryption and 5-hop routing, and receiving reconstructed messages on the other side. -**The Mixnet is not like regular internet networking.** There are no persistent connections, no guaranteed message ordering, and no TCP. Each message is independently Sphinx-encrypted, routed through multiple mix nodes, and reconstructed at the destination. This is the Mixnet's native communication model — powerful for privacy, but different from what most developers expect. If you want familiar socket-like I/O (`read`/`write`), use the [Stream module](./stream) instead — it's an abstraction layer we've built to bridge the gap. +Messages are individually routed through the Mixnet with no guaranteed ordering or persistent connections. If you want familiar socket-like I/O (`read`/`write`), use the [Stream module](./stream) instead. See the [Tour](./tour) for how the two approaches compare. ## Two operating modes The client operates in one of two mutually exclusive modes: -**Message mode** (default) — send and receive raw message payloads: +**Message mode** (default): send and receive raw message payloads: ```rust use nym_sdk::mixnet::{self, MixnetMessageSender}; @@ -2043,14 +1997,14 @@ if let Some(msgs) = client.wait_for_messages().await { client.disconnect().await; ``` -**Stream mode** — persistent `AsyncRead + AsyncWrite` channels. See the [Stream module](./stream) for details. +**Stream mode:** persistent `AsyncRead + AsyncWrite` channels. See the [Stream module](./stream) for details. Stream mode is activated by calling `open_stream()` or `listener()`. Once active, message-mode methods return `Error::StreamModeActive`. This is a one-way transition. ## API reference -- [API reference on docs.rs](https://docs.rs/nym-sdk/latest/nym_sdk/mixnet/) — full architecture documentation, all types, builder methods, traits, and configuration options -- [Examples on GitHub](https://github.com/nymtech/nym/tree/develop/sdk/rust/nym-sdk/examples) — runnable examples covering simple send/receive, builder patterns, custom topologies, SOCKS proxy, anonymous replies, and more +- [API reference on docs.rs](https://docs.rs/nym-sdk/latest/nym_sdk/mixnet/): full architecture documentation, all types, builder methods, traits, and configuration options +- [Examples on GitHub](https://github.com/nymtech/nym/tree/develop/sdk/rust/nym-sdk/examples): runnable examples covering simple send/receive, builder patterns, custom topologies, SOCKS proxy, anonymous replies, and more Run any example with: ```sh @@ -2059,9 +2013,9 @@ cargo run --example ## Next steps -- [Tutorial: Send your first private message](./mixnet/tutorial) — step-by-step guide covering sending, receiving, SURBs, and persistent identity -- [Troubleshooting](./mixnet/troubleshooting) — common issues with logging, empty messages, and client lifecycle -- [Stream module](./stream) — if you need persistent bidirectional byte channels +- [Tutorial: Send your first private message](./mixnet/tutorial): step-by-step guide covering sending, receiving, SURBs, and persistent identity +- [Troubleshooting](./mixnet/troubleshooting): common issues with logging, empty messages, and client lifecycle +- [Stream module](./stream): if you need persistent bidirectional byte channels --- title: Mixnet Tutorial: Send Your First Private Message @@ -2071,22 +2025,9 @@ url: https://nym.com/docs/developers/rust/mixnet/tutorial # Tutorial: Send Your First Private Message -Build a program that connects to the Nym Mixnet, sends a message to yourself, receives it, and replies anonymously using SURBs. Then extend it with persistent identity and concurrent send/receive. +By the end of this tutorial you'll have a working program that sends a Sphinx-encrypted message to itself through the Nym Mixnet, receives it, and replies anonymously using SURBs. The later sections cover persistent identity and concurrent send/receive. -## What you'll learn - -- Connecting an ephemeral client to the Nym Mixnet -- Sending and receiving Sphinx-encrypted messages -- Replying anonymously using SURBs (Single Use Reply Blocks) -- Persisting client identity to disk with `MixnetClientBuilder` -- Using `split_sender()` for concurrent send and receive tasks - -## Prerequisites - -- Rust toolchain (1.70+) -- A working internet connection (clients connect to the live Nym Mixnet) - -Code verified against `nym-sdk` v1.20.4 ([`4077717`](https://github.com/nymtech/nym/commit/4077717d3)). If the API has changed since then, check the [examples in the repo](https://github.com/nymtech/nym/tree/develop/sdk/rust/nym-sdk/examples) for the latest usage. +**You'll need:** Rust {RUST_MSRV}+ and an internet connection (clients connect to the live Mixnet). ## Step 1: Set up the project @@ -2099,7 +2040,8 @@ Add dependencies to `Cargo.toml`: ```toml [dependencies] -nym-sdk = "1.20.4" +nym-sdk = "1.21.0" +nym-bin-common = { version = "1.21.0", features = ["basic_tracing"] } tokio = { version = "1", features = ["full"] } ``` @@ -2112,28 +2054,32 @@ use nym_sdk::mixnet::{self, MixnetMessageSender}; #[tokio::main] async fn main() { - // connect_new() creates an ephemeral client — keys are generated in + nym_bin_common::logging::setup_tracing_logger(); + + // connect_new() creates an ephemeral client: keys are generated in // memory and discarded on disconnect. let mut client = mixnet::MixnetClient::connect_new().await.unwrap(); let our_address = client.nym_address(); println!("Connected: {our_address}"); // The message is Sphinx-encrypted and mixed across 5 nodes. - // send_plain_message only blocks until the message is queued — + // send_plain_message only blocks until the message is queued; // encryption and mixing happen in background tasks. client .send_plain_message(*our_address, "hello from the mixnet!") .await .unwrap(); - println!("Sent — waiting for arrival..."); + println!("Sent, waiting for arrival..."); ``` +`setup_tracing_logger()` shows what the SDK is doing under the hood: gateway connections, topology fetches, Sphinx packet encryption. If the output is too verbose, comment out the line or filter with `RUST_LOG=warn cargo run`. + ## Step 3: Receive ```rust // wait_for_messages() returns the next batch of incoming messages. - // Filter empty messages — these are SURB replenishment requests. + // Filter empty messages: these are SURB replenishment requests. let message = loop { if let Some(msgs) = client.wait_for_messages().await { if let Some(msg) = msgs.into_iter().find(|m| !m.message.is_empty()) { @@ -2147,12 +2093,12 @@ async fn main() { ## Step 4: Reply anonymously -Every message includes a `sender_tag` — an opaque `AnonymousSenderTag` that lets you reply **without knowing the sender's address**. The SDK includes SURBs (Single Use Reply Blocks) with every message by default: +Every message includes a `sender_tag`, an opaque `AnonymousSenderTag` that lets you reply **without knowing the sender's address**. The SDK bundles SURBs (Single Use Reply Blocks) with every outgoing message by default: ```rust let sender_tag = message.sender_tag.expect("should have sender tag"); - // send_reply uses the SURB — the sender's address is never revealed. + // send_reply uses the SURB: the sender's address is never revealed. client.send_reply(sender_tag, "hello back, anonymously!").await.unwrap(); let reply = loop { @@ -2172,12 +2118,12 @@ Every message includes a `sender_tag` — an opaque `AnonymousSenderTag` that le ## Step 5: Run it ```sh -cargo run +RUST_LOG=info cargo run ``` ``` Connected: 8gk4Y...@2xU4d... -Sent — waiting for arrival... +Sent, waiting for arrival... Received: hello from the mixnet! Reply: hello back, anonymously! ``` @@ -2203,27 +2149,44 @@ async fn main() { .await .unwrap(); - println!("Address: {}", client.nym_address()); + let our_address = client.nym_address(); + println!("Address: {our_address}"); - // Same API as before — send, receive, reply. + // Same API as before: send, receive, SURB reply. client - .send_plain_message(*client.nym_address(), "persistent identity!") + .send_plain_message(*our_address, "hello from persistent client!") .await .unwrap(); + println!("Sent, waiting for arrival..."); - if let Some(msgs) = client.wait_for_messages().await { - for m in msgs.into_iter().filter(|m| !m.message.is_empty()) { - println!("Received: {}", String::from_utf8_lossy(&m.message)); + let message = loop { + if let Some(msgs) = client.wait_for_messages().await { + if let Some(msg) = msgs.into_iter().find(|m| !m.message.is_empty()) { + break msg; + } } - } + }; + println!("Received: {}", String::from_utf8_lossy(&message.message)); - // Always disconnect for clean shutdown — background tasks need to be + let sender_tag = message.sender_tag.expect("should have sender tag"); + client.send_reply(sender_tag, "anonymous reply!").await.unwrap(); + + let reply = loop { + if let Some(msgs) = client.wait_for_messages().await { + if let Some(msg) = msgs.into_iter().find(|m| !m.message.is_empty()) { + break msg; + } + } + }; + println!("Reply: {}", String::from_utf8_lossy(&reply.message)); + + // Always disconnect for clean shutdown: background tasks need to be // stopped and state files flushed. client.disconnect().await; } ``` -Run it twice — the address stays the same. +Run it twice; the address stays the same. ## Going further: send and receive from different tasks @@ -2247,7 +2210,7 @@ async fn main() { // split_sender() returns a clone-able MixnetClientSender. let sender = client.split_sender(); - // Spawn a receiver — the original client implements futures::Stream. + // Spawn a receiver: the original client implements futures::Stream. let rx = tokio::spawn(async move { if let Some(msg) = client.next().await { println!("Received: {}", String::from_utf8_lossy(&msg.message)); @@ -2265,39 +2228,27 @@ async fn main() { } ``` -## What's happening under the hood +## What's happening underneath -1. **`connect_new()`** generates an ephemeral identity (ed25519 + x25519 keys), fetches the current network topology, selects a gateway, and opens a persistent WebSocket connection. +`connect_new()` generates an ephemeral identity (ed25519 + x25519 keypair), fetches the current network topology, selects a gateway, and opens a persistent WebSocket connection. `send_plain_message()` wraps the payload in Sphinx packets, layered encryption where each of the 5 Mix Nodes can only decrypt one layer and learn the next hop, never the full route. `wait_for_messages()` drains a local queue fed by the gateway; messages arrive out of order by design, to defeat timing analysis. -2. **`send_plain_message()`** wraps your data in **Sphinx packets** — layered encryption where each of the 5 mix nodes can only decrypt one layer and learn the next hop, never the full route. +SURBs (Single Use Reply Blocks) are pre-computed return routes bundled with each outgoing message. The recipient uses them to reply without learning the sender's address. Each is single-use; the SDK replenishes them automatically. -3. **`wait_for_messages()`** pulls from a local queue that is fed by the gateway. Messages arrive out of order (by design — this prevents timing analysis). - -4. **SURBs** (Single Use Reply Blocks) are pre-computed return routes bundled with each outgoing message. The recipient can reply without learning the sender's address. Each SURB is single-use; the SDK replenishes them automatically. - -5. **`split_sender()`** clones the send channel while the original client retains the receive side. Both can run on separate tokio tasks without locks. - -## What you've learned - -- **`MixnetClient::connect_new()`** creates an ephemeral client with in-memory keys -- **`send_plain_message(recipient, data)`** queues a Sphinx-encrypted message -- **`wait_for_messages()`** returns the next batch of received messages -- **`send_reply(sender_tag, data)`** replies anonymously via SURBs -- **`MixnetClientBuilder` + `StoragePaths`** persists identity to disk -- **`split_sender()`** enables concurrent send/receive on separate tasks -- **Always call `disconnect()`** for clean shutdown +`split_sender()` clones the send channel while the original client retains the receive side. Both halves can run on separate tokio tasks without synchronization. ## Complete code ### Ephemeral client -New address on every run — good for quick experiments: +New address on every run, good for quick experiments: ```rust use nym_sdk::mixnet::{self, MixnetMessageSender}; #[tokio::main] async fn main() { + nym_bin_common::logging::setup_tracing_logger(); + let mut client = mixnet::MixnetClient::connect_new().await.unwrap(); let our_address = client.nym_address(); println!("Connected: {our_address}"); @@ -2306,7 +2257,7 @@ async fn main() { .send_plain_message(*our_address, "hello from the mixnet!") .await .unwrap(); - println!("Sent — waiting for arrival..."); + println!("Sent, waiting for arrival..."); let message = loop { if let Some(msgs) = client.wait_for_messages().await { @@ -2335,14 +2286,17 @@ async fn main() { ### Persistent identity -Same address across restarts — use this for real applications: +Same address across restarts. Use this for real applications: ```rust use nym_sdk::mixnet::{self, MixnetMessageSender, StoragePaths}; #[tokio::main] async fn main() { + nym_bin_common::logging::setup_tracing_logger(); + let paths = StoragePaths::new_from_dir("./my-client-data").unwrap(); + let mut client = mixnet::MixnetClientBuilder::new_with_default_storage(paths) .await .unwrap() @@ -2353,13 +2307,13 @@ async fn main() { .unwrap(); let our_address = client.nym_address(); - println!("Connected: {our_address}"); + println!("Address: {our_address}"); client - .send_plain_message(*our_address, "hello from the mixnet!") + .send_plain_message(*our_address, "hello from persistent client!") .await .unwrap(); - println!("Sent — waiting for arrival..."); + println!("Sent, waiting for arrival..."); let message = loop { if let Some(msgs) = client.wait_for_messages().await { @@ -2371,7 +2325,7 @@ async fn main() { println!("Received: {}", String::from_utf8_lossy(&message.message)); let sender_tag = message.sender_tag.expect("should have sender tag"); - client.send_reply(sender_tag, "hello back, anonymously!").await.unwrap(); + client.send_reply(sender_tag, "anonymous reply!").await.unwrap(); let reply = loop { if let Some(msgs) = client.wait_for_messages().await { @@ -2397,7 +2351,7 @@ url: https://nym.com/docs/developers/rust/mixnet/examples Runnable examples in [`sdk/rust/nym-sdk/examples/`](https://github.com/nymtech/nym/tree/develop/sdk/rust/nym-sdk/examples). Each file is self-contained with step-by-step comments. ```bash -cargo run --example +cargo run --example ``` | Example | Source | What it demonstrates | @@ -2407,6 +2361,12 @@ cargo run --example | Builder | [`builder.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/builder.rs) | Using `MixnetClientBuilder` with ephemeral keys | | Builder with Storage | [`builder_with_storage.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/builder_with_storage.rs) | Persisting keys to disk with `StoragePaths` | | Parallel Send/Receive | [`parallel_sending_and_receiving.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/parallel_sending_and_receiving.rs) | Using `split_sender()` for concurrent tasks | +| Sandbox Testnet | [`sandbox.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/sandbox.rs) | Connecting to the Sandbox testnet instead of mainnet | +| Bandwidth Credential | [`bandwidth.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/bandwidth.rs) | Acquiring a bandwidth credential for paid mixnet access | +| Custom Topology | [`custom_topology_provider.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/custom_topology_provider.rs) | Implementing the `TopologyProvider` trait to filter or customize node selection | +| Overwrite Topology | [`manually_overwrite_topology.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/manually_overwrite_topology.rs) | Manually constructing a topology with hardcoded nodes | +| Control Requests | [`control_requests.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/control_requests.rs) | Sending service provider control requests (health, version, binary info) | +| Custom Storage | [`manually_handle_storage.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/manually_handle_storage.rs) | Implementing custom storage backends for keys, gateways, and credentials | --- title: Mixnet Module Troubleshooting @@ -2424,7 +2384,7 @@ You should always **manually disconnect your client** with `client.disconnect(). ## Waiting for non-empty messages -When listening for a response, you may receive empty messages. These are SURB replenishment requests — the remote side asking for more reply SURBs. Filter them out: +When listening for a response, you may receive empty messages. These are SURB replenishment requests: the remote side asking for more reply SURBs. Filter them out: ```rust let mut message = None; @@ -2436,7 +2396,7 @@ while let Some(new_message) = client.wait_for_messages().await { } ``` -Prefer `client.next().await` (from the `futures::StreamExt` trait — not the Nym Stream module) over `client.wait_for_messages().await` — it returns one message at a time which is easier to work with. You'll need `use futures::StreamExt;` in scope. +Prefer `client.next().await` (from the `futures::StreamExt` trait, not the Nym Stream module) over `client.wait_for_messages().await`; it returns one message at a time which is easier to work with. You'll need `use futures::StreamExt;` in scope. ## Verbose `task client is being dropped` logging @@ -2458,7 +2418,7 @@ If you see these messages unexpectedly, you may be killing the client process to If you see errors like `Polling shutdown failed: channel closed` or panics about `action control task has died`, your client is being dropped before it finishes sending. -`send_plain_message()` is async, but **it only blocks until the message is placed in the client's internal queue** — not until it's actually sent into the Mixnet. After queuing, the client still needs to route-encrypt the message and interleave it with cover traffic. +`send_plain_message()` is async, but **it only blocks until the message is placed in the client's internal queue**, not until it's actually sent into the Mixnet. After queuing, the client still needs to route-encrypt the message and interleave it with cover traffic. Make sure the program stays alive long enough. In practice this means awaiting a response or calling `sleep` before disconnecting: @@ -2479,7 +2439,7 @@ client.disconnect().await; ## Lots of `duplicate fragment received` messages -`WARN` level logs about duplicate fragments are caused by Mixnet-level packet retransmission — the original and the retransmitted copy both arrive. This is not a bug in your client logic. +`WARN` level logs about duplicate fragments are caused by Mixnet-level packet retransmission: the original and the retransmitted copy both arrive. This is not a bug in your client logic. --- title: Stream Module: AsyncRead/AsyncWrite Over the Mixnet @@ -2489,50 +2449,60 @@ url: https://nym.com/docs/developers/rust/stream # Stream Module -The Mixnet is fundamentally a message-based anonymity network — no persistent connections, no guaranteed ordering, no TCP. The default [message API](./mixnet) works at this native level: individual payloads sent independently through mix nodes. This is powerful for privacy, but it's not how most networking code works. +The Mixnet is fundamentally message-based: no persistent connections, no guaranteed ordering, no TCP. The default [message API](./mixnet) works at this level, sending individual payloads independently through Mix Nodes. This is effective for privacy but unlike how most networking code is structured. -The **Stream module** bridges that gap. It gives you persistent, bidirectional byte channels that look and feel like TCP sockets. Each `MixnetStream` implements Rust's standard [`AsyncRead`](https://docs.rs/tokio/latest/tokio/io/trait.AsyncRead.html) and [`AsyncWrite`](https://docs.rs/tokio/latest/tokio/io/trait.AsyncWrite.html) traits — use `tokio::io::copy`, codecs, `BufReader`/`BufWriter`, or any library that works with async I/O. Under the hood, the module handles framing, multiplexing, and routing so you don't have to. +The **Stream module** bridges the gap by providing persistent, bidirectional byte channels that behave like TCP sockets. Each `MixnetStream` implements [`AsyncRead`](https://docs.rs/tokio/latest/tokio/io/trait.AsyncRead.html) and [`AsyncWrite`](https://docs.rs/tokio/latest/tokio/io/trait.AsyncWrite.html), so `tokio::io::copy`, codecs, `BufReader`/`BufWriter`, and any other async I/O consumer work without modification. **If you're coming from socket-based networking, start here.** -**If you're coming from socket-based networking, start here.** - -Under the hood, every stream is multiplexed over a single `MixnetClient`. A background router task decodes a small header on each incoming Mixnet message and dispatches payloads to the correct stream by ID — no extra connections or gateways needed. +All streams are multiplexed over a single `MixnetClient`. A background router task reads a small header on each incoming message and dispatches the payload to the correct stream by ID, so multiple concurrent streams require no additional connections or gateways. ## How it works The two sides of a stream connection follow a client/server pattern: -1. **Opener** calls `client.open_stream(recipient, surbs)` — this generates a random `StreamId`, registers the stream locally, and sends an `Open` message through the Mixnet. -2. **Listener** calls `listener.accept()` — this blocks until an `Open` arrives, registers the new stream, and returns a `MixnetStream` ready for reading and writing. -3. Both sides read and write using standard `AsyncRead`/`AsyncWrite` — bytes are wrapped with a 10-byte stream header, routed through the Mixnet, and demultiplexed on arrival. -4. **Cleanup** happens on `drop` — the stream deregisters from the local router. No close message is sent over the wire (the Mixnet doesn't guarantee message ordering, so a close could arrive before the final data). +1. **Opener** calls `client.open_stream(recipient, surbs)`. This generates a random `StreamId`, registers the stream locally, and sends an `Open` message through the Mixnet. +2. **Listener** calls `listener.accept()`, which blocks until an `Open` arrives, registers the new stream, and returns a `MixnetStream` ready for reading and writing. +3. Both sides read and write using standard `AsyncRead`/`AsyncWrite`. Bytes are wrapped in a 16-byte LP frame header (stream ID, message type, sequence number), routed through the Mixnet, and demultiplexed on arrival. +4. **Cleanup** happens on `drop`. The stream deregisters from the local router. No close message is sent over the wire, since a close could race ahead of in-flight data. -```mermaid ---- -config: - theme: neo-dark ---- -sequenceDiagram - participant A as Client A (opener) - participant M as Mixnet - participant B as Client B (listener) - - Note over B: listener = client.listener() - A->>M: Open message (StreamId + initial data) - M->>B: Open message delivered - Note over B: stream = listener.accept() - - A->>M: Data (StreamId + payload) - M->>B: Data delivered to stream - B->>M: Data (reply via SURBs) - M->>A: Reply delivered to stream - - Note over A: drop(stream) - Note over B: drop(stream) +```text +┌─────────────────────────────────────────────────────────┐ +│ MixnetClient │ +│ │ +│ ┌──────────────┐ ┌──────────────┐ │ +│ │ MixnetStream │ │ MixnetStream │ ... │ +│ │ (peer A) │ │ (peer B) │ │ +│ └──────┬───────┘ └──────┬───────┘ │ +│ │writes │writes │ +│ ▼ ▼ │ +│ ┌─────────────────────────────────┐ │ +│ │ ClientInput.input_sender │ │ +│ └──────────────┬──────────────────┘ │ +│ │ │ +│ ▼ │ +│ ── mixnet ── │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────┐ │ +│ │ reconstructed_receiver │ │ +│ └──────────────┬──────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────┐ │ +│ │ Router task │ │ +│ │ decode header → dispatch by ID │ │ +│ └──┬──────────────────────────┬───┘ │ +│ │ Open messages │ Data messages │ +│ ▼ ▼ │ +│ ┌──────────────┐ ┌──────────────────┐ │ +│ │MixnetListener│ │ StreamMap lookup │ │ +│ │ .accept() │ │ → per-stream tx │ │ +│ └──────────────┘ └──────────────────┘ │ +└─────────────────────────────────────────────────────────┘ ``` ## Complete example -This is a minimal but complete example: two clients on the same machine, one opens a stream to the other, sends a message, and reads a reply. +A minimal example with two clients on the same machine: one opens a stream to the other, sends a message, and reads a reply. ```rust use nym_sdk::mixnet; @@ -2543,24 +2513,24 @@ const TIMEOUT: Duration = Duration::from_secs(60); #[tokio::main] async fn main() { - // 1. Connect two ephemeral clients + // Connect two ephemeral clients let mut sender = mixnet::MixnetClient::connect_new().await.unwrap(); let mut receiver = mixnet::MixnetClient::connect_new().await.unwrap(); let receiver_addr = *receiver.nym_address(); - // 2. The receiver creates a listener (activates stream mode) + // The receiver creates a listener (activates stream mode) let mut listener = receiver.listener().unwrap(); - // 3. The sender opens a stream to the receiver's Nym address + // The sender opens a stream to the receiver's Nym address let mut outbound = sender.open_stream(receiver_addr, None).await.unwrap(); - // 4. The receiver accepts the incoming stream + // The receiver accepts the incoming stream let mut inbound = tokio::time::timeout(TIMEOUT, listener.accept()) .await .expect("timed out") .expect("listener closed"); - // 5. Send data and read it back — just like a TCP socket + // Send data and read it back, just like a TCP socket outbound.write_all(b"hello from sender").await.unwrap(); outbound.flush().await.unwrap(); @@ -2571,7 +2541,7 @@ async fn main() { .expect("read failed"); println!("Receiver got: {}", String::from_utf8_lossy(&buf[..n])); - // 6. Reply back through the same stream + // Reply back through the same stream inbound.write_all(b"hello from receiver").await.unwrap(); inbound.flush().await.unwrap(); @@ -2581,7 +2551,7 @@ async fn main() { .expect("read failed"); println!("Sender got: {}", String::from_utf8_lossy(&buf[..n])); - // 7. Clean up — streams deregister on drop, then disconnect clients + // Streams deregister on drop, then disconnect clients drop(outbound); drop(inbound); sender.disconnect().await; @@ -2589,7 +2559,7 @@ async fn main() { } ``` -The receiver replies via **reply SURBs** (Single Use Reply Blocks) — it never learns the sender's Nym address. This is the same anonymous reply mechanism used by the message API, applied transparently to streams. +The receiver replies via **reply SURBs** (Single Use Reply Blocks) and never learns the sender's Nym address. ## When to use streams vs messages @@ -2598,24 +2568,17 @@ The receiver replies via **reply SURBs** (Single Use Reply Blocks) — it never | **Pattern** | Raw message payloads | Persistent bidirectional channels | TCP socket proxying | | **API** | `send_plain_message()` / `wait_for_messages()` | `AsyncRead` + `AsyncWrite` | Localhost TCP socket | | **Multiplexing** | N/A | Multiple streams per client | One client per TCP connection | -| **Ordering** | No guarantees | No guarantees (yet) | Session-based ordering | +| **Ordering** | No guarantees | Sequence-based reordering | Session-based ordering | | **Best for** | Simple notifications, one-shot requests | Interactive protocols, streaming data, any code expecting async I/O | Wrapping existing TCP applications | | **Status** | Stable | New | Deprecated | -**Streams and messages are mutually exclusive.** Once you call `open_stream()` or `listener()`, the message-based API (`send_plain_message`, `wait_for_messages`) is permanently disabled on that client. This is a one-way transition — there is no switching back without disconnecting and reconnecting. See the [mode guard example](./stream/examples/mode-guard) for details. - -## Key types - -- [**`MixnetStream`**](https://docs.rs/nym-sdk/latest/nym_sdk/mixnet/struct.MixnetStream.html) — a single stream implementing `AsyncRead + AsyncWrite`. Obtained from `open_stream()` (outbound) or `listener.accept()` (inbound). -- [**`MixnetListener`**](https://docs.rs/nym-sdk/latest/nym_sdk/mixnet/struct.MixnetListener.html) — accepts inbound streams from remote peers. Created once per client via `client.listener()`. -- [**`StreamId`**](https://docs.rs/nym-sdk/latest/nym_sdk/mixnet/struct.StreamId.html) — 8-byte random identifier (`u64`) generated by the stream opener, used to multiplex streams over a single client. +**Streams and messages are mutually exclusive.** Once you call `open_stream()` or `listener()`, the message-based API (`send_plain_message`, `wait_for_messages`) is permanently disabled on that client. This is a one-way transition: there is no switching back without disconnecting and reconnecting. See the [`stream_mode_guard.rs` example](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/stream_mode_guard.rs) for details. ## Next steps -- [Tutorial: Build a private echo server](./stream/tutorial) — step-by-step guide with a server and client communicating over streams -- [Architecture](./stream/architecture) — wire protocol, router task, data flow, stream cleanup, and known limitations -- [Examples](./stream/examples) — annotated walkthroughs of the SDK examples (multi-stream, idle timeout, throughput testing) -- [API reference on docs.rs](https://docs.rs/nym-sdk/latest/nym_sdk/mixnet/stream/) — type details and method signatures +- [Tutorial: Build a private echo server](./stream/tutorial): server and client communicating over streams +- [Architecture](./stream/architecture): wire protocol, router task, data flow, stream cleanup, and known limitations +- [Examples](./stream/examples): annotated walkthroughs of the SDK examples (multi-stream, idle timeout, throughput testing) --- title: Stream Tutorial: Build a Private Echo Server @@ -2625,7 +2588,7 @@ url: https://nym.com/docs/developers/rust/stream/tutorial # Tutorial: Build a Private Echo Server -In this tutorial you'll build two programs — a server that listens for incoming streams and echoes back whatever it receives, and a client that opens a stream, sends data, and reads the echo. Both communicate through the Nym Mixnet using `AsyncRead` and `AsyncWrite`, just like TCP sockets. +In this tutorial you'll build two programs: a server that listens for incoming streams and echoes back whatever it receives, and a client that opens a stream, sends data, and reads the echo. Both communicate through the Nym Mixnet using `AsyncRead` and `AsyncWrite`, just like TCP sockets. ## What you'll learn @@ -2635,11 +2598,9 @@ In this tutorial you'll build two programs — a server that listens for incomin - How streams are multiplexed over a single `MixnetClient` - Clean shutdown and stream lifecycle -Code verified against `nym-sdk` v1.20.4 ([`4077717`](https://github.com/nymtech/nym/commit/4077717d3)). If the API has changed since then, check the [examples in the repo](https://github.com/nymtech/nym/tree/develop/sdk/rust/nym-sdk/examples) for the latest usage. - ## Prerequisites -- Rust toolchain (1.70+) +- Rust toolchain ({RUST_MSRV}+) - A working internet connection (clients connect to the live Nym Mixnet) ## Step 1: Set up the project @@ -2647,14 +2608,17 @@ Code verified against `nym-sdk` v1.20.4 ([`4077717`](https://github.com/nymtech/ ```sh cargo init nym-echo cd nym-echo +rm src/main.rs ``` Add dependencies to `Cargo.toml`: ```toml [dependencies] -nym-sdk = "1.20.4" +nym-sdk = "1.21.0" +nym-bin-common = { version = "1.21.0", features = ["basic_tracing"] } tokio = { version = "1", features = ["full"] } +rand = "0.8" ``` ## Step 2: Build the echo server @@ -2669,11 +2633,13 @@ use tokio::io::{AsyncReadExt, AsyncWriteExt}; #[tokio::main] async fn main() { + nym_bin_common::logging::setup_tracing_logger(); + // Connect to the Mixnet let mut client = mixnet::MixnetClient::connect_new().await.unwrap(); println!("Echo server listening at: {}", client.nym_address()); - // Create a listener — this activates stream mode. + // Create a listener: this activates stream mode. // From this point, message-based methods are disabled. let mut listener = client.listener().unwrap(); @@ -2692,11 +2658,11 @@ async fn main() { // Spawn a task to handle each stream concurrently tokio::spawn(async move { - let mut buf = vec![0u8; 4096]; + let mut buf = vec![0u8; 32_000]; loop { let n = match stream.read(&mut buf).await { - Ok(0) => break, // EOF — stream closed + Ok(0) => break, // EOF: stream closed Ok(n) => n, Err(e) => { eprintln!("Stream {stream_id} read error: {e}"); @@ -2705,11 +2671,7 @@ async fn main() { }; let data = &buf[..n]; - println!( - "Stream {stream_id} received {} bytes: {:?}", - n, - String::from_utf8_lossy(data) - ); + println!("Stream {stream_id} received {n} bytes"); // Echo it back if let Err(e) = stream.write_all(data).await { @@ -2742,6 +2704,8 @@ const TIMEOUT: Duration = Duration::from_secs(60); #[tokio::main] async fn main() { + nym_bin_common::logging::setup_tracing_logger(); + // Read the server's Nym address from the command line let server_addr: Recipient = std::env::args() .nth(1) @@ -2758,22 +2722,32 @@ async fn main() { let mut stream = client.open_stream(server_addr, None).await.unwrap(); println!("Stream opened: {}", stream.id()); - // Send three messages and read back the echo for each - for i in 1..=3 { - let msg = format!("message {i}"); - println!("Sending: {msg}"); + // Give the Open message time to traverse the mixnet and reach the server. + // open_stream() returns immediately after sending. It doesn't wait for + // the server to accept. Writing too soon risks the data arriving before + // the Open, which the server would drop. + tokio::time::sleep(Duration::from_secs(5)).await; - stream.write_all(msg.as_bytes()).await.unwrap(); + // Send three payloads of different sizes and verify the echo. + // Random bytes show that streams are binary-safe, not just text. + let sizes = [320, 25_000, 1280]; + + for (i, &size) in sizes.iter().enumerate() { + let payload: Vec = (0..size).map(|_| rand::random::()).collect(); + println!("Sending message {} ({size} bytes)", i + 1); + + stream.write_all(&payload).await.unwrap(); stream.flush().await.unwrap(); // Read the echo - let mut buf = vec![0u8; 1024]; + let mut buf = vec![0u8; 32_000]; let n = tokio::time::timeout(TIMEOUT, stream.read(&mut buf)) .await .expect("timed out waiting for echo") .expect("read failed"); - println!("Echo: {}", String::from_utf8_lossy(&buf[..n])); + assert_eq!(&buf[..n], &payload[..], "echo mismatch on message {}", i + 1); + println!("Received echo: {n} bytes ok"); } // Drop the stream to deregister it from the router @@ -2790,7 +2764,7 @@ async fn main() { In one terminal, start the server: ```sh -cargo run --bin server +RUST_LOG=info cargo run --bin server ``` It prints its Nym address: @@ -2802,7 +2776,7 @@ Echo server listening at: 8gk4Y...@2xU4d... In a second terminal, start the client with the server's address: ```sh -cargo run --bin client 8gk4Y...@2xU4d... +RUST_LOG=info cargo run --bin client -- 8gk4Y...@2xU4d... ``` You'll see the messages traverse the Mixnet and echo back: @@ -2810,12 +2784,12 @@ You'll see the messages traverse the Mixnet and echo back: ``` Client address: F3qR7...@9nK2m... Stream opened: 12345678 -Sending: message 1 -Echo: message 1 -Sending: message 2 -Echo: message 2 -Sending: message 3 -Echo: message 3 +Sending message 1 (320 bytes) +Received echo: 320 bytes ok +Sending message 2 (25000 bytes) +Received echo: 25000 bytes ok +Sending message 3 (1280 bytes) +Received echo: 1280 bytes ok Done! ``` @@ -2823,13 +2797,13 @@ On the server side: ``` Accepted stream 12345678 -Stream 12345678 received 9 bytes: "message 1" -Stream 12345678 received 9 bytes: "message 2" -Stream 12345678 received 9 bytes: "message 3" +Stream 12345678 received 320 bytes +Stream 12345678 received 25000 bytes +Stream 12345678 received 1280 bytes Stream 12345678 closed ``` -## What's happening under the hood +## How it works internally 1. The server's `listener()` activates **stream mode**, which spawns a **router task** that decodes incoming Mixnet messages and dispatches them by stream ID. @@ -2837,25 +2811,25 @@ Stream 12345678 closed 3. When the server's router receives the `Open` message, it delivers it to `listener.accept()`, which creates the inbound `MixnetStream`. -4. Each `write_all()` prepends a 10-byte header (`[version][stream_id][message_type]`) and sends the data through the Mixnet as a Sphinx packet. +4. Each `write_all()` prepends a 16-byte LP frame header (`[LpFrameKind: 2B][StreamId: 8B][MsgType: 1B][SequenceNum: 4B][Reserved: 1B]`) and sends the data through the Mixnet as a Sphinx packet. -5. On arrival, the router decodes the header, finds the matching stream, and delivers the raw payload to `read()`. +5. On arrival, the router reads the `LpFrameKind` to identify it as stream traffic, decodes the header, finds the matching stream by ID, and delivers the raw payload to `read()`. -6. The inbound stream replies via **reply SURBs** — it never learns the client's Nym address. This is the same anonymous reply mechanism used by the message API, applied transparently. +6. The inbound stream replies via **reply SURBs**, the same anonymous reply mechanism as the message API, applied transparently. The server never learns the client's Nym address. -7. When a stream is dropped, it deregisters from the local router. No close message is sent over the wire (because the Mixnet doesn't guarantee message ordering — a close could arrive before the final data). +7. When a stream is dropped, it deregisters from the local router. No close message is sent over the wire, since a close could race ahead of in-flight data. See the [Architecture](./architecture) page for the full technical details. ## What you've learned -- **`client.listener()`** activates stream mode and returns a `MixnetListener` -- **`listener.accept()`** blocks until a remote peer opens a stream -- **`client.open_stream(recipient, surbs)`** opens an outbound stream -- **`MixnetStream`** implements `AsyncRead + AsyncWrite` — standard tokio I/O -- Streams are **multiplexed** over a single client — you can open many to different peers -- **Cleanup is automatic on `drop`** — no close handshake needed -- **Reply SURBs** enable the server to respond without knowing the client's address +- `client.listener()` activates stream mode and returns a `MixnetListener` +- `listener.accept()` blocks until a remote peer opens a stream +- `client.open_stream(recipient, surbs)` opens an outbound stream to a Nym address +- `MixnetStream` implements `AsyncRead + AsyncWrite`, so standard tokio I/O works unchanged +- Multiple streams are multiplexed over a single client +- Streams deregister on `drop`; no close handshake is needed +- The server replies via SURBs and never learns the client's address ## Complete code @@ -2867,6 +2841,8 @@ use tokio::io::{AsyncReadExt, AsyncWriteExt}; #[tokio::main] async fn main() { + nym_bin_common::logging::setup_tracing_logger(); + let mut client = mixnet::MixnetClient::connect_new().await.unwrap(); println!("Echo server listening at: {}", client.nym_address()); @@ -2875,24 +2851,38 @@ async fn main() { loop { let mut stream = match listener.accept().await { Some(s) => s, - None => break, + None => { + println!("Listener closed"); + break; + } }; let stream_id = stream.id(); println!("Accepted stream {stream_id}"); tokio::spawn(async move { - let mut buf = vec![0u8; 4096]; + let mut buf = vec![0u8; 32_000]; + loop { let n = match stream.read(&mut buf).await { - Ok(0) | Err(_) => break, + Ok(0) => break, Ok(n) => n, + Err(e) => { + eprintln!("Stream {stream_id} read error: {e}"); + break; + } }; - if let Err(_) = stream.write_all(&buf[..n]).await { + + let data = &buf[..n]; + println!("Stream {stream_id} received {n} bytes"); + + if let Err(e) = stream.write_all(data).await { + eprintln!("Stream {stream_id} write error: {e}"); break; } stream.flush().await.unwrap(); } + println!("Stream {stream_id} closed"); }); } @@ -2910,6 +2900,8 @@ const TIMEOUT: Duration = Duration::from_secs(60); #[tokio::main] async fn main() { + nym_bin_common::logging::setup_tracing_logger(); + let server_addr: Recipient = std::env::args() .nth(1) .expect("Usage: client ") @@ -2922,20 +2914,26 @@ async fn main() { let mut stream = client.open_stream(server_addr, None).await.unwrap(); println!("Stream opened: {}", stream.id()); - for i in 1..=3 { - let msg = format!("message {i}"); - println!("Sending: {msg}"); + // Wait for the Open message to reach the server through the mixnet + tokio::time::sleep(Duration::from_secs(5)).await; - stream.write_all(msg.as_bytes()).await.unwrap(); + let sizes = [320, 25_000, 1280]; + + for (i, &size) in sizes.iter().enumerate() { + let payload: Vec = (0..size).map(|_| rand::random::()).collect(); + println!("Sending message {} ({size} bytes)", i + 1); + + stream.write_all(&payload).await.unwrap(); stream.flush().await.unwrap(); - let mut buf = vec![0u8; 1024]; + let mut buf = vec![0u8; 32_000]; let n = tokio::time::timeout(TIMEOUT, stream.read(&mut buf)) .await .expect("timed out waiting for echo") .expect("read failed"); - println!("Echo: {}", String::from_utf8_lossy(&buf[..n])); + assert_eq!(&buf[..n], &payload[..], "echo mismatch on message {}", i + 1); + println!("Received echo: {n} bytes ok"); } drop(stream); @@ -2978,17 +2976,19 @@ flowchart TD ## Wire protocol -Every stream message has a fixed 10-byte header prepended to the payload: +Every stream message has a fixed 16-byte LP frame header prepended to the payload: ``` -[Version: 1 byte][StreamId: 8 bytes][MessageType: 1 byte][payload ...] +[LpFrameKind: 2 bytes LE][StreamId: 8 bytes BE][MsgType: 1 byte][SequenceNum: 4 bytes BE][Reserved: 1 byte][payload ...] ``` -- **Version** — protocol version (`1`). Unknown versions are rejected. -- **StreamId** — random `u64` generated by the opener, used to multiplex streams. -- **MessageType** — `Open` (0) or `Data` (1). +- **LpFrameKind:** `3` (SphinxStream). Distinguishes stream traffic from other LP frame types (Opaque, Registration, Forward). +- **StreamId:** random `u64` generated by the opener, used to multiplex streams. +- **MsgType:** `Open` (0) or `Data` (1). +- **SequenceNum:** `u32` counter, incremented per write. Used by the receiver's per-stream reorder buffer to deliver data in the correct order. +- **Reserved:** must be `0x00`. -There is no `Close` message type — see [Known Limitations](#known-limitations) for why. +There is no `Close` message type; see [Known Limitations](#known-limitations) for why. ## Stream mode @@ -3014,23 +3014,20 @@ There is no switching back without disconnecting and creating a new client. ## Cleanup -- **On `drop`** — the stream deregisters from the routing table. No close message is sent over the wire. -- **Idle timeout** — streams idle for longer than the configured timeout (default: 30 minutes) are automatically cleaned up. Configure with [`MixnetClientBuilder::with_stream_idle_timeout()`](https://docs.rs/nym-sdk/latest/nym_sdk/mixnet/struct.MixnetClientBuilder.html). +- **On `drop`:** the stream deregisters from the routing table. No close message is sent over the wire. +- **Idle timeout:** streams idle for longer than the configured timeout (default: 30 minutes) are automatically cleaned up. Configure with [`MixnetClientBuilder::with_stream_idle_timeout()`](https://docs.rs/nym-sdk/latest/nym_sdk/mixnet/struct.MixnetClientBuilder.html). ## Known limitations -**No message ordering.** The Mixnet does not guarantee message ordering. Messages on a stream can arrive out of order. This means: -- Large writes that span multiple Sphinx packets may arrive shuffled -- There is no `Close` message — a close could race ahead of in-flight data -- Protocols that depend on byte ordering (HTTP, TLS, protobuf) may not work correctly over streams yet +**Sequence-based reordering.** The Mixnet does not guarantee message ordering at the transport level, but each stream write includes a `sequence_num` in the LP frame header. The receiver maintains a per-stream reorder buffer (BTreeMap keyed by sequence number) that buffers out-of-order messages and drains them in sequence. This means protocols that depend on byte ordering (HTTP, TLS, protobuf) work correctly over streams. -Sequencing (similar to the `MessageBuffer` in the TcpProxy module) is planned for a future release. - -**No protocol discriminator.** There is currently no way to distinguish stream messages from regular Mixnet messages. Sending to a non-stream client will deliver bytes with the stream header prepended. A protocol discriminator is planned for a future release. +- **Buffer cap:** 256 messages per stream. If the buffer fills (e.g. a large gap in sequence numbers), the receiver skips ahead to the lowest buffered sequence. +- **Duplicates:** messages with a sequence number below the next expected are dropped. +- There is no `Close` message type, since a close could race ahead of in-flight data. ## Internal details -For the full implementation details (router task, `StreamMap`, `PollSender` usage, base-client type rationale), see the [architecture documentation in the source tree](https://docs.rs/nym-sdk/latest/nym_sdk/mixnet/stream/) or the `ARCHITECTURE.md` file next to the module code. +For the full implementation details (router task, `StreamMap`, `PollSender` usage, base-client type rationale), see the `ARCHITECTURE.md` file next to the module source code, or the [docs.rs](https://docs.rs/nym-sdk/latest/nym_sdk/) API reference. --- title: Stream Module Examples @@ -3061,15 +3058,15 @@ url: https://nym.com/docs/developers/rust/tcpproxy # TcpProxy Module - **This module is unmaintained.** The TcpProxy is no longer actively developed in favour of the [Stream module](./stream), which provides `AsyncRead + AsyncWrite` streams directly over the Mixnet without the TCP socket overhead. Existing users should plan to migrate to streams when possible. The TcpProxy will continue to work but will not receive new features or bug fixes. + **This module is unmaintained.** The TcpProxy is no longer actively developed in favour of the [Stream module](/developers/rust/stream), which provides `AsyncRead + AsyncWrite` streams directly over the Mixnet without the TCP socket overhead. Existing users should plan to migrate to streams when possible. The TcpProxy will continue to work but will not receive new features or bug fixes. -The Stream module offers the same key benefit (familiar I/O patterns on top of the Mixnet) with a simpler API, multiplexed connections on a single client, and no localhost socket overhead. The one feature TcpProxy has that streams don't yet have is **message ordering** — see the [stream architecture](./stream/architecture#known-limitations) for details. If your application requires guaranteed byte ordering today, TcpProxy still works. +The Stream module offers the same key benefit (familiar I/O patterns on top of the Mixnet) with a simpler API. Streams multiplex connections on a single client, eliminate the localhost socket overhead, and now include sequence-based message reordering. There is no remaining reason to choose TcpProxy over Streams for new projects. --- -This module exposes `NymProxyClient` and `NymProxyServer` for proxying TCP traffic through the Mixnet. Both are initialised and run in a background thread, exposing a configurable `localhost` socket which you can read/write to without worrying about the Mixnet's message-based internals. +`NymProxyClient` and `NymProxyServer` proxy TCP traffic through the Mixnet. Both run in a background thread and expose a configurable `localhost` socket that you read and write to like any other TCP connection. -> Non-Rust/Go developers who want to experiment with this module can start with the [standalone binaries](../tools/standalone-tcpproxy). +> Non-Rust/Go developers who want to experiment with this module can start with the [standalone binaries](/developers/tools/standalone-tcpproxy). ## Examples @@ -3078,60 +3075,34 @@ This module exposes `NymProxyClient` and `NymProxyServer` for proxying TCP traff | Single connection | [`tcp_proxy_single_connection.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/tcp_proxy_single_connection.rs) | | Multiple connections | [`tcp_proxy_multistream.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/tcp_proxy_multistream.rs) | +```bash +cargo run --example tcp_proxy_single_connection +cargo run --example tcp_proxy_multistream +``` + ## API reference -- [API reference on docs.rs](https://docs.rs/nym-sdk/latest/nym_sdk/tcp_proxy/) — architecture overview, client/server examples, and type documentation +- [API reference on docs.rs](https://docs.rs/nym-sdk/latest/nym_sdk/tcp_proxy/): architecture overview, client/server examples, and type documentation -## Troubleshooting +## Tutorial -### Lots of `duplicate fragment received` messages - -`WARN` level logs about duplicate fragments are caused by Mixnet-level packet retransmission — the original and the retransmitted copy both arrive at the destination. This is not a bug in your client logic or the TcpProxy module. - ---- -title: TcpProxy Tutorial: Tunnel TCP Through the Mixnet -description: Build a proxy server and client that tunnel TCP traffic through the Nym mixnet using the TcpProxy module. Includes NymProxyServer and NymProxyClient setup. -url: https://nym.com/docs/developers/rust/tcpproxy/tutorial ---- - -# Tutorial: Tunnel TCP Through the Mixnet - -The TcpProxy module is **unmaintained**. For new projects, use the [Stream module](../stream) instead. This tutorial exists for users working with existing TcpProxy-based code. - -Build two programs — a proxy server that forwards TCP traffic to a local service, and a proxy client that tunnels connections through the Mixnet. - -## What you'll learn - -- Setting up a `NymProxyServer` that forwards Mixnet traffic to a local TCP service -- Setting up a `NymProxyClient` that tunnels localhost TCP connections through the Mixnet -- How TcpProxy differs from the Stream module - -## Prerequisites - -- Rust toolchain (1.70+) -- A working internet connection - -Code verified against `nym-sdk` v1.20.4 ([`4077717`](https://github.com/nymtech/nym/commit/4077717d3)). If the API has changed since then, check the [examples in the repo](https://github.com/nymtech/nym/tree/develop/sdk/rust/nym-sdk/examples) for the latest usage. - -## Step 1: Set up the project +Set up the project: ```sh cargo init nym-tcp-proxy cd nym-tcp-proxy +rm src/main.rs ``` Add dependencies to `Cargo.toml`: ```toml [dependencies] -nym-sdk = "1.20.4" +nym-sdk = "1.21.0" +nym-network-defaults = "1.21.0" +nym-bin-common = { version = "1.21.0", features = ["basic_tracing"] } tokio = { version = "1", features = ["full"] } -anyhow = "1" -``` -This tutorial creates two binaries (server and client). Add them to `Cargo.toml`: - -```toml [[bin]] name = "proxy_server" path = "src/bin/proxy_server.rs" @@ -3141,79 +3112,68 @@ name = "proxy_client" path = "src/bin/proxy_client.rs" ``` -Create the `src/bin/` directory: +### Server -```sh -mkdir -p src/bin -``` - -## Step 2: Build the server - -The server connects to the Mixnet and forwards incoming traffic to a local TCP service (e.g. a web server on port 3000). - -Create `src/bin/proxy_server.rs`: +The server connects to the Mixnet and forwards incoming traffic to a local TCP service (e.g. a web server on port 8000). ```rust use nym_sdk::tcp_proxy::NymProxyServer; #[tokio::main] -async fn main() -> anyhow::Result<()> { - // Forward traffic to localhost:3000 (your upstream service). - // The second argument is a directory for persistent key storage. +async fn main() -> Result<(), Box> { + nym_bin_common::logging::setup_tracing_logger(); + let mut server = NymProxyServer::new( - "127.0.0.1:3000", // upstream address (host:port) + "127.0.0.1:8000", // upstream address (host:port) "./proxy-server-config", // config directory for persistent keys None, // env file (None = mainnet) None, // gateway (None = auto-select) ).await?; - // Print the Nym address — the client needs this to connect. println!("Proxy server address: {}", server.nym_address()); - - // Blocks until shutdown. Traffic from the Mixnet is forwarded to - // localhost:3000, and responses are sent back via reply SURBs. server.run_with_shutdown().await?; Ok(()) } ``` -## Step 3: Build the client +### Client The client opens a localhost TCP socket and tunnels all traffic through the Mixnet to the server. -Create `src/bin/proxy_client.rs`: - ```rust use nym_sdk::tcp_proxy::NymProxyClient; use nym_sdk::mixnet::Recipient; +use nym_network_defaults::setup_env; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::net::TcpStream; #[tokio::main] -async fn main() -> anyhow::Result<()> { - // Parse the server's Nym address from the command line. +async fn main() -> Result<(), Box> { + nym_bin_common::logging::setup_tracing_logger(); + // Load mainnet network defaults into env vars (required by NymProxyClient's internal ClientPool) + setup_env(None::); + let server_addr: Recipient = std::env::args() .nth(1).expect("Usage: proxy_client ") .parse()?; - // Create the proxy client — listens on localhost:1080. let client = NymProxyClient::new( server_addr, "127.0.0.1", // listen host - "1080", // listen port + "8070", // listen port 60, // close timeout (seconds) None, // env file (None = mainnet) - 2, // client pool size + 1, // client pool size ).await?; - // Spawn the proxy in the background. let proxy = tokio::spawn(async move { client.run().await }); - // Give the proxy a moment to start listening. - tokio::time::sleep(std::time::Duration::from_secs(3)).await; + // Wait for the pool to create a client and the proxy to be ready. + // The first startup takes ~10-15s while the client connects to the Mixnet. + println!("Waiting for proxy to be ready..."); + tokio::time::sleep(std::time::Duration::from_secs(15)).await; - // Connect to the local proxy socket — traffic goes through the Mixnet. - let mut stream = TcpStream::connect("127.0.0.1:1080").await?; + let mut stream = TcpStream::connect("127.0.0.1:8070").await?; stream.write_all(b"GET / HTTP/1.0\r\nHost: localhost\r\n\r\n").await?; let mut response = Vec::new(); @@ -3226,78 +3186,41 @@ async fn main() -> anyhow::Result<()> { } ``` -## Step 4: Run it +### Run it -Start a simple upstream service (e.g. Python's HTTP server): +Start an upstream TCP service (e.g. a simple HTTP server): ```sh -cd /tmp && echo "hello from upstream" > index.html -python3 -m http.server 3000 +python3 -m http.server 8000 ``` -Terminal 1 — start the proxy server: +In a second terminal, start the proxy server: + ```sh -cargo run --bin proxy_server -# Proxy server address: 8gk4Y...@2xU4d... +RUST_LOG=info cargo run --bin proxy_server ``` -Terminal 2 — start the proxy client: +Copy the Nym address it prints, then in a third terminal: + ```sh -cargo run --bin proxy_client 8gk4Y...@2xU4d... -# Response: -# HTTP/1.0 200 OK -# ... -# hello from upstream +RUST_LOG=info cargo run --bin proxy_client -- ``` -The HTTP request travelled through the Mixnet — the upstream server only sees a connection from `localhost`, not the client's real IP. +The response will take 30–60 seconds to arrive as it traverses the Mixnet in both directions. -## How it differs from streams +## Architecture -TcpProxy handles **message ordering** internally using session IDs and sequence numbers, which the [Stream module](../stream) does not yet provide. This means TcpProxy can work with protocols that depend on byte ordering (HTTP, TLS). The trade-off is higher overhead: each side runs a localhost TCP socket, and ordering adds latency. For new code, the stream API is simpler and more efficient. +Each sub-module handles Nym clients differently: +- **`NymProxyClient`** relies on the [Client Pool](/developers/rust/client-pool) to create clients and keep a reserve. If incoming TCP connections outpace the pool, it creates an ephemeral client per connection. One client maps to one TCP connection. +- **`NymProxyServer`** has a single Nym client with a persistent identity. -## What you've learned +### Sessions & message ordering -- **`NymProxyServer::new(upstream, config_dir, env, gateway)`** creates a server that forwards Mixnet traffic to a local TCP service -- **`NymProxyClient::new(recipient, host, port, timeout, env, pool_size)`** creates a client that tunnels localhost TCP through the Mixnet -- **The server uses persistent keys** (stored in `config_dir`) so its Nym address stays the same across restarts -- **The client uses ephemeral keys** from a `ClientPool` — one per TCP connection -- **TcpProxy handles message ordering** — unlike the Stream module, it can work with order-dependent protocols like HTTP +Messages are wrapped in a session ID per connection, with individual messages given an incrementing message ID. Once all messages are sent, the client sends a `Close` message to notify the server that there are no more outbound messages for this session. ---- -title: TcpProxy Architecture -description: Architecture of the Nym TcpProxy module: client and server design, byte framing, session management, and message ordering over the mixnet. -url: https://nym.com/docs/developers/rust/tcpproxy/architecture ---- +> Session management and message IDs are necessary since *the Mixnet guarantees message delivery but not message ordering*: in the case of trying to e.g. send gRPC protobuf through the Mixnet, ordering is required so that a buffer is not split across Sphinx packet payloads, and that the 2nd half of the frame is not passed upstream to the parser before the 1st half. -# Architecture - -**This module is unmaintained.** See the [Stream module](../stream) for the actively developed replacement. Existing users should plan to migrate when possible. - -## Motivations -The motivation behind the creation of the `TcpProxy` module is to allow developers to interact with the Mixnet in a way that is far more familiar to them: simply setting up a connection with a transport, being returned a socket, and then being able to stream data to/from it, similar to something like the Tor [`arti`](https://gitlab.torproject.org/tpo/core/arti/-/tree/main/crates/arti-client) client. - -## Clients -Each of the sub-modules exposed by the `TcpProxy` deal with Nym clients in a different way. -- the `NymProxyClient` relies on the [`Client Pool`](../client-pool) to create clients and keep a certain number of them in reserve. If the amount of incoming TCP connections rises quicker than the Client Pool can create clients, or you have the pool size set to `0`, the `TcpProxyClient` creates an ephemeral client per new TCP connection, which is closed according to the configurable timeout: we map one ephemeral client per TCP connection. This is to deal with multiple simultaneous streams. -- the `NymProxyServer` has a single Nym client with a persistent identity. - -## Framing -We are currently relying on the [`tokio::Bytecodec`](https://docs.rs/tokio-util/latest/tokio_util/codec/struct.BytesCodec.html) and [`framedRead`](https://docs.rs/tokio-util/latest/tokio_util/codec/struct.Framed.html) to frame bytes moving through the `NymProxyClient` and `NymProxyServer`. - -> For those interested, under the hood the client uses our own [`NymCodec`](https://github.com/nymtech/nym/blob/27ac34522cf0f8bfe1ca265e0b57ee52f2ded0d2/common/nymsphinx/framing/src/codec.rs) to frame message bytes as Sphinx packet payloads. - -## Sessions & Message Ordering -We have implemented session management and message ordering, where messages are wrapped in a session ID per connection, with individual messages being given an incrementing message ID. Once all the messages have been sent, the `NymProxyClient` then sends a `Close` message as the last outgoing message. This is to notify the `NymProxyServer` that there are no more outbound messages for this session, and that it can trigger the session timeout. - -> Session management and message IDs are necessary since *the Mixnet guarantees message delivery but not message ordering*: in the case of trying to e.g. send gRPC protobuf through the Mixnet, ordering is required so that a buffer is not split across Sphinx packet payloads, and that the 2nd half of the frame is not passed upstream to the gRPC parser before the 1st half, even if it is received first. - -Lets step through a full request/response path between a client process communicating with a remote host via the proxies: - -### Outgoing Client Request -The `NymProxyClient` instance, once initialised and running, listens out for incoming TCP connections on its localhost port. - -On receiving one, it will create a new session ID and packetise the incoming bytes into messages of the following structure: +The key data structure: ```rust pub struct ProxiedMessage { @@ -3307,106 +3230,8 @@ pub struct ProxiedMessage { } ``` -> This code can be found [here](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/src/tcp_proxy/utils.rs#L147C1-L152C2) +### Full request/response flow -And then send these to the Nym address of the `NymProxyServer` instance. Not much to see here regarding message ordering, as the potential for reordering only starts once packets are travelling through the Mixnet. - -```mermaid ---- -config: - theme: neo-dark - layout: elk ---- -sequenceDiagram - box Local Machine - participant Client Process - participant NymProxyClient - end - Client Process->>NymProxyClient: Request bytes - NymProxyClient->>NymProxyClient: New session - NymProxyClient->>EntryGateway: Sphinx Packets: Message 1 - EntryGateway-->>NymProxyClient: Acks - NymProxyClient->>EntryGateway: Sphinx Packets: Message 2 - EntryGateway-->>NymProxyClient: Acks - NymProxyClient->>EntryGateway: Sphinx Packets: Message 3 - EntryGateway-->>NymProxyClient: Acks - NymProxyClient->>EntryGateway: Sphinx Packets: Close Message - NymProxyClient->>NymProxyClient: Start Client Close timeout - EntryGateway-->>NymProxyClient: Acks -``` - -### Server Receives Request & Responds - -Here is a diagrammatic representation of a situation in which the request arrives out of order, and how the message buffer deals with this so as not to pass a malformed request upstream to the process running on the same remote host: - -```mermaid ---- -config: - theme: neo-dark - layout: elk ---- -sequenceDiagram - Exit Gateway->>NymProxyServer: Sphinx Packets: Message 2 - NymProxyServer-->>Exit Gateway: Acks - Exit Gateway->>NymProxyServer: Sphinx Packets: Message 3 - NymProxyServer-->>Exit Gateway: Acks - loop Message Buffer - NymProxyServer->>NymProxyServer: Wait for Message 1 - Exit Gateway->>NymProxyServer: Sphinx Packets: Message 1 - NymProxyServer-->>Exit Gateway: Acks - NymProxyServer->>NymProxyServer: Message Received: trigger upstream send - end - Note right of NymProxyServer: Note this happens **per session** - NymProxyServer->>Upstream Process: Reconstructed request bytes - Upstream Process->>Upstream Process: Do something with request - Exit Gateway->>NymProxyServer: Sphinx Packets: Message Close - NymProxyServer-->>Exit Gateway: Acks - NymProxyServer->>NymProxyServer: Trigger Client timeout start for session - Upstream Process->>NymProxyServer: Response bytes - NymProxyServer->>NymProxyServer: Write to provided SURB payloads - NymProxyServer->>Exit Gateway: Anonymous replies - - box Remote Host - participant NymProxyServer - participant Upstream Process - end -``` - -> Note that this is per-session, with a session mapped to a single TCP connection. Both the `NymProxyClient` and `Server` are able to handle multiple concurrent connections. - -### Client Receives Response - -The `ProxyClient` deals with incoming traffic in the same way as the `ProxyServer`, with a per-session message queue: - -```mermaid ---- -config: - theme: neo-dark - layout: elk ---- -sequenceDiagram - box Local Machine - participant Client Process - participant NymProxyClient - end - Entry Gateway--xNymProxyClient: Sphinx Packets: Reply Message 1 dropped: No Ack! - Entry Gateway->>NymProxyClient: Sphinx Packets: Reply Message 2 - NymProxyClient-->Entry Gateway: Ack - Entry Gateway->>NymProxyClient: Sphinx Packets: Reply Message 3 - NymProxyClient-->Entry Gateway: Ack - Loop Message Buffer: - NymProxyClient->>NymProxyClient: Wait for Message 1 - Entry Gateway->>NymProxyClient: Sphinx Packets: Message 1 - NymProxyClient-->>Entry Gateway: Acks - NymProxyClient->>NymProxyClient: Message Received: trigger send - NymProxyClient->>Client Process: Response bytes - end - Note right of NymProxyClient: Note this happens **per session** -``` - -After receiving the packets, it can then forward the recorded bytes to the requesting process. - -### Full Flow Diagram ```mermaid --- config: @@ -3424,8 +3249,6 @@ sequenceDiagram Entry Gateway-->>NymProxyClient: Acks NymProxyClient->>Entry Gateway: Sphinx Packets: Message 2 Entry Gateway-->>NymProxyClient: Acks - NymProxyClient->>Entry Gateway: Sphinx Packets: Message 3 - Entry Gateway-->>NymProxyClient: Acks NymProxyClient->>Entry Gateway: Sphinx Packets: Close Message Entry Gateway-->>NymProxyClient: Acks @@ -3435,8 +3258,6 @@ sequenceDiagram Exit Gateway->>NymProxyServer: Sphinx Packets: Message 2 NymProxyServer-->>Exit Gateway: Acks - Exit Gateway->>NymProxyServer: Sphinx Packets: Message 3 - NymProxyServer-->>Exit Gateway: Acks loop Message Buffer NymProxyServer->>NymProxyServer: Wait for Message 1 Exit Gateway->>NymProxyServer: Sphinx Packets: Message 1 @@ -3457,11 +3278,8 @@ sequenceDiagram participant Upstream Process end - Entry Gateway--xNymProxyClient: Sphinx Packets: Reply Message 1 dropped: No Ack! Entry Gateway->>NymProxyClient: Sphinx Packets: Reply Message 2 NymProxyClient-->Entry Gateway: Ack - Entry Gateway->>NymProxyClient: Sphinx Packets: Reply Message 3 - NymProxyClient-->Entry Gateway: Ack Loop Message Buffer: NymProxyClient->>NymProxyClient: Wait for Message 1 Entry Gateway->>NymProxyClient: Sphinx Packets: Message 1 @@ -3472,6 +3290,12 @@ sequenceDiagram Note right of NymProxyClient: Note this happens **per session** ``` +## Troubleshooting + +### Lots of `duplicate fragment received` messages + +`WARN` level logs about duplicate fragments are caused by Mixnet-level packet retransmission, where both the original and the retransmitted copy arrive at the destination. This is expected behaviour, not a bug in the client or TcpProxy module. + --- title: Client Pool: Pre-Connected Mixnet Clients description: The Nym ClientPool maintains ready-to-use MixnetClient instances, eliminating connection latency for bursty traffic patterns. @@ -3480,7 +3304,7 @@ url: https://nym.com/docs/developers/rust/client-pool # Client Pool -The `ClientPool` maintains a configurable number of connected ephemeral `MixnetClient` instances, ready for immediate use. This eliminates the connection latency (gateway handshake, key generation, topology fetch) that comes with creating a new client on each request. +The `ClientPool` maintains a configurable number of connected ephemeral `MixnetClient` instances, ready for immediate use. This eliminates the connection latency that comes with creating a new client on each request: the gateway handshake, key generation, and topology fetch all happen ahead of time. ## How it works @@ -3497,22 +3321,27 @@ flowchart LR ``` 1. **Create** the pool with a target reserve size: `ClientPool::new(5)` -2. **Start** the background loop: `pool.start()` — it immediately begins connecting clients +2. **Start** the background loop: `pool.start()`. It immediately begins connecting clients 3. **Pop** a client when needed: `pool.get_mixnet_client()` returns `Some(client)` or `None` if the pool is empty -4. **Use** the client normally — send messages, open streams, etc. -5. **Disconnect** the client when done — the background loop notices the pool is below reserve and creates a replacement +4. **Use** the client normally: send messages, open streams, etc. +5. **Disconnect** the client when done. The background loop notices the pool is below reserve and creates a replacement Clients are **consumed, not returned**. The pool creates new ones to maintain the reserve. If the pool is empty, you can fall back to `MixnetClient::connect_new()` (slower, but keeps things working). -The `NymProxyClient` (TcpProxy) uses a `ClientPool` internally — one client per incoming TCP connection. +The `NymProxyClient` (TcpProxy) uses a `ClientPool` internally: one client per incoming TCP connection. ## Quick example ```rust use nym_sdk::client_pool::ClientPool; +use nym_network_defaults::setup_env; #[tokio::main] -async fn main() -> anyhow::Result<()> { +async fn main() -> Result<(), Box> { + nym_bin_common::logging::setup_tracing_logger(); + // Load mainnet network defaults into env vars (required by ClientPool) + setup_env(None::); + let pool = ClientPool::new(5); // maintain 5 clients in reserve let pool_clone = pool.clone(); @@ -3531,9 +3360,9 @@ async fn main() -> anyhow::Result<()> { ## Further reading -- [Tutorial: Handle bursty traffic](./client-pool/tutorial) — step-by-step guide covering pool creation, burst handling, and fallback logic -- [API reference on docs.rs](https://docs.rs/nym-sdk/latest/nym_sdk/client_pool/) — type details, method signatures, and architecture docs -- [Example source on GitHub](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/client_pool.rs) — complete working example +- [Tutorial: Handle bursty traffic](./client-pool/tutorial): step-by-step guide covering pool creation, burst handling, and fallback logic +- [API reference on docs.rs](https://docs.rs/nym-sdk/latest/nym_sdk/client_pool/): type details, method signatures, and architecture docs +- [Example source on GitHub](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/client_pool.rs): complete working example --- title: Client Pool Tutorial: Handle Bursty Traffic @@ -3553,11 +3382,9 @@ In this tutorial you'll build a program that uses `ClientPool` to handle bursts - Observing pool replenishment - Graceful shutdown -Code verified against `nym-sdk` v1.20.4 ([`4077717`](https://github.com/nymtech/nym/commit/4077717d3)). If the API has changed since then, check the [examples in the repo](https://github.com/nymtech/nym/tree/develop/sdk/rust/nym-sdk/examples) for the latest usage. - ## Prerequisites -- Rust toolchain (1.70+) +- Rust toolchain ({RUST_MSRV}+) - A working internet connection ## Step 1: Set up the project @@ -3571,23 +3398,31 @@ Add dependencies to `Cargo.toml`: ```toml [dependencies] -nym-sdk = "1.20.4" +nym-sdk = "1.21.0" +nym-network-defaults = "1.21.0" +nym-bin-common = { version = "1.21.0", features = ["basic_tracing"] } tokio = { version = "1", features = ["full"] } ``` ## Step 2: Create and start the pool -The pool is created with a **reserve size** — the number of connected clients it tries to maintain at all times. The `start()` method runs a background loop that creates clients whenever the pool drops below the reserve. +The pool is created with a **reserve size**: the number of connected clients it tries to maintain at all times. The `start()` method runs a background loop that creates clients whenever the pool drops below the reserve. Create `src/main.rs`: ```rust use nym_sdk::client_pool::ClientPool; use nym_sdk::mixnet::MixnetMessageSender; +use nym_network_defaults::setup_env; use std::time::Duration; #[tokio::main] async fn main() { + nym_bin_common::logging::setup_tracing_logger(); + + // Load mainnet network defaults into env vars (required by ClientPool) + setup_env(None::); + // Create a pool that maintains 3 clients in reserve let pool = ClientPool::new(3); @@ -3598,7 +3433,7 @@ async fn main() { pool_bg.start().await.unwrap(); }); - println!("Pool started — waiting for clients to connect..."); + println!("Pool started, waiting for clients to connect..."); tokio::time::sleep(Duration::from_secs(15)).await; // Check how many are ready @@ -3621,20 +3456,20 @@ When you call `get_mixnet_client()`, the pool removes a client and returns it. T let handle = tokio::spawn(async move { // Pop a client from the pool - let client = match pool.get_mixnet_client().await { + let mut client = match pool.get_mixnet_client().await { Some(c) => { println!("Task {i}: got client {} from pool", c.nym_address()); c } None => { - // Pool is empty — fall back to creating one on the fly. + // Pool is empty: fall back to creating one on the fly. // This is slower but keeps things working. println!("Task {i}: pool empty, creating client on the fly..."); nym_sdk::mixnet::MixnetClient::connect_new().await.unwrap() } }; - // Do something with the client — here, send a message to ourselves + // Do something with the client: here, send a message to ourselves let addr = *client.nym_address(); client .send_plain_message(addr, format!("hello from task {i}")) @@ -3653,7 +3488,7 @@ When you call `get_mixnet_client()`, the pool removes a client and returns it. T } } - // Disconnect when done — the pool will create a replacement + // Disconnect when done: the pool will create a replacement client.disconnect().await; println!("Task {i}: done"); }); @@ -3692,13 +3527,13 @@ After popping all 3 clients, the pool background loop starts creating replacemen ## Step 6: Run it ```sh -cargo run +RUST_LOG=info cargo run ``` You'll see output like: ``` -Pool started — waiting for clients to connect... +Pool started, waiting for clients to connect... Pool has 3 clients ready Task 1: got client 8gk4Y...@2xU4d... from pool Task 2: got client F3qR7...@9nK2m... from pool @@ -3719,20 +3554,20 @@ Pool shut down The pool is most useful when: -- **You have bursty traffic** — many concurrent operations that each need their own client -- **Latency matters** — you can't afford the several-second delay of creating a client on each request -- **You're building a service** — an API endpoint that creates a client per request would benefit from pre-warmed clients +- **You have bursty traffic:** many concurrent operations that each need their own client +- **Latency matters:** you can't afford the several-second delay of creating a client on each request +- **You're building a service:** an API endpoint that creates a client per request would benefit from pre-warmed clients If your application only ever needs one client at a time, just use `MixnetClient::connect_new()` directly. -The `NymProxyClient` (TcpProxy module) uses a `ClientPool` internally — one client per incoming TCP connection. +The `NymProxyClient` (TcpProxy module) uses a `ClientPool` internally: one client per incoming TCP connection. ## What you've learned - **`ClientPool::new(n)`** creates a pool targeting `n` reserve clients - **`pool.start()`** runs a background loop that creates clients whenever the pool is below reserve -- **`pool.get_mixnet_client()`** pops a client — returns `None` if the pool is empty -- **Clients are consumed, not returned** — the pool automatically creates replacements +- **`pool.get_mixnet_client()`** pops a client; returns `None` if the pool is empty +- **Clients are consumed, not returned.** The pool automatically creates replacements - **`pool.disconnect_pool()`** shuts down all remaining clients and stops the background loop - **Fall back to on-demand creation** when the pool is empty for resilience @@ -3741,25 +3576,42 @@ The `NymProxyClient` (TcpProxy module) uses a `ClientPool` internally — one cl ```rust use nym_sdk::client_pool::ClientPool; use nym_sdk::mixnet::MixnetMessageSender; +use nym_network_defaults::setup_env; use std::time::Duration; #[tokio::main] async fn main() { - let pool = ClientPool::new(3); - let pool_bg = pool.clone(); - tokio::spawn(async move { pool_bg.start().await.unwrap() }); + nym_bin_common::logging::setup_tracing_logger(); + setup_env(None::); - println!("Waiting for pool to fill..."); + let pool = ClientPool::new(3); + + let pool_bg = pool.clone(); + tokio::spawn(async move { + pool_bg.start().await.unwrap(); + }); + + println!("Pool started, waiting for clients to connect..."); tokio::time::sleep(Duration::from_secs(15)).await; - println!("Pool has {} clients", pool.get_client_count().await); + + let count = pool.get_client_count().await; + println!("Pool has {count} clients ready"); let mut handles = vec![]; + for i in 1..=3 { let pool = pool.clone(); - handles.push(tokio::spawn(async move { - let client = match pool.get_mixnet_client().await { - Some(c) => c, - None => nym_sdk::mixnet::MixnetClient::connect_new().await.unwrap(), + + let handle = tokio::spawn(async move { + let mut client = match pool.get_mixnet_client().await { + Some(c) => { + println!("Task {i}: got client {} from pool", c.nym_address()); + c + } + None => { + println!("Task {i}: pool empty, creating client on the fly..."); + nym_sdk::mixnet::MixnetClient::connect_new().await.unwrap() + } }; let addr = *client.nym_address(); @@ -3769,28 +3621,56 @@ async fn main() { .unwrap(); if let Some(msgs) = client.wait_for_messages().await { - for msg in msgs.iter().filter(|m| !m.message.is_empty()) { - println!("Task {i}: {}", String::from_utf8_lossy(&msg.message)); + for msg in msgs { + if !msg.message.is_empty() { + println!( + "Task {i}: received {:?}", + String::from_utf8_lossy(&msg.message) + ); + } } } client.disconnect().await; - })); + println!("Task {i}: done"); + }); + + handles.push(handle); } for h in handles { h.await.unwrap(); } - println!("Waiting for replenishment..."); + println!("\nWaiting for pool to replenish..."); tokio::time::sleep(Duration::from_secs(15)).await; - println!("Pool has {} clients", pool.get_client_count().await); + + let count = pool.get_client_count().await; + println!("Pool has {count} clients ready again"); pool.disconnect_pool().await; - println!("Done"); + println!("Pool shut down"); } ``` +--- +title: Client Pool Examples +description: Runnable Rust example for the Nym Client Pool: managing multiple MixnetClients with ephemeral fallback. +url: https://nym.com/docs/developers/rust/client-pool/examples +--- + +# Examples + +Runnable examples in [`sdk/rust/nym-sdk/examples/`](https://github.com/nymtech/nym/tree/develop/sdk/rust/nym-sdk/examples). Each file is self-contained with step-by-step comments. + +```bash +cargo run --example +``` + +| Example | Source | What it demonstrates | +|---|---|---| +| Client Pool | [`client_pool.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/client_pool.rs) | Creating a pool of `MixnetClient`s, retrieving clients from the pool, and falling back to ephemeral clients when the pool is empty | + --- title: FFI Bindings: Go and C/C++ description: Use the Nym SDK from Go and C/C++ via FFI bindings. Covers mixnet messaging, anonymous replies, and TcpProxy lifecycle from non-Rust languages. @@ -3812,13 +3692,13 @@ Core logic lives in `shared/` and is imported into language-specific wrappers. T ## What's exposed -**Mixnet** (Go and C/C++) — ephemeral and persistent client creation, sending messages, anonymous replies via SURBs, and listening for incoming messages. +**Mixnet** (Go and C/C++): ephemeral and persistent client creation, sending messages, anonymous replies via SURBs, listening for incoming messages. -**TcpProxy** (Go only) — client and server creation and lifecycle. +**TcpProxy** (Go only): client and server creation and lifecycle. The TcpProxy module is deprecated. For new projects, use the [Stream module](./stream) instead. -**Client Pool and Stream** — no standalone FFI bindings yet. The TcpProxy bindings use the Client Pool internally. +**Client Pool and Stream** have no standalone FFI bindings yet. The TcpProxy bindings use the Client Pool internally. ## Quick example (Go) @@ -3886,10 +3766,10 @@ Each language has a `build.sh` script that compiles the Rust shared library and ## Examples and source -- [Go mixnet example](https://github.com/nymtech/nym/blob/develop/sdk/ffi/go/example.go) — full client lifecycle: init, send, receive, SURB reply -- [Go TcpProxy example](https://github.com/nymtech/nym/blob/develop/sdk/ffi/go/proxy_example.go) — proxy client and server with TCP echo -- [C++ example](https://github.com/nymtech/nym/blob/develop/sdk/ffi/cpp/src/main.cpp) — same flow using Boost threads -- [`sdk/ffi` source](https://github.com/nymtech/nym/tree/develop/sdk/ffi) — full source and build scripts +- [Go mixnet example](https://github.com/nymtech/nym/blob/develop/sdk/ffi/go/example.go): init, send, receive, SURB reply +- [Go TcpProxy example](https://github.com/nymtech/nym/blob/develop/sdk/ffi/go/proxy_example.go): proxy client and server with TCP echo +- [C++ example](https://github.com/nymtech/nym/blob/develop/sdk/ffi/cpp/src/main.cpp): same flow using Boost threads +- [`sdk/ffi` source](https://github.com/nymtech/nym/tree/develop/sdk/ffi): full source and build scripts --- title: Nym TypeScript SDK: Privacy for Web Apps @@ -3901,7 +3781,7 @@ url: https://nym.com/docs/developers/typescript The TypeScript SDK lets you build browser-based applications that communicate through the Nym mixnet. Import SDK packages via NPM as you would any other TypeScript library. -**The Nym mixnet is not like regular internet networking.** There are no persistent connections, no guaranteed message ordering, and no TCP underneath. Traffic is Sphinx-encrypted, mixed through multiple nodes, and independently reconstructed at the destination. This means sending data through the mixnet works differently from what web developers typically expect. The SDK abstracts the complexity, but understanding the underlying model helps when debugging. +The Nym Mixnet routes traffic through multiple nodes with no persistent connections or guaranteed ordering. The SDK abstracts the complexity, but understanding the [underlying model](/developers/rust/tour) helps when debugging. ## Packages @@ -3930,10 +3810,10 @@ The TypeScript SDK lets you build browser-based applications that communicate th All packages (except Contract Clients) come in four variants: -- **ESM** — For new projects with current tooling. You may need to [configure your bundler](./typescript/bundling) to handle WASM and web worker components. -- **ESM full-fat** — Pre-bundled with inline WASM and web workers. No bundler config needed. -- **CommonJS** — For older projects using CommonJS. WASM and web workers need to be [bundled](./typescript/bundling/webpack). -- **CommonJS full-fat** — Pre-bundled, works without additional configuration. +- **ESM:** For new projects with current tooling. You may need to [configure your bundler](./typescript/bundling) to handle WASM and web worker components. +- **ESM full-fat:** Pre-bundled with inline WASM and web workers. No bundler config needed. +- **CommonJS:** For older projects using CommonJS. WASM and web workers need to be [bundled](./typescript/bundling/webpack). +- **CommonJS full-fat:** Pre-bundled, works without additional configuration. All `*-full-fat` variants have large bundle sizes because they include WASM and web workers as inline Base64 strings. Use the standard ESM variant if bundle size matters. @@ -4033,28 +3913,26 @@ console.log(`Tx Hash = ${result.transactionHash}`); ## Next steps -- **[Step-by-step examples](./typescript/examples)** — Full working projects for each package -- **[Live playground](./typescript/playground)** — Try the SDK in your browser -- **[Bundling](./typescript/bundling)** — Configure Webpack or ESBuild for WASM and web workers -- **[TypeDoc reference](./typescript/api)** — generated reference for all packages +- **[Step-by-step examples](./typescript/examples):** Full working projects for each package +- **[Live playground](./typescript/playground):** Try the SDK in your browser +- **[Bundling](./typescript/bundling):** Configure Webpack or ESBuild for WASM and web workers +- **[TypeDoc reference](./typescript/api):** generated reference for all packages --- title: mixFetch Example: Private HTTP Requests -description: Replace browser fetch with mixFetch to route HTTP requests through the Nym mixnet. Covers setup, CA certificates, WSS gateways, and usage examples. +description: Replace browser fetch with mixFetch to route HTTP requests through the Nym mixnet. Covers setup, CA certificates, TLS configuration, and usage examples. url: https://nym.com/docs/developers/typescript/examples/mix-fetch --- # mixFetch -An easy way to secure parts or all of your web app is to replace calls to [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) with `mixFetch`. It works the same as vanilla `fetch` — it's a proxied wrapper around the original function. +An easy way to secure parts or all of your web app is to replace calls to [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) with `mixFetch`. It works the same as vanilla `fetch`: it's a proxied wrapper around the original function. Things to be aware of: -- CA certificates in `mixFetch` are periodically updated. If you get a certificate error, the root certificate you need might not be valid yet — [send a PR](https://github.com/nymtech/nym/pulls) if you need changes to the certificates. -- If you are using `mixFetch` in a web app with HTTPS, you will need to use a gateway that has Secure Websockets (WSS) to avoid a [mixed content](https://developer.mozilla.org/en-US/docs/Web/Security/Mixed_content) error. -- `mixFetch` supports concurrent requests (up to 10) to the same or different URLs. - -Right now Gateways are not required to run a Secure Websocket (WSS) listener, so only a subset of nodes running in Gateway mode have configured their nodes to do so. You need to select a Gateway that has WSS from [Harbourmaster](https://harbourmaster.nymtech.net/). +- **CA certificates** are bundled into the WASM binary at build time. They're updated with each SDK release, so if you hit a certificate error, update to the latest `@nymproject/mix-fetch-full-fat` version. +- **HTTPS and WSS.** When serving your app over HTTPS, the mixnet connection must also use Secure WebSockets to avoid a [mixed content](https://developer.mozilla.org/en-US/docs/Web/Security/Mixed_content) error. Set `forceTls: true` in your `SetupMixFetchOps` config (see below) and the SDK will automatically select a WSS-capable gateway. +- `mixFetch` supports **concurrent requests** (up to 10) to the same or different URLs. ## Environment Setup @@ -4075,28 +3953,32 @@ npm run dev ## Installation ```bash -npm install @nymproject/mix-fetch-full-fat +npm install @nymproject/mix-fetch-full-fat @mui/material @emotion/react @emotion/styled ``` +The MUI packages are used by the example UI below. If you only need `mixFetch` itself, install just `@nymproject/mix-fetch-full-fat`. + ## Configuration ```ts const mixFetchOptions: SetupMixFetchOps = { - clientId: "docs-mixfetch-demo", + clientId: "my-app", preferredGateway: "q2A2cbooyC16YJzvdYaSMH9X3cSiieZNtfBr8cE8Fi1", mixFetchOverride: { requestTimeoutMs: 60_000, }, - forceTls: true, // force WSS + forceTls: true, // use Secure WebSockets (required when serving over HTTPS) }; ``` +`preferredGateway` is optional. If omitted, the SDK auto-selects a gateway. You can pin a specific one via [Harbourmaster](https://harbourmaster.nymtech.net/). + ## Full Example This example shows explicit initialization via `createMixFetch`, single URL fetch, and concurrent requests. Results appear both in the UI and in a visible log panel. -For this example we use the `full-fat` version of the ESM SDK. If you use the unbundled ESM variant, make sure your [bundler configuration](../bundling/bundling) copies the WASM and web worker files to the output bundle. +For this example we use the `full-fat` version of the ESM SDK. If you use the unbundled ESM variant, make sure your [bundler configuration](/developers/typescript/bundling/bundling) copies the WASM and web worker files to the output bundle. ```tsx @@ -4176,7 +4058,7 @@ export const MixFetch = () => { } }; - // Single URL fetch — reuses the existing MixFetch singleton + // Single URL fetch: reuses the existing MixFetch singleton const handleFetch = async () => { try { setBusy(true); @@ -4241,7 +4123,7 @@ export const MixFetch = () => { status === "ready" ? "Ready" : `Error: ${errorMsg}`} - {/* Fetch controls — disabled until MixFetch is ready */} + {/* Fetch controls: disabled until MixFetch is ready */} {/* Single fetch */} @@ -4308,7 +4190,7 @@ url: https://nym.com/docs/developers/typescript/examples/mixnet The [`SDK Client`](https://www.npmjs.com/package/@nymproject/sdk) lets you send and receive messages over the Nym mixnet. -The client is message-based — it sends one-way messages to another client's address. Replying can be achieved in two ways: +The client is message-based: it sends one-way messages to another client's address. Replying can be achieved in two ways: - Reveal the sender's address to the recipient (as part of the payload) - Use a SURB (single use reply block) that lets the recipient reply without compromising the identity of either party @@ -4338,7 +4220,7 @@ npm install @nymproject/sdk-full-fat This example creates a Mixnet client, connects to a gateway, and provides a UI for sending and receiving messages through the mixnet. -For this example we use the `full-fat` version of the ESM SDK. If you use the unbundled ESM variant, make sure your [bundler configuration](../bundling/bundling) copies the WASM and web worker files to the output bundle. +For this example we use the `full-fat` version of the ESM SDK. If you use the unbundled ESM variant, make sure your [bundler configuration](/developers/typescript/bundling/bundling) copies the WASM and web worker files to the output bundle. ```ts copy filename="App.tsx" @@ -4871,8 +4753,8 @@ You'll have to experiment with either adjusting the CSP or use another variant t The mixnet client will complain about insufficient topology in the following cases: - There are empty mix layers (rare) -- The gateway you've registered with does not appear in the network topology — it is either unbonded or was blacklisted -- The gateway you want to send packets to does not appear in the network topology — it is either unbonded or was blacklisted +- The gateway you've registered with does not appear in the network topology; it is either unbonded or was blacklisted +- The gateway you want to send packets to does not appear in the network topology; it is either unbonded or was blacklisted To avoid the last two, make sure the gateway you are using is bonded and whitelisted. @@ -4884,7 +4766,7 @@ For example: `DpB3cHAchJi...suko.ANNWrvHq...U2Vx@2BuMSfMW...3SEh` - First part: client's identity key - Second part: client's Diffie-Hellman key -- After `@`: gateway's identity key — search for this in the [Nym Explorer](https://nym.com/explorer) to check its status +- After `@`: gateway's identity key. Search for this in the [Nym Explorer](https://nym.com/explorer) to check its status --- title: Troubleshooting bundling with ESbuild @@ -5029,9 +4911,9 @@ Download and run instructions for the GUIs can be found [here](https://nymvpn.co ## Download & Extract Binary Check the [release page](https://github.com/nymtech/nym-vpn-client/releases/) page for the latest release version and modify the instructions accordingly. These instructions use the latest as of the time of writing. ```sh -wget -q https://github.com/nymtech/nym-vpn-client/releases/download/nym-vpn-core-v1.27.0-beta/nym-vpn-core-v1.27.0-beta_.tar.gz && -tar -xzf nym-vpn-core-v1.27.0-beta_.tar.gz && -cd nym-vpn-core-v1.27.0-beta_/ && +wget -q https://github.com/nymtech/nym-vpn-client/releases/download/nym-vpn-core-v1.29.0/nym-vpn-core-v1.29.0_.tar.gz && +tar -xzf nym-vpn-core-v1.29.0_.tar.gz && +cd nym-vpn-core-v1.29.0_/ && chmod u+x * ``` @@ -5173,6 +5055,64 @@ View the current device identity: nym-vpnc device get ``` +## Pay as You Go: Decentralized Access to Nym + +You can fund your VPN usage directly from your own wallet instead of going through the NymVPN account system. You deposit `$NYM` into the ticketbook smart contract and receive zk-nym ticketbooks that authenticate you on the network. + + If you already have an account stored in `nym-vpnc`, you must remove it first with `nym-vpnc account forget` before setting a new mnemonic. + +### Set Your Mnemonic + +Store the recovery phrase for your on-chain wallet address (`n1...`) that holds your `$NYM` tokens: + +```sh +nym-vpnc account set "" --location blockchain +``` + +You must fund this address yourself, for example by transferring `$NYM` from an exchange or another wallet. The `--location blockchain` flag tells `nym-vpnc` to use the on-chain wallet directly rather than the NymVPN account system. + +### Obtain Ticketbooks + +Deposit `$NYM` into the ticketbook smart contract and receive zk-nym credentials: + +```sh +nym-vpnc account obtain-ticketbooks --amount 1 --source smartcontract +``` + +You can omit `--source` to use the default: + +```sh +nym-vpnc account obtain-ticketbooks --amount 1 +``` + +The `--amount` flag specifies how many ticketbooks to obtain **per ticket type**. Each request issues one ticketbook for each of the three types listed below, so `--amount 1` produces 3 ticketbooks total and `--amount 2` produces 6. + +Each ticketbook contains **50 tickets** and is valid for **7 days**. Each ticketbook costs **75 NYM**, so `--amount 1` deposits **225 NYM** (75 × 3 types) and `--amount 2` deposits **450 NYM**. + +| Kind | Ticket size | Ticketbook capacity | Used for | +|------|-------------|---------------------|----------| +| Mixnet Entry | 200 MB | 10 GB (200 MB × 50) | 5-hop mixnet mode | +| WireGuard Entry | 500 MB | 25 GB (500 MB × 50) | 2-hop WireGuard mode (entry side) | +| WireGuard Exit | 500 MB | 25 GB (500 MB × 50) | 2-hop WireGuard mode (exit side) | + + If you only use two-hop (WireGuard) mode, the Mixnet Entry ticketbooks will go unused. There is currently no way to obtain ticketbooks for a single type. + +This command: + +- Deposits `$NYM` into the ticketbook smart contract (plus a small fee buffer per deposit) +- Requests credential issuance from the decentralised Nym API validators +- Stores the resulting zk-nym ticketbooks locally on the device + +### Connect + +Connect using the locally stored ticketbooks: + +```sh +nym-vpnc connect-v2 +``` + +The CLI uses the ticketbooks to authenticate with entry nodes (gateways) and connect to the Nym network. You will see the connection happening in the `nym-vpnd` logs. + ## Tunnel Configuration Print current tunnel configuration: @@ -5181,13 +5121,13 @@ Print current tunnel configuration: nym-vpnc tunnel get ``` -Enable two-hop mode (WireGuard) — traffic jumps directly from entry gateway to exit gateway: +Enable two-hop mode (WireGuard): traffic jumps directly from entry gateway to exit gateway: ```sh nym-vpnc tunnel set --two-hop on ``` -Enable Mixnet (5-hop) — disable two-hop to route traffic through the full mixnet for maximum privacy: +Enable Mixnet (5-hop): disable two-hop to route traffic through the full mixnet for maximum privacy: ```sh nym-vpnc tunnel set --two-hop off @@ -5298,7 +5238,7 @@ Disable ad-block: nym-vpnc ad-block set disabled ``` - You can test ad-blocking with [adblock.turtlecute.org](https://adblock.turtlecute.org/). Some browsers cache DNS internally, so toggling ad-block on/off at runtime may not have an immediate effect — a browser restart may be needed. Use `nslookup` or `dig` to verify that domains are being blocked. + You can test ad-blocking with [adblock.turtlecute.org](https://adblock.turtlecute.org/). Some browsers cache DNS internally, so toggling ad-block on/off at runtime may not have an immediate effect; a browser restart may be needed. Use `nslookup` or `dig` to verify that domains are being blocked. ## DNS @@ -5391,7 +5331,7 @@ There are two options for interacting with the blockchain to send tokens or inte * `nyxd` binary ## Nym-CLI tool (recommended in most cases) -The `nym-cli` tool is a binary offering a simple interface for interacting with deployed smart contract (for instance, bonding and unbonding a mix node from the CLI), as well as creating and managing accounts and keypairs, sending tokens, and querying the blockchain. +The `nym-cli` tool is a binary offering a simple interface for interacting with deployed smart contract (for instance, bonding and unbonding a Mix Node from the CLI), as well as creating and managing accounts and keypairs, sending tokens, and querying the blockchain. Instructions on how to do so can be found on the [`nym-cli` docs page](./tools/nym-cli) @@ -5471,7 +5411,7 @@ nyxd tx bank send ledger_account $DESTINATION_ACCOOUNT 1000000unym --ledger --no > When a command is run, the transaction will appear on the Ledger device and will require physical confirmation from the device before being signed. ## Nym-specific transactions -Nym-specific commands and queries, like bonding a mix node or delegating unvested tokens, are available in the `wasm` module, and follow the following pattern: +Nym-specific commands and queries, like bonding a Mix Node or delegating unvested tokens, are available in the `wasm` module, and follow the following pattern: ``` # Executing commands @@ -5486,8 +5426,8 @@ You can find the value of `$CONTRACT_ADDRESS` in the [`network defaults`](https: The value of `$JSON_MSG` will be a blog of `json` formatted as defined for each command and query. You can find these definitions for the mixnet smart contract [here](https://github.com/nymtech/nym/blob/master/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs) and for the vesting contract [here](https://github.com/nymtech/nym/blob/master/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs) under `ExecuteMsg` and `QueryMsg`. ### Example command execution: -#### Delegate to a mix node -You can delegate to a mix node from the CLI using `nyxd` and signing the transaction with your ledger by filling in the values of this example: +#### Delegate to a Mix Node +You can delegate to a Mix Node from the CLI using `nyxd` and signing the transaction with your ledger by filling in the values of this example: ``` CONTRACT_ADDRESS=mixnet_contract_address @@ -5528,13 +5468,13 @@ url: https://nym.com/docs/developers/tools # Tools -Standalone binaries for development and testing. These don't require an SDK — download or compile them and use them directly. +Standalone binaries for development and testing that don't require an SDK. Download or compile them and use them directly. | Tool | Use case | |---|---| -| [nym-cli](./tools/nym-cli) | Command-line interface for interacting with the Nyx blockchain — querying state, submitting transactions, managing keys. An easier-to-use wrapper around `nyxd`. | +| [nym-cli](./tools/nym-cli) | Command-line interface for interacting with the Nyx blockchain: querying state, submitting transactions, managing keys. An easier-to-use wrapper around `nyxd`. | | [Diagnostic Tool](./tools/diagnostic-tool) | Network diagnostic utility for troubleshooting connectivity issues. | -| [Standalone TcpProxy](./tools/standalone-tcpproxy) | Pre-built binaries of the TcpProxy client and server for proxying TCP traffic through the Mixnet. Note: the TcpProxy module is unmaintained — use the [Stream module](./rust/stream) for new projects. | +| [Standalone TcpProxy](./tools/standalone-tcpproxy) | Pre-built binaries of the TcpProxy client and server for proxying TCP traffic through the Mixnet. Note: the TcpProxy module is unmaintained; use the [Stream module](./rust/stream) for new projects. | --- title: Nym CLI: Mixnet & Blockchain Commands @@ -5562,21 +5502,21 @@ url: https://nym.com/docs/developers/tools/nym-cli/usage The `nym-cli` binary can be built by running `cargo build --release` in the `nym/tools/nym-cli` directory. ## Usage -See the [commands](commands.mdx) page for an overview of all command options. +See the [commands](/developers/tools/nym-cli/commands) page for an overview of all command options. ## Staking on someone's behalf (for custodians) There is a limitation the staking address can only perform the following actions (and are visible via the Nym Wallet: -- Bond on the gateway's or mix node's behalf. -- Delegate or Un-delegate (to a mix node in order to begin receiving rewards) +- Bond on the gateway's or Mix Node's behalf. +- Delegate or Un-delegate (to a Mix Node in order to begin receiving rewards) - Claiming the rewards on the account ```admonish note title="" The staking address has no ability to withdraw any coins from the parent's account. ``` -The staking address must maintain the same level of security as the parent mnemonic; while the parent mnemonic's delegations and bonding events will be visible to the parent owner, the staking address will be the only account capable of undoing the bonding and delegating from the mix nodes or gateway. +The staking address must maintain the same level of security as the parent mnemonic; while the parent mnemonic's delegations and bonding events will be visible to the parent owner, the staking address will be the only account capable of undoing the bonding and delegating from the Mix Nodes or gateway. Query for staking on behalf of someone else ``` @@ -5625,7 +5565,7 @@ Options: Print help ``` -## `account` +## `account` ```sh Query and manage Nyx blockchain accounts @@ -5667,7 +5607,7 @@ Options: --mnemonic Provide the mnemonic for your account. You can also provide this is an env var called MNEMONIC. --word-count - + -c, --config-env-file Overrides configuration as a file of environment variables. Note: individual env vars take precedence over this file. --nyxd-url @@ -5760,7 +5700,7 @@ Options: -c, --config-env-file Overrides configuration as a file of environment variables. Note: individual env vars take precedence over this file. --memo - + --nyxd-url Overrides the nyxd URL provided either as an environment variable NYXD_VALIDATOR or in a config file --nym-api-url @@ -5781,7 +5721,7 @@ Usage: nym-cli account send-multiple [OPTIONS] --input Options: --memo - + --mnemonic Provide the mnemonic for your account. You can also provide this is an env var called MNEMONIC. -c, --config-env-file @@ -5802,7 +5742,7 @@ Options: Print help ``` -## `signature` +## `signature` ```sh Sign and verify messages @@ -5885,7 +5825,7 @@ Options: Print help ``` -## `ecash` +## `ecash` ```sh Ecash related stuff @@ -5893,13 +5833,13 @@ Usage: nym-cli ecash [OPTIONS] nym-cli ecash Commands: - issue-ticket-book - recover-ticket-book - import-ticket-book - generate-ticket - import-coin-index-signatures - import-expiration-date-signatures - import-master-verification-key + issue-ticket-book + recover-ticket-book + import-ticket-book + generate-ticket + import-coin-index-signatures + import-expiration-date-signatures + import-master-verification-key help Print this message or the help of the given subcommand(s) Options: @@ -6010,7 +5950,7 @@ Options: Print help ``` -## `coconut` +## `coconut` ## `coconut generate-freepass` @@ -6020,7 +5960,7 @@ Options: ## `coconut import-credential` -## `block` +## `block` ```sh Query chain blocks @@ -6125,7 +6065,7 @@ Options: Print help ``` -## `cosmwasm` +## `cosmwasm` ```sh Manage and execute WASM smart contracts @@ -6168,11 +6108,11 @@ Options: --mnemonic Provide the mnemonic for your account. You can also provide this is an env var called MNEMONIC. --wasm-path - + -c, --config-env-file Overrides configuration as a file of environment variables. Note: individual env vars take precedence over this file. --memo - + --nyxd-url Overrides the nyxd URL provided either as an environment variable NYXD_VALIDATOR or in a config file --nym-api-url @@ -6195,19 +6135,19 @@ Arguments: Options: --memo - + --mnemonic Provide the mnemonic for your account. You can also provide this is an env var called MNEMONIC. -c, --config-env-file Overrides configuration as a file of environment variables. Note: individual env vars take precedence over this file. --label