From 7b62fa792c565bebc8a34192e0a38984b95f5033 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Wed, 29 Apr 2026 10:12:44 +0100 Subject: [PATCH] Remove documentation changes (split to separate branch) --- documentation/.gitignore | 1 - .../docs/components/code-verified.tsx | 33 +- .../docs/components/crates-paused.tsx | 13 + documentation/docs/components/mix-fetch.tsx | 197 +- .../circulating-supply.json | 4 +- .../api-scraping-outputs/nodes-count.json | 8 +- .../nyx-outputs/circulating-supply.md | 2 +- .../nyx-outputs/epoch-reward-budget.md | 2 +- .../nyx-outputs/nyx-percent-stake.md | 2 +- .../nyx-outputs/nyx-total-stake.md | 2 +- .../nyx-outputs/stake-saturation.md | 2 +- .../nyx-outputs/staking-target.md | 2 +- .../nyx-outputs/staking_supply.md | 2 +- .../nyx-outputs/token-table.md | 6 +- .../api-scraping-outputs/reward-params.json | 8 +- .../outputs/api-scraping-outputs/time-now.md | 2 +- .../outputs/command-outputs/nym-node-help.md | 3 +- .../command-outputs/nym-node-run-help.md | 164 +- .../docs/pages/developers/_meta.json | 9 - .../pages/developers/archive/nym-connect.mdx | 10 +- .../docs/pages/developers/browsers.mdx | 27 +- .../docs/pages/developers/clients/socks5.mdx | 2 +- .../developers/clients/webassembly-client.mdx | 4 +- .../developers/concepts/message-queue.mdx | 4 +- documentation/docs/pages/developers/index.mdx | 1 - .../docs/pages/developers/integrations.mdx | 8 +- .../docs/pages/developers/native.mdx | 54 +- documentation/docs/pages/developers/rust.mdx | 4 +- .../pages/developers/rust/client-pool.mdx | 2 +- .../developers/rust/client-pool/tutorial.mdx | 58 +- .../docs/pages/developers/rust/importing.mdx | 28 +- .../pages/developers/rust/mixnet/tutorial.mdx | 47 +- .../docs/pages/developers/rust/stream.mdx | 4 +- .../developers/rust/stream/architecture.mdx | 2 +- .../pages/developers/rust/stream/tutorial.mdx | 29 +- .../docs/pages/developers/rust/tcpproxy.mdx | 19 +- .../pages/developers/tools/nym-cli/usage.mdx | 2 +- .../developers/tools/standalone-tcpproxy.mdx | 2 +- .../typescript/examples/mix-fetch.mdx | 24 +- .../developers/typescript/examples/mixnet.mdx | 2 +- .../docs/pages/network/dvpn-mode/protocol.mdx | 2 - .../pages/network/infrastructure/_meta.json | 3 +- .../network/infrastructure/nym-nodes.mdx | 2 +- documentation/docs/public/llms-full.txt | 3143 ++++++----------- documentation/docs/public/llms.txt | 9 +- 45 files changed, 1483 insertions(+), 2471 deletions(-) create mode 100644 documentation/docs/components/crates-paused.tsx diff --git a/documentation/.gitignore b/documentation/.gitignore index cf1c85310b..4329a0d6c0 100644 --- a/documentation/.gitignore +++ b/documentation/.gitignore @@ -1,3 +1,2 @@ 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 1742160b4c..f3390c6668 100644 --- a/documentation/docs/components/code-verified.tsx +++ b/documentation/docs/components/code-verified.tsx @@ -1,33 +1,22 @@ import { Callout } from "nextra/components"; -import { NYM_SDK_VERSION, SMOLMIX_VERSION } from "./versions"; -const VERSIONS: Record = { - "nym-sdk": NYM_SDK_VERSION, - smolmix: SMOLMIX_VERSION, -}; +const COMMIT_SHORT = "97068b2"; +const COMMIT_FULL = "97068b2aa"; +const EXAMPLES_URL = + "https://github.com/nymtech/nym/tree/develop/sdk/rust/nym-sdk/examples"; -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) => ( +export const CodeVerified = () => ( - Code verified against{" "} - {crateName} v{VERSIONS[crateName]}. If the API has changed - since then, check the{" "} + Code verified against commit{" "} + {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 new file mode 100644 index 0000000000..1194a5222e --- /dev/null +++ b/documentation/docs/components/crates-paused.tsx @@ -0,0 +1,13 @@ +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/mix-fetch.tsx b/documentation/docs/components/mix-fetch.tsx index 0fb6298e37..a754ba7f62 100644 --- a/documentation/docs/components/mix-fetch.tsx +++ b/documentation/docs/components/mix-fetch.tsx @@ -153,116 +153,115 @@ export const MixFetch = () => { error: `Error: ${errorMsg}`, }; const statusColor: Record = { - idle: "#9e9e9e", + idle: "gray", starting: "orange", - ready: "#85E89D", - error: "#ff6b6b", + ready: "green", + error: "red", }; return ( - - - - {/* --- Start MixFetch Section --- */} - - - {status === "starting" && } - - {statusText[status]} - - - - {/* --- Fetch Controls (disabled until ready) --- */} - + {/* --- Start MixFetch Section --- */} + + + - - {busy && ( - - - - )} - {html && ( - <> - - Response - - - - {html} - - - - )} - - {/* Concurrent fetch demo */} - - Concurrent Requests - - - - - {concurrentBusy && ( - - - - )} - {concurrentResults.length > 0 && ( - - {concurrentResults.map((result, i) => ( - - {result} - - ))} - - )} - + Start MixFetch + + {status === "starting" && } + + {statusText[status]} + + {/* --- 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) => ( @@ -277,6 +276,6 @@ export const MixFetch = () => { ))} )} - + ); }; diff --git a/documentation/docs/components/outputs/api-scraping-outputs/circulating-supply.json b/documentation/docs/components/outputs/api-scraping-outputs/circulating-supply.json index 4be1f2b19c..b0b84dce4f 100644 --- a/documentation/docs/components/outputs/api-scraping-outputs/circulating-supply.json +++ b/documentation/docs/components/outputs/api-scraping-outputs/circulating-supply.json @@ -5,7 +5,7 @@ }, "mixmining_reserve": { "denom": "unym", - "amount": "166613455567357" + "amount": "168575020719057" }, "vesting_tokens": { "denom": "unym", @@ -13,6 +13,6 @@ }, "circulating_supply": { "denom": "unym", - "amount": "833386544432643" + "amount": "831424979280943" } } 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 5b7b180436..5cb146d756 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": 744, - "locations": 77, - "mixnodes": 264, - "exit_gateways": 473 + "nodes": 752, + "locations": 76, + "mixnodes": 273, + "exit_gateways": 470 } diff --git a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/circulating-supply.md b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/circulating-supply.md index 6f6c094083..ae8c3a68df 100644 --- a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/circulating-supply.md +++ b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/circulating-supply.md @@ -1 +1 @@ -833_386_544 +831_424_979 diff --git a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/epoch-reward-budget.md b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/epoch-reward-budget.md index b72f034b0c..1015a52561 100644 --- a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/epoch-reward-budget.md +++ b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/epoch-reward-budget.md @@ -1 +1 @@ -4_628 +4_682 diff --git a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-percent-stake.md b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-percent-stake.md index 41bb4abda8..dfc77dadd9 100644 --- a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-percent-stake.md +++ b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-percent-stake.md @@ -1 +1 @@ -1.06% +0.95% diff --git a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-total-stake.md b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-total-stake.md index 14c14b06e8..dc5feb0f32 100644 --- a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-total-stake.md +++ b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-total-stake.md @@ -1 +1 @@ -27.103 +30.134 diff --git a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/stake-saturation.md b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/stake-saturation.md index 02acf9bc71..48a177e40e 100644 --- a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/stake-saturation.md +++ b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/stake-saturation.md @@ -1 +1 @@ -254_977 +254_377 diff --git a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/staking-target.md b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/staking-target.md index c5a32267aa..6bd8deaa29 100644 --- a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/staking-target.md +++ b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/staking-target.md @@ -1 +1 @@ -61_194_673 +61_050_638 diff --git a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/staking_supply.md b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/staking_supply.md index aa81fc5ba9..38e6ef2c2b 100644 --- a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/staking_supply.md +++ b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/staking_supply.md @@ -1 +1 @@ -61_194_672 +61_050_637 diff --git a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/token-table.md b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/token-table.md index fdf11e42a6..b68b982b3f 100644 --- a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/token-table.md +++ b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/token-table.md @@ -1,7 +1,7 @@ | **Item** | **Description** | **Amount in NYM** | |:-------------------|:------------------------------------------------------|--------------------:| | Total Supply | Maximum amount of NYM token in existence | 1_000_000_000 | -| Mixmining Reserve | Tokens releasing for operators rewards | 166_613_455 | +| Mixmining Reserve | Tokens releasing for operators rewards | 168_575_020 | | Vesting Tokens | Tokens locked outside of circulation for future claim | 0 | -| Circulating Supply | Amount of unlocked tokens | 833_386_544 | -| Stake Saturation | Optimal size of node self-bond + delegation | 254_977 | +| Circulating Supply | Amount of unlocked tokens | 831_424_979 | +| Stake Saturation | Optimal size of node self-bond + delegation | 254_377 | diff --git a/documentation/docs/components/outputs/api-scraping-outputs/reward-params.json b/documentation/docs/components/outputs/api-scraping-outputs/reward-params.json index 9e7ce9741e..c35c75bb28 100644 --- a/documentation/docs/components/outputs/api-scraping-outputs/reward-params.json +++ b/documentation/docs/components/outputs/api-scraping-outputs/reward-params.json @@ -1,10 +1,10 @@ { "interval": { - "reward_pool": "166613455567357.391753168447944888", - "staking_supply": "61194672938727.325886595653401629", + "reward_pool": "168575020719057.456153922699547282", + "staking_supply": "61050637328128.353993717821521057", "staking_supply_scale_factor": "0.07342892", - "epoch_reward_budget": "4628151543.537705326476901331", - "stake_saturation_point": "254977803911.363857860815222506", + "epoch_reward_budget": "4682639464.418262670942297209", + "stake_saturation_point": "254377655533.868141640490923004", "sybil_resistance": "0.3", "active_set_work_factor": "10", "interval_pool_emission": "0.02" 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 7dfd216126..cb9158928b 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 @@ -Tuesday, April 28th 2026, 22:40:06 UTC +Thursday, April 9th 2026, 15:33:24 UTC 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 45d2749641..7eaa0fdaa3 100644 --- a/documentation/docs/components/outputs/command-outputs/nym-node-help.md +++ b/documentation/docs/components/outputs/command-outputs/nym-node-help.md @@ -12,8 +12,7 @@ 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-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 ef3e70a472..b897b09fea 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,116 +4,56 @@ 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/pages/developers/_meta.json b/documentation/docs/pages/developers/_meta.json index 3e2186a3aa..63a7809c49 100644 --- a/documentation/docs/pages/developers/_meta.json +++ b/documentation/docs/pages/developers/_meta.json @@ -8,15 +8,6 @@ "native": "Native Apps", "browsers": "Browser Apps", "concepts": "Nym-Specific Concepts for Integrations", - "-----": { - "type": "separator", - "title": "Standalone Crates" - }, - "smolmix": "smolmix", - "smolmix-dns": "smolmix-dns", - "smolmix-tls": "smolmix-tls", - "smolmix-hyper": "smolmix-hyper", - "smolmix-extending": "Building on smolmix", "-": { "type": "separator", "title": "SDKs" diff --git a/documentation/docs/pages/developers/archive/nym-connect.mdx b/documentation/docs/pages/developers/archive/nym-connect.mdx index e4b2da93c2..024eff6ab5 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](/developers/clients/socks5/setup). +If you want to route traffic through SOCKS5, use the maintained [Nym Socks5 Client](../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`](/developers/clients/socks5/setup)) +1. Start and connect NymConnect (or [`nym-socks5-client`](../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`](/developers/clients/socks5/setup)) +1. Start and connect NymConnect (or [`nym-socks5-client`](../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`](/developers/clients/socks5/setup)) +1. Start and connect NymConnect (or [`nym-socks5-client`](../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`](/developers/clients/socks5/setup)) +1. Start and connect NymConnect (or [`nym-socks5-client`](../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 index 7d1ed7e939..e61cacd694 100644 --- a/documentation/docs/pages/developers/browsers.mdx +++ b/documentation/docs/pages/developers/browsers.mdx @@ -16,15 +16,28 @@ Two integration options are available, both delivered as packages bundled into y ![](/images/developers/nym-browser-arch.png) -| Module | What it does | Links | -|---|---|---| -| **mixFetch** | Drop-in `fetch` replacement — 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 + +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. - -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. - +## 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/clients/socks5.mdx b/documentation/docs/pages/developers/clients/socks5.mdx index 9567f1e03b..bcc1a6458d 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](/developers/rust). +> This client can also be utilised via the [Rust SDK](../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: diff --git a/documentation/docs/pages/developers/clients/webassembly-client.mdx b/documentation/docs/pages/developers/clients/webassembly-client.mdx index 9a4cdb5c38..9c7241c1ac 100644 --- a/documentation/docs/pages/developers/clients/webassembly-client.mdx +++ b/documentation/docs/pages/developers/clients/webassembly-client.mdx @@ -3,12 +3,12 @@ ## 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. -This is currently packaged and distributed for ease of use via the [Nym Typescript SDK library](/developers/typescript). **We imagine most developers will use this client via the SDK for ease.** +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.** 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). ## Building apps with Webassembly Client -Check out the [Typescript SDK docs](/developers/typescript) for examples of usage. +Check out the [Typescript SDK docs](../typescript) for examples of usage. ## Think about what you're sending! import { Callout } from 'nextra/components' diff --git a/documentation/docs/pages/developers/concepts/message-queue.mdx b/documentation/docs/pages/developers/concepts/message-queue.mdx index d9f0f1aa1a..c93dc5bf3c 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`](/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). + 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). ## 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](/developers/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](../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. diff --git a/documentation/docs/pages/developers/index.mdx b/documentation/docs/pages/developers/index.mdx index 9589d1b813..846afb616a 100644 --- a/documentation/docs/pages/developers/index.mdx +++ b/documentation/docs/pages/developers/index.mdx @@ -15,7 +15,6 @@ Build applications that protect user metadata using the Nym Mixnet. This section **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](/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. diff --git a/documentation/docs/pages/developers/integrations.mdx b/documentation/docs/pages/developers/integrations.mdx index 79dd4b5873..713d5cd2a1 100644 --- a/documentation/docs/pages/developers/integrations.mdx +++ b/documentation/docs/pages/developers/integrations.mdx @@ -16,8 +16,8 @@ Any application that integrates with Nym sends its traffic through the Mixnet vi 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](/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). +- **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). ## Architecture @@ -25,10 +25,10 @@ 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. Companion crates add DNS ([`smolmix-dns`](/developers/smolmix-dns)), TLS ([`smolmix-tls`](/developers/smolmix-tls)), and HTTP ([`smolmix-hyper`](/developers/smolmix-hyper)) on top. +**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. 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. +See the [Native / Desktop](./native) and [Browser](./browsers) pages for the specific modules available in each environment. diff --git a/documentation/docs/pages/developers/native.mdx b/documentation/docs/pages/developers/native.mdx index d65cdedf21..499c65fb95 100644 --- a/documentation/docs/pages/developers/native.mdx +++ b/documentation/docs/pages/developers/native.mdx @@ -10,40 +10,54 @@ import { Callout } from 'nextra/components'; # Native / Desktop Apps -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. +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. -## Mixnet End-To-End +## 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) -| 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` — 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) | +### 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) -**TcpProxy is unmaintained.** Use the [Stream module](/developers/rust/stream) for new projects. +**This module is unmaintained.** Use the [Stream module](./rust/stream) for new projects. Existing users should plan to migrate when possible. -## Mixnet-As-Proxy +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) -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. + +### 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. -| Standalone Crate | What it does | Links | -|---|---|---| -| **`smolmix`** | Userspace IP tunnel — `TcpStream` and `UdpSocket` over the mixnet, compatible with the entire async Rust ecosystem | [docs](/developers/smolmix) | -| **`smolmix-dns`** | DNS resolution through the tunnel — prevents hostname leaks to local network | [docs](/developers/smolmix-dns) | -| **`smolmix-tls`** | TLS connector with webpki roots — encrypts the final hop between exit gateway and destination | [docs](/developers/smolmix-tls) | -| **`smolmix-hyper`** | HTTP client routing DNS + TCP + TLS through the mixnet — the "fetch a URL anonymously" crate | [docs](/developers/smolmix-hyper) | +### 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. -| 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) | +- [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/rust.mdx b/documentation/docs/pages/developers/rust.mdx index 0598c40aa8..93affb53ae 100644 --- a/documentation/docs/pages/developers/rust.mdx +++ b/documentation/docs/pages/developers/rust.mdx @@ -9,7 +9,7 @@ lastUpdated: "2026-03-13" # Rust SDK import { Callout } from 'nextra/components' -import { VersionBanner } from '../../components/version-banner' +import { CratesPaused } from '../../components/crates-paused' All modules share a common `MixnetClient` that manages gateway connections, Sphinx packet encryption, routing, and cover traffic. @@ -17,7 +17,7 @@ All modules share a common `MixnetClient` that manages gateway connections, Sphi 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). diff --git a/documentation/docs/pages/developers/rust/client-pool.mdx b/documentation/docs/pages/developers/rust/client-pool.mdx index 8188c8cafb..311bd147be 100644 --- a/documentation/docs/pages/developers/rust/client-pool.mdx +++ b/documentation/docs/pages/developers/rust/client-pool.mdx @@ -45,7 +45,7 @@ use nym_sdk::client_pool::ClientPool; use nym_network_defaults::setup_env; #[tokio::main] -async fn main() -> Result<(), Box> { +async fn main() -> anyhow::Result<()> { 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 9ef9a2b9c9..525636c226 100644 --- a/documentation/docs/pages/developers/rust/client-pool/tutorial.mdx +++ b/documentation/docs/pages/developers/rust/client-pool/tutorial.mdx @@ -3,14 +3,13 @@ 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-04-17" +lastUpdated: "2026-03-26" --- # 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. @@ -26,7 +25,7 @@ In this tutorial you'll build a program that uses `ClientPool` to handle bursts ## Prerequisites -- Rust toolchain ({RUST_MSRV}+) +- Rust toolchain (1.70+) - A working internet connection ## Step 1: Set up the project @@ -40,9 +39,9 @@ Add dependencies to `Cargo.toml`: ```toml [dependencies] -nym-sdk = "X.Y.Z" -nym-network-defaults = "X.Y.Z" -nym-bin-common = { version = "X.Y.Z", features = ["basic_tracing"] } +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"] } blake3 = "=1.7.0" # required pin — see https://nymtech.net/docs/developers/rust/importing ``` @@ -232,33 +231,20 @@ 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(); - }); + tokio::spawn(async move { pool_bg.start().await.unwrap() }); - println!("Pool started — waiting for clients to connect..."); + println!("Waiting for pool to fill..."); tokio::time::sleep(Duration::from_secs(15)).await; - - let count = pool.get_client_count().await; - println!("Pool has {count} clients ready"); + println!("Pool has {} clients", pool.get_client_count().await); let mut handles = vec![]; - for i in 1..=3 { let pool = pool.clone(); - - let handle = tokio::spawn(async move { + handles.push(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() - } + Some(c) => c, + None => nym_sdk::mixnet::MixnetClient::connect_new().await.unwrap(), }; let addr = *client.nym_address(); @@ -268,34 +254,24 @@ async fn main() { .unwrap(); if let Some(msgs) = client.wait_for_messages().await { - for msg in msgs { - if !msg.message.is_empty() { - println!( - "Task {i}: received {:?}", - String::from_utf8_lossy(&msg.message) - ); - } + for msg in msgs.iter().filter(|m| !m.message.is_empty()) { + println!("Task {i}: {}", String::from_utf8_lossy(&msg.message)); } } client.disconnect().await; - println!("Task {i}: done"); - }); - - handles.push(handle); + })); } for h in handles { h.await.unwrap(); } - println!("\nWaiting for pool to replenish..."); + println!("Waiting for replenishment..."); tokio::time::sleep(Duration::from_secs(15)).await; - - let count = pool.get_client_count().await; - println!("Pool has {count} clients ready again"); + println!("Pool has {} clients", pool.get_client_count().await); pool.disconnect_pool().await; - println!("Pool shut down"); + println!("Done"); } ``` diff --git a/documentation/docs/pages/developers/rust/importing.mdx b/documentation/docs/pages/developers/rust/importing.mdx index 858a8b184a..dd9c95c976 100644 --- a/documentation/docs/pages/developers/rust/importing.mdx +++ b/documentation/docs/pages/developers/rust/importing.mdx @@ -3,20 +3,19 @@ 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-04-17" +lastUpdated: "2026-03-27" --- # Installation import { Callout } from 'nextra/components'; -import { VersionBanner } from '../../../components/version-banner' -import { RUST_MSRV } from '../../../components/versions' +import { CratesPaused } from '../../../components/crates-paused' - + ```toml [dependencies] -nym-sdk = "X.Y.Z" +nym-sdk = { git = "https://github.com/nymtech/nym", rev = "97068b2" } blake3 = "=1.7.0" # pin to avoid a transitive dependency conflict — see note below ``` @@ -24,11 +23,7 @@ blake3 = "=1.7.0" # pin to avoid a transitive dependency conflict — see note **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}+ - -### From Git - -You can also import directly from Git if you want unreleased changes: +You can also track a branch if you want the latest changes: ```toml # development branch (latest changes, may be unstable) @@ -38,6 +33,19 @@ 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/tutorial.mdx b/documentation/docs/pages/developers/rust/mixnet/tutorial.mdx index 5699fb0ad9..0453266998 100644 --- a/documentation/docs/pages/developers/rust/mixnet/tutorial.mdx +++ b/documentation/docs/pages/developers/rust/mixnet/tutorial.mdx @@ -3,18 +3,17 @@ 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-04-17" +lastUpdated: "2026-03-26" --- # 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. -**You'll need:** Rust {RUST_MSRV}+ and an internet connection (clients connect to the live Mixnet). +**You'll need:** Rust 1.70+ and an internet connection (clients connect to the live Mixnet). @@ -29,8 +28,8 @@ Add dependencies to `Cargo.toml`: ```toml [dependencies] -nym-sdk = "X.Y.Z" -nym-bin-common = { version = "X.Y.Z", features = ["basic_tracing"] } +nym-sdk = { 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"] } blake3 = "=1.7.0" # required pin — see https://nymtech.net/docs/developers/rust/importing ``` @@ -141,36 +140,19 @@ async fn main() { .await .unwrap(); - let our_address = client.nym_address(); - println!("Address: {our_address}"); + println!("Address: {}", client.nym_address()); - // Same API as before — send, receive, SURB reply. + // Same API as before — send, receive, reply. client - .send_plain_message(*our_address, "hello from persistent client!") + .send_plain_message(*client.nym_address(), "persistent identity!") .await .unwrap(); - println!("Sent — waiting for arrival..."); - 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; - } + 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)); } - }; - println!("Received: {}", String::from_utf8_lossy(&message.message)); - - 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. @@ -288,7 +270,6 @@ 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() @@ -299,10 +280,10 @@ async fn main() { .unwrap(); let our_address = client.nym_address(); - println!("Address: {our_address}"); + println!("Connected: {our_address}"); client - .send_plain_message(*our_address, "hello from persistent client!") + .send_plain_message(*our_address, "hello from the mixnet!") .await .unwrap(); println!("Sent — waiting for arrival..."); @@ -317,7 +298,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, "anonymous reply!").await.unwrap(); + client.send_reply(sender_tag, "hello back, anonymously!").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 ab6531330d..38d8d7e10a 100644 --- a/documentation/docs/pages/developers/rust/stream.mdx +++ b/documentation/docs/pages/developers/rust/stream.mdx @@ -9,9 +9,9 @@ lastUpdated: "2026-03-15" # Stream Module import { Callout } from 'nextra/components' -import { VersionBanner } from '../../../components/version-banner' +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. diff --git a/documentation/docs/pages/developers/rust/stream/architecture.mdx b/documentation/docs/pages/developers/rust/stream/architecture.mdx index 6015d41226..205eee370e 100644 --- a/documentation/docs/pages/developers/rust/stream/architecture.mdx +++ b/documentation/docs/pages/developers/rust/stream/architecture.mdx @@ -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, or the [docs.rs](https://docs.rs/nym-sdk/latest/nym_sdk/) API reference. +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. diff --git a/documentation/docs/pages/developers/rust/stream/tutorial.mdx b/documentation/docs/pages/developers/rust/stream/tutorial.mdx index 29f1366430..a9ec02f722 100644 --- a/documentation/docs/pages/developers/rust/stream/tutorial.mdx +++ b/documentation/docs/pages/developers/rust/stream/tutorial.mdx @@ -3,14 +3,13 @@ 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-04-17" +lastUpdated: "2026-03-26" --- # 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. @@ -26,7 +25,7 @@ In this tutorial you'll build two programs: a server that listens for incoming s ## Prerequisites -- Rust toolchain ({RUST_MSRV}+) +- Rust toolchain (1.70+) - A working internet connection (clients connect to the live Nym Mixnet) ## Step 1: Set up the project @@ -41,8 +40,8 @@ Add dependencies to `Cargo.toml`: ```toml [dependencies] -nym-sdk = "X.Y.Z" -nym-bin-common = { version = "X.Y.Z", features = ["basic_tracing"] } +nym-sdk = { 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"] } rand = "0.8" blake3 = "=1.7.0" # required pin — see https://nymtech.net/docs/developers/rust/importing @@ -280,10 +279,7 @@ async fn main() { loop { let mut stream = match listener.accept().await { Some(s) => s, - None => { - println!("Listener closed"); - break; - } + None => break, }; let stream_id = stream.id(); @@ -291,27 +287,16 @@ 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) => break, + Ok(0) | Err(_) => break, Ok(n) => n, - Err(e) => { - eprintln!("Stream {stream_id} read error: {e}"); - break; - } }; - - 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}"); + if let Err(_) = stream.write_all(&buf[..n]).await { 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 73527c3e05..e25154b474 100644 --- a/documentation/docs/pages/developers/rust/tcpproxy.mdx +++ b/documentation/docs/pages/developers/rust/tcpproxy.mdx @@ -3,7 +3,7 @@ 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." schemaType: "TechArticle" section: "Developers" -lastUpdated: "2026-04-17" +lastUpdated: "2026-03-27" --- # TcpProxy Module @@ -11,7 +11,7 @@ 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](/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. + **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. 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. @@ -20,7 +20,7 @@ The Stream module offers the same key benefit (familiar I/O patterns on top of t `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](/developers/tools/standalone-tcpproxy). +> Non-Rust/Go developers who want to experiment with this module can start with the [standalone binaries](../tools/standalone-tcpproxy). ## Examples @@ -54,10 +54,11 @@ Add dependencies to `Cargo.toml`: ```toml [dependencies] -nym-sdk = "1.20.4" -nym-network-defaults = "1.20.4" -nym-bin-common = { version = "1.20.4", features = ["basic_tracing"] } +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]] @@ -77,7 +78,7 @@ The server connects to the Mixnet and forwards incoming traffic to a local TCP s use nym_sdk::tcp_proxy::NymProxyServer; #[tokio::main] -async fn main() -> Result<(), Box> { +async fn main() -> anyhow::Result<()> { nym_bin_common::logging::setup_tracing_logger(); let mut server = NymProxyServer::new( @@ -105,7 +106,7 @@ use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::net::TcpStream; #[tokio::main] -async fn main() -> Result<(), Box> { +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::); @@ -168,7 +169,7 @@ The response will take 30–60 seconds to arrive as it traverses the Mixnet in b ## Architecture 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. +- **`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. ### Sessions & message ordering diff --git a/documentation/docs/pages/developers/tools/nym-cli/usage.mdx b/documentation/docs/pages/developers/tools/nym-cli/usage.mdx index e2c24e237e..6dced9fd92 100644 --- a/documentation/docs/pages/developers/tools/nym-cli/usage.mdx +++ b/documentation/docs/pages/developers/tools/nym-cli/usage.mdx @@ -4,7 +4,7 @@ The `nym-cli` binary can be built by running `cargo build --release` in the `nym/tools/nym-cli` directory. ## Usage -See the [commands](/developers/tools/nym-cli/commands) page for an overview of all command options. +See the [commands](commands.mdx) page for an overview of all command options. ## Staking on someone's behalf (for custodians) diff --git a/documentation/docs/pages/developers/tools/standalone-tcpproxy.mdx b/documentation/docs/pages/developers/tools/standalone-tcpproxy.mdx index 29e11d0868..89372e7b34 100644 --- a/documentation/docs/pages/developers/tools/standalone-tcpproxy.mdx +++ b/documentation/docs/pages/developers/tools/standalone-tcpproxy.mdx @@ -6,7 +6,7 @@ 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](/developers/rust/tcpproxy) can be found [here](https://github.com/nymtech/standalone-tcp-proxies/tree/main). +Standalone versions of the `TcpProxyClient` and `TcpProxyServer` [sdk module](../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. diff --git a/documentation/docs/pages/developers/typescript/examples/mix-fetch.mdx b/documentation/docs/pages/developers/typescript/examples/mix-fetch.mdx index 266e52220f..961df307ec 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, TLS configuration, and usage examples." +description: "Replace browser fetch with mixFetch to route HTTP requests through the Nym mixnet. Covers setup, CA certificates, WSS gateways, and usage examples." schemaType: "TechArticle" section: "Developers" lastUpdated: "2026-03-15" @@ -14,9 +14,13 @@ 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** are bundled into the WASM binary at build time. They're updated with each SDK release — 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. +- 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/). + ## Environment Setup @@ -37,34 +41,30 @@ npm run dev ## Installation ```bash -npm install @nymproject/mix-fetch-full-fat @mui/material @emotion/react @emotion/styled +npm install @nymproject/mix-fetch-full-fat ``` -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 import type { SetupMixFetchOps } from '@nymproject/mix-fetch-full-fat'; const mixFetchOptions: SetupMixFetchOps = { - clientId: "my-app", + clientId: "docs-mixfetch-demo", preferredGateway: "q2A2cbooyC16YJzvdYaSMH9X3cSiieZNtfBr8cE8Fi1", mixFetchOverride: { requestTimeoutMs: 60_000, }, - forceTls: true, // use Secure WebSockets (required when serving over HTTPS) + forceTls: true, // force WSS }; ``` -`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](/developers/typescript/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](../bundling/bundling) copies the WASM and web worker files to the output bundle. ```tsx diff --git a/documentation/docs/pages/developers/typescript/examples/mixnet.mdx b/documentation/docs/pages/developers/typescript/examples/mixnet.mdx index 6d0810c4f6..ef3e292fab 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](/developers/typescript/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](../bundling/bundling) copies the WASM and web worker files to the output bundle. ```ts copy filename="App.tsx" diff --git a/documentation/docs/pages/network/dvpn-mode/protocol.mdx b/documentation/docs/pages/network/dvpn-mode/protocol.mdx index fc0511d3fd..92cd9f9bf2 100644 --- a/documentation/docs/pages/network/dvpn-mode/protocol.mdx +++ b/documentation/docs/pages/network/dvpn-mode/protocol.mdx @@ -60,6 +60,4 @@ 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/infrastructure/_meta.json b/documentation/docs/pages/network/infrastructure/_meta.json index 5a13c8a080..d8114f2306 100644 --- a/documentation/docs/pages/network/infrastructure/_meta.json +++ b/documentation/docs/pages/network/infrastructure/_meta.json @@ -1,5 +1,4 @@ { "nyx": "Nyx Blockchain", - "nym-nodes": "Nym Nodes", - "exit-services": "Exit Gateway Services" + "nym-nodes": "Nym Nodes" } diff --git a/documentation/docs/pages/network/infrastructure/nym-nodes.mdx b/documentation/docs/pages/network/infrastructure/nym-nodes.mdx index b7b428bdb2..59cfbe2bf5 100644 --- a/documentation/docs/pages/network/infrastructure/nym-nodes.mdx +++ b/documentation/docs/pages/network/infrastructure/nym-nodes.mdx @@ -16,7 +16,7 @@ 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 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. +**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. ## Unified binary diff --git a/documentation/docs/public/llms-full.txt b/documentation/docs/public/llms-full.txt index 6d3d97bcd0..4c38206c07 100644 --- a/documentation/docs/public/llms-full.txt +++ b/documentation/docs/public/llms-full.txt @@ -1,7 +1,7 @@ # Nym Documentation @version: 1.20.4 -@generated: 2026-04-17 +@generated: 2026-03-26 @pages: 150 @source: https://github.com/nymtech/nym/tree/develop/documentation/docs @@ -13,33 +13,51 @@ url: https://nym.com/docs/network # 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 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. -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. +## 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: ### 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, 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. +- **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. Both modes use the same underlying infrastructure. ### Developer SDKs -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. +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. ## 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 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. -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. +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**. -## Further reading +## Documentation structure -- **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) +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). --- title: Nym Network Architecture: How the Mixnet Works @@ -49,33 +67,51 @@ url: https://nym.com/docs/network # 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 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. -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. +## 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: ### 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, 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. +- **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. Both modes use the same underlying infrastructure. ### Developer SDKs -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. +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. ## 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 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. -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. +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**. -## Further reading +## Documentation structure -- **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) +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). --- title: Nym Network Overview @@ -85,17 +121,15 @@ 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 -- [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. +- [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 --- title: The Privacy Problem: Why Metadata Matters @@ -105,45 +139,94 @@ url: https://nym.com/docs/network/overview/privacy-problem # The Privacy Problem -## Metadata +## Metadata is the message -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. +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. -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. +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. -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/) +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/) ## The adversary models -**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. +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. -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. +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. -**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. +**dVPN mode** offers reduced protections against E2E surveillance and timing analysis, but still offers similar protections to Tor whilst offering increased speeds. -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). +## 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). --- 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 traffic analysis. +description: When to use NymVPN's dVPN mode for low-latency browsing versus Mixnet mode for metadata protection against sophisticated adversaries. url: https://nym.com/docs/network/overview/choosing-a-mode --- # Choosing a Mode -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. +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. ## 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 | @@ -151,33 +234,75 @@ Both modes run on the same Nym infrastructure but defend against different threa ## Use dVPN mode when -- 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 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 ## Use Mixnet mode when -- 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 +- 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 ## For developers -The [Nym SDKs](/developers) only expose **Mixnet mode**. dVPN mode is specific to the NymVPN application. +Developers using the [Nym SDKs](/developers) have access to **Mixnet mode only**—dVPN mode is specific to the NymVPN application. -There are two integration models: +There are two integration models available via the SDKs: -**Proxy** (traffic exits to the internet, analogous to Tor's exit relay model): +**As a proxy** (traffic exits to the internet): ``` Your App --> Entry --> Mix Nodes --> Exit --> Internet ``` -**End-to-end** (Sphinx-encrypted the entire way, traffic stays within the Mixnet): +**End-to-end** (traffic stays within the Mixnet): ``` Your App --> Entry --> Mix Nodes --> Exit --> Nym Client ``` -See the [integration overview](/developers/integrations) for guidance on choosing between them. +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 --- title: Nym vs VPNs, Tor, I2P, and E2EE @@ -191,31 +316,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 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. +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. 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 details. +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 vs Tor -[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. +[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. 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 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. ## 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 message content and the communication metadata around 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 what you're saying and the fact that you're saying it. For a practical breakdown of when to use dVPN vs Mixnet mode, see [Choosing a Mode](/network/overview/choosing-a-mode). @@ -235,93 +360,95 @@ 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 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. +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. ## Privacy guarantees -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). +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). ## Performance -Added latency is comparable to traditional VPNs, and WireGuard keeps cryptographic overhead low, so browsing, streaming, and downloads are not noticeably affected. +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). ## 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: nested WireGuard tunnels, split-knowledge architecture, and packet format tradeoffs. +description: Technical details of Nym dVPN mode's protocol layers, including WireGuard tunnels, AES-GCM-SIV-256 layer encryption, 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 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. +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. ``` +-----------------------------------------+ | Application Data | +-----------------------------------------+ -| Inner WireGuard tunnel (Client → Exit) | +| Layer Encryption (Entry -> Exit) | +-----------------------------------------+ -| Outer WireGuard tunnel (Client → Entry)| +| WireGuard (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 -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. +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. ## 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. 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. ## 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 is 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's re-randomized for each connection and cannot be linked to previous usage. -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. +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. ## Security properties -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 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 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. +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. 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](/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. +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. This shared infrastructure means improvements to Gateways and credentials benefit both modes. @@ -339,23 +466,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 @@ -363,7 +490,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 @@ -382,35 +509,41 @@ 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. Each mixing layer adds random delays, reorders packets, and injects cover traffic. 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 — 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). ## 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 ``` -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. +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. ## Privacy properties -- **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). +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. ## Performance -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. +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. -Updated latency measurements will be published after the Lewes Protocol release. +For help deciding between dVPN and Mixnet mode, see [Choosing a Mode](/network/overview/choosing-a-mode). ## Further reading The following pages cover mixnet internals in detail: -- [Loopix Design](/network/mixnet-mode/loopix) explains the academic foundation of Nym's Mixnet design +- [Loopix Design](/network/mixnet-mode/loopix) explains the academic foundation - [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 @@ -424,11 +557,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) design, with modifications for decentralized operation and economic incentives. +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 insight -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. +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. 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. @@ -440,7 +573,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, where each message is delayed independently according to an exponential distribution and then forwarded as soon as its delay expires. +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. 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. @@ -448,19 +581,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, 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). +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. ## 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. 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. ## 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, 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. +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. 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). @@ -477,13 +610,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 -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. +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. -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. +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. ```mermaid sequenceDiagram @@ -603,11 +736,11 @@ url: https://nym.com/docs/network/mixnet-mode/cover-traffic # Cover Traffic -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. +Cover traffic is dummy packets that hide when real communication is occurring. It's a fundamental mechanism for achieving **unobservability**. ## 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, and 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. Consistent traffic volumes to certain destinations reveal ongoing relationships. ## The solution @@ -626,17 +759,17 @@ Time --------------------------------------> Constant rate (activity hidden) ``` -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. +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. ## Loop traffic -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. +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. 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 @@ -646,7 +779,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), 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), 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. --- title: Packet Mixing and Random Delays @@ -660,11 +793,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. 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. +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. ## 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 @@ -680,27 +813,25 @@ 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 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. +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. -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. +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. ## 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. -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. +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. ## Combined with cover traffic -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. +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. --- title: Anonymous Replies with SURBs @@ -714,13 +845,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, 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. +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. ## 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 @@ -755,15 +886,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), 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), 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. ## 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 @@ -775,37 +906,125 @@ 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 +# Sphinx Packets 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, which 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. This 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. @@ -825,9 +1044,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? @@ -852,7 +1071,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. Most networked services do not need to know _who_ is making a request, only whether the requester has the _right to use_ the system. +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. 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. @@ -878,7 +1097,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, the closest analogy in conventional systems is a decentralized, privacy-preserving [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, you might think of it as a sort of supercharged decentralized privacy-friendly [JWT](https://jwt.io/). --- title: Generating and using zk-nym anonymous credentials @@ -887,7 +1106,14 @@ url: https://nym.com/docs/network/cryptography/zk-nym/zk-nym-overview # 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. + 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_. 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. @@ -899,10 +1125,10 @@ 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 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. ## 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/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. - 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. @@ -940,12 +1166,14 @@ 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 `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. + 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. ## 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. -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. +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. Going back to the `nym-cli` tool to illustrate this; we can generate multiple unlinkable tickets from a single ticketbook aggregated from PSCs: @@ -1070,12 +1298,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, where 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. 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 @@ -1091,9 +1319,9 @@ To interact with the chain, see [Interacting with Nyx](/developers/chain). To ru ## Role in the network -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. +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. -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. +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. And it hosts **smart contracts** for mixnet coordination and the zk-nym credential system. @@ -1105,7 +1333,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. @@ -1126,23 +1354,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. 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 (up to 24 hours). 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 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. +**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. ## 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. +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. -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 @@ -1156,85 +1384,6 @@ 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. @@ -1247,9 +1396,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 @@ -1269,7 +1418,7 @@ A Nym address has three parts separated by dots and an @ symbol: .@ ``` -The **identity key** identifies the client for routing purposes and is derived from the client's Ed25519 keypair and base58-encoded for readability. +The **identity key** identifies the client for routing purposes. It's 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. @@ -1299,11 +1448,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, 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. Nodes that successfully forward 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. @@ -1313,7 +1462,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 @@ -1337,7 +1486,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 @@ -1345,9 +1494,7 @@ 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. - -**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. +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. --- title: Licensing @@ -1401,16 +1548,15 @@ Build applications that protect user metadata using the Nym Mixnet. This section ## Where to start -**Choosing an integration approach:** read [Integrations](/developers/integrations) to understand the architectural trade-offs (native SDK vs proxy vs mixFetch), then pick your path: +**Choosing an integration approach** — read [Integrations](./integrations) to understand the architectural trade-offs (native SDK vs proxy vs mixFetch), then pick your path: -- **[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. +- **[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. ## 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](/developers/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](./chain). ## API reference @@ -1428,16 +1574,15 @@ Build applications that protect user metadata using the Nym Mixnet. This section ## Where to start -**Choosing an integration approach:** read [Integrations](/developers/integrations) to understand the architectural trade-offs (native SDK vs proxy vs mixFetch), then pick your path: +**Choosing an integration approach** — read [Integrations](./integrations) to understand the architectural trade-offs (native SDK vs proxy vs mixFetch), then pick your path: -- **[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. +- **[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. ## 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](/developers/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](./chain). ## API reference @@ -1445,32 +1590,19 @@ 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). -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**. +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. -## Environment + 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. -Different runtimes have different transport constraints: a browser cannot open raw sockets or access the filesystem, while a desktop app can. +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). -- **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). - -## 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`](./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](./native) and [Browser](./browsers) pages for the specific modules available in each environment. + This is because of the different security considerations each option offers. These are detailed in the following pages. --- title: Native and Desktop App Integration @@ -1480,36 +1612,52 @@ url: https://nym.com/docs/developers/native # Native / Desktop Apps -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. +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. -## 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. +## 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. ![](/images/developers/nym-arch-client-to-client.png) -| SDK Module | What it does | Status | Links | -|---|---|---|---| -| **Stream** | `AsyncRead + AsyncWrite` byte streams multiplexed over the mixnet — the closest analogue to TCP sockets | Recommended | [docs](./rust/stream) · [tutorial](./rust/stream/tutorial) | -| **Mixnet** | Raw message API and `MixnetClient` — full control over the communication model | Stable | [docs](./rust/mixnet) · [tutorial](./rust/mixnet/tutorial) | -| **Client Pool** | Pre-connected client pool for bursty workloads | Stable | [docs](./rust/client-pool) | -| **TcpProxy** | Localhost TCP sockets that proxy traffic through the mixnet | Unmaintained | [docs](./rust/tcpproxy) | +### 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. -**TcpProxy is unmaintained.** Use the [Stream module](./rust/stream) for new projects. +- [docs](./rust/stream) +- [tutorial](./rust/stream/tutorial) -## 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. +### 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. ![](/images/developers/nym-arch-ip-routing.png) -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. +### Security Considerations -| Standalone Crate | What it does | Links | -|---|---|---| -| **`smolmix`** | Userspace IP tunnel — `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](./smolmix) | +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**. -| 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](./rust/mixnet) | +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. --- title: Browser-Based App Integration @@ -1518,21 +1666,43 @@ 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. -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. +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. ![](/images/developers/nym-browser-arch.png) -| Module | What it does | Links | -|---|---|---| -| **mixFetch** | Drop-in `fetch` replacement — HTTP(S) requests via Exit Gateways with an embedded CA store for browser-to-destination TLS over the Mixnet | [docs](./typescript#mixfetch) · [example](./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](./typescript#mixnet-client) · [example](./typescript/playground/traffic) | +Both `mixFetch` and the WASM client are delivered to the client bundled into a web application. -`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. +## mixFetch +Drop-in replacement for browser's `fetch` API that makes HTTP(S) requests via Exit Gateways using the SOCKS Network Requester. -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. +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) --- title: Nym Client Message Queue and Cover Traffic @@ -1542,7 +1712,7 @@ url: https://nym.com/docs/developers/concepts/message-queue # 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). + 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). ## 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. @@ -1617,1121 +1787,6 @@ Accidentally dropping a client before your message has been sent is something th - 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 tokio-rustls, hyper, tungstenite, and the async 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 real `TcpStream` and `UdpSocket` types that work transparently with the async Rust ecosystem — `tokio-rustls`, `hyper`, `tokio-tungstenite`, and anything else built on `AsyncRead + AsyncWrite`. - -It also serves as the core for upcoming companion crates that plug into specific frameworks (e.g. `hyper` connectors, DNS resolvers). - -## Why IP, not messages - -The Nym SDK works at the **message layer**: you send and receive `Vec` payloads through the mixnet. Every protocol must be hand-adapted — you need custom framing, ordering, connection state, and flow control. - -`smolmix` operates at the **IP layer**. A userspace `smoltcp` stack manages real TCP state machines (retransmits, windowing, port allocation) and UDP datagram delivery, and the mixnet becomes a transparent transport underneath. Any protocol that works over TCP or UDP works over `smolmix` with zero adaptation. - -## Architecture - -```text -┌──────────────────────────────────────────────────────────────────┐ -│ Application protocols that "just work" over smolmix │ -│ │ -│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ ┌────────────────┐ │ -│ │ TLS │ │ HTTP/1.1 │ │ WebSocket │ │ libp2p │ │ -│ │ (rustls) │ │ (hyper) │ │ (tungstenite)│ │ (noise+yamux) │ │ -│ └────┬─────┘ └────┬─────┘ └──────┬───────┘ └───────┬────────┘ │ -│ │ │ │ │ │ -│ └─────────────┴──────────────┴─────────────────┘ │ -│ │ │ -│ tokio_smoltcp::TcpStream │ -│ (AsyncRead + AsyncWrite, Send, Unpin) │ -├──────────────────────────────────────────────────────────────────┤ -│ smolmix Tunnel │ -│ (smoltcp → mixnet → IPR) │ -└──────────────────────────────────────────────────────────────────┘ -``` - -Internally, the `Tunnel` wires together four components: - -```text -┌─────────────────────────────────────────────────────────────────┐ -│ User code │ -│ tunnel.tcp_connect() → TcpStream (AsyncRead + AsyncWrite) │ -│ tunnel.udp_socket() → UdpSocket (send_to / recv_from) │ -├─────────────────────────────────────────────────────────────────┤ -│ tokio-smoltcp::Net │ -│ Owns the smoltcp Interface + SocketSet + async poll loop. │ -│ Manages TCP state machines, retransmits, port allocation. │ -├─────────────────────────────────────────────────────────────────┤ -│ NymAsyncDevice (device adapter) │ -│ Stream + Sink adapter for raw IP packets over mpsc channels. │ -├─────────────────────────────────────────────────────────────────┤ -│ NymIprBridge (background task) │ -│ Shuttles packets between channels and the mixnet. │ -│ Bundles outgoing packets with MultiIpPacketCodec (IPR protocol)│ -├─────────────────────────────────────────────────────────────────┤ -│ IpMixStream → MixnetClient → Nym mixnet → IPR exit node │ -└─────────────────────────────────────────────────────────────────┘ -``` - -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. - -Traffic is Sphinx-encrypted until the Exit Gateway, where it's unwrapped into raw IP. The last hop to the remote host **travels as normal internet traffic**. Use TLS or another encryption layer to protect the final hop. - -## Quick start - -```rust -use smolmix::Tunnel; -use tokio::io::{AsyncReadExt, AsyncWriteExt}; - -#[tokio::main] -async fn main() -> Result<(), Box> { - let tunnel = Tunnel::new().await?; - - // TCP — works with any protocol - let mut tcp = tunnel.tcp_connect("1.1.1.1:80".parse()?).await?; - tcp.write_all(b"GET / HTTP/1.1\r\nHost: 1.1.1.1\r\nConnection: close\r\n\r\n").await?; - let mut buf = Vec::new(); - tcp.read_to_end(&mut buf).await?; - - // UDP — datagrams over the mixnet - let udp = tunnel.udp_socket().await?; - udp.send_to(b"\x12\x34\x01\x00...", "1.1.1.1:53".parse()?).await?; - - tunnel.shutdown().await; - Ok(()) -} -``` - -## Public API - -| Type | Description | -|---|---| -| **`Tunnel`** | Entry point. Connects to the mixnet, spawns the bridge and smoltcp reactor. Cheaply cloneable (`Arc`-based) for sharing across tasks. | -| **`TunnelBuilder`** | Builder for configuring the tunnel before connecting — e.g. targeting a specific IPR exit node. | -| **`TcpStream`** | TCP stream routed through the mixnet. Implements `AsyncRead + AsyncWrite` — drop-in replacement for `tokio::net::TcpStream`. | -| **`UdpSocket`** | UDP socket routed through the mixnet. Supports `send_to` / `recv_from`. | -| **`IpPair`** | The IPv4/IPv6 address pair allocated to this tunnel by the IPR. | -| **`Recipient`** | A Nym mixnet address, used to target a specific IPR exit node. | - -### Tunnel construction - -```rust -// Auto-discover the best IPR exit node: -let tunnel = Tunnel::new().await?; - -// Or configure via builder: -use smolmix::Recipient; -let ipr: Recipient = "gateway-address...".parse()?; -let tunnel = Tunnel::builder() - .ipr_address(ipr) - .build() - .await?; - -// Or bring your own pre-configured IpMixStream: -let tunnel = Tunnel::from_stream(my_stream).await?; -``` - -### Opening sockets - -```rust -// TCP connection to a remote host -let tcp = tunnel.tcp_connect("1.1.1.1:443".parse()?).await?; - -// UDP socket on an ephemeral port -let udp = tunnel.udp_socket().await?; - -// UDP socket on a specific port -let udp = tunnel.udp_socket_on(5353).await?; - -// Check allocated IPs -let ips = tunnel.allocated_ips(); -println!("IPv4: {}, IPv6: {}", ips.ipv4, ips.ipv6); -``` - -### Shutdown - -```rust -// Graceful — waits for the bridge to disconnect from the mixnet -tunnel.shutdown().await; - -// Or just drop — fire-and-forget cleanup via oneshot channel -// (bridge still shuts down, but you can't await it) -drop(tunnel); -``` - -## When to use smolmix - -| | smolmix | Stream module | mixFetch | SOCKS client | -|---|---|---|---|---| -| **Layer** | IP (TCP/UDP) | Message (multiplexed streams) | HTTP | TCP (SOCKS proxy) | -| **Controls both sides?** | No — proxy mode | Yes — E2E | No — proxy mode | No — proxy mode | -| **Runtime** | Rust (tokio) | Rust (tokio) | Browser (WASM) | Any (SOCKS-capable) | -| **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 | - -## Examples - -Runnable examples in [`smolmix/core/examples/`](https://github.com/nymtech/nym/tree/max/mixtcp/smolmix/core/examples). Each is self-contained and compares clearnet vs. mixnet performance. - -```sh -cargo run -p smolmix --example -``` - -All examples accept `--ipr
` to target a specific exit node. - -| Example | Source | What it demonstrates | -|---|---|---| -| TCP | [`tcp.rs`](https://github.com/nymtech/nym/blob/max/mixtcp/smolmix/core/examples/tcp.rs) | HTTPS request via hyper + tokio-rustls — fetches Cloudflare's `/cdn-cgi/trace` to show that the exit IP differs from clearnet | -| UDP | [`udp.rs`](https://github.com/nymtech/nym/blob/max/mixtcp/smolmix/core/examples/udp.rs) | DNS lookup via hickory-proto — sends a raw UDP query to `1.1.1.1:53` through the mixnet | -| WebSocket | [`websocket.rs`](https://github.com/nymtech/nym/blob/max/mixtcp/smolmix/core/examples/websocket.rs) | WebSocket echo via tokio-tungstenite + tokio-rustls — shows the full TCP → TLS → WebSocket stack composing over smolmix | - -## Next steps - -- [Tutorial 1: Private File Downloads](./smolmix/tutorial) — resolve DNS, connect TCP+TLS, and download files over a single HTTP connection through the mixnet -- [Tutorial 2: Private DNS & NTP via UDP](./smolmix/tutorial-udp) — resolve multiple hostnames and sync time via NTP, all over the mixnet's UDP transport - ---- -title: smolmix Tutorial 1: Private File Downloads via DNS + TLS -description: Step-by-step Rust tutorial that resolves a hostname via mixnet DNS, connects over mixnet TCP, negotiates TLS, and downloads files over a single HTTP connection — all through the Nym mixnet. -url: https://nym.com/docs/developers/smolmix/tutorial ---- - -# Tutorial 1: Private File Downloads - -In this tutorial you'll build a single program that sends HTTP requests to a public server — with every byte routed through the Nym mixnet: - -1. **DNS** — resolve the hostname via a UDP query to Cloudflare's `1.1.1.1` -2. **TCP** — connect to the resolved IP -3. **TLS** — negotiate a secure channel with tokio-rustls -4. **HTTP** — use hyper to send multiple requests over a single keep-alive connection - -Each layer stacks on the one below it using standard Rust traits. None of them know they're running over the mixnet — that's the point. - -## What you'll learn - -- Creating a `Tunnel` and using its `TcpStream` and `UdpSocket` -- Constructing a raw DNS query with `hickory-proto` and sending it via mixnet UDP -- Stacking `tokio-rustls` TLS on a `smolmix` `TcpStream` -- Using `hyper`'s low-level HTTP/1.1 client over a custom transport -- Reusing a single connection for multiple requests (HTTP keep-alive) -- Graceful tunnel shutdown - -## Prerequisites - -- Rust toolchain (1.70+) -- A working internet connection (the tunnel connects to the live Nym mixnet) - -## Step 1: Set up the project - -```sh -cargo init smolmix-echo -cd smolmix-echo -``` - -Add dependencies to `Cargo.toml`: - -```toml -[dependencies] -smolmix = "X.Y.Z" -nym-bin-common = { version = "X.Y.Z", features = ["basic_tracing"] } -tokio = { version = "1", features = ["rt-multi-thread", "macros", "time"] } -tokio-rustls = "0.26" -rustls = { version = "0.23", features = ["std", "ring"] } -webpki-roots = "0.26" -hickory-proto = "0.25" -hyper = { version = "1", features = ["client", "http1"] } -hyper-util = { version = "0.1", features = ["tokio"] } -http-body-util = "0.1" -blake3 = "=1.7.0" # required pin — see https://nymtech.net/docs/developers/rust/importing -``` - -We use `hyper` for HTTP/1.1 client requests, `hyper-util` for the `TokioIo` adapter, and `http-body-util` to collect response bodies. - -## Step 2: Scaffold `main()` - -Start with the boilerplate: a tokio runtime, tracing for logs, the rustls crypto provider, and `--ipr` arg parsing. - -Replace `src/main.rs` with: - -```rust -use std::net::Ipv4Addr; -use std::sync::Arc; - -use hickory_proto::op::{Message, Query}; -use hickory_proto::rr::{Name, RData, RecordType}; -use http_body_util::{BodyExt, Empty}; -use hyper::body::Bytes; -use hyper::client::conn::http1; -use hyper_util::rt::TokioIo; -use rustls::pki_types::ServerName; -use smolmix::Tunnel; - -type BoxError = Box; - -const HOST: &str = "httpbin.org"; - -/// Sizes (in bytes) to download sequentially over one connection. -const SIZES: &[usize] = &[100, 1_000, 10_000]; - -#[tokio::main] -async fn main() -> Result<(), BoxError> { - nym_bin_common::logging::setup_tracing_logger(); - rustls::crypto::ring::default_provider() - .install_default() - .expect("Failed to install rustls crypto provider"); - - // Usage: cargo run [-- --ipr
] - - Ok(()) -} -``` - -## Step 3: Create the tunnel - -The `Tunnel` connects to the mixnet, registers with an IPR (Internet Packet Router) exit gateway, and spawns the internal bridge and smoltcp reactor. From this point on, `tcp_connect()` and `udp_socket()` route through the mixnet. - -```rust - let args: Vec = std::env::args().collect(); - let ipr_addr = args - .iter() - .position(|a| a == "--ipr") - .and_then(|i| args.get(i + 1)); - - let mut builder = Tunnel::builder(); - if let Some(addr) = ipr_addr { - builder = builder.ipr_address(addr.parse().expect("invalid IPR address")); - } - let tunnel = builder.build().await?; - println!("Tunnel ready — allocated IP: {}", tunnel.allocated_ips().ipv4); -``` - -## Step 4: Resolve DNS through the mixnet - -Instead of using the system resolver (which would go over clearnet), we send a raw DNS query through the tunnel's UDP socket to Cloudflare's `1.1.1.1:53`: - -```rust -async fn resolve_dns(tunnel: &Tunnel, host: &str) -> Result { - let mut query = Message::new(); - query.set_recursion_desired(true); - query.add_query(Query::query(Name::from_ascii(host)?, RecordType::A)); - let query_bytes = query.to_vec()?; - - let udp = tunnel.udp_socket().await?; - udp.send_to(&query_bytes, "1.1.1.1:53".parse()?).await?; - - let mut buf = vec![0u8; 1500]; - let (n, _) = udp.recv_from(&mut buf).await?; - - let response = Message::from_vec(&buf[..n])?; - let ip = response - .answers() - .iter() - .find_map(|r| match r.data() { - RData::A(a) => Some(a.0), - _ => None, - }) - .ok_or("no A record in DNS response")?; - Ok(ip) -} -``` - -Call it from `main()`: - -```rust - let ip = resolve_dns(&tunnel, HOST).await?; - println!("Resolved {HOST} → {ip} (via mixnet DNS)"); -``` - -The DNS query and response both travel through the mixnet as UDP datagrams. The IPR exit gateway sends the query to `1.1.1.1` on your behalf — the DNS server sees the gateway's IP, not yours. - -## Step 5: TCP + TLS through the mixnet - -Now connect to the resolved IP over TCP, then layer TLS on top. The smolmix `TcpStream` implements `AsyncRead + AsyncWrite`, so tokio-rustls accepts it directly — no adapters needed. - -```rust - println!("Connecting to {HOST}:443..."); - let tcp = tunnel.tcp_connect((ip, 443).into()).await?; - println!("TCP connected to {ip}:443 via mixnet"); - - let mut root_store = rustls::RootCertStore::empty(); - root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); - let tls_config = rustls::ClientConfig::builder() - .with_root_certificates(root_store) - .with_no_client_auth(); - let connector = tokio_rustls::TlsConnector::from(Arc::new(tls_config)); - - let domain = ServerName::try_from(HOST)?.to_owned(); - let tls = connector.connect(domain, tcp).await?; - println!("TLS established with {HOST}"); -``` - -TLS is essential here. Traffic is Sphinx-encrypted inside the mixnet, but between the exit gateway and the remote host it travels as normal internet traffic. TLS protects the final hop. - -## Step 6: HTTP/1.1 connection with hyper - -Use hyper's low-level `http1::handshake()` API to establish an HTTP connection over our TLS stream. The `TokioIo` wrapper bridges hyper's I/O traits with tokio's. - -```rust - let io = TokioIo::new(tls); - let (mut sender, conn) = http1::handshake(io).await?; - tokio::spawn(conn); -``` - -`handshake()` returns a `SendRequest` handle for sending requests and a connection future that drives the HTTP state machine. We spawn the connection so it runs in the background. - -## Step 7: Send multiple requests - -Now send several GET requests to `httpbin.org/bytes/{n}` over the same connection. Each request downloads a different amount of random bytes, showing HTTP keep-alive working transparently over the mixnet: - -```rust - let total = SIZES.len(); - println!("\nSending {total} requests over one connection...\n"); - let overall = std::time::Instant::now(); - let mut total_bytes = 0usize; - - for (i, &size) in SIZES.iter().enumerate() { - let seq = i + 1; - let start = std::time::Instant::now(); - - let req = hyper::Request::get(format!("/bytes/{size}")) - .header("Host", HOST) - .body(Empty::::new())?; - - // Spinner while waiting for response - let spinner = tokio::spawn(async move { - let frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; - let mut i = 0; - loop { - eprint!("\r [{seq}/{total}] GET /bytes/{size:<5} {}", frames[i % frames.len()]); - i += 1; - tokio::time::sleep(std::time::Duration::from_millis(80)).await; - } - }); - - let resp = sender.send_request(req).await?; - let status = resp.status(); - let body = resp.into_body().collect().await?.to_bytes(); - let elapsed = start.elapsed(); - spinner.abort(); - - let speed = body.len() as f64 / elapsed.as_secs_f64(); - eprintln!( - "\r [{seq}/{total}] GET /bytes/{size:<5} → {status} {} in {elapsed:.1?} ({}/s) ", - format_bytes(body.len() as u64), - format_bytes(speed as u64), - ); - total_bytes += body.len(); - } - - let elapsed = overall.elapsed(); - println!( - "\nDone! {} in {total} requests over {elapsed:.1?}", - format_bytes(total_bytes as u64), - ); - - tunnel.shutdown().await; -``` - -Key points: -- `Empty::::new()` is an empty body for our GET requests -- `collect().await?.to_bytes()` gathers each response body into memory -- A spinner runs while each request is in-flight, then gets replaced with the result -- All three requests reuse the same TCP+TLS connection — no reconnection overhead - -## Step 8: Run it - -```sh -RUST_LOG=info cargo run -``` - -Or target a specific IPR exit node: - -```sh -RUST_LOG=info cargo run -- --ipr -``` - -You'll see the full flow logged: - -``` -Tunnel ready — allocated IP: 10.0.232.7 -Resolved httpbin.org → 18.214.245.199 (via mixnet DNS) -Connecting to httpbin.org:443... -TCP connected to 18.214.245.199:443 via mixnet -TLS established with httpbin.org - -Sending 3 requests over one connection... - - [1/3] GET /bytes/100 → 200 OK 100 B in 2.0s (51 B/s) - [2/3] GET /bytes/1000 → 200 OK 1.0 KB in 1.8s (554 B/s) - [3/3] GET /bytes/10000 → 200 OK 10.0 KB in 11.5s (868 B/s) - -Done! 11.1 KB in 3 requests over 15.3s -``` - -The remote server sees traffic from the IPR exit gateway's IP — not yours. All three requests travel through the mixnet over a single TCP connection. - -## How it works - -Here's the full protocol stack for this tutorial: - -```text -┌──────────────────────────────────────────────────────┐ -│ hyper (HTTP/1.1 client, keep-alive) │ -│ └─ tokio-rustls (TLS encryption) │ -│ └─ smolmix::TcpStream (TCP over mixnet) │ -│ └─ smoltcp (userspace TCP state machine) │ -│ └─ NymIprBridge → Nym mixnet → IPR │ -└──────────────────────────────────────────────────────┘ -``` - -Each layer only knows about the one directly below it: -- **hyper** thinks it's talking to a normal TLS stream (via `TokioIo`) -- **rustls** thinks it's talking to a normal TCP stream -- **smolmix** handles the mixnet routing transparently - -This is the same composability model as regular networking — swap `tokio::net::TcpStream` for `smolmix::TcpStream` and the rest of your stack works unchanged. - -## What you've learned - -- `Tunnel::new()` connects to the mixnet and gives you TCP/UDP socket access -- `tunnel.udp_socket()` creates a UDP socket for sending datagrams (like DNS queries) through the mixnet -- `tunnel.tcp_connect()` returns a `TcpStream` that implements `AsyncRead + AsyncWrite` -- `hyper`'s low-level `http1::handshake()` works over any `AsyncRead + AsyncWrite` transport via `TokioIo` -- HTTP keep-alive works transparently — multiple requests over a single mixnet connection -- Standard crates (hyper, tokio-rustls, hickory-proto) work unmodified on top of smolmix -- `tunnel.shutdown()` disconnects gracefully; dropping the tunnel triggers fire-and-forget cleanup - -## Complete code - -```rust -use std::net::Ipv4Addr; -use std::sync::Arc; - -use hickory_proto::op::{Message, Query}; -use hickory_proto::rr::{Name, RData, RecordType}; -use http_body_util::{BodyExt, Empty}; -use hyper::body::Bytes; -use hyper::client::conn::http1; -use hyper_util::rt::TokioIo; -use rustls::pki_types::ServerName; -use smolmix::Tunnel; - -type BoxError = Box; - -const HOST: &str = "httpbin.org"; - -/// Sizes (in bytes) to download sequentially over one connection. -const SIZES: &[usize] = &[100, 1_000, 10_000]; - -#[tokio::main] -async fn main() -> Result<(), BoxError> { - nym_bin_common::logging::setup_tracing_logger(); - rustls::crypto::ring::default_provider() - .install_default() - .expect("Failed to install rustls crypto provider"); - - // Parse --ipr flag - let args: Vec = std::env::args().collect(); - let ipr_addr = args - .iter() - .position(|a| a == "--ipr") - .and_then(|i| args.get(i + 1)); - - // Step 1: Create the tunnel - let mut builder = Tunnel::builder(); - if let Some(addr) = ipr_addr { - builder = builder.ipr_address(addr.parse().expect("invalid IPR address")); - } - let tunnel = builder.build().await?; - println!("Tunnel ready — allocated IP: {}", tunnel.allocated_ips().ipv4); - - // Step 2: DNS resolution via mixnet UDP - let ip = resolve_dns(&tunnel, HOST).await?; - println!("Resolved {HOST} → {ip} (via mixnet DNS)"); - - // Step 3: TCP + TLS through the mixnet (single connection) - println!("Connecting to {HOST}:443..."); - let tcp = tunnel.tcp_connect((ip, 443).into()).await?; - println!("TCP connected to {ip}:443 via mixnet"); - - let mut root_store = rustls::RootCertStore::empty(); - root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); - let tls_config = rustls::ClientConfig::builder() - .with_root_certificates(root_store) - .with_no_client_auth(); - let connector = tokio_rustls::TlsConnector::from(Arc::new(tls_config)); - let domain = ServerName::try_from(HOST)?.to_owned(); - let tls = connector.connect(domain, tcp).await?; - println!("TLS established with {HOST}"); - - // Step 4: HTTP/1.1 connection (reused for all requests) - let io = TokioIo::new(tls); - let (mut sender, conn) = http1::handshake(io).await?; - tokio::spawn(conn); - - // Step 5: Send multiple requests over the same connection - let total = SIZES.len(); - println!("\nSending {total} requests over one connection...\n"); - let overall = std::time::Instant::now(); - let mut total_bytes = 0usize; - - for (i, &size) in SIZES.iter().enumerate() { - let seq = i + 1; - let start = std::time::Instant::now(); - - let req = hyper::Request::get(format!("/bytes/{size}")) - .header("Host", HOST) - .body(Empty::::new())?; - - // Spinner while waiting for response - let spinner = tokio::spawn(async move { - let frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; - let mut i = 0; - loop { - eprint!("\r [{seq}/{total}] GET /bytes/{size:<5} {}", frames[i % frames.len()]); - i += 1; - tokio::time::sleep(std::time::Duration::from_millis(80)).await; - } - }); - - let resp = sender.send_request(req).await?; - let status = resp.status(); - let body = resp.into_body().collect().await?.to_bytes(); - let elapsed = start.elapsed(); - spinner.abort(); - - let speed = body.len() as f64 / elapsed.as_secs_f64(); - eprintln!( - "\r [{seq}/{total}] GET /bytes/{size:<5} → {status} {} in {elapsed:.1?} ({}/s) ", - format_bytes(body.len() as u64), - format_bytes(speed as u64), - ); - total_bytes += body.len(); - } - - let elapsed = overall.elapsed(); - println!( - "\nDone! {} in {total} requests over {elapsed:.1?}", - format_bytes(total_bytes as u64), - ); - - tunnel.shutdown().await; - Ok(()) -} - -async fn resolve_dns(tunnel: &Tunnel, host: &str) -> Result { - let mut query = Message::new(); - query.set_recursion_desired(true); - query.add_query(Query::query(Name::from_ascii(host)?, RecordType::A)); - let query_bytes = query.to_vec()?; - - let udp = tunnel.udp_socket().await?; - udp.send_to(&query_bytes, "1.1.1.1:53".parse()?).await?; - - let mut buf = vec![0u8; 1500]; - let (n, _) = udp.recv_from(&mut buf).await?; - - let response = Message::from_vec(&buf[..n])?; - let ip = response - .answers() - .iter() - .find_map(|r| match r.data() { - RData::A(a) => Some(a.0), - _ => None, - }) - .ok_or("no A record in DNS response")?; - Ok(ip) -} - -fn format_bytes(n: u64) -> String { - if n >= 1_000_000 { - format!("{:.1} MB", n as f64 / 1_000_000.0) - } else if n >= 1_000 { - format!("{:.1} KB", n as f64 / 1_000.0) - } else { - format!("{n} B") - } -} -``` - ---- -title: smolmix Tutorial 2: Private DNS & NTP via UDP -description: Step-by-step Rust tutorial that sends private DNS lookups and an NTP time query through the Nym mixnet using smolmix's UDP socket. -url: https://nym.com/docs/developers/smolmix/tutorial-udp ---- - -# Tutorial 2: Private DNS & NTP via UDP - -In this tutorial you'll build a program that uses the mixnet's UDP socket for two real-world tasks: - -1. **Private DNS lookups** — resolve multiple hostnames through Cloudflare's `1.1.1.1` without revealing your IP -2. **NTP time sync** — query a public NTP server to get the current time, privately - -Both are simple request/response protocols over UDP — perfect for the mixnet's datagram transport. - -## What you'll learn - -- Creating a `Tunnel` and using its `UdpSocket` -- Constructing raw DNS queries with `hickory-proto` -- Parsing DNS responses to extract A records -- Building an NTP request packet from scratch (48 bytes) -- Parsing NTP timestamps and converting to UTC -- Using timeouts for UDP operations - -## Prerequisites - -- Rust toolchain (1.70+) -- A working internet connection (the tunnel connects to the live Nym mixnet) - -## Step 1: Set up the project - -```sh -cargo init smolmix-udp -cd smolmix-udp -``` - -Add dependencies to `Cargo.toml`: - -```toml -[dependencies] -smolmix = "X.Y.Z" -nym-bin-common = { version = "X.Y.Z", features = ["basic_tracing"] } -tokio = { version = "1", features = ["rt-multi-thread", "macros", "time"] } -rustls = { version = "0.23", features = ["std", "ring"] } -hickory-proto = "0.25" -chrono = "0.4" -blake3 = "=1.7.0" # required pin — see https://nymtech.net/docs/developers/rust/importing -``` - -No TLS or HTTP crates needed — this tutorial is pure UDP. - -## Step 2: Scaffold `main()` - -Replace `src/main.rs` with: - -```rust -use std::net::Ipv4Addr; - -use hickory_proto::op::{Message, Query}; -use hickory_proto::rr::{Name, RData, RecordType}; -use smolmix::Tunnel; - -type BoxError = Box; - -/// Hostnames to resolve via mixnet DNS. -const DNS_TARGETS: &[&str] = &["example.com", "cloudflare.com", "nymtech.net"]; - -#[tokio::main] -async fn main() -> Result<(), BoxError> { - nym_bin_common::logging::setup_tracing_logger(); - rustls::crypto::ring::default_provider() - .install_default() - .expect("Failed to install rustls crypto provider"); - - // Usage: cargo run [-- --ipr
] - - Ok(()) -} -``` - -## Step 3: Create the tunnel - -Same as the TCP tutorial — connect to the mixnet and get a tunnel with UDP/TCP socket access: - -```rust - let args: Vec = std::env::args().collect(); - let ipr_addr = args - .iter() - .position(|a| a == "--ipr") - .and_then(|i| args.get(i + 1)); - - let mut builder = Tunnel::builder(); - if let Some(addr) = ipr_addr { - builder = builder.ipr_address(addr.parse().expect("invalid IPR address")); - } - let tunnel = builder.build().await?; - println!("Tunnel ready — allocated IP: {}", tunnel.allocated_ips().ipv4); -``` - -## Step 4: Private DNS lookups - -Create a UDP socket and resolve several hostnames. Each query goes through the mixnet to Cloudflare's `1.1.1.1:53` — the DNS server sees the exit gateway's IP, not yours. - -```rust - println!("\nPrivate DNS Lookups (via mixnet UDP)\n"); - - let udp = tunnel.udp_socket().await?; - - for host in DNS_TARGETS { - let start = std::time::Instant::now(); - - // Build DNS query - let mut query = Message::new(); - query.set_recursion_desired(true); - query.add_query(Query::query(Name::from_ascii(host)?, RecordType::A)); - let query_bytes = query.to_vec()?; - - // Send to Cloudflare DNS via mixnet - udp.send_to(&query_bytes, "1.1.1.1:53".parse()?).await?; - - // Read response with timeout - let mut buf = vec![0u8; 1500]; - let result = tokio::time::timeout( - std::time::Duration::from_secs(15), - udp.recv_from(&mut buf), - ) - .await; - - match result { - Ok(Ok((n, _))) => { - let rtt = start.elapsed(); - let response = Message::from_vec(&buf[..n])?; - let ips: Vec<_> = response - .answers() - .iter() - .filter_map(|r| match r.data() { - RData::A(a) => Some(a.0.to_string()), - _ => None, - }) - .collect(); - println!("{host:<20} → {} (rtt: {rtt:.1?})", ips.join(", ")); - } - Ok(Err(e)) => println!("{host:<20} → ERROR: {e}"), - Err(_) => println!("{host:<20} → TIMEOUT"), - } - } -``` - -Key points: -- `tunnel.udp_socket()` creates a UDP socket routed through the mixnet -- `send_to` / `recv_from` work just like `tokio::net::UdpSocket` -- We wrap the receive in `tokio::time::timeout` since UDP has no built-in delivery guarantee -- The same socket is reused for all three queries - -Using `hickory-proto` gives us proper DNS query construction and response parsing. We could also construct raw DNS packets by hand, but there's no reason to — standard crates work unchanged over smolmix. - -## Step 5: NTP time sync - -NTP is a simple 48-byte request/response protocol over UDP. We'll resolve `pool.ntp.org`, send a client request, and parse the server's timestamp. - -First, resolve the NTP server. We extract the DNS logic into a reusable helper: - -```rust -async fn resolve_dns(tunnel: &Tunnel, host: &str) -> Result { - let mut query = Message::new(); - query.set_recursion_desired(true); - query.add_query(Query::query(Name::from_ascii(host)?, RecordType::A)); - let query_bytes = query.to_vec()?; - - let udp = tunnel.udp_socket().await?; - udp.send_to(&query_bytes, "1.1.1.1:53".parse()?).await?; - - let mut buf = vec![0u8; 1500]; - let (n, _) = udp.recv_from(&mut buf).await?; - - let response = Message::from_vec(&buf[..n])?; - let ip = response - .answers() - .iter() - .find_map(|r| match r.data() { - RData::A(a) => Some(a.0), - _ => None, - }) - .ok_or("no A record in DNS response")?; - Ok(ip) -} -``` - -Then build and send the NTP request: - -```rust - println!("\n── NTP Time Sync (via mixnet UDP) ──\n"); - - let ntp_ip = resolve_dns(&tunnel, "pool.ntp.org").await?; - println!("Resolved pool.ntp.org → {ntp_ip}"); - - // Build NTP request (48 bytes, version 4, client mode) - let mut ntp_req = [0u8; 48]; - ntp_req[0] = 0x23; // LI=0, Version=4, Mode=3 (client) - - let ntp_udp = tunnel.udp_socket().await?; - let start = std::time::Instant::now(); - let ntp_dest: std::net::SocketAddr = (ntp_ip, 123).into(); - ntp_udp.send_to(&ntp_req, ntp_dest).await?; -``` - -The NTP request is minimal — a 48-byte packet with only the first byte set: `0x23` means LI (leap indicator) = 0, version = 4, mode = 3 (client). - -## Step 6: Parse the NTP response - -The server's transmit timestamp lives at bytes 40–47: a 32-bit seconds count since 1900-01-01 followed by a 32-bit fraction. - -```rust - let mut buf = [0u8; 48]; - let result = tokio::time::timeout( - std::time::Duration::from_secs(30), - ntp_udp.recv_from(&mut buf), - ) - .await; - - match result { - Ok(Ok((n, _))) if n >= 48 => { - let rtt = start.elapsed(); - // Transmit timestamp at bytes 40..48 (seconds since 1900-01-01) - let secs = u32::from_be_bytes([buf[40], buf[41], buf[42], buf[43]]); - let frac = u32::from_be_bytes([buf[44], buf[45], buf[46], buf[47]]); - - // Convert NTP epoch (1900) to Unix epoch (1970) - const NTP_TO_UNIX: u64 = 2_208_988_800; - let unix_secs = secs as u64 - NTP_TO_UNIX; - let millis = (frac as u64 * 1000) >> 32; - - let dt = chrono::DateTime::from_timestamp(unix_secs as i64, (millis * 1_000_000) as u32) - .expect("valid timestamp"); - println!("NTP response in {rtt:.1?}"); - println!("Unix timestamp: {unix_secs}.{millis:03}"); - println!("UTC: {}", dt.format("%Y-%m-%d %H:%M:%S%.3f UTC")); - } - Ok(Ok((n, _))) => println!("Short response: {n} bytes (expected 48)"), - Ok(Err(e)) => println!("ERROR: {e}"), - Err(_) => println!("TIMEOUT (30s)"), - } - - tunnel.shutdown().await; -``` - -## Step 7: Run it - -```sh -RUST_LOG=info cargo run -``` - -Or target a specific IPR exit node: - -```sh -RUST_LOG=info cargo run -- --ipr -``` - -You'll see: - -``` -Tunnel ready — allocated IP: 10.0.132.128 - -── Private DNS Lookups (via mixnet UDP) ── - - example.com → 104.20.23.154, 172.66.147.243 (rtt: 1.6s) - cloudflare.com → 104.16.133.229, 104.16.132.229 (rtt: 1.9s) - nymtech.net → 76.76.21.21 (rtt: 2.8s) - -── NTP Time Sync (via mixnet UDP) ── - - Resolved pool.ntp.org → 172.232.146.46 - NTP response in 1.5s - Unix timestamp: 1776188886.068 - UTC: 2026-04-14 17:48:06.068 UTC -``` - -Every DNS query and the NTP request travel through the mixnet as UDP datagrams. The remote servers see the exit gateway's IP — not yours. - -## How it works - -```text -┌──────────────────────────────────────────────────────┐ -│ DNS / NTP (application-layer UDP protocols) │ -│ └─ smolmix::UdpSocket (UDP over mixnet) │ -│ └─ smoltcp (userspace IP stack) │ -│ └─ NymIprBridge → Nym mixnet → IPR │ -└──────────────────────────────────────────────────────┘ -``` - -The smolmix `UdpSocket` has the same `send_to`/`recv_from` API as `tokio::net::UdpSocket`. Any UDP-based protocol works unchanged — DNS and NTP are just two examples. - -## What you've learned - -- `tunnel.udp_socket()` creates a UDP socket that routes datagrams through the mixnet -- The UDP socket API (`send_to`, `recv_from`) matches `tokio::net::UdpSocket` -- Standard crates like `hickory-proto` work unmodified over smolmix UDP -- Raw protocols like NTP (48-byte request/response) work perfectly over the mixnet -- `tokio::time::timeout` is essential for UDP — there's no built-in delivery guarantee -- DNS queries through the mixnet hide your IP from the DNS server -- NTP through the mixnet lets you sync time without revealing your IP to time servers - -## Complete code - -```rust -use std::net::Ipv4Addr; - -use hickory_proto::op::{Message, Query}; -use hickory_proto::rr::{Name, RData, RecordType}; -use smolmix::Tunnel; - -type BoxError = Box; - -/// Hostnames to resolve via mixnet DNS. -const DNS_TARGETS: &[&str] = &["example.com", "cloudflare.com", "nymtech.net"]; - -#[tokio::main] -async fn main() -> Result<(), BoxError> { - nym_bin_common::logging::setup_tracing_logger(); - rustls::crypto::ring::default_provider() - .install_default() - .expect("Failed to install rustls crypto provider"); - - // Parse --ipr flag - let args: Vec = std::env::args().collect(); - let ipr_addr = args - .iter() - .position(|a| a == "--ipr") - .and_then(|i| args.get(i + 1)); - - // Create the tunnel - let mut builder = Tunnel::builder(); - if let Some(addr) = ipr_addr { - builder = builder.ipr_address(addr.parse().expect("invalid IPR address")); - } - let tunnel = builder.build().await?; - println!("Tunnel ready — allocated IP: {}", tunnel.allocated_ips().ipv4); - - // DNS lookups - println!("\nPrivate DNS Lookups (via mixnet UDP)\n"); - - let udp = tunnel.udp_socket().await?; - - for host in DNS_TARGETS { - let start = std::time::Instant::now(); - - // Build DNS query - let mut query = Message::new(); - query.set_recursion_desired(true); - query.add_query(Query::query(Name::from_ascii(host)?, RecordType::A)); - let query_bytes = query.to_vec()?; - - // Send to Cloudflare DNS via mixnet - udp.send_to(&query_bytes, "1.1.1.1:53".parse()?).await?; - - // Read response with timeout - let mut buf = vec![0u8; 1500]; - let result = tokio::time::timeout( - std::time::Duration::from_secs(15), - udp.recv_from(&mut buf), - ) - .await; - - match result { - Ok(Ok((n, _))) => { - let rtt = start.elapsed(); - let response = Message::from_vec(&buf[..n])?; - let ips: Vec<_> = response - .answers() - .iter() - .filter_map(|r| match r.data() { - RData::A(a) => Some(a.0.to_string()), - _ => None, - }) - .collect(); - println!("{host:<20} → {} (rtt: {rtt:.1?})", ips.join(", ")); - } - Ok(Err(e)) => println!("{host:<20} → ERROR: {e}"), - Err(_) => println!("{host:<20} → TIMEOUT"), - } - } - - // NTP time sync - println!("\n── NTP Time Sync (via mixnet UDP) ──\n"); - - // Resolve pool.ntp.org - let ntp_ip = resolve_dns(&tunnel, "pool.ntp.org").await?; - println!(" Resolved pool.ntp.org → {ntp_ip}"); - - // Build NTP request (48 bytes, version 4, client mode) - let mut ntp_req = [0u8; 48]; - ntp_req[0] = 0x23; // LI=0, Version=4, Mode=3 (client) - - let ntp_udp = tunnel.udp_socket().await?; - let start = std::time::Instant::now(); - let ntp_dest: std::net::SocketAddr = (ntp_ip, 123).into(); - ntp_udp.send_to(&ntp_req, ntp_dest).await?; - - // Spinner while waiting - let spinner = tokio::spawn(async { - let frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; - let mut i = 0; - loop { - eprint!("\rWaiting for NTP response {}", frames[i % frames.len()]); - i += 1; - tokio::time::sleep(std::time::Duration::from_millis(80)).await; - } - }); - - let mut buf = [0u8; 48]; - let result = tokio::time::timeout( - std::time::Duration::from_secs(30), - ntp_udp.recv_from(&mut buf), - ) - .await; - spinner.abort(); - - match result { - Ok(Ok((n, _))) if n >= 48 => { - let rtt = start.elapsed(); - // Transmit timestamp at bytes 40..48 (seconds since 1900-01-01) - let secs = u32::from_be_bytes([buf[40], buf[41], buf[42], buf[43]]); - let frac = u32::from_be_bytes([buf[44], buf[45], buf[46], buf[47]]); - - // Convert NTP epoch (1900) to Unix epoch (1970) - const NTP_TO_UNIX: u64 = 2_208_988_800; - let unix_secs = secs as u64 - NTP_TO_UNIX; - let millis = (frac as u64 * 1000) >> 32; - - let dt = chrono::DateTime::from_timestamp(unix_secs as i64, (millis * 1_000_000) as u32) - .expect("valid timestamp"); - eprintln!("\rNTP response in {rtt:.1?}"); - println!("Unix timestamp: {unix_secs}.{millis:03}"); - println!("UTC: {}", dt.format("%Y-%m-%d %H:%M:%S%.3f UTC")); - } - Ok(Ok((n, _))) => eprintln!("\rShort response: {n} bytes (expected 48)"), - Ok(Err(e)) => eprintln!("\rERROR: {e}"), - Err(_) => eprintln!("\rTIMEOUT (30s)"), - } - - println!(); - tunnel.shutdown().await; - Ok(()) -} - -async fn resolve_dns(tunnel: &Tunnel, host: &str) -> Result { - let mut query = Message::new(); - query.set_recursion_desired(true); - query.add_query(Query::query(Name::from_ascii(host)?, RecordType::A)); - let query_bytes = query.to_vec()?; - - let udp = tunnel.udp_socket().await?; - udp.send_to(&query_bytes, "1.1.1.1:53".parse()?).await?; - - let mut buf = vec![0u8; 1500]; - let (n, _) = udp.recv_from(&mut buf).await?; - - let response = Message::from_vec(&buf[..n])?; - let ip = response - .answers() - .iter() - .find_map(|r| match r.data() { - RData::A(a) => Some(a.0), - _ => None, - }) - .ok_or("no A record in DNS response")?; - Ok(ip) -} - -``` - --- 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. @@ -2740,23 +1795,25 @@ url: https://nym.com/docs/developers/rust # Rust SDK -All modules share a common `MixnetClient` that manages gateway connections, Sphinx packet encryption, routing, and cover traffic. +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. -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). +Full API reference, architecture documentation, and type details are available on [**docs.rs/nym-sdk**](https://docs.rs/nym-sdk/latest/nym_sdk/). ## Modules -- **[Stream](./rust/stream)**: multiplexed `AsyncRead + AsyncWrite` byte streams over the Mixnet. **If you're used to TCP sockets, start here.** +- **[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. -- **[Mixnet](./rust/mixnet)**: raw message payloads, independently routed, no connections or ordering. Use this when you want full control over the communication model. +- **[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.** -- **[Client Pool](./rust/client-pool)**: keeps ready-to-use `MixnetClient` instances warm for bursty workloads. +- **[TcpProxy](./rust/tcpproxy)** *(deprecated)* — TCP socket proxying through the Mixnet with session management and message ordering. For new projects, use the Stream module instead. -- **[TcpProxy](./rust/tcpproxy)** *(deprecated)*: TCP socket proxying with session management and message ordering. Use Stream for new projects. +- **[Client Pool](./rust/client-pool)** — A connection pool that maintains ready-to-use `MixnetClient` instances for high-throughput applications. -- **[FFI](./rust/ffi)**: Go and C/C++ bindings. +- **[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. --- title: Tour of the Rust SDK @@ -2767,9 +1824,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. -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. +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.** ## Send a raw message payload @@ -2804,11 +1861,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... @@ -2816,11 +1873,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, enabling anonymous communication without mutual identity disclosure. +The replying side never learns where the reply is going. This is the foundation of anonymous communication on the Mixnet. ## 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; @@ -2858,7 +1915,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 @@ -2885,9 +1942,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 @@ -2916,33 +1973,28 @@ println!("Persistent address: {}", client.nym_address()); ## Where to go next -- [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 +- **[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 --- 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. +description: Add nym-sdk to your Rust project from crates.io or Git. 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 = "X.Y.Z" -blake3 = "=1.7.0" # pin to avoid a transitive dependency conflict — see note below +nym-sdk = "1.20.4" ``` -**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:** 1.70+ - -### From Git - -You can also import directly from Git if you want unreleased changes: +You can also import directly from the Git repository if you need unreleased changes: ```toml # development branch (latest changes, may be unstable) @@ -2952,7 +2004,9 @@ nym-sdk = { git = "https://github.com/nymtech/nym", branch = "develop" } nym-sdk = { git = "https://github.com/nymtech/nym", branch = "master" } ``` -**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. +**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. --- title: Nym Rust SDK: Mixnet Messaging Module @@ -2964,13 +2018,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. -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. +**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. ## 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}; @@ -2989,14 +2043,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 @@ -3005,9 +2059,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 @@ -3017,9 +2071,22 @@ url: https://nym.com/docs/developers/rust/mixnet/tutorial # Tutorial: Send Your First Private Message -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. +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. -**You'll need:** Rust 1.70+ and an internet connection (clients connect to the live Mixnet). +## 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. ## Step 1: Set up the project @@ -3032,10 +2099,8 @@ Add dependencies to `Cargo.toml`: ```toml [dependencies] -nym-sdk = "X.Y.Z" -nym-bin-common = { version = "X.Y.Z", features = ["basic_tracing"] } +nym-sdk = "1.20.4" tokio = { version = "1", features = ["full"] } -blake3 = "=1.7.0" # required pin — see https://nymtech.net/docs/developers/rust/importing ``` ## Step 2: Connect and send @@ -3047,8 +2112,6 @@ use nym_sdk::mixnet::{self, MixnetMessageSender}; #[tokio::main] async fn main() { - 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(); @@ -3066,8 +2129,6 @@ async fn main() { 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 @@ -3086,7 +2147,7 @@ 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 bundles SURBs (Single Use Reply Blocks) with every outgoing message by default: +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: ```rust let sender_tag = message.sender_tag.expect("should have sender tag"); @@ -3111,7 +2172,7 @@ Every message includes a `sender_tag`, an opaque `AnonymousSenderTag` that lets ## Step 5: Run it ```sh -RUST_LOG=info cargo run +cargo run ``` ``` @@ -3162,7 +2223,7 @@ async fn main() { } ``` -Run it twice; the address stays the same. +Run it twice — the address stays the same. ## Going further: send and receive from different tasks @@ -3204,27 +2265,39 @@ async fn main() { } ``` -## What's happening underneath +## What's happening under the hood -`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. +1. **`connect_new()`** generates an ephemeral identity (ed25519 + x25519 keys), fetches the current network topology, selects a gateway, and opens a persistent WebSocket connection. -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. +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. -`split_sender()` clones the send channel while the original client retains the receive side. Both halves can run on separate tokio tasks without synchronization. +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 ## 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}"); @@ -3262,15 +2335,13 @@ 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 @@ -3326,7 +2397,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 | @@ -3336,12 +2407,6 @@ 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 @@ -3359,7 +2424,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; @@ -3371,7 +2436,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 @@ -3393,7 +2458,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: @@ -3414,7 +2479,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 @@ -3424,60 +2489,50 @@ url: https://nym.com/docs/developers/rust/stream # Stream Module -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 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 **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 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. -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. +**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. ## 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()`, 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. +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). -```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 │ │ -│ └──────────────┘ └──────────────────┘ │ -└─────────────────────────────────────────────────────────┘ +```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) ``` ## Complete example -A minimal example with two clients on the same machine: one opens a stream to the other, sends a message, and reads a reply. +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. ```rust use nym_sdk::mixnet; @@ -3488,24 +2543,24 @@ const TIMEOUT: Duration = Duration::from_secs(60); #[tokio::main] async fn main() { - // Connect two ephemeral clients + // 1. 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(); - // The receiver creates a listener (activates stream mode) + // 2. The receiver creates a listener (activates stream mode) let mut listener = receiver.listener().unwrap(); - // The sender opens a stream to the receiver's Nym address + // 3. The sender opens a stream to the receiver's Nym address let mut outbound = sender.open_stream(receiver_addr, None).await.unwrap(); - // The receiver accepts the incoming stream + // 4. The receiver accepts the incoming stream let mut inbound = tokio::time::timeout(TIMEOUT, listener.accept()) .await .expect("timed out") .expect("listener closed"); - // Send data and read it back — just like a TCP socket + // 5. Send data and read it back — just like a TCP socket outbound.write_all(b"hello from sender").await.unwrap(); outbound.flush().await.unwrap(); @@ -3516,7 +2571,7 @@ async fn main() { .expect("read failed"); println!("Receiver got: {}", String::from_utf8_lossy(&buf[..n])); - // Reply back through the same stream + // 6. Reply back through the same stream inbound.write_all(b"hello from receiver").await.unwrap(); inbound.flush().await.unwrap(); @@ -3526,7 +2581,7 @@ async fn main() { .expect("read failed"); println!("Sender got: {}", String::from_utf8_lossy(&buf[..n])); - // Streams deregister on drop, then disconnect clients + // 7. Clean up — streams deregister on drop, then disconnect clients drop(outbound); drop(inbound); sender.disconnect().await; @@ -3534,7 +2589,7 @@ async fn main() { } ``` -The receiver replies via **reply SURBs** (Single Use Reply Blocks) and never learns the sender's Nym address. +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. ## When to use streams vs messages @@ -3543,17 +2598,24 @@ The receiver replies via **reply SURBs** (Single Use Reply Blocks) and never lea | **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 | Sequence-based reordering | Session-based ordering | +| **Ordering** | No guarantees | No guarantees (yet) | 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 [`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 — 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. ## Next steps -- [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) +- [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 --- title: Stream Tutorial: Build a Private Echo Server @@ -3563,7 +2625,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 @@ -3573,6 +2635,8 @@ In this tutorial you'll build two programs: a server that listens for incoming s - 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+) @@ -3583,18 +2647,14 @@ In this tutorial you'll build two programs: a server that listens for incoming s ```sh cargo init nym-echo cd nym-echo -rm src/main.rs ``` Add dependencies to `Cargo.toml`: ```toml [dependencies] -nym-sdk = "X.Y.Z" -nym-bin-common = { version = "X.Y.Z", features = ["basic_tracing"] } +nym-sdk = "1.20.4" 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 @@ -3609,8 +2669,6 @@ 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()); @@ -3634,7 +2692,7 @@ async fn main() { // Spawn a task to handle each stream concurrently tokio::spawn(async move { - let mut buf = vec![0u8; 32_000]; + let mut buf = vec![0u8; 4096]; loop { let n = match stream.read(&mut buf).await { @@ -3647,7 +2705,11 @@ async fn main() { }; let data = &buf[..n]; - println!("Stream {stream_id} received {n} bytes"); + println!( + "Stream {stream_id} received {} bytes: {:?}", + n, + String::from_utf8_lossy(data) + ); // Echo it back if let Err(e) = stream.write_all(data).await { @@ -3680,8 +2742,6 @@ 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) @@ -3698,32 +2758,22 @@ async fn main() { let mut stream = client.open_stream(server_addr, None).await.unwrap(); 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 - // 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 messages and read back the echo for each + for i in 1..=3 { + let msg = format!("message {i}"); + println!("Sending: {msg}"); - // 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.write_all(msg.as_bytes()).await.unwrap(); stream.flush().await.unwrap(); // Read the echo - let mut buf = vec![0u8; 32_000]; + let mut buf = vec![0u8; 1024]; let n = tokio::time::timeout(TIMEOUT, stream.read(&mut buf)) .await .expect("timed out waiting for echo") .expect("read failed"); - assert_eq!(&buf[..n], &payload[..], "echo mismatch on message {}", i + 1); - println!("Received echo: {n} bytes ok"); + println!("Echo: {}", String::from_utf8_lossy(&buf[..n])); } // Drop the stream to deregister it from the router @@ -3740,7 +2790,7 @@ async fn main() { In one terminal, start the server: ```sh -RUST_LOG=info cargo run --bin server +cargo run --bin server ``` It prints its Nym address: @@ -3752,7 +2802,7 @@ Echo server listening at: 8gk4Y...@2xU4d... In a second terminal, start the client with the server's address: ```sh -RUST_LOG=info cargo run --bin client -- 8gk4Y...@2xU4d... +cargo run --bin client 8gk4Y...@2xU4d... ``` You'll see the messages traverse the Mixnet and echo back: @@ -3760,12 +2810,12 @@ You'll see the messages traverse the Mixnet and echo back: ``` Client address: F3qR7...@9nK2m... Stream opened: 12345678 -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 +Sending: message 1 +Echo: message 1 +Sending: message 2 +Echo: message 2 +Sending: message 3 +Echo: message 3 Done! ``` @@ -3773,13 +2823,13 @@ On the server side: ``` Accepted stream 12345678 -Stream 12345678 received 320 bytes -Stream 12345678 received 25000 bytes -Stream 12345678 received 1280 bytes +Stream 12345678 received 9 bytes: "message 1" +Stream 12345678 received 9 bytes: "message 2" +Stream 12345678 received 9 bytes: "message 3" Stream 12345678 closed ``` -## How it works internally +## What's happening under the hood 1. The server's `listener()` activates **stream mode**, which spawns a **router task** that decodes incoming Mixnet messages and dispatches them by stream ID. @@ -3787,25 +2837,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 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. +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. -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()`. +5. On arrival, the router decodes the header, finds the matching stream, 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** — it never learns the client's Nym address. This is the same anonymous reply mechanism used by the message API, applied transparently. -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. +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). 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 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 +- **`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 ## Complete code @@ -3817,8 +2867,6 @@ 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()); @@ -3834,7 +2882,7 @@ async fn main() { println!("Accepted stream {stream_id}"); tokio::spawn(async move { - let mut buf = vec![0u8; 32_000]; + let mut buf = vec![0u8; 4096]; loop { let n = match stream.read(&mut buf).await { Ok(0) | Err(_) => break, @@ -3862,8 +2910,6 @@ 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 ") @@ -3876,26 +2922,20 @@ async fn main() { let mut stream = client.open_stream(server_addr, None).await.unwrap(); println!("Stream opened: {}", stream.id()); - // Wait for the Open message to reach the server through the mixnet - tokio::time::sleep(Duration::from_secs(5)).await; + for i in 1..=3 { + let msg = format!("message {i}"); + println!("Sending: {msg}"); - 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.write_all(msg.as_bytes()).await.unwrap(); stream.flush().await.unwrap(); - let mut buf = vec![0u8; 32_000]; + let mut buf = vec![0u8; 1024]; let n = tokio::time::timeout(TIMEOUT, stream.read(&mut buf)) .await .expect("timed out waiting for echo") .expect("read failed"); - assert_eq!(&buf[..n], &payload[..], "echo mismatch on message {}", i + 1); - println!("Received echo: {n} bytes ok"); + println!("Echo: {}", String::from_utf8_lossy(&buf[..n])); } drop(stream); @@ -3938,19 +2978,17 @@ flowchart TD ## Wire protocol -Every stream message has a fixed 16-byte LP frame header prepended to the payload: +Every stream message has a fixed 10-byte header prepended to the payload: ``` -[LpFrameKind: 2 bytes LE][StreamId: 8 bytes BE][MsgType: 1 byte][SequenceNum: 4 bytes BE][Reserved: 1 byte][payload ...] +[Version: 1 byte][StreamId: 8 bytes][MessageType: 1 byte][payload ...] ``` -- **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`. +- **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). -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 @@ -3976,20 +3014,23 @@ 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 -**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. +**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 -- **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. +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. ## 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, or the [docs.rs](https://docs.rs/nym-sdk/latest/nym_sdk/) API reference. +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. --- title: Stream Module Examples @@ -4022,11 +3063,11 @@ url: https://nym.com/docs/developers/rust/tcpproxy **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. -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. +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. --- -`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. +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. > Non-Rust/Go developers who want to experiment with this module can start with the [standalone binaries](../tools/standalone-tcpproxy). @@ -4037,35 +3078,60 @@ The Stream module offers the same key benefit (familiar I/O patterns on top of t | 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 -## Tutorial +## Troubleshooting -Set up the project: +### 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 ```sh cargo init nym-tcp-proxy cd nym-tcp-proxy -rm src/main.rs ``` Add dependencies to `Cargo.toml`: ```toml [dependencies] -nym-sdk = "X.Y.Z" -nym-network-defaults = "X.Y.Z" -nym-bin-common = { version = "X.Y.Z", features = ["basic_tracing"] } +nym-sdk = "1.20.4" tokio = { version = "1", features = ["full"] } -blake3 = "=1.7.0" # required pin — see https://nymtech.net/docs/developers/rust/importing +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" @@ -4075,68 +3141,79 @@ name = "proxy_client" path = "src/bin/proxy_client.rs" ``` -### Server +Create the `src/bin/` directory: -The server connects to the Mixnet and forwards incoming traffic to a local TCP service (e.g. a web server on port 8000). +```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`: ```rust use nym_sdk::tcp_proxy::NymProxyServer; #[tokio::main] -async fn main() -> Result<(), Box> { - nym_bin_common::logging::setup_tracing_logger(); - +async fn main() -> anyhow::Result<()> { + // Forward traffic to localhost:3000 (your upstream service). + // The second argument is a directory for persistent key storage. let mut server = NymProxyServer::new( - "127.0.0.1:8000", // upstream address (host:port) + "127.0.0.1:3000", // 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(()) } ``` -### Client +## Step 3: Build the 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() -> 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::); - +async fn main() -> anyhow::Result<()> { + // Parse the server's Nym address from the command line. 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 - "8070", // listen port + "1080", // listen port 60, // close timeout (seconds) None, // env file (None = mainnet) - 1, // client pool size + 2, // client pool size ).await?; + // Spawn the proxy in the background. 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; + // Give the proxy a moment to start listening. + tokio::time::sleep(std::time::Duration::from_secs(3)).await; - let mut stream = TcpStream::connect("127.0.0.1:8070").await?; + // Connect to the local proxy socket — traffic goes through the Mixnet. + let mut stream = TcpStream::connect("127.0.0.1:1080").await?; stream.write_all(b"GET / HTTP/1.0\r\nHost: localhost\r\n\r\n").await?; let mut response = Vec::new(); @@ -4149,41 +3226,78 @@ async fn main() -> Result<(), Box> { } ``` -### Run it +## Step 4: Run it -Start an upstream TCP service (e.g. a simple HTTP server): +Start a simple upstream service (e.g. Python's HTTP server): ```sh -python3 -m http.server 8000 +cd /tmp && echo "hello from upstream" > index.html +python3 -m http.server 3000 ``` -In a second terminal, start the proxy server: - +Terminal 1 — start the proxy server: ```sh -RUST_LOG=info cargo run --bin proxy_server +cargo run --bin proxy_server +# Proxy server address: 8gk4Y...@2xU4d... ``` -Copy the Nym address it prints, then in a third terminal: - +Terminal 2 — start the proxy client: ```sh -RUST_LOG=info cargo run --bin proxy_client -- +cargo run --bin proxy_client 8gk4Y...@2xU4d... +# Response: +# HTTP/1.0 200 OK +# ... +# hello from upstream ``` -The response will take 30–60 seconds to arrive as it traverses the Mixnet in both directions. +The HTTP request travelled through the Mixnet — the upstream server only sees a connection from `localhost`, not the client's real IP. -## Architecture +## How it differs from streams -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. +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. -### Sessions & message ordering +## What you've learned -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. +- **`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 -> 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. +--- +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 +--- -The key data structure: +# 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: ```rust pub struct ProxiedMessage { @@ -4193,8 +3307,106 @@ pub struct ProxiedMessage { } ``` -### Full request/response flow +> This code can be found [here](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/src/tcp_proxy/utils.rs#L147C1-L152C2) +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: @@ -4212,6 +3424,8 @@ 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 @@ -4221,6 +3435,8 @@ 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 @@ -4241,8 +3457,11 @@ 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 @@ -4253,12 +3472,6 @@ 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. @@ -4267,7 +3480,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 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` 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. ## How it works @@ -4284,27 +3497,22 @@ 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() -> Result<(), Box> { - nym_bin_common::logging::setup_tracing_logger(); - // Load mainnet network defaults into env vars (required by ClientPool) - setup_env(None::); - +async fn main() -> anyhow::Result<()> { let pool = ClientPool::new(5); // maintain 5 clients in reserve let pool_clone = pool.clone(); @@ -4323,9 +3531,9 @@ async fn main() -> Result<(), Box> { ## 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 @@ -4345,6 +3553,8 @@ 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+) @@ -4361,32 +3571,23 @@ Add dependencies to `Cargo.toml`: ```toml [dependencies] -nym-sdk = "X.Y.Z" -nym-network-defaults = "X.Y.Z" -nym-bin-common = { version = "X.Y.Z", features = ["basic_tracing"] } +nym-sdk = "1.20.4" 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 -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); @@ -4420,7 +3621,7 @@ 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 mut client = match pool.get_mixnet_client().await { + let client = match pool.get_mixnet_client().await { Some(c) => { println!("Task {i}: got client {} from pool", c.nym_address()); c @@ -4491,7 +3692,7 @@ After popping all 3 clients, the pool background loop starts creating replacemen ## Step 6: Run it ```sh -RUST_LOG=info cargo run +cargo run ``` You'll see output like: @@ -4518,20 +3719,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 @@ -4540,14 +3741,10 @@ The `NymProxyClient` (TcpProxy module) uses a `ClientPool` internally: one clien ```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(); - setup_env(None::); - let pool = ClientPool::new(3); let pool_bg = pool.clone(); tokio::spawn(async move { pool_bg.start().await.unwrap() }); @@ -4560,7 +3757,7 @@ async fn main() { for i in 1..=3 { let pool = pool.clone(); handles.push(tokio::spawn(async move { - let mut client = match pool.get_mixnet_client().await { + let client = match pool.get_mixnet_client().await { Some(c) => c, None => nym_sdk::mixnet::MixnetClient::connect_new().await.unwrap(), }; @@ -4594,24 +3791,6 @@ async fn main() { } ``` ---- -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. @@ -4633,13 +3812,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, listening for incoming messages. +**Mixnet** (Go and C/C++) — ephemeral and persistent client creation, sending messages, anonymous replies via SURBs, and 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** have no standalone FFI bindings yet. The TcpProxy bindings use the Client Pool internally. +**Client Pool and Stream** — no standalone FFI bindings yet. The TcpProxy bindings use the Client Pool internally. ## Quick example (Go) @@ -4707,10 +3886,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): 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) — 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 --- title: Nym TypeScript SDK: Privacy for Web Apps @@ -4722,7 +3901,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 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. +**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. ## Packages @@ -4751,10 +3930,10 @@ The Nym Mixnet routes traffic through multiple nodes with no persistent connecti 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. @@ -4854,10 +4033,10 @@ 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 @@ -4867,11 +4046,11 @@ 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. +- 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. @@ -5129,7 +4308,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 @@ -5692,8 +4871,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. @@ -5705,7 +4884,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 @@ -6002,13 +5181,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 @@ -6119,7 +5298,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 @@ -6212,7 +5391,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) @@ -6292,7 +5471,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 @@ -6307,8 +5486,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 @@ -6349,13 +5528,13 @@ url: https://nym.com/docs/developers/tools # 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. These 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 @@ -6389,15 +5568,15 @@ See the [commands](commands.mdx) page for an overview of all command options. 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 ``` @@ -6446,7 +5625,7 @@ Options: Print help ``` -## `account` +## `account` ```sh Query and manage Nyx blockchain accounts @@ -6488,7 +5667,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 @@ -6581,7 +5760,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 @@ -6602,7 +5781,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 @@ -6623,7 +5802,7 @@ Options: Print help ``` -## `signature` +## `signature` ```sh Sign and verify messages @@ -6706,7 +5885,7 @@ Options: Print help ``` -## `ecash` +## `ecash` ```sh Ecash related stuff @@ -6714,13 +5893,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: @@ -6831,7 +6010,7 @@ Options: Print help ``` -## `coconut` +## `coconut` ## `coconut generate-freepass` @@ -6841,7 +6020,7 @@ Options: ## `coconut import-credential` -## `block` +## `block` ```sh Query chain blocks @@ -6946,7 +6125,7 @@ Options: Print help ``` -## `cosmwasm` +## `cosmwasm` ```sh Manage and execute WASM smart contracts @@ -6989,11 +6168,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 @@ -7016,19 +6195,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