Compare commits

..

3 Commits

Author SHA1 Message Date
Andrej Mihajlov 81e72ba61b Add shutdown event handle 2025-10-14 20:01:47 +02:00
Jędrzej Stuczyński 0e3ffb749e using same hierarchy of trackers for client shutdown control 2025-10-14 13:13:39 +03:00
Bogdan-Ștefan Neacşu bd2e6ee812 Signal shutdown when gateway is considered unreachable 2025-10-14 12:12:16 +03:00
638 changed files with 19201 additions and 31517 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/**'
@@ -16,7 +16,7 @@ jobs:
uses: actions/checkout@v4
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.48.1
uses: mikefarah/yq@v4.47.1
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
@@ -10,13 +10,13 @@ env:
jobs:
check-if-tag-exists:
runs-on: arc-linux-latest-dind
runs-on: arc-ubuntu-22.04-dind
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.48.1
uses: mikefarah/yq@v4.47.1
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
+1 -1
View File
@@ -54,7 +54,7 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: test
args: --lib --manifest-path contracts/Cargo.toml --all-features
args: --lib --manifest-path contracts/Cargo.toml
- name: Check formatting
uses: actions-rs/cargo@v1
+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
+1 -1
View File
@@ -31,7 +31,7 @@ jobs:
components: rustfmt, clippy
- name: Set up Go
uses: actions/setup-go@v6
uses: actions/setup-go@v5
with:
go-version: "1.24.6"
+1 -1
View File
@@ -14,6 +14,6 @@ jobs:
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- name: SonarQube Scan
uses: SonarSource/sonarqube-scan-action@v6
uses: SonarSource/sonarqube-scan-action@v5
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
+1 -1
View File
@@ -34,7 +34,7 @@ jobs:
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Upload artifact
uses: actions/upload-pages-artifact@v4
uses: actions/upload-pages-artifact@v3
with:
# Upload entire repository
path: './ppa'
+3 -3
View File
@@ -8,6 +8,6 @@ jobs:
steps:
- uses: actions/first-interaction@v3
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
issue_message: 'Thank you for raising this issue'
pr_message: 'Thank you for making this first PR'
repo-token: ${{ secrets.GITHUB_TOKEN }}
issue-message: 'Thank you for raising this issue'
pr-message: 'Thank you for making this first PR'
+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
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.48.1
uses: mikefarah/yq@v4.47.1
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-credential-proxy/Cargo.toml
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.48.1
uses: mikefarah/yq@v4.47.1
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.48.1
uses: mikefarah/yq@v4.47.1
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-network-monitor/Cargo.toml
+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
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.48.1
uses: mikefarah/yq@v4.47.1
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-api/Cargo.toml
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.48.1
uses: mikefarah/yq@v4.47.1
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.48.1
uses: mikefarah/yq@v4.47.1
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.48.1
uses: mikefarah/yq@v4.47.1
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.48.1
uses: mikefarah/yq@v4.47.1
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
-194
View File
@@ -4,200 +4,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [Unreleased]
## [2026.1-niolo] (2026-01-13)
- bugfix: mozzarella -> niolo config migration ([#6259])
- chore: remove run DKG migration ([#6253])
- bugfix: reexposed 'derive_extended_private_key' ([#6247])
- Bump js-yaml from 3.14.1 to 3.14.2 in /sdk/typescript/codegen/contract-clients ([#6231])
- Statistics API v2 ([#6227])
- Bump golang.org/x/crypto from 0.39.0 to 0.45.0 in /nym-gateway-probe/netstack_ping ([#6220])
- Update chain registry link ([#6219])
- Bump glob from 10.3.4 to 10.5.0 in /documentation/scripts/post-process ([#6216])
- Bump js-yaml from 4.1.0 to 4.1.1 in /sdk/typescript/tests/integration-tests/mix-fetch ([#6215])
- gateway-probe fixes for run-local ([#6212])
- chore: updated default endpoint for retrieving attestation.json ([#6207])
- chore: remove support for legacy mixnode within the performance contract ([#6205])
- feat: upgrade mode: VPN adjustments ([#6189])
- Bump min-document from 2.19.0 to 2.19.1 ([#6181])
- Bump next from 15.4.1 to 15.4.7 in /nym-node-status-api/nym-node-status-ui ([#6180])
- feat: merge intermediate upgrade mode changes ([#6174])
- Add weighted scoring to NS API ([#6144])
- build(deps): bump mikefarah/yq from 4.47.1 to 4.48.1 ([#6107])
- build(deps): bump SonarSource/sonarqube-scan-action from 5 to 6 in /.github/workflows ([#6068])
- build(deps): bump tar-fs from 3.0.9 to 3.1.1 in /sdk/typescript/tests/integration-tests/mix-fetch ([#6063])
- build(deps): bump ammonia from 4.1.1 to 4.1.2 ([#6057])
- build(deps): bump tower-http from 0.5.2 to 0.6.6 ([#6030])
- build(deps): bump actions/setup-go from 5 to 6 ([#6013])
- build(deps): bump next from 14.2.28 to 14.2.32 ([#5996])
- build(deps): bump tracing-subscriber from 0.3.19 to 0.3.20 ([#5993])
- build(deps): bump actions/upload-pages-artifact from 3 to 4 ([#5992])
[#6259]: https://github.com/nymtech/nym/pull/6259
[#6253]: https://github.com/nymtech/nym/pull/6253
[#6247]: https://github.com/nymtech/nym/pull/6247
[#6231]: https://github.com/nymtech/nym/pull/6231
[#6227]: https://github.com/nymtech/nym/pull/6227
[#6220]: https://github.com/nymtech/nym/pull/6220
[#6219]: https://github.com/nymtech/nym/pull/6219
[#6216]: https://github.com/nymtech/nym/pull/6216
[#6215]: https://github.com/nymtech/nym/pull/6215
[#6212]: https://github.com/nymtech/nym/pull/6212
[#6207]: https://github.com/nymtech/nym/pull/6207
[#6205]: https://github.com/nymtech/nym/pull/6205
[#6189]: https://github.com/nymtech/nym/pull/6189
[#6181]: https://github.com/nymtech/nym/pull/6181
[#6180]: https://github.com/nymtech/nym/pull/6180
[#6174]: https://github.com/nymtech/nym/pull/6174
[#6144]: https://github.com/nymtech/nym/pull/6144
[#6107]: https://github.com/nymtech/nym/pull/6107
[#6068]: https://github.com/nymtech/nym/pull/6068
[#6063]: https://github.com/nymtech/nym/pull/6063
[#6057]: https://github.com/nymtech/nym/pull/6057
[#6030]: https://github.com/nymtech/nym/pull/6030
[#6013]: https://github.com/nymtech/nym/pull/6013
[#5996]: https://github.com/nymtech/nym/pull/5996
[#5993]: https://github.com/nymtech/nym/pull/5993
[#5992]: https://github.com/nymtech/nym/pull/5992
## [2025.21-mozzarella] (2025-11-25)
- [bugfix] Tunnel not waiting on MixnetClient to shut down cleanly ([#6225])
- bugfix: fix credential proxy upgrade mode attestation url arg ([#6202])
- HTTP API resilience enable & domain rotation conditions ([#6200])
- Remove debug feature from http-macro spec in gateway probe ([#6195])
- DNS relibility and troubleshooting ([#6179])
- [bugfix] Distinguish authenticator errors by credential spent ([#6176])
- Typescript SDK 1.4.1 ([#6146])
- Enable URL rotation and retries for mixnet gateway init ([#6126])
- Feature/credential proxy jwt ([#5957])
[#6225]: https://github.com/nymtech/nym/pull/6225
[#6202]: https://github.com/nymtech/nym/pull/6202
[#6200]: https://github.com/nymtech/nym/pull/6200
[#6195]: https://github.com/nymtech/nym/pull/6195
[#6179]: https://github.com/nymtech/nym/pull/6179
[#6176]: https://github.com/nymtech/nym/pull/6176
[#6146]: https://github.com/nymtech/nym/pull/6146
[#6126]: https://github.com/nymtech/nym/pull/6126
[#5957]: https://github.com/nymtech/nym/pull/5957
## [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])
- NS API: use new probe download filesize and milliseconds field ([#6097])
- ns-api: use download files size from probes instead of parsing filenames ([#6095])
- ns-api: add new fields for probe output for query_metadata and download file size and duration in ms ([#6091])
- Bugfix/bloomfilters purge ([#6089])
- Hotfix: Update API source in node ping tester script ([#6082])
- Get wireguard keypair as arg instead of reading it from disk ([#6078])
- Feature: Ping probe all nodes /described nodes from a server ([#6074])
- Node Status API: add bridge information to dVPN endpoint ([#6069])
- frontdoor typo fix ([#6067])
- Feature: Node rewards tracker ([#6064])
- [chore] Clippy fix ([#6060])
- Registration Client ([#6059])
- Bugfix: Nym node CLI download nym-node exception ([#6058])
- Feature: Nym node html landing page ([#6053])
- feat: DKG contract method for updating announce address ([#6050])
- feat: NS ticket faucet ([#6047])
- Bridge proto client params in Self-Described ([#6035])
- Node Status API: remove sqlite support ([#6004])
- Benny/ci contract fix ([#5962])
[#6102]: https://github.com/nymtech/nym/pull/6102
[#6097]: https://github.com/nymtech/nym/pull/6097
[#6095]: https://github.com/nymtech/nym/pull/6095
[#6091]: https://github.com/nymtech/nym/pull/6091
[#6089]: https://github.com/nymtech/nym/pull/6089
[#6082]: https://github.com/nymtech/nym/pull/6082
[#6078]: https://github.com/nymtech/nym/pull/6078
[#6074]: https://github.com/nymtech/nym/pull/6074
[#6069]: https://github.com/nymtech/nym/pull/6069
[#6067]: https://github.com/nymtech/nym/pull/6067
[#6064]: https://github.com/nymtech/nym/pull/6064
[#6060]: https://github.com/nymtech/nym/pull/6060
[#6059]: https://github.com/nymtech/nym/pull/6059
[#6058]: https://github.com/nymtech/nym/pull/6058
[#6053]: https://github.com/nymtech/nym/pull/6053
[#6050]: https://github.com/nymtech/nym/pull/6050
[#6047]: https://github.com/nymtech/nym/pull/6047
[#6035]: https://github.com/nymtech/nym/pull/6035
[#6004]: https://github.com/nymtech/nym/pull/6004
[#5962]: https://github.com/nymtech/nym/pull/5962
## [2025.17-isabirra] (2025-09-29)
- Bugfix | Fix the registration handshake ([#6062])
Generated
+851 -721
View File
File diff suppressed because it is too large Load Diff
+16 -11
View File
@@ -150,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",
@@ -171,7 +171,6 @@ members = [
default-members = [
"clients/native",
"clients/socks5",
"nym-authenticator-client",
"nym-api",
"nym-credential-proxy/nym-credential-proxy",
"nym-node",
@@ -216,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"
@@ -243,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"
dirs = "5.0"
doc-comment = "0.3"
dotenvy = "0.15.6"
dyn-clone = "1.0.19"
ecdsa = "0.16"
@@ -263,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"
@@ -288,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"
@@ -300,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"
@@ -307,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"
@@ -347,17 +358,16 @@ tokio-tungstenite = { version = "0.20.1" }
tokio-util = "0.7.15"
toml = "0.8.22"
tower = "0.5.2"
tower-http = "0.6.6"
tower-http = "0.5.2"
tracing = "0.1.41"
tracing-log = "0.2"
tracing-opentelemetry = "0.19.0"
tracing-subscriber = "0.3.20"
tracing-subscriber = "0.3.19"
tracing-tree = "0.2.2"
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"
@@ -454,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.68"
version = "1.1.63"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
-1
View File
@@ -60,7 +60,6 @@ impl SocketClient {
let ClientInput {
connection_command_sender,
input_sender,
..
} = client_input;
let ClientOutput {
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.68"
version = "1.1.63"
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 -1
View File
@@ -36,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" }
@@ -114,12 +114,13 @@ where
})?;
hardcoded_topology.entry_capable_nodes().cloned().collect()
} else {
let mut rng = rand::thread_rng();
crate::init::helpers::gateways_for_init(
&mut rng,
&core.client.nym_api_urls,
user_agent,
core.debug.topology.minimum_gateway_performance,
core.debug.topology.ignore_ingress_epoch_role,
None,
)
.await?
};
@@ -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,16 +173,13 @@ 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);
let mut rng = rand::thread_rng();
crate::init::helpers::gateways_for_init(
&mut rng,
&core.client.nym_api_urls,
user_agent,
minimum_performance,
core.debug.topology.minimum_gateway_performance,
core.debug.topology.ignore_ingress_epoch_role,
None,
)
.await?
};
+29 -199
View File
@@ -7,12 +7,11 @@ use super::statistics_control::StatisticsControl;
use crate::client::base_client::storage::helpers::store_client_keys;
use crate::client::base_client::storage::MixnetClientStorage;
use crate::client::cover_traffic_stream::LoopCoverTrafficStream;
use crate::client::event_control::EventControl;
use crate::client::inbound_messages::{InputMessage, InputMessageReceiver, InputMessageSender};
use crate::client::key_manager::persistence::KeyStore;
use crate::client::key_manager::ClientKeys;
use crate::client::mix_traffic::transceiver::{GatewayReceiver, GatewayTransceiver, RemoteGateway};
use crate::client::mix_traffic::{BatchMixMessageSender, MixTrafficController, MixTrafficEvent};
use crate::client::mix_traffic::{BatchMixMessageSender, MixTrafficController};
use crate::client::real_messages_control;
use crate::client::real_messages_control::RealMessagesController;
use crate::client::received_buffer::{
@@ -67,16 +66,13 @@ use std::path::Path;
use std::sync::Arc;
use time::OffsetDateTime;
use tokio::sync::mpsc::Sender;
use tracing::*;
use url::Url;
#[cfg(target_arch = "wasm32")]
#[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",
@@ -87,28 +83,10 @@ pub mod non_wasm_helpers;
pub mod helpers;
pub mod storage;
#[derive(Clone, Copy, Debug)]
pub enum MixnetClientEvent {
Traffic(MixTrafficEvent),
}
pub type EventReceiver = mpsc::UnboundedReceiver<MixnetClientEvent>;
#[derive(Clone)]
pub struct EventSender(pub mpsc::UnboundedSender<MixnetClientEvent>);
impl EventSender {
pub fn send(&self, event: MixnetClientEvent) {
if let Err(err) = self.0.unbounded_send(event) {
tracing::warn!("Failed to send error event. The caller event reader was closed: {err}");
}
}
}
#[derive(Clone)]
pub struct ClientInput {
pub connection_command_sender: ConnectionCommandSender,
pub input_sender: InputMessageSender,
pub client_request_sender: ClientRequestSender,
}
impl ClientInput {
@@ -216,14 +194,10 @@ 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>>,
shutdown: Option<ShutdownTracker>,
event_tx: Option<EventSender>,
user_agent: Option<UserAgent>,
setup_method: GatewaySetup,
@@ -248,12 +222,10 @@ 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,
shutdown: None,
event_tx: None,
user_agent: None,
setup_method: GatewaySetup::MustLoad { gateway_id: None },
#[cfg(unix)]
@@ -271,16 +243,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;
@@ -326,12 +288,6 @@ where
self
}
#[must_use]
pub fn with_event_tx(mut self, event_tx: EventSender) -> Self {
self.event_tx = Some(event_tx);
self
}
#[must_use]
pub fn with_user_agent(mut self, user_agent: UserAgent) -> Self {
self.user_agent = Some(user_agent);
@@ -362,18 +318,6 @@ where
details.client_address()
}
fn start_event_control(
parent_event_tx: Option<EventSender>,
children_event_rx: EventReceiver,
shutdown_tracker: &ShutdownTracker,
) {
let event_control = EventControl::new(parent_event_tx, children_event_rx);
shutdown_tracker.try_spawn_named_with_shutdown(
async move { event_control.run().await },
"EventControl",
);
}
// future constantly pumping loop cover traffic at some specified average rate
// the pumped traffic goes to the MixTrafficController
fn start_cover_traffic_stream(
@@ -385,7 +329,7 @@ where
stats_tx: ClientStatsSender,
shutdown_tracker: &ShutdownTracker,
) {
tracing::info!("Starting loop cover traffic stream...");
info!("Starting loop cover traffic stream...");
let mut stream = LoopCoverTrafficStream::new(
ack_key,
@@ -417,7 +361,7 @@ where
stats_tx: ClientStatsSender,
shutdown_tracker: &ShutdownTracker,
) {
tracing::info!("Starting real traffic stream...");
info!("Starting real traffic stream...");
let real_messages_controller = RealMessagesController::new(
controller_config,
@@ -502,7 +446,7 @@ where
metrics_reporter: ClientStatsSender,
shutdown_tracker: &ShutdownTracker,
) {
tracing::info!("Starting received messages buffer controller...");
info!("Starting received messages buffer controller...");
let controller = ReceivedMessagesBufferController::<SphinxMessageReceiver>::new(
local_encryption_keypair,
query_receiver,
@@ -613,7 +557,7 @@ where
details_store
.upgrade_stored_remote_gateway_key(gateway_client.gateway_identity(), &updated_key)
.await.map_err(|err| {
tracing::error!("failed to store upgraded gateway key! this connection might be forever broken now: {err}");
error!("failed to store upgraded gateway key! this connection might be forever broken now: {err}");
ClientCoreError::GatewaysDetailsStoreError { source: Box::new(err) }
})?
}
@@ -710,7 +654,7 @@ where
if topology_config.disable_refreshing {
// if we're not spawning the refresher, don't cause shutdown immediately
tracing::info!("The background topology refresher is not going to be started");
info!("The background topology refresher is not going to be started");
}
let mut topology_refresher = TopologyRefresher::new(
@@ -720,7 +664,7 @@ where
);
// before returning, block entire runtime to refresh the current network view so that any
// components depending on topology would see a non-empty view
tracing::info!("Obtaining initial network topology");
info!("Obtaining initial network topology");
topology_refresher.try_refresh().await;
if let Err(err) = topology_refresher.ensure_topology_is_routable().await {
@@ -746,13 +690,13 @@ where
.wait_for_gateway(local_gateway, waiting_timeout)
.await
{
tracing::error!(
error!(
"the gateway did not come back online within the specified timeout: {err}"
);
return Err(err.into());
}
} else {
tracing::error!("the gateway we're supposedly connected to does not exist. We'll not be able to send any packets to ourselves: {err}");
error!("the gateway we're supposedly connected to does not exist. We'll not be able to send any packets to ourselves: {err}");
return Err(err.into());
}
}
@@ -760,7 +704,7 @@ where
if !topology_config.disable_refreshing {
// don't spawn the refresher if we don't want to be refreshing the topology.
// only use the initial values obtained
tracing::info!("Starting topology refresher...");
info!("Starting topology refresher...");
shutdown_tracker.try_spawn_named_with_shutdown(
async move { topology_refresher.run().await },
"TopologyRefresher",
@@ -777,7 +721,7 @@ where
input_sender: Sender<InputMessage>,
shutdown_tracker: &ShutdownTracker,
) -> ClientStatsSender {
tracing::info!("Starting statistics control...");
info!("Starting statistics control...");
StatisticsControl::create_and_start(
config.debug.stats_reporting,
user_agent
@@ -792,17 +736,10 @@ where
fn start_mix_traffic_controller(
gateway_transceiver: Box<dyn GatewayTransceiver + Send>,
shutdown_tracker: &ShutdownTracker,
event_tx: EventSender,
) -> (BatchMixMessageSender, ClientRequestSender) {
tracing::info!("Starting mix traffic controller...");
let mut mix_traffic_controller = MixTrafficController::new(
gateway_transceiver,
shutdown_tracker.clone_shutdown_token(),
event_tx,
);
let mix_tx = mix_traffic_controller.mix_tx();
let client_tx = mix_traffic_controller.client_tx();
info!("Starting mix traffic controller...");
let (mut mix_traffic_controller, mix_tx, client_tx) =
MixTrafficController::new(gateway_transceiver, shutdown_tracker.clone_shutdown_token());
shutdown_tracker.try_spawn_named(
async move { mix_traffic_controller.run().await },
@@ -866,7 +803,7 @@ where
{
// if client keys do not exist already, create and persist them
if key_store.load_keys().await.is_err() {
tracing::info!("could not find valid client keys - a new set will be generated");
info!("could not find valid client keys - a new set will be generated");
let mut rng = OsRng;
let keys = if let Some(derivation_material) = derivation_material {
ClientKeys::from_master_key(&mut rng, &derivation_material)
@@ -881,67 +818,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)
}
@@ -959,7 +850,7 @@ where
<S::CredentialStore as CredentialStorage>::StorageError: Send + Sync + 'static,
<S::GatewaysDetailsStore as GatewaysDetailsStore>::StorageError: Sync + Send,
{
tracing::info!("Starting nym client");
info!("Starting nym client");
#[cfg(debug_assertions)]
#[cfg(target_arch = "wasm32")]
{
@@ -993,9 +884,6 @@ where
// channels responsible for controlling real messages
let (input_sender, input_receiver) = tokio::sync::mpsc::channel::<InputMessage>(1);
// channels responsible for event management
let (event_sender, event_receiver) = mpsc::unbounded();
// channels responsible for controlling ack messages
let (ack_sender, ack_receiver) = mpsc::unbounded();
let shared_topology_accessor =
@@ -1005,11 +893,9 @@ where
// 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()?,
None => nym_task::get_sdk_shutdown_tracker()?,
};
Self::start_event_control(self.event_tx, event_receiver, &shutdown_tracker);
// channels responsible for dealing with reply-related fun
let (reply_controller_sender, reply_controller_receiver) =
reply_controller::requests::new_control_channels();
@@ -1025,11 +911,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(
@@ -1101,11 +983,8 @@ where
// traffic stream.
// The MixTrafficController then sends the actual traffic
let (message_sender, client_request_sender) = Self::start_mix_traffic_controller(
gateway_transceiver,
&shutdown_tracker.clone(),
EventSender(event_sender),
);
let (message_sender, client_request_sender) =
Self::start_mix_traffic_controller(gateway_transceiver, &shutdown_tracker.clone());
// Channels that the websocket listener can use to signal downstream to the real traffic
// controller that connections are closed.
@@ -1154,8 +1033,8 @@ where
);
}
tracing::debug!("Core client startup finished!");
tracing::debug!("The address of this client is: {self_address}");
debug!("Core client startup finished!");
debug!("The address of this client is: {self_address}");
#[cfg(debug_assertions)]
#[cfg(target_arch = "wasm32")]
@@ -1171,7 +1050,6 @@ where
client_input: ClientInput {
connection_command_sender: client_connection_tx,
input_sender,
client_request_sender,
},
},
client_output: ClientOutputStatus::AwaitingConsumer {
@@ -1187,6 +1065,7 @@ where
},
stats_reporter,
shutdown_handle: shutdown_tracker, // The primary tracker for this client
client_request_sender,
forget_me: self.config.debug.forget_me,
remember_me: self.config.debug.remember_me,
})
@@ -1200,57 +1079,8 @@ pub struct BaseClient {
pub client_output: ClientOutputStatus,
pub client_state: ClientState,
pub stats_reporter: ClientStatsSender,
pub client_request_sender: ClientRequestSender,
pub shutdown_handle: ShutdownTracker,
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,40 +0,0 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use futures::StreamExt;
use crate::client::base_client::{EventReceiver, EventSender, MixnetClientEvent};
/// Launches and manages task events, propagating upwards what is not strictly internal.
pub(crate) struct EventControl {
parent_event_tx: Option<EventSender>,
children_event_rx: EventReceiver,
}
impl EventControl {
pub(crate) fn new(
parent_event_tx: Option<EventSender>,
children_event_rx: EventReceiver,
) -> Self {
EventControl {
parent_event_tx,
children_event_rx,
}
}
fn is_internal(event: MixnetClientEvent) -> bool {
match event {
MixnetClientEvent::Traffic(_) => false,
}
}
pub(crate) async fn run(mut self) {
while let Some(event) = self.children_event_rx.next().await {
if let Some(parent_event_tx) = &self.parent_event_tx {
if !Self::is_internal(event) {
parent_event_tx.send(event);
}
}
}
}
}
@@ -1,10 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::{
base_client::{EventSender, MixnetClientEvent},
mix_traffic::transceiver::GatewayTransceiver,
};
use crate::client::mix_traffic::transceiver::GatewayTransceiver;
use nym_gateway_requests::ClientRequest;
use nym_sphinx::forwarding::packet::MixPacket;
use nym_task::ShutdownToken;
@@ -20,41 +17,33 @@ 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;
#[derive(Clone, Copy, Debug)]
pub enum MixTrafficEvent {
FailedSendingSphinx,
}
pub struct MixTrafficController {
gateway_transceiver: Box<dyn GatewayTransceiver + Send>,
mix_tx: BatchMixMessageSender,
mix_rx: BatchMixMessageReceiver,
client_rx: ClientRequestReceiver,
client_tx: ClientRequestSender,
// TODO: this is temporary work-around.
// in long run `gateway_client` will be moved away from `MixTrafficController` anyway.
consecutive_gateway_failure_count: usize,
shutdown_token: ShutdownToken,
event_tx: EventSender,
}
impl MixTrafficController {
pub fn new<T>(
gateway_transceiver: T,
shutdown_token: ShutdownToken,
event_tx: EventSender,
) -> MixTrafficController
) -> (
MixTrafficController,
BatchMixMessageSender,
ClientRequestSender,
)
where
T: GatewayTransceiver + Send + 'static,
{
@@ -63,32 +52,41 @@ impl MixTrafficController {
let (client_sender, client_receiver) = tokio::sync::mpsc::channel(8);
MixTrafficController {
gateway_transceiver: Box::new(gateway_transceiver),
mix_tx: message_sender,
mix_rx: message_receiver,
client_rx: client_receiver,
client_tx: client_sender,
consecutive_gateway_failure_count: 0,
shutdown_token,
event_tx,
}
(
MixTrafficController {
gateway_transceiver: Box::new(gateway_transceiver),
mix_rx: message_receiver,
client_rx: client_receiver,
consecutive_gateway_failure_count: 0,
shutdown_token,
},
message_sender,
client_sender,
)
}
pub fn new_dynamic(
gateway_transceiver: Box<dyn GatewayTransceiver + Send>,
shutdown_token: ShutdownToken,
event_tx: EventSender,
) -> MixTrafficController {
Self::new(gateway_transceiver, shutdown_token, event_tx)
}
pub fn client_tx(&self) -> ClientRequestSender {
self.client_tx.clone()
}
pub fn mix_tx(&self) -> BatchMixMessageSender {
self.mix_tx.clone()
) -> (
MixTrafficController,
BatchMixMessageSender,
ClientRequestSender,
) {
let (message_sender, message_receiver) =
tokio::sync::mpsc::channel(MIX_MESSAGE_RECEIVER_BUFFER_SIZE);
let (client_sender, client_receiver) = tokio::sync::mpsc::channel(8);
(
MixTrafficController {
gateway_transceiver,
mix_rx: message_receiver,
client_rx: client_receiver,
consecutive_gateway_failure_count: 0,
shutdown_token,
},
message_sender,
client_sender,
)
}
async fn on_messages(
@@ -147,31 +145,35 @@ impl MixTrafficController {
trace!("MixTrafficController: Received shutdown");
break;
}
// mix_rx should never error out as we're holding one instance of the sender
Some(mix_packets) = self.mix_rx.recv() => {
if let Err(err) = self.on_messages(mix_packets).await {
error!("Failed to send sphinx packet(s) to the gateway: {err}");
if self.consecutive_gateway_failure_count == MAX_FAILURE_COUNT {
// Disconnect from the gateway. If we should try to re-connect
// is handled at a higher layer.
error!("Failed to send sphinx packet to the gateway {MAX_FAILURE_COUNT} times in a row - assuming the gateway is dead");
// 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;
mix_packets = self.mix_rx.recv() => match mix_packets {
Some(mix_packets) => {
if let Err(err) = self.on_messages(mix_packets).await {
error!("Failed to send sphinx packet(s) to the gateway: {err}");
if self.consecutive_gateway_failure_count == MAX_FAILURE_COUNT {
// Disconnect from the gateway. If we should try to re-connect
// is handled at a higher layer.
error!("Failed to send sphinx packet to the gateway {MAX_FAILURE_COUNT} times in a row - assuming the gateway is dead");
// Do we need to handle the embedded mixnet client case
// separately?
self.shutdown_token.cancel();
break;
}
}
},
None => {
trace!("MixTrafficController: Stopping since channel closed");
break;
}
},
client_request = self.client_rx.recv() => match client_request {
Some(client_request) => {
self.on_client_request(client_request).await;
},
None => {
trace!("MixTrafficController, client request channel closed");
break
}
},
// client_rx should never error out as we're holding one instance of the sender
Some(client_request) = self.client_rx.recv() => {
self.on_client_request(client_request).await;
}
}
}
debug!("MixTrafficController: Exiting");
-1
View File
@@ -3,7 +3,6 @@
pub mod base_client;
pub mod cover_traffic_stream;
pub(crate) mod event_control;
pub(crate) mod helpers;
pub mod inbound_messages;
pub mod key_manager;
@@ -298,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() {
+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,
+20 -77
View File
@@ -45,7 +45,6 @@ type WsConn = JSWebsocket;
const CONCURRENT_GATEWAYS_MEASURED: usize = 20;
const MEASUREMENTS: usize = 3;
const DEFAULT_NYM_API_RETRIES: usize = 3;
#[cfg(not(target_arch = "wasm32"))]
const CONN_TIMEOUT: Duration = Duration::from_millis(1500);
@@ -133,27 +132,25 @@ impl<'a, G: ConnectableGateway> GatewayWithLatency<'a, G> {
}
}
pub async fn gateways_for_init(
pub async fn gateways_for_init<R: Rng>(
rng: &mut R,
nym_apis: &[Url],
user_agent: Option<UserAgent>,
minimum_performance: u8,
ignore_epoch_roles: bool,
retry_count: Option<usize>,
) -> Result<Vec<RoutingNode>, ClientCoreError> {
// Build client with ALL URLs for fallback support
let nym_api_urls: Vec<nym_http_api_client::Url> = nym_apis
.iter()
.map(|url| nym_http_api_client::Url::from(url.clone()))
.collect();
let nym_api = nym_apis
.choose(rng)
.ok_or(ClientCoreError::ListOfNymApisIsEmpty)?;
if nym_api_urls.is_empty() {
return Err(ClientCoreError::ListOfNymApisIsEmpty);
}
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())?
.with_retries(retry_count)
.with_bincode();
// Use the unified HTTP client directly with optional user agent
let mut builder = nym_http_api_client::Client::builder(nym_api.clone())
.map_err(|e| {
ClientCoreError::ValidatorClientError(nym_validator_client::ValidatorClientError::from(
e,
))
})?
.with_bincode(); // Use bincode for better performance
if let Some(user_agent) = user_agent {
builder = builder.with_user_agent(user_agent);
@@ -163,7 +160,7 @@ pub async fn gateways_for_init(
ClientCoreError::ValidatorClientError(nym_validator_client::ValidatorClientError::from(e))
})?;
tracing::debug!("Fetching list of gateways from: {:?}", nym_api_urls);
tracing::debug!("Fetching list of gateways from: {nym_api}");
// Use our helper to handle pagination
let gateways = get_all_basic_entry_nodes_with_metadata(&client, true)
@@ -175,15 +172,17 @@ pub async fn gateways_for_init(
// filter out gateways below minimum performance and ones that could operate as a mixnode
// (we don't want instability)
let valid_gateways: Vec<RoutingNode> = gateways
let valid_gateways = gateways
.iter()
.filter(|g| ignore_epoch_roles || !g.supported_roles.mixnode)
.filter(|g| g.performance.round_to_integer() >= minimum_performance)
.filter_map(|gateway| gateway.try_into().ok())
.collect();
.collect::<Vec<_>>();
tracing::debug!("After checking validity: {}", valid_gateways.len());
tracing::trace!("Valid gateways: {valid_gateways:#?}");
tracing::info!(
"Found {} valid gateways after filtering",
"and {} after validity and performance filtering",
valid_gateways.len()
);
@@ -346,20 +345,13 @@ pub(super) fn get_specified_gateway(
must_use_tls: bool,
) -> Result<RoutingNode, ClientCoreError> {
tracing::debug!("Requesting specified gateway: {gateway_identity}");
let user_gateway = ed25519::PublicKey::from_base58_string(gateway_identity)
.map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)?;
let gateway = gateways
.iter()
.find(|gateway| gateway.identity_key == user_gateway)
.ok_or_else(|| {
tracing::debug!(
"Gateway {gateway_identity} not found in {} available gateways",
gateways.len()
);
ClientCoreError::NoGatewayWithId(gateway_identity.to_string())
})?;
.ok_or_else(|| ClientCoreError::NoGatewayWithId(gateway_identity.to_string()))?;
let Some(entry_details) = gateway.entry.as_ref() else {
return Err(ClientCoreError::UnsupportedEntry {
@@ -422,52 +414,3 @@ pub(super) async fn register_with_gateway(
authenticated_ephemeral_client: gateway_client,
})
}
#[cfg(test)]
mod tests {
use url::Url;
#[test]
fn test_single_url_builds_without_retries() {
let urls = [Url::parse("https://api.nym.com").unwrap()];
let nym_api_urls: Vec<nym_http_api_client::Url> = urls
.iter()
.map(|url| nym_http_api_client::Url::from(url.clone()))
.collect();
assert_eq!(nym_api_urls.len(), 1, "Should have exactly one URL");
}
#[test]
fn test_multiple_urls_prepared_for_retries() {
let urls = [
Url::parse("https://api1.nym.com").unwrap(),
Url::parse("https://api2.nym.com").unwrap(),
Url::parse("https://api3.nym.com").unwrap(),
];
let nym_api_urls: Vec<nym_http_api_client::Url> = urls
.iter()
.map(|url| nym_http_api_client::Url::from(url.clone()))
.collect();
assert_eq!(nym_api_urls.len(), 3, "Should have all three URLs");
assert!(
nym_api_urls.len() > 1,
"Multiple URLs trigger retry behavior"
);
}
#[test]
fn test_empty_url_list_is_detected() {
let urls: Vec<Url> = vec![];
let nym_api_urls: Vec<nym_http_api_client::Url> = urls
.iter()
.map(|url| nym_http_api_client::Url::from(url.clone()))
.collect();
assert!(nym_api_urls.is_empty(), "Empty list should remain empty");
}
}
@@ -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",
}
}
}
@@ -75,7 +75,6 @@ workspace = true
features = ["json", "rustls-tls"]
[dev-dependencies]
anyhow = { workspace = true }
bip39 = { workspace = true }
cosmrs = { workspace = true, features = ["bip32"] }
ts-rs = { workspace = true }
@@ -7,7 +7,6 @@ use cosmrs::{tx, AccountId, Coin, Denom};
use nym_validator_client::http_client;
use nym_validator_client::nyxd::CosmWasmClient;
use nym_validator_client::signing::direct_wallet::DirectSecp256k1HdWallet;
use nym_validator_client::signing::signer::OfflineSigner;
use nym_validator_client::signing::tx_signer::TxSigner;
use nym_validator_client::signing::SignerData;
@@ -20,8 +19,8 @@ async fn main() {
let validator = "https://rpc.sandbox.nymtech.net";
let to_address: AccountId = "n1pefc2utwpy5w78p2kqdsfmpjxfwmn9d39k5mqa".parse().unwrap();
let signer = DirectSecp256k1HdWallet::checked_from_mnemonic(prefix, signer_mnemonic).unwrap();
let signer_address = signer.signer_addresses()[0].clone();
let signer = DirectSecp256k1HdWallet::from_mnemonic(prefix, signer_mnemonic);
let signer_address = signer.try_derive_accounts().unwrap()[0].address().clone();
// local 'client' ONLY signing messages
let tx_signer = signer;
@@ -58,15 +57,9 @@ async fn main() {
100000u32,
);
let tx_raw = TxSigner::sign_direct(
&tx_signer,
&signer_address,
vec![send_msg],
fee,
memo,
signer_data,
)
.unwrap();
let tx_raw = tx_signer
.sign_direct(&signer_address, vec![send_msg], fee, memo, signer_data)
.unwrap();
let tx_bytes = tx_raw.to_bytes().unwrap();
// compare balances from before and after the tx
@@ -5,7 +5,8 @@ use crate::nyxd::{self, NyxdClient};
use crate::signing::direct_wallet::DirectSecp256k1HdWallet;
use crate::signing::signer::{NoSigner, OfflineSigner};
use crate::{
DirectSigningReqwestRpcValidatorClient, QueryReqwestRpcValidatorClient, ValidatorClientError,
DirectSigningReqwestRpcValidatorClient, QueryReqwestRpcValidatorClient, ReqwestRpcClient,
ValidatorClientError,
};
use nym_api_requests::ecash::models::{
AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse,
@@ -163,7 +164,7 @@ impl Client<HttpRpcClient, DirectSecp256k1HdWallet> {
) -> Result<DirectSigningHttpRpcValidatorClient, ValidatorClientError> {
let rpc_client = http_client(config.nyxd_url.as_str())?;
let prefix = &config.nyxd_config.chain_details.bech32_account_prefix;
let wallet = DirectSecp256k1HdWallet::checked_from_mnemonic(prefix, mnemonic)?;
let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic);
Ok(Self::new_signing_with_rpc_client(
config, rpc_client, wallet,
@@ -176,13 +177,12 @@ impl Client<HttpRpcClient, DirectSecp256k1HdWallet> {
}
}
#[allow(deprecated)]
impl Client<crate::ReqwestRpcClient, DirectSecp256k1HdWallet> {
impl Client<ReqwestRpcClient, DirectSecp256k1HdWallet> {
pub fn new_reqwest_signing(
config: Config,
mnemonic: bip39::Mnemonic,
) -> DirectSigningReqwestRpcValidatorClient {
let rpc_client = crate::ReqwestRpcClient::new(config.nyxd_url.clone());
let rpc_client = ReqwestRpcClient::new(config.nyxd_url.clone());
let prefix = &config.nyxd_config.chain_details.bech32_account_prefix;
let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic);
@@ -203,10 +203,9 @@ impl Client<HttpRpcClient> {
}
}
#[allow(deprecated)]
impl Client<crate::ReqwestRpcClient> {
impl Client<ReqwestRpcClient> {
pub fn new_reqwest_query(config: Config) -> QueryReqwestRpcValidatorClient {
let rpc_client = crate::ReqwestRpcClient::new(config.nyxd_url.clone());
let rpc_client = ReqwestRpcClient::new(config.nyxd_url.clone());
Self::new_with_rpc_client(config, rpc_client)
}
}
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: Apache-2.0
use crate::nym_api;
use crate::signing::direct_wallet::DirectSecp256k1HdWalletError;
pub use tendermint_rpc::error::Error as TendermintRpcError;
use thiserror::Error;
@@ -27,12 +26,6 @@ pub enum ValidatorClientError {
#[error("No validator API url has been provided")]
NoAPIUrlAvailable,
#[error("failed to derive signing accounts: {source}")]
AccountDerivationFailure {
#[from]
source: DirectSecp256k1HdWalletError,
},
}
impl From<nym_api::error::NymAPIError> for ValidatorClientError {
@@ -12,7 +12,6 @@ pub mod rpc;
pub mod signing;
pub use crate::error::ValidatorClientError;
#[allow(deprecated)]
pub use crate::rpc::reqwest::ReqwestRpcClient;
pub use crate::signing::direct_wallet::DirectSecp256k1HdWallet;
pub use client::{Client, Config, EcashApiClient};
@@ -39,13 +38,9 @@ pub type DirectSigningHttpRpcValidatorClient = Client<HttpRpcClient, DirectSecp2
#[cfg(feature = "http-client")]
pub type DirectSigningHttpRpcNyxdClient = nyxd::NyxdClient<HttpRpcClient, DirectSecp256k1HdWallet>;
#[allow(deprecated)]
pub type QueryReqwestRpcValidatorClient = Client<ReqwestRpcClient>;
#[allow(deprecated)]
pub type QueryReqwestRpcNyxdClient = nyxd::NyxdClient<ReqwestRpcClient>;
#[allow(deprecated)]
pub type DirectSigningReqwestRpcValidatorClient = Client<ReqwestRpcClient, DirectSecp256k1HdWallet>;
#[allow(deprecated)]
pub type DirectSigningReqwestRpcNyxdClient =
nyxd::NyxdClient<ReqwestRpcClient, DirectSecp256k1HdWallet>;
@@ -178,7 +178,7 @@ where
.ok_or_else(|| NyxdError::unavailable_contract_address("dkg contract"))?;
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier())));
let signer_address = &self.signer_addresses()[0];
let signer_address = &self.signer_addresses()?[0];
self.execute(signer_address, dkg_contract_address, &msg, fee, memo, funds)
.await
@@ -99,7 +99,7 @@ where
.ok_or_else(|| NyxdError::unavailable_contract_address("coconut bandwidth contract"))?;
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier())));
let signer_address = &self.signer_addresses()[0];
let signer_address = &self.signer_addresses()?[0];
self.execute(
signer_address,
@@ -95,7 +95,7 @@ where
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier())));
let signer_address = &self.signer_addresses()[0];
let signer_address = &self.signer_addresses()?[0];
self.execute(
signer_address,
group_contract_address,
@@ -667,7 +667,7 @@ where
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier())));
let memo = msg.default_memo();
let signer_address = &self.signer_addresses()[0];
let signer_address = &self.signer_addresses()?[0];
self.execute(
signer_address,
mixnet_contract_address,
@@ -133,7 +133,7 @@ where
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier())));
let signer_address = &self.signer_addresses()[0];
let signer_address = &self.signer_addresses()?[0];
self.execute(
signer_address,
multisig_contract_address,
@@ -165,7 +165,7 @@ where
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier())));
let signer_address = &self.signer_addresses()[0];
let signer_address = &self.signer_addresses()?[0];
self.execute(
signer_address,
performance_contract_address,
@@ -375,7 +375,7 @@ where
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier())));
let memo = msg.name().to_string();
let signer_address = &self.signer_addresses()[0];
let signer_address = &self.signer_addresses()?[0];
self.execute(
signer_address,
vesting_contract_address,
@@ -324,7 +324,7 @@ where
{
type Error = S::Error;
fn get_accounts(&self) -> &[AccountData] {
fn get_accounts(&self) -> Result<Vec<AccountData>, Self::Error> {
self.signer.get_accounts()
}
@@ -19,7 +19,7 @@ use crate::signing::signer::NoSigner;
use crate::signing::signer::OfflineSigner;
use crate::signing::tx_signer::TxSigner;
use crate::signing::AccountData;
use crate::{DirectSigningReqwestRpcNyxdClient, QueryReqwestRpcNyxdClient};
use crate::{DirectSigningReqwestRpcNyxdClient, QueryReqwestRpcNyxdClient, ReqwestRpcClient};
use async_trait::async_trait;
use cosmrs::tendermint::{abci, evidence::Evidence, Genesis};
use cosmrs::tx::{Raw, SignDoc};
@@ -158,13 +158,12 @@ impl NyxdClient<HttpClient> {
}
}
#[allow(deprecated)]
impl NyxdClient<crate::ReqwestRpcClient> {
impl NyxdClient<ReqwestRpcClient> {
pub fn connect_reqwest(
config: Config,
endpoint: Url,
) -> Result<QueryReqwestRpcNyxdClient, NyxdError> {
let client = crate::ReqwestRpcClient::new(endpoint);
let client = ReqwestRpcClient::new(endpoint);
Ok(NyxdClient {
client: MaybeSigningClient::new(client, (&config).into()),
@@ -196,19 +195,18 @@ impl NyxdClient<HttpClient, DirectSecp256k1HdWallet> {
let client = http_client(endpoint)?;
let prefix = &config.chain_details.bech32_account_prefix;
let wallet = DirectSecp256k1HdWallet::checked_from_mnemonic(prefix, mnemonic)?;
let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic);
Ok(Self::connect_with_signer(config, client, wallet))
}
}
#[allow(deprecated)]
impl NyxdClient<crate::ReqwestRpcClient, DirectSecp256k1HdWallet> {
impl NyxdClient<ReqwestRpcClient, DirectSecp256k1HdWallet> {
pub fn connect_reqwest_with_mnemonic(
config: Config,
endpoint: Url,
mnemonic: bip39::Mnemonic,
) -> DirectSigningReqwestRpcNyxdClient {
let client = crate::ReqwestRpcClient::new(endpoint);
let client = ReqwestRpcClient::new(endpoint);
let prefix = &config.chain_details.bech32_account_prefix;
let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic);
@@ -393,12 +391,17 @@ where
S: OfflineSigner + Send + Sync,
NyxdError: From<<S as OfflineSigner>::Error>,
{
pub fn signing_account(&self) -> Result<&AccountData, NyxdError> {
pub fn signing_account(&self) -> Result<AccountData, NyxdError> {
Ok(self.find_account(&self.address())?)
}
pub fn address(&self) -> AccountId {
self.client.signer_addresses()[0].clone()
match self.client.signer_addresses() {
Ok(addresses) => addresses[0].clone(),
Err(_) => {
panic!("key derivation failure")
}
}
}
pub fn mix_coin(&self, amount: u128) -> Coin {
@@ -864,7 +867,7 @@ where
{
type Error = S::Error;
fn get_accounts(&self) -> &[AccountData] {
fn get_accounts(&self) -> Result<Vec<AccountData>, Self::Error> {
self.client.get_accounts()
}
@@ -42,15 +42,12 @@ macro_rules! perform_with_compat {
}};
}
// the separate implementation is now completely redundant
#[deprecated(note = "use HttpClient directly instead")]
pub struct ReqwestRpcClient {
compat: CompatMode,
inner: reqwest::Client,
url: Url,
}
#[allow(deprecated)]
impl ReqwestRpcClient {
pub fn new(url: Url) -> Self {
ReqwestRpcClient {
@@ -134,7 +131,6 @@ impl TendermintRpcErrorMap for reqwest::Error {
}
}
#[allow(deprecated)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl TendermintRpcClient for ReqwestRpcClient {
@@ -2,18 +2,18 @@
// SPDX-License-Identifier: Apache-2.0
use crate::signing::signer::{OfflineSigner, SigningError};
use crate::signing::{
derive_extended_private_key, derive_keypair, AccountData, Secp256k1Derivation, Secp256k1Keypair,
};
use bip32::XPrv;
use cosmrs::bip32::DerivationPath;
use crate::signing::{AccountData, Secp256k1Derivation};
use cosmrs::bip32::{DerivationPath, XPrv};
use cosmrs::crypto::secp256k1::SigningKey;
use cosmrs::crypto::PublicKey;
use cosmrs::tx;
use cosmrs::tx::SignDoc;
use nym_config::defaults;
use std::borrow::Cow;
use thiserror::Error;
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
type Secp256k1Keypair = (SigningKey, PublicKey);
#[derive(Debug, Error)]
pub enum DirectSecp256k1HdWalletError {
#[error(transparent)]
@@ -36,22 +36,28 @@ pub enum DirectSecp256k1HdWalletError {
}
// TODO: maybe lock this one behind feature flag?
#[derive(Zeroize, ZeroizeOnDrop)]
#[derive(Debug, Clone, Zeroize, ZeroizeOnDrop)]
pub struct DirectSecp256k1HdWallet {
/// Base secret
secret: bip39::Mnemonic,
/// Derived accounts
/// BIP39 seed
seed: [u8; 64],
// An unfortunate result of immature rust async story is that async traits (only available in the separate package)
// can't yet figure out everything and if we stored our derived account data on the struct,
// that would include the secret key which is a dyn EcdsaSigner and hence not Sync making the wallet
// not Sync and if used on the signing client in an async trait, it wouldn't be Send
/// Derivation instructions
#[zeroize(skip)]
// unfortunately `dyn EcdsaSigner` does not guarantee Zeroize
accounts: Vec<AccountData>,
accounts: Vec<Secp256k1Derivation>,
}
impl OfflineSigner for DirectSecp256k1HdWallet {
type Error = DirectSecp256k1HdWalletError;
fn get_accounts(&self) -> &[AccountData] {
&self.accounts
fn get_accounts(&self) -> Result<Vec<AccountData>, Self::Error> {
self.try_derive_accounts()
}
fn sign_direct_with_account(
@@ -71,27 +77,55 @@ impl DirectSecp256k1HdWallet {
}
/// Restores a wallet from the given BIP39 mnemonic using default options.
#[deprecated(
note = "this function can potentially panic if accounts can't be derived correctly. please use .checked_from_mnemonic() instead"
)]
pub fn from_mnemonic(prefix: &str, mnemonic: bip39::Mnemonic) -> Self {
// unfortunately due to backwards compatibility requirements,
// we can't change signature of this method
#[allow(deprecated)]
DirectSecp256k1HdWalletBuilder::new(prefix).build(mnemonic)
}
/// Restores a wallet from the given BIP39 mnemonic using default options.
pub fn checked_from_mnemonic(
prefix: &str,
mnemonic: bip39::Mnemonic,
) -> Result<Self, DirectSecp256k1HdWalletError> {
DirectSecp256k1HdWalletBuilder::new(prefix).try_build(mnemonic)
}
pub fn generate(prefix: &str, word_count: usize) -> Result<Self, DirectSecp256k1HdWalletError> {
let mneomonic = bip39::Mnemonic::generate(word_count)?;
Self::checked_from_mnemonic(prefix, mneomonic)
Ok(Self::from_mnemonic(prefix, mneomonic))
}
fn derive_keypair(
&self,
hd_path: &DerivationPath,
) -> Result<Secp256k1Keypair, DirectSecp256k1HdWalletError> {
let extended_private_key = XPrv::derive_from_path(self.seed, hd_path)?;
let private_key: SigningKey = extended_private_key.into();
let public_key = private_key.public_key();
Ok((private_key, public_key))
}
pub fn derive_extended_private_key(
&self,
hd_path: &DerivationPath,
) -> Result<XPrv, DirectSecp256k1HdWalletError> {
Ok(XPrv::derive_from_path(self.seed, hd_path)?)
}
pub fn try_derive_accounts(&self) -> Result<Vec<AccountData>, DirectSecp256k1HdWalletError> {
let mut accounts = Vec::with_capacity(self.accounts.len());
for derivation_info in &self.accounts {
let keypair = self.derive_keypair(&derivation_info.hd_path)?;
// it seems this can only fail if the provided account prefix is invalid
let address = keypair
.1
.account_id(&derivation_info.prefix)
.map_err(
|source| DirectSecp256k1HdWalletError::AccountDerivationError { source },
)?;
accounts.push(AccountData {
address,
public_key: keypair.1,
private_key: keypair.0,
})
}
Ok(accounts)
}
pub fn secret(&self) -> &bip39::Mnemonic {
@@ -108,43 +142,6 @@ impl DirectSecp256k1HdWallet {
pub fn mnemonic_string(&self) -> Zeroizing<String> {
Zeroizing::new(self.secret.to_string())
}
pub fn account_seed<'a, P: Into<Cow<'a, str>>>(
&self,
bip39_password: P,
) -> Zeroizing<[u8; 64]> {
Zeroizing::new(self.secret.to_seed(bip39_password))
}
/// Derive an extended private key from the stored account secret assuming no bip39 password
#[deprecated(
note = "use derive_extended_private_key_with_password to ensure correct derivation if used bip39 password"
)]
pub fn derive_extended_private_key(
&self,
hd_path: &DerivationPath,
) -> Result<XPrv, DirectSecp256k1HdWalletError> {
let seed = self.account_seed("");
derive_extended_private_key(seed, hd_path)
}
pub fn derive_keypair<'a, P: Into<Cow<'a, str>>>(
&self,
hd_path: &DerivationPath,
bip39_password: P,
) -> Result<Secp256k1Keypair, DirectSecp256k1HdWalletError> {
let seed = self.account_seed(bip39_password);
derive_keypair(seed, hd_path)
}
pub fn derive_extended_private_key_with_password<'a, P: Into<Cow<'a, str>>>(
&self,
hd_path: &DerivationPath,
bip39_password: P,
) -> Result<XPrv, DirectSecp256k1HdWalletError> {
let seed = self.account_seed(bip39_password);
derive_extended_private_key(seed, hd_path)
}
}
#[must_use]
@@ -191,39 +188,23 @@ impl DirectSecp256k1HdWalletBuilder {
self
}
#[deprecated(
note = "this function can potentially panic if accounts can't be derived correctly. please use .try_build() instead"
)]
pub fn build(self, mnemonic: bip39::Mnemonic) -> DirectSecp256k1HdWallet {
// unfortunately due to backwards compatibility requirements,
// we can't change signature of this method
#[allow(clippy::expect_used)]
self.try_build(mnemonic)
.expect("account derivation failure")
}
pub fn try_build(
self,
mnemonic: bip39::Mnemonic,
) -> Result<DirectSecp256k1HdWallet, DirectSecp256k1HdWalletError> {
let seed = Zeroizing::new(mnemonic.to_seed(&self.bip39_password));
let seed = mnemonic.to_seed(&self.bip39_password);
let prefix = self.prefix.clone();
let accounts = self
.hd_paths
.iter()
.map(|hd_path| {
Secp256k1Derivation {
hd_path: hd_path.clone(),
prefix: prefix.clone(),
}
.try_derive_account(&seed)
.map(|hd_path| Secp256k1Derivation {
hd_path: hd_path.clone(),
prefix: prefix.clone(),
})
.collect::<Result<_, _>>()?;
.collect();
Ok(DirectSecp256k1HdWallet {
DirectSecp256k1HdWallet {
accounts,
seed,
secret: mnemonic,
})
}
}
}
@@ -234,7 +215,7 @@ mod tests {
use super::*;
#[test]
fn generating_account_addresses() -> anyhow::Result<()> {
fn generating_account_addresses() {
// test vectors produced from our js wallet
let mnemonics = ["crush minute paddle tobacco message debate cabin peace bar jacket execute twenty winner view sure mask popular couch penalty fragile demise fresh pizza stove",
"acquire rebel spot skin gun such erupt pull swear must define ill chief turtle today flower chunk truth battle claw rigid detail gym feel",
@@ -249,10 +230,11 @@ mod tests {
"n17n9flp6jflljg6fp05dsy07wcprf2uuu8g40rf",
];
for (idx, mnemonic) in mnemonics.iter().enumerate() {
let wallet =
DirectSecp256k1HdWallet::checked_from_mnemonic(&prefix, mnemonic.parse()?)?;
assert_eq!(wallet.signer_addresses()[0], addrs[idx].parse().unwrap());
let wallet = DirectSecp256k1HdWallet::from_mnemonic(&prefix, mnemonic.parse().unwrap());
assert_eq!(
wallet.try_derive_accounts().unwrap()[0].address,
addrs[idx].parse().unwrap()
)
}
Ok(())
}
}
@@ -1,8 +1,6 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::signing::direct_wallet::DirectSecp256k1HdWalletError;
use bip32::XPrv;
use cosmrs::bip32::DerivationPath;
use cosmrs::crypto::secp256k1::SigningKey;
use cosmrs::crypto::PublicKey;
@@ -14,64 +12,14 @@ pub mod direct_wallet;
pub mod signer;
pub mod tx_signer;
pub(crate) type Secp256k1Keypair = (SigningKey, PublicKey);
/// Derivation information required to derive a keypair and an address from a mnemonic.
#[derive(Debug, Clone)]
pub(crate) struct Secp256k1Derivation {
struct Secp256k1Derivation {
hd_path: DerivationPath,
prefix: String,
}
impl Secp256k1Derivation {
pub(crate) fn try_derive_account<S>(
&self,
seed: S,
) -> Result<AccountData, DirectSecp256k1HdWalletError>
where
S: AsRef<[u8]>,
{
let keypair = derive_keypair(seed, &self.hd_path)?;
// it seems this can only fail if the provided account prefix is invalid
let address = keypair
.1
.account_id(&self.prefix)
.map_err(|source| DirectSecp256k1HdWalletError::AccountDerivationError { source })?;
Ok(AccountData {
address,
public_key: keypair.1,
private_key: keypair.0,
})
}
}
pub fn derive_keypair<S>(
seed: S,
hd_path: &DerivationPath,
) -> Result<Secp256k1Keypair, DirectSecp256k1HdWalletError>
where
S: AsRef<[u8]>,
{
let extended_private_key = derive_extended_private_key(seed, hd_path)?;
let private_key: SigningKey = extended_private_key.into();
let public_key = private_key.public_key();
Ok((private_key, public_key))
}
pub fn derive_extended_private_key<S>(
seed: S,
hd_path: &DerivationPath,
) -> Result<XPrv, DirectSecp256k1HdWalletError>
where
S: AsRef<[u8]>,
{
Ok(XPrv::derive_from_path(seed, hd_path)?)
}
// TODO: is this struct going to be derivable with other signer types?
pub struct AccountData {
pub address: AccountId,
@@ -33,18 +33,23 @@ pub enum SignerType {
pub trait OfflineSigner {
type Error: From<SigningError>;
fn signer_addresses(&self) -> Vec<AccountId> {
self.get_accounts()
.iter()
.map(|account| account.address.clone())
.collect()
// I really dislike existence of this function because it makes you re-derive your key **twice** for each contract transaction
fn signer_addresses(&self) -> Result<Vec<AccountId>, Self::Error> {
let derived_addresses = self
.get_accounts()?
.into_iter()
.map(|account| account.address)
.collect();
Ok(derived_addresses)
}
fn get_accounts(&self) -> &[AccountData];
fn get_accounts(&self) -> Result<Vec<AccountData>, Self::Error>;
fn find_account(&self, signer_address: &AccountId) -> Result<&AccountData, Self::Error> {
self.get_accounts()
.iter()
fn find_account(&self, signer_address: &AccountId) -> Result<AccountData, Self::Error> {
// TODO: we could really use some zeroize action here
let accounts = self.get_accounts()?;
accounts
.into_iter()
.find(|account| &account.address == signer_address)
.ok_or_else(|| {
SigningError::AccountNotFound {
@@ -71,7 +76,7 @@ pub trait OfflineSigner {
message: M,
) -> Result<Signature, Self::Error> {
let signer = self.find_account(signer_address)?;
self.sign_raw_with_account(signer, message)
self.sign_raw_with_account(&signer, message)
}
fn sign_direct(
@@ -80,7 +85,7 @@ pub trait OfflineSigner {
sign_doc: SignDoc,
) -> Result<tx::Raw, Self::Error> {
let signer = self.find_account(signer_address)?;
self.sign_direct_with_account(signer, sign_doc)
self.sign_direct_with_account(&signer, sign_doc)
}
// unless explicitly defined, each signing method is unsupported
@@ -117,7 +122,7 @@ pub struct NoSigner;
// impl OfflineSigner for NoSigner {
// type Error = SignerUnavailable;
//
// fn get_accounts(&self) -> &[AccountData] {
// fn get_accounts(&self) -> Result<Vec<AccountData>, Self::Error> {
// return Err(SignerUnavailable);
// }
// }
@@ -50,7 +50,7 @@ pub trait TxSigner: OfflineSigner {
)
.map_err(|source| SigningError::SignDocFailure { source })?;
self.sign_direct_with_account(account_from_signer, sign_doc)
self.sign_direct_with_account(&account_from_signer, sign_doc)
}
}

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