Compare commits

..

3 Commits

Author SHA1 Message Date
benedettadavico 867acfef26 .. 2025-10-22 10:02:17 +02:00
benedettadavico e4996dc0ce comment out migration 2025-10-21 14:28:01 +02:00
Jędrzej Stuczyński a6e23a210b bugfix: update stored epoch share when changing ownership 2025-10-21 11:10:24 +01:00
470 changed files with 12210 additions and 19208 deletions
-3
View File
@@ -8,13 +8,10 @@ on:
- 'gateway/**'
- 'integrations/**'
- 'nym-api/**'
- 'nym-authenticator-client/**'
- 'nym-credential-proxy/**'
- 'nym-ip-packet-client/**'
- 'nym-network-monitor/**'
- 'nym-node/**'
- 'nym-node-status-api/**'
- 'nym-registration-client/**'
- 'nym-statistics-api/**'
- 'nym-outfox/**'
- 'nym-validator-rewarder/**'
+9 -10
View File
@@ -6,14 +6,16 @@ on:
paths:
- "ts-packages/**"
- "sdk/typescript/**"
- "nym-connect/desktop/src/**"
- "nym-connect/desktop/package.json"
- "nym-wallet/src/**"
- "nym-wallet/package.json"
- "explorer-v2/**"
- "explorer/**"
- ".github/workflows/ci-lint-typescript.yml"
jobs:
build:
runs-on: arc-linux-latest
runs-on: ubuntu-22.04
env:
RUSTUP_PERMIT_COPY_RENAME: 1
steps:
@@ -23,7 +25,6 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Setup yarn
run: npm install -g yarn
@@ -36,12 +37,14 @@ jobs:
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
- name: Install wasm-opt
run: cargo install wasm-opt
uses: ./.github/actions/install-wasm-opt
with:
version: '116'
- name: Set up Go
uses: actions/setup-go@v6
uses: actions/setup-go@v5
with:
go-version: "1.24.6"
go-version: "1.23.7"
- name: Install
run: yarn
@@ -49,11 +52,7 @@ jobs:
- name: Build packages
run: yarn build:ci
- name: Install again
run: yarn
- name: Lint
run: yarn lint
- name: Typecheck with tsc
run: yarn tsc
@@ -8,7 +8,7 @@ on:
jobs:
build:
runs-on: arc-linux-latest
runs-on: custom-linux
steps:
- uses: actions/checkout@v4
+4 -7
View File
@@ -4,7 +4,7 @@ on:
jobs:
publish:
runs-on: ubuntu-latest
runs-on: arc-ubuntu-22.04
steps:
- uses: actions/checkout@v4
@@ -17,13 +17,10 @@ jobs:
- name: Setup yarn
run: npm install -g yarn
- name: Install rust toolchain
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
components: rustfmt, clippy
- name: Install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
@@ -32,9 +29,9 @@ jobs:
run: cargo install wasm-opt
- name: Set up Go
uses: actions/setup-go@v6
uses: actions/setup-go@v5
with:
go-version: "1.24.6"
go-version: "1.23.7"
- name: Install dependencies
run: yarn
+30 -3
View File
@@ -3,6 +3,11 @@ name: Build and upload Node Status agent container to harbor.nymte.ch
on:
workflow_dispatch:
inputs:
gateway_probe_git_ref:
type: string
default: nym-vpn-core-v1.4.0
required: true
description: Which gateway probe git ref to build the image with
release_image:
description: 'Tag image as a release'
required: true
@@ -38,6 +43,16 @@ jobs:
VERSION=$(yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml)
echo "result=$VERSION" >> $GITHUB_OUTPUT
- name: cleanup-gateway-probe-ref
id: cleanup_gateway_probe_ref
run: |
GATEWAY_PROBE_GIT_REF=${{ github.event.inputs.gateway_probe_git_ref }}
GIT_REF_SLUG="${GATEWAY_PROBE_GIT_REF//\//-}"
echo "git_ref=${GIT_REF_SLUG}" >> $GITHUB_OUTPUT
- name: Set GIT_TAG variable
run: echo "GIT_TAG=${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }}" >> $GITHUB_ENV
- name: Initialize RELEASE_TAG
run: echo "RELEASE_TAG=" >> $GITHUB_ENV
@@ -46,12 +61,24 @@ jobs:
run: echo "RELEASE_TAG=golden-" >> $GITHUB_ENV
- name: Set IMAGE_NAME_AND_TAGS variable
run: echo "IMAGE_NAME_AND_TAGS=${{ env.CONTAINER_NAME }}:${{ env.RELEASE_TAG }}${{ steps.get_version.outputs.result }}" >> $GITHUB_ENV
run: echo "IMAGE_NAME_AND_TAGS=${{ env.CONTAINER_NAME }}:${{ env.RELEASE_TAG }}${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }}" >> $GITHUB_ENV
- name: New env vars
run: echo "RELEASE_TAG='$RELEASE_TAG' IMAGE_NAME_AND_TAGS='$IMAGE_NAME_AND_TAGS'"
run: echo "RELEASE_TAG='$RELEASE_TAG' GIT_TAG='$GIT_TAG' IMAGE_NAME_AND_TAGS='$IMAGE_NAME_AND_TAGS'"
# - name: Remove existing tag if exists
# run: |
# if git rev-parse $${{ env.GIT_TAG }} >/dev/null 2>&1; then
# git push --delete origin $${{ env.GIT_TAG }}
# git tag -d $${{ env.GIT_TAG }}
# fi
# - name: Create tag
# run: |
# git tag -a $${{ env.GIT_TAG }} -m "Version ${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }}"
# git push origin $${{ env.GIT_TAG }}
- name: BuildAndPushImageOnHarbor
run: |
docker build -f ${{ env.WORKING_DIRECTORY }}/Dockerfile . -t harbor.nymte.ch/nym/${{ env.IMAGE_NAME_AND_TAGS }}
docker build --build-arg GIT_REF=${{ github.event.inputs.gateway_probe_git_ref }} -f ${{ env.WORKING_DIRECTORY }}/Dockerfile . -t harbor.nymte.ch/nym/${{ env.IMAGE_NAME_AND_TAGS }}
docker push harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }} --all-tags
-72
View File
@@ -4,78 +4,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [Unreleased]
## [2025.20-leerdammer] (2025-11-12)
- Max/tweak ts sdk actions ([#6185])
- chore: resolve clippy 1.91 warnings ([#6168])
- [chore] Remove unused dependencies ([#6151])
- Use typed-builder for registration client builder config ([#6150])
- tommy is too quick ([#6149])
- configurable mixnet client startup timeout ([#6148])
- [Feature/operators]: QUIC bridge deployment script v2 ([#6145])
- Bugfix: Add circuit breaker ([#6143])
- bugfix: update internal owner address in transferred share ([#6139])
- Update quic_bridge_deployment.sh for IPv4 and .deb package ([#6138])
- feat: expose more explicit new_with_fronted_urls builder for http API client ([#6136])
- bugfix: update stored epoch share when changing ownership ([#6135])
- Domain fronting ([#6134])
- bugfix: update stored epoch share when changing announce address ([#6131])
[#6185]: https://github.com/nymtech/nym/pull/6185
[#6168]: https://github.com/nymtech/nym/pull/6168
[#6151]: https://github.com/nymtech/nym/pull/6151
[#6150]: https://github.com/nymtech/nym/pull/6150
[#6149]: https://github.com/nymtech/nym/pull/6149
[#6148]: https://github.com/nymtech/nym/pull/6148
[#6145]: https://github.com/nymtech/nym/pull/6145
[#6143]: https://github.com/nymtech/nym/pull/6143
[#6139]: https://github.com/nymtech/nym/pull/6139
[#6138]: https://github.com/nymtech/nym/pull/6138
[#6136]: https://github.com/nymtech/nym/pull/6136
[#6135]: https://github.com/nymtech/nym/pull/6135
[#6134]: https://github.com/nymtech/nym/pull/6134
[#6131]: https://github.com/nymtech/nym/pull/6131
## [2025.19-kase] (2025-10-30)
- update ns agent workflow ([#6154])
- Cherry pick - request #6143 from nymtech/bugfix/mix-tx-closed-v2 ([#6153])
- bugfix: nym-credential-proxy query params parsing regression ([#6121])
- bugfix: revert some dep updates introduced in #6043 ([#6120])
- Skip ipv6 metadata endpoint request ([#6118])
- update to no longer use 1mb files ([#6117])
- chore: restore pending dkg contract state migration ([#6116])
- Revert "Propagate cancel token to mixnet client" ([#6115])
- Update dirs to 6.0 ([#6109])
- Propagate cancel token to mixnet client ([#6105])
- bugfix: retrieve and update ticketbook in the same query ([#6101])
- bugfix: include network name in the default gateway probe config path ([#6100])
- Bugfix/incompatibility fixes ([#6099])
- [DOCs/operators] QUIC deployment script & docs ([#6098])
- bugfix: testnet manager 02sql migration ([#6096])
- feat: move gateway probe to monorepo (and update to rust edition 2024) ([#6094])
- bugfix: use custom topology provider for list of init gateways ([#6092])
- Max/fix wasm client + build commands ([#6043])
[#6154]: https://github.com/nymtech/nym/pull/6154
[#6153]: https://github.com/nymtech/nym/pull/6153
[#6121]: https://github.com/nymtech/nym/pull/6121
[#6120]: https://github.com/nymtech/nym/pull/6120
[#6118]: https://github.com/nymtech/nym/pull/6118
[#6117]: https://github.com/nymtech/nym/pull/6117
[#6116]: https://github.com/nymtech/nym/pull/6116
[#6115]: https://github.com/nymtech/nym/pull/6115
[#6109]: https://github.com/nymtech/nym/pull/6109
[#6105]: https://github.com/nymtech/nym/pull/6105
[#6101]: https://github.com/nymtech/nym/pull/6101
[#6100]: https://github.com/nymtech/nym/pull/6100
[#6099]: https://github.com/nymtech/nym/pull/6099
[#6098]: https://github.com/nymtech/nym/pull/6098
[#6096]: https://github.com/nymtech/nym/pull/6096
[#6094]: https://github.com/nymtech/nym/pull/6094
[#6092]: https://github.com/nymtech/nym/pull/6092
[#6043]: https://github.com/nymtech/nym/pull/6043
## [2025.18-jarlsberg] (2025-10-14)
- ns-api: add descriptions to dVPN gateway responses ([#6102])
Generated
+42 -81
View File
@@ -133,9 +133,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "ammonia"
version = "4.1.2"
version = "4.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17e913097e1a2124b46746c980134e8c954bc17a6a59bb3fde96f088d126dde6"
checksum = "d6b346764dd0814805de8abf899fe03065bcee69bb1a4771c785817e39f3978f"
dependencies = [
"cssparser",
"html5ever",
@@ -2262,7 +2262,7 @@ dependencies = [
"libc",
"option-ext",
"redox_users",
"windows-sys 0.60.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -2579,7 +2579,7 @@ dependencies = [
[[package]]
name = "extension-storage"
version = "1.4.1"
version = "1.4.0-rc.0"
dependencies = [
"bip39",
"console_error_panic_hook",
@@ -4478,7 +4478,7 @@ dependencies = [
[[package]]
name = "mix-fetch-wasm"
version = "1.4.1"
version = "1.4.0-rc.0"
dependencies = [
"async-trait",
"futures",
@@ -4488,7 +4488,6 @@ dependencies = [
"nym-ordered-buffer",
"nym-service-providers-common",
"nym-socks5-requests",
"nym-validator-client",
"rand 0.8.5",
"serde",
"serde-wasm-bindgen 0.6.5",
@@ -4825,7 +4824,7 @@ dependencies = [
[[package]]
name = "nym-api"
version = "1.1.69"
version = "1.1.67"
dependencies = [
"anyhow",
"async-trait",
@@ -4988,7 +4987,6 @@ dependencies = [
"nym-network-defaults",
"nym-service-provider-requests-common",
"nym-sphinx",
"nym-test-utils",
"nym-wireguard-types",
"rand 0.8.5",
"semver 1.0.26",
@@ -4996,7 +4994,6 @@ dependencies = [
"sha2 0.10.9",
"strum_macros",
"thiserror 2.0.12",
"tracing",
"x25519-dalek",
]
@@ -5005,16 +5002,21 @@ name = "nym-bandwidth-controller"
version = "0.1.0"
dependencies = [
"async-trait",
"bip39",
"log",
"nym-credential-storage",
"nym-credentials",
"nym-credentials-interface",
"nym-crypto",
"nym-ecash-contract-common",
"nym-ecash-time",
"nym-network-defaults",
"nym-task",
"nym-validator-client",
"rand 0.8.5",
"thiserror 2.0.12",
"url",
"zeroize",
]
[[package]]
@@ -5048,7 +5050,7 @@ dependencies = [
[[package]]
name = "nym-cli"
version = "1.1.66"
version = "1.1.64"
dependencies = [
"anyhow",
"base64 0.22.1",
@@ -5131,7 +5133,7 @@ dependencies = [
[[package]]
name = "nym-client"
version = "1.1.66"
version = "1.1.64"
dependencies = [
"bs58",
"clap",
@@ -5204,7 +5206,6 @@ dependencies = [
"nym-task",
"nym-topology",
"nym-validator-client",
"once_cell",
"rand 0.8.5",
"rand_chacha 0.3.1",
"serde",
@@ -5280,25 +5281,9 @@ dependencies = [
"tracing",
]
[[package]]
name = "nym-client-rtt-tester"
version = "0.1.0"
dependencies = [
"nym-client-core",
"nym-config",
"nym-crypto",
"nym-network-defaults",
"nym-sdk",
"nym-sphinx",
"nym-task",
"nym-topology",
"tokio",
"tracing",
]
[[package]]
name = "nym-client-wasm"
version = "1.4.1"
version = "1.4.0-rc.0"
dependencies = [
"anyhow",
"futures",
@@ -5477,11 +5462,8 @@ dependencies = [
"nym-crypto",
"nym-ecash-contract-common",
"nym-ecash-signer-check",
"nym-http-api-client",
"nym-http-api-common",
"nym-network-defaults",
"nym-pemstore",
"nym-upgrade-mode-check",
"nym-validator-client",
"rand 0.8.5",
"reqwest 0.12.22",
@@ -5552,7 +5534,6 @@ dependencies = [
"nym-http-api-client",
"nym-http-api-common",
"nym-serde-helpers",
"nym-upgrade-mode-check",
"reqwest 0.12.22",
"schemars 0.8.22",
"serde",
@@ -5584,7 +5565,6 @@ dependencies = [
"sqlx",
"sqlx-pool-guard",
"thiserror 2.0.12",
"time",
"tokio",
"zeroize",
]
@@ -5620,13 +5600,12 @@ dependencies = [
"nym-api-requests",
"nym-credentials",
"nym-credentials-interface",
"nym-crypto",
"nym-ecash-contract-common",
"nym-gateway-requests",
"nym-gateway-storage",
"nym-task",
"nym-upgrade-mode-check",
"nym-validator-client",
"rand 0.8.5",
"si-scale",
"thiserror 2.0.12",
"time",
@@ -5666,7 +5645,6 @@ dependencies = [
"nym-compact-ecash",
"nym-ecash-time",
"nym-network-defaults",
"nym-upgrade-mode-check",
"rand 0.8.5",
"serde",
"strum",
@@ -5683,7 +5661,6 @@ dependencies = [
"aead",
"aes",
"aes-gcm-siv",
"anyhow",
"base64 0.22.1",
"blake3",
"bs58",
@@ -5697,12 +5674,10 @@ dependencies = [
"jwt-simple",
"nym-pemstore",
"nym-sphinx-types",
"nym-test-utils",
"rand 0.8.5",
"rand_chacha 0.3.1",
"serde",
"serde_bytes",
"serde_json",
"sha2 0.10.9",
"subtle-encoding",
"thiserror 2.0.12",
@@ -5817,6 +5792,7 @@ dependencies = [
name = "nym-gateway"
version = "1.1.36"
dependencies = [
"anyhow",
"async-trait",
"bincode",
"bip39",
@@ -5827,6 +5803,7 @@ dependencies = [
"futures",
"ipnetwork",
"mock_instant",
"nym-api-requests",
"nym-authenticator-requests",
"nym-client-core",
"nym-credential-verification",
@@ -5839,6 +5816,7 @@ dependencies = [
"nym-id",
"nym-ip-packet-router",
"nym-mixnet-client",
"nym-mixnode-common",
"nym-network-defaults",
"nym-network-requester",
"nym-node-metrics",
@@ -5848,18 +5826,20 @@ dependencies = [
"nym-statistics-common",
"nym-task",
"nym-topology",
"nym-upgrade-mode-check",
"nym-types",
"nym-validator-client",
"nym-wireguard",
"nym-wireguard-private-metadata-server",
"nym-wireguard-types",
"rand 0.8.5",
"serde",
"sha2 0.10.9",
"thiserror 2.0.12",
"time",
"tokio",
"tokio-stream",
"tokio-tungstenite",
"tokio-util",
"tracing",
"url",
"zeroize",
@@ -6070,7 +6050,6 @@ dependencies = [
"thiserror 2.0.12",
"tokio",
"tracing",
"tracing-subscriber",
"url",
"wasmtimer",
]
@@ -6375,7 +6354,7 @@ dependencies = [
[[package]]
name = "nym-network-requester"
version = "1.1.67"
version = "1.1.65"
dependencies = [
"addr",
"anyhow",
@@ -6425,7 +6404,7 @@ dependencies = [
[[package]]
name = "nym-node"
version = "1.21.0"
version = "1.19.0"
dependencies = [
"anyhow",
"arc-swap",
@@ -6456,7 +6435,6 @@ dependencies = [
"nym-bin-common",
"nym-client-core-config-types",
"nym-config",
"nym-credential-verification",
"nym-crypto",
"nym-gateway",
"nym-gateway-stats-storage",
@@ -6529,13 +6507,13 @@ version = "0.1.0"
dependencies = [
"async-trait",
"celes",
"humantime",
"humantime-serde",
"nym-bin-common",
"nym-crypto",
"nym-exit-policy",
"nym-http-api-client",
"nym-noise-keys",
"nym-upgrade-mode-check",
"nym-wireguard-types",
"rand_chacha 0.3.1",
"schemars 0.8.22",
@@ -6546,7 +6524,6 @@ dependencies = [
"thiserror 2.0.12",
"time",
"tokio",
"url",
"utoipa",
]
@@ -6569,7 +6546,7 @@ dependencies = [
[[package]]
name = "nym-node-status-api"
version = "4.0.11-rc1"
version = "4.0.10"
dependencies = [
"ammonia",
"anyhow",
@@ -6665,7 +6642,7 @@ dependencies = [
[[package]]
name = "nym-node-tester-wasm"
version = "1.3.1"
version = "1.3.0-rc.0"
dependencies = [
"futures",
"js-sys",
@@ -6821,7 +6798,6 @@ dependencies = [
"tokio",
"tokio-util",
"tracing",
"typed-builder",
"url",
]
@@ -6954,7 +6930,7 @@ dependencies = [
[[package]]
name = "nym-socks5-client"
version = "1.1.66"
version = "1.1.64"
dependencies = [
"bs58",
"clap",
@@ -7401,14 +7377,12 @@ dependencies = [
"jwt-simple",
"nym-crypto",
"nym-http-api-client",
"nym-test-utils",
"reqwest 0.12.22",
"serde",
"serde_json",
"thiserror 2.0.12",
"time",
"tracing",
"utoipa",
]
[[package]]
@@ -7588,10 +7562,17 @@ dependencies = [
name = "nym-wireguard"
version = "0.1.0"
dependencies = [
"async-trait",
"base64 0.22.1",
"bincode",
"chrono",
"dashmap",
"defguard_wireguard_rs",
"dyn-clone",
"futures",
"ip_network",
"log",
"nym-authenticator-requests",
"nym-credential-verification",
"nym-credentials-interface",
"nym-crypto",
@@ -7602,9 +7583,11 @@ dependencies = [
"nym-task",
"nym-wireguard-types",
"thiserror 2.0.12",
"time",
"tokio",
"tokio-stream",
"tracing",
"x25519-dalek",
]
[[package]]
@@ -7656,20 +7639,15 @@ version = "1.0.0"
dependencies = [
"async-trait",
"axum",
"futures",
"nym-credential-verification",
"nym-credentials-interface",
"nym-crypto",
"nym-http-api-client",
"nym-http-api-common",
"nym-upgrade-mode-check",
"nym-wireguard",
"nym-wireguard-private-metadata-client",
"nym-wireguard-private-metadata-server",
"nym-wireguard-private-metadata-shared",
"time",
"tokio",
"tower 0.5.2",
"tower-http 0.5.2",
"utoipa",
]
@@ -7679,7 +7657,10 @@ name = "nym-wireguard-types"
version = "0.1.0"
dependencies = [
"base64 0.22.1",
"log",
"nym-config",
"nym-crypto",
"nym-network-defaults",
"rand 0.8.5",
"serde",
"thiserror 2.0.12",
@@ -7688,7 +7669,7 @@ dependencies = [
[[package]]
name = "nymvisor"
version = "0.1.31"
version = "0.1.29"
dependencies = [
"anyhow",
"bytes",
@@ -7842,9 +7823,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
[[package]]
name = "openssl-sys"
version = "0.9.110"
version = "0.9.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a9f0075ba3c21b09f8e8b2026584b1d18d49388648f2fbbf3c97ea8deced8e2"
checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571"
dependencies = [
"cc",
"libc",
@@ -11410,26 +11391,6 @@ dependencies = [
"utf-8",
]
[[package]]
name = "typed-builder"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d0dd654273fc253fde1df4172c31fb6615cf8b041d3a4008a028ef8b1119e66"
dependencies = [
"typed-builder-macro",
]
[[package]]
name = "typed-builder-macro"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "016c26257f448222014296978b2c8456e2cad4de308c35bdb1e383acd569ef5b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.106",
]
[[package]]
name = "typenum"
version = "1.18.0"
+13 -9
View File
@@ -18,7 +18,6 @@ resolver = "2"
members = [
"clients/native",
"clients/native/websocket-requests",
"clients/rtt-tester",
"clients/socks5",
"common/async-file-watcher",
"common/authenticator-requests",
@@ -151,7 +150,7 @@ members = [
"tools/internal/contract-state-importer/importer-cli",
"tools/internal/contract-state-importer/importer-contract",
"tools/internal/mixnet-connectivity-check",
# "tools/internal/sdk-version-bump",
# "tools/internal/sdk-version-bump",
"tools/internal/ssl-inject",
"tools/internal/testnet-manager",
"tools/internal/testnet-manager/dkg-bypass-contract",
@@ -172,7 +171,6 @@ members = [
default-members = [
"clients/native",
"clients/socks5",
"nym-authenticator-client",
"nym-api",
"nym-credential-proxy/nym-credential-proxy",
"nym-node",
@@ -217,6 +215,7 @@ base64 = "0.22.1"
base85rs = "0.1.3"
bincode = "1.3.3"
bip39 = { version = "2.0.0", features = ["zeroize"] }
bit-vec = "0.7.0" # can we unify those?
bitvec = "1.0.0"
blake3 = "1.7.0"
bloomfilter = "3.0.1"
@@ -244,11 +243,13 @@ criterion = "0.5"
csv = "1.3.1"
ctr = "0.9.1"
cupid = "0.6.1"
curve25519-dalek = "4.1"
dashmap = "5.5.3"
# We want https://github.com/DefGuard/wireguard-rs/pull/64 , but there's no crates.io release being pushed out anymore
defguard_wireguard_rs = { git = "https://github.com/DefGuard/wireguard-rs.git", rev = "v0.4.7" }
digest = "0.10.7"
dirs = "6.0"
doc-comment = "0.3"
dotenvy = "0.15.6"
dyn-clone = "1.0.19"
ecdsa = "0.16"
@@ -264,8 +265,11 @@ futures = "0.3.31"
futures-util = "0.3"
generic-array = "0.14.7"
getrandom = "0.2.10"
getset = "0.1.5"
handlebars = "3.5.5"
headers = "0.4.0"
hex = "0.4.3"
hex-literal = "0.3.3"
hickory-resolver = "0.25"
hkdf = "0.12.3"
hmac = "0.12.1"
@@ -289,10 +293,12 @@ lazy_static = "1.5.0"
ledger-transport = "0.10.0"
ledger-transport-hid = "0.10.0"
log = "0.4"
maxminddb = "0.23.0"
mime = "0.3.17"
moka = { version = "0.12", features = ["future"] }
nix = "0.27.1"
notify = "5.1.0"
okapi = "0.7.0"
once_cell = "1.21.3"
opentelemetry = "0.19.0"
opentelemetry-jaeger = "0.18.0"
@@ -301,6 +307,7 @@ pem = "0.8"
petgraph = "0.6.5"
pin-project = "1.1"
pnet_packet = "0.35.0"
pin-project-lite = "0.2.16"
publicsuffix = "2.3.0"
proc_pidinfo = "0.1.3"
quote = "1"
@@ -308,10 +315,13 @@ rand = "0.8.5"
rand_chacha = "0.3"
rand_core = "0.6.3"
rand_distr = "0.4"
rand_pcg = "0.3.1"
rand_seeder = "0.2.3"
rayon = "1.5.1"
regex = "1.10.6"
reqwest = { version = "0.12.15", default-features = false }
rs_merkle = "1.5.0"
safer-ffi = "0.1.13"
schemars = "0.8.22"
semver = "1.0.26"
serde = "1.0.219"
@@ -358,7 +368,6 @@ tracing-indicatif = "0.3.9"
tracing-test = "0.2.5"
ts-rs = "10.1.0"
tungstenite = { version = "0.20.1", default-features = false }
typed-builder = "0.23.0"
uniffi = "0.29.2"
uniffi_build = "0.29.0"
url = "2.5"
@@ -455,11 +464,6 @@ opt-level = 'z'
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tokio_unstable)'] }
[workspace.lints.clippy]
suspicious = "deny"
complexity = "deny"
perf = "deny"
style = "deny"
unwrap_used = "deny"
expect_used = "deny"
todo = "deny"
+4 -4
View File
@@ -107,16 +107,16 @@ sdk-wasm-build:
$(MAKE) -C nym-browser-extension/storage wasm-pack
$(MAKE) -C wasm/client
$(MAKE) -C wasm/node-tester
$(MAKE) -C wasm/mix-fetch
# $(MAKE) -C wasm/mix-fetch
$(MAKE) -C wasm/zknym-lib
# $(MAKE) -C wasm/full-nym-wasm
# run this from npm/yarn to ensure tools are in the path, e.g. yarn build:sdk from root of repo
sdk-typescript-build:
npx lerna run --scope @nymproject/sdk build --stream
npx lerna run --scope @nymproject/mix-fetch build --stream
npx lerna run --scope @nymproject/node-tester build --stream
yarn --cwd sdk/typescript/codegen/contract-clients build
# npx lerna run --scope @nymproject/mix-fetch build --stream
# npx lerna run --scope @nymproject/node-tester build --stream
# yarn --cwd sdk/typescript/codegen/contract-clients build
# NOTE: These targets are part of the main workspace (but not as wasm32-unknown-unknown)
WASM_CRATES = extension-storage nym-client-wasm nym-node-tester-wasm zknym-lib
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.66"
version = "1.1.64"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
-22
View File
@@ -1,22 +0,0 @@
[package]
name = "nym-client-rtt-tester"
version = "0.1.0"
edition = "2021"
description = "RTT testing client built using nym-client-core"
license.workspace = true
[[bin]]
name = "nym-client-rtt-tester"
path = "src/main.rs"
[dependencies]
tokio = { workspace = true, features = ["full"] }
tracing = { workspace = true }
nym-client-core = { path = "../../common/client-core" }
nym-network-defaults = { path = "../../common/network-defaults" }
nym-sphinx = { path = "../../common/nymsphinx" }
nym-topology = { path = "../../common/topology" }
nym-config = { path = "../../common/config" }
nym-task = { path = "../../common/task" }
nym-crypto = { path = "../../common/crypto" }
nym-sdk = { path = "../../sdk/rust/nym-sdk" }
-466
View File
@@ -1,466 +0,0 @@
use nym_sdk::mixnet;
use nym_sdk::mixnet::MixnetMessageSender;
use nym_client_core::client::rtt_analyzer::{RttAnalyzer, RttConfig, RttEvent, RttPattern};
use nym_sdk::DebugConfig;
use tokio::io::{self, AsyncBufReadExt};
use tokio::time::{sleep, Duration};
#[tokio::main]
async fn main() {
// ============================================================
// 1. Start RTT Analyzer + background worker
// ============================================================
let _analyzer = RttAnalyzer::new();
let tx = RttAnalyzer::producer().expect("Analyzer was not initialized!");
// ============================================================
// 2. Build mixnet client
// ============================================================
let mut debug = DebugConfig::default();
// Disable ALL Poisson & cover streams
debug.traffic.disable_main_poisson_packet_distribution = true;
debug.cover_traffic.disable_loop_cover_traffic_stream = false;
let client = mixnet::MixnetClientBuilder::new_ephemeral()
.debug_config(debug)
.build()
.unwrap();
let client = client.connect_to_mixnet().await.unwrap();
let our_address = client.nym_address();
println!("Our client nym address is: {our_address}");
// ============================================================
// 3. Ask the user for RTT TEST configuration
// ============================================================
let config = ask_user_for_rtt_config().await;
println!("\nStarting RTT test with:");
println!(" packets_per_route = {}", config.packets_per_route);
println!(" pattern = {:?}", config.pattern);
println!(" delay (ms) = {}", config.inter_route_delay_ms);
// START THE TEST
let _ = client
.send_rtt_test(*our_address, None, tx.clone(), config)
.await
.unwrap();
// ============================================================
// 4. Background listener for incoming messages
// ============================================================
tokio::spawn({
let mut client = client;
async move {
loop {
if client.wait_for_messages().await.is_some() {
//I should do something here to shutdown
}
sleep(Duration::from_millis(10)).await;
}
}
});
// ============================================================
// 5. Main input loop
// ============================================================
let stdin = io::BufReader::new(io::stdin());
let mut lines = stdin.lines();
println!("Type 'menu' to show RTT commands.");
loop {
if let Ok(Some(input)) = lines.next_line().await {
let input = input.trim().to_lowercase();
if input == "menu" {
show_menu_and_handle_choice(&tx).await;
}
}
sleep(Duration::from_millis(50)).await;
}
}
// =====================================================================
// ASK USER FOR RTT TEST SETTINGS AT PROGRAM START
// =====================================================================
async fn ask_user_for_rtt_config() -> RttConfig {
let stdin = io::BufReader::new(io::stdin());
let mut lines = stdin.lines();
println!("\n========== RTT TEST CONFIGURATION ==========");
// -----------------------------
// Ask for packets per route
// -----------------------------
println!("Enter number of packets per route: ");
let packets = read_u32_from_stdin(&mut lines).await;
// -----------------------------
// Ask for pattern: Burst / RR
// -----------------------------
println!("Choose pattern:");
println!(" 1) Burst");
println!(" 2) Round Robin");
let pattern = loop {
let input = read_string(&mut lines).await;
match input.as_str() {
"1" => break RttPattern::Burst,
"2" => break RttPattern::RoundRobin,
_ => println!("Invalid choice! Please type 1 or 2:"),
}
};
// -----------------------------
// Ask for delay between packets
// -----------------------------
println!("Enter delay between packets (ms): ");
let delay = read_u64_from_stdin(&mut lines).await;
// Build Config
RttConfig {
packets_per_route: packets,
pattern,
inter_route_delay_ms: delay,
}
}
// =====================================================================
// Util functions for reading typed input
// =====================================================================
async fn read_string(lines: &mut tokio::io::Lines<io::BufReader<io::Stdin>>) -> String {
loop {
if let Ok(Some(line)) = lines.next_line().await {
let trimmed = line.trim().to_string();
if !trimmed.is_empty() {
return trimmed;
}
}
println!("Please type a value:");
}
}
async fn read_u32_from_stdin(lines: &mut tokio::io::Lines<io::BufReader<io::Stdin>>) -> u32 {
loop {
if let Ok(Some(line)) = lines.next_line().await {
if let Ok(num) = line.trim().parse::<u32>() {
return num;
}
}
println!("Invalid number, try again:");
}
}
async fn read_u64_from_stdin(lines: &mut tokio::io::Lines<io::BufReader<io::Stdin>>) -> u64 {
loop {
if let Ok(Some(line)) = lines.next_line().await {
if let Ok(num) = line.trim().parse::<u64>() {
return num;
}
}
println!("Invalid number, try again:");
}
}
// =====================================================================
// MENU HANDLER (FULL VERSION WITH HELP / DOCS)
// =====================================================================
async fn show_menu_and_handle_choice(tx: &tokio::sync::mpsc::Sender<RttEvent>) {
println!("\n======================== RTT MENU ========================");
println!("1) Print global RTT statistics");
println!("2) Write statistics to CSV file");
println!("3) Print route details by ROUTE INDEX");
println!("4) Print route details by ROUTE NODES STRING");
println!("5) Print routes with AVG RTT above threshold");
println!("6) Print routes with ANY RTT above threshold");
println!("7) Help (Show all commands & how to use them)");
println!("8) Write CSV and generate RTT histogram(s) with Python");
println!("9) Show overall experiment completion percentage");
println!("===========================================================");
print!("Select option: ");
use std::io::Write;
std::io::stdout().flush().unwrap();
let mut input = String::new();
let _ = std::io::stdin().read_line(&mut input);
let choice = input.trim();
match choice {
// -------------------- 1. PRINT GLOBAL STATS --------------------
"1" => {
let _ = tx.send(RttEvent::PrintStats).await;
}
// -------------------- 2. WRITE STATS ---------------------------
"2" => {
print!("Enter file path: ");
std::io::stdout().flush().unwrap();
let mut path = String::new();
let _ = std::io::stdin().read_line(&mut path);
let path = path.trim().to_string();
let _ = tx.send(RttEvent::WriteStats { path }).await;
}
// -------------------- 3. PRINT ROUTE DETAILS -------------------
"3" => {
print!("Enter route index (0-based): ");
std::io::stdout().flush().unwrap();
let mut s = String::new();
let _ = std::io::stdin().read_line(&mut s);
if let Ok(index) = s.trim().parse::<usize>() {
let _ = tx
.send(RttEvent::PrintRouteDetail { route_index: index })
.await;
} else {
println!("Invalid index.");
}
}
// -------------------- 4. PRINT STATS BY NODE STRING -----------
"4" => {
println!("Enter Node String EXACTLY as stored.");
println!("Example format:");
println!(" <base58_node1> > <base58_node2> > <base58_node3>");
print!("Nodes: ");
std::io::stdout().flush().unwrap();
let mut nodes = String::new();
let _ = std::io::stdin().read_line(&mut nodes);
let nodes = nodes.trim().to_string();
let _ = tx.send(RttEvent::PrintRouteStatsByNodes { nodes }).await;
}
// -------------------- 5. AVG ABOVE THRESHOLD ------------------
"5" => {
print!("Enter threshold in ms: ");
std::io::stdout().flush().unwrap();
let mut s = String::new();
let _ = std::io::stdin().read_line(&mut s);
if let Ok(th) = s.trim().parse::<u128>() {
let _ = tx
.send(RttEvent::PrintRoutesWithAvgAbove { threshold_ms: th })
.await;
} else {
println!("Invalid number.");
}
}
// -------------------- 6. ANY ABOVE THRESHOLD ------------------
"6" => {
print!("Enter threshold in ms: ");
std::io::stdout().flush().unwrap();
let mut s = String::new();
let _ = std::io::stdin().read_line(&mut s);
if let Ok(th) = s.trim().parse::<u128>() {
let _ = tx
.send(RttEvent::PrintRoutesWithAnyAbove { threshold_ms: th })
.await;
} else {
println!("Invalid number.");
}
}
// -------------------- 7. HELP --------------------------------
"7" => {
print_help();
}
"8" => {
// Ask for CSV path
print!("Enter CSV output path (e.g. rtt_stats.csv): ");
std::io::stdout().flush().unwrap();
let mut path = String::new();
let _ = std::io::stdin().read_line(&mut path);
let path = path.trim().to_string();
// Sub-menu for histogram mode
println!("\nHistogram mode:");
println!(" 1) One plot with ALL RTT samples (including outliers)");
println!(" 2) One plot with INLIERS only (RTT <= cutoff)");
println!(" 3) TWO plots: one for INLIERS and one for OUTLIERS");
print!("Select mode: ");
std::io::stdout().flush().unwrap();
let mut mode_input = String::new();
let _ = std::io::stdin().read_line(&mut mode_input);
let mode_choice = mode_input.trim();
let outlier_mode = match mode_choice {
// 1) All RTTs
"1" => "all".to_string(),
// 2) Only inliers, ask for cutoff in seconds
"2" => {
print!("Enter cutoff in seconds (e.g. 1.0 for 1 second): ");
std::io::stdout().flush().unwrap();
let mut c = String::new();
let _ = std::io::stdin().read_line(&mut c);
let cutoff = c.trim();
cutoff.to_string() // e.g. "1.0"
}
// 3) Two plots: inliers + outliers (both)
"3" => {
print!("Enter cutoff in seconds (e.g. 1.0 for 1 second): ");
std::io::stdout().flush().unwrap();
let mut c = String::new();
let _ = std::io::stdin().read_line(&mut c);
let cutoff = c.trim();
// Encode as 'both:<cutoff>' so Python can understand it
format!("both:{cutoff}")
}
_ => {
println!("Invalid mode, aborting histogram generation.");
return;
}
};
let _ = tx
.send(RttEvent::WriteStatsAndPlot { path, outlier_mode })
.await;
}
"9" => {
// Send an event to the RTT analyzer to compute and print progress
let _ = tx.send(RttEvent::PrintExperimentProgress).await;
}
_ => println!("Invalid selection."),
}
}
fn print_help() {
println!("\n======================== RTT HELP ========================\n");
println!("This tool allows you to perform detailed RTT analysis over all mixnet routes.");
println!("The client sends RTT probe traffic through every candidate route,");
println!("and the RTT analyzer collects per-route statistics in the background.\n");
println!("Main commands (from the RTT menu):\n");
println!(" 1) Print global RTT statistics");
println!(" Prints one summary line per route:");
println!(" - route index");
println!(" - packets sent (including retransmissions)");
println!(" - number of ACKs");
println!(" - number of timeouts");
println!(" - average RTT (computed over all stored RTT samples, in ms)");
println!();
println!(" 2) Write stats to CSV file");
println!(" Writes one line per route to a CSV file on disk.");
println!(" Current CSV columns:");
println!(" route,sent,acks,timeouts,avg_rtt");
println!(" route : numeric route index");
println!(" sent : how many FragmentSent events were recorded");
println!(" acks : how many FragmentAckReceived events were recorded");
println!(" timeouts : how many FragmentAckExpired events were recorded");
println!(" avg_rtt : average RTT (in milliseconds) from all RTT samples");
println!();
println!(" 3) Print route details BY ROUTE INDEX");
println!(" Input: a 0-based route index.");
println!(" Output for that route:");
println!(" - node list (base58 identities) in order: Node1 > Node2 > Node3");
println!(" - ALL RTT samples recorded for that route (each sample shown in ms)");
println!(" This is useful when you already know the route index and");
println!(" want to inspect exactly how it behaves packet by packet.");
println!();
println!(" 4) Print route details BY NODE STRING");
println!(" Input format must match exactly what the analyzer stored, for example:");
println!(" <node1_base58> > <node2_base58> > <node3_base58>");
println!(" If a route with that node sequence exists, the tool will:");
println!(" - print the matching route index");
println!(" - print the full per-route detail (same as option 3).");
println!(" This is useful when you have a specific mixnode combination");
println!(" (e.g. a slow or suspicious path) and want its statistics.");
println!();
println!(" 5) Print routes with AVERAGE RTT ABOVE a threshold");
println!(" You provide a threshold in milliseconds (e.g. 150).");
println!(" The tool will:");
println!(" - compute avg RTT for each route");
println!(" - select only routes where avg RTT > threshold");
println!(" - print detailed info for each matching route (nodes + RTT samples).");
println!(" Use this to quickly find generally slow routes.");
println!();
println!(" 6) Print routes with ANY RTT ABOVE a threshold");
println!(" You provide a threshold in milliseconds (e.g. 500).");
println!(" For each route, if at least one RTT sample exceeds the threshold,");
println!(" that route is printed with full details.");
println!(" Use this to find routes that occasionally spike very high,");
println!(" even if their average RTT is still acceptable.");
println!();
println!(" 7) Show experiment progress (percentage completed)");
println!(" Uses the stored experiment configuration (total_routes, packets_per_route)");
println!(" plus the number of RTT samples recorded so far to estimate:");
println!(" completion = received_samples / (total_routes * packets_per_route)");
println!(" The result is printed as a percentage (0%100%).");
println!(" This tells you roughly how far the RTT experiment has progressed.");
println!();
println!(" 8) Write stats AND generate histogram(s) via Python");
println!(" This command will:");
println!(" 1) Write the current route statistics to a CSV file (same as option 2).");
println!(" 2) Call the Python script 'rtt_histogram.py' to visualize RTTs.");
println!();
println!(" When prompted, you will provide two things:");
println!(" - CSV file path (where to save the stats)");
println!(" - outlier_mode string, which controls which histograms are generated:");
println!();
println!("\"all\"");
println!(" Use ALL avg_rtt values from the CSV.");
println!(
" Result: a single histogram containing every route's avg RTT (in seconds)."
);
println!();
println!("\"<cutoff>\" (numeric, in seconds, e.g. \"1.0\")");
println!(" Only keep avg_rtt <= cutoff.");
println!(" Result: a single histogram with INLIERS only (values <= cutoff).");
println!(
" Example: \"1.0\" keeps everything at or below 1.0s and drops slower routes."
);
println!();
println!("\"both:<cutoff>\" (e.g. \"both:1.0\")");
println!(" Split the data into two sets:");
println!(" - inliers : avg_rtt <= cutoff");
println!(" - outliers : avg_rtt > cutoff");
println!(" Result: TWO histograms are generated:");
println!(" 1) Distribution of inliers");
println!(" 2) Distribution of outliers");
println!(
" This helps visually compare the \"normal\" routes and the very slow ones."
);
println!();
println!("Helpful notes:");
println!(" • RTT samples are computed when a FragmentReceived event arrives.");
println!(" For each fragment that may be retransmitted, the analyzer stores");
println!(" multiple send times and receive times, and pairs them in order");
println!(" to compute multiple RTT values for that fragment if needed.");
println!("\"sent\" in the stats includes retransmissions as well, so it may be");
println!(" higher than packets_per_route for unstable routes.");
println!("===========================================================\n");
}
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.66"
version = "1.1.64"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
edition = "2021"
-7
View File
@@ -16,7 +16,6 @@ serde = { workspace = true, features = ["derive"] }
semver = { workspace = true }
strum_macros = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }
nym-credentials-interface = { path = "../credentials-interface" }
nym-crypto = { path = "../crypto", features = ["asymmetric"] }
@@ -30,13 +29,7 @@ hmac = { workspace = true, optional = true }
sha2 = { workspace = true, optional = true }
x25519-dalek = { workspace = true, features = ["static_secrets"] }
[dev-dependencies]
nym-test-utils = { path = "../test-utils" }
[features]
default = ["verify"]
# this is moved to a separate feature as we really need clients to import it (especially, *cough*, wasm)
verify = ["hmac", "sha2"]
[lints]
workspace = true
@@ -6,11 +6,9 @@ use nym_wireguard_types::PeerPublicKey;
use crate::{
AuthenticatorVersion, Error,
traits::{
FinalMessage, InitMessage, QueryBandwidthMessage, TopUpMessage, UpgradeModeMessage,
Versionable,
},
v2, v3, v4, v5, v6,
latest::registration::IpPair,
traits::{FinalMessage, InitMessage, QueryBandwidthMessage, TopUpMessage, Versionable},
v2, v3, v4, v5,
};
// This is very redundant with AuthenticatorRequest and I reckon they could be smooshed.
@@ -21,293 +19,6 @@ pub enum ClientMessage {
Final(Box<dyn FinalMessage + Send + Sync + 'static>),
Query(Box<dyn QueryBandwidthMessage + Send + Sync + 'static>),
TopUp(Box<dyn TopUpMessage + Send + Sync + 'static>),
UpgradeModeCheck(Box<dyn UpgradeModeMessage + Send + Sync + 'static>),
}
pub struct SerialisedRequest {
pub bytes: Vec<u8>,
pub request_id: u64,
}
impl SerialisedRequest {
pub fn new(bytes: Vec<u8>, request_id: u64) -> Self {
Self { bytes, request_id }
}
}
impl ClientMessage {
fn serialise_v1(&self) -> Result<SerialisedRequest, Error> {
Err(Error::UnsupportedVersion)
}
fn serialise_v2(&self, reply_to: Recipient) -> Result<SerialisedRequest, Error> {
use v2::{
registration::{ClientMac, FinalMessage, GatewayClient, InitMessage},
request::AuthenticatorRequest,
};
match self {
ClientMessage::Initial(init_message) => {
let (req, id) = AuthenticatorRequest::new_initial_request(
InitMessage {
pub_key: init_message.pub_key(),
},
reply_to,
);
Ok(SerialisedRequest::new(req.to_bytes()?, id))
}
ClientMessage::Final(final_message) => {
let (req, id) = AuthenticatorRequest::new_final_request(
FinalMessage {
gateway_client: GatewayClient {
pub_key: final_message.gateway_client_pub_key(),
private_ip: final_message
.gateway_client_ipv4()
.ok_or(Error::UnsupportedMessage)?
.into(),
mac: ClientMac::new(final_message.gateway_client_mac()),
},
credential: final_message
.credential()
.and_then(|c| c.credential.into_zk_nym())
.map(|c| *c),
},
reply_to,
);
Ok(SerialisedRequest::new(req.to_bytes()?, id))
}
ClientMessage::Query(query_message) => {
let (req, id) =
AuthenticatorRequest::new_query_request(query_message.pub_key(), reply_to);
Ok(SerialisedRequest::new(req.to_bytes()?, id))
}
_ => Err(Error::UnsupportedMessage),
}
}
fn serialise_v3(&self, reply_to: Recipient) -> Result<SerialisedRequest, Error> {
use v3::{
registration::{ClientMac, FinalMessage, GatewayClient, InitMessage},
request::AuthenticatorRequest,
topup::TopUpMessage,
};
match self {
ClientMessage::Initial(init_message) => {
let (req, id) = AuthenticatorRequest::new_initial_request(
InitMessage {
pub_key: init_message.pub_key(),
},
reply_to,
);
Ok(SerialisedRequest::new(req.to_bytes()?, id))
}
ClientMessage::Final(final_message) => {
let (req, id) = AuthenticatorRequest::new_final_request(
FinalMessage {
gateway_client: GatewayClient {
pub_key: final_message.gateway_client_pub_key(),
private_ip: final_message
.gateway_client_ipv4()
.ok_or(Error::UnsupportedMessage)?
.into(),
mac: ClientMac::new(final_message.gateway_client_mac()),
},
credential: final_message
.credential()
.and_then(|c| c.credential.into_zk_nym())
.map(|c| *c),
},
reply_to,
);
Ok(SerialisedRequest::new(req.to_bytes()?, id))
}
ClientMessage::Query(query_message) => {
let (req, id) =
AuthenticatorRequest::new_query_request(query_message.pub_key(), reply_to);
Ok(SerialisedRequest::new(req.to_bytes()?, id))
}
ClientMessage::TopUp(top_up_message) => {
let (req, id) = AuthenticatorRequest::new_topup_request(
TopUpMessage {
pub_key: top_up_message.pub_key(),
credential: top_up_message.credential(),
},
reply_to,
);
Ok(SerialisedRequest::new(req.to_bytes()?, id))
}
_ => Err(Error::UnsupportedMessage),
}
}
fn serialise_v4(&self, reply_to: Recipient) -> Result<SerialisedRequest, Error> {
use v4::{
registration::{ClientMac, FinalMessage, GatewayClient, InitMessage, IpPair},
request::AuthenticatorRequest,
topup::TopUpMessage,
};
match self {
ClientMessage::Initial(init_message) => {
let (req, id) = AuthenticatorRequest::new_initial_request(
InitMessage {
pub_key: init_message.pub_key(),
},
reply_to,
);
Ok(SerialisedRequest::new(req.to_bytes()?, id))
}
ClientMessage::Final(final_message) => {
let (req, id) = AuthenticatorRequest::new_final_request(
FinalMessage {
gateway_client: GatewayClient {
pub_key: final_message.gateway_client_pub_key(),
private_ips: IpPair {
ipv4: final_message
.gateway_client_ipv4()
.ok_or(Error::UnsupportedMessage)?,
ipv6: final_message
.gateway_client_ipv6()
.ok_or(Error::UnsupportedMessage)?,
},
mac: ClientMac::new(final_message.gateway_client_mac()),
},
credential: final_message
.credential()
.and_then(|c| c.credential.into_zk_nym())
.map(|c| *c),
},
reply_to,
);
Ok(SerialisedRequest::new(req.to_bytes()?, id))
}
ClientMessage::Query(query_message) => {
let (req, id) =
AuthenticatorRequest::new_query_request(query_message.pub_key(), reply_to);
Ok(SerialisedRequest::new(req.to_bytes()?, id))
}
ClientMessage::TopUp(top_up_message) => {
let (req, id) = AuthenticatorRequest::new_topup_request(
TopUpMessage {
pub_key: top_up_message.pub_key(),
credential: top_up_message.credential(),
},
reply_to,
);
Ok(SerialisedRequest::new(req.to_bytes()?, id))
}
_ => Err(Error::UnsupportedMessage),
}
}
fn serialise_v5(&self) -> Result<SerialisedRequest, Error> {
use v5::{
registration::{ClientMac, FinalMessage, GatewayClient, InitMessage, IpPair},
request::AuthenticatorRequest,
topup::TopUpMessage,
};
match self {
ClientMessage::Initial(init_message) => {
let (req, id) = AuthenticatorRequest::new_initial_request(InitMessage {
pub_key: init_message.pub_key(),
});
Ok(SerialisedRequest::new(req.to_bytes()?, id))
}
ClientMessage::Final(final_message) => {
let (req, id) = AuthenticatorRequest::new_final_request(FinalMessage {
gateway_client: GatewayClient {
pub_key: final_message.gateway_client_pub_key(),
private_ips: IpPair {
ipv4: final_message
.gateway_client_ipv4()
.ok_or(Error::UnsupportedMessage)?,
ipv6: final_message
.gateway_client_ipv6()
.ok_or(Error::UnsupportedMessage)?,
},
mac: ClientMac::new(final_message.gateway_client_mac()),
},
credential: final_message
.credential()
.and_then(|c| c.credential.into_zk_nym())
.map(|c| *c),
});
Ok(SerialisedRequest::new(req.to_bytes()?, id))
}
ClientMessage::Query(query_message) => {
let (req, id) = AuthenticatorRequest::new_query_request(query_message.pub_key());
Ok(SerialisedRequest::new(req.to_bytes()?, id))
}
ClientMessage::TopUp(top_up_message) => {
let (req, id) = AuthenticatorRequest::new_topup_request(TopUpMessage {
pub_key: top_up_message.pub_key(),
credential: top_up_message.credential(),
});
Ok(SerialisedRequest::new(req.to_bytes()?, id))
}
_ => Err(Error::UnsupportedMessage),
}
}
fn serialise_v6(&self) -> Result<SerialisedRequest, Error> {
use v6::{
registration::{ClientMac, FinalMessage, GatewayClient, InitMessage, IpPair},
request::AuthenticatorRequest,
topup::TopUpMessage,
upgrade_mode_check::UpgradeModeCheckRequest,
};
match self {
ClientMessage::Initial(init_message) => {
let (req, id) = AuthenticatorRequest::new_initial_request(InitMessage {
pub_key: init_message.pub_key(),
});
Ok(SerialisedRequest::new(req.to_bytes()?, id))
}
ClientMessage::Final(final_message) => {
let (req, id) = AuthenticatorRequest::new_final_request(FinalMessage {
gateway_client: GatewayClient {
pub_key: final_message.gateway_client_pub_key(),
private_ips: IpPair {
ipv4: final_message
.gateway_client_ipv4()
.ok_or(Error::UnsupportedMessage)?,
ipv6: final_message
.gateway_client_ipv6()
.ok_or(Error::UnsupportedMessage)?,
},
mac: ClientMac::new(final_message.gateway_client_mac()),
},
credential: final_message.credential(),
});
Ok(SerialisedRequest::new(req.to_bytes()?, id))
}
ClientMessage::Query(query_message) => {
let (req, id) = AuthenticatorRequest::new_query_request(query_message.pub_key());
Ok(SerialisedRequest::new(req.to_bytes()?, id))
}
ClientMessage::TopUp(top_up_message) => {
let (req, id) = AuthenticatorRequest::new_topup_request(TopUpMessage {
pub_key: top_up_message.pub_key(),
credential: top_up_message.credential(),
});
Ok(SerialisedRequest::new(req.to_bytes()?, id))
}
ClientMessage::UpgradeModeCheck(upgrade_mode_check) => {
// currently JWT is the only emergency credential option
let Some(upgrade_mode_jwt) =
upgrade_mode_check.upgrade_mode_global_attestation_jwt()
else {
return Err(Error::conversion(
"no valid known upgrade mode check variants",
));
};
let msg = UpgradeModeCheckRequest::UpgradeModeJwt {
token: upgrade_mode_jwt,
};
let (req, id) = AuthenticatorRequest::new_upgrade_mode_check_request(msg);
Ok(SerialisedRequest::new(req.to_bytes()?, id))
}
}
}
}
impl ClientMessage {
@@ -316,7 +27,7 @@ impl ClientMessage {
match self {
Self::Final(msg) => msg.credential().is_some(),
Self::TopUp(_) => true,
Self::Initial(_) | Self::Query(_) | Self::UpgradeModeCheck(_) => false,
Self::Initial(_) | Self::Query(_) => false,
}
}
@@ -326,18 +37,208 @@ impl ClientMessage {
ClientMessage::Final(msg) => msg.version(),
ClientMessage::Query(msg) => msg.version(),
ClientMessage::TopUp(msg) => msg.version(),
ClientMessage::UpgradeModeCheck(msg) => msg.version(),
}
}
pub fn bytes(&self, reply_to: Recipient) -> Result<SerialisedRequest, Error> {
pub fn bytes(&self, reply_to: Recipient) -> Result<(Vec<u8>, u64), Error> {
match self.version() {
AuthenticatorVersion::V1 => self.serialise_v1(),
AuthenticatorVersion::V2 => self.serialise_v2(reply_to),
AuthenticatorVersion::V3 => self.serialise_v3(reply_to),
AuthenticatorVersion::V4 => self.serialise_v4(reply_to),
AuthenticatorVersion::V5 => self.serialise_v5(),
AuthenticatorVersion::V6 => self.serialise_v6(),
AuthenticatorVersion::V1 => Err(Error::UnsupportedVersion),
AuthenticatorVersion::V2 => {
use v2::{
registration::{ClientMac, FinalMessage, GatewayClient, InitMessage},
request::AuthenticatorRequest,
};
match self {
ClientMessage::Initial(init_message) => {
let (req, id) = AuthenticatorRequest::new_initial_request(
InitMessage {
pub_key: init_message.pub_key(),
},
reply_to,
);
Ok((req.to_bytes()?, id))
}
ClientMessage::Final(final_message) => {
let (req, id) = AuthenticatorRequest::new_final_request(
FinalMessage {
gateway_client: GatewayClient {
pub_key: final_message.gateway_client_pub_key(),
private_ip: final_message
.gateway_client_ipv4()
.ok_or(Error::UnsupportedMessage)?
.into(),
mac: ClientMac::new(final_message.gateway_client_mac()),
},
credential: final_message.credential(),
},
reply_to,
);
Ok((req.to_bytes()?, id))
}
ClientMessage::Query(query_message) => {
let (req, id) = AuthenticatorRequest::new_query_request(
query_message.pub_key(),
reply_to,
);
Ok((req.to_bytes()?, id))
}
_ => Err(Error::UnsupportedMessage),
}
}
AuthenticatorVersion::V3 => {
use v3::{
registration::{ClientMac, FinalMessage, GatewayClient, InitMessage},
request::AuthenticatorRequest,
topup::TopUpMessage,
};
match self {
ClientMessage::Initial(init_message) => {
let (req, id) = AuthenticatorRequest::new_initial_request(
InitMessage {
pub_key: init_message.pub_key(),
},
reply_to,
);
Ok((req.to_bytes()?, id))
}
ClientMessage::Final(final_message) => {
let (req, id) = AuthenticatorRequest::new_final_request(
FinalMessage {
gateway_client: GatewayClient {
pub_key: final_message.gateway_client_pub_key(),
private_ip: final_message
.gateway_client_ipv4()
.ok_or(Error::UnsupportedMessage)?
.into(),
mac: ClientMac::new(final_message.gateway_client_mac()),
},
credential: final_message.credential(),
},
reply_to,
);
Ok((req.to_bytes()?, id))
}
ClientMessage::Query(query_message) => {
let (req, id) = AuthenticatorRequest::new_query_request(
query_message.pub_key(),
reply_to,
);
Ok((req.to_bytes()?, id))
}
ClientMessage::TopUp(top_up_message) => {
let (req, id) = AuthenticatorRequest::new_topup_request(
TopUpMessage {
pub_key: top_up_message.pub_key(),
credential: top_up_message.credential(),
},
reply_to,
);
Ok((req.to_bytes()?, id))
}
}
}
AuthenticatorVersion::V4 => {
use v4::{
registration::{ClientMac, FinalMessage, GatewayClient, InitMessage},
request::AuthenticatorRequest,
topup::TopUpMessage,
};
match self {
ClientMessage::Initial(init_message) => {
let (req, id) = AuthenticatorRequest::new_initial_request(
InitMessage {
pub_key: init_message.pub_key(),
},
reply_to,
);
Ok((req.to_bytes()?, id))
}
ClientMessage::Final(final_message) => {
let (req, id) = AuthenticatorRequest::new_final_request(
FinalMessage {
gateway_client: GatewayClient {
pub_key: final_message.gateway_client_pub_key(),
private_ips: IpPair {
ipv4: final_message
.gateway_client_ipv4()
.ok_or(Error::UnsupportedMessage)?,
ipv6: final_message
.gateway_client_ipv6()
.ok_or(Error::UnsupportedMessage)?,
}
.into(),
mac: ClientMac::new(final_message.gateway_client_mac()),
},
credential: final_message.credential(),
},
reply_to,
);
Ok((req.to_bytes()?, id))
}
ClientMessage::Query(query_message) => {
let (req, id) = AuthenticatorRequest::new_query_request(
query_message.pub_key(),
reply_to,
);
Ok((req.to_bytes()?, id))
}
ClientMessage::TopUp(top_up_message) => {
let (req, id) = AuthenticatorRequest::new_topup_request(
TopUpMessage {
pub_key: top_up_message.pub_key(),
credential: top_up_message.credential(),
},
reply_to,
);
Ok((req.to_bytes()?, id))
}
}
}
AuthenticatorVersion::V5 => {
use v5::{
registration::{ClientMac, FinalMessage, GatewayClient, InitMessage},
request::AuthenticatorRequest,
topup::TopUpMessage,
};
match self {
ClientMessage::Initial(init_message) => {
let (req, id) = AuthenticatorRequest::new_initial_request(InitMessage {
pub_key: init_message.pub_key(),
});
Ok((req.to_bytes()?, id))
}
ClientMessage::Final(final_message) => {
let (req, id) = AuthenticatorRequest::new_final_request(FinalMessage {
gateway_client: GatewayClient {
pub_key: final_message.gateway_client_pub_key(),
private_ips: IpPair {
ipv4: final_message
.gateway_client_ipv4()
.ok_or(Error::UnsupportedMessage)?,
ipv6: final_message
.gateway_client_ipv6()
.ok_or(Error::UnsupportedMessage)?,
},
mac: ClientMac::new(final_message.gateway_client_mac()),
},
credential: final_message.credential(),
});
Ok((req.to_bytes()?, id))
}
ClientMessage::Query(query_message) => {
let (req, id) =
AuthenticatorRequest::new_query_request(query_message.pub_key());
Ok((req.to_bytes()?, id))
}
ClientMessage::TopUp(top_up_message) => {
let (req, id) = AuthenticatorRequest::new_topup_request(TopUpMessage {
pub_key: top_up_message.pub_key(),
credential: top_up_message.credential(),
});
Ok((req.to_bytes()?, id))
}
}
}
AuthenticatorVersion::UNKNOWN => Err(Error::UnknownVersion),
}
}
@@ -346,7 +247,7 @@ impl ClientMessage {
use AuthenticatorVersion::*;
match self.version() {
V1 | V2 | V3 | V4 => false,
V5 | V6 => true,
V5 => true,
UNKNOWN => true,
}
}
@@ -1,7 +1,6 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::fmt::Display;
use thiserror::Error;
#[derive(Debug, Error)]
@@ -38,13 +37,3 @@ pub enum Error {
#[error(transparent)]
Bincode(#[from] bincode::Error),
}
impl Error {
pub fn conversion(msg: impl Into<String>) -> Self {
Error::Conversion(msg.into())
}
pub fn conversion_display(msg: impl Display) -> Self {
Error::Conversion(msg.to_string())
}
}
+1 -3
View File
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: Apache-2.0
pub mod client_message;
pub mod models;
pub mod request;
pub mod response;
pub mod traits;
@@ -11,14 +10,13 @@ pub mod v2;
pub mod v3;
pub mod v4;
pub mod v5;
pub mod v6;
mod error;
mod util;
mod version;
pub use error::Error;
pub use v6 as latest;
pub use v5 as latest;
pub use version::AuthenticatorVersion;
pub const CURRENT_VERSION: u8 = latest::VERSION;
@@ -1,58 +0,0 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_credentials_interface::{
BandwidthCredential, CredentialSpendingData, TicketType, UnknownTicketType,
};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
pub enum CurrentUpgradeModeStatus {
Enabled,
Disabled,
// everything pre-v6
Unknown,
}
impl CurrentUpgradeModeStatus {
pub fn is_enabled(&self) -> bool {
matches!(self, CurrentUpgradeModeStatus::Enabled)
}
}
impl From<bool> for CurrentUpgradeModeStatus {
fn from(value: bool) -> Self {
if value {
CurrentUpgradeModeStatus::Enabled
} else {
CurrentUpgradeModeStatus::Disabled
}
}
}
impl From<CurrentUpgradeModeStatus> for Option<bool> {
fn from(value: CurrentUpgradeModeStatus) -> Self {
match value {
CurrentUpgradeModeStatus::Enabled => Some(true),
CurrentUpgradeModeStatus::Disabled => Some(false),
CurrentUpgradeModeStatus::Unknown => None,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct BandwidthClaim {
pub credential: BandwidthCredential,
pub kind: TicketType,
}
impl TryFrom<CredentialSpendingData> for BandwidthClaim {
type Error = UnknownTicketType;
fn try_from(credential: CredentialSpendingData) -> Result<Self, Self::Error> {
Ok(BandwidthClaim {
kind: TicketType::try_from_encoded(credential.payment.t_type)?,
credential: BandwidthCredential::from(credential),
})
}
}
+2 -51
View File
@@ -4,10 +4,8 @@
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
use nym_sphinx::addressing::Recipient;
use crate::traits::{
FinalMessage, InitMessage, QueryBandwidthMessage, TopUpMessage, UpgradeModeMessage,
};
use crate::{v1, v2, v3, v4, v5, v6};
use crate::traits::{FinalMessage, InitMessage, QueryBandwidthMessage, TopUpMessage};
use crate::{v1, v2, v3, v4, v5};
#[derive(Debug)]
pub enum AuthenticatorRequest {
@@ -35,11 +33,6 @@ pub enum AuthenticatorRequest {
reply_to: Option<Recipient>,
request_id: u64,
},
CheckUpgradeMode {
msg: Box<dyn UpgradeModeMessage + Send + Sync + 'static>,
protocol: Protocol,
request_id: u64,
},
}
impl From<v1::request::AuthenticatorRequest> for AuthenticatorRequest {
@@ -209,45 +202,3 @@ impl From<v5::request::AuthenticatorRequest> for AuthenticatorRequest {
}
}
}
impl From<v6::request::AuthenticatorRequest> for AuthenticatorRequest {
fn from(value: v6::request::AuthenticatorRequest) -> Self {
match value.data {
v6::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
msg: Box::new(init_message),
protocol: value.protocol,
reply_to: None,
request_id: value.request_id,
},
v6::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
msg: final_message,
protocol: value.protocol,
reply_to: None,
request_id: value.request_id,
},
v6::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
Self::QueryBandwidth {
msg: Box::new(peer_public_key),
protocol: value.protocol,
reply_to: None,
request_id: value.request_id,
}
}
v6::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message) => {
Self::TopUpBandwidth {
msg: top_up_message,
protocol: value.protocol,
reply_to: None,
request_id: value.request_id,
}
}
v6::request::AuthenticatorRequestData::CheckUpgradeMode(upgrade_mode_check_msg) => {
Self::CheckUpgradeMode {
msg: Box::new(upgrade_mode_check_msg),
protocol: value.protocol,
request_id: value.request_id,
}
}
}
}
}
+2 -49
View File
@@ -1,12 +1,11 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::models::CurrentUpgradeModeStatus;
use crate::traits::{
Id, PendingRegistrationResponse, RegisteredResponse, RemainingBandwidthResponse,
TopUpBandwidthResponse, UpgradeModeStatus,
TopUpBandwidthResponse,
};
use crate::{v2, v3, v4, v5, v6};
use crate::{v2, v3, v4, v5};
#[derive(Debug)]
pub enum AuthenticatorResponse {
@@ -14,29 +13,6 @@ pub enum AuthenticatorResponse {
Registered(Box<dyn RegisteredResponse + Send + Sync + 'static>),
RemainingBandwidth(Box<dyn RemainingBandwidthResponse + Send + Sync + 'static>),
TopUpBandwidth(Box<dyn TopUpBandwidthResponse + Send + Sync + 'static>),
UpgradeMode(Box<dyn UpgradeModeStatus + Send + Sync + 'static>),
}
impl UpgradeModeStatus for AuthenticatorResponse {
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
match self {
AuthenticatorResponse::PendingRegistration(pending_registration_response) => {
pending_registration_response.upgrade_mode_status()
}
AuthenticatorResponse::Registered(registered_response) => {
registered_response.upgrade_mode_status()
}
AuthenticatorResponse::RemainingBandwidth(remaining_bandwidth_response) => {
remaining_bandwidth_response.upgrade_mode_status()
}
AuthenticatorResponse::TopUpBandwidth(top_up_bandwidth_response) => {
top_up_bandwidth_response.upgrade_mode_status()
}
AuthenticatorResponse::UpgradeMode(upgrade_mode_response) => {
upgrade_mode_response.upgrade_mode_status()
}
}
}
}
impl Id for AuthenticatorResponse {
@@ -52,7 +28,6 @@ impl Id for AuthenticatorResponse {
AuthenticatorResponse::TopUpBandwidth(top_up_bandwidth_response) => {
top_up_bandwidth_response.id()
}
AuthenticatorResponse::UpgradeMode(upgrade_mode_response) => upgrade_mode_response.id(),
}
}
}
@@ -129,25 +104,3 @@ impl From<v5::response::AuthenticatorResponse> for AuthenticatorResponse {
}
}
}
impl From<v6::response::AuthenticatorResponse> for AuthenticatorResponse {
fn from(value: v6::response::AuthenticatorResponse) -> Self {
match value.data {
v6::response::AuthenticatorResponseData::PendingRegistration(
pending_registration_response,
) => Self::PendingRegistration(Box::new(pending_registration_response)),
v6::response::AuthenticatorResponseData::Registered(registered_response) => {
Self::Registered(Box::new(registered_response))
}
v6::response::AuthenticatorResponseData::RemainingBandwidth(
remaining_bandwidth_response,
) => Self::RemainingBandwidth(Box::new(remaining_bandwidth_response)),
v6::response::AuthenticatorResponseData::TopUpBandwidth(top_up_bandwidth_response) => {
Self::TopUpBandwidth(Box::new(top_up_bandwidth_response))
}
v6::response::AuthenticatorResponseData::UpgradeMode(upgrade_mode_check_response) => {
Self::UpgradeMode(Box::new(upgrade_mode_check_response))
}
}
}
}
+34 -456
View File
@@ -1,15 +1,15 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::latest::registration::IpPair;
use crate::models::{BandwidthClaim, CurrentUpgradeModeStatus};
use crate::{AuthenticatorVersion, Error, v1, v2, v3, v4, v5, v6};
use nym_credentials_interface::CredentialSpendingData;
use nym_crypto::asymmetric::x25519;
use nym_wireguard_types::PeerPublicKey;
use std::fmt;
use std::net::{Ipv4Addr, Ipv6Addr};
use tracing::error;
use nym_credentials_interface::CredentialSpendingData;
use nym_crypto::asymmetric::x25519::PrivateKey;
use nym_wireguard_types::PeerPublicKey;
use crate::latest::registration::IpPair;
use crate::{AuthenticatorVersion, Error, v1, v2, v3, v4, v5};
pub trait Versionable {
fn version(&self) -> AuthenticatorVersion;
@@ -51,12 +51,6 @@ impl Versionable for v5::registration::InitMessage {
}
}
impl Versionable for v6::registration::InitMessage {
fn version(&self) -> AuthenticatorVersion {
AuthenticatorVersion::V6
}
}
impl Versionable for v2::registration::FinalMessage {
fn version(&self) -> AuthenticatorVersion {
AuthenticatorVersion::V2
@@ -81,12 +75,6 @@ impl Versionable for v5::registration::FinalMessage {
}
}
impl Versionable for v6::registration::FinalMessage {
fn version(&self) -> AuthenticatorVersion {
AuthenticatorVersion::V6
}
}
impl Versionable for PeerPublicKey {
fn version(&self) -> AuthenticatorVersion {
AuthenticatorVersion::V3
@@ -110,158 +98,6 @@ impl Versionable for v5::topup::TopUpMessage {
AuthenticatorVersion::V5
}
}
impl Versionable for v6::topup::TopUpMessage {
fn version(&self) -> AuthenticatorVersion {
AuthenticatorVersion::V6
}
}
impl Versionable for v6::upgrade_mode_check::UpgradeModeCheckRequest {
fn version(&self) -> AuthenticatorVersion {
AuthenticatorVersion::V6
}
}
pub trait UpgradeModeStatus: Id + fmt::Debug {
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus;
}
impl UpgradeModeStatus for v1::response::PendingRegistrationResponse {
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
CurrentUpgradeModeStatus::Unknown
}
}
impl UpgradeModeStatus for v1::response::RegisteredResponse {
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
CurrentUpgradeModeStatus::Unknown
}
}
impl UpgradeModeStatus for v1::response::RemainingBandwidthResponse {
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
CurrentUpgradeModeStatus::Unknown
}
}
impl UpgradeModeStatus for v2::response::PendingRegistrationResponse {
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
CurrentUpgradeModeStatus::Unknown
}
}
impl UpgradeModeStatus for v2::response::RegisteredResponse {
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
CurrentUpgradeModeStatus::Unknown
}
}
impl UpgradeModeStatus for v2::response::RemainingBandwidthResponse {
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
CurrentUpgradeModeStatus::Unknown
}
}
impl UpgradeModeStatus for v3::response::PendingRegistrationResponse {
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
CurrentUpgradeModeStatus::Unknown
}
}
impl UpgradeModeStatus for v3::response::RegisteredResponse {
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
CurrentUpgradeModeStatus::Unknown
}
}
impl UpgradeModeStatus for v3::response::RemainingBandwidthResponse {
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
CurrentUpgradeModeStatus::Unknown
}
}
impl UpgradeModeStatus for v3::response::TopUpBandwidthResponse {
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
CurrentUpgradeModeStatus::Unknown
}
}
impl UpgradeModeStatus for v4::response::PendingRegistrationResponse {
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
CurrentUpgradeModeStatus::Unknown
}
}
impl UpgradeModeStatus for v4::response::RegisteredResponse {
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
CurrentUpgradeModeStatus::Unknown
}
}
impl UpgradeModeStatus for v4::response::RemainingBandwidthResponse {
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
CurrentUpgradeModeStatus::Unknown
}
}
impl UpgradeModeStatus for v4::response::TopUpBandwidthResponse {
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
CurrentUpgradeModeStatus::Unknown
}
}
impl UpgradeModeStatus for v5::response::PendingRegistrationResponse {
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
CurrentUpgradeModeStatus::Unknown
}
}
impl UpgradeModeStatus for v5::response::RegisteredResponse {
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
CurrentUpgradeModeStatus::Unknown
}
}
impl UpgradeModeStatus for v5::response::RemainingBandwidthResponse {
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
CurrentUpgradeModeStatus::Unknown
}
}
impl UpgradeModeStatus for v5::response::TopUpBandwidthResponse {
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
CurrentUpgradeModeStatus::Unknown
}
}
impl UpgradeModeStatus for v6::response::PendingRegistrationResponse {
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
self.upgrade_mode_enabled.into()
}
}
impl UpgradeModeStatus for v6::response::RegisteredResponse {
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
self.upgrade_mode_enabled.into()
}
}
impl UpgradeModeStatus for v6::response::RemainingBandwidthResponse {
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
self.upgrade_mode_enabled.into()
}
}
impl UpgradeModeStatus for v6::response::TopUpBandwidthResponse {
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
self.upgrade_mode_enabled.into()
}
}
impl UpgradeModeStatus for v6::response::UpgradeModeResponse {
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
self.upgrade_mode_enabled.into()
}
}
pub trait InitMessage: Versionable + fmt::Debug {
fn pub_key(&self) -> PeerPublicKey;
@@ -297,20 +133,14 @@ impl InitMessage for v5::registration::InitMessage {
}
}
impl InitMessage for v6::registration::InitMessage {
fn pub_key(&self) -> PeerPublicKey {
self.pub_key
}
}
pub trait FinalMessage: Versionable + fmt::Debug {
fn gateway_client_pub_key(&self) -> PeerPublicKey;
fn verify(&self, private_key: &x25519::PrivateKey, nonce: u64) -> Result<(), Error>;
fn verify(&self, private_key: &PrivateKey, nonce: u64) -> Result<(), Error>;
fn private_ips(&self) -> IpPair;
fn gateway_client_ipv4(&self) -> Option<Ipv4Addr>;
fn gateway_client_ipv6(&self) -> Option<Ipv6Addr>;
fn gateway_client_mac(&self) -> Vec<u8>;
fn credential(&self) -> Option<BandwidthClaim>;
fn credential(&self) -> Option<CredentialSpendingData>;
}
impl FinalMessage for v1::GatewayClient {
@@ -318,7 +148,7 @@ impl FinalMessage for v1::GatewayClient {
self.pub_key
}
fn verify(&self, private_key: &x25519::PrivateKey, nonce: u64) -> Result<(), Error> {
fn verify(&self, private_key: &PrivateKey, nonce: u64) -> Result<(), Error> {
self.verify(private_key, nonce)
}
@@ -341,7 +171,7 @@ impl FinalMessage for v1::GatewayClient {
self.mac.to_vec()
}
fn credential(&self) -> Option<BandwidthClaim> {
fn credential(&self) -> Option<CredentialSpendingData> {
None
}
}
@@ -351,7 +181,7 @@ impl FinalMessage for v2::registration::FinalMessage {
self.gateway_client.pub_key
}
fn verify(&self, private_key: &x25519::PrivateKey, nonce: u64) -> Result<(), Error> {
fn verify(&self, private_key: &PrivateKey, nonce: u64) -> Result<(), Error> {
self.gateway_client.verify(private_key, nonce)
}
@@ -374,12 +204,8 @@ impl FinalMessage for v2::registration::FinalMessage {
self.gateway_client.mac.to_vec()
}
fn credential(&self) -> Option<BandwidthClaim> {
self.credential.clone().and_then(|c| {
c.try_into()
.inspect_err(|err| error!("credential conversion error: {err}"))
.ok()
})
fn credential(&self) -> Option<CredentialSpendingData> {
self.credential.clone()
}
}
@@ -388,7 +214,7 @@ impl FinalMessage for v3::registration::FinalMessage {
self.gateway_client.pub_key
}
fn verify(&self, private_key: &x25519::PrivateKey, nonce: u64) -> Result<(), Error> {
fn verify(&self, private_key: &PrivateKey, nonce: u64) -> Result<(), Error> {
self.gateway_client.verify(private_key, nonce)
}
@@ -411,12 +237,8 @@ impl FinalMessage for v3::registration::FinalMessage {
self.gateway_client.mac.to_vec()
}
fn credential(&self) -> Option<BandwidthClaim> {
self.credential.clone().and_then(|c| {
c.try_into()
.inspect_err(|err| error!("credential conversion error: {err}"))
.ok()
})
fn credential(&self) -> Option<CredentialSpendingData> {
self.credential.clone()
}
}
@@ -425,42 +247,7 @@ impl FinalMessage for v4::registration::FinalMessage {
self.gateway_client.pub_key
}
fn verify(&self, private_key: &x25519::PrivateKey, nonce: u64) -> Result<(), Error> {
self.gateway_client.verify(private_key, nonce)
}
fn private_ips(&self) -> IpPair {
// v4 -> v5 -> v6
v5::registration::IpPair::from(self.gateway_client.private_ips).into()
}
fn gateway_client_ipv4(&self) -> Option<Ipv4Addr> {
Some(self.gateway_client.private_ips.ipv4)
}
fn gateway_client_ipv6(&self) -> Option<Ipv6Addr> {
Some(self.gateway_client.private_ips.ipv6)
}
fn gateway_client_mac(&self) -> Vec<u8> {
self.gateway_client.mac.to_vec()
}
fn credential(&self) -> Option<BandwidthClaim> {
self.credential.clone().and_then(|c| {
c.try_into()
.inspect_err(|err| error!("credential conversion error: {err}"))
.ok()
})
}
}
impl FinalMessage for v5::registration::FinalMessage {
fn gateway_client_pub_key(&self) -> PeerPublicKey {
self.gateway_client.pub_key
}
fn verify(&self, private_key: &x25519::PrivateKey, nonce: u64) -> Result<(), Error> {
fn verify(&self, private_key: &PrivateKey, nonce: u64) -> Result<(), Error> {
self.gateway_client.verify(private_key, nonce)
}
@@ -480,21 +267,17 @@ impl FinalMessage for v5::registration::FinalMessage {
self.gateway_client.mac.to_vec()
}
fn credential(&self) -> Option<BandwidthClaim> {
self.credential.clone().and_then(|c| {
c.try_into()
.inspect_err(|err| error!("credential conversion error: {err}"))
.ok()
})
fn credential(&self) -> Option<CredentialSpendingData> {
self.credential.clone()
}
}
impl FinalMessage for v6::registration::FinalMessage {
impl FinalMessage for v5::registration::FinalMessage {
fn gateway_client_pub_key(&self) -> PeerPublicKey {
self.gateway_client.pub_key
}
fn verify(&self, private_key: &x25519::PrivateKey, nonce: u64) -> Result<(), Error> {
fn verify(&self, private_key: &PrivateKey, nonce: u64) -> Result<(), Error> {
self.gateway_client.verify(private_key, nonce)
}
@@ -514,7 +297,7 @@ impl FinalMessage for v6::registration::FinalMessage {
self.gateway_client.mac.to_vec()
}
fn credential(&self) -> Option<BandwidthClaim> {
fn credential(&self) -> Option<CredentialSpendingData> {
self.credential.clone()
}
}
@@ -564,42 +347,10 @@ impl TopUpMessage for v5::topup::TopUpMessage {
}
}
impl TopUpMessage for v6::topup::TopUpMessage {
fn pub_key(&self) -> PeerPublicKey {
self.pub_key
}
fn credential(&self) -> CredentialSpendingData {
self.credential.clone()
}
}
pub trait UpgradeModeMessage: Versionable + fmt::Debug {
// the idea is to expose different types of emergency credentials here,
// like upgrade mode JWT, emergency threshold credential issued by signers, etc.
fn upgrade_mode_global_attestation_jwt(&self) -> Option<String>;
}
impl UpgradeModeMessage for v6::upgrade_mode_check::UpgradeModeCheckRequest {
fn upgrade_mode_global_attestation_jwt(&self) -> Option<String> {
use v6::upgrade_mode_check::UpgradeModeCheckRequest;
match self {
UpgradeModeCheckRequest::UpgradeModeJwt { token } => Some(token.clone()),
}
}
}
pub trait Id {
fn id(&self) -> u64;
}
impl Id for v1::response::PendingRegistrationResponse {
fn id(&self) -> u64 {
self.request_id
}
}
impl Id for v2::response::PendingRegistrationResponse {
fn id(&self) -> u64 {
self.request_id
@@ -624,18 +375,6 @@ impl Id for v5::response::PendingRegistrationResponse {
}
}
impl Id for v6::response::PendingRegistrationResponse {
fn id(&self) -> u64 {
self.request_id
}
}
impl Id for v1::response::RegisteredResponse {
fn id(&self) -> u64 {
self.request_id
}
}
impl Id for v2::response::RegisteredResponse {
fn id(&self) -> u64 {
self.request_id
@@ -660,18 +399,6 @@ impl Id for v5::response::RegisteredResponse {
}
}
impl Id for v6::response::RegisteredResponse {
fn id(&self) -> u64 {
self.request_id
}
}
impl Id for v1::response::RemainingBandwidthResponse {
fn id(&self) -> u64 {
self.request_id
}
}
impl Id for v2::response::RemainingBandwidthResponse {
fn id(&self) -> u64 {
self.request_id
@@ -696,12 +423,6 @@ impl Id for v5::response::RemainingBandwidthResponse {
}
}
impl Id for v6::response::RemainingBandwidthResponse {
fn id(&self) -> u64 {
self.request_id
}
}
impl Id for v3::response::TopUpBandwidthResponse {
fn id(&self) -> u64 {
self.request_id
@@ -720,28 +441,11 @@ impl Id for v5::response::TopUpBandwidthResponse {
}
}
impl Id for v6::response::TopUpBandwidthResponse {
fn id(&self) -> u64 {
self.request_id
}
}
impl Id for v6::response::UpgradeModeResponse {
fn id(&self) -> u64 {
self.request_id
}
}
pub trait PendingRegistrationResponse: Id + UpgradeModeStatus + fmt::Debug {
pub trait PendingRegistrationResponse: Id + fmt::Debug {
fn nonce(&self) -> u64;
fn verify(&self, gateway_key: &x25519::PrivateKey) -> Result<(), Error>;
fn verify(&self, gateway_key: &PrivateKey) -> std::result::Result<(), Error>;
fn pub_key(&self) -> PeerPublicKey;
fn private_ips(&self) -> IpPair;
fn finalise_registration(
&self,
private_key: &x25519::PrivateKey,
credential: Option<BandwidthClaim>,
) -> Box<dyn FinalMessage + Send + Sync>;
}
impl PendingRegistrationResponse for v2::response::PendingRegistrationResponse {
@@ -749,7 +453,7 @@ impl PendingRegistrationResponse for v2::response::PendingRegistrationResponse {
self.reply.nonce
}
fn verify(&self, gateway_key: &x25519::PrivateKey) -> Result<(), Error> {
fn verify(&self, gateway_key: &PrivateKey) -> std::result::Result<(), Error> {
self.reply.gateway_data.verify(gateway_key, self.nonce())
}
@@ -760,22 +464,6 @@ impl PendingRegistrationResponse for v2::response::PendingRegistrationResponse {
fn private_ips(&self) -> IpPair {
self.reply.gateway_data.private_ip.into()
}
fn finalise_registration(
&self,
private_key: &x25519::PrivateKey,
credential: Option<BandwidthClaim>,
) -> Box<dyn FinalMessage + Send + Sync> {
Box::new(v2::registration::FinalMessage {
gateway_client: v2::registration::GatewayClient::new(
private_key,
self.pub_key().inner(),
self.private_ips().ipv4.into(),
self.nonce(),
),
credential: credential.and_then(|b| b.credential.into_zk_nym().map(|c| *c)),
})
}
}
impl PendingRegistrationResponse for v3::response::PendingRegistrationResponse {
@@ -783,7 +471,7 @@ impl PendingRegistrationResponse for v3::response::PendingRegistrationResponse {
self.reply.nonce
}
fn verify(&self, gateway_key: &x25519::PrivateKey) -> Result<(), Error> {
fn verify(&self, gateway_key: &PrivateKey) -> std::result::Result<(), Error> {
self.reply.gateway_data.verify(gateway_key, self.nonce())
}
@@ -794,22 +482,6 @@ impl PendingRegistrationResponse for v3::response::PendingRegistrationResponse {
fn private_ips(&self) -> IpPair {
self.reply.gateway_data.private_ip.into()
}
fn finalise_registration(
&self,
private_key: &x25519::PrivateKey,
credential: Option<BandwidthClaim>,
) -> Box<dyn FinalMessage + Send + Sync> {
Box::new(v3::registration::FinalMessage {
gateway_client: v3::registration::GatewayClient::new(
private_key,
self.pub_key().inner(),
self.private_ips().ipv4.into(),
self.nonce(),
),
credential: credential.and_then(|b| b.credential.into_zk_nym().map(|c| *c)),
})
}
}
impl PendingRegistrationResponse for v4::response::PendingRegistrationResponse {
@@ -817,42 +489,7 @@ impl PendingRegistrationResponse for v4::response::PendingRegistrationResponse {
self.reply.nonce
}
fn verify(&self, gateway_key: &x25519::PrivateKey) -> Result<(), Error> {
self.reply.gateway_data.verify(gateway_key, self.nonce())
}
fn pub_key(&self) -> PeerPublicKey {
self.reply.gateway_data.pub_key
}
fn private_ips(&self) -> IpPair {
// v4 -> v5 -> v6
v5::registration::IpPair::from(self.reply.gateway_data.private_ips).into()
}
fn finalise_registration(
&self,
private_key: &x25519::PrivateKey,
credential: Option<BandwidthClaim>,
) -> Box<dyn FinalMessage + Send + Sync> {
Box::new(v4::registration::FinalMessage {
gateway_client: v4::registration::GatewayClient::new(
private_key,
self.pub_key().inner(),
self.reply.gateway_data.private_ips,
self.nonce(),
),
credential: credential.and_then(|b| b.credential.into_zk_nym().map(|c| *c)),
})
}
}
impl PendingRegistrationResponse for v5::response::PendingRegistrationResponse {
fn nonce(&self) -> u64 {
self.reply.nonce
}
fn verify(&self, gateway_key: &x25519::PrivateKey) -> Result<(), Error> {
fn verify(&self, gateway_key: &PrivateKey) -> std::result::Result<(), Error> {
self.reply.gateway_data.verify(gateway_key, self.nonce())
}
@@ -863,30 +500,14 @@ impl PendingRegistrationResponse for v5::response::PendingRegistrationResponse {
fn private_ips(&self) -> IpPair {
self.reply.gateway_data.private_ips.into()
}
fn finalise_registration(
&self,
private_key: &x25519::PrivateKey,
credential: Option<BandwidthClaim>,
) -> Box<dyn FinalMessage + Send + Sync> {
Box::new(v5::registration::FinalMessage {
gateway_client: v5::registration::GatewayClient::new(
private_key,
self.pub_key().inner(),
self.reply.gateway_data.private_ips,
self.nonce(),
),
credential: credential.and_then(|b| b.credential.into_zk_nym().map(|c| *c)),
})
}
}
impl PendingRegistrationResponse for v6::response::PendingRegistrationResponse {
impl PendingRegistrationResponse for v5::response::PendingRegistrationResponse {
fn nonce(&self) -> u64 {
self.reply.nonce
}
fn verify(&self, gateway_key: &x25519::PrivateKey) -> Result<(), Error> {
fn verify(&self, gateway_key: &PrivateKey) -> std::result::Result<(), Error> {
self.reply.gateway_data.verify(gateway_key, self.nonce())
}
@@ -897,25 +518,9 @@ impl PendingRegistrationResponse for v6::response::PendingRegistrationResponse {
fn private_ips(&self) -> IpPair {
self.reply.gateway_data.private_ips
}
fn finalise_registration(
&self,
private_key: &x25519::PrivateKey,
credential: Option<BandwidthClaim>,
) -> Box<dyn FinalMessage + Send + Sync> {
Box::new(v6::registration::FinalMessage {
gateway_client: v6::registration::GatewayClient::new(
private_key,
self.pub_key().inner(),
self.reply.gateway_data.private_ips,
self.nonce(),
),
credential,
})
}
}
pub trait RegisteredResponse: Id + UpgradeModeStatus + fmt::Debug {
pub trait RegisteredResponse: Id + fmt::Debug {
fn private_ips(&self) -> IpPair;
fn pub_key(&self) -> PeerPublicKey;
fn wg_port(&self) -> u16;
@@ -950,8 +555,7 @@ impl RegisteredResponse for v3::response::RegisteredResponse {
}
impl RegisteredResponse for v4::response::RegisteredResponse {
fn private_ips(&self) -> IpPair {
// v4 -> v5 -> v6
v5::registration::IpPair::from(self.reply.private_ips).into()
self.reply.private_ips.into()
}
fn pub_key(&self) -> PeerPublicKey {
@@ -964,20 +568,6 @@ impl RegisteredResponse for v4::response::RegisteredResponse {
}
impl RegisteredResponse for v5::response::RegisteredResponse {
fn private_ips(&self) -> IpPair {
self.reply.private_ips.into()
}
fn pub_key(&self) -> PeerPublicKey {
self.reply.pub_key
}
fn wg_port(&self) -> u16 {
self.reply.wg_port
}
}
impl RegisteredResponse for v6::response::RegisteredResponse {
fn private_ips(&self) -> IpPair {
self.reply.private_ips
}
@@ -991,7 +581,7 @@ impl RegisteredResponse for v6::response::RegisteredResponse {
}
}
pub trait RemainingBandwidthResponse: Id + UpgradeModeStatus + fmt::Debug {
pub trait RemainingBandwidthResponse: Id + fmt::Debug {
fn available_bandwidth(&self) -> Option<i64>;
}
@@ -1019,13 +609,7 @@ impl RemainingBandwidthResponse for v5::response::RemainingBandwidthResponse {
}
}
impl RemainingBandwidthResponse for v6::response::RemainingBandwidthResponse {
fn available_bandwidth(&self) -> Option<i64> {
self.reply.as_ref().map(|r| r.available_bandwidth)
}
}
pub trait TopUpBandwidthResponse: Id + UpgradeModeStatus + fmt::Debug {
pub trait TopUpBandwidthResponse: Id + fmt::Debug {
fn available_bandwidth(&self) -> i64;
}
@@ -1046,9 +630,3 @@ impl TopUpBandwidthResponse for v5::response::TopUpBandwidthResponse {
self.reply.available_bandwidth
}
}
impl TopUpBandwidthResponse for v6::response::TopUpBandwidthResponse {
fn available_bandwidth(&self) -> i64 {
self.reply.available_bandwidth
}
}
@@ -48,7 +48,7 @@ pub struct RegistrationData {
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct RegisteredData {
pub struct RegistredData {
pub pub_key: PeerPublicKey,
pub private_ip: IpAddr,
pub wg_port: u16,
@@ -1,7 +1,7 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::registration::{RegisteredData, RegistrationData, RemainingBandwidthData};
use super::registration::{RegistrationData, RegistredData, RemainingBandwidthData};
use nym_sphinx::addressing::Recipient;
use serde::{Deserialize, Serialize};
@@ -34,7 +34,7 @@ impl AuthenticatorResponse {
}
pub fn new_registered(
registred_data: RegisteredData,
registred_data: RegistredData,
reply_to: Recipient,
request_id: u64,
) -> Self {
@@ -108,7 +108,7 @@ pub struct PendingRegistrationResponse {
pub struct RegisteredResponse {
pub request_id: u64,
pub reply_to: Recipient,
pub reply: RegisteredData,
pub reply: RegistredData,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
@@ -154,8 +154,8 @@ impl From<v2::registration::RegistrationData> for v1::registration::Registration
}
}
impl From<v2::registration::RegisteredData> for v1::registration::RegisteredData {
fn from(value: v2::registration::RegisteredData) -> Self {
impl From<v2::registration::RegistredData> for v1::registration::RegistredData {
fn from(value: v2::registration::RegistredData) -> Self {
Self {
pub_key: value.pub_key,
private_ip: value.private_ip,
@@ -58,7 +58,7 @@ pub struct RegistrationData {
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct RegisteredData {
pub struct RegistredData {
pub pub_key: PeerPublicKey,
pub private_ip: IpAddr,
pub wg_port: u16,
@@ -1,7 +1,7 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::registration::{RegisteredData, RegistrationData, RemainingBandwidthData};
use super::registration::{RegistrationData, RegistredData, RemainingBandwidthData};
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
use nym_sphinx::addressing::Recipient;
use serde::{Deserialize, Serialize};
@@ -38,7 +38,7 @@ impl AuthenticatorResponse {
}
pub fn new_registered(
registred_data: RegisteredData,
registred_data: RegistredData,
reply_to: Recipient,
request_id: u64,
) -> Self {
@@ -118,7 +118,7 @@ pub struct PendingRegistrationResponse {
pub struct RegisteredResponse {
pub request_id: u64,
pub reply_to: Recipient,
pub reply: RegisteredData,
pub reply: RegistredData,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
@@ -299,8 +299,8 @@ impl From<v2::registration::RegistrationData> for v3::registration::Registration
}
}
impl From<v3::registration::RegisteredData> for v2::registration::RegisteredData {
fn from(value: v3::registration::RegisteredData) -> Self {
impl From<v3::registration::RegistredData> for v2::registration::RegistredData {
fn from(value: v3::registration::RegistredData) -> Self {
Self {
pub_key: value.pub_key,
private_ip: value.private_ip,
@@ -309,8 +309,8 @@ impl From<v3::registration::RegisteredData> for v2::registration::RegisteredData
}
}
impl From<v2::registration::RegisteredData> for v3::registration::RegisteredData {
fn from(value: v2::registration::RegisteredData) -> Self {
impl From<v2::registration::RegistredData> for v3::registration::RegistredData {
fn from(value: v2::registration::RegistredData) -> Self {
Self {
pub_key: value.pub_key,
private_ip: value.private_ip,
@@ -674,7 +674,7 @@ mod tests {
let pub_key = PeerPublicKey::new(PublicKey::from([0; 32]));
let private_ip = IpAddr::from_str("10.10.10.10").unwrap();
let wg_port = 51822;
let registred_data = v2::registration::RegisteredData {
let registred_data = v2::registration::RegistredData {
pub_key,
private_ip,
wg_port,
@@ -701,7 +701,7 @@ mod tests {
v3::response::AuthenticatorResponseData::Registered(v3::response::RegisteredResponse {
request_id,
reply_to,
reply: v3::registration::RegisteredData {
reply: v3::registration::RegistredData {
wg_port,
pub_key,
private_ip
@@ -715,7 +715,7 @@ mod tests {
let pub_key = PeerPublicKey::new(PublicKey::from([0; 32]));
let private_ip = IpAddr::from_str("10.10.10.10").unwrap();
let wg_port = 51822;
let registred_data = v3::registration::RegisteredData {
let registred_data = v3::registration::RegistredData {
pub_key,
private_ip,
wg_port,
@@ -742,7 +742,7 @@ mod tests {
v2::response::AuthenticatorResponseData::Registered(v2::response::RegisteredResponse {
request_id,
reply_to,
reply: v2::registration::RegisteredData {
reply: v2::registration::RegistredData {
wg_port,
pub_key,
private_ip
@@ -58,7 +58,7 @@ pub struct RegistrationData {
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct RegisteredData {
pub struct RegistredData {
pub pub_key: PeerPublicKey,
pub private_ip: IpAddr,
pub wg_port: u16,
@@ -1,7 +1,7 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::registration::{RegisteredData, RegistrationData, RemainingBandwidthData};
use super::registration::{RegistrationData, RegistredData, RemainingBandwidthData};
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
use nym_sphinx::addressing::Recipient;
use serde::{Deserialize, Serialize};
@@ -38,7 +38,7 @@ impl AuthenticatorResponse {
}
pub fn new_registered(
registred_data: RegisteredData,
registred_data: RegistredData,
reply_to: Recipient,
request_id: u64,
) -> Self {
@@ -139,7 +139,7 @@ pub struct PendingRegistrationResponse {
pub struct RegisteredResponse {
pub request_id: u64,
pub reply_to: Recipient,
pub reply: RegisteredData,
pub reply: RegistredData,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
@@ -262,8 +262,8 @@ impl From<v4::response::TopUpBandwidthResponse> for v3::response::TopUpBandwidth
}
}
impl From<v3::registration::RegisteredData> for v4::registration::RegisteredData {
fn from(value: v3::registration::RegisteredData) -> Self {
impl From<v3::registration::RegistredData> for v4::registration::RegistredData {
fn from(value: v3::registration::RegistredData) -> Self {
Self {
pub_key: value.pub_key,
private_ips: value.private_ip.into(),
@@ -272,8 +272,8 @@ impl From<v3::registration::RegisteredData> for v4::registration::RegisteredData
}
}
impl From<v4::registration::RegisteredData> for v3::registration::RegisteredData {
fn from(value: v4::registration::RegisteredData) -> Self {
impl From<v4::registration::RegistredData> for v3::registration::RegistredData {
fn from(value: v4::registration::RegistredData) -> Self {
Self {
pub_key: value.pub_key,
private_ip: value.private_ips.ipv4.into(),
@@ -565,7 +565,7 @@ mod tests {
let private_ips =
v4::registration::IpPair::new(ipv4, Ipv6Addr::from_str("fc01::a0a").unwrap());
let wg_port = 51822;
let registred_data = v3::registration::RegisteredData {
let registred_data = v3::registration::RegistredData {
pub_key,
private_ip: ipv4.into(),
wg_port,
@@ -592,7 +592,7 @@ mod tests {
v4::response::AuthenticatorResponseData::Registered(v4::response::RegisteredResponse {
request_id,
reply_to,
reply: v4::registration::RegisteredData {
reply: v4::registration::RegistredData {
wg_port,
pub_key,
private_ips
@@ -608,7 +608,7 @@ mod tests {
let private_ips =
v4::registration::IpPair::new(ipv4, Ipv6Addr::from_str("fc01::10").unwrap());
let wg_port = 51822;
let registred_data = v4::registration::RegisteredData {
let registred_data = v4::registration::RegistredData {
pub_key,
private_ips,
wg_port,
@@ -635,7 +635,7 @@ mod tests {
v3::response::AuthenticatorResponseData::Registered(v3::response::RegisteredResponse {
request_id,
reply_to,
reply: v3::registration::RegisteredData {
reply: v3::registration::RegistredData {
wg_port,
pub_key,
private_ip: ipv4.into()
@@ -110,7 +110,7 @@ pub struct RegistrationData {
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct RegisteredData {
pub struct RegistredData {
pub pub_key: PeerPublicKey,
pub private_ips: IpPair,
pub wg_port: u16,
@@ -1,7 +1,7 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::registration::{RegisteredData, RegistrationData, RemainingBandwidthData};
use super::registration::{RegistrationData, RegistredData, RemainingBandwidthData};
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
use nym_sphinx::addressing::Recipient;
use serde::{Deserialize, Serialize};
@@ -38,7 +38,7 @@ impl AuthenticatorResponse {
}
pub fn new_registered(
registred_data: RegisteredData,
registred_data: RegistredData,
reply_to: Recipient,
request_id: u64,
) -> Self {
@@ -139,7 +139,7 @@ pub struct PendingRegistrationResponse {
pub struct RegisteredResponse {
pub request_id: u64,
pub reply_to: Recipient,
pub reply: RegisteredData,
pub reply: RegistredData,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
@@ -186,8 +186,8 @@ impl From<v4::response::TopUpBandwidthResponse> for v5::response::TopUpBandwidth
}
}
impl From<v4::registration::RegisteredData> for v5::registration::RegisteredData {
fn from(value: v4::registration::RegisteredData) -> Self {
impl From<v4::registration::RegistredData> for v5::registration::RegistredData {
fn from(value: v4::registration::RegistredData) -> Self {
Self {
pub_key: value.pub_key,
private_ips: value.private_ips.into(),
@@ -405,7 +405,7 @@ mod tests {
let ipv6 = Ipv6Addr::from_str("fc01::a0a").unwrap();
let private_ips = v4::registration::IpPair::new(ipv4, ipv6);
let wg_port = 51822;
let registred_data = v4::registration::RegisteredData {
let registred_data = v4::registration::RegistredData {
pub_key,
private_ips,
wg_port,
@@ -431,7 +431,7 @@ mod tests {
upgraded_msg.data,
v5::response::AuthenticatorResponseData::Registered(v5::response::RegisteredResponse {
request_id,
reply: v5::registration::RegisteredData {
reply: v5::registration::RegistredData {
wg_port,
pub_key,
private_ips: v5::registration::IpPair::new(ipv4, ipv6)
@@ -108,7 +108,7 @@ pub struct RegistrationData {
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct RegisteredData {
pub struct RegistredData {
pub pub_key: PeerPublicKey,
pub private_ips: IpPair,
pub wg_port: u16,
@@ -1,7 +1,7 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::registration::{RegisteredData, RegistrationData, RemainingBandwidthData};
use super::registration::{RegistrationData, RegistredData, RemainingBandwidthData};
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
use serde::{Deserialize, Serialize};
@@ -32,7 +32,7 @@ impl AuthenticatorResponse {
}
}
pub fn new_registered(registred_data: RegisteredData, request_id: u64) -> Self {
pub fn new_registered(registred_data: RegistredData, request_id: u64) -> Self {
Self {
protocol: Protocol {
service_provider_type: ServiceProviderType::Authenticator,
@@ -116,7 +116,7 @@ pub struct PendingRegistrationResponse {
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct RegisteredResponse {
pub request_id: u64,
pub reply: RegisteredData,
pub reply: RegistredData,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
@@ -1,441 +0,0 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{v5, v6};
impl TryFrom<v5::request::AuthenticatorRequest> for v6::request::AuthenticatorRequest {
type Error = crate::Error;
fn try_from(
authenticator_request: v5::request::AuthenticatorRequest,
) -> Result<Self, Self::Error> {
Ok(Self {
protocol: v6::PROTOCOL,
data: authenticator_request.data.try_into()?,
request_id: authenticator_request.request_id,
})
}
}
impl TryFrom<v5::request::AuthenticatorRequestData> for v6::request::AuthenticatorRequestData {
type Error = crate::Error;
fn try_from(
authenticator_request_data: v5::request::AuthenticatorRequestData,
) -> Result<Self, Self::Error> {
match authenticator_request_data {
v5::request::AuthenticatorRequestData::Initial(init_msg) => Ok(
v6::request::AuthenticatorRequestData::Initial(init_msg.into()),
),
v5::request::AuthenticatorRequestData::Final(final_msg) => Ok(
v6::request::AuthenticatorRequestData::Final(Box::new((*final_msg).try_into()?)),
),
v5::request::AuthenticatorRequestData::QueryBandwidth(pub_key) => Ok(
v6::request::AuthenticatorRequestData::QueryBandwidth(pub_key),
),
v5::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message) => Ok(
v6::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message.into()),
),
}
}
}
impl From<v5::registration::InitMessage> for v6::registration::InitMessage {
fn from(init_msg: v5::registration::InitMessage) -> Self {
Self {
pub_key: init_msg.pub_key,
}
}
}
impl TryFrom<v5::registration::FinalMessage> for v6::registration::FinalMessage {
type Error = crate::Error;
fn try_from(final_msg: v5::registration::FinalMessage) -> Result<Self, Self::Error> {
Ok(Self {
gateway_client: final_msg.gateway_client.into(),
credential: final_msg
.credential
.map(TryInto::try_into)
.transpose()
.map_err(Self::Error::conversion_display)?,
})
}
}
impl From<v5::registration::GatewayClient> for v6::registration::GatewayClient {
fn from(gateway_client: v5::registration::GatewayClient) -> Self {
Self {
pub_key: gateway_client.pub_key,
private_ips: gateway_client.private_ips.into(),
mac: gateway_client.mac.into(),
}
}
}
impl From<v6::registration::GatewayClient> for v5::registration::GatewayClient {
fn from(gateway_client: v6::registration::GatewayClient) -> Self {
Self {
pub_key: gateway_client.pub_key,
private_ips: gateway_client.private_ips.into(),
mac: gateway_client.mac.into(),
}
}
}
impl From<v5::registration::ClientMac> for v6::registration::ClientMac {
fn from(client_mac: v5::registration::ClientMac) -> Self {
Self::new((*client_mac).clone())
}
}
impl From<v6::registration::ClientMac> for v5::registration::ClientMac {
fn from(client_mac: v6::registration::ClientMac) -> Self {
Self::new((*client_mac).clone())
}
}
impl From<Box<v5::topup::TopUpMessage>> for Box<v6::topup::TopUpMessage> {
fn from(top_up_message: Box<v5::topup::TopUpMessage>) -> Self {
Box::new(v6::topup::TopUpMessage {
pub_key: top_up_message.pub_key,
credential: top_up_message.credential,
})
}
}
impl From<v5::response::AuthenticatorResponse> for v6::response::AuthenticatorResponse {
fn from(value: v5::response::AuthenticatorResponse) -> Self {
Self {
protocol: v6::PROTOCOL,
data: value.data.into(),
}
}
}
impl From<v5::response::AuthenticatorResponseData> for v6::response::AuthenticatorResponseData {
fn from(authenticator_response_data: v5::response::AuthenticatorResponseData) -> Self {
match authenticator_response_data {
v5::response::AuthenticatorResponseData::PendingRegistration(pending_response) => {
v6::response::AuthenticatorResponseData::PendingRegistration(
pending_response.into(),
)
}
v5::response::AuthenticatorResponseData::Registered(registered_response) => {
v6::response::AuthenticatorResponseData::Registered(registered_response.into())
}
v5::response::AuthenticatorResponseData::RemainingBandwidth(
remaining_bandwidth_response,
) => v6::response::AuthenticatorResponseData::RemainingBandwidth(
remaining_bandwidth_response.into(),
),
v5::response::AuthenticatorResponseData::TopUpBandwidth(top_up_response) => {
v6::response::AuthenticatorResponseData::TopUpBandwidth(top_up_response.into())
}
}
}
}
impl From<v5::response::RegisteredResponse> for v6::response::RegisteredResponse {
fn from(value: v5::response::RegisteredResponse) -> Self {
Self {
request_id: value.request_id,
reply: value.reply.into(),
upgrade_mode_enabled: false,
}
}
}
impl From<v5::response::PendingRegistrationResponse> for v6::response::PendingRegistrationResponse {
fn from(value: v5::response::PendingRegistrationResponse) -> Self {
Self {
request_id: value.request_id,
reply: value.reply.into(),
upgrade_mode_enabled: false,
}
}
}
impl From<v5::registration::RegistrationData> for v6::registration::RegistrationData {
fn from(value: v5::registration::RegistrationData) -> Self {
Self {
nonce: value.nonce,
gateway_data: value.gateway_data.into(),
wg_port: value.wg_port,
}
}
}
impl From<v6::registration::RegistrationData> for v5::registration::RegistrationData {
fn from(value: v6::registration::RegistrationData) -> Self {
Self {
nonce: value.nonce,
gateway_data: value.gateway_data.into(),
wg_port: value.wg_port,
}
}
}
impl From<v5::response::RemainingBandwidthResponse> for v6::response::RemainingBandwidthResponse {
fn from(value: v5::response::RemainingBandwidthResponse) -> Self {
Self {
request_id: value.request_id,
reply: value.reply.map(Into::into),
upgrade_mode_enabled: false,
}
}
}
impl From<v5::response::TopUpBandwidthResponse> for v6::response::TopUpBandwidthResponse {
fn from(value: v5::response::TopUpBandwidthResponse) -> Self {
Self {
request_id: value.request_id,
reply: value.reply.into(),
upgrade_mode_enabled: false,
}
}
}
impl From<v5::registration::RegisteredData> for v6::registration::RegisteredData {
fn from(value: v5::registration::RegisteredData) -> Self {
Self {
pub_key: value.pub_key,
private_ips: value.private_ips.into(),
wg_port: value.wg_port,
}
}
}
impl From<v5::registration::RemainingBandwidthData> for v6::registration::RemainingBandwidthData {
fn from(value: v5::registration::RemainingBandwidthData) -> Self {
Self {
available_bandwidth: value.available_bandwidth,
}
}
}
impl From<v5::registration::IpPair> for v6::registration::IpPair {
fn from(value: v5::registration::IpPair) -> Self {
Self {
ipv4: value.ipv4,
ipv6: value.ipv6,
}
}
}
impl From<v6::registration::IpPair> for v5::registration::IpPair {
fn from(value: v6::registration::IpPair) -> Self {
Self {
ipv4: value.ipv4,
ipv6: value.ipv6,
}
}
}
#[cfg(test)]
mod tests {
use std::{
net::{Ipv4Addr, Ipv6Addr},
str::FromStr,
};
use nym_credentials_interface::{BandwidthCredential, CredentialSpendingData, TicketType};
use nym_crypto::asymmetric::x25519::PrivateKey;
use nym_wireguard_types::PeerPublicKey;
use x25519_dalek::PublicKey;
use super::*;
use crate::models::BandwidthClaim;
use crate::{util::tests::CREDENTIAL_BYTES, v5};
#[test]
fn upgrade_initial_req() {
let pub_key = PeerPublicKey::new(PublicKey::from([0; 32]));
let (msg, _) = v5::request::AuthenticatorRequest::new_initial_request(
v5::registration::InitMessage::new(pub_key),
);
let upgraded_msg = v6::request::AuthenticatorRequest::try_from(msg).unwrap();
assert_eq!(upgraded_msg.protocol, v6::PROTOCOL);
assert_eq!(
upgraded_msg.data,
v6::request::AuthenticatorRequestData::Initial(v6::registration::InitMessage {
pub_key
})
);
}
#[test]
fn upgrade_final_req() {
let mut rng = rand::thread_rng();
let local_secret = PrivateKey::new(&mut rng);
let remote_secret = x25519_dalek::StaticSecret::random_from_rng(&mut rng);
let ipv4 = Ipv4Addr::from_str("10.10.10.10").unwrap();
let ipv6 = Ipv6Addr::from_str("fc01::a0a").unwrap();
let ips = v5::registration::IpPair::new(ipv4, ipv6);
let nonce = 42;
let gateway_client = v5::registration::GatewayClient::new(
&local_secret,
(&remote_secret).into(),
ips,
nonce,
);
let credential = CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap();
let final_message = v5::registration::FinalMessage {
gateway_client: gateway_client.clone(),
credential: Some(credential.clone()),
};
let (msg, _) = v5::request::AuthenticatorRequest::new_final_request(final_message);
let upgraded_msg = v6::request::AuthenticatorRequest::try_from(msg).unwrap();
assert_eq!(upgraded_msg.protocol, v6::PROTOCOL);
assert_eq!(
upgraded_msg.data,
v6::request::AuthenticatorRequestData::Final(Box::new(
v6::registration::FinalMessage {
gateway_client: v6::registration::GatewayClient::new(
&local_secret,
(&remote_secret).into(),
v6::registration::IpPair::new(ipv4, ipv6),
nonce
),
credential: Some(BandwidthClaim {
credential: BandwidthCredential::ZkNym(Box::new(credential)),
kind: TicketType::V1MixnetEntry,
})
}
))
);
}
#[test]
fn upgrade_query_req() {
let pub_key = PeerPublicKey::new(PublicKey::from([0; 32]));
let (msg, _) = v5::request::AuthenticatorRequest::new_query_request(pub_key);
let upgraded_msg = v6::request::AuthenticatorRequest::try_from(msg).unwrap();
assert_eq!(upgraded_msg.protocol, v6::PROTOCOL);
assert_eq!(
upgraded_msg.data,
v6::request::AuthenticatorRequestData::QueryBandwidth(pub_key)
);
}
#[test]
fn upgrade_pending_reg_resp() {
let mut rng = rand::thread_rng();
let local_secret = PrivateKey::new(&mut rng);
let remote_secret = x25519_dalek::StaticSecret::random_from_rng(&mut rng);
let ipv4 = Ipv4Addr::from_str("10.10.10.10").unwrap();
let ipv6 = Ipv6Addr::from_str("fc01::a0a").unwrap();
let ips = v5::registration::IpPair::new(ipv4, ipv6);
let nonce = 42;
let wg_port = 51822;
let gateway_data = v5::registration::GatewayClient::new(
&local_secret,
(&remote_secret).into(),
ips,
nonce,
);
let registration_data = v5::registration::RegistrationData {
nonce,
gateway_data,
wg_port,
};
let request_id = 123;
let msg = v5::response::AuthenticatorResponse::new_pending_registration_success(
registration_data,
request_id,
);
let upgraded_msg = v6::response::AuthenticatorResponse::from(msg);
assert_eq!(upgraded_msg.protocol, v6::PROTOCOL);
assert_eq!(
upgraded_msg.data,
v6::response::AuthenticatorResponseData::PendingRegistration(
v6::response::PendingRegistrationResponse {
request_id,
reply: v6::registration::RegistrationData {
nonce,
gateway_data: v6::registration::GatewayClient::new(
&local_secret,
(&remote_secret).into(),
v6::registration::IpPair::new(ipv4, ipv6),
nonce
),
wg_port
},
upgrade_mode_enabled: false,
}
)
);
}
#[test]
fn upgrade_registered_resp() {
let pub_key = PeerPublicKey::new(PublicKey::from([0; 32]));
let ipv4 = Ipv4Addr::from_str("10.1.10.10").unwrap();
let ipv6 = Ipv6Addr::from_str("fc01::a0a").unwrap();
let private_ips = v5::registration::IpPair::new(ipv4, ipv6);
let wg_port = 51822;
let registered_data = v5::registration::RegisteredData {
pub_key,
private_ips,
wg_port,
};
let request_id = 123;
let msg = v5::response::AuthenticatorResponse::new_registered(registered_data, request_id);
let upgraded_msg = v6::response::AuthenticatorResponse::from(msg);
assert_eq!(upgraded_msg.protocol, v6::PROTOCOL);
assert_eq!(
upgraded_msg.data,
v6::response::AuthenticatorResponseData::Registered(v6::response::RegisteredResponse {
request_id,
reply: v6::registration::RegisteredData {
wg_port,
pub_key,
private_ips: v6::registration::IpPair::new(ipv4, ipv6)
},
upgrade_mode_enabled: false,
})
);
}
#[test]
fn upgrade_remaining_bandwidth_resp() {
let available_bandwidth = 42;
let remaining_bandwidth_data = Some(v5::registration::RemainingBandwidthData {
available_bandwidth,
});
let request_id = 123;
let msg = v5::response::AuthenticatorResponse::new_remaining_bandwidth(
remaining_bandwidth_data,
request_id,
);
let upgraded_msg = v6::response::AuthenticatorResponse::from(msg);
assert_eq!(upgraded_msg.protocol, v6::PROTOCOL);
assert_eq!(
upgraded_msg.data,
v6::response::AuthenticatorResponseData::RemainingBandwidth(
v6::response::RemainingBandwidthResponse {
request_id,
reply: Some(v6::registration::RemainingBandwidthData {
available_bandwidth,
}),
upgrade_mode_enabled: false,
}
)
);
}
}
@@ -1,15 +0,0 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
pub mod conversion;
pub mod registration;
pub mod request;
pub mod response;
pub mod topup;
pub mod upgrade_mode_check;
pub const VERSION: u8 = 6;
pub const PROTOCOL: Protocol = Protocol::new(VERSION, ServiceProviderType::Authenticator);
@@ -1,287 +0,0 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::Error;
use crate::models::BandwidthClaim;
use base64::{Engine, engine::general_purpose};
use nym_network_defaults::constants::{WG_TUN_DEVICE_IP_ADDRESS_V4, WG_TUN_DEVICE_IP_ADDRESS_V6};
use nym_wireguard_types::PeerPublicKey;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::time::SystemTime;
use std::{fmt, ops::Deref, str::FromStr};
#[cfg(feature = "verify")]
use hmac::{Hmac, Mac};
#[cfg(feature = "verify")]
use nym_crypto::asymmetric::x25519::{PrivateKey, PublicKey};
#[cfg(feature = "verify")]
use sha2::Sha256;
pub type PendingRegistrations = HashMap<PeerPublicKey, RegistrationData>;
pub type PrivateIPs = HashMap<IpPair, Taken>;
#[cfg(feature = "verify")]
pub type HmacSha256 = Hmac<Sha256>;
pub type Nonce = u64;
pub type Taken = Option<SystemTime>;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct IpPair {
pub ipv4: Ipv4Addr,
pub ipv6: Ipv6Addr,
}
impl IpPair {
pub fn new(ipv4: Ipv4Addr, ipv6: Ipv6Addr) -> Self {
IpPair { ipv4, ipv6 }
}
}
impl From<(Ipv4Addr, Ipv6Addr)> for IpPair {
fn from((ipv4, ipv6): (Ipv4Addr, Ipv6Addr)) -> Self {
IpPair { ipv4, ipv6 }
}
}
impl fmt::Display for IpPair {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({}, {})", self.ipv4, self.ipv6)
}
}
impl From<IpAddr> for IpPair {
fn from(value: IpAddr) -> Self {
let (before_last_byte, last_byte) = match value {
IpAddr::V4(ipv4_addr) => (ipv4_addr.octets()[2], ipv4_addr.octets()[3]),
IpAddr::V6(ipv6_addr) => (ipv6_addr.octets()[14], ipv6_addr.octets()[15]),
};
let last_bytes = ((before_last_byte as u16) << 8) | last_byte as u16;
let ipv4 = Ipv4Addr::new(
WG_TUN_DEVICE_IP_ADDRESS_V4.octets()[0],
WG_TUN_DEVICE_IP_ADDRESS_V4.octets()[1],
before_last_byte,
last_byte,
);
let ipv6 = Ipv6Addr::new(
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[0],
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[1],
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[2],
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[3],
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[4],
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[5],
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[6],
last_bytes,
);
IpPair::new(ipv4, ipv6)
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct InitMessage {
/// Base64 encoded x25519 public key
pub pub_key: PeerPublicKey,
}
impl InitMessage {
pub fn new(pub_key: PeerPublicKey) -> Self {
InitMessage { pub_key }
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct FinalMessage {
/// Gateway client data
pub gateway_client: GatewayClient,
/// Ecash credential
pub credential: Option<BandwidthClaim>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct RegistrationData {
pub nonce: u64,
pub gateway_data: GatewayClient,
pub wg_port: u16,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct RegisteredData {
pub pub_key: PeerPublicKey,
pub private_ips: IpPair,
pub wg_port: u16,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct RemainingBandwidthData {
pub available_bandwidth: i64,
}
/// Client that wants to register sends its PublicKey bytes mac digest encrypted with a DH shared secret.
/// Gateway/Nym node can then verify pub_key payload using the same process
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct GatewayClient {
/// Base64 encoded x25519 public key
pub pub_key: PeerPublicKey,
/// Assigned private IPs (v4 and v6)
pub private_ips: IpPair,
/// Sha256 hmac on the data (alongside the prior nonce)
pub mac: ClientMac,
}
impl GatewayClient {
#[cfg(feature = "verify")]
pub fn new(
local_secret: &PrivateKey,
remote_public: x25519_dalek::PublicKey,
private_ips: IpPair,
nonce: u64,
) -> Self {
let local_public = PublicKey::from(local_secret);
let remote_public = PublicKey::from(remote_public);
let dh = local_secret.diffie_hellman(&remote_public);
// TODO: change that to use our nym_crypto::hmac module instead
#[allow(clippy::expect_used)]
let mut mac = HmacSha256::new_from_slice(&dh[..])
.expect("x25519 shared secret is always 32 bytes long");
mac.update(local_public.as_bytes());
mac.update(private_ips.to_string().as_bytes());
mac.update(&nonce.to_le_bytes());
GatewayClient {
pub_key: PeerPublicKey::new(local_public.into()),
private_ips,
mac: ClientMac(mac.finalize().into_bytes().to_vec()),
}
}
// Reusable secret should be gateways Wireguard PK
// Client should perform this step when generating its payload, using its own WG PK
#[cfg(feature = "verify")]
pub fn verify(&self, gateway_key: &PrivateKey, nonce: u64) -> Result<(), Error> {
// use gateways key as a ref to an x25519_dalek key
let dh = gateway_key.inner().diffie_hellman(&self.pub_key);
// TODO: change that to use our nym_crypto::hmac module instead
#[allow(clippy::expect_used)]
let mut mac = HmacSha256::new_from_slice(dh.as_bytes())
.expect("x25519 shared secret is always 32 bytes long");
mac.update(self.pub_key.as_bytes());
mac.update(self.private_ips.to_string().as_bytes());
mac.update(&nonce.to_le_bytes());
mac.verify_slice(&self.mac)
.map_err(|source| Error::FailedClientMacVerification {
client: self.pub_key.to_string(),
source,
})
}
pub fn pub_key(&self) -> PeerPublicKey {
self.pub_key
}
}
// TODO: change the inner type into generic array of size HmacSha256::OutputSize
// TODO2: rely on our internal crypto/hmac
#[derive(Debug, Clone, PartialEq)]
pub struct ClientMac(Vec<u8>);
impl fmt::Display for ClientMac {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", general_purpose::STANDARD.encode(&self.0))
}
}
impl From<Vec<u8>> for ClientMac {
fn from(v: Vec<u8>) -> Self {
ClientMac(v)
}
}
impl ClientMac {
#[allow(dead_code)]
pub fn new(mac: Vec<u8>) -> Self {
ClientMac(mac)
}
}
impl Deref for ClientMac {
type Target = Vec<u8>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl FromStr for ClientMac {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mac_bytes: Vec<u8> =
general_purpose::STANDARD
.decode(s)
.map_err(|source| Error::MalformedClientMac {
mac: s.to_string(),
source,
})?;
Ok(ClientMac(mac_bytes))
}
}
impl Serialize for ClientMac {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let encoded_key = general_purpose::STANDARD.encode(self.0.clone());
serializer.serialize_str(&encoded_key)
}
}
impl<'de> Deserialize<'de> for ClientMac {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let encoded_key = String::deserialize(deserializer)?;
ClientMac::from_str(&encoded_key).map_err(serde::de::Error::custom)
}
}
#[cfg(test)]
mod tests {
use super::*;
use nym_crypto::asymmetric::x25519;
use nym_test_utils::helpers::deterministic_rng;
#[test]
fn create_ip_pair() {
let ipv4: IpAddr = Ipv4Addr::from_str("10.1.10.50").unwrap().into();
let ipv6: IpAddr = Ipv6Addr::from_str("fc01::0a32").unwrap().into();
assert_eq!(IpPair::from(ipv4), IpPair::from(ipv6));
}
#[test]
#[cfg(feature = "verify")]
fn client_request_roundtrip() {
let mut rng = deterministic_rng();
let gateway_key_pair = x25519::KeyPair::new(&mut rng);
let client_key_pair = x25519::KeyPair::new(&mut rng);
let nonce = 1234567890;
let client = GatewayClient::new(
client_key_pair.private_key(),
x25519_dalek::PublicKey::from(gateway_key_pair.public_key().to_bytes()),
IpPair::new("10.0.0.42".parse().unwrap(), "fc00::42".parse().unwrap()),
nonce,
);
assert!(client.verify(gateway_key_pair.private_key(), nonce).is_ok())
}
}
@@ -1,135 +0,0 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::{
PROTOCOL,
registration::{FinalMessage, InitMessage},
topup::TopUpMessage,
upgrade_mode_check::UpgradeModeCheckRequest,
};
use nym_service_provider_requests_common::Protocol;
use nym_wireguard_types::PeerPublicKey;
use serde::{Deserialize, Serialize};
use crate::make_bincode_serializer;
fn generate_random() -> u64 {
use rand::RngCore;
let mut rng = rand::rngs::OsRng;
rng.next_u64()
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct AuthenticatorRequest {
pub protocol: Protocol,
pub data: AuthenticatorRequestData,
pub request_id: u64,
}
impl AuthenticatorRequest {
pub fn from_reconstructed_message(
message: &nym_sphinx::receiver::ReconstructedMessage,
) -> Result<Self, bincode::Error> {
use bincode::Options;
make_bincode_serializer().deserialize(&message.message)
}
pub fn new_initial_request(init_message: InitMessage) -> (Self, u64) {
let request_id = generate_random();
(
Self {
protocol: PROTOCOL,
data: AuthenticatorRequestData::Initial(init_message),
request_id,
},
request_id,
)
}
pub fn new_final_request(final_message: FinalMessage) -> (Self, u64) {
let request_id = generate_random();
(
Self {
protocol: PROTOCOL,
data: AuthenticatorRequestData::Final(Box::new(final_message)),
request_id,
},
request_id,
)
}
pub fn new_query_request(peer_public_key: PeerPublicKey) -> (Self, u64) {
let request_id = generate_random();
(
Self {
protocol: PROTOCOL,
data: AuthenticatorRequestData::QueryBandwidth(peer_public_key),
request_id,
},
request_id,
)
}
pub fn new_topup_request(top_up_message: TopUpMessage) -> (Self, u64) {
let request_id = generate_random();
(
Self {
protocol: PROTOCOL,
data: AuthenticatorRequestData::TopUpBandwidth(Box::new(top_up_message)),
request_id,
},
request_id,
)
}
pub fn new_upgrade_mode_check_request(message: UpgradeModeCheckRequest) -> (Self, u64) {
let request_id = generate_random();
(
Self {
protocol: PROTOCOL,
data: AuthenticatorRequestData::CheckUpgradeMode(message),
request_id,
},
request_id,
)
}
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
use bincode::Options;
make_bincode_serializer().serialize(self)
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum AuthenticatorRequestData {
Initial(InitMessage),
Final(Box<FinalMessage>),
QueryBandwidth(PeerPublicKey),
TopUpBandwidth(Box<TopUpMessage>),
CheckUpgradeMode(UpgradeModeCheckRequest),
}
#[cfg(test)]
mod tests {
use super::super::VERSION;
use super::*;
use nym_service_provider_requests_common::ServiceProviderType;
use std::str::FromStr;
#[test]
fn check_first_bytes_protocol() {
let version = VERSION;
let data = AuthenticatorRequest {
protocol: Protocol {
version,
service_provider_type: ServiceProviderType::Authenticator,
},
data: AuthenticatorRequestData::Initial(InitMessage::new(
PeerPublicKey::from_str("yvNUDpT5l7W/xDhiu6HkqTHDQwbs/B3J5UrLmORl1EQ=").unwrap(),
)),
request_id: 1,
};
let bytes = *data.to_bytes().unwrap().first_chunk::<2>().unwrap();
assert_eq!(bytes, [version, ServiceProviderType::Authenticator as u8]);
}
}
@@ -1,153 +0,0 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::registration::{RegisteredData, RegistrationData, RemainingBandwidthData};
use nym_service_provider_requests_common::Protocol;
use serde::{Deserialize, Serialize};
use crate::make_bincode_serializer;
use super::PROTOCOL;
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct AuthenticatorResponse {
pub protocol: Protocol,
pub data: AuthenticatorResponseData,
}
impl AuthenticatorResponse {
pub fn new_pending_registration_success(
registration_data: RegistrationData,
request_id: u64,
upgrade_mode_enabled: bool,
) -> Self {
Self {
protocol: PROTOCOL,
data: AuthenticatorResponseData::PendingRegistration(PendingRegistrationResponse {
reply: registration_data,
request_id,
upgrade_mode_enabled,
}),
}
}
pub fn new_registered(
registered_data: RegisteredData,
request_id: u64,
upgrade_mode_enabled: bool,
) -> Self {
Self {
protocol: PROTOCOL,
data: AuthenticatorResponseData::Registered(RegisteredResponse {
reply: registered_data,
request_id,
upgrade_mode_enabled,
}),
}
}
pub fn new_remaining_bandwidth(
remaining_bandwidth_data: Option<RemainingBandwidthData>,
request_id: u64,
upgrade_mode_enabled: bool,
) -> Self {
Self {
protocol: PROTOCOL,
data: AuthenticatorResponseData::RemainingBandwidth(RemainingBandwidthResponse {
reply: remaining_bandwidth_data,
request_id,
upgrade_mode_enabled,
}),
}
}
pub fn new_topup_bandwidth(
remaining_bandwidth_data: RemainingBandwidthData,
request_id: u64,
upgrade_mode_enabled: bool,
) -> Self {
Self {
protocol: PROTOCOL,
data: AuthenticatorResponseData::TopUpBandwidth(TopUpBandwidthResponse {
reply: remaining_bandwidth_data,
request_id,
upgrade_mode_enabled,
}),
}
}
pub fn new_upgrade_mode_check(request_id: u64, upgrade_mode_enabled: bool) -> Self {
Self {
protocol: PROTOCOL,
data: AuthenticatorResponseData::UpgradeMode(UpgradeModeResponse {
request_id,
upgrade_mode_enabled,
}),
}
}
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
use bincode::Options;
make_bincode_serializer().serialize(self)
}
pub fn from_reconstructed_message(
message: &nym_sphinx::receiver::ReconstructedMessage,
) -> Result<Self, bincode::Error> {
use bincode::Options;
make_bincode_serializer().deserialize(&message.message)
}
pub fn id(&self) -> Option<u64> {
match &self.data {
AuthenticatorResponseData::PendingRegistration(response) => Some(response.request_id),
AuthenticatorResponseData::Registered(response) => Some(response.request_id),
AuthenticatorResponseData::RemainingBandwidth(response) => Some(response.request_id),
AuthenticatorResponseData::TopUpBandwidth(response) => Some(response.request_id),
AuthenticatorResponseData::UpgradeMode(response) => Some(response.request_id),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum AuthenticatorResponseData {
PendingRegistration(PendingRegistrationResponse),
Registered(RegisteredResponse),
RemainingBandwidth(RemainingBandwidthResponse),
TopUpBandwidth(TopUpBandwidthResponse),
UpgradeMode(UpgradeModeResponse),
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct PendingRegistrationResponse {
pub request_id: u64,
pub reply: RegistrationData,
pub upgrade_mode_enabled: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct RegisteredResponse {
pub request_id: u64,
pub reply: RegisteredData,
pub upgrade_mode_enabled: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct RemainingBandwidthResponse {
pub request_id: u64,
pub reply: Option<RemainingBandwidthData>,
pub upgrade_mode_enabled: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct TopUpBandwidthResponse {
pub request_id: u64,
pub reply: RemainingBandwidthData,
pub upgrade_mode_enabled: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct UpgradeModeResponse {
pub request_id: u64,
pub upgrade_mode_enabled: bool,
}
@@ -1,15 +0,0 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_credentials_interface::CredentialSpendingData;
use nym_wireguard_types::PeerPublicKey;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct TopUpMessage {
/// Base64 encoded x25519 public key
pub pub_key: PeerPublicKey,
/// Ecash credential
pub credential: CredentialSpendingData,
}
@@ -1,12 +0,0 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum UpgradeModeCheckRequest {
/// Attempt to request upgrade mode recheck via the JWT issued as the result of
/// global attestation.json being published
UpgradeModeJwt { token: String },
}
+5 -21
View File
@@ -1,7 +1,7 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::{v1, v2, v3, v4, v5, v6};
use super::{v1, v2, v3, v4, v5};
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
#[derive(Copy, Clone, Debug, PartialEq, strum_macros::Display)]
@@ -22,15 +22,11 @@ pub enum AuthenticatorVersion {
/// introduced in dorina-patched release (1.6.1)
V5,
/// introduced in niolo release (1.23.0)
V6,
/// an unknown, future, variant that can be present if running outdated software
UNKNOWN,
}
impl AuthenticatorVersion {
pub const LATEST: Self = Self::V6;
pub const LATEST: Self = Self::V5;
pub const fn release_version(&self) -> semver::Version {
match self {
@@ -39,7 +35,6 @@ impl AuthenticatorVersion {
AuthenticatorVersion::V3 => semver::Version::new(1, 1, 10),
AuthenticatorVersion::V4 => semver::Version::new(1, 2, 0),
AuthenticatorVersion::V5 => semver::Version::new(1, 6, 1),
AuthenticatorVersion::V6 => semver::Version::new(1, 23, 0),
AuthenticatorVersion::UNKNOWN => semver::Version::new(0, 0, 0),
}
}
@@ -59,8 +54,6 @@ impl From<Protocol> for AuthenticatorVersion {
AuthenticatorVersion::V4
} else if value.version == v5::VERSION {
AuthenticatorVersion::V5
} else if value.version == v6::VERSION {
AuthenticatorVersion::V6
} else {
AuthenticatorVersion::UNKNOWN
}
@@ -79,8 +72,6 @@ impl From<u8> for AuthenticatorVersion {
AuthenticatorVersion::V4
} else if value == v5::VERSION {
AuthenticatorVersion::V5
} else if value == v6::VERSION {
AuthenticatorVersion::V6
} else {
AuthenticatorVersion::UNKNOWN
}
@@ -135,14 +126,11 @@ impl From<semver::Version> for AuthenticatorVersion {
if semver < AuthenticatorVersion::V5.release_version() {
return Self::V4;
}
if semver < AuthenticatorVersion::V6.release_version() {
return Self::V5;
}
// if provided version is higher (or equal) to release version of V6,
// we return the latest (i.e. v6)
// if provided version is higher (or equal) to release version of V5,
// we return the latest (i.e. v5)
debug_assert_eq!(
Self::V6,
Self::V5,
Self::LATEST,
"a new AuthenticatorVersion variant has been introduced without adjusting the `From<semver::Version>` trait"
);
@@ -203,9 +191,5 @@ mod tests {
assert_eq!(AuthenticatorVersion::V5, "1.7.0".into());
assert_eq!(AuthenticatorVersion::V5, "1.16.11".into());
assert_eq!(AuthenticatorVersion::V5, "1.17.0".into());
assert_eq!(AuthenticatorVersion::V5, "1.22.0".into());
assert_eq!(AuthenticatorVersion::V6, "1.23.0".into());
assert_eq!(AuthenticatorVersion::V6, "1.23.1".into());
assert_eq!(AuthenticatorVersion::V6, "1.24.0".into());
}
}
+6 -1
View File
@@ -7,16 +7,21 @@ license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
async-trait = { workspace = true }
async-trait = { workspace = true }
bip39 = { workspace = true }
log = { workspace = true }
rand = { workspace = true }
thiserror = { workspace = true }
url = { workspace = true }
zeroize = { workspace = true }
nym-credential-storage = { path = "../credential-storage" }
nym-credentials = { path = "../credentials" }
nym-credentials-interface = { path = "../credentials-interface" }
nym-crypto = { path = "../crypto", features = ["rand", "asymmetric", "stream_cipher", "aes", "hashing"] }
nym-ecash-contract-common = { path = "../cosmwasm-smart-contracts/ecash-contract" }
nym-ecash-time = { path = "../ecash-time" }
nym-network-defaults = { path = "../network-defaults" }
nym-task = { path = "../task" }
nym-validator-client = { path = "../client-libs/validator-client", default-features = false }
-3
View File
@@ -21,9 +21,6 @@ pub enum BandwidthControllerError {
#[error("There was a credential storage error - {0}")]
CredentialStorageError(Box<dyn std::error::Error + Send + Sync>),
#[error("retrieved upgrade mode token is not a valid String")]
MalformedUpgradeModeToken,
#[error("the credential storage does not contain any usable credentials")]
NoCredentialsAvailable,
+1 -14
View File
@@ -12,7 +12,7 @@ use crate::utils::{
ApiClientsWrapper,
};
use log::error;
use nym_credential_storage::models::{EmergencyCredential, RetrievedTicketbook};
use nym_credential_storage::models::RetrievedTicketbook;
use nym_credential_storage::storage::Storage;
use nym_credentials::ecash::bandwidth::CredentialSpendingData;
use nym_credentials_interface::{
@@ -220,19 +220,6 @@ impl<C, St: Storage> BandwidthController<C, St> {
}
}
}
pub async fn get_emergency_credential(
&self,
typ: &str,
) -> Result<Option<EmergencyCredential>, BandwidthControllerError>
where
<St as Storage>::StorageError: Send + Sync + 'static,
{
self.storage
.get_emergency_credential(typ)
.await
.map_err(BandwidthControllerError::credential_storage_error)
}
}
impl<C, St> Clone for BandwidthController<C, St>
-17
View File
@@ -11,9 +11,6 @@ use crate::{error::BandwidthControllerError, BandwidthController, PreparedCreden
pub const DEFAULT_TICKETS_TO_SPEND: u32 = 1;
// TODO: this does not really belong here
pub const UPGRADE_MODE_JWT_TYPE: &str = "UPGRADE_MODE_JWT";
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait BandwidthTicketProvider: Send + Sync {
@@ -23,8 +20,6 @@ pub trait BandwidthTicketProvider: Send + Sync {
gateway_id: ed25519::PublicKey,
tickets_to_spend: u32,
) -> Result<PreparedCredential, BandwidthControllerError>;
async fn get_upgrade_mode_token(&self) -> Result<Option<String>, BandwidthControllerError>;
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
@@ -44,16 +39,4 @@ where
self.prepare_ecash_ticket(ticket_type, gateway_id.to_bytes(), tickets_to_spend)
.await
}
async fn get_upgrade_mode_token(&self) -> Result<Option<String>, BandwidthControllerError> {
let Some(emergency_credential) =
self.get_emergency_credential(UPGRADE_MODE_JWT_TYPE).await?
else {
return Ok(None);
};
// upgrade mode credential is just a simple stringified JWT
let token = String::from_utf8(emergency_credential.data.content)
.map_err(|_| BandwidthControllerError::MalformedUpgradeModeToken)?;
Ok(Some(token))
}
}
+1 -3
View File
@@ -29,8 +29,6 @@ time = { workspace = true }
tokio = { workspace = true, features = ["sync", "macros"] }
tracing = { workspace = true }
zeroize = { workspace = true }
once_cell = "1.19"
# internal
nym-id = { path = "../nym-id" }
@@ -38,7 +36,7 @@ nym-bandwidth-controller = { path = "../bandwidth-controller" }
nym-crypto = { path = "../crypto" }
nym-gateway-client = { path = "../client-libs/gateway-client" }
nym-gateway-requests = { path = "../gateway-requests" }
nym-http-api-client = { path = "../http-api-client", features = ["network-defaults"] }
nym-http-api-client = { path = "../http-api-client" }
nym-nonexhaustive-delayqueue = { path = "../nonexhaustive-delayqueue" }
nym-sphinx = { path = "../nymsphinx" }
nym-statistics-common = { path = "../statistics" }
@@ -81,10 +81,6 @@ pub struct CommonClientInitArgs {
#[cfg_attr(feature = "cli", clap(long, hide = true))]
pub enabled_credentials_mode: Option<bool>,
/// Change the default minimum node performance used during initial node selection filtering.
#[cfg_attr(feature = "cli", clap(long, hide = true))]
pub minimum_gateway_performance: Option<u8>,
/// Mostly debug-related option to increase default traffic rate so that you would not need to
/// modify config post init
#[cfg_attr(feature = "cli", clap(long, hide = true))]
@@ -177,14 +173,10 @@ where
})?;
hardcoded_topology.entry_capable_nodes().cloned().collect()
} else {
let minimum_performance = common_args
.minimum_gateway_performance
.unwrap_or(core.debug.topology.minimum_gateway_performance);
crate::init::helpers::gateways_for_init(
&core.client.nym_api_urls,
user_agent,
minimum_performance,
core.debug.topology.minimum_gateway_performance,
core.debug.topology.ignore_ingress_epoch_role,
None,
)
+16 -134
View File
@@ -73,10 +73,6 @@ use url::Url;
#[cfg(debug_assertions)]
use wasm_utils::console_log;
/// Default number of retries for Nym API requests when using network details with domain fronting.
/// This allows the client to try alternative URLs if the primary endpoint is unavailable.
const DEFAULT_NYM_API_RETRIES: usize = 3;
#[cfg(all(
not(target_arch = "wasm32"),
feature = "fs-surb-storage",
@@ -216,9 +212,6 @@ pub struct BaseClientBuilder<C, S: MixnetClientStorage> {
client_store: S,
dkg_query_client: Option<C>,
// Optional API URLs for domain fronting support
nym_api_urls: Option<Vec<nym_network_defaults::ApiUrl>>,
wait_for_gateway: bool,
custom_topology_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
custom_gateway_transceiver: Option<Box<dyn GatewayTransceiver + Send>>,
@@ -248,7 +241,6 @@ where
config: base_config,
client_store,
dkg_query_client,
nym_api_urls: None,
wait_for_gateway: false,
custom_topology_provider: None,
custom_gateway_transceiver: None,
@@ -271,16 +263,6 @@ where
self
}
/// Set Nym API URLs for domain fronting support.
///
/// When provided, the client will use these API URLs (which include front_hosts)
/// to construct HTTP clients with domain fronting enabled.
#[must_use]
pub fn with_nym_api_urls(mut self, nym_api_urls: Vec<nym_network_defaults::ApiUrl>) -> Self {
self.nym_api_urls = Some(nym_api_urls);
self
}
#[must_use]
pub fn with_forget_me(mut self, forget_me: &ForgetMe) -> Self {
self.config.debug.forget_me = *forget_me;
@@ -801,7 +783,7 @@ where
event_tx,
);
let mix_tx = mix_traffic_controller.mix_tx();
let mix_tx = mix_traffic_controller.mix_rx();
let client_tx = mix_traffic_controller.client_tx();
shutdown_tracker.try_spawn_named(
@@ -881,67 +863,21 @@ where
}
fn construct_nym_api_client(
nym_api_urls: Option<&Vec<nym_network_defaults::ApiUrl>>,
config: &Config,
user_agent: Option<UserAgent>,
) -> Result<nym_http_api_client::Client, ClientCoreError> {
tracing::debug!(
"construct_nym_api_client called with nym_api_urls: {}",
nym_api_urls.is_some()
);
// If API URLs are provided, use new_with_fronted_urls() which handles domain fronting
if let Some(nym_api_urls) = nym_api_urls {
if nym_api_urls.is_empty() {
tracing::warn!("Provided nym_api_urls is empty, falling back to config endpoints");
} else {
tracing::info!(
"Building nym-api client from provided URLs (with domain fronting support): {} URLs",
nym_api_urls.len()
);
let mut builder =
nym_http_api_client::ClientBuilder::new_with_fronted_urls(nym_api_urls.clone())
.map_err(ClientCoreError::from)?
.with_retries(DEFAULT_NYM_API_RETRIES);
if let Some(user_agent) = user_agent {
builder = builder.with_user_agent(user_agent);
}
return builder.build().map_err(ClientCoreError::from);
}
}
// Fallback to basic client for backwards compatibility
tracing::debug!("Building basic nym-api HTTP client from config endpoints");
let mut nym_api_urls = config.get_nym_api_endpoints();
if nym_api_urls.is_empty() {
tracing::warn!("No API endpoints configured in config, this may cause issues");
}
nym_api_urls.shuffle(&mut thread_rng());
// Convert config URLs to ApiUrl format for consistency
let api_urls: Vec<nym_network_defaults::ApiUrl> = nym_api_urls
.into_iter()
.map(|url| nym_network_defaults::ApiUrl {
url: url.to_string(),
front_hosts: None,
})
.collect();
tracing::debug!("Using {} config API endpoints", api_urls.len());
let mut builder = nym_http_api_client::ClientBuilder::new_with_fronted_urls(api_urls)
.map_err(ClientCoreError::from)?
.with_retries(DEFAULT_NYM_API_RETRIES)
.with_bincode();
let mut builder = nym_http_api_client::Client::builder(nym_api_urls[0].clone())
.map_err(ClientCoreError::from)?;
if let Some(user_agent) = user_agent {
builder = builder.with_user_agent(user_agent);
}
builder = builder.with_bincode();
builder.build().map_err(ClientCoreError::from)
}
@@ -1004,8 +940,8 @@ where
// Create a shutdown tracker for this client - either as a child of provided tracker
// or get one from the registry
let shutdown_tracker = match self.shutdown {
Some(parent_tracker) => parent_tracker.clone(),
None => nym_task::create_sdk_shutdown_tracker()?,
Some(parent_tracker) => parent_tracker.child_tracker(),
None => nym_task::get_sdk_shutdown_tracker()?,
};
Self::start_event_control(self.event_tx, event_receiver, &shutdown_tracker);
@@ -1025,11 +961,7 @@ where
.dkg_query_client
.map(|client| BandwidthController::new(credential_store, client));
let nym_api_client = Self::construct_nym_api_client(
self.nym_api_urls.as_ref(),
&self.config,
self.user_agent.clone(),
)?;
let nym_api_client = Self::construct_nym_api_client(&self.config, self.user_agent.clone())?;
let key_rotation_config = Self::determine_key_rotation_state(&nym_api_client).await?;
let topology_provider = Self::setup_topology_provider(
@@ -1044,7 +976,7 @@ where
self.user_agent.clone(),
generate_client_stats_id(*self_address.identity()),
input_sender.clone(),
&shutdown_tracker.clone(),
&shutdown_tracker.child_tracker(),
);
// needs to be started as the first thing to block if required waiting for the gateway
@@ -1054,7 +986,7 @@ where
shared_topology_accessor.clone(),
self_address.gateway(),
self.wait_for_gateway,
&shutdown_tracker.clone(),
&shutdown_tracker.child_tracker(),
)
.await?;
@@ -1074,7 +1006,7 @@ where
stats_reporter.clone(),
#[cfg(unix)]
self.connection_fd_callback,
&shutdown_tracker.clone(),
&shutdown_tracker.child_tracker(),
)
.await?;
let gateway_ws_fd = gateway_transceiver.ws_fd();
@@ -1082,7 +1014,7 @@ where
let reply_storage = Self::setup_persistent_reply_storage(
reply_storage_backend,
key_rotation_config,
&shutdown_tracker.clone(),
&shutdown_tracker.child_tracker(),
)
.await?;
@@ -1093,7 +1025,7 @@ where
reply_storage.key_storage(),
reply_controller_sender.clone(),
stats_reporter.clone(),
&shutdown_tracker.clone(),
&shutdown_tracker.child_tracker(),
);
// The message_sender is the transmitter for any component generating sphinx packets
@@ -1103,7 +1035,7 @@ where
let (message_sender, client_request_sender) = Self::start_mix_traffic_controller(
gateway_transceiver,
&shutdown_tracker.clone(),
&shutdown_tracker.child_tracker(),
EventSender(event_sender),
);
@@ -1134,7 +1066,7 @@ where
shared_lane_queue_lengths.clone(),
client_connection_rx,
stats_reporter.clone(),
&shutdown_tracker.clone(),
&shutdown_tracker.child_tracker(),
);
if !self
@@ -1150,7 +1082,7 @@ where
shared_topology_accessor.clone(),
message_sender,
stats_reporter.clone(),
&shutdown_tracker.clone(),
&shutdown_tracker.child_tracker(),
);
}
@@ -1204,53 +1136,3 @@ pub struct BaseClient {
pub forget_me: ForgetMe,
pub remember_me: RememberMe,
}
#[cfg(test)]
mod tests {
use super::*;
use nym_network_defaults::{ApiUrl, NymNetworkDetails};
#[test]
fn test_network_details_with_multiple_urls() {
// Verify that network details can be configured with multiple API URLs
let mut network_details = NymNetworkDetails::new_empty();
network_details.nym_api_urls = Some(vec![
ApiUrl {
url: "https://validator.nymtech.net/api/".to_string(),
front_hosts: None,
},
ApiUrl {
url: "https://nym-frontdoor.vercel.app/api/".to_string(),
front_hosts: Some(vec!["vercel.app".to_string(), "vercel.com".to_string()]),
},
]);
assert_eq!(network_details.nym_api_urls.as_ref().unwrap().len(), 2);
assert!(network_details.nym_api_urls.as_ref().unwrap()[1]
.front_hosts
.is_some());
}
#[test]
fn test_network_details_with_front_hosts() {
// Verify that ApiUrl can store domain fronting configuration
let api_url = ApiUrl {
url: "https://nym-frontdoor.vercel.app/api/".to_string(),
front_hosts: Some(vec!["vercel.app".to_string(), "vercel.com".to_string()]),
};
assert_eq!(api_url.url, "https://nym-frontdoor.vercel.app/api/");
assert_eq!(api_url.front_hosts.as_ref().unwrap().len(), 2);
assert!(api_url
.front_hosts
.as_ref()
.unwrap()
.contains(&"vercel.app".to_string()));
}
#[test]
fn test_default_nym_api_retries_constant() {
// Verify the retry constant is set correctly
assert_eq!(DEFAULT_NYM_API_RETRIES, 3);
}
}
@@ -205,7 +205,7 @@ impl LoopCoverTrafficStream<OsRng> {
TrySendError::Full(_) => {
// This isn't a problem, if the channel is full means we're already sending the
// max amount of messages downstream can handle.
tracing::trace!("Failed to send cover message - channel full");
tracing::debug!("Failed to send cover message - channel full");
}
TrySendError::Closed(_) => {
tracing::warn!("Failed to send cover message - channel closed");
@@ -1,13 +1,11 @@
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::rtt_analyzer::{RttConfig, RttEvent};
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_sphinx::forwarding::packet::MixPacket;
use nym_sphinx::params::PacketType;
use nym_task::connections::TransmissionLane;
use tokio::sync::mpsc::Sender;
pub type InputMessageSender = tokio::sync::mpsc::Sender<InputMessage>;
pub type InputMessageReceiver = tokio::sync::mpsc::Receiver<InputMessage>;
@@ -48,13 +46,6 @@ pub enum InputMessage {
lane: TransmissionLane,
max_retransmissions: Option<u32>,
},
RunRTTTest {
recipient: Recipient,
lane: TransmissionLane,
max_retransmissions: Option<u32>,
sender: Sender<RttEvent>,
config: RttConfig,
},
/// Attempt to use our internally received and stored `ReplySurb` to send the message back
/// to specified recipient whilst not knowing its full identity (or even gateway).
@@ -159,7 +150,6 @@ impl InputMessage {
match self {
InputMessage::Regular { lane, .. }
| InputMessage::Anonymous { lane, .. }
| InputMessage::RunRTTTest { lane, .. }
| InputMessage::Reply { lane, .. }
| InputMessage::Premade { lane, .. } => lane,
InputMessage::MessageWrapper { message, .. } => message.lane(),
@@ -176,10 +166,6 @@ impl InputMessage {
max_retransmissions: m,
..
}
| InputMessage::RunRTTTest {
max_retransmissions: m,
..
}
| InputMessage::Reply {
max_retransmissions: m,
..
@@ -20,10 +20,7 @@ pub mod transceiver;
// We remind ourselves that 32 x 32kb = 1024kb, a reasonable size for a network buffer.
pub const MIX_MESSAGE_RECEIVER_BUFFER_SIZE: usize = 32;
/// Reduced from 100 to 20 to fail fast (~1-2 seconds instead of ~6 seconds).
/// If we can't send 20 packets in a row, the gateway is unreachable.
const MAX_FAILURE_COUNT: usize = 20;
const MAX_FAILURE_COUNT: usize = 100;
// that's also disgusting.
pub struct Empty;
@@ -87,7 +84,7 @@ impl MixTrafficController {
self.client_tx.clone()
}
pub fn mix_tx(&self) -> BatchMixMessageSender {
pub fn mix_rx(&self) -> BatchMixMessageSender {
self.mix_tx.clone()
}
@@ -159,11 +156,6 @@ impl MixTrafficController {
// Do we need to handle the embedded mixnet client case
// separately?
self.event_tx.send(MixnetClientEvent::Traffic(MixTrafficEvent::FailedSendingSphinx));
// IMO it shouldn't be signalled from there but it is how it is
// TODO : report the failure upwards and shutdown from upwards
// Gateway is dead, we have to shut down currently
error!("Signalling shutdown from the MixTrafficController");
self.shutdown_token.cancel();
break;
}
}
-1
View File
@@ -11,7 +11,6 @@ pub mod mix_traffic;
pub mod real_messages_control;
pub mod received_buffer;
pub mod replies;
pub mod rtt_analyzer;
pub mod statistics_control;
pub mod topology_control;
pub(crate) mod transmission_buffer;
@@ -4,7 +4,6 @@
use super::action_controller::{AckActionSender, Action};
use nym_statistics_common::clients::{packet_statistics::PacketStatisticsEvent, ClientStatsSender};
use crate::client::rtt_analyzer::{RttAnalyzer, RttEvent};
use futures::StreamExt;
use nym_gateway_client::AcknowledgementReceiver;
use nym_sphinx::{
@@ -13,7 +12,6 @@ use nym_sphinx::{
};
use nym_task::ShutdownToken;
use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};
use tracing::*;
/// Module responsible for listening for any data resembling acknowledgements from the network
@@ -40,11 +38,7 @@ impl AcknowledgementListener {
}
}
async fn on_ack(
&mut self,
ack_content: Vec<u8>,
rtt_producer: Option<tokio::sync::mpsc::Sender<RttEvent>>,
) {
async fn on_ack(&mut self, ack_content: Vec<u8>) {
trace!("Received an ack");
self.stats_tx
.report(PacketStatisticsEvent::AckReceived(ack_content.len()).into());
@@ -68,16 +62,6 @@ impl AcknowledgementListener {
return;
}
if let Some(ref producer) = rtt_producer {
if let Ok(duration) = SystemTime::now().duration_since(UNIX_EPOCH) {
let now = duration.as_millis();
let _ = producer.try_send(RttEvent::FragmentAckReceived {
fragment_id: frag_id.set_id().to_string(),
timestamp: now,
});
}
}
trace!("Received {frag_id} from the mix network");
self.stats_tx
.report(PacketStatisticsEvent::RealAckReceived(ack_content.len()).into());
@@ -86,20 +70,15 @@ impl AcknowledgementListener {
.unbounded_send(Action::new_remove(frag_id));
}
async fn handle_ack_receiver_item(
&mut self,
item: Vec<Vec<u8>>,
rtt_producer: Option<tokio::sync::mpsc::Sender<RttEvent>>,
) {
async fn handle_ack_receiver_item(&mut self, item: Vec<Vec<u8>>) {
// realistically we would only be getting one ack at the time
for ack in item {
self.on_ack(ack, rtt_producer.clone()).await;
self.on_ack(ack).await;
}
}
pub(crate) async fn run(&mut self, shutdown_token: ShutdownToken) {
debug!("Started AcknowledgementListener with graceful shutdown support");
let rtt_producer = RttAnalyzer::producer();
loop {
tokio::select! {
@@ -109,7 +88,7 @@ impl AcknowledgementListener {
break;
}
acks = self.ack_receiver.next() => match acks {
Some(acks) => self.handle_ack_receiver_item(acks,rtt_producer.clone()).await,
Some(acks) => self.handle_ack_receiver_item(acks).await,
None => {
tracing::trace!("AcknowledgementListener: Stopping since channel closed");
break;
@@ -3,7 +3,6 @@
use super::PendingAcknowledgement;
use crate::client::real_messages_control::acknowledgement_control::RetransmissionRequestSender;
use crate::client::rtt_analyzer::{RttAnalyzer, RttEvent};
use futures::channel::mpsc;
use futures::StreamExt;
use nym_nonexhaustive_delayqueue::{Expired, NonExhaustiveDelayQueue, QueueKey};
@@ -17,7 +16,6 @@ use tracing::*;
pub(crate) type AckActionSender = mpsc::UnboundedSender<Action>;
pub(crate) type AckActionReceiver = mpsc::UnboundedReceiver<Action>;
use std::time::{SystemTime, UNIX_EPOCH};
// The actual data being sent off as well as potential key to the delay queue
type PendingAckEntry = (Arc<PendingAcknowledgement>, Option<QueueKey>);
@@ -209,22 +207,8 @@ impl ActionController {
// note: when the entry expires it's automatically removed from pending_acks_timers
#[allow(clippy::panic)]
fn handle_expired_ack_timer(
&mut self,
expired_ack: Expired<FragmentIdentifier>,
rtt_producer: Option<tokio::sync::mpsc::Sender<RttEvent>>,
) {
fn handle_expired_ack_timer(&mut self, expired_ack: Expired<FragmentIdentifier>) {
let frag_id = expired_ack.into_inner();
if let Some(ref producer) = rtt_producer {
if let Ok(duration) = SystemTime::now().duration_since(UNIX_EPOCH) {
let now: u128 = duration.as_millis();
let _ = producer.try_send(RttEvent::FragmentAckExpired {
fragment_id: frag_id.set_id().to_string(),
timestamp: now,
});
}
}
trace!("{frag_id} has expired");
@@ -260,7 +244,7 @@ impl ActionController {
pub(crate) async fn run(&mut self, shutdown_token: ShutdownToken) {
debug!("Started ActionController with graceful shutdown support");
let rtt_producer = RttAnalyzer::producer();
loop {
tokio::select! {
biased;
@@ -278,7 +262,7 @@ impl ActionController {
}
},
expired_ack = self.pending_acks_timers.next() => match expired_ack {
Some(expired_ack) => self.handle_expired_ack_timer(expired_ack,rtt_producer.clone()),
Some(expired_ack) => self.handle_expired_ack_timer(expired_ack),
None => {
tracing::trace!("ActionController: Stopping since ack channel closed");
break;
@@ -5,7 +5,6 @@ use crate::client::inbound_messages::{InputMessage, InputMessageReceiver};
use crate::client::real_messages_control::message_handler::MessageHandler;
use crate::client::real_messages_control::real_traffic_stream::RealMessage;
use crate::client::replies::reply_controller::ReplyControllerSender;
use crate::client::rtt_analyzer::{RttConfig, RttEvent};
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_sphinx::forwarding::packet::MixPacket;
@@ -13,7 +12,6 @@ use nym_sphinx::params::PacketType;
use nym_task::connections::TransmissionLane;
use nym_task::ShutdownToken;
use rand::{CryptoRng, Rng};
use tokio::sync::mpsc::Sender;
use tracing::*;
/// Module responsible for dealing with the received messages: splitting them, creating acknowledgements,
@@ -113,30 +111,7 @@ where
warn!("failed to send a repliable message - {err}")
}
}
async fn run_rtt_test(
&mut self,
recipient: Recipient,
lane: TransmissionLane,
packet_type: PacketType,
max_retransmissions: Option<u32>,
sender: Sender<RttEvent>,
config: RttConfig,
) {
if let Err(err) = self
.message_handler
.try_run_rtt_test(
recipient,
lane,
packet_type,
max_retransmissions,
sender,
config,
)
.await
{
warn!("failed to send a repliable message - {err}")
}
}
#[allow(clippy::panic)]
async fn on_input_message(&mut self, msg: InputMessage) {
match msg {
@@ -172,23 +147,6 @@ where
)
.await
}
InputMessage::RunRTTTest {
recipient,
lane,
max_retransmissions,
sender,
config,
} => {
self.run_rtt_test(
recipient,
lane,
PacketType::Mix,
max_retransmissions,
sender,
config,
)
.await
}
InputMessage::Reply {
recipient_tag,
data,
@@ -218,23 +176,6 @@ where
)
.await
}
InputMessage::RunRTTTest {
recipient,
lane,
max_retransmissions,
sender,
config,
} => {
self.run_rtt_test(
recipient,
lane,
PacketType::Mix,
max_retransmissions,
sender,
config,
)
.await
}
InputMessage::Anonymous {
recipient,
data,
@@ -8,7 +8,6 @@ use crate::client::real_messages_control::real_traffic_stream::{
use crate::client::real_messages_control::{AckActionSender, Action};
use crate::client::replies::reply_controller::MaxRetransmissions;
use crate::client::replies::reply_storage::{ReceivedReplySurbsMap, SentReplyKeys, UsedSenderTags};
use crate::client::rtt_analyzer::{RttConfig, RttEvent, RttPattern};
use crate::client::topology_control::{TopologyAccessor, TopologyReadPermit};
use nym_client_core_surb_storage::RetrievedReplySurb;
use nym_sphinx::acknowledgements::AckKey;
@@ -28,8 +27,6 @@ use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use thiserror::Error;
use tokio::sync::mpsc::Sender;
use tokio::time::sleep;
use tracing::{debug, error, info, trace, warn};
// TODO: move that error elsewhere since it seems to be contaminating different files
@@ -558,97 +555,6 @@ where
Ok(())
}
pub(crate) async fn try_split_and_send_non_reply_rtt_message(
&mut self,
sender_tag: AnonymousSenderTag,
recipient: Recipient,
lane: TransmissionLane,
packet_type: PacketType,
topology: &NymRouteProvider,
max_retransmissions: Option<u32>,
route_index: usize,
sender: Sender<RttEvent>,
) -> Result<(), PreparationError> {
debug!("Sending RTT test message on route index {route_index} with packet type {packet_type:?}");
// Construct the base message
let message = NymMessage::new_repliable(RepliableMessage::new_data(
self.config.use_legacy_sphinx_format,
Vec::new(),
sender_tag,
Vec::new(),
));
debug_assert!(!matches!(message, NymMessage::Reply(_)));
let packet_size = if packet_type == PacketType::Outfox {
PacketSize::OutfoxRegularPacket
} else {
self.optimal_packet_size(&message)
};
trace!("Using packet size {packet_size:?}");
// ✅ Drop the read lock before mutably borrowing self
// Prepare fragments from message
let fragments = self
.message_preparer
.pad_and_split_message(message, packet_size);
if fragments.len() > 1 {
println!(
"[RTT TEST] Warning: message was split into {} fragments",
fragments.len()
);
}
let mut pending_acks = Vec::with_capacity(fragments.len());
let mut real_messages = Vec::with_capacity(fragments.len());
for fragment in &fragments {
let prepared_fragment = self
.message_preparer
.prepare_chunk_for_sending_with_deterministic_route(
fragment.clone(),
&topology,
&self.config.ack_key,
&recipient,
packet_type,
route_index,
)?;
let _ = sender.try_send(RttEvent::RouteUsed {
route_index,
fragment_id: (fragment.fragment_identifier().set_id().to_string()),
});
let real_message = RealMessage::new(
prepared_fragment.mix_packet,
Some(fragment.fragment_identifier().clone()),
);
let pending_ack = PendingAcknowledgement::new_known(
fragment.clone(),
prepared_fragment.total_delay,
recipient,
max_retransmissions,
);
real_messages.push(real_message);
pending_acks.push(pending_ack);
}
// Record ACKs and forward messages for *this route only*
self.insert_pending_acks(pending_acks);
self.forward_messages(real_messages, lane).await;
// // Optional: small delay to avoid flooding
// sleep(Duration::from_millis(200)).await;
Ok(())
}
pub(crate) async fn try_send_additional_reply_surbs(
&mut self,
recipient: Recipient,
@@ -727,142 +633,6 @@ where
Ok(())
}
// Helper: sends ONE RTT packet on ONE specific route.
// Rust requires this to be a standalone async function (not an async closure),
// because async closures cannot borrow local variables safely.
async fn send_packet_on_route(
&mut self,
recipient: &Recipient,
num_reply_surbs: u32,
lane: TransmissionLane,
packet_type: PacketType,
topology: &NymRouteProvider,
max_retransmissions: Option<u32>,
route_index: usize,
sender: &Sender<RttEvent>,
) -> Result<(), SurbWrappedPreparationError> {
let sender_tag = self.get_or_create_sender_tag(recipient);
// Prepare reply SURBs
let reply_surbs = self.generate_reply_surbs(num_reply_surbs as usize).await?;
let reply_keys = reply_surbs
.iter()
.map(|s| *s.encryption_key())
.collect::<Vec<_>>();
// Send message on the given route
self.try_split_and_send_non_reply_rtt_message(
sender_tag,
recipient.clone(),
lane,
packet_type,
topology,
max_retransmissions,
route_index,
sender.clone(),
)
.await?;
// Store reply keys after sending
self.reply_key_storage.insert_multiple(reply_keys);
Ok(())
}
pub(crate) async fn try_run_rtt_test(
&mut self,
recipient: Recipient,
lane: TransmissionLane,
packet_type: PacketType,
max_retransmissions: Option<u32>,
sender: Sender<RttEvent>,
config: RttConfig,
) -> Result<(), SurbWrappedPreparationError> {
debug!("Starting RTT test using pattern {:?}", config.pattern);
// Load topology
let topology_permit = self.topology_access.get_read_permit().await;
let mut topology = self.get_topology(&topology_permit)?.clone();
let route_strings = topology
.topology
.initialize_static_mixnodes_for_rtt_testing()?;
let total_routes = topology.topology.all_mix_routes.len();
drop(topology_permit);
// =====================================================
// SEND ROUTE STRINGS TO RTT ANALYZER USING try_send()
// =====================================================
for (route_index, nodes_string) in route_strings {
let _ = sender.try_send(RttEvent::RouteNodes {
route_index,
nodes: nodes_string,
});
}
sender
.send(RttEvent::ExperimentConfiguration {
total_routes: total_routes as usize,
per_route_sent: config.packets_per_route as usize,
})
.await
.unwrap();
// ==============================================================
// PATTERN: BURST
// Send packets_per_route packets on each route sequentially
// ==============================================================
if let RttPattern::Burst = config.pattern {
for route in 0..total_routes {
for _ in 0..config.packets_per_route {
self.send_packet_on_route(
&recipient,
0,
lane,
packet_type,
&topology,
max_retransmissions,
route,
&sender,
)
.await?;
// Optional delay between packets on the same route
if config.inter_route_delay_ms > 0 {
tokio::time::sleep(Duration::from_millis(config.inter_route_delay_ms))
.await;
}
}
}
return Ok(());
}
// ==============================================================
// PATTERN: ROUND ROBIN
// Send packets in cycles: 1 on route 0, 1 on route 1, ..., repeat
// ==============================================================
if let RttPattern::RoundRobin = config.pattern {
for _cycle in 0..config.packets_per_route {
for route in 0..total_routes {
self.send_packet_on_route(
&recipient,
0,
lane,
packet_type,
&topology,
max_retransmissions,
route,
&sender,
)
.await?;
if config.inter_route_delay_ms > 0 {
tokio::time::sleep(Duration::from_millis(config.inter_route_delay_ms))
.await;
}
}
}
return Ok(());
}
Ok(())
}
pub(crate) async fn try_prepare_single_chunk_for_sending(
&mut self,
@@ -4,7 +4,6 @@
use self::sending_delay_controller::SendingDelayController;
use crate::client::mix_traffic::BatchMixMessageSender;
use crate::client::real_messages_control::acknowledgement_control::SentPacketNotificationSender;
use crate::client::rtt_analyzer::{RttAnalyzer, RttEvent};
use crate::client::topology_control::TopologyAccessor;
use crate::client::transmission_buffer::TransmissionBuffer;
use crate::config;
@@ -22,7 +21,6 @@ use nym_statistics_common::clients::{packet_statistics::PacketStatisticsEvent, C
use nym_task::connections::{
ConnectionCommand, ConnectionCommandReceiver, ConnectionId, LaneQueueLengths, TransmissionLane,
};
use nym_task::ShutdownToken;
use rand::{CryptoRng, Rng};
use std::pin::Pin;
@@ -226,11 +224,7 @@ where
}
}
async fn on_message(
&mut self,
next_message: StreamMessage,
rtt_producer: Option<tokio::sync::mpsc::Sender<RttEvent>>,
) {
async fn on_message(&mut self, next_message: StreamMessage) {
trace!("created new message");
let (next_message, fragment_id, packet_size) = match next_message {
@@ -277,21 +271,6 @@ where
)
}
StreamMessage::Real(real_message) => {
if let Some(ref producer) = rtt_producer {
if let Some(fragment_id_local) = real_message.fragment_id {
use std::time::{SystemTime, UNIX_EPOCH};
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis();
let _ = producer.try_send(RttEvent::FragmentSent {
fragment_id: (fragment_id_local.set_id().to_string()),
timestamp: (now),
});
}
}
let packet_size = real_message.packet_size();
(
real_message.mix_packet,
@@ -319,8 +298,6 @@ where
"failed to send mixnet packet due to closed channel (outside of shutdown!)"
);
}
// Early return to avoid further processing when channel is closed
return;
}
Ok(_) => {
let event = if fragment_id.is_some() {
@@ -605,7 +582,7 @@ where
pub(crate) async fn run(&mut self) {
debug!("Started OutQueueControl with graceful shutdown support");
let rtt_producer = RttAnalyzer::producer();
// avoid borrow on self
let shutdown_token = self.shutdown_token.clone();
#[cfg(not(target_arch = "wasm32"))]
@@ -623,7 +600,7 @@ where
self.log_status();
}
next_message = self.next() => if let Some(next_message) = next_message {
self.on_message(next_message,rtt_producer.clone()).await;
self.on_message(next_message).await;
} else {
tracing::trace!("OutQueueControl: Stopping since channel closed");
break;
@@ -5,7 +5,6 @@ use crate::client::helpers::get_time_now;
use crate::client::replies::{
reply_controller::ReplyControllerSender, reply_storage::SentReplyKeys,
};
use crate::client::rtt_analyzer::{RttAnalyzer, RttEvent};
use futures::channel::mpsc;
use futures::lock::Mutex;
use futures::StreamExt;
@@ -56,7 +55,6 @@ struct ReceivedMessagesBufferInner<R: MessageReceiver> {
// Periodically check for stale buffers to clean up
last_stale_check: crate::client::helpers::Instant,
rtt_producer: Option<tokio::sync::mpsc::Sender<RttEvent>>,
}
impl<R: MessageReceiver> ReceivedMessagesBufferInner<R> {
@@ -83,20 +81,7 @@ impl<R: MessageReceiver> ReceivedMessagesBufferInner<R> {
warn!("failed to recover fragment from raw data: {err}. The whole underlying message might be corrupted and unrecoverable!");
return None;
}
Ok(frag) => {
if let Some(ref producer) = self.rtt_producer {
use std::time::{SystemTime, UNIX_EPOCH};
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis();
let _ = producer.try_send(RttEvent::FragmentReceived {
fragment_id: frag.id().to_string(),
timestamp: now,
});
}
frag
}
Ok(frag) => frag,
};
if self.recently_reconstructed.contains(&fragment.id()) {
@@ -196,7 +181,6 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
reply_controller_sender: ReplyControllerSender,
stats_tx: ClientStatsSender,
shutdown_token: ShutdownToken,
rtt_producer: Option<tokio::sync::mpsc::Sender<RttEvent>>,
) -> Self {
ReceivedMessagesBuffer {
inner: Arc::new(Mutex::new(ReceivedMessagesBufferInner {
@@ -207,7 +191,6 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
recently_reconstructed: HashSet::new(),
stats_tx,
last_stale_check: get_time_now(),
rtt_producer: rtt_producer.clone(),
})),
reply_key_storage,
reply_controller_sender,
@@ -602,7 +585,6 @@ impl<R: MessageReceiver + Clone + Send + 'static> ReceivedMessagesBufferControll
reply_controller_sender,
metrics_reporter,
shutdown_token.clone(),
RttAnalyzer::producer(),
);
ReceivedMessagesBufferController {
@@ -1,514 +0,0 @@
use std::collections::HashMap;
use std::fs::File;
use std::io::{BufWriter, Write};
use once_cell::sync::OnceCell;
use std::process::Command;
use std::sync::Mutex;
use tokio::sync::mpsc::{self, Sender};
use tokio::task::JoinHandle;
#[derive(Debug, Clone)]
pub enum RttPattern {
Burst,
RoundRobin,
}
#[derive(Debug, Clone)]
pub struct RttConfig {
pub packets_per_route: u32,
pub pattern: RttPattern,
pub inter_route_delay_ms: u64,
}
#[derive(Debug, Clone)]
pub enum RttEvent {
RouteUsed {
route_index: usize,
fragment_id: String,
},
FragmentSent {
fragment_id: String,
timestamp: u128,
},
FragmentAckReceived {
fragment_id: String,
timestamp: u128,
},
FragmentAckExpired {
fragment_id: String,
timestamp: u128,
},
FragmentReceived {
fragment_id: String,
timestamp: u128,
},
RouteNodes {
route_index: usize,
nodes: String,
},
ExperimentConfiguration {
total_routes: usize,
per_route_sent: usize,
},
PrintRouteDetail {
route_index: usize,
},
PrintRouteStatsByNodes {
nodes: String,
},
PrintRoutesWithAvgAbove {
threshold_ms: u128,
},
PrintRoutesWithAnyAbove {
threshold_ms: u128,
},
PrintStats,
WriteStats {
path: String,
},
WriteStatsAndPlot {
path: String,
outlier_mode: String, // "all" or cutoff() seconds (e.g. "1.0"))
},
PrintExperimentProgress,
}
pub struct StoredRouteSummary {
pub total_routes: usize,
pub per_route_sent: usize,
}
static PRODUCER: OnceCell<Mutex<Option<Sender<RttEvent>>>> = OnceCell::new();
#[derive(Default, Debug)]
pub struct RouteStats {
pub sent: u32,
pub acks: u32,
pub timeouts: u32,
pub rtts: Vec<u128>,
}
pub struct RttAnalyzer {
/// fragment_id → (route, Vec<sent_times>)
fragments: HashMap<String, (usize, Vec<u128>)>,
/// fragment_id → Vec<recv_times>
receive_times: HashMap<String, Vec<u128>>,
/// fragment_id → last ack
ack_times: HashMap<String, u128>,
route_stats: HashMap<usize, RouteStats>,
route_summary: Option<StoredRouteSummary>,
route_nodes: HashMap<usize, String>,
consumer_handle: JoinHandle<()>,
}
impl RttAnalyzer {
pub fn consumer_handle(&self) -> &JoinHandle<()> {
&self.consumer_handle
}
pub fn new() -> Self {
let (tx, mut rx) = mpsc::channel(80000);
PRODUCER
.set(Mutex::new(Some(tx.clone())))
.expect("PRODUCER already initialized");
let handle = tokio::spawn(async move {
let mut analyzer = RttAnalyzer {
fragments: HashMap::new(),
receive_times: HashMap::new(),
ack_times: HashMap::new(),
route_stats: HashMap::new(),
route_summary: None,
route_nodes: HashMap::new(),
consumer_handle: tokio::spawn(async {}),
};
while let Some(event) = rx.recv().await {
analyzer.process(event);
}
println!("RTT Analyzer consumer exited");
});
Self {
fragments: HashMap::new(),
receive_times: HashMap::new(),
ack_times: HashMap::new(),
route_stats: HashMap::new(),
route_summary: None,
route_nodes: HashMap::new(),
consumer_handle: handle,
}
}
pub fn producer() -> Option<Sender<RttEvent>> {
let lock = PRODUCER.get()?.lock().unwrap();
lock.clone()
}
pub fn process(&mut self, event: RttEvent) {
match event {
// -------------------------
// FIRST USE OF A FRAGMENT
// -------------------------
RttEvent::RouteUsed {
route_index,
fragment_id,
} => {
self.fragments
.insert(fragment_id.clone(), (route_index, Vec::new()));
}
// -------------------------
// RETRANSMISSION → append new sent time
// -------------------------
RttEvent::FragmentSent {
fragment_id,
timestamp,
} => {
if let Some((_route, sent_list)) = self.fragments.get_mut(&fragment_id) {
sent_list.push(timestamp);
self.route_stats.entry(*_route).or_default().sent += 1;
}
}
// -------------------------
// ACK RECEIVED
// -------------------------
RttEvent::FragmentAckReceived {
fragment_id,
timestamp,
} => {
if let Some((route, _)) = self.fragments.get(&fragment_id) {
let stats = self.route_stats.entry(*route).or_default();
stats.acks += 1;
}
self.ack_times.insert(fragment_id, timestamp);
}
// -------------------------
// ACK TIMEOUT
// -------------------------
RttEvent::FragmentAckExpired { fragment_id, .. } => {
if let Some((route, _)) = self.fragments.get(&fragment_id) {
self.route_stats.entry(*route).or_default().timeouts += 1;
}
}
RttEvent::WriteStatsAndPlot { path, outlier_mode } => {
// 1) write the csv
if let Err(e) = self.write_csv(&path) {
eprintln!("Failed to write CSV: {}", e);
return;
}
// 2) Call the Python script
if let Err(e) = Self::run_histogram_script(&path, &outlier_mode) {
eprintln!("Failed to run histogram script: {}", e);
}
}
// -------------------------
// PACKET RECEIVED → compute RTTs
// -------------------------
RttEvent::FragmentReceived {
fragment_id,
timestamp,
} => {
// Append receive time
let recv_list = self.receive_times.entry(fragment_id.clone()).or_default();
recv_list.push(timestamp);
// Lookup route + sent times
if let Some((route, sent_list)) = self.fragments.get(&fragment_id) {
let recv_list = self.receive_times.get(&fragment_id).unwrap();
// Index of the *newly added* receive time
let idx = recv_list.len() - 1;
/*
Maybe we can put a retransmission flag and not counting the new RTTs and only the basic N?
*/
//println!("Fragment id: {} Sent list length: {} Recv list length:{}",fragment_id,sent_list.len(),recv_list.len());
// Check if we have a matching sent timestamp
if idx < sent_list.len() {
let sent_ts = sent_list[idx];
let rtt = recv_list[idx] - sent_ts;
self.route_stats.entry(*route).or_default().rtts.push(rtt);
}
}
}
RttEvent::RouteNodes { route_index, nodes } => {
self.route_nodes.insert(route_index, nodes);
}
RttEvent::PrintStats => self.print_stats(),
RttEvent::WriteStats { path } => {
if let Err(e) = self.write_csv(&path) {
eprintln!("Failed to write CSV: {}", e)
}
}
RttEvent::ExperimentConfiguration {
total_routes,
per_route_sent,
} => {
self.route_summary = Some(StoredRouteSummary {
total_routes,
per_route_sent,
});
}
RttEvent::PrintExperimentProgress => {
self.print_experiment_progress();
}
RttEvent::PrintRouteDetail { route_index } => {
self.print_route_detail(route_index);
}
RttEvent::PrintRoutesWithAvgAbove { threshold_ms } => {
self.print_routes_with_avg_above(threshold_ms);
}
RttEvent::PrintRoutesWithAnyAbove { threshold_ms } => {
self.print_routes_with_any_above(threshold_ms);
}
RttEvent::PrintRouteStatsByNodes { nodes } => {
self.print_route_by_nodes(nodes);
}
}
}
// ---------------------- PRINT FUNCTIONS (unchanged) ----------------------
pub fn print_stats(&self) {
println!("\n================ Route RTT Statistics ================");
for (route, stats) in self.route_stats.iter() {
let avg_rtt = if !stats.rtts.is_empty() {
stats.rtts.iter().sum::<u128>() as f64 / stats.rtts.len() as f64
} else {
0.0
};
println!(
"Route {:5} | Sent {:4} | ACKs {:4} | Timeouts {:4} | Avg RTT {:8.2}",
route, stats.sent, stats.acks, stats.timeouts, avg_rtt
);
}
println!("======================================================\n");
}
pub fn write_csv(&self, path: &str) -> std::io::Result<()> {
let mut writer = BufWriter::new(File::create(path)?);
writer.write_all(b"route,sent,acks,timeouts,avg_rtt\n")?;
for (route, stats) in &self.route_stats {
let avg_rtt = if !stats.rtts.is_empty() {
stats.rtts.iter().sum::<u128>() as f64 / stats.rtts.len() as f64
} else {
0.0
};
writer.write_all(
format!(
"{},{},{},{},{:.2}\n",
route, stats.sent, stats.acks, stats.timeouts, avg_rtt
)
.as_bytes(),
)?;
}
Ok(())
}
pub fn print_route_detail(&self, route_index: usize) {
println!(
"\n================ Route #{} Details ================\n",
route_index
);
if let Some(nodes) = self.route_nodes.get(&route_index) {
println!(" Route Nodes:");
for (i, node) in nodes.split(" > ").enumerate() {
println!(" • Node {}: {}", i + 1, node);
}
}
if let Some(stats) = self.route_stats.get(&route_index) {
println!("\n RTT Values:");
for (i, rtt) in stats.rtts.iter().enumerate() {
println!(" [{:3}] {} ms", i, rtt);
}
}
println!("======================================================\n");
}
fn run_histogram_script(csv_path: &str, outlier_mode: &str) -> std::io::Result<()> {
let status = Command::new("python")
.arg("rtt_histogram.py") // path του script
.arg(csv_path)
.arg(outlier_mode)
.status()?;
if !status.success() {
eprintln!("Python histogram script exited with status: {}", status);
}
Ok(())
}
pub fn print_routes_with_avg_above(&self, threshold_ms: u128) {
println!(
"\n======= Routes with AVG RTT > {} ms =======\n",
threshold_ms
);
let mut matches: Vec<usize> = self
.route_stats
.iter()
.filter_map(|(route, stats)| {
if stats.rtts.is_empty() {
return None;
}
let avg = stats.rtts.iter().sum::<u128>() as f64 / stats.rtts.len() as f64;
if (avg as u128) > threshold_ms {
Some(*route)
} else {
None
}
})
.collect();
matches.sort();
for route in matches {
self.print_route_detail(route);
}
println!("====================================================\n");
}
/// Prints overall experiment completion percentage.
///
/// It uses:
/// - self.route_summary.total_routes
/// - self.route_summary.per_route_sent
/// to compute how many packets were planned in total.
///
/// Then it sums, over all routes:
/// - how many packets were actually sent (RouteStats.sent)
/// - how many packets have a completed RTT sample (RouteStats.rtts.len())
///
/// Finally it prints:
/// - total expected packets
/// - total sent packets and percentage
/// - total completed RTT packets and percentage
pub fn print_experiment_progress(&self) {
println!("\n=========== RTT Experiment Progress ===========");
// Check if experiment configuration is available
let summary = match &self.route_summary {
Some(s) => s,
None => {
println!("No experiment configuration stored (route_summary is None).");
println!("You must send an ExperimentConfiguration event first.");
println!("==============================================\n");
return;
}
};
let total_routes = summary.total_routes;
let per_route_sent = summary.per_route_sent;
// Total number of packets that were planned for the whole experiment
let expected_total: usize = total_routes.saturating_mul(per_route_sent);
if expected_total == 0 {
println!("Experiment configuration has zero expected packets.");
println!("==============================================\n");
return;
}
// Sum how many packets were actually sent and how many have a measured RTT
let mut sent_total: usize = 0;
let mut received_total: usize = 0;
for (_route_idx, stats) in &self.route_stats {
// 'sent' counts how many times we called FragmentSent for this route
sent_total += std::cmp::min(stats.sent, per_route_sent as u32) as usize;
// Each RTT entry corresponds to one packet for which we have both send and receive time
let route_recv = std::cmp::min(stats.rtts.len(), per_route_sent);
received_total += route_recv;
}
let sent_pct = (sent_total as f64 / expected_total as f64) * 100.0;
let recv_pct = (received_total as f64 / expected_total as f64) * 100.0;
println!("Total routes configured : {}", total_routes);
println!("Packets per route (planned) : {}", per_route_sent);
println!("Total expected packets : {}", expected_total);
println!("---------------------------------------------");
println!(
"Total sent packets : {} ({:.2}%)",
sent_total, sent_pct
);
println!(
"Total completed RTT packets : {} ({:.2}%)",
received_total, recv_pct
);
println!("==============================================\n");
}
pub fn print_routes_with_any_above(&self, threshold_ms: u128) {
println!(
"\n======= Routes with ANY RTT > {} ms =======\n",
threshold_ms
);
let mut matches: Vec<usize> = self
.route_stats
.iter()
.filter_map(|(route, stats)| {
if stats.rtts.iter().any(|&x| x > threshold_ms) {
Some(*route)
} else {
None
}
})
.collect();
matches.sort();
for route in matches {
self.print_route_detail(route);
}
println!("====================================================\n");
}
pub fn print_route_by_nodes(&self, nodes: String) {
println!("\n========== Searching route by nodes ==========\n");
let mut routes: Vec<(usize, &String)> =
self.route_nodes.iter().map(|(k, v)| (*k, v)).collect();
routes.sort_by_key(|(idx, _)| *idx);
for (route, stored) in routes {
if *stored == nodes {
println!("Found route {}!", route);
self.print_route_detail(route);
return;
}
}
println!("No route found with nodes: {}", nodes);
println!("=============================================\n");
}
}
+1 -1
View File
@@ -45,7 +45,7 @@ pub enum ClientCoreError {
#[cfg(not(target_arch = "wasm32"))]
#[error("resolution failed: {0}")]
ResolutionFailed(#[from] nym_http_api_client::ResolveError),
ResolutionFailed(#[from] nym_http_api_client::HickoryDnsError),
#[error("no gateways on network")]
NoGatewaysOnNetwork,
+2 -2
View File
@@ -151,7 +151,7 @@ pub async fn gateways_for_init(
}
let retry_count = retry_count.unwrap_or(DEFAULT_NYM_API_RETRIES);
let mut builder = nym_http_api_client::ClientBuilder::new_with_urls(nym_api_urls.clone())?
let mut builder = nym_http_api_client::ClientBuilder::new_with_urls(nym_api_urls.clone())
.with_retries(retry_count)
.with_bincode();
@@ -441,7 +441,7 @@ mod tests {
#[test]
fn test_multiple_urls_prepared_for_retries() {
let urls = [
let urls = vec![
Url::parse("https://api1.nym.com").unwrap(),
Url::parse("https://api2.nym.com").unwrap(),
Url::parse("https://api3.nym.com").unwrap(),
@@ -30,6 +30,7 @@ pub(crate) async fn connect_async(
resolver
.resolve_str(domain)
.await?
.into_iter()
.map(|a| SocketAddr::new(a, port))
.collect()
}
@@ -88,6 +88,3 @@ features = ["js"]
[features]
wasm = []
[lints]
workspace = true
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: Apache-2.0
use si_scale::helpers::bibytes2;
use std::fmt::{Display, Formatter};
use std::sync::atomic::{AtomicBool, AtomicI64, Ordering};
use std::sync::Arc;
use std::time::Duration;
@@ -27,39 +26,6 @@ pub struct ClientBandwidth {
inner: Arc<ClientBandwidthInner>,
}
// simple helper for logging purposes to accommodate 'unknown' case
pub(crate) enum UpgradeModeEnabledWrapper {
True,
False,
Unknown,
}
impl From<Option<bool>> for UpgradeModeEnabledWrapper {
fn from(value: Option<bool>) -> Self {
match value {
Some(true) => UpgradeModeEnabledWrapper::True,
Some(false) => UpgradeModeEnabledWrapper::False,
None => UpgradeModeEnabledWrapper::Unknown,
}
}
}
impl From<bool> for UpgradeModeEnabledWrapper {
fn from(value: bool) -> Self {
Some(value).into()
}
}
impl Display for UpgradeModeEnabledWrapper {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
UpgradeModeEnabledWrapper::True => write!(f, "true"),
UpgradeModeEnabledWrapper::False => write!(f, "false"),
UpgradeModeEnabledWrapper::Unknown => write!(f, "unknown"),
}
}
}
struct ClientBandwidthInner {
/// the actual bandwidth amount (in bytes) available
available: AtomicI64,
@@ -105,41 +71,26 @@ impl ClientBandwidth {
self.inner.available.load(Ordering::Acquire)
}
pub(crate) fn maybe_log_bandwidth(
&self,
now: Option<OffsetDateTime>,
upgrade_mode: impl Into<UpgradeModeEnabledWrapper>,
) {
pub(crate) fn maybe_log_bandwidth(&self, now: Option<OffsetDateTime>) {
let last = self.last_logged();
let now = now.unwrap_or_else(OffsetDateTime::now_utc);
if last + Duration::from_secs(10) < now {
self.log_bandwidth(Some(now), upgrade_mode)
self.log_bandwidth(Some(now))
}
}
pub(crate) fn log_bandwidth(
&self,
now: Option<OffsetDateTime>,
upgrade_mode: impl Into<UpgradeModeEnabledWrapper>,
) {
pub(crate) fn log_bandwidth(&self, now: Option<OffsetDateTime>) {
let now = now.unwrap_or_else(OffsetDateTime::now_utc);
let upgrade_mode = upgrade_mode.into();
let remaining = self.remaining();
let remaining_bi2 = bibytes2(remaining as f64);
if remaining < 0 {
tracing::warn!(
"OUT OF BANDWIDTH. remaining: {remaining_bi2}. in 'upgrade mode': {upgrade_mode}"
);
tracing::warn!("OUT OF BANDWIDTH. remaining: {remaining_bi2}");
} else if remaining < 1_000_000 {
tracing::info!(
"remaining bandwidth: {remaining_bi2}. in 'upgrade mode': {upgrade_mode}"
);
tracing::info!("remaining bandwidth: {remaining_bi2}");
} else {
tracing::trace!(
"remaining bandwidth: {remaining_bi2}. in 'upgrade mode': {upgrade_mode}"
);
tracing::debug!("remaining bandwidth: {remaining_bi2}");
}
self.inner
@@ -147,35 +98,26 @@ impl ClientBandwidth {
.store(now.unix_timestamp(), Ordering::Relaxed)
}
pub(crate) fn update_and_maybe_log(
&self,
remaining: i64,
upgrade_mode: impl Into<UpgradeModeEnabledWrapper>,
) {
pub(crate) fn update_and_maybe_log(&self, remaining: i64) {
let now = OffsetDateTime::now_utc();
self.inner.available.store(remaining, Ordering::Release);
self.inner
.last_updated_ts
.store(now.unix_timestamp(), Ordering::Relaxed);
self.maybe_log_bandwidth(Some(now), upgrade_mode)
self.maybe_log_bandwidth(Some(now))
}
pub(crate) fn update_and_log(
&self,
remaining: i64,
upgrade_mode: impl Into<UpgradeModeEnabledWrapper>,
) {
pub(crate) fn update_and_log(&self, remaining: i64) {
let now = OffsetDateTime::now_utc();
self.inner.available.store(remaining, Ordering::Release);
self.inner
.last_updated_ts
.store(now.unix_timestamp(), Ordering::Relaxed);
self.log_bandwidth(Some(now), upgrade_mode)
self.log_bandwidth(Some(now))
}
fn last_logged(&self) -> OffsetDateTime {
// SAFETY: this value is always populated with valid timestamps
#[allow(clippy::unwrap_used)]
OffsetDateTime::from_unix_timestamp(self.inner.last_logged_ts.load(Ordering::Relaxed))
.unwrap()
}
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::error::GatewayClientError;
use nym_credentials_interface::DEFAULT_MIXNET_REQUEST_BANDWIDTH_THRESHOLD;
use nym_network_defaults::TicketTypeRepr::V1MixnetEntry;
use si_scale::helpers::bibytes2;
use std::time::Duration;
@@ -103,7 +103,7 @@ impl BandwidthTickets {
// 20% of entry ticket value
pub const DEFAULT_REMAINING_BANDWIDTH_THRESHOLD: i64 =
DEFAULT_MIXNET_REQUEST_BANDWIDTH_THRESHOLD;
(V1MixnetEntry.bandwidth_value() / 5) as i64;
pub const DEFAULT_CUTOFF_REMAINING_BANDWIDTH_THRESHOLD: Option<i64> = None;
@@ -20,9 +20,9 @@ use nym_credentials_interface::TicketType;
use nym_crypto::asymmetric::ed25519;
use nym_gateway_requests::registration::handshake::client_handshake;
use nym_gateway_requests::{
BandwidthResponse, BinaryRequest, ClientControlRequest, ClientRequest, GatewayProtocolVersion,
GatewayProtocolVersionExt, GatewayRequestsError, SensitiveServerResponse, ServerResponse,
SharedGatewayKey, SharedSymmetricKey, CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION,
BinaryRequest, ClientControlRequest, ClientRequest, GatewayProtocolVersionExt,
GatewayRequestsError, SensitiveServerResponse, ServerResponse, SharedGatewayKey,
SharedSymmetricKey, CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION, CURRENT_PROTOCOL_VERSION,
};
use nym_sphinx::forwarding::packet::MixPacket;
use nym_statistics_common::clients::connection::ConnectionStatsEvent;
@@ -101,7 +101,8 @@ pub struct GatewayClient<C, St = EphemeralCredentialStorage> {
bandwidth_controller: Option<BandwidthController<C, St>>,
stats_reporter: ClientStatsSender,
negotiated_protocol: Option<GatewayProtocolVersion>,
// currently unused (but populated)
negotiated_protocol: Option<u8>,
// Callback on the fd as soon as the connection has been established
#[cfg(unix)]
@@ -165,12 +166,10 @@ impl<C, St> GatewayClient<C, St> {
}
#[cfg(not(target_arch = "wasm32"))]
#[allow(clippy::unreachable)]
async fn _close_connection(&mut self) -> Result<(), GatewayClientError> {
match std::mem::replace(&mut self.connection, SocketState::NotConnected) {
SocketState::Available(mut socket) => Ok((*socket).close(None).await?),
SocketState::PartiallyDelegated(_) => {
// SAFETY: this is only called after the caller has already recovered the connection
unreachable!("this branch should have never been reached!")
}
_ => Ok(()), // no need to do anything in those cases
@@ -178,7 +177,6 @@ impl<C, St> GatewayClient<C, St> {
}
#[cfg(target_arch = "wasm32")]
#[allow(clippy::unreachable)]
async fn _close_connection(&mut self) -> Result<(), GatewayClientError> {
match std::mem::replace(&mut self.connection, SocketState::NotConnected) {
SocketState::Available(socket) => {
@@ -186,7 +184,6 @@ impl<C, St> GatewayClient<C, St> {
Ok(())
}
SocketState::PartiallyDelegated(_) => {
// SAFETY: this is only called after the caller has already recovered the connection
unreachable!("this branch should have never been reached!")
}
_ => Ok(()), // no need to do anything in those cases
@@ -461,16 +458,43 @@ impl<C, St> GatewayClient<C, St> {
}
}
fn check_gateway_protocol(
&self,
gateway_protocol: Option<u8>,
) -> Result<(), GatewayClientError> {
debug!("gateway protocol: {gateway_protocol:?}, ours: {CURRENT_PROTOCOL_VERSION}");
// right now there are no failure cases here, but this might change in the future
match gateway_protocol {
None => {
warn!("the gateway we're connected to has not specified its protocol version. It's probably running version < 1.1.X, but that's still fine for now. It will become a hard error in 1.2.0");
// note: in +1.2.0 we will have to return a hard error here
Ok(())
}
Some(v) if v > CURRENT_PROTOCOL_VERSION => {
let err = GatewayClientError::IncompatibleProtocol {
gateway: Some(v),
current: CURRENT_PROTOCOL_VERSION,
};
error!("{err}");
Err(err)
}
Some(_) => {
debug!("the gateway is using exactly the same (or older) protocol version as we are. We're good to continue!");
Ok(())
}
}
}
async fn register(
&mut self,
supported_gateway_protocol: Option<GatewayProtocolVersion>,
derive_aes256_gcm_siv_key: bool,
) -> Result<(), GatewayClientError> {
if !self.connection.is_established() {
return Err(GatewayClientError::ConnectionNotEstablished);
}
let derive_aes256_gcm_siv_key = supported_gateway_protocol.supports_aes256_gcm_siv();
debug_assert!(self.connection.is_available());
log::debug!(
"registering with gateway. using legacy key derivation: {}",
@@ -481,13 +505,14 @@ impl<C, St> GatewayClient<C, St> {
// and putting it into the GatewayClient struct would be a hassle
let mut rng = OsRng;
let handshake_result = match &mut self.connection {
let shared_key = match &mut self.connection {
SocketState::Available(ws_stream) => client_handshake(
&mut rng,
ws_stream,
self.local_identity.as_ref(),
self.gateway_identity,
supported_gateway_protocol,
self.cfg.bandwidth.require_tickets,
derive_aes256_gcm_siv_key,
#[cfg(not(target_arch = "wasm32"))]
self.shutdown_token.clone(),
)
@@ -496,31 +521,26 @@ impl<C, St> GatewayClient<C, St> {
_ => return Err(GatewayClientError::ConnectionInInvalidState),
}?;
let authentication_status = match self.read_control_response().await? {
let (authentication_status, gateway_protocol) = match self.read_control_response().await? {
ServerResponse::Register {
protocol_version,
status,
upgrade_mode,
..
} => {
if upgrade_mode {
warn!("the system is currently undergoing an upgrade. some of its functionalities might be unstable")
}
status
}
} => (status, protocol_version),
ServerResponse::Error { message } => {
return Err(GatewayClientError::GatewayError(message))
}
other => return Err(GatewayClientError::UnexpectedResponse { name: other.name() }),
};
self.check_gateway_protocol(gateway_protocol)?;
self.authenticated = authentication_status;
if self.authenticated {
self.shared_key = Some(Arc::new(handshake_result.derived_key));
self.shared_key = Some(Arc::new(shared_key));
}
// populate the negotiated protocol for future uses
self.negotiated_protocol = Some(handshake_result.negotiated_protocol);
self.negotiated_protocol = gateway_protocol;
Ok(())
}
@@ -603,24 +623,13 @@ impl<C, St> GatewayClient<C, St> {
protocol_version,
status,
bandwidth_remaining,
upgrade_mode,
} => {
if protocol_version.is_future_version() {
// SAFETY: future version is always defined
#[allow(clippy::unwrap_used)]
let version = protocol_version.unwrap();
error!("the gateway insists on using v{version} protocol which is not supported by this client");
return Err(GatewayClientError::AuthenticationFailure);
}
self.check_gateway_protocol(protocol_version)?;
self.authenticated = status;
self.bandwidth
.update_and_maybe_log(bandwidth_remaining, upgrade_mode);
self.bandwidth.update_and_maybe_log(bandwidth_remaining);
self.negotiated_protocol = protocol_version;
log::debug!("authenticated: {status}, bandwidth remaining: {bandwidth_remaining}");
if upgrade_mode {
warn!("the system is currently undergoing an upgrade. some of its functionalities might be unstable")
}
Ok(())
}
@@ -641,7 +650,7 @@ impl<C, St> GatewayClient<C, St> {
.public_key()
.derive_destination_address();
let msg = ClientControlRequest::new_legacy_authenticate(
let msg = ClientControlRequest::new_authenticate(
self_address,
shared_key,
self.cfg.bandwidth.require_tickets,
@@ -650,40 +659,25 @@ impl<C, St> GatewayClient<C, St> {
.await
}
async fn authenticate_v2(
&mut self,
requested_protocol_version: GatewayProtocolVersion,
) -> Result<(), GatewayClientError> {
async fn authenticate_v2(&mut self) -> Result<(), GatewayClientError> {
debug!("using v2 authentication");
let Some(shared_key) = self.shared_key.as_ref() else {
return Err(GatewayClientError::NoSharedKeyAvailable);
};
let msg = ClientControlRequest::new_authenticate_v2(
shared_key,
&self.local_identity,
requested_protocol_version,
)?;
let msg = ClientControlRequest::new_authenticate_v2(shared_key, &self.local_identity)?;
self.send_authenticate_request_and_handle_response(msg)
.await
}
async fn authenticate(
&mut self,
supported_gateway_protocol: Option<GatewayProtocolVersion>,
) -> Result<(), GatewayClientError> {
async fn authenticate(&mut self, use_v2: bool) -> Result<(), GatewayClientError> {
if !self.connection.is_established() {
return Err(GatewayClientError::ConnectionNotEstablished);
}
debug!("authenticating with gateway");
if supported_gateway_protocol.supports_authenticate_v2() {
// use the highest possible protocol version the gateway has announced support for
// SAFETY: if announced protocol supports auth v2, it means it's properly set
#[allow(clippy::unwrap_used)]
self.authenticate_v2(supported_gateway_protocol.unwrap())
.await
if use_v2 {
self.authenticate_v2().await
} else {
self.authenticate_v1().await
}
@@ -714,12 +708,9 @@ impl<C, St> GatewayClient<C, St> {
}
};
debug!("supported gateway protocol: {gw_protocol:?}");
let supports_aes_gcm_siv = gw_protocol.supports_aes256_gcm_siv();
let supports_auth_v2 = gw_protocol.supports_authenticate_v2();
let supports_key_rotation_info = gw_protocol.supports_key_rotation_packet();
let supports_upgrade_mode = gw_protocol.supports_upgrade_mode();
if !supports_aes_gcm_siv {
warn!("this gateway is on an old version that doesn't support AES256-GCM-SIV");
@@ -730,16 +721,6 @@ impl<C, St> GatewayClient<C, St> {
if !supports_key_rotation_info {
warn!("this gateway is on an old version that doesn't support key rotation packets")
}
if !supports_upgrade_mode {
warn!("this gateway is on an old version that doesn't support upgrade mode")
}
let gw_protocol = if gw_protocol.is_future_version() {
warn!("we're running outdated software as gateway is announcing protocol {gw_protocol:?} whilst we're using {}. we're going to attempt to downgrade", GatewayProtocolVersion::CURRENT);
Some(GatewayProtocolVersion::CURRENT)
} else {
gw_protocol
};
if self.authenticated {
debug!("Already authenticated");
@@ -754,11 +735,10 @@ impl<C, St> GatewayClient<C, St> {
}
if self.shared_key.is_some() {
self.authenticate(gw_protocol).await?;
self.authenticate(supports_auth_v2).await?;
if self.authenticated {
// if we are authenticated it means we MUST have an associated shared_key
#[allow(clippy::unwrap_used)]
let shared_key = self.shared_key.as_ref().unwrap();
let requires_key_upgrade = shared_key.is_legacy() && supports_aes_gcm_siv;
@@ -771,10 +751,9 @@ impl<C, St> GatewayClient<C, St> {
Err(GatewayClientError::AuthenticationFailure)
}
} else {
self.register(gw_protocol).await?;
self.register(supports_aes_gcm_siv).await?;
// if registration didn't return an error, we MUST have an associated shared key
#[allow(clippy::unwrap_used)]
let shared_key = self.shared_key.as_ref().unwrap();
// we're always registering with the highest supported protocol,
@@ -804,81 +783,51 @@ impl<C, St> GatewayClient<C, St> {
}
}
async fn wait_for_bandwidth_response(
async fn claim_ecash_bandwidth(
&mut self,
msg: ClientControlRequest,
) -> Result<BandwidthResponse, GatewayClientError> {
let response = match self
credential: CredentialSpendingData,
) -> Result<(), GatewayClientError> {
let msg = ClientControlRequest::new_enc_ecash_credential(
credential,
self.shared_key.as_ref().unwrap(),
)?;
let bandwidth_remaining = match self
.send_websocket_message_with_non_send_response(msg)
.await?
{
ServerResponse::Bandwidth(response) => {
if response.upgrade_mode {
info!("the system is currently undergoing an upgrade. our bandwidth shouldn't have been metered")
}
Ok(response)
}
ServerResponse::Bandwidth { available_total } => Ok(available_total),
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)),
ServerResponse::TypedError { error } => {
Err(GatewayClientError::TypedGatewayError(error))
}
other => Err(GatewayClientError::UnexpectedResponse { name: other.name() }),
}?;
Ok(response)
}
async fn claim_ecash_bandwidth(
&mut self,
credential: CredentialSpendingData,
) -> Result<(), GatewayClientError> {
// SAFETY: claiming ecash bandwidth is called as part of `claim_bandwidth` which
// ensures the shared key is defined
#[allow(clippy::unwrap_used)]
let msg = ClientControlRequest::new_enc_ecash_credential(
credential,
self.shared_key.as_ref().unwrap(),
)?;
let response = self.wait_for_bandwidth_response(msg).await?;
// TODO: create tracing span
info!("managed to claim ecash bandwidth");
self.bandwidth
.update_and_log(response.available_total, response.upgrade_mode);
Ok(())
}
pub async fn send_upgrade_mode_jwt(&mut self, token: String) -> Result<(), GatewayClientError> {
let msg = ClientControlRequest::new_upgrade_mode_jwt(token);
let response = self.wait_for_bandwidth_response(msg).await?;
// if gateway rejected our jwt, we would have returned an error
info!("gateway has accepted our jwt");
if !response.upgrade_mode {
error!("but we're not in upgrade mode - something is wrong!");
return Err(GatewayClientError::UnexpectedUpgradeModeState);
}
self.bandwidth
.update_and_log(response.available_total, response.upgrade_mode);
self.bandwidth.update_and_log(bandwidth_remaining);
Ok(())
}
async fn try_claim_testnet_bandwidth(&mut self) -> Result<(), GatewayClientError> {
let msg = ClientControlRequest::ClaimFreeTestnetBandwidth;
let response = self.wait_for_bandwidth_response(msg).await?;
let bandwidth_remaining = match self
.send_websocket_message_with_non_send_response(msg)
.await?
{
ServerResponse::Bandwidth { available_total } => Ok(available_total),
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)),
other => Err(GatewayClientError::UnexpectedResponse { name: other.name() }),
}?;
info!("managed to claim testnet bandwidth");
self.bandwidth
.update_and_log(response.available_total, response.upgrade_mode);
self.bandwidth.update_and_log(bandwidth_remaining);
Ok(())
}
fn unchecked_bandwidth_controller(&self) -> &BandwidthController<C, St> {
// this is an unchecked method
#[allow(clippy::unwrap_used)]
self.bandwidth_controller.as_ref().unwrap()
}
@@ -970,7 +919,6 @@ impl<C, St> GatewayClient<C, St> {
BinaryRequest::ForwardSphinx { packet }
};
#[allow(clippy::expect_used)]
req.into_ws_message(
self.shared_key
.as_ref()
@@ -1077,8 +1025,6 @@ impl<C, St> GatewayClient<C, St> {
self.send_with_reconnection_on_failure(msg).await
}
// SAFETY: this method is only called when the connection is in `PartiallyDelegated` state
#[allow(clippy::unreachable)]
async fn recover_socket_connection(&mut self) -> Result<(), GatewayClientError> {
if self.connection.is_available() {
return Ok(());
@@ -1108,7 +1054,6 @@ impl<C, St> GatewayClient<C, St> {
return Err(GatewayClientError::ConnectionInInvalidState);
}
#[allow(clippy::expect_used)]
let partially_delegated =
match std::mem::replace(&mut self.connection, SocketState::Invalid) {
SocketState::Available(conn) => {
@@ -1124,13 +1069,7 @@ impl<C, St> GatewayClient<C, St> {
self.shutdown_token.clone(),
)
}
other => {
error!(
"attempted to start mixnet listener whilst the connection is in {} state!",
other.name()
);
return Err(GatewayClientError::ConnectionInInvalidState);
}
_ => unreachable!(),
};
self.connection = SocketState::PartiallyDelegated(partially_delegated);
@@ -1143,7 +1082,8 @@ impl<C, St> GatewayClient<C, St> {
}
// if we're reconnecting, because we lost connection, we need to re-authenticate the connection
self.authenticate(self.negotiated_protocol).await?;
self.authenticate(self.negotiated_protocol.supports_authenticate_v2())
.await?;
// this call is NON-blocking
self.start_listening_for_mixnet_messages()?;
@@ -39,6 +39,7 @@ pub(crate) async fn connect_async(
resolver
.resolve_str(domain)
.await?
.into_iter()
.map(|a| SocketAddr::new(a, port))
.collect()
}
@@ -54,7 +54,7 @@ pub enum GatewayClientError {
#[cfg(not(target_arch = "wasm32"))]
#[error("resolution failed: {0}")]
ResolutionFailed(#[from] nym_http_api_client::ResolveError),
ResolutionFailed(#[from] nym_http_api_client::HickoryDnsError),
#[error("No shared key was provided or obtained")]
NoSharedKeyAvailable,
@@ -128,9 +128,6 @@ pub enum GatewayClientError {
"this operation couldn't be completed as the program is in the process of shutting down"
)]
ShutdownInProgress,
#[error("the system is an unexpected upgrade mode state")]
UnexpectedUpgradeModeState,
}
impl From<WsError> for GatewayClientError {
@@ -35,7 +35,6 @@ impl PacketRouter {
}
}
#[allow(clippy::panic)]
pub fn route_mixnet_messages(
&self,
received_messages: Vec<Vec<u8>>,
@@ -55,7 +54,6 @@ impl PacketRouter {
Ok(())
}
#[allow(clippy::panic)]
pub fn route_acks(&self, received_acks: Vec<Vec<u8>>) -> Result<(), GatewayClientError> {
if let Err(err) = self.ack_sender.unbounded_send(received_acks) {
// check if the failure is due to the shutdown being in progress and thus the receiver channel
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: Apache-2.0
use crate::bandwidth::ClientBandwidth;
use crate::client::config::BandwidthTickets;
use crate::error::GatewayClientError;
use crate::packet_router::PacketRouter;
use crate::traits::GatewayPacketRouter;
@@ -11,9 +10,7 @@ use futures::channel::oneshot;
use futures::stream::{SplitSink, SplitStream};
use futures::{SinkExt, StreamExt};
use nym_gateway_requests::shared_key::SharedGatewayKey;
use nym_gateway_requests::{
SendResponse, SensitiveServerResponse, ServerResponse, SimpleGatewayRequestsError,
};
use nym_gateway_requests::{SensitiveServerResponse, ServerResponse, SimpleGatewayRequestsError};
use nym_task::ShutdownToken;
use si_scale::helpers::bibytes2;
use std::os::raw::c_int as RawFd;
@@ -157,12 +154,11 @@ impl PartiallyDelegatedRouter {
fn handle_text_message(&self, text: String) -> Result<(), GatewayClientError> {
// if we fail to deserialise the response, return a hard error. we can't handle garbage
match ServerResponse::try_from(text).map_err(|_| GatewayClientError::MalformedResponse)? {
ServerResponse::Send(SendResponse {
ServerResponse::Send {
remaining_bandwidth,
upgrade_mode,
}) => {
} => {
self.client_bandwidth
.update_and_maybe_log(remaining_bandwidth, upgrade_mode);
.update_and_maybe_log(remaining_bandwidth);
Ok(())
}
ServerResponse::Error { message } => {
@@ -178,20 +174,7 @@ impl PartiallyDelegatedRouter {
let available_bi2 = bibytes2(available as f64);
let required_bi2 = bibytes2(required as f64);
warn!("run out of bandwidth when attempting to send the message! we got {available_bi2} available, but needed at least {required_bi2} to send the previous message");
// if we run out of bandwidth (and tried to send reasonable amount of data),
// the upgrade mode is implicitly disabled, as otherwise we would have been
// to proceed
let upgrade_mode = if available
< BandwidthTickets::DEFAULT_REMAINING_BANDWIDTH_THRESHOLD
{
Some(false)
} else {
// we were attempting to send a lot of data at once
// - we have no certainty about upgrade mode at this point
None
};
self.client_bandwidth
.update_and_log(available, upgrade_mode);
self.client_bandwidth.update_and_log(available);
// UNIMPLEMENTED: we should stop sending messages until we recover bandwidth
Ok(())
}
@@ -344,7 +327,6 @@ impl PartiallyDelegatedHandle {
Ok(self.sink_half.send_all(&mut send_stream).await?)
}
#[allow(clippy::panic)]
pub(crate) async fn merge(self) -> Result<WsConn, GatewayClientError> {
let (mut stream_receiver, notify) = self.delegated_stream;
@@ -373,10 +355,8 @@ impl PartiallyDelegatedHandle {
// in receive_res
.map_err(|_| GatewayClientError::ConnectionAbruptlyClosed)?;
let stream = stream_results?;
// the error is thrown when trying to reunite sink and stream that did not originate
// from the same split which is impossible to happen here
#[allow(clippy::unwrap_used)]
Ok(self.sink_half.reunite(stream).unwrap())
}
}
@@ -407,13 +387,4 @@ impl SocketState {
SocketState::Available(_) | SocketState::PartiallyDelegated(_)
)
}
pub(crate) fn name(&self) -> &'static str {
match self {
SocketState::Available(_) => "available",
SocketState::PartiallyDelegated(_) => "partially delegated",
SocketState::NotConnected => "not connected",
SocketState::Invalid => "invalid",
}
}
}
@@ -241,28 +241,23 @@ impl Epoch {
//
// Note: It's important that the variant ordering is not changed otherwise it would mess up the derived `PartialOrd`
#[cw_serde]
#[derive(Copy, Default)]
#[derive(Copy)]
pub enum EpochState {
#[default]
WaitingInitialisation,
PublicKeySubmission {
resharing: bool,
},
DealingExchange {
resharing: bool,
},
VerificationKeySubmission {
resharing: bool,
},
VerificationKeyValidation {
resharing: bool,
},
VerificationKeyFinalization {
resharing: bool,
},
PublicKeySubmission { resharing: bool },
DealingExchange { resharing: bool },
VerificationKeySubmission { resharing: bool },
VerificationKeyValidation { resharing: bool },
VerificationKeyFinalization { resharing: bool },
InProgress,
}
impl Default for EpochState {
fn default() -> Self {
Self::WaitingInitialisation
}
}
impl Display for EpochState {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
+1 -1
View File
@@ -8,7 +8,7 @@ async fn main() -> anyhow::Result<()> {
use sqlx::{Connection, SqliteConnection};
use std::env;
let out_dir = env::var("OUT_DIR").context("missing OUT_DIR env variable")?;
let out_dir = env::var("OUT_DIR")?;
let database_path = format!("{out_dir}/nym-credential-proxy-example.sqlite");
// remove the db file if it already existed from previous build
-26
View File
@@ -1,7 +1,6 @@
// Copyright 2025 Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use nym_crypto::asymmetric::ed25519;
use nym_ecash_signer_check::SignerCheckError;
use nym_validator_client::coconut::EcashApiError;
use nym_validator_client::nym_api::{EpochId, error::NymAPIError};
@@ -128,13 +127,6 @@ pub enum CredentialProxyError {
#[error("failed to create deposit")]
DepositFailure,
#[error("failed to load jwt signing key from {path}: {err}")]
JWTSigningKeyLoadFailure {
path: String,
#[source]
err: std::io::Error,
},
#[error("can't obtain sufficient number of credential shares due to unavailable quorum")]
UnavailableSigningQuorum,
@@ -169,24 +161,6 @@ pub enum CredentialProxyError {
device_id: String,
credential_id: String,
},
#[error(
"the attestation check url has not been provided through either the CLI nor the default .env config"
)]
AttestationCheckUrlNotSet,
#[error("the provided attester public key is malformed: {source}")]
MalformedAttestationCheckUrl { source: url::ParseError },
#[error(
"the attester public key has not been provided through either the CLI nor the default .env config"
)]
AttesterPublicKeyNotSet,
#[error("the provided attester public key is malformed: {source}")]
MalformedAttesterPublicKey {
source: ed25519::Ed25519RecoveryError,
},
}
impl From<NymAPIError> for CredentialProxyError {
@@ -7,9 +7,9 @@ use crate::ticketbook_manager::TicketbookManager;
use nym_compact_ecash::Base58;
use nym_credential_proxy_requests::api::v1::ticketbook::models::{
CurrentEpochResponse, DepositResponse, GlobalDataParams, MasterVerificationKeyResponse,
ObtainTicketBookSharesAsyncResponse, PartialVerificationKey, PartialVerificationKeysResponse,
TicketbookAsyncRequest, TicketbookObtainParams, TicketbookRequest,
TicketbookWalletSharesAsyncResponse, TicketbookWalletSharesResponse,
PartialVerificationKey, PartialVerificationKeysResponse, TicketbookAsyncRequest,
TicketbookObtainParams, TicketbookRequest, TicketbookWalletSharesAsyncResponse,
TicketbookWalletSharesResponse,
};
use time::OffsetDateTime;
use tracing::{Instrument, Level, error, info, span, warn};
@@ -65,7 +65,7 @@ impl TicketbookManager {
uuid: Uuid,
request: TicketbookAsyncRequest,
params: TicketbookObtainParams,
) -> Result<ObtainTicketBookSharesAsyncResponse, CredentialProxyError> {
) -> Result<TicketbookWalletSharesAsyncResponse, CredentialProxyError> {
let requested_on = OffsetDateTime::now_utc();
let span = span!(Level::INFO, "[async] obtain ticketboook", uuid = %uuid);
async move {
@@ -110,7 +110,7 @@ impl TicketbookManager {
}
// 4. in the meantime, return the id to the user
Ok(TicketbookWalletSharesAsyncResponse { id, uuid }.into())
Ok(TicketbookWalletSharesAsyncResponse { id, uuid })
}
.instrument(span)
.await
-1
View File
@@ -14,7 +14,6 @@ bincode = { workspace = true, optional = true }
log = { workspace = true }
thiserror = { workspace = true }
serde = { workspace = true, features = ["derive"], optional = true }
time = { workspace = true }
tokio = { workspace = true, features = ["sync"] }
zeroize = { workspace = true, features = ["zeroize_derive"] }
@@ -1,17 +0,0 @@
/*
* Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
* SPDX-License-Identifier: Apache-2.0
*/
CREATE TABLE emergency_credential
(
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
type TEXT NOT NULL,
-- don't define any strict schema on the content as it might be implementation-dependant
content BLOB NOT NULL,
expiration TIMESTAMP WITHOUT TIME ZONE
);
-- no point in allowing duplicate data
CREATE UNIQUE INDEX emergency_credential_unique_type_content
ON emergency_credential (type, content);
@@ -1,10 +1,7 @@
// Copyright 2023-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::models::{
BasicTicketbookInformation, EmergencyCredential, EmergencyCredentialContent,
RetrievedPendingTicketbook, RetrievedTicketbook,
};
use crate::models::{BasicTicketbookInformation, RetrievedPendingTicketbook, RetrievedTicketbook};
use nym_compact_ecash::scheme::coin_indices_signatures::AnnotatedCoinIndexSignature;
use nym_compact_ecash::scheme::expiration_date_signatures::AnnotatedExpirationDateSignature;
use nym_compact_ecash::VerificationKeyAuth;
@@ -25,12 +22,6 @@ pub struct MemoryEcachTicketbookManager {
inner: Arc<RwLock<EcashCredentialManagerInner>>,
}
#[derive(Default)]
struct InternalIdCounters {
next_ticketbook_id: i64,
next_emergency_credential_id: i64,
}
#[derive(Default)]
struct EcashCredentialManagerInner {
ticketbooks: HashMap<i64, RetrievedTicketbook>,
@@ -38,22 +29,13 @@ struct EcashCredentialManagerInner {
master_vk: HashMap<u64, VerificationKeyAuth>,
coin_indices_sigs: HashMap<u64, Vec<AnnotatedCoinIndexSignature>>,
expiration_date_sigs: HashMap<(u64, Date), Vec<AnnotatedExpirationDateSignature>>,
emergency_credentials: HashMap<String, Vec<EmergencyCredential>>,
// internal counters emulating assignment of an increasing id to new inserted database entries
internal_counters: InternalIdCounters,
_next_id: i64,
}
impl EcashCredentialManagerInner {
fn next_ticketbook_id(&mut self) -> i64 {
let next = self.internal_counters.next_ticketbook_id;
self.internal_counters.next_ticketbook_id += 1;
next
}
fn next_emergency_credential_id(&mut self) -> i64 {
let next = self.internal_counters.next_emergency_credential_id;
self.internal_counters.next_emergency_credential_id += 1;
fn next_id(&mut self) -> i64 {
let next = self._next_id;
self._next_id += 1;
next
}
}
@@ -188,7 +170,7 @@ impl MemoryEcachTicketbookManager {
used_tickets: u32,
) {
let mut guard = self.inner.write().await;
let id = guard.next_ticketbook_id();
let id = guard.next_id();
#[allow(clippy::unwrap_used)]
let mut nasty_clone = hack_clone_ticketbook(ticketbook);
@@ -295,41 +277,4 @@ impl MemoryEcachTicketbookManager {
sigs.signatures.clone(),
);
}
pub(crate) async fn get_emergency_credential(&self, typ: &str) -> Option<EmergencyCredential> {
let guard = self.inner.read().await;
guard.emergency_credentials.get(typ)?.first().cloned()
}
pub(crate) async fn insert_emergency_credential(
&self,
credential: &EmergencyCredentialContent,
) {
let mut guard = self.inner.write().await;
let id = guard.next_emergency_credential_id();
guard
.emergency_credentials
.entry(credential.typ.clone())
.or_default()
.push(EmergencyCredential {
id,
data: credential.clone(),
});
}
pub(crate) async fn remove_emergency_credential(&self, id: i64) {
let mut guard = self.inner.write().await;
guard.emergency_credentials.retain(|_, credentials| {
credentials.retain(|c| c.id != id);
!credentials.is_empty()
})
}
pub(crate) async fn remove_emergency_credentials_of_type(&self, typ: &str) {
let mut guard = self.inner.write().await;
guard.emergency_credentials.remove(typ);
}
}
@@ -2,9 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
use crate::models::{
BasicTicketbookInformation, EmergencyCredential, EmergencyCredentialContent,
RawCoinIndexSignatures, RawExpirationDateSignatures, RawVerificationKey,
StoredIssuedTicketbook, StoredPendingTicketbook,
BasicTicketbookInformation, RawCoinIndexSignatures, RawExpirationDateSignatures,
RawVerificationKey, StoredIssuedTicketbook, StoredPendingTicketbook,
};
use nym_ecash_time::Date;
use sqlx::{Executor, Sqlite, Transaction};
@@ -306,74 +305,6 @@ impl SqliteEcashTicketbookManager {
.await?;
Ok(())
}
pub(crate) async fn get_emergency_credential(
&self,
typ: &str,
) -> Result<Option<EmergencyCredential>, sqlx::Error> {
sqlx::query_as(
r#"
SELECT *
FROM emergency_credential
WHERE type = ?
AND (expiration IS NULL OR expiration > CURRENT_TIMESTAMP)
ORDER BY expiration DESC NULLS LAST
LIMIT 1
"#,
)
.bind(typ)
.fetch_optional(&*self.connection_pool)
.await
}
pub(crate) async fn insert_emergency_credential(
&self,
credential: &EmergencyCredentialContent,
) -> Result<(), sqlx::Error> {
sqlx::query!(
r#"
INSERT INTO emergency_credential
(type, content, expiration)
VALUES (?, ?, ?)
ON CONFLICT(type, content) DO NOTHING;
"#,
credential.typ,
credential.content,
credential.expiration,
)
.execute(&*self.connection_pool)
.await?;
Ok(())
}
pub(crate) async fn remove_emergency_credential(&self, id: i64) -> Result<(), sqlx::Error> {
sqlx::query!(
r#"
DELETE FROM emergency_credential
WHERE id = ?
"#,
id
)
.execute(&*self.connection_pool)
.await?;
Ok(())
}
pub(crate) async fn remove_emergency_credentials_of_type(
&self,
typ: &str,
) -> Result<(), sqlx::Error> {
sqlx::query!(
r#"
DELETE FROM emergency_credential
WHERE type = ?
"#,
typ
)
.execute(&*self.connection_pool)
.await?;
Ok(())
}
}
pub(crate) async fn get_next_unspent_ticketbook<'a, E>(
@@ -3,10 +3,7 @@
use crate::backends::memory::MemoryEcachTicketbookManager;
use crate::error::StorageError;
use crate::models::{
BasicTicketbookInformation, EmergencyCredential, EmergencyCredentialContent,
RetrievedPendingTicketbook, RetrievedTicketbook,
};
use crate::models::{BasicTicketbookInformation, RetrievedPendingTicketbook, RetrievedTicketbook};
use crate::storage::Storage;
use async_trait::async_trait;
use nym_compact_ecash::scheme::coin_indices_signatures::AnnotatedCoinIndexSignature;
@@ -221,38 +218,6 @@ impl Storage for EphemeralStorage {
.await;
Ok(())
}
async fn get_emergency_credential(
&self,
typ: &str,
) -> Result<Option<EmergencyCredential>, Self::StorageError> {
Ok(self.storage_manager.get_emergency_credential(typ).await)
}
async fn insert_emergency_credential(
&self,
credential: &EmergencyCredentialContent,
) -> Result<(), Self::StorageError> {
self.storage_manager
.insert_emergency_credential(credential)
.await;
Ok(())
}
async fn remove_emergency_credential(&self, id: i64) -> Result<(), Self::StorageError> {
self.storage_manager.remove_emergency_credential(id).await;
Ok(())
}
async fn remove_emergency_credentials_of_type(
&self,
typ: &str,
) -> Result<(), Self::StorageError> {
self.storage_manager
.remove_emergency_credentials_of_type(typ)
.await;
Ok(())
}
}
#[cfg(test)]
@@ -281,7 +246,7 @@ mod tests {
let _exp_date_sigs = generate_expiration_date_signatures(
sig_req.expiration_date.ecash_unix_timestamp(),
&[signing_keys.secret_key()],
&[signing_keys.verification_key()],
&vec![signing_keys.verification_key()],
&signing_keys.verification_key(),
&[1],
)?;
@@ -298,7 +263,7 @@ mod tests {
let wallet = issuance.aggregate_signature_shares(
&signing_keys.verification_key(),
&[partial_wallet],
&vec![partial_wallet],
sig_req,
)?;
-18
View File
@@ -3,7 +3,6 @@
use nym_credentials::{IssuanceTicketBook, IssuedTicketBook};
use nym_ecash_time::Date;
use time::OffsetDateTime;
use zeroize::{Zeroize, ZeroizeOnDrop};
pub struct RetrievedTicketbook {
@@ -79,20 +78,3 @@ pub struct RawVerificationKey {
pub serialised_key: Vec<u8>,
pub serialization_revision: u8,
}
#[derive(Clone, Debug)]
#[cfg_attr(not(target_arch = "wasm32"), derive(sqlx::FromRow))]
pub struct EmergencyCredential {
pub id: i64,
#[cfg_attr(not(target_arch = "wasm32"), sqlx(flatten))]
pub data: EmergencyCredentialContent,
}
#[derive(Clone, Debug)]
#[cfg_attr(not(target_arch = "wasm32"), derive(sqlx::FromRow))]
pub struct EmergencyCredentialContent {
#[cfg_attr(not(target_arch = "wasm32"), sqlx(rename = "type"))]
pub typ: String,
pub content: Vec<u8>,
pub expiration: Option<OffsetDateTime>,
}
@@ -3,7 +3,6 @@
mod legacy_helpers;
use crate::models::{EmergencyCredential, EmergencyCredentialContent};
use crate::{
backends::sqlite::{
get_next_unspent_ticketbook, increase_used_ticketbook_tickets, SqliteEcashTicketbookManager,
@@ -402,36 +401,4 @@ impl Storage for PersistentStorage {
.await?;
Ok(())
}
async fn get_emergency_credential(
&self,
typ: &str,
) -> Result<Option<EmergencyCredential>, Self::StorageError> {
Ok(self.storage_manager.get_emergency_credential(typ).await?)
}
async fn insert_emergency_credential(
&self,
credential: &EmergencyCredentialContent,
) -> Result<(), Self::StorageError> {
self.storage_manager
.insert_emergency_credential(credential)
.await?;
Ok(())
}
async fn remove_emergency_credential(&self, id: i64) -> Result<(), Self::StorageError> {
self.storage_manager.remove_emergency_credential(id).await?;
Ok(())
}
async fn remove_emergency_credentials_of_type(
&self,
typ: &str,
) -> Result<(), Self::StorageError> {
self.storage_manager
.remove_emergency_credentials_of_type(typ)
.await?;
Ok(())
}
}
+1 -21
View File
@@ -1,10 +1,7 @@
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::models::{
BasicTicketbookInformation, EmergencyCredential, EmergencyCredentialContent,
RetrievedPendingTicketbook, RetrievedTicketbook,
};
use crate::models::{BasicTicketbookInformation, RetrievedPendingTicketbook, RetrievedTicketbook};
use async_trait::async_trait;
use nym_compact_ecash::VerificationKeyAuth;
use nym_credentials::ecash::bandwidth::serialiser::keys::EpochVerificationKey;
@@ -111,21 +108,4 @@ pub trait Storage: Clone + Send + Sync {
&self,
signatures: &AggregatedExpirationDateSignatures,
) -> Result<(), Self::StorageError>;
async fn get_emergency_credential(
&self,
typ: &str,
) -> Result<Option<EmergencyCredential>, Self::StorageError>;
async fn insert_emergency_credential(
&self,
credential: &EmergencyCredentialContent,
) -> Result<(), Self::StorageError>;
async fn remove_emergency_credential(&self, id: i64) -> Result<(), Self::StorageError>;
async fn remove_emergency_credentials_of_type(
&self,
typ: &str,
) -> Result<(), Self::StorageError>;
}
+1 -2
View File
@@ -17,6 +17,7 @@ cosmwasm-std = { workspace = true }
cw-utils = { workspace = true }
dyn-clone = { workspace = true }
futures = { workspace = true }
rand = { workspace = true }
si-scale = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
@@ -26,10 +27,8 @@ tracing = { workspace = true }
nym-api-requests = { path = "../../nym-api/nym-api-requests" }
nym-credentials = { path = "../credentials" }
nym-credentials-interface = { path = "../credentials-interface" }
nym-crypto = { path = "../crypto", features = ["asymmetric"] }
nym-ecash-contract-common = { path = "../cosmwasm-smart-contracts/ecash-contract" }
nym-gateway-requests = { path = "../gateway-requests" }
nym-gateway-storage = { path = "../gateway-storage" }
nym-task = { path = "../task" }
nym-validator-client = { path = "../client-libs/validator-client" }
nym-upgrade-mode-check = { path = "../upgrade-mode-check" }
@@ -6,6 +6,7 @@ use crate::ClientBandwidth;
use crate::error::*;
use nym_credentials::ecash::utils::ecash_today;
use nym_credentials_interface::Bandwidth;
use nym_gateway_requests::ServerResponse;
use nym_gateway_storage::traits::BandwidthGatewayStorage;
use si_scale::helpers::bibytes2;
use time::OffsetDateTime;
@@ -65,7 +66,7 @@ impl BandwidthStorageManager {
Ok(())
}
pub async fn handle_claim_testnet_bandwidth(&mut self) -> Result<i64> {
pub async fn handle_claim_testnet_bandwidth(&mut self) -> Result<ServerResponse> {
debug!("handling testnet bandwidth request");
if self.only_coconut_credentials {
@@ -75,7 +76,8 @@ impl BandwidthStorageManager {
self.increase_bandwidth(FREE_TESTNET_BANDWIDTH_VALUE, ecash_today())
.await?;
let available_total = self.client_bandwidth.available().await;
Ok(available_total)
Ok(ServerResponse::Bandwidth { available_total })
}
#[instrument(skip_all)]
@@ -94,7 +96,7 @@ impl BandwidthStorageManager {
let available_bi2 = bibytes2(available_bandwidth as f64);
let required_bi2 = bibytes2(required_bandwidth as f64);
trace!(available = available_bi2, required = required_bi2);
debug!(available = available_bi2, required = required_bi2);
self.consume_bandwidth(required_bandwidth).await?;
let remaining_bandwidth = self.client_bandwidth.available().await;
@@ -47,25 +47,6 @@ pub enum Error {
UnknownTicketType(#[from] nym_credentials_interface::UnknownTicketType),
}
impl Error {
pub fn is_out_of_bandwidth(&self) -> bool {
matches!(self, Error::OutOfBandwidth { .. })
}
}
pub trait OutOfBandwidthResultExt {
fn is_out_of_bandwidth(&self) -> bool;
}
impl<T> OutOfBandwidthResultExt for Result<T> {
fn is_out_of_bandwidth(&self) -> bool {
match &self {
Ok(_) => false,
Err(err) => err.is_out_of_bandwidth(),
}
}
}
impl From<EcashTicketError> for Error {
fn from(err: EcashTicketError) -> Self {
// don't expose storage issue details to the user
@@ -13,13 +13,11 @@ use tracing::*;
pub use client_bandwidth::*;
pub use error::*;
pub use upgrade_mode::UpgradeModeState;
pub mod bandwidth_storage_manager;
mod client_bandwidth;
pub mod ecash;
pub mod error;
pub mod upgrade_mode;
pub struct CredentialVerifier {
credential: CredentialSpendingRequest,
@@ -1,293 +0,0 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender};
use nym_crypto::asymmetric::ed25519;
use nym_upgrade_mode_check::{
CREDENTIAL_PROXY_JWT_ISSUER, UpgradeModeAttestation, validate_upgrade_mode_jwt,
};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicI64, Ordering};
use std::time::Duration;
use thiserror::Error;
use time::OffsetDateTime;
use tokio::sync::{Notify, RwLock};
use tracing::{debug, error, info};
#[derive(Debug, Error)]
pub enum UpgradeModeEnableError {
#[error("too soon to perform another upgrade mode attestation check")]
TooManyRecheckRequests,
#[error("provided upgrade mode JWT is invalid: {0}")]
InvalidUpgradeModeJWT(#[from] nym_upgrade_mode_check::UpgradeModeCheckError),
#[error("the upgrade mode attestation does not appear to have been published")]
AttestationNotPublished,
#[error("the provided upgrade mode attestation is different from the published one")]
MismatchedUpgradeModeAttestation,
}
// the idea behind this is as follows:
// it's been relatively a long time since the watcher last performed its checks (since it's in 'regular' mode)
// and some client has just sent a JWT. we have to retrieve most recent information in case upgrade mode
// has just been enabled, and we haven't learned about it yet
#[derive(Clone)]
pub struct UpgradeModeCheckRequestSender(Option<UnboundedSender<CheckRequest>>);
impl UpgradeModeCheckRequestSender {
pub fn new(sender: UnboundedSender<CheckRequest>) -> Self {
UpgradeModeCheckRequestSender(Some(sender))
}
pub fn new_empty() -> Self {
Self(None)
}
pub(crate) fn send_request(&self, on_done: Arc<Notify>) {
let Some(ref inner) = self.0 else {
// make sure the caller gets notified so it doesn't wait forever
on_done.notify_waiters();
return;
};
if let Err(not_sent) = inner.unbounded_send(CheckRequest { on_done }) {
debug!("failed to send upgrade mode check request - {not_sent}");
// make sure the caller gets notified so it doesn't wait forever
not_sent.into_inner().on_done.notify_waiters();
}
}
}
pub type UpgradeModeCheckRequestReceiver = UnboundedReceiver<CheckRequest>;
pub struct CheckRequest {
on_done: Arc<Notify>,
}
impl CheckRequest {
pub fn finalize(self) {
self.on_done.notify_waiters();
}
}
#[derive(Clone, Copy)]
pub struct UpgradeModeCheckConfig {
/// The minimum duration since the last explicit check to allow creation of separate request.
pub min_staleness_recheck: Duration,
}
/// Full upgrade mode information, that apart from boolean flag indicating the state
/// and the attestation information, includes channel connection to relevant
/// attestation watcher to request state rechecks
#[derive(Clone)]
pub struct UpgradeModeDetails {
pub(crate) config: UpgradeModeCheckConfig,
pub(crate) request_checker: UpgradeModeCheckRequestSender,
pub(crate) state: UpgradeModeState,
}
impl UpgradeModeDetails {
pub fn new(
config: UpgradeModeCheckConfig,
request_checker: UpgradeModeCheckRequestSender,
state: UpgradeModeState,
) -> Self {
UpgradeModeDetails {
config,
request_checker,
state,
}
}
pub fn state(&self) -> &UpgradeModeState {
&self.state
}
pub fn enabled(&self) -> bool {
self.state.upgrade_mode_enabled()
}
fn since_last_query(&self) -> Duration {
self.state.since_last_query()
}
pub fn can_request_recheck(&self) -> bool {
self.since_last_query() > self.config.min_staleness_recheck
}
// explicitly request state update. this is only called when upgrade mode is NOT enabled,
// and client has sent a JWT instead of ticket
async fn request_recheck(&self) -> bool {
// send request
let on_done = Arc::new(Notify::new());
self.request_checker.send_request(on_done.clone());
// wait for response - note, if we fail to send, notification will be sent regardless,
// so that we wouldn't get stuck in here
on_done.notified().await;
// check the state again
self.enabled()
}
pub async fn try_enable_via_received_jwt(
&self,
token: String,
) -> Result<(), UpgradeModeEnableError> {
// see if it's viable to perform another expedited check
if !self.can_request_recheck() {
return Err(UpgradeModeEnableError::TooManyRecheckRequests);
}
// first validate whether the received JWT is even valid
let attestation = validate_upgrade_mode_jwt(&token, Some(CREDENTIAL_PROXY_JWT_ISSUER))?;
// send request to revalidate internal state
// this will, among other things, pull fresh attestation from the configured endpoint
// and also verify required signatures (and pubkeys)
self.request_recheck().await;
// not strictly necessary, but check if provided attestation actually matches the one retrieved
// (if any)
let Some(retrieved_attestation) = self.state.attestation().await else {
return Err(UpgradeModeEnableError::AttestationNotPublished);
};
if retrieved_attestation != attestation {
return Err(UpgradeModeEnableError::MismatchedUpgradeModeAttestation);
}
// note: if attestation has been returned, it means we're definitely in upgrade mode
// (otherwise it wouldn't have existed in the state)
info!("managed to initialise upgrade mode through received JWT");
Ok(())
}
}
/// Detailed upgrade mode information, that apart from boolean flag,
/// also includes, if applicable, the associated attestation
#[derive(Clone)]
pub struct UpgradeModeState {
inner: Arc<UpgradeModeStateInner>,
}
/// Just a shareable flag to indicate whether upgrade mode is enabled or disabled
#[derive(Clone, Default)]
pub struct UpgradeModeStatus(Arc<AtomicBool>);
impl UpgradeModeStatus {
pub fn enabled(&self) -> bool {
self.0.load(Ordering::Acquire)
}
pub fn enable(&self) {
self.0.store(true, Ordering::Relaxed);
}
pub fn disable(&self) {
self.0.store(false, Ordering::Release);
}
}
impl UpgradeModeState {
pub fn new(attester_public_key: ed25519::PublicKey) -> UpgradeModeState {
UpgradeModeState {
inner: Arc::new(UpgradeModeStateInner {
expected_attester_public_key: attester_public_key,
expected_attestation: RwLock::new(None),
last_queried_ts: AtomicI64::new(OffsetDateTime::UNIX_EPOCH.unix_timestamp()),
status: UpgradeModeStatus(Arc::new(AtomicBool::new(false))),
}),
}
}
pub fn attester_pubkey(&self) -> ed25519::PublicKey {
self.inner.expected_attester_public_key
}
pub async fn attestation(&self) -> Option<UpgradeModeAttestation> {
self.inner.expected_attestation.read().await.clone()
}
pub async fn try_set_expected_attestation(
&self,
expected_attestation: Option<UpgradeModeAttestation>,
) {
// make sure to only enable upgrade mode flag AFTER we have written the expected value
// (or still hold the exclusive lock as in this instance)
let mut guard = self.inner.expected_attestation.write().await;
// ensure that the attestation had been signed with the expected key
if let Some(attestation) = expected_attestation.as_ref() {
if attestation.content.attester_public_key != self.inner.expected_attester_public_key {
self.update_last_queried(OffsetDateTime::now_utc());
return;
}
self.enable_upgrade_mode()
} else {
self.disable_upgrade_mode()
}
self.update_last_queried(OffsetDateTime::now_utc());
*guard = expected_attestation;
}
pub fn upgrade_mode_status(&self) -> UpgradeModeStatus {
self.inner.status.clone()
}
pub fn upgrade_mode_enabled(&self) -> bool {
self.inner.status.enabled()
}
pub fn enable_upgrade_mode(&self) {
self.inner.status.enable()
}
pub fn disable_upgrade_mode(&self) {
self.inner.status.disable()
}
pub fn last_queried(&self) -> OffsetDateTime {
// SAFETY: the stored value here is always a valid unix timestamp
#[allow(clippy::unwrap_used)]
OffsetDateTime::from_unix_timestamp(self.inner.last_queried_ts.load(Ordering::Acquire))
.unwrap()
}
pub fn update_last_queried(&self, queried_at: OffsetDateTime) {
self.inner
.last_queried_ts
.store(queried_at.unix_timestamp(), Ordering::Release);
}
pub fn since_last_query(&self) -> Duration {
(OffsetDateTime::now_utc() - self.last_queried())
.try_into()
.unwrap_or_else(|_| {
error!("somehow our last query for upgrade mode was in the future!");
Duration::ZERO
})
}
}
struct UpgradeModeStateInner {
/// Expected public key of the entity issuing upgrade mode attestations.
expected_attester_public_key: ed25519::PublicKey,
/// Contents of the published upgrade mode attestation, as queried by this node
expected_attestation: RwLock<Option<UpgradeModeAttestation>>,
/// timestamp indicating last time this node has queried for the current upgrade mode attestation
/// it is used to determine if an additional expedited query should be made in case client sends a JWT
/// whilst this node is not aware of the upgrade mode
last_queried_ts: AtomicI64,
/// flag indicating whether upgrade mode is currently enabled. this is to perform cheap checks
/// that avoid having to acquire the lock
// (and dealing with the async consequences of that)
status: UpgradeModeStatus,
}
-2
View File
@@ -23,5 +23,3 @@ rand = { workspace = true }
nym-compact-ecash = { path = "../nym_offline_compact_ecash" }
nym-ecash-time = { path = "../ecash-time" }
nym-network-defaults = { path = "../network-defaults" }
nym-upgrade-mode-check = { path = "../upgrade-mode-check" }
-29
View File
@@ -30,35 +30,6 @@ pub use nym_compact_ecash::{
};
pub use nym_ecash_time::{EcashTime, ecash_today};
pub use nym_network_defaults::TicketTypeRepr;
use nym_network_defaults::TicketTypeRepr::V1MixnetEntry;
/// Default bandwidth amount under which [mixnet] clients will attempt to send additional zk-nyms
/// to increase their allowance.
// currently defined as 20% of entry ticket value
// clients are, of course, free to override this value
pub const DEFAULT_MIXNET_REQUEST_BANDWIDTH_THRESHOLD: i64 =
(V1MixnetEntry.bandwidth_value() / 5) as i64;
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub enum BandwidthCredential {
ZkNym(Box<CredentialSpendingData>),
UpgradeModeJWT { token: String },
}
impl BandwidthCredential {
pub fn into_zk_nym(self) -> Option<Box<CredentialSpendingData>> {
match self {
BandwidthCredential::ZkNym(credential) => Some(credential),
_ => None,
}
}
}
impl From<CredentialSpendingData> for BandwidthCredential {
fn from(credential: CredentialSpendingData) -> Self {
Self::ZkNym(Box::new(credential))
}
}
#[derive(Debug, Clone)]
pub struct CredentialSigningData {
-4
View File
@@ -36,11 +36,7 @@ nym-sphinx-types = { path = "../nymsphinx/types", version = "0.2.0", default-fea
nym-pemstore = { path = "../../common/pemstore", version = "0.3.0" }
[dev-dependencies]
anyhow = { workspace = true }
rand_chacha = { workspace = true }
serde_json = { workspace = true }
nym-test-utils = { path = "../test-utils" }
[features]
default = []
@@ -17,51 +17,6 @@ pub mod bs58_ed25519_pubkey {
}
}
pub mod vec_bs58_ed25519_pubkey {
use super::*;
use serde::{Deserialize, Deserializer, Serializer, ser::SerializeSeq};
pub fn serialize<S: Serializer>(
keys: &Vec<PublicKey>,
serializer: S,
) -> Result<S::Ok, S::Error> {
let mut seq = serializer.serialize_seq(Some(keys.len()))?;
for key in keys {
seq.serialize_element(&Bs58KeyWrapper(*key))?;
}
seq.end()
}
pub fn deserialize<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<Vec<PublicKey>, D::Error> {
let wrapped = Vec::<Bs58KeyWrapper>::deserialize(deserializer)?;
Ok(wrapped.into_iter().map(|k| k.0).collect())
}
struct Bs58KeyWrapper(PublicKey);
impl serde::Serialize for Bs58KeyWrapper {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
bs58_ed25519_pubkey::serialize(&self.0, serializer)
}
}
impl<'de> Deserialize<'de> for Bs58KeyWrapper {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Ok(Bs58KeyWrapper(bs58_ed25519_pubkey::deserialize(
deserializer,
)?))
}
}
}
pub mod bs58_ed25519_signature {
use crate::asymmetric::ed25519::Signature;
use serde::{Deserialize, Deserializer, Serializer};
@@ -78,53 +33,3 @@ pub mod bs58_ed25519_signature {
Signature::from_base58_string(s).map_err(serde::de::Error::custom)
}
}
#[cfg(test)]
mod tests {
use super::*;
use jwt_simple::reexports::{anyhow, serde_json};
use nym_test_utils::helpers::deterministic_rng;
use serde::{Deserialize, Serialize};
#[test]
fn vec_bs58_ed25519_pubkey_json() -> anyhow::Result<()> {
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct KeysWrapper(#[serde(with = "vec_bs58_ed25519_pubkey")] Vec<PublicKey>);
use crate::asymmetric::ed25519;
let mut rng = deterministic_rng();
let empty = KeysWrapper(vec![]);
let single_key = KeysWrapper(vec![PublicKey::from_base58_string(
"Be9wH7xuXBRJAuV1pC7MALZv6a61RvWQ3SypsNarqTt",
)?]);
let three_keys = KeysWrapper(vec![
ed25519::KeyPair::new(&mut rng).public_key,
ed25519::KeyPair::new(&mut rng).public_key,
ed25519::KeyPair::new(&mut rng).public_key,
]);
let se_empty = serde_json::to_string(&empty)?;
let se_single_key = serde_json::to_string(&single_key)?;
let se_three_keys = serde_json::to_string(&three_keys)?;
assert_eq!(se_empty, r#"[]"#);
assert_eq!(
se_single_key,
r#"["Be9wH7xuXBRJAuV1pC7MALZv6a61RvWQ3SypsNarqTt"]"#
);
assert_eq!(
se_three_keys,
r#"["HmgHDV79LpnEaSUp8QZQwSroxVvS4RewF7yM9e7qu8y3","311xRh859qCd5MVqoPRCoNx26eYhLknGwtjzkkTJFGhf","A5BMp8WJ6Uk91U4JpWRv2Bc6X35AaRaSEy8QEWeAkaBv"]"#
);
let empty_de = serde_json::from_str::<KeysWrapper>(&se_empty)?;
let single_key_de = serde_json::from_str::<KeysWrapper>(&se_single_key)?;
let three_keys_de = serde_json::from_str::<KeysWrapper>(&se_three_keys)?;
assert_eq!(empty, empty_de);
assert_eq!(single_key, single_key_de);
assert_eq!(three_keys, three_keys_de);
Ok(())
}
}
-3
View File
@@ -51,6 +51,3 @@ anyhow = { workspace = true }
nym-compact-ecash = { path = "../nym_offline_compact_ecash" } # we need specific imports in tests
nym-test-utils = { path = "../test-utils" }
tokio = { workspace = true, features = ["full"] }
[lints]
workspace = true
+13 -53
View File
@@ -19,9 +19,7 @@ pub use shared_key::{
SharedGatewayKey, SharedKeyConversionError, SharedKeyUsageError, SharedSymmetricKey,
};
pub type GatewayProtocolVersion = u8;
pub const CURRENT_PROTOCOL_VERSION: GatewayProtocolVersion = UPGRADE_MODE_VERSION;
pub const CURRENT_PROTOCOL_VERSION: u8 = EMBEDDED_KEY_ROTATION_INFO_VERSION;
/// Defines the current version of the communication protocol between gateway and clients.
/// It has to be incremented for any breaking change.
@@ -31,73 +29,35 @@ pub const CURRENT_PROTOCOL_VERSION: GatewayProtocolVersion = UPGRADE_MODE_VERSIO
// 3 - change to AES-GCM-SIV and non-zero IVs
// 4 - introduction of v2 authentication protocol to prevent reply attacks
// 5 - add key rotation information to the serialised mix packet
// 6 - support for 'upgrade mode'
pub const INITIAL_PROTOCOL_VERSION: GatewayProtocolVersion = 1;
pub const CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION: GatewayProtocolVersion = 2;
pub const AES_GCM_SIV_PROTOCOL_VERSION: GatewayProtocolVersion = 3;
pub const AUTHENTICATE_V2_PROTOCOL_VERSION: GatewayProtocolVersion = 4;
pub const EMBEDDED_KEY_ROTATION_INFO_VERSION: GatewayProtocolVersion = 5;
pub const UPGRADE_MODE_VERSION: GatewayProtocolVersion = 6;
pub const INITIAL_PROTOCOL_VERSION: u8 = 1;
pub const CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION: u8 = 2;
pub const AES_GCM_SIV_PROTOCOL_VERSION: u8 = 3;
pub const AUTHENTICATE_V2_PROTOCOL_VERSION: u8 = 4;
pub const EMBEDDED_KEY_ROTATION_INFO_VERSION: u8 = 5;
// TODO: could using `Mac` trait here for OutputSize backfire?
// Should hmac itself be exposed, imported and used instead?
pub type LegacyGatewayMacSize = <GatewayIntegrityHmacAlgorithm as OutputSizeUser>::OutputSize;
pub trait GatewayProtocolVersionExt {
const CURRENT: GatewayProtocolVersion = CURRENT_PROTOCOL_VERSION;
fn supports_aes256_gcm_siv(&self) -> bool;
fn supports_authenticate_v2(&self) -> bool;
fn supports_key_rotation_packet(&self) -> bool;
fn supports_upgrade_mode(&self) -> bool;
fn is_future_version(&self) -> bool;
}
impl GatewayProtocolVersionExt for Option<GatewayProtocolVersion> {
impl GatewayProtocolVersionExt for Option<u8> {
fn supports_aes256_gcm_siv(&self) -> bool {
let Some(protocol) = self else { return false };
protocol.supports_aes256_gcm_siv()
let Some(protocol) = *self else { return false };
protocol >= AES_GCM_SIV_PROTOCOL_VERSION
}
fn supports_authenticate_v2(&self) -> bool {
let Some(protocol) = self else { return false };
protocol.supports_authenticate_v2()
let Some(protocol) = *self else { return false };
protocol >= AUTHENTICATE_V2_PROTOCOL_VERSION
}
fn supports_key_rotation_packet(&self) -> bool {
let Some(protocol) = self else { return false };
protocol.supports_key_rotation_packet()
}
fn supports_upgrade_mode(&self) -> bool {
let Some(protocol) = self else { return false };
protocol.supports_upgrade_mode()
}
fn is_future_version(&self) -> bool {
let Some(protocol) = self else { return false };
protocol.is_future_version()
}
}
impl GatewayProtocolVersionExt for GatewayProtocolVersion {
fn supports_aes256_gcm_siv(&self) -> bool {
*self >= AES_GCM_SIV_PROTOCOL_VERSION
}
fn supports_authenticate_v2(&self) -> bool {
*self >= AUTHENTICATE_V2_PROTOCOL_VERSION
}
fn supports_key_rotation_packet(&self) -> bool {
*self >= EMBEDDED_KEY_ROTATION_INFO_VERSION
}
fn supports_upgrade_mode(&self) -> bool {
*self >= UPGRADE_MODE_VERSION
}
fn is_future_version(&self) -> bool {
*self > CURRENT_PROTOCOL_VERSION
let Some(protocol) = *self else { return false };
protocol >= EMBEDDED_KEY_ROTATION_INFO_VERSION
}
}
+3 -3
View File
@@ -82,7 +82,7 @@ mod tests {
let exp_date_sigs = generate_expiration_date_signatures(
sig_req.expiration_date.ecash_unix_timestamp(),
&[keypair.secret_key()],
&[keypair.verification_key()],
&vec![keypair.verification_key()],
&keypair.verification_key(),
&[keypair.index.unwrap()],
)
@@ -106,14 +106,14 @@ mod tests {
.unwrap();
let wallet = issuance
.aggregate_signature_shares(&keypair.verification_key(), &[partial_wallet], sig_req)
.aggregate_signature_shares(&keypair.verification_key(), &vec![partial_wallet], sig_req)
.unwrap();
let mut issued = issuance.into_issued_ticketbook(wallet, 1);
let coin_indices_signatures = generate_coin_indices_signatures(
nym_credentials_interface::ecash_parameters(),
&[keypair.secret_key()],
&[keypair.verification_key()],
&vec![keypair.verification_key()],
&keypair.verification_key(),
&[keypair.index.unwrap()],
)
@@ -3,12 +3,10 @@
use crate::registration::handshake::messages::{Finalization, GatewayMaterialExchange};
use crate::registration::handshake::state::State;
use crate::registration::handshake::HandshakeResult;
use crate::registration::handshake::SharedGatewayKey;
use crate::registration::handshake::{error::HandshakeError, WsItem};
use crate::{GatewayProtocolVersionExt, INITIAL_PROTOCOL_VERSION};
use futures::{Sink, Stream};
use rand::{CryptoRng, RngCore};
use tracing::info;
use tungstenite::Message as WsMessage;
impl<S, R> State<'_, S, R> {
@@ -27,26 +25,10 @@ impl<S, R> State<'_, S, R> {
// 2. wait for response with remote x25519 pubkey as well as encrypted signature
// <- g^y || AES(k, sig(gate_priv, (g^y || g^x)) || MAYBE_NONCE
let (mid_res, gateway_protocol) = self
let mid_res = self
.receive_handshake_message::<GatewayMaterialExchange>()
.await?;
// NEGOTIATE PROTOCOL
if gateway_protocol.is_future_version() {
// SAFETY: future version means it's greater than CURRENT, which is always a `Some`
#[allow(clippy::unwrap_used)]
return Err(HandshakeError::UnsupportedProtocol {
version: gateway_protocol.unwrap(),
});
}
let gateway_protocol = gateway_protocol.unwrap_or(INITIAL_PROTOCOL_VERSION);
// that should never happen, but we're fine with that outcome
if Some(gateway_protocol) != self.proposed_protocol_version() {
info!("the gateway insists on protocol version different from the one we suggested. it wants {gateway_protocol} whilst we wanted {:?}, however, we can support it", self.proposed_protocol_version());
self.set_protocol_version(gateway_protocol);
}
// 3. derive shared keys locally
// hkdf::<blake3>::(g^xy)
self.derive_shared_key(&mid_res.ephemeral_dh, maybe_hkdf_salt.as_deref());
@@ -60,14 +42,14 @@ impl<S, R> State<'_, S, R> {
self.send_handshake_data(materials).await?;
// 6. wait for remote confirmation of finalizing the handshake
let (finalization, _) = self.receive_handshake_message::<Finalization>().await?;
let finalization = self.receive_handshake_message::<Finalization>().await?;
finalization.ensure_success()?;
Ok(())
}
pub(crate) async fn perform_client_handshake(
mut self,
) -> Result<HandshakeResult, HandshakeError>
) -> Result<SharedGatewayKey, HandshakeError>
where
S: Stream<Item = WsItem> + Sink<WsMessage> + Unpin,
R: CryptoRng + RngCore,
@@ -2,8 +2,6 @@
// SPDX-License-Identifier: Apache-2.0
use crate::shared_key::SharedKeyUsageError;
use crate::GatewayProtocolVersion;
use crate::GatewayProtocolVersionExt;
use thiserror::Error;
#[derive(Debug, Error)]
@@ -36,10 +34,4 @@ pub enum HandshakeError {
#[error("timed out waiting for a handshake message")]
Timeout,
#[error("Connection is in an invalid state - please send a bug report")]
ConnectionInInvalidState,
#[error("the gateway requests protocol version that's not supported by this client. it wants to use v{version} whilst we only understand up to v{}", GatewayProtocolVersion::CURRENT)]
UnsupportedProtocol { version: GatewayProtocolVersion },
}
@@ -5,11 +5,9 @@ use crate::registration::handshake::messages::{
HandshakeMessage, Initialisation, MaterialExchange,
};
use crate::registration::handshake::state::State;
use crate::registration::handshake::HandshakeResult;
use crate::registration::handshake::SharedGatewayKey;
use crate::registration::handshake::{error::HandshakeError, WsItem};
use crate::{GatewayProtocolVersion, GatewayProtocolVersionExt};
use futures::{Sink, Stream};
use tracing::{debug, warn};
use tungstenite::Message as WsMessage;
impl<S, R> State<'_, S, R> {
@@ -20,39 +18,11 @@ impl<S, R> State<'_, S, R> {
where
S: Stream<Item = WsItem> + Sink<WsMessage> + Unpin,
{
// NEGOTIATE PROTOCOL
// old clients were sending protocol version as defined by the following:
/*
fn request_protocol_version(&self) -> u8 {
if self.derive_aes256_gcm_siv_key {
AES_GCM_SIV_PROTOCOL_VERSION
} else if self.expects_credential_usage {
CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION
} else {
INITIAL_PROTOCOL_VERSION
}
}
*/
// meaning the highest possible value they could have sent was `4` (AUTHENTICATE_V2_PROTOCOL_VERSION)
// so if we received anything higher than that, it means they understand negotiation.
// currently not strictly needed as we just blindly accept what they proposed,
// but will be needed in the future.
if self.proposed_protocol_version().is_future_version() {
// this should never happen in a non-malicious client as it should use at most whatever version this gateway has announced
self.set_protocol_version(GatewayProtocolVersion::CURRENT)
} else {
// currently we accept all protocols, i.e. legacy keys, aes128, etc. so we downgrade to whatever
// the client has proposed. this will change in the future
debug!(
"using the protocol version proposed by the client: {:?}",
self.proposed_protocol_version()
)
}
// 1. receive remote ed25519 pubkey alongside ephemeral x25519 pubkey and maybe a flag indicating non-legacy client
// LOCAL_ID_PUBKEY || EPHEMERAL_KEY || MAYBE_NON_LEGACY
let init_message = Initialisation::try_from_bytes(&raw_init_message)?;
self.update_remote_identity(init_message.identity);
self.set_aes256_gcm_siv_key_derivation(!init_message.is_legacy());
// 2. derive shared keys locally
// hkdf::<blake3>::(g^xy)
@@ -69,12 +39,7 @@ impl<S, R> State<'_, S, R> {
self.send_handshake_data(material).await?;
// 4. wait for the remote response with their own encrypted signature
let (materials, client_protocol) =
self.receive_handshake_message::<MaterialExchange>().await?;
if client_protocol != self.proposed_protocol_version() {
warn!("the client hasn't accepted our proposed protocol version. we suggested {:?} while it returned {client_protocol:?}", self.proposed_protocol_version());
// TBD what to do here
}
let materials = self.receive_handshake_message::<MaterialExchange>().await?;
// 5. verify the received signature using the locally derived keys
self.verify_remote_key_material(&materials, &init_message.ephemeral_dh)?;
@@ -89,7 +54,7 @@ impl<S, R> State<'_, S, R> {
pub(crate) async fn perform_gateway_handshake(
mut self,
raw_init_message: Vec<u8>,
) -> Result<HandshakeResult, HandshakeError>
) -> Result<SharedGatewayKey, HandshakeError>
where
S: Stream<Item = WsItem> + Sink<WsMessage> + Unpin,
{
@@ -24,6 +24,13 @@ pub struct Initialisation {
pub initiator_salt: Option<Vec<u8>>,
}
impl Initialisation {
#[cfg(not(target_arch = "wasm32"))]
pub fn is_legacy(&self) -> bool {
self.initiator_salt.is_none()
}
}
#[derive(Debug)]
pub struct MaterialExchange {
pub signature_ciphertext: Vec<u8>,
@@ -92,9 +99,8 @@ impl HandshakeMessage for Initialisation {
let identity = ed25519::PublicKey::from_bytes(&bytes[..ed25519::PUBLIC_KEY_LENGTH])
.map_err(|_| HandshakeError::MalformedRequest)?;
// SAFETY: this can only fail if the provided bytes have len different from encryption::PUBLIC_KEY_SIZE
// this can only fail if the provided bytes have len different from encryption::PUBLIC_KEY_SIZE
// which is impossible
#[allow(clippy::unwrap_used)]
let ephemeral_dh =
x25519::PublicKey::from_bytes(&bytes[ed25519::PUBLIC_KEY_LENGTH..legacy_len]).unwrap();
@@ -188,7 +194,6 @@ impl HandshakeMessage for GatewayMaterialExchange {
// this can only fail if the provided bytes have len different from PUBLIC_KEY_SIZE
// which is impossible
#[allow(clippy::unwrap_used)]
let ephemeral_dh =
x25519::PublicKey::from_bytes(&bytes[..x25519::PUBLIC_KEY_SIZE]).unwrap();
let materials = MaterialExchange::try_from_bytes(&bytes[x25519::PUBLIC_KEY_SIZE..])?;

Some files were not shown because too many files have changed in this diff Show More