Compare commits

..

89 Commits

Author SHA1 Message Date
import this 340960d957 [DOCs/operators]: Release notes for v2025.19 kase (#6157)
* add release and operators notes

* bump up version

* fix location in csv to USA

* bump up stats

* typo fix
2025-10-31 09:02:13 +00:00
Mark Sinclair 554e446208 change migration and bump version 2025-10-31 09:02:13 +00:00
Mark Sinclair a6325b922a bump version to rc 2025-10-31 09:02:13 +00:00
Mark Sinclair fae4768b99 add tracing output 2025-10-31 09:02:13 +00:00
Mark Sinclair 2689a4dbd8 clippy 2025-10-31 09:02:13 +00:00
Mark Sinclair 02e40ccaef save custom_http_port to db 2025-10-31 09:02:13 +00:00
Mark Sinclair b216338364 allow NS API to run once for scraping for troubleshooting and debugging 2025-10-31 09:02:13 +00:00
Mark Sinclair 4a83bb9ba8 wip 2025-10-31 09:02:13 +00:00
Mark Sinclair 66ec3b037f ns-api: fix scraping bug when operator specifies custom node HTTP API port in bond 2025-10-31 09:02:13 +00:00
import this 168baa5071 [Feature/operators]: QUIC bridge deployment script v2 (#6145)
* new quick deployment script

* docs tweak

* update script to use .deb postinst

* final clean - ready to go

* correct nym-node config dir search with a fallback
2025-10-31 09:02:13 +00:00
Simon Wicky 7aef468839 remove unused deps (#6151) 2025-10-31 09:02:12 +00:00
Simon Wicky cb3ccd7f7e use typed builder (#6150) 2025-10-31 09:02:12 +00:00
Simon Wicky 16509dbace allow overwriting existing sdk shutdown manager 2025-10-31 09:02:12 +00:00
Simon Wicky b907ccbd5b typo 2025-10-31 09:02:12 +00:00
Simon Wicky 7b3194d7d2 calling for shutdown from the MixTrafficController 2025-10-31 09:02:12 +00:00
Jędrzej Stuczyński 0b81edfc66 using same hierarchy of trackers for client shutdown control 2025-10-31 09:02:12 +00:00
Tommy Verrall 8c6150e5ef Internal comments 2025-10-31 09:02:12 +00:00
Tommy Verrall 46164a389a Fix comments 2025-10-31 09:02:12 +00:00
Tommy Verrall 96d256b48f Better message to come in the PR description 2025-10-31 09:02:12 +00:00
Simon Wicky f98b93f60e tommy is too quick 2025-10-31 09:02:12 +00:00
Simon Wicky 0f6c356f39 configurable mixnet client startup timeout 2025-10-31 09:02:12 +00:00
p17o cf3f5d9a53 Update quic_bridge_deployment.sh for IPv4 and .deb package (#6138)
Updated ping commands to explicitly use IPv4 and adjusted file permission checks with sudo. Changed the forward address prompt to specify IPv4 and modified the binary download process to fetch and install the latest .deb release URL automatically.
2025-10-31 09:02:12 +00:00
Jędrzej Stuczyński 2f47e6349a bugfix: update internal owner address in transferred share (#6139) 2025-10-31 09:02:12 +00:00
Tommy Verrall 4b2b5390d3 Last failing test - fix 2025-10-31 09:02:12 +00:00
Tommy Verrall 734ff63f6d Use explicit Vec<ApiUrl> handling in BaseClientBuilder
- Replace NymNetworkDetails with explicit API URL handling
- Fix deprecated from_network() usage and improve fallback logic
- Add URL validation and remove unused backwards compatibility
2025-10-31 09:02:11 +00:00
Tommy Verrall 910c24e7da Actually commit the recommended changes 2025-10-31 09:02:11 +00:00
Tommy Verrall c96b73172a Fix broken tests in CI 2025-10-31 09:02:11 +00:00
Tommy Verrall 823c2c7262 Replace deprecated from_network() with new_with_fronted_urls() 2025-10-31 09:02:11 +00:00
Jędrzej Stuczyński 03d1b72a9e feat: expose more explicit new_with_fronted_urls builder for http API client (#6136) 2025-10-31 09:02:10 +00:00
Jędrzej Stuczyński 066669440f bugfix: update stored epoch share when changing ownership (#6135) 2025-10-31 09:01:27 +00:00
Jędrzej Stuczyński 9fb3443fd1 bugfix: update stored epoch share when changing announce address (#6131)
* bugfix: update stored epoch share when changing announce address

* chore: remove placeholder legacy mixnode bonding test [mixnet contract]
2025-10-31 09:01:27 +00:00
Tommy Verrall cd89a590b5 Fix new_from_env() to populate nym_api_urls for domain fronting 2025-10-31 09:01:27 +00:00
Tommy Verrall 06cb8bd969 fix all clippy messages 2025-10-31 09:01:27 +00:00
Tommy Verrall c72fef169c Add more tests for retry logic 2025-10-31 09:01:27 +00:00
Tommy Verrall 3c27eb41b8 Fix confusing tracing logs 2025-10-31 09:01:27 +00:00
Tommy Verrall 2428374fe7 Fix retries - this is working 2025-10-31 09:01:27 +00:00
Tommy Verrall 5a491f0a7e Add configuration-based domain fronting support
Changes:
- Add network_details field to BaseClientBuilder (optional, backwards compatible)
- Add with_network_details() method for opt-in domain fronting
- Update construct_nym_api_client() to use from_network() when network_details provided
- Enable network-defaults feature in nym-client-core Cargo.toml
- SDK passes network_details to BaseClientBuilder
2025-10-31 09:01:27 +00:00
Tommy Verrall 530f9ccb6f Fix CI issues 2025-10-31 09:01:26 +00:00
Tommy Verrall 2228a81d43 Allow clippy::enum_variant_names for BuilderConfigError 2025-10-31 09:01:26 +00:00
Tommy Verrall c8a1b53071 Improve error handling
Changes:
- Replace String error with BuilderConfigError enum in BuilderConfigBuilder
- Update tests to use pattern matching instead of string assertions
2025-10-31 09:01:26 +00:00
Tommy Verrall b896aaaed1 - Add DEFAULT_NYM_API_RETRIES constant (replaces magic number 3)
- Run cargo fmt on all affected packages
- All clippy warnings resolved
2025-10-31 09:01:26 +00:00
Tommy Verrall 828ffc6710 not sure what happened but it's fixed 2025-10-31 09:01:26 +00:00
Andy Duplain 05cdb27029 VPN-4262: Update Url to return url and front fields.
The VPN client is using the `Url` type alot now and in order to avoid
double URL-parsing we would like the content of the `Url` type exposed.
2025-10-31 09:01:26 +00:00
Tommy Verrall 3f29d3eba5 Add accessor methods for Url internals
Add inner_url() and fronts() accessor methods to nym_http_api_client::Url
for VPN client integration
2025-10-31 09:01:22 +00:00
Tommy Verrall aa2c41dc41 Merge resolution 2025-10-31 09:01:02 +00:00
Tommy Verrall 048de771ab Remove tests for removed with_nym_api_client method
These tests were referencing with_nym_api_client() which was removed when
cleaning domain fronting code from this branch
2025-10-31 09:01:02 +00:00
Tommy Verrall e1b06f02f3 Add optional builder pattern for BuilderConfig (non-breaking)
Addresses @jstuczyn's feedback about too many arguments by adding
BuilderConfigBuilder as an alternative to the existing new() method.
2025-10-31 09:01:02 +00:00
Tommy Verrall b5b8b8f224 fix conversion type && make the retry count configurable 2025-10-31 09:01:02 +00:00
Tommy Verrall 2d7141dfb1 Revert node filtering changes per Andrew's feedback
Andrew clarified that get_basic_entry_assigned_nodes_v2() already filters by
supported_roles.entry
2025-10-31 09:01:02 +00:00
Tommy Verrall a07522258f Remove domain fronting code to keep gateway changes only
This branch now contains only gateway registration improvements:
- Multiple URL fallback support in gateways_for_init()
- Get all entry-capable nodes for registration
- Performance and code quality improvements
2025-10-31 09:00:17 +00:00
Tommy Verrall 547a441002 Address PR feedback: simplify code and reduce log noise
- Reverted all changes to topology_control/nym_api_provider.rs
- Changed info/warn logs to debug for custom client messages
- Removed unused _rng parameter from gateways_for_init()
- Simplified URL builder to always use new_with_urls()
2025-10-31 09:00:17 +00:00
Tommy Verrall 93208fb5e0 Fix clippy warnings: use arrays instead of vec! in tests 2025-10-31 09:00:17 +00:00
Bogdan-Ștefan Neacşu c9b50dd979 Introduce event backchannel (#6119)
* Introduce even backchannel

* Rust fmt

* Rename Event to MixnetClientEvent

* Use unbounded_send for events

* Remove unused file

* Remove mut borrow

* Event hierarchy and mixnet client intermediary

* Export MixTrafficEvent in sdk
2025-10-31 09:00:15 +00:00
Jędrzej Stuczyński 74cdfd5d94 Merge pull request #6099 from nymtech/bugfix/incompatibility-fixes
Bugfix/incompatibility fixes
2025-10-31 08:59:40 +00:00
Jędrzej Stuczyński 953e813f0e Bugfix/bloomfilters purge (#6089)
* remove all old bloomfilters upon starting binary

* remove old bloomfilter file upon purging secondary data
2025-10-31 08:59:38 +00:00
Tommy Verrall 29cf5058a6 feat: pass custom HTTP client through SDK stack for domain fronting
- Add with_nym_api_client() to BaseClientBuilder, MixnetClientBuilder, and RegistrationClientBuilderConfig

- Modify nym_api_provider to fetch all nodes then filter by supported_roles.entry (fixes metadata inconsistency)

- Update helpers.rs to build HTTP client with all nym_apis URLs and retries for fallback support

- Fix SDK to use entry_capable_nodes() instead of entry_gateways() for broader gateway selection

This enables domain fronting and URL rotation throughout the entire SDK stack, improving censorship resistance and connection reliability. All changes are backward compatible - custom client is optional.
2025-10-31 08:57:08 +00:00
Tommy Verrall a2856552d8 enable URL rotation and retries for mixnet gateway init 2025-10-31 08:57:08 +00:00
Andrej Mihajlov a33c603471 Update dirs to 6.0 2025-10-31 08:57:08 +00:00
Jędrzej Stuczyński a9f9266992 bugfix: nym-credential-proxy query params parsing regression (#6121) 2025-10-31 08:57:08 +00:00
Tommy Verrall cf34d0d24a Skip ipv6 metadata endpoint request (#6118)
Co-authored-by: Tommy Verrall <tommy@nymtech.net>
2025-10-31 08:57:07 +00:00
Jędrzej Stuczyński 5fa7b0a709 bugfix: revert some dep updates introduced in #6043 (#6120) 2025-10-31 08:57:07 +00:00
Andrej Mihajlov e232b4fd24 Revert "Propagate cancel token to mixnet client"
This reverts commit 50a259d454.
2025-10-31 08:57:07 +00:00
Jędrzej Stuczyński 609f174e8d chore: restore pending dkg contract state migration (#6116)
since it has not yet been run on mainnet
2025-10-31 08:57:07 +00:00
benedetta davico a0f4627647 Update lib.go 2025-10-31 08:57:07 +00:00
Jędrzej Stuczyński 258f8f5f5d bugfix: retrieve and update ticketbook in the same query (#6101)
* bugfix: retrieve and update ticketbook in the same query

* bump up NS version

* Update Cargo.toml

* remove SKIP LOCKED part of the query

---------

Co-authored-by: benedetta davico <46782255+benedettadavico@users.noreply.github.com>
2025-10-31 08:57:07 +00:00
mfahampshire 38220e05f1 DOCS Jarlsberg Release (#6111)
* First pass release notes

* build info
2025-10-31 08:57:07 +00:00
Andrej Mihajlov 6250ebe235 Propagate cancel token to mixnet client 2025-10-31 08:57:07 +00:00
mfahampshire a55323c0e2 Patch for operators to open wg metadata port (#6106) 2025-10-31 08:57:07 +00:00
Jędrzej Stuczyński baa8ac3610 bugfix: use custom topology provider for list of init gateways (#6092) 2025-10-31 08:57:07 +00:00
Jędrzej Stuczyński 933da11e8f bugfix: include network name in the default gateway probe config path (#6100) 2025-10-31 08:57:07 +00:00
Jędrzej Stuczyński 0469036da4 feat: expose obtaining reference to Mnemonic from DirectSecp256k1HdWallet (#6083)
* feat: expose obtaining reference to Mnemonic from DirectSecp256k1HdWallet

* updated getters for stringified mnemonic
2025-10-31 08:57:07 +00:00
Georgio Nicolas 63f9a856fa Another offering for Clippy 2025-10-31 08:57:07 +00:00
Georgio Nicolas c068948c62 Offerings for clippy 2025-10-31 08:57:06 +00:00
Georgio Nicolas 0105f9fa5e Precompute BSGS table 2025-10-31 08:57:06 +00:00
Georgio Nicolas 004c737965 Use LazyLock to precompute generators 2025-10-31 08:57:06 +00:00
Georgio Nicolas 549121ca32 Fix clippy suggestion 2025-10-31 08:57:06 +00:00
Georgio Nicolas 3f2278dafc Fix zeroization 2025-10-31 08:57:06 +00:00
Georgio Nicolas 25ce0ac814 replace unsafe static values by function calls 2025-10-31 08:57:06 +00:00
Mark Sinclair 38d313a101 ns-api: add descriptions to dVPN gateway responses (#6102)
* ns-api: add descriptions to dVPN gateway responses

* clippy

* fmt

---------

Co-authored-by: Mark Sinclair <mmsinclair@users.noreply.github.com>
2025-10-31 08:57:06 +00:00
import this f67bc0ead5 [DOCs/operators] QUIC deployment script & docs (#6098)
* add quic_bridge_deployment.sh

* create a snippet with quick install steps

* add quic deployment to changelog

* add quic deployment to node config page

* add version compatibility callout

* last edits and scraped stats update

* correct name of QUIC snippet

* fix naming

* fix naming

* re-run python-prebuild.sh aka time-now updated

* attempt to fix vercel build the hard way

* rerun npm

* build with pnpm

* restore lock file and rebuild w pnpm

* chore: update pnpm lockfile

* attempt to fix build

* attempt to fix runtime builds

* update ci-docs run OS
2025-10-31 08:57:06 +00:00
Mark Sinclair 90aaa3572d Update ci-docs.yml 2025-10-31 08:57:06 +00:00
Mark Sinclair ecc61e4a4a NS API: use new probe download filesize and milliseconds field (#6097)
* use milliseconds field

* change score thresholds

* bump to version 4.0.8

* NS API: adjust score categories (#6103)

* testing scores

* test version

* Update Cargo.toml

---------

Co-authored-by: Mark Sinclair <mmsinclair@users.noreply.github.com>
Co-authored-by: benedetta davico <46782255+benedettadavico@users.noreply.github.com>
2025-10-31 08:57:06 +00:00
Jędrzej Stuczyński 22ac4919e5 bugfix: testnet manager 02sql migration (#6096) 2025-10-31 08:57:06 +00:00
Jędrzej Stuczyński a1e7cc8e87 chore: remove unnecessary closure in 'calculate_score' inside node-status-api 2025-10-31 08:57:06 +00:00
Mark Sinclair 57df00637c ns-api: use download files size from probes instead of parsing filenames 2025-10-31 08:57:06 +00:00
Jędrzej Stuczyński c7eb3bdb7b moved nym-gateway-probe to monorepo and updated rust-edition to 2024 (#6094)
dont build netstack in CI

additional rust 2024 fixes

fixes

removed temp.rs

first round of cleanup

removed duplicated NS types

moved gateway probe to the monorepo
2025-10-31 08:57:05 +00:00
Mark Sinclair 8f9b704541 ns-api: add new fields for probe output for query_metadata and download file size and duration in ms (#6091)
Co-authored-by: Mark Sinclair <mmsinclair@users.noreply.github.com>
2025-10-31 08:56:49 +00:00
Mark Sinclair 6a956c790a NS API: clamp load to offline when score is offline and add mixnet_score field to preformance_v2 (#6076)
* ns-api: when `score` is `Offline`, clamp `load` to `Offline`

* ns-api: bump version

* ns-api: add mixnet score field to performance_v2 struct

---------

Co-authored-by: Mark Sinclair <mmsinclair@users.noreply.github.com>
2025-10-31 08:56:49 +00:00
mfahampshire 2ff5c7221a Max/fix wasm client + build commands (#6043)
* Debug logging 

* Yield based logging

* Reintroduce non-dummy task manager, try add counting for
BatchMessageSender, a couple of compiler target introductions on use
statements.

* Fixed time runtime err

* Uncomment forgetme/rememberme

* remove diffs from debug

* missed commented out forgetme

* yet more forgetme comments

* * Added missing clientreqestsender clone to wasm client to stop
  premature drop & busyloop
* Removed hacky mem::forget fix

* Remove debug panic_hook

* Conditional import + use of wasm_utils::console_log

* add wasm_util dep

* Commenting out or removing debug logging

* Remove missed comment

* cleanup gitignore

* clippy

* update go version in ci

* removed unused deps

* add clippy ignore

* remove mixfetch from ci build

* add minifetch fix

* comment out unused ts builds

* stop contract clients killing ci for the moment

* wasm target locking for imports

* Either remove console_log! macro or introduce cfg(debug_assertions)

* downgrade netlink

* debug assertions for console_log import

* modify config logging (debug -> normal)

* remove clone for client_request_sender + grab directly in struct
  creation

* reintroduce debug print for config in debug mode

* remove ood / unused custom topology from worker example file

* clippy

* clippy - ignore todo() tests

* modified humantime test in line with new parsing rules
2025-10-31 08:56:49 +00:00
202 changed files with 2422 additions and 8263 deletions
+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
-40
View File
@@ -4,46 +4,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [Unreleased]
## [2025.19-kase] (2025-10-30)
- update ns agent workflow ([#6154])
- Cherry pick - request #6143 from nymtech/bugfix/mix-tx-closed-v2 ([#6153])
- bugfix: nym-credential-proxy query params parsing regression ([#6121])
- bugfix: revert some dep updates introduced in #6043 ([#6120])
- Skip ipv6 metadata endpoint request ([#6118])
- update to no longer use 1mb files ([#6117])
- chore: restore pending dkg contract state migration ([#6116])
- Revert "Propagate cancel token to mixnet client" ([#6115])
- Update dirs to 6.0 ([#6109])
- Propagate cancel token to mixnet client ([#6105])
- bugfix: retrieve and update ticketbook in the same query ([#6101])
- bugfix: include network name in the default gateway probe config path ([#6100])
- Bugfix/incompatibility fixes ([#6099])
- [DOCs/operators] QUIC deployment script & docs ([#6098])
- bugfix: testnet manager 02sql migration ([#6096])
- feat: move gateway probe to monorepo (and update to rust edition 2024) ([#6094])
- bugfix: use custom topology provider for list of init gateways ([#6092])
- Max/fix wasm client + build commands ([#6043])
[#6154]: https://github.com/nymtech/nym/pull/6154
[#6153]: https://github.com/nymtech/nym/pull/6153
[#6121]: https://github.com/nymtech/nym/pull/6121
[#6120]: https://github.com/nymtech/nym/pull/6120
[#6118]: https://github.com/nymtech/nym/pull/6118
[#6117]: https://github.com/nymtech/nym/pull/6117
[#6116]: https://github.com/nymtech/nym/pull/6116
[#6115]: https://github.com/nymtech/nym/pull/6115
[#6109]: https://github.com/nymtech/nym/pull/6109
[#6105]: https://github.com/nymtech/nym/pull/6105
[#6101]: https://github.com/nymtech/nym/pull/6101
[#6100]: https://github.com/nymtech/nym/pull/6100
[#6099]: https://github.com/nymtech/nym/pull/6099
[#6098]: https://github.com/nymtech/nym/pull/6098
[#6096]: https://github.com/nymtech/nym/pull/6096
[#6094]: https://github.com/nymtech/nym/pull/6094
[#6092]: https://github.com/nymtech/nym/pull/6092
[#6043]: https://github.com/nymtech/nym/pull/6043
## [2025.18-jarlsberg] (2025-10-14)
- ns-api: add descriptions to dVPN gateway responses ([#6102])
Generated
+31 -26
View File
@@ -4824,7 +4824,7 @@ dependencies = [
[[package]]
name = "nym-api"
version = "1.1.68"
version = "1.1.67"
dependencies = [
"anyhow",
"async-trait",
@@ -4987,7 +4987,6 @@ dependencies = [
"nym-network-defaults",
"nym-service-provider-requests-common",
"nym-sphinx",
"nym-test-utils",
"nym-wireguard-types",
"rand 0.8.5",
"semver 1.0.26",
@@ -4995,7 +4994,6 @@ dependencies = [
"sha2 0.10.9",
"strum_macros",
"thiserror 2.0.12",
"tracing",
"x25519-dalek",
]
@@ -5004,16 +5002,21 @@ name = "nym-bandwidth-controller"
version = "0.1.0"
dependencies = [
"async-trait",
"bip39",
"log",
"nym-credential-storage",
"nym-credentials",
"nym-credentials-interface",
"nym-crypto",
"nym-ecash-contract-common",
"nym-ecash-time",
"nym-network-defaults",
"nym-task",
"nym-validator-client",
"rand 0.8.5",
"thiserror 2.0.12",
"url",
"zeroize",
]
[[package]]
@@ -5047,7 +5050,7 @@ dependencies = [
[[package]]
name = "nym-cli"
version = "1.1.65"
version = "1.1.64"
dependencies = [
"anyhow",
"base64 0.22.1",
@@ -5130,7 +5133,7 @@ dependencies = [
[[package]]
name = "nym-client"
version = "1.1.65"
version = "1.1.64"
dependencies = [
"bs58",
"clap",
@@ -5459,11 +5462,8 @@ dependencies = [
"nym-crypto",
"nym-ecash-contract-common",
"nym-ecash-signer-check",
"nym-http-api-client",
"nym-http-api-common",
"nym-network-defaults",
"nym-pemstore",
"nym-upgrade-mode-check",
"nym-validator-client",
"rand 0.8.5",
"reqwest 0.12.22",
@@ -5534,7 +5534,6 @@ dependencies = [
"nym-http-api-client",
"nym-http-api-common",
"nym-serde-helpers",
"nym-upgrade-mode-check",
"reqwest 0.12.22",
"schemars 0.8.22",
"serde",
@@ -5605,8 +5604,8 @@ dependencies = [
"nym-gateway-requests",
"nym-gateway-storage",
"nym-task",
"nym-upgrade-mode-check",
"nym-validator-client",
"rand 0.8.5",
"si-scale",
"thiserror 2.0.12",
"time",
@@ -5646,7 +5645,6 @@ dependencies = [
"nym-compact-ecash",
"nym-ecash-time",
"nym-network-defaults",
"nym-upgrade-mode-check",
"rand 0.8.5",
"serde",
"strum",
@@ -5663,7 +5661,6 @@ dependencies = [
"aead",
"aes",
"aes-gcm-siv",
"anyhow",
"base64 0.22.1",
"blake3",
"bs58",
@@ -5677,12 +5674,10 @@ dependencies = [
"jwt-simple",
"nym-pemstore",
"nym-sphinx-types",
"nym-test-utils",
"rand 0.8.5",
"rand_chacha 0.3.1",
"serde",
"serde_bytes",
"serde_json",
"sha2 0.10.9",
"subtle-encoding",
"thiserror 2.0.12",
@@ -5797,6 +5792,7 @@ dependencies = [
name = "nym-gateway"
version = "1.1.36"
dependencies = [
"anyhow",
"async-trait",
"bincode",
"bip39",
@@ -5807,6 +5803,7 @@ dependencies = [
"futures",
"ipnetwork",
"mock_instant",
"nym-api-requests",
"nym-authenticator-requests",
"nym-client-core",
"nym-credential-verification",
@@ -5819,6 +5816,7 @@ dependencies = [
"nym-id",
"nym-ip-packet-router",
"nym-mixnet-client",
"nym-mixnode-common",
"nym-network-defaults",
"nym-network-requester",
"nym-node-metrics",
@@ -5828,18 +5826,20 @@ dependencies = [
"nym-statistics-common",
"nym-task",
"nym-topology",
"nym-upgrade-mode-check",
"nym-types",
"nym-validator-client",
"nym-wireguard",
"nym-wireguard-private-metadata-server",
"nym-wireguard-types",
"rand 0.8.5",
"serde",
"sha2 0.10.9",
"thiserror 2.0.12",
"time",
"tokio",
"tokio-stream",
"tokio-tungstenite",
"tokio-util",
"tracing",
"url",
"zeroize",
@@ -6354,7 +6354,7 @@ dependencies = [
[[package]]
name = "nym-network-requester"
version = "1.1.66"
version = "1.1.65"
dependencies = [
"addr",
"anyhow",
@@ -6404,7 +6404,7 @@ dependencies = [
[[package]]
name = "nym-node"
version = "1.20.0"
version = "1.19.0"
dependencies = [
"anyhow",
"arc-swap",
@@ -6931,7 +6931,7 @@ dependencies = [
[[package]]
name = "nym-socks5-client"
version = "1.1.65"
version = "1.1.64"
dependencies = [
"bs58",
"clap",
@@ -7378,14 +7378,12 @@ dependencies = [
"jwt-simple",
"nym-crypto",
"nym-http-api-client",
"nym-test-utils",
"reqwest 0.12.22",
"serde",
"serde_json",
"thiserror 2.0.12",
"time",
"tracing",
"utoipa",
]
[[package]]
@@ -7565,10 +7563,17 @@ dependencies = [
name = "nym-wireguard"
version = "0.1.0"
dependencies = [
"async-trait",
"base64 0.22.1",
"bincode",
"chrono",
"dashmap",
"defguard_wireguard_rs",
"dyn-clone",
"futures",
"ip_network",
"log",
"nym-authenticator-requests",
"nym-credential-verification",
"nym-credentials-interface",
"nym-crypto",
@@ -7579,9 +7584,11 @@ dependencies = [
"nym-task",
"nym-wireguard-types",
"thiserror 2.0.12",
"time",
"tokio",
"tokio-stream",
"tracing",
"x25519-dalek",
]
[[package]]
@@ -7633,20 +7640,15 @@ version = "1.0.0"
dependencies = [
"async-trait",
"axum",
"futures",
"nym-credential-verification",
"nym-credentials-interface",
"nym-crypto",
"nym-http-api-client",
"nym-http-api-common",
"nym-upgrade-mode-check",
"nym-wireguard",
"nym-wireguard-private-metadata-client",
"nym-wireguard-private-metadata-server",
"nym-wireguard-private-metadata-shared",
"time",
"tokio",
"tower 0.5.2",
"tower-http 0.5.2",
"utoipa",
]
@@ -7656,7 +7658,10 @@ name = "nym-wireguard-types"
version = "0.1.0"
dependencies = [
"base64 0.22.1",
"log",
"nym-config",
"nym-crypto",
"nym-network-defaults",
"rand 0.8.5",
"serde",
"thiserror 2.0.12",
@@ -7665,7 +7670,7 @@ dependencies = [
[[package]]
name = "nymvisor"
version = "0.1.30"
version = "0.1.29"
dependencies = [
"anyhow",
"bytes",
-6
View File
@@ -171,7 +171,6 @@ members = [
default-members = [
"clients/native",
"clients/socks5",
"nym-authenticator-client",
"nym-api",
"nym-credential-proxy/nym-credential-proxy",
"nym-node",
@@ -454,11 +453,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"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.65"
version = "1.1.64"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.65"
version = "1.1.64"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
edition = "2021"
-7
View File
@@ -16,7 +16,6 @@ serde = { workspace = true, features = ["derive"] }
semver = { workspace = true }
strum_macros = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }
nym-credentials-interface = { path = "../credentials-interface" }
nym-crypto = { path = "../crypto", features = ["asymmetric"] }
@@ -30,13 +29,7 @@ hmac = { workspace = true, optional = true }
sha2 = { workspace = true, optional = true }
x25519-dalek = { workspace = true, features = ["static_secrets"] }
[dev-dependencies]
nym-test-utils = { path = "../test-utils" }
[features]
default = ["verify"]
# this is moved to a separate feature as we really need clients to import it (especially, *cough*, wasm)
verify = ["hmac", "sha2"]
[lints]
workspace = true
@@ -6,8 +6,9 @@ use nym_wireguard_types::PeerPublicKey;
use crate::{
AuthenticatorVersion, Error,
latest::registration::IpPair,
traits::{FinalMessage, InitMessage, QueryBandwidthMessage, TopUpMessage, Versionable},
v2, v3, v4, v5, v6,
v2, v3, v4, v5,
};
// This is very redundant with AuthenticatorRequest and I reckon they could be smooshed.
@@ -20,272 +21,6 @@ pub enum ClientMessage {
TopUp(Box<dyn TopUpMessage + 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))
}
}
}
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))
}
}
}
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))
}
}
}
fn serialise_v6(&self) -> Result<SerialisedRequest, Error> {
use v6::{
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(),
});
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))
}
}
}
}
impl ClientMessage {
// check if message is wasteful e.g. contains a credential
pub fn is_wasteful(&self) -> bool {
@@ -305,14 +40,205 @@ impl ClientMessage {
}
}
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),
}
}
@@ -321,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 -2
View File
@@ -10,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 -36
View File
@@ -5,7 +5,7 @@ use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
use nym_sphinx::addressing::Recipient;
use crate::traits::{FinalMessage, InitMessage, QueryBandwidthMessage, TopUpMessage};
use crate::{v1, v2, v3, v4, v5, v6};
use crate::{v1, v2, v3, v4, v5};
#[derive(Debug)]
pub enum AuthenticatorRequest {
@@ -202,38 +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,
}
}
}
}
}
+3 -41
View File
@@ -2,10 +2,10 @@
// SPDX-License-Identifier: Apache-2.0
use crate::traits::{
CurrentUpgradeModeStatus, Id, PendingRegistrationResponse, RegisteredResponse,
RemainingBandwidthResponse, TopUpBandwidthResponse, UpgradeModeStatus,
Id, PendingRegistrationResponse, RegisteredResponse, RemainingBandwidthResponse,
TopUpBandwidthResponse,
};
use crate::{v2, v3, v4, v5, v6};
use crate::{v2, v3, v4, v5};
#[derive(Debug)]
pub enum AuthenticatorResponse {
@@ -15,25 +15,6 @@ pub enum AuthenticatorResponse {
TopUpBandwidth(Box<dyn TopUpBandwidthResponse + 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()
}
}
}
}
impl Id for AuthenticatorResponse {
fn id(&self) -> u64 {
match self {
@@ -123,22 +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))
}
}
}
}
+31 -455
View File
@@ -4,66 +4,12 @@
use std::fmt;
use std::net::{Ipv4Addr, Ipv6Addr};
use crate::latest::registration::IpPair;
use crate::{AuthenticatorVersion, Error, v1, v2, v3, v4, v5, v6};
use nym_credentials_interface::{
BandwidthCredential, CredentialSpendingData, TicketType, UnknownTicketType,
};
use nym_crypto::asymmetric::x25519;
use nym_credentials_interface::CredentialSpendingData;
use nym_crypto::asymmetric::x25519::PrivateKey;
use nym_wireguard_types::PeerPublicKey;
use serde::{Deserialize, Serialize};
use tracing::error;
#[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),
})
}
}
use crate::latest::registration::IpPair;
use crate::{AuthenticatorVersion, Error, v1, v2, v3, v4, v5};
pub trait Versionable {
fn version(&self) -> AuthenticatorVersion;
@@ -105,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
@@ -135,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
@@ -164,146 +98,6 @@ impl Versionable for v5::topup::TopUpMessage {
AuthenticatorVersion::V5
}
}
impl Versionable for v6::topup::TopUpMessage {
fn version(&self) -> AuthenticatorVersion {
AuthenticatorVersion::V6
}
}
pub trait UpgradeModeStatus {
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()
}
}
pub trait InitMessage: Versionable + fmt::Debug {
fn pub_key(&self) -> PeerPublicKey;
@@ -339,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 {
@@ -360,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)
}
@@ -383,7 +171,7 @@ impl FinalMessage for v1::GatewayClient {
self.mac.to_vec()
}
fn credential(&self) -> Option<BandwidthClaim> {
fn credential(&self) -> Option<CredentialSpendingData> {
None
}
}
@@ -393,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)
}
@@ -416,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()
}
}
@@ -430,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)
}
@@ -453,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()
}
}
@@ -467,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)
}
@@ -522,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)
}
@@ -556,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()
}
}
@@ -606,16 +347,6 @@ 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 Id {
fn id(&self) -> u64;
}
@@ -644,12 +375,6 @@ impl Id for v5::response::PendingRegistrationResponse {
}
}
impl Id for v6::response::PendingRegistrationResponse {
fn id(&self) -> u64 {
self.request_id
}
}
impl Id for v2::response::RegisteredResponse {
fn id(&self) -> u64 {
self.request_id
@@ -674,12 +399,6 @@ impl Id for v5::response::RegisteredResponse {
}
}
impl Id for v6::response::RegisteredResponse {
fn id(&self) -> u64 {
self.request_id
}
}
impl Id for v2::response::RemainingBandwidthResponse {
fn id(&self) -> u64 {
self.request_id
@@ -704,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
@@ -728,22 +441,11 @@ impl Id for v5::response::TopUpBandwidthResponse {
}
}
impl Id for v6::response::TopUpBandwidthResponse {
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 {
@@ -751,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())
}
@@ -762,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 {
@@ -785,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())
}
@@ -796,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 {
@@ -819,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())
}
@@ -865,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())
}
@@ -899,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;
@@ -952,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 {
@@ -966,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
}
@@ -993,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>;
}
@@ -1021,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;
}
@@ -1048,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::traits::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,14 +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 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 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};
use crate::traits::BandwidthClaim;
#[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,121 +0,0 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::{
PROTOCOL,
registration::{FinalMessage, InitMessage},
topup::TopUpMessage,
};
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 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>),
}
#[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,135 +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 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),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum AuthenticatorResponseData {
PendingRegistration(PendingRegistrationResponse),
Registered(RegisteredResponse),
RemainingBandwidth(RemainingBandwidthResponse),
TopUpBandwidth(TopUpBandwidthResponse),
}
#[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,
}
@@ -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,
}
+5 -23
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,19 +22,12 @@ pub enum AuthenticatorVersion {
/// introduced in dorina-patched release (1.6.1)
V5,
/// introduced in yet to be named release, currently aiming for Leerdammer (1.22.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;
#[deprecated(
note = "the final version of V6 won't be known until appropriate release is scheduled. after that happens From<semver::Version> trait will have to be adjusted"
)]
pub const fn release_version(&self) -> semver::Version {
match self {
AuthenticatorVersion::V1 => semver::Version::new(1, 1, 5),
@@ -42,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, 22, 0),
AuthenticatorVersion::UNKNOWN => semver::Version::new(0, 0, 0),
}
}
@@ -62,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
}
@@ -82,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
}
@@ -138,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"
);
@@ -206,8 +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.20.0".into());
assert_eq!(AuthenticatorVersion::V6, "1.21.0".into());
assert_eq!(AuthenticatorVersion::V6, "1.22.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 }
+1 -11
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,16 +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
}
}
impl<C, St> Clone for BandwidthController<C, St>
-20
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,19 +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.content).map_err(|err| {
BandwidthControllerError::CredentialStorageError(Box::new(format!(
"malformed upgrade mode token: {err}"
)))
})?;
Ok(Some(token))
}
}
+1 -1
View File
@@ -441,7 +441,7 @@ mod tests {
#[test]
fn test_multiple_urls_prepared_for_retries() {
let urls = [
let urls = vec![
Url::parse("https://api1.nym.com").unwrap(),
Url::parse("https://api2.nym.com").unwrap(),
Url::parse("https://api3.nym.com").unwrap(),
@@ -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()
}
@@ -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()?;
@@ -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
@@ -10,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;
@@ -156,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 } => {
@@ -177,7 +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");
self.client_bandwidth.update_and_log(available, None);
self.client_bandwidth.update_and_log(available);
// UNIMPLEMENTED: we should stop sending messages until we recover bandwidth
Ok(())
}
@@ -330,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;
@@ -359,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())
}
}
@@ -393,13 +387,4 @@ impl SocketState {
SocketState::Available(_) | SocketState::PartiallyDelegated(_)
)
}
pub(crate) fn name(&self) -> &'static str {
match self {
SocketState::Available(_) => "available",
SocketState::PartiallyDelegated(_) => "partially delegated",
SocketState::NotConnected => "not connected",
SocketState::Invalid => "invalid",
}
}
}
@@ -241,28 +241,23 @@ impl Epoch {
//
// Note: It's important that the variant ordering is not changed otherwise it would mess up the derived `PartialOrd`
#[cw_serde]
#[derive(Copy, Default)]
#[derive(Copy)]
pub enum EpochState {
#[default]
WaitingInitialisation,
PublicKeySubmission {
resharing: bool,
},
DealingExchange {
resharing: bool,
},
VerificationKeySubmission {
resharing: bool,
},
VerificationKeyValidation {
resharing: bool,
},
VerificationKeyFinalization {
resharing: bool,
},
PublicKeySubmission { resharing: bool },
DealingExchange { resharing: bool },
VerificationKeySubmission { resharing: bool },
VerificationKeyValidation { resharing: bool },
VerificationKeyFinalization { resharing: bool },
InProgress,
}
impl Default for EpochState {
fn default() -> Self {
Self::WaitingInitialisation
}
}
impl Display for EpochState {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
+1 -1
View File
@@ -8,7 +8,7 @@ async fn main() -> anyhow::Result<()> {
use sqlx::{Connection, SqliteConnection};
use std::env;
let out_dir = env::var("OUT_DIR").context("missing OUT_DIR env variable")?;
let out_dir = env::var("OUT_DIR")?;
let database_path = format!("{out_dir}/nym-credential-proxy-example.sqlite");
// remove the db file if it already existed from previous build
-7
View File
@@ -127,13 +127,6 @@ pub enum CredentialProxyError {
#[error("failed to create deposit")]
DepositFailure,
#[error("failed to load jwt signing key from {path}: {err}")]
JWTSigningKeyLoadFailure {
path: String,
#[source]
err: std::io::Error,
},
#[error("can't obtain sufficient number of credential shares due to unavailable quorum")]
UnavailableSigningQuorum,
@@ -7,9 +7,9 @@ use crate::ticketbook_manager::TicketbookManager;
use nym_compact_ecash::Base58;
use nym_credential_proxy_requests::api::v1::ticketbook::models::{
CurrentEpochResponse, DepositResponse, GlobalDataParams, MasterVerificationKeyResponse,
ObtainTicketBookSharesAsyncResponse, PartialVerificationKey, PartialVerificationKeysResponse,
TicketbookAsyncRequest, TicketbookObtainParams, TicketbookRequest,
TicketbookWalletSharesAsyncResponse, TicketbookWalletSharesResponse,
PartialVerificationKey, PartialVerificationKeysResponse, TicketbookAsyncRequest,
TicketbookObtainParams, TicketbookRequest, TicketbookWalletSharesAsyncResponse,
TicketbookWalletSharesResponse,
};
use time::OffsetDateTime;
use tracing::{Instrument, Level, error, info, span, warn};
@@ -65,7 +65,7 @@ impl TicketbookManager {
uuid: Uuid,
request: TicketbookAsyncRequest,
params: TicketbookObtainParams,
) -> Result<ObtainTicketBookSharesAsyncResponse, CredentialProxyError> {
) -> Result<TicketbookWalletSharesAsyncResponse, CredentialProxyError> {
let requested_on = OffsetDateTime::now_utc();
let span = span!(Level::INFO, "[async] obtain ticketboook", uuid = %uuid);
async move {
@@ -110,7 +110,7 @@ impl TicketbookManager {
}
// 4. in the meantime, return the id to the user
Ok(TicketbookWalletSharesAsyncResponse { id, uuid }.into())
Ok(TicketbookWalletSharesAsyncResponse { id, uuid })
}
.instrument(span)
.await
@@ -1,14 +0,0 @@
/*
* Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
* SPDX-License-Identifier: Apache-2.0
*/
CREATE TABLE emergency_credential
(
type TEXT NOT NULL,
-- don't define any strict schema on the content as it might be implementation-dependant
content BLOB NOT NULL,
expiration TIMESTAMP WITHOUT TIME ZONE
);
@@ -1,10 +1,7 @@
// Copyright 2023-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::models::{
BasicTicketbookInformation, EmergencyCredential, RetrievedPendingTicketbook,
RetrievedTicketbook,
};
use crate::models::{BasicTicketbookInformation, RetrievedPendingTicketbook, RetrievedTicketbook};
use nym_compact_ecash::scheme::coin_indices_signatures::AnnotatedCoinIndexSignature;
use nym_compact_ecash::scheme::expiration_date_signatures::AnnotatedExpirationDateSignature;
use nym_compact_ecash::VerificationKeyAuth;
@@ -32,7 +29,6 @@ struct EcashCredentialManagerInner {
master_vk: HashMap<u64, VerificationKeyAuth>,
coin_indices_sigs: HashMap<u64, Vec<AnnotatedCoinIndexSignature>>,
expiration_date_sigs: HashMap<(u64, Date), Vec<AnnotatedExpirationDateSignature>>,
emergency_credentials: HashMap<String, Vec<EmergencyCredential>>,
_next_id: i64,
}
@@ -281,20 +277,4 @@ impl MemoryEcachTicketbookManager {
sigs.signatures.clone(),
);
}
pub(crate) async fn get_emergency_credential(&self, typ: &str) -> Option<EmergencyCredential> {
let guard = self.inner.read().await;
guard.emergency_credentials.get(typ)?.first().cloned()
}
pub(crate) async fn insert_emergency_credential(&self, credential: &EmergencyCredential) {
let mut guard = self.inner.write().await;
guard
.emergency_credentials
.entry(credential.typ.clone())
.or_default()
.push(credential.clone());
}
}
@@ -2,9 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
use crate::models::{
BasicTicketbookInformation, EmergencyCredential, RawCoinIndexSignatures,
RawExpirationDateSignatures, RawVerificationKey, StoredIssuedTicketbook,
StoredPendingTicketbook,
BasicTicketbookInformation, RawCoinIndexSignatures, RawExpirationDateSignatures,
RawVerificationKey, StoredIssuedTicketbook, StoredPendingTicketbook,
};
use nym_ecash_time::Date;
use sqlx::{Executor, Sqlite, Transaction};
@@ -306,44 +305,6 @@ impl SqliteEcashTicketbookManager {
.await?;
Ok(())
}
pub(crate) async fn get_emergency_credential(
&self,
typ: &str,
) -> Result<Option<EmergencyCredential>, sqlx::Error> {
sqlx::query_as(
r#"
SELECT *
FROM emergency_credential
WHERE type = ?
AND (expiration IS NULL OR expiration > CURRENT_TIMESTAMP)
ORDER BY expiration ASC NULLS LAST
LIMIT 1
"#,
)
.bind(typ)
.fetch_optional(&*self.connection_pool)
.await
}
pub(crate) async fn insert_emergency_credential(
&self,
credential: &EmergencyCredential,
) -> Result<(), sqlx::Error> {
sqlx::query!(
r#"
INSERT INTO emergency_credential
(type, content, expiration)
VALUES (?, ?, ?)
"#,
credential.typ,
credential.content,
credential.expiration,
)
.execute(&*self.connection_pool)
.await?;
Ok(())
}
}
pub(crate) async fn get_next_unspent_ticketbook<'a, E>(
@@ -3,10 +3,7 @@
use crate::backends::memory::MemoryEcachTicketbookManager;
use crate::error::StorageError;
use crate::models::{
BasicTicketbookInformation, EmergencyCredential, RetrievedPendingTicketbook,
RetrievedTicketbook,
};
use crate::models::{BasicTicketbookInformation, RetrievedPendingTicketbook, RetrievedTicketbook};
use crate::storage::Storage;
use async_trait::async_trait;
use nym_compact_ecash::scheme::coin_indices_signatures::AnnotatedCoinIndexSignature;
@@ -221,23 +218,6 @@ impl Storage for EphemeralStorage {
.await;
Ok(())
}
async fn get_emergency_credential(
&self,
typ: &str,
) -> Result<Option<EmergencyCredential>, Self::StorageError> {
Ok(self.storage_manager.get_emergency_credential(typ).await)
}
async fn insert_emergency_credential(
&self,
credential: &EmergencyCredential,
) -> Result<(), Self::StorageError> {
self.storage_manager
.insert_emergency_credential(credential)
.await;
Ok(())
}
}
#[cfg(test)]
@@ -266,7 +246,7 @@ mod tests {
let _exp_date_sigs = generate_expiration_date_signatures(
sig_req.expiration_date.ecash_unix_timestamp(),
&[signing_keys.secret_key()],
&[signing_keys.verification_key()],
&vec![signing_keys.verification_key()],
&signing_keys.verification_key(),
&[1],
)?;
@@ -283,7 +263,7 @@ mod tests {
let wallet = issuance.aggregate_signature_shares(
&signing_keys.verification_key(),
&[partial_wallet],
&vec![partial_wallet],
sig_req,
)?;
-10
View File
@@ -3,7 +3,6 @@
use nym_credentials::{IssuanceTicketBook, IssuedTicketBook};
use nym_ecash_time::Date;
use sqlx::types::time::OffsetDateTime;
use zeroize::{Zeroize, ZeroizeOnDrop};
pub struct RetrievedTicketbook {
@@ -79,12 +78,3 @@ pub struct RawVerificationKey {
pub serialised_key: Vec<u8>,
pub serialization_revision: u8,
}
#[derive(Clone, Debug)]
#[cfg_attr(not(target_arch = "wasm32"), derive(sqlx::FromRow))]
pub struct EmergencyCredential {
#[cfg_attr(not(target_arch = "wasm32"), sqlx(rename = "type"))]
pub typ: String,
pub content: Vec<u8>,
pub expiration: Option<OffsetDateTime>,
}
@@ -3,7 +3,6 @@
mod legacy_helpers;
use crate::models::EmergencyCredential;
use crate::{
backends::sqlite::{
get_next_unspent_ticketbook, increase_used_ticketbook_tickets, SqliteEcashTicketbookManager,
@@ -402,21 +401,4 @@ impl Storage for PersistentStorage {
.await?;
Ok(())
}
async fn get_emergency_credential(
&self,
typ: &str,
) -> Result<Option<EmergencyCredential>, Self::StorageError> {
Ok(self.storage_manager.get_emergency_credential(typ).await?)
}
async fn insert_emergency_credential(
&self,
credential: &EmergencyCredential,
) -> Result<(), Self::StorageError> {
self.storage_manager
.insert_emergency_credential(credential)
.await?;
Ok(())
}
}
+1 -14
View File
@@ -1,10 +1,7 @@
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::models::{
BasicTicketbookInformation, EmergencyCredential, RetrievedPendingTicketbook,
RetrievedTicketbook,
};
use crate::models::{BasicTicketbookInformation, RetrievedPendingTicketbook, RetrievedTicketbook};
use async_trait::async_trait;
use nym_compact_ecash::VerificationKeyAuth;
use nym_credentials::ecash::bandwidth::serialiser::keys::EpochVerificationKey;
@@ -111,14 +108,4 @@ pub trait Storage: Clone + Send + Sync {
&self,
signatures: &AggregatedExpirationDateSignatures,
) -> Result<(), Self::StorageError>;
async fn get_emergency_credential(
&self,
typ: &str,
) -> Result<Option<EmergencyCredential>, Self::StorageError>;
async fn insert_emergency_credential(
&self,
credential: &EmergencyCredential,
) -> Result<(), Self::StorageError>;
}
+1 -1
View File
@@ -17,6 +17,7 @@ cosmwasm-std = { workspace = true }
cw-utils = { workspace = true }
dyn-clone = { workspace = true }
futures = { workspace = true }
rand = { workspace = true }
si-scale = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
@@ -31,4 +32,3 @@ nym-gateway-requests = { path = "../gateway-requests" }
nym-gateway-storage = { path = "../gateway-storage" }
nym-task = { path = "../task" }
nym-validator-client = { path = "../client-libs/validator-client" }
nym-upgrade-mode-check = { path = "../upgrade-mode-check" }
@@ -6,6 +6,7 @@ use crate::ClientBandwidth;
use crate::error::*;
use nym_credentials::ecash::utils::ecash_today;
use nym_credentials_interface::Bandwidth;
use nym_gateway_requests::ServerResponse;
use nym_gateway_storage::traits::BandwidthGatewayStorage;
use si_scale::helpers::bibytes2;
use time::OffsetDateTime;
@@ -65,7 +66,7 @@ impl BandwidthStorageManager {
Ok(())
}
pub async fn handle_claim_testnet_bandwidth(&mut self) -> Result<i64> {
pub async fn handle_claim_testnet_bandwidth(&mut self) -> Result<ServerResponse> {
debug!("handling testnet bandwidth request");
if self.only_coconut_credentials {
@@ -75,7 +76,8 @@ impl BandwidthStorageManager {
self.increase_bandwidth(FREE_TESTNET_BANDWIDTH_VALUE, ecash_today())
.await?;
let available_total = self.client_bandwidth.available().await;
Ok(available_total)
Ok(ServerResponse::Bandwidth { available_total })
}
#[instrument(skip_all)]
@@ -94,7 +96,7 @@ impl BandwidthStorageManager {
let available_bi2 = bibytes2(available_bandwidth as f64);
let required_bi2 = bibytes2(required_bandwidth as f64);
trace!(available = available_bi2, required = required_bi2);
debug!(available = available_bi2, required = required_bi2);
self.consume_bandwidth(required_bandwidth).await?;
let remaining_bandwidth = self.client_bandwidth.available().await;
@@ -47,25 +47,6 @@ pub enum Error {
UnknownTicketType(#[from] nym_credentials_interface::UnknownTicketType),
}
impl Error {
pub fn is_out_of_bandwidth(&self) -> bool {
matches!(self, Error::OutOfBandwidth { .. })
}
}
pub trait OutOfBandwidthResultExt {
fn is_out_of_bandwidth(&self) -> bool;
}
impl<T> OutOfBandwidthResultExt for Result<T> {
fn is_out_of_bandwidth(&self) -> bool {
match &self {
Ok(_) => false,
Err(err) => err.is_out_of_bandwidth(),
}
}
}
impl From<EcashTicketError> for Error {
fn from(err: EcashTicketError) -> Self {
// don't expose storage issue details to the user
@@ -18,7 +18,6 @@ pub mod bandwidth_storage_manager;
mod client_bandwidth;
pub mod ecash;
pub mod error;
pub mod upgrade_mode;
pub struct CredentialVerifier {
credential: CredentialSpendingRequest,
@@ -1,271 +0,0 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender};
use nym_upgrade_mode_check::{
CREDENTIAL_PROXY_JWT_ISSUER, UpgradeModeAttestation, validate_upgrade_mode_jwt,
};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicI64, Ordering};
use std::time::Duration;
use thiserror::Error;
use time::OffsetDateTime;
use tokio::sync::{Notify, RwLock};
use tracing::{debug, error};
#[derive(Debug, Error)]
pub enum UpgradeModeEnableError {
#[error("too soon to perform another upgrade mode attestation check")]
TooManyRecheckRequests,
#[error("provided upgrade mode JWT is invalid: {0}")]
InvalidUpgradeModeJWT(#[from] nym_upgrade_mode_check::UpgradeModeCheckError),
#[error("the upgrade mode attestation does not appear to have been published")]
AttestationNotPublished,
#[error("the provided upgrade mode attestation is different from the published one")]
MismatchedUpgradeModeAttestation,
}
// the idea behind this is as follows:
// it's been relatively a long time since the watcher last performed its checks (since it's in 'regular' mode)
// and some client has just sent a JWT. we have to retrieve most recent information in case upgrade mode
// has just been enabled, and we haven't learned about it yet
#[derive(Clone)]
pub struct UpgradeModeCheckRequestSender(Option<UnboundedSender<CheckRequest>>);
impl UpgradeModeCheckRequestSender {
pub fn new(sender: UnboundedSender<CheckRequest>) -> Self {
UpgradeModeCheckRequestSender(Some(sender))
}
pub fn new_empty() -> Self {
Self(None)
}
pub(crate) fn send_request(&self, on_done: Arc<Notify>) {
let Some(ref inner) = self.0 else {
// make sure the caller gets notified so it doesn't wait forever
on_done.notify_waiters();
return;
};
if let Err(not_sent) = inner.unbounded_send(CheckRequest { on_done }) {
debug!("failed to send upgrade mode check request - {not_sent}");
// make sure the caller gets notified so it doesn't wait forever
not_sent.into_inner().on_done.notify_waiters();
}
}
}
pub type UpgradeModeCheckRequestReceiver = UnboundedReceiver<CheckRequest>;
pub struct CheckRequest {
on_done: Arc<Notify>,
}
impl CheckRequest {
pub fn finalize(self) {
self.on_done.notify_waiters();
}
}
#[derive(Clone, Copy)]
pub struct UpgradeModeCheckConfig {
/// The minimum duration since the last explicit check to allow creation of separate request.
pub min_staleness_recheck: Duration,
}
/// Full upgrade mode information, that apart from boolean flag indicating the state
/// and the attestation information, includes channel connection to relevant
/// attestation watcher to request state rechecks
#[derive(Clone)]
pub struct UpgradeModeDetails {
pub(crate) config: UpgradeModeCheckConfig,
pub(crate) request_checker: UpgradeModeCheckRequestSender,
pub(crate) state: UpgradeModeState,
}
impl UpgradeModeDetails {
pub fn new(
config: UpgradeModeCheckConfig,
request_checker: UpgradeModeCheckRequestSender,
state: UpgradeModeState,
) -> Self {
UpgradeModeDetails {
config,
request_checker,
state,
}
}
pub fn enabled(&self) -> bool {
self.state.upgrade_mode_enabled()
}
fn since_last_query(&self) -> Duration {
self.state.since_last_query()
}
pub fn can_request_recheck(&self) -> bool {
self.since_last_query() > self.config.min_staleness_recheck
}
// explicitly request state update. this is only called when upgrade mode is NOT enabled,
// and client has sent a JWT instead of ticket
async fn request_recheck(&self) -> bool {
// send request
let on_done = Arc::new(Notify::new());
self.request_checker.send_request(on_done.clone());
// wait for response - note, if we fail to send, notification will be sent regardless,
// so that we wouldn't get stuck in here
on_done.notified().await;
// check the state again
self.enabled()
}
pub async fn try_enable_via_received_jwt(
&self,
token: String,
) -> Result<(), UpgradeModeEnableError> {
// see if it's viable to perform another expedited check
if !self.can_request_recheck() {
return Err(UpgradeModeEnableError::TooManyRecheckRequests);
}
// first validate whether the received JWT is even valid
// note: we expect the token has been signed by our credential proxy
// (in the future, we won't care about it, and we'll have proper key discovery endpoint. 2026™️)
let attestation = validate_upgrade_mode_jwt(&token, Some(CREDENTIAL_PROXY_JWT_ISSUER))?;
// send request to revalidate internal state
self.request_recheck().await;
// not strictly necessary, but check if provided attestation actually matches the one retrieved
// (if any)
let Some(retrieved_attestation) = self.state.attestation().await else {
return Err(UpgradeModeEnableError::AttestationNotPublished);
};
if retrieved_attestation != attestation {
return Err(UpgradeModeEnableError::MismatchedUpgradeModeAttestation);
}
// note: if attestation has been returned, it means we're definitely in upgrade mode
// (otherwise it wouldn't have existed in the state)
Ok(())
}
}
/// Detailed upgrade mode information, that apart from boolean flag,
/// also includes, if applicable, the associated attestation
#[derive(Clone)]
pub struct UpgradeModeState {
inner: Arc<UpgradeModeStateInner>,
}
/// Just a shareable flag to indicate whether upgrade mode is enabled or disabled
#[derive(Clone, Default)]
pub struct UpgradeModeStatus(Arc<AtomicBool>);
impl UpgradeModeStatus {
pub fn enabled(&self) -> bool {
self.0.load(Ordering::Acquire)
}
pub fn enable(&self) {
self.0.store(true, Ordering::Relaxed);
}
pub fn disable(&self) {
self.0.store(false, Ordering::Release);
}
}
impl UpgradeModeState {
pub fn new_empty() -> UpgradeModeState {
UpgradeModeState {
inner: Arc::new(UpgradeModeStateInner {
expected_attestation: RwLock::new(None),
last_queried_ts: AtomicI64::new(OffsetDateTime::UNIX_EPOCH.unix_timestamp()),
status: UpgradeModeStatus(Arc::new(AtomicBool::new(false))),
}),
}
}
pub async fn attestation(&self) -> Option<UpgradeModeAttestation> {
self.inner.expected_attestation.read().await.clone()
}
pub async fn set_expected_attestation(
&self,
expected_attestation: Option<UpgradeModeAttestation>,
) {
let mut guard = self.inner.expected_attestation.write().await;
// make sure to only enable upgrade mode flag AFTER we have written the expected value
// (or still hold the exclusive lock as in this instance)
if expected_attestation.is_some() {
self.enable_upgrade_mode()
} else {
self.disable_upgrade_mode()
}
self.update_last_queried(OffsetDateTime::now_utc());
*guard = expected_attestation;
}
pub fn upgrade_mode_status(&self) -> UpgradeModeStatus {
self.inner.status.clone()
}
pub fn upgrade_mode_enabled(&self) -> bool {
self.inner.status.enabled()
}
pub fn enable_upgrade_mode(&self) {
self.inner.status.enable()
}
pub fn disable_upgrade_mode(&self) {
self.inner.status.disable()
}
pub fn last_queried(&self) -> OffsetDateTime {
// SAFETY: the stored value here is always a valid unix timestamp
#[allow(clippy::unwrap_used)]
OffsetDateTime::from_unix_timestamp(self.inner.last_queried_ts.load(Ordering::Acquire))
.unwrap()
}
pub fn update_last_queried(&self, queried_at: OffsetDateTime) {
self.inner
.last_queried_ts
.store(queried_at.unix_timestamp(), Ordering::Release);
}
pub fn since_last_query(&self) -> Duration {
(OffsetDateTime::now_utc() - self.last_queried())
.try_into()
.unwrap_or_else(|_| {
error!("somehow our last query for upgrade mode was in the future!");
Duration::ZERO
})
}
}
struct UpgradeModeStateInner {
/// Contents of the published upgrade mode attestation, as queried by this node
expected_attestation: RwLock<Option<UpgradeModeAttestation>>,
/// timestamp indicating last time this node has queried for the current upgrade mode attestation
/// it is used to determine if an additional expedited query should be made in case client sends a JWT
/// whilst this node is not aware of the upgrade mode
last_queried_ts: AtomicI64,
/// flag indicating whether upgrade mode is currently enabled. this is to perform cheap checks
/// that avoid having to acquire the lock
// (and dealing with the async consequences of that)
status: UpgradeModeStatus,
}
-2
View File
@@ -23,5 +23,3 @@ rand = { workspace = true }
nym-compact-ecash = { path = "../nym_offline_compact_ecash" }
nym-ecash-time = { path = "../ecash-time" }
nym-network-defaults = { path = "../network-defaults" }
nym-upgrade-mode-check = { path = "../upgrade-mode-check" }
-22
View File
@@ -30,28 +30,6 @@ pub use nym_compact_ecash::{
};
pub use nym_ecash_time::{EcashTime, ecash_today};
pub use nym_network_defaults::TicketTypeRepr;
pub use nym_upgrade_mode_check::UpgradeModeAttestation;
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub enum BandwidthCredential {
ZkNym(Box<CredentialSpendingData>),
UpgradeModeJWT { token: String },
}
impl BandwidthCredential {
pub fn into_zk_nym(self) -> Option<Box<CredentialSpendingData>> {
match self {
BandwidthCredential::ZkNym(credential) => Some(credential),
_ => None,
}
}
}
impl From<CredentialSpendingData> for BandwidthCredential {
fn from(credential: CredentialSpendingData) -> Self {
Self::ZkNym(Box::new(credential))
}
}
#[derive(Debug, Clone)]
pub struct CredentialSigningData {
-4
View File
@@ -36,11 +36,7 @@ nym-sphinx-types = { path = "../nymsphinx/types", version = "0.2.0", default-fea
nym-pemstore = { path = "../../common/pemstore", version = "0.3.0" }
[dev-dependencies]
anyhow = { workspace = true }
rand_chacha = { workspace = true }
serde_json = { workspace = true }
nym-test-utils = { path = "../test-utils" }
[features]
default = []
@@ -17,51 +17,6 @@ pub mod bs58_ed25519_pubkey {
}
}
pub mod vec_bs58_ed25519_pubkey {
use super::*;
use serde::{Deserialize, Deserializer, Serializer, ser::SerializeSeq};
pub fn serialize<S: Serializer>(
keys: &Vec<PublicKey>,
serializer: S,
) -> Result<S::Ok, S::Error> {
let mut seq = serializer.serialize_seq(Some(keys.len()))?;
for key in keys {
seq.serialize_element(&Bs58KeyWrapper(*key))?;
}
seq.end()
}
pub fn deserialize<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<Vec<PublicKey>, D::Error> {
let wrapped = Vec::<Bs58KeyWrapper>::deserialize(deserializer)?;
Ok(wrapped.into_iter().map(|k| k.0).collect())
}
struct Bs58KeyWrapper(PublicKey);
impl serde::Serialize for Bs58KeyWrapper {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
bs58_ed25519_pubkey::serialize(&self.0, serializer)
}
}
impl<'de> Deserialize<'de> for Bs58KeyWrapper {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Ok(Bs58KeyWrapper(bs58_ed25519_pubkey::deserialize(
deserializer,
)?))
}
}
}
pub mod bs58_ed25519_signature {
use crate::asymmetric::ed25519::Signature;
use serde::{Deserialize, Deserializer, Serializer};
@@ -78,53 +33,3 @@ pub mod bs58_ed25519_signature {
Signature::from_base58_string(s).map_err(serde::de::Error::custom)
}
}
#[cfg(test)]
mod tests {
use super::*;
use jwt_simple::reexports::{anyhow, serde_json};
use nym_test_utils::helpers::deterministic_rng;
use serde::{Deserialize, Serialize};
#[test]
fn vec_bs58_ed25519_pubkey_json() -> anyhow::Result<()> {
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct KeysWrapper(#[serde(with = "vec_bs58_ed25519_pubkey")] Vec<PublicKey>);
use crate::asymmetric::ed25519;
let mut rng = deterministic_rng();
let empty = KeysWrapper(vec![]);
let single_key = KeysWrapper(vec![PublicKey::from_base58_string(
"Be9wH7xuXBRJAuV1pC7MALZv6a61RvWQ3SypsNarqTt",
)?]);
let three_keys = KeysWrapper(vec![
ed25519::KeyPair::new(&mut rng).public_key,
ed25519::KeyPair::new(&mut rng).public_key,
ed25519::KeyPair::new(&mut rng).public_key,
]);
let se_empty = serde_json::to_string(&empty)?;
let se_single_key = serde_json::to_string(&single_key)?;
let se_three_keys = serde_json::to_string(&three_keys)?;
assert_eq!(se_empty, r#"[]"#);
assert_eq!(
se_single_key,
r#"["Be9wH7xuXBRJAuV1pC7MALZv6a61RvWQ3SypsNarqTt"]"#
);
assert_eq!(
se_three_keys,
r#"["HmgHDV79LpnEaSUp8QZQwSroxVvS4RewF7yM9e7qu8y3","311xRh859qCd5MVqoPRCoNx26eYhLknGwtjzkkTJFGhf","A5BMp8WJ6Uk91U4JpWRv2Bc6X35AaRaSEy8QEWeAkaBv"]"#
);
let empty_de = serde_json::from_str::<KeysWrapper>(&se_empty)?;
let single_key_de = serde_json::from_str::<KeysWrapper>(&se_single_key)?;
let three_keys_de = serde_json::from_str::<KeysWrapper>(&se_three_keys)?;
assert_eq!(empty, empty_de);
assert_eq!(single_key, single_key_de);
assert_eq!(three_keys, three_keys_de);
Ok(())
}
}
-3
View File
@@ -51,6 +51,3 @@ anyhow = { workspace = true }
nym-compact-ecash = { path = "../nym_offline_compact_ecash" } # we need specific imports in tests
nym-test-utils = { path = "../test-utils" }
tokio = { workspace = true, features = ["full"] }
[lints]
workspace = true
+13 -53
View File
@@ -19,9 +19,7 @@ pub use shared_key::{
SharedGatewayKey, SharedKeyConversionError, SharedKeyUsageError, SharedSymmetricKey,
};
pub type GatewayProtocolVersion = u8;
pub const CURRENT_PROTOCOL_VERSION: GatewayProtocolVersion = UPGRADE_MODE_VERSION;
pub const CURRENT_PROTOCOL_VERSION: u8 = EMBEDDED_KEY_ROTATION_INFO_VERSION;
/// Defines the current version of the communication protocol between gateway and clients.
/// It has to be incremented for any breaking change.
@@ -31,73 +29,35 @@ pub const CURRENT_PROTOCOL_VERSION: GatewayProtocolVersion = UPGRADE_MODE_VERSIO
// 3 - change to AES-GCM-SIV and non-zero IVs
// 4 - introduction of v2 authentication protocol to prevent reply attacks
// 5 - add key rotation information to the serialised mix packet
// 6 - support for 'upgrade mode'
pub const INITIAL_PROTOCOL_VERSION: GatewayProtocolVersion = 1;
pub const CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION: GatewayProtocolVersion = 2;
pub const AES_GCM_SIV_PROTOCOL_VERSION: GatewayProtocolVersion = 3;
pub const AUTHENTICATE_V2_PROTOCOL_VERSION: GatewayProtocolVersion = 4;
pub const EMBEDDED_KEY_ROTATION_INFO_VERSION: GatewayProtocolVersion = 5;
pub const UPGRADE_MODE_VERSION: GatewayProtocolVersion = 6;
pub const INITIAL_PROTOCOL_VERSION: u8 = 1;
pub const CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION: u8 = 2;
pub const AES_GCM_SIV_PROTOCOL_VERSION: u8 = 3;
pub const AUTHENTICATE_V2_PROTOCOL_VERSION: u8 = 4;
pub const EMBEDDED_KEY_ROTATION_INFO_VERSION: u8 = 5;
// TODO: could using `Mac` trait here for OutputSize backfire?
// Should hmac itself be exposed, imported and used instead?
pub type LegacyGatewayMacSize = <GatewayIntegrityHmacAlgorithm as OutputSizeUser>::OutputSize;
pub trait GatewayProtocolVersionExt {
const CURRENT: GatewayProtocolVersion = CURRENT_PROTOCOL_VERSION;
fn supports_aes256_gcm_siv(&self) -> bool;
fn supports_authenticate_v2(&self) -> bool;
fn supports_key_rotation_packet(&self) -> bool;
fn supports_upgrade_mode(&self) -> bool;
fn is_future_version(&self) -> bool;
}
impl GatewayProtocolVersionExt for Option<GatewayProtocolVersion> {
impl GatewayProtocolVersionExt for Option<u8> {
fn supports_aes256_gcm_siv(&self) -> bool {
let Some(protocol) = self else { return false };
protocol.supports_aes256_gcm_siv()
let Some(protocol) = *self else { return false };
protocol >= AES_GCM_SIV_PROTOCOL_VERSION
}
fn supports_authenticate_v2(&self) -> bool {
let Some(protocol) = self else { return false };
protocol.supports_authenticate_v2()
let Some(protocol) = *self else { return false };
protocol >= AUTHENTICATE_V2_PROTOCOL_VERSION
}
fn supports_key_rotation_packet(&self) -> bool {
let Some(protocol) = self else { return false };
protocol.supports_key_rotation_packet()
}
fn supports_upgrade_mode(&self) -> bool {
let Some(protocol) = self else { return false };
protocol.supports_upgrade_mode()
}
fn is_future_version(&self) -> bool {
let Some(protocol) = self else { return false };
protocol.is_future_version()
}
}
impl GatewayProtocolVersionExt for GatewayProtocolVersion {
fn supports_aes256_gcm_siv(&self) -> bool {
*self >= AES_GCM_SIV_PROTOCOL_VERSION
}
fn supports_authenticate_v2(&self) -> bool {
*self >= AUTHENTICATE_V2_PROTOCOL_VERSION
}
fn supports_key_rotation_packet(&self) -> bool {
*self >= EMBEDDED_KEY_ROTATION_INFO_VERSION
}
fn supports_upgrade_mode(&self) -> bool {
*self >= UPGRADE_MODE_VERSION
}
fn is_future_version(&self) -> bool {
*self > CURRENT_PROTOCOL_VERSION
let Some(protocol) = *self else { return false };
protocol >= EMBEDDED_KEY_ROTATION_INFO_VERSION
}
}
+3 -3
View File
@@ -82,7 +82,7 @@ mod tests {
let exp_date_sigs = generate_expiration_date_signatures(
sig_req.expiration_date.ecash_unix_timestamp(),
&[keypair.secret_key()],
&[keypair.verification_key()],
&vec![keypair.verification_key()],
&keypair.verification_key(),
&[keypair.index.unwrap()],
)
@@ -106,14 +106,14 @@ mod tests {
.unwrap();
let wallet = issuance
.aggregate_signature_shares(&keypair.verification_key(), &[partial_wallet], sig_req)
.aggregate_signature_shares(&keypair.verification_key(), &vec![partial_wallet], sig_req)
.unwrap();
let mut issued = issuance.into_issued_ticketbook(wallet, 1);
let coin_indices_signatures = generate_coin_indices_signatures(
nym_credentials_interface::ecash_parameters(),
&[keypair.secret_key()],
&[keypair.verification_key()],
&vec![keypair.verification_key()],
&keypair.verification_key(),
&[keypair.index.unwrap()],
)
@@ -3,12 +3,10 @@
use crate::registration::handshake::messages::{Finalization, GatewayMaterialExchange};
use crate::registration::handshake::state::State;
use crate::registration::handshake::HandshakeResult;
use crate::registration::handshake::SharedGatewayKey;
use crate::registration::handshake::{error::HandshakeError, WsItem};
use crate::{GatewayProtocolVersionExt, INITIAL_PROTOCOL_VERSION};
use futures::{Sink, Stream};
use rand::{CryptoRng, RngCore};
use tracing::info;
use tungstenite::Message as WsMessage;
impl<S, R> State<'_, S, R> {
@@ -27,26 +25,10 @@ impl<S, R> State<'_, S, R> {
// 2. wait for response with remote x25519 pubkey as well as encrypted signature
// <- g^y || AES(k, sig(gate_priv, (g^y || g^x)) || MAYBE_NONCE
let (mid_res, gateway_protocol) = self
let mid_res = self
.receive_handshake_message::<GatewayMaterialExchange>()
.await?;
// NEGOTIATE PROTOCOL
if gateway_protocol.is_future_version() {
// SAFETY: future version means it's greater than CURRENT, which is always a `Some`
#[allow(clippy::unwrap_used)]
return Err(HandshakeError::UnsupportedProtocol {
version: gateway_protocol.unwrap(),
});
}
let gateway_protocol = gateway_protocol.unwrap_or(INITIAL_PROTOCOL_VERSION);
// that should never happen, but we're fine with that outcome
if Some(gateway_protocol) != self.proposed_protocol_version() {
info!("the gateway insists on protocol version different from the one we suggested. it wants {gateway_protocol} whilst we wanted {:?}, however, we can support it", self.proposed_protocol_version());
self.set_protocol_version(gateway_protocol);
}
// 3. derive shared keys locally
// hkdf::<blake3>::(g^xy)
self.derive_shared_key(&mid_res.ephemeral_dh, maybe_hkdf_salt.as_deref());
@@ -60,14 +42,14 @@ impl<S, R> State<'_, S, R> {
self.send_handshake_data(materials).await?;
// 6. wait for remote confirmation of finalizing the handshake
let (finalization, _) = self.receive_handshake_message::<Finalization>().await?;
let finalization = self.receive_handshake_message::<Finalization>().await?;
finalization.ensure_success()?;
Ok(())
}
pub(crate) async fn perform_client_handshake(
mut self,
) -> Result<HandshakeResult, HandshakeError>
) -> Result<SharedGatewayKey, HandshakeError>
where
S: Stream<Item = WsItem> + Sink<WsMessage> + Unpin,
R: CryptoRng + RngCore,
@@ -2,8 +2,6 @@
// SPDX-License-Identifier: Apache-2.0
use crate::shared_key::SharedKeyUsageError;
use crate::GatewayProtocolVersion;
use crate::GatewayProtocolVersionExt;
use thiserror::Error;
#[derive(Debug, Error)]
@@ -36,10 +34,4 @@ pub enum HandshakeError {
#[error("timed out waiting for a handshake message")]
Timeout,
#[error("Connection is in an invalid state - please send a bug report")]
ConnectionInInvalidState,
#[error("the gateway requests protocol version that's not supported by this client. it wants to use v{version} whilst we only understand up to v{}", GatewayProtocolVersion::CURRENT)]
UnsupportedProtocol { version: GatewayProtocolVersion },
}
@@ -5,11 +5,9 @@ use crate::registration::handshake::messages::{
HandshakeMessage, Initialisation, MaterialExchange,
};
use crate::registration::handshake::state::State;
use crate::registration::handshake::HandshakeResult;
use crate::registration::handshake::SharedGatewayKey;
use crate::registration::handshake::{error::HandshakeError, WsItem};
use crate::{GatewayProtocolVersion, GatewayProtocolVersionExt};
use futures::{Sink, Stream};
use tracing::{debug, warn};
use tungstenite::Message as WsMessage;
impl<S, R> State<'_, S, R> {
@@ -20,39 +18,11 @@ impl<S, R> State<'_, S, R> {
where
S: Stream<Item = WsItem> + Sink<WsMessage> + Unpin,
{
// NEGOTIATE PROTOCOL
// old clients were sending protocol version as defined by the following:
/*
fn request_protocol_version(&self) -> u8 {
if self.derive_aes256_gcm_siv_key {
AES_GCM_SIV_PROTOCOL_VERSION
} else if self.expects_credential_usage {
CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION
} else {
INITIAL_PROTOCOL_VERSION
}
}
*/
// meaning the highest possible value they could have sent was `4` (AUTHENTICATE_V2_PROTOCOL_VERSION)
// so if we received anything higher than that, it means they understand negotiation.
// currently not strictly needed as we just blindly accept what they proposed,
// but will be needed in the future.
if self.proposed_protocol_version().is_future_version() {
// this should never happen in a non-malicious client as it should use at most whatever version this gateway has announced
self.set_protocol_version(GatewayProtocolVersion::CURRENT)
} else {
// currently we accept all protocols, i.e. legacy keys, aes128, etc. so we downgrade to whatever
// the client has proposed. this will change in the future
debug!(
"using the protocol version proposed by the client: {:?}",
self.proposed_protocol_version()
)
}
// 1. receive remote ed25519 pubkey alongside ephemeral x25519 pubkey and maybe a flag indicating non-legacy client
// LOCAL_ID_PUBKEY || EPHEMERAL_KEY || MAYBE_NON_LEGACY
let init_message = Initialisation::try_from_bytes(&raw_init_message)?;
self.update_remote_identity(init_message.identity);
self.set_aes256_gcm_siv_key_derivation(!init_message.is_legacy());
// 2. derive shared keys locally
// hkdf::<blake3>::(g^xy)
@@ -69,12 +39,7 @@ impl<S, R> State<'_, S, R> {
self.send_handshake_data(material).await?;
// 4. wait for the remote response with their own encrypted signature
let (materials, client_protocol) =
self.receive_handshake_message::<MaterialExchange>().await?;
if client_protocol != self.proposed_protocol_version() {
warn!("the client hasn't accepted our proposed protocol version. we suggested {:?} while it returned {client_protocol:?}", self.proposed_protocol_version());
// TBD what to do here
}
let materials = self.receive_handshake_message::<MaterialExchange>().await?;
// 5. verify the received signature using the locally derived keys
self.verify_remote_key_material(&materials, &init_message.ephemeral_dh)?;
@@ -89,7 +54,7 @@ impl<S, R> State<'_, S, R> {
pub(crate) async fn perform_gateway_handshake(
mut self,
raw_init_message: Vec<u8>,
) -> Result<HandshakeResult, HandshakeError>
) -> Result<SharedGatewayKey, HandshakeError>
where
S: Stream<Item = WsItem> + Sink<WsMessage> + Unpin,
{
@@ -24,6 +24,13 @@ pub struct Initialisation {
pub initiator_salt: Option<Vec<u8>>,
}
impl Initialisation {
#[cfg(not(target_arch = "wasm32"))]
pub fn is_legacy(&self) -> bool {
self.initiator_salt.is_none()
}
}
#[derive(Debug)]
pub struct MaterialExchange {
pub signature_ciphertext: Vec<u8>,
@@ -92,9 +99,8 @@ impl HandshakeMessage for Initialisation {
let identity = ed25519::PublicKey::from_bytes(&bytes[..ed25519::PUBLIC_KEY_LENGTH])
.map_err(|_| HandshakeError::MalformedRequest)?;
// SAFETY: this can only fail if the provided bytes have len different from encryption::PUBLIC_KEY_SIZE
// this can only fail if the provided bytes have len different from encryption::PUBLIC_KEY_SIZE
// which is impossible
#[allow(clippy::unwrap_used)]
let ephemeral_dh =
x25519::PublicKey::from_bytes(&bytes[ed25519::PUBLIC_KEY_LENGTH..legacy_len]).unwrap();
@@ -188,7 +194,6 @@ impl HandshakeMessage for GatewayMaterialExchange {
// this can only fail if the provided bytes have len different from PUBLIC_KEY_SIZE
// which is impossible
#[allow(clippy::unwrap_used)]
let ephemeral_dh =
x25519::PublicKey::from_bytes(&bytes[..x25519::PUBLIC_KEY_SIZE]).unwrap();
let materials = MaterialExchange::try_from_bytes(&bytes[x25519::PUBLIC_KEY_SIZE..])?;
@@ -3,7 +3,7 @@
use self::error::HandshakeError;
use crate::registration::handshake::state::State;
use crate::{GatewayProtocolVersion, SharedGatewayKey};
use crate::SharedGatewayKey;
use futures::future::BoxFuture;
use futures::{Sink, Stream};
use nym_crypto::asymmetric::ed25519;
@@ -34,29 +34,24 @@ pub const KDF_SALT_LENGTH: usize = 16;
// we do not need to worry about that.
pub struct GatewayHandshake<'a> {
handshake_future: BoxFuture<'a, Result<HandshakeResult, HandshakeError>>,
handshake_future: BoxFuture<'a, Result<SharedGatewayKey, HandshakeError>>,
}
impl Future for GatewayHandshake<'_> {
type Output = Result<HandshakeResult, HandshakeError>;
type Output = Result<SharedGatewayKey, HandshakeError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut self.handshake_future).poll(cx)
}
}
#[derive(Debug, PartialEq)]
pub struct HandshakeResult {
pub negotiated_protocol: GatewayProtocolVersion,
pub derived_key: SharedGatewayKey,
}
pub fn client_handshake<'a, S, R>(
rng: &'a mut R,
ws_stream: &'a mut S,
identity: &'a ed25519::KeyPair,
gateway_pubkey: ed25519::PublicKey,
gateway_protocol: Option<GatewayProtocolVersion>,
expects_credential_usage: bool,
derive_aes256_gcm_siv_key: bool,
#[cfg(not(target_arch = "wasm32"))] shutdown_token: ShutdownToken,
) -> GatewayHandshake<'a>
where
@@ -68,10 +63,11 @@ where
ws_stream,
identity,
Some(gateway_pubkey),
gateway_protocol,
#[cfg(not(target_arch = "wasm32"))]
shutdown_token,
);
)
.with_credential_usage(expects_credential_usage)
.with_aes256_gcm_siv_key(derive_aes256_gcm_siv_key);
GatewayHandshake {
handshake_future: Box::pin(state.perform_client_handshake()),
@@ -84,21 +80,13 @@ pub fn gateway_handshake<'a, S, R>(
ws_stream: &'a mut S,
identity: &'a ed25519::KeyPair,
received_init_payload: Vec<u8>,
requested_client_protocol: Option<GatewayProtocolVersion>,
shutdown_token: ShutdownToken,
) -> GatewayHandshake<'a>
where
S: Stream<Item = WsItem> + Sink<WsMessage> + Unpin + Send + 'a,
R: CryptoRng + RngCore + Send,
{
let state = State::new(
rng,
ws_stream,
identity,
None,
requested_client_protocol,
shutdown_token,
);
let state = State::new(rng, ws_stream, identity, None, shutdown_token);
GatewayHandshake {
handshake_future: Box::pin(state.perform_gateway_handshake(received_init_payload)),
}
@@ -125,8 +113,7 @@ DONE(status)
#[cfg(test)]
mod tests {
use super::*;
use crate::{ClientControlRequest, CURRENT_PROTOCOL_VERSION, INITIAL_PROTOCOL_VERSION};
use anyhow::{bail, Context};
use crate::ClientControlRequest;
use futures::StreamExt;
use nym_test_utils::helpers::u64_seeded_rng;
use nym_test_utils::mocks::stream_sink::mock_streams;
@@ -134,53 +121,10 @@ mod tests {
use tokio::join;
use tungstenite::Message;
trait ClientControlRequestExt {
async fn get_handshake_init_data(&mut self) -> anyhow::Result<Vec<u8>> {
let ClientControlRequest::RegisterHandshakeInitRequest {
protocol_version: _,
data,
} = self.get_control_request().await?
else {
bail!("unexpected ClientControlRequest")
};
Ok(data)
}
async fn get_control_request(&mut self) -> anyhow::Result<ClientControlRequest>;
}
#[tokio::test]
async fn basic_handshake() -> anyhow::Result<()> {
use anyhow::Context as _;
impl<T> ClientControlRequestExt for T
where
T: Stream<Item = WsItem> + Unpin,
{
async fn get_control_request(&mut self) -> anyhow::Result<ClientControlRequest> {
let msg = self
.next()
.timeboxed()
.await
.context("timeout")?
.context("no message!")??
.into_text()?
.parse::<ClientControlRequest>()?;
Ok(msg)
}
}
struct Party<R: 'static, S: 'static> {
rng: &'static mut R,
keys: &'static mut ed25519::KeyPair,
socket: &'static mut S,
}
fn setup() -> (
Party<
impl CryptoRng + RngCore + Send,
impl Stream<Item = WsItem> + Sink<WsMessage> + Unpin,
>,
Party<
impl CryptoRng + RngCore + Send,
impl Stream<Item = WsItem> + Sink<WsMessage> + Unpin,
>,
) {
// solve the lifetime issue by just leaking the contents of the boxes
// which is perfectly fine in test
let client_rng = u64_seeded_rng(42).leak();
@@ -198,139 +142,51 @@ mod tests {
let client_ws = client_ws.leak();
let gateway_ws = gateway_ws.leak();
(
Party {
rng: client_rng,
keys: client_keys,
socket: client_ws,
},
Party {
rng: gateway_rng,
keys: gateway_keys,
socket: gateway_ws,
},
)
}
#[tokio::test]
async fn basic_handshake() -> anyhow::Result<()> {
let (client, gateway) = setup();
let handshake_client = client_handshake(
client.rng,
client.socket,
client.keys,
*gateway.keys.public_key(),
Some(CURRENT_PROTOCOL_VERSION),
client_rng,
client_ws,
client_keys,
*gateway_keys.public_key(),
false,
true,
ShutdownToken::default(),
);
let client_fut = handshake_client.spawn_timeboxed();
// we need to receive the first message so that it could be propagated to the gateway side of the handshake
let init_msg = gateway.socket.get_handshake_init_data().await?;
let ClientControlRequest::RegisterHandshakeInitRequest {
protocol_version: _,
data,
} = (gateway_ws.next())
.timeboxed()
.await
.context("timeout")?
.context("no message!")??
.into_text()?
.parse::<ClientControlRequest>()?
else {
panic!("bad message")
};
let init_msg = data;
let handshake_gateway = gateway_handshake(
gateway.rng,
gateway.socket,
gateway.keys,
gateway_rng,
gateway_ws,
gateway_keys,
init_msg,
Some(CURRENT_PROTOCOL_VERSION),
ShutdownToken::default(),
);
let gateway_fut = handshake_gateway.spawn_timeboxed();
let (client, gateway) = join!(client_fut, gateway_fut);
let client_res = client???;
let gateway_res = gateway???;
let client_key = client???;
let gateway_key = gateway???;
// ensure the created keys are the same
assert_eq!(client_res, gateway_res);
assert_eq!(client_res.negotiated_protocol, CURRENT_PROTOCOL_VERSION);
Ok(())
}
#[tokio::test]
async fn protocol_downgrade() -> anyhow::Result<()> {
let (client, gateway) = setup();
let handshake_client = client_handshake(
client.rng,
client.socket,
client.keys,
*gateway.keys.public_key(),
Some(CURRENT_PROTOCOL_VERSION + 42),
ShutdownToken::default(),
);
let client_fut = handshake_client.spawn_timeboxed();
// we need to receive the first message so that it could be propagated to the gateway side of the handshake
let init_msg = gateway.socket.get_handshake_init_data().await?;
let handshake_gateway = gateway_handshake(
gateway.rng,
gateway.socket,
gateway.keys,
init_msg,
Some(CURRENT_PROTOCOL_VERSION + 42),
ShutdownToken::default(),
);
let gateway_fut = handshake_gateway.spawn_timeboxed();
let (client, gateway) = join!(client_fut, gateway_fut);
let client_res = client???;
let gateway_res = gateway???;
// ensure the created keys are the same
assert_eq!(client_res, gateway_res);
// and the protocol got downgraded for both parties
assert_eq!(client_res.negotiated_protocol, CURRENT_PROTOCOL_VERSION);
Ok(())
}
#[tokio::test]
async fn protocol_upgrade() -> anyhow::Result<()> {
let (client, gateway) = setup();
let handshake_client = client_handshake(
client.rng,
client.socket,
client.keys,
*gateway.keys.public_key(),
None,
ShutdownToken::default(),
);
let client_fut = handshake_client.spawn_timeboxed();
// we need to receive the first message so that it could be propagated to the gateway side of the handshake
let init_msg = gateway.socket.get_handshake_init_data().await?;
let handshake_gateway = gateway_handshake(
gateway.rng,
gateway.socket,
gateway.keys,
init_msg,
None,
ShutdownToken::default(),
);
let gateway_fut = handshake_gateway.spawn_timeboxed();
let (client, gateway) = join!(client_fut, gateway_fut);
let client_res = client???;
let gateway_res = gateway???;
// ensure the created keys are the same
assert_eq!(client_res, gateway_res);
// and the protocol got upgraded to the first known version
assert_eq!(client_res.negotiated_protocol, INITIAL_PROTOCOL_VERSION);
assert_eq!(client_key, gateway_key);
Ok(())
}
@@ -5,11 +5,11 @@ use crate::registration::handshake::error::HandshakeError;
use crate::registration::handshake::messages::{
HandshakeMessage, Initialisation, MaterialExchange,
};
use crate::registration::handshake::{HandshakeResult, SharedGatewayKey, WsItem, KDF_SALT_LENGTH};
use crate::registration::handshake::{SharedGatewayKey, WsItem, KDF_SALT_LENGTH};
use crate::shared_key::SharedKeySize;
use crate::{
types, GatewayProtocolVersion, GatewayProtocolVersionExt, LegacySharedKeySize,
LegacySharedKeys, SharedSymmetricKey, INITIAL_PROTOCOL_VERSION,
types, LegacySharedKeySize, LegacySharedKeys, SharedSymmetricKey, AES_GCM_SIV_PROTOCOL_VERSION,
CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION, INITIAL_PROTOCOL_VERSION,
};
use futures::{Sink, SinkExt, Stream, StreamExt};
use nym_crypto::asymmetric::{ed25519, x25519};
@@ -54,11 +54,12 @@ pub(crate) struct State<'a, S, R> {
/// Ideally it would always be known before the handshake was initiated.
remote_pubkey: Option<ed25519::PublicKey>,
/// Version of the protocol to use during the handshake that also implicitly specifies
/// additional features such as the type of derived shared keys, i.e.
/// AES128Ctr + blake3 HMAC keys (legacy) or AES256-GCM-SIV (current)
/// the above is decided by whether the specified protocol version supports the new variant or not.
protocol_version: Option<GatewayProtocolVersion>,
// this field is really out of place here, however, we need to propagate this information somehow
// in order to establish correct protocol for backwards compatibility reasons
expects_credential_usage: bool,
/// Specifies whether the end product should be an AES128Ctr + blake3 HMAC keys (legacy) or AES256-GCM-SIV (current)
derive_aes256_gcm_siv_key: bool,
// channel to receive shutdown signal
#[cfg(not(target_arch = "wasm32"))]
@@ -71,7 +72,6 @@ impl<'a, S, R> State<'a, S, R> {
ws_stream: &'a mut S,
identity: &'a ed25519::KeyPair,
remote_pubkey: Option<ed25519::PublicKey>,
protocol_version: Option<GatewayProtocolVersion>,
#[cfg(not(target_arch = "wasm32"))] shutdown_token: ShutdownToken,
) -> Self
where
@@ -84,31 +84,40 @@ impl<'a, S, R> State<'a, S, R> {
ephemeral_keypair,
identity,
remote_pubkey,
protocol_version,
derived_shared_keys: None,
// later on this should become the default
expects_credential_usage: false,
derive_aes256_gcm_siv_key: false,
#[cfg(not(target_arch = "wasm32"))]
shutdown_token,
}
}
pub(crate) fn with_credential_usage(mut self, expects_credential_usage: bool) -> Self {
self.expects_credential_usage = expects_credential_usage;
self
}
pub(crate) fn with_aes256_gcm_siv_key(mut self, derive_aes256_gcm_siv_key: bool) -> Self {
self.derive_aes256_gcm_siv_key = derive_aes256_gcm_siv_key;
self
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn set_aes256_gcm_siv_key_derivation(&mut self, derive_aes256_gcm_siv_key: bool) {
self.derive_aes256_gcm_siv_key = derive_aes256_gcm_siv_key;
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn local_ephemeral_key(&self) -> &x25519::PublicKey {
self.ephemeral_keypair.public_key()
}
pub(crate) fn proposed_protocol_version(&self) -> Option<GatewayProtocolVersion> {
self.protocol_version
}
pub(crate) fn set_protocol_version(&mut self, protocol_version: GatewayProtocolVersion) {
self.protocol_version = Some(protocol_version);
}
pub(crate) fn maybe_generate_initiator_salt(&mut self) -> Option<Vec<u8>>
where
R: CryptoRng + RngCore,
{
if self.protocol_version.supports_aes256_gcm_siv() {
if self.derive_aes256_gcm_siv_key {
let mut salt = vec![0u8; KDF_SALT_LENGTH];
self.rng.fill_bytes(&mut salt);
Some(salt)
@@ -145,14 +154,13 @@ impl<'a, S, R> State<'a, S, R> {
.private_key()
.diffie_hellman(remote_ephemeral_key);
let key_size = if self.protocol_version.supports_aes256_gcm_siv() {
let key_size = if self.derive_aes256_gcm_siv_key {
SharedKeySize::to_usize()
} else {
LegacySharedKeySize::to_usize()
};
// SAFETY: there is no reason for this to fail as our okm is expected to be only 16 bytes
#[allow(clippy::expect_used)]
// there is no reason for this to fail as our okm is expected to be only 16 bytes
let okm = hkdf::extract_then_expand::<GatewaySharedKeyHkdfAlgorithm>(
initiator_salt,
&dh_result,
@@ -161,14 +169,11 @@ impl<'a, S, R> State<'a, S, R> {
)
.expect("somehow too long okm was provided");
// SAFETY: the okm has been expanded to the length expected by the corresponding keys
let shared_key = if self.protocol_version.supports_aes256_gcm_siv() {
#[allow(clippy::expect_used)]
let shared_key = if self.derive_aes256_gcm_siv_key {
let current_key = SharedSymmetricKey::try_from_bytes(&okm)
.expect("okm was expanded to incorrect length!");
SharedGatewayKey::Current(current_key)
} else {
#[allow(clippy::expect_used)]
let legacy_key = LegacySharedKeys::try_from_bytes(&okm)
.expect("okm was expanded to incorrect length!");
SharedGatewayKey::Legacy(legacy_key)
@@ -191,7 +196,7 @@ impl<'a, S, R> State<'a, S, R> {
.collect();
let signature = self.identity.private_key().sign(plaintext);
let nonce = if self.protocol_version.supports_aes256_gcm_siv() {
let nonce = if self.derive_aes256_gcm_siv_key {
let mut rng = thread_rng();
Some(random_nonce::<GatewayEncryptionAlgorithm, _>(&mut rng).to_vec())
} else {
@@ -199,7 +204,6 @@ impl<'a, S, R> State<'a, S, R> {
};
// SAFETY: this function is only called after the local key has already been derived
#[allow(clippy::expect_used)]
let signature_ciphertext = self
.derived_shared_keys
.as_ref()
@@ -218,14 +222,13 @@ impl<'a, S, R> State<'a, S, R> {
remote_ephemeral_key: &x25519::PublicKey,
) -> Result<(), HandshakeError> {
// SAFETY: this function is only called after the local key has already been derived
#[allow(clippy::expect_used)]
let derived_shared_key = self
.derived_shared_keys
.as_ref()
.expect("shared key was not derived!");
// if the [client] init message contained non-legacy flag, the associated nonce MUST be present
if self.protocol_version.supports_aes256_gcm_siv() && remote_response.nonce.is_none() {
if self.derive_aes256_gcm_siv_key && remote_response.nonce.is_none() {
return Err(HandshakeError::MissingNonceForCurrentKey);
}
@@ -246,7 +249,6 @@ impl<'a, S, R> State<'a, S, R> {
.chain(self.ephemeral_keypair.public_key().to_bytes())
.collect();
#[allow(clippy::unwrap_used)]
self.remote_pubkey
.as_ref()
.unwrap()
@@ -259,10 +261,7 @@ impl<'a, S, R> State<'a, S, R> {
self.remote_pubkey = Some(remote_pubkey)
}
#[allow(clippy::complexity)]
fn on_wg_msg(
msg: Option<WsItem>,
) -> Result<Option<(Vec<u8>, Option<GatewayProtocolVersion>)>, HandshakeError> {
fn on_wg_msg(msg: Option<WsItem>) -> Result<Option<Vec<u8>>, HandshakeError> {
let Some(msg) = msg else {
return Err(HandshakeError::ClosedStream);
};
@@ -278,10 +277,9 @@ impl<'a, S, R> State<'a, S, R> {
// hehe, that's a bit disgusting that the type system requires we explicitly ignore the
// protocol_version field that we actually never attach at this point
// yet another reason for the overdue refactor
types::RegistrationHandshake::HandshakePayload {
protocol_version,
data,
} => Ok(Some((data, protocol_version))),
types::RegistrationHandshake::HandshakePayload { data, .. } => {
Ok(Some(data))
}
types::RegistrationHandshake::HandshakeError { message } => {
Err(HandshakeError::RemoteError(message))
}
@@ -301,9 +299,7 @@ impl<'a, S, R> State<'a, S, R> {
}
#[cfg(not(target_arch = "wasm32"))]
async fn _receive_handshake_message_bytes(
&mut self,
) -> Result<(Vec<u8>, Option<GatewayProtocolVersion>), HandshakeError>
async fn _receive_handshake_message_bytes(&mut self) -> Result<Vec<u8>, HandshakeError>
where
S: Stream<Item = WsItem> + Unpin,
{
@@ -322,9 +318,7 @@ impl<'a, S, R> State<'a, S, R> {
}
#[cfg(target_arch = "wasm32")]
async fn _receive_handshake_message_bytes(
&mut self,
) -> Result<(Vec<u8>, Option<GatewayProtocolVersion>), HandshakeError>
async fn _receive_handshake_message_bytes(&mut self) -> Result<Vec<u8>, HandshakeError>
where
S: Stream<Item = WsItem> + Unpin,
{
@@ -337,22 +331,20 @@ impl<'a, S, R> State<'a, S, R> {
}
}
pub(crate) async fn receive_handshake_message<M>(
&mut self,
) -> Result<(M, Option<GatewayProtocolVersion>), HandshakeError>
pub(crate) async fn receive_handshake_message<M>(&mut self) -> Result<M, HandshakeError>
where
S: Stream<Item = WsItem> + Unpin,
M: HandshakeMessage,
{
// TODO: make timeout duration configurable
let (bytes, protocol) = timeout(
let bytes = timeout(
Duration::from_secs(5),
self._receive_handshake_message_bytes(),
)
.await
.map_err(|_| HandshakeError::Timeout)??;
M::try_from_bytes(&bytes).map(|msg| (msg, protocol))
M::try_from_bytes(&bytes)
}
// upon receiving this, the receiver should terminate the handshake
@@ -365,11 +357,21 @@ impl<'a, S, R> State<'a, S, R> {
{
let handshake_message = types::RegistrationHandshake::new_error(message);
self.ws_stream
.send(WsMessage::Text(handshake_message.into()))
.send(WsMessage::Text(handshake_message.try_into().unwrap()))
.await
.map_err(|_| HandshakeError::ClosedStream)
}
fn request_protocol_version(&self) -> u8 {
if self.derive_aes256_gcm_siv_key {
AES_GCM_SIV_PROTOCOL_VERSION
} else if self.expects_credential_usage {
CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION
} else {
INITIAL_PROTOCOL_VERSION
}
}
pub(crate) async fn send_handshake_data<M>(
&mut self,
inner_message: M,
@@ -382,25 +384,18 @@ impl<'a, S, R> State<'a, S, R> {
let handshake_message = types::RegistrationHandshake::new_payload(
inner_message.into_bytes(),
self.protocol_version,
self.request_protocol_version(),
);
self.ws_stream
.send(WsMessage::Text(handshake_message.into()))
.send(WsMessage::Text(handshake_message.try_into().unwrap()))
.await
.map_err(|_| HandshakeError::ClosedStream)
}
/// Finish the handshake, yielding the derived shared key and implicitly dropping all borrowed
/// values.
pub(crate) fn finalize_handshake(self) -> HandshakeResult {
// SAFETY: handshake can't be finalised without deriving the shared keys
#[allow(clippy::unwrap_used)]
HandshakeResult {
negotiated_protocol: self
.proposed_protocol_version()
.unwrap_or(INITIAL_PROTOCOL_VERSION),
derived_key: self.derived_shared_keys.unwrap(),
}
pub(crate) fn finalize_handshake(self) -> SharedGatewayKey {
self.derived_shared_keys.unwrap()
}
// If any step along the way failed (that are non-network related),
@@ -43,7 +43,6 @@ impl LegacySharedKeys {
rng.fill_bytes(&mut salt);
let legacy_bytes = Zeroizing::new(self.to_bytes());
#[allow(clippy::expect_used)]
let okm = hkdf::extract_then_expand::<GatewaySharedKeyHkdfAlgorithm>(
Some(&salt),
&legacy_bytes,
@@ -52,7 +51,6 @@ impl LegacySharedKeys {
)
.expect("somehow too long okm was provided");
#[allow(clippy::expect_used)]
let key = SharedSymmetricKey::try_from_bytes(&okm)
.expect("okm was expanded to incorrect length!");
(key, salt)
@@ -64,7 +62,6 @@ impl LegacySharedKeys {
expected_digest: &[u8],
) -> Option<SharedSymmetricKey> {
let legacy_bytes = Zeroizing::new(self.to_bytes());
#[allow(clippy::expect_used)]
let okm = hkdf::extract_then_expand::<GatewaySharedKeyHkdfAlgorithm>(
Some(salt),
&legacy_bytes,
@@ -72,8 +69,6 @@ impl LegacySharedKeys {
SharedKeySize::to_usize(),
)
.expect("somehow too long okm was provided");
#[allow(clippy::expect_used)]
let key = SharedSymmetricKey::try_from_bytes(&okm)
.expect("okm was expanded to incorrect length!");
if key.digest() != expected_digest {
@@ -47,8 +47,6 @@ impl SharedGatewayKey {
}
}
// it is responsibility of the caller to ensure the correct variant is present
#[allow(clippy::panic)]
pub fn unwrap_legacy(&self) -> &LegacySharedKeys {
match self {
SharedGatewayKey::Current(_) => panic!("expected legacy key"),
@@ -1,7 +1,6 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::GatewayProtocolVersion;
use serde::{Deserialize, Serialize};
use std::str::FromStr;
@@ -10,7 +9,7 @@ use std::str::FromStr;
pub enum RegistrationHandshake {
HandshakePayload {
#[serde(default)]
protocol_version: Option<GatewayProtocolVersion>,
protocol_version: Option<u8>,
data: Vec<u8>,
},
HandshakeError {
@@ -19,9 +18,9 @@ pub enum RegistrationHandshake {
}
impl RegistrationHandshake {
pub fn new_payload(data: Vec<u8>, protocol_version: Option<GatewayProtocolVersion>) -> Self {
pub fn new_payload(data: Vec<u8>, protocol_version: u8) -> Self {
RegistrationHandshake::HandshakePayload {
protocol_version,
protocol_version: Some(protocol_version),
data,
}
}
@@ -49,11 +48,11 @@ impl TryFrom<String> for RegistrationHandshake {
}
}
impl From<RegistrationHandshake> for String {
fn from(value: RegistrationHandshake) -> Self {
// SAFETY: we have infallible serde implementation
#[allow(clippy::unwrap_used)]
serde_json::to_string(&value).unwrap()
impl TryInto<String> for RegistrationHandshake {
type Error = serde_json::Error;
fn try_into(self) -> Result<String, serde_json::Error> {
serde_json::to_string(&self)
}
}
@@ -80,7 +79,7 @@ mod tests {
assert_eq!(protocol_version, Some(42));
assert_eq!(data, handshake_data)
}
_ => panic!("this branch shouldn't have been reached!"),
_ => unreachable!("this branch shouldn't have been reached!"),
}
let handshake_payload_without_protocol = RegistrationHandshake::HandshakePayload {
@@ -98,7 +97,7 @@ mod tests {
assert!(protocol_version.is_none());
assert_eq!(data, handshake_data)
}
_ => panic!("this branch shouldn't have been reached!"),
_ => unreachable!("this branch shouldn't have been reached!"),
}
}
}
@@ -1,9 +1,7 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::{
AuthenticationFailure, GatewayProtocolVersion, GatewayRequestsError, SharedGatewayKey,
};
use crate::{AuthenticationFailure, GatewayRequestsError, SharedGatewayKey};
use nym_crypto::asymmetric::ed25519;
use serde::{Deserialize, Serialize};
use std::iter;
@@ -22,7 +20,7 @@ pub struct AuthenticateRequest {
impl AuthenticateRequest {
pub fn new(
protocol_version: GatewayProtocolVersion,
protocol_version: u8,
shared_key: &SharedGatewayKey,
identity_keys: &ed25519::KeyPair,
) -> Result<AuthenticateRequest, GatewayRequestsError> {
@@ -100,7 +98,7 @@ impl AuthenticateRequest {
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct AuthenticateRequestContent {
pub protocol_version: GatewayProtocolVersion,
pub protocol_version: u8,
// this is identical to the client's address
pub client_identity: ed25519::PublicKey,
@@ -4,8 +4,9 @@
use crate::models::CredentialSpendingRequest;
use crate::text_request::authenticate::AuthenticateRequest;
use crate::{
GatewayProtocolVersion, GatewayRequestsError, SharedGatewayKey, SymmetricKey,
AES_GCM_SIV_PROTOCOL_VERSION, CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION, INITIAL_PROTOCOL_VERSION,
GatewayRequestsError, SharedGatewayKey, SymmetricKey, AES_GCM_SIV_PROTOCOL_VERSION,
AUTHENTICATE_V2_PROTOCOL_VERSION, CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION,
INITIAL_PROTOCOL_VERSION,
};
use nym_credentials_interface::CredentialSpendingData;
use nym_crypto::asymmetric::ed25519;
@@ -45,7 +46,6 @@ impl ClientRequest {
// - the schema is self-describing which simplifies deserialisation
// SAFETY: the trait has been derived correctly with no weird variants
#[allow(clippy::unwrap_used)]
let plaintext = serde_json::to_vec(self).unwrap();
let nonce = key.random_nonce_or_iv();
let ciphertext = key.encrypt(&plaintext, Some(&nonce))?;
@@ -72,7 +72,7 @@ pub enum ClientControlRequest {
// have the shared key derived?
Authenticate {
#[serde(default)]
protocol_version: Option<GatewayProtocolVersion>,
protocol_version: Option<u8>,
address: String,
enc_address: String,
iv: String,
@@ -83,7 +83,7 @@ pub enum ClientControlRequest {
#[serde(alias = "handshakePayload")]
RegisterHandshakeInitRequest {
#[serde(default)]
protocol_version: Option<GatewayProtocolVersion>,
protocol_version: Option<u8>,
data: Vec<u8>,
},
BandwidthCredential {
@@ -98,10 +98,6 @@ pub enum ClientControlRequest {
enc_credential: Vec<u8>,
iv: Vec<u8>,
},
UpgradeModeJWT {
// no need to encrypt it as it's public anyway
token: String,
},
ClaimFreeTestnetBandwidth,
EncryptedRequest {
ciphertext: Vec<u8>,
@@ -112,14 +108,12 @@ pub enum ClientControlRequest {
}
impl ClientControlRequest {
pub fn new_legacy_authenticate(
pub fn new_authenticate(
address: DestinationAddressBytes,
shared_key: &SharedGatewayKey,
uses_credentials: bool,
) -> Result<Self, GatewayRequestsError> {
// if we're encrypting with non-legacy key, the remote must support AES256-GCM-SIV
// since we are using legacy authentication, the gateway definitely doesn't understand the protocol downgrade,
// so use the lowest possible version we can
let protocol_version = if !shared_key.is_legacy() {
Some(AES_GCM_SIV_PROTOCOL_VERSION)
} else if uses_credentials {
@@ -144,8 +138,10 @@ impl ClientControlRequest {
pub fn new_authenticate_v2(
shared_key: &SharedGatewayKey,
identity_keys: &ed25519::KeyPair,
protocol_version: GatewayProtocolVersion,
) -> Result<Self, GatewayRequestsError> {
// if we're using v2 authentication, we must announce at least that protocol version
let protocol_version = AUTHENTICATE_V2_PROTOCOL_VERSION;
Ok(ClientControlRequest::AuthenticateV2(Box::new(
AuthenticateRequest::new(protocol_version, shared_key, identity_keys)?,
)))
@@ -163,7 +159,6 @@ impl ClientControlRequest {
"BandwidthCredentialV2".to_string()
}
ClientControlRequest::EcashCredential { .. } => "EcashCredential".to_string(),
ClientControlRequest::UpgradeModeJWT { .. } => "UpgradeModeJWT".to_string(),
ClientControlRequest::ClaimFreeTestnetBandwidth => {
"ClaimFreeTestnetBandwidth".to_string()
}
@@ -197,16 +192,12 @@ impl ClientControlRequest {
CredentialSpendingRequest::try_from_bytes(credential_bytes.as_slice())
.map_err(|_| GatewayRequestsError::MalformedEncryption)
}
pub fn new_upgrade_mode_jwt(token: String) -> Self {
ClientControlRequest::UpgradeModeJWT { token }
}
}
impl From<ClientControlRequest> for Message {
fn from(req: ClientControlRequest) -> Self {
// SAFETY: all of the enum variants have valid (for json) serde impl
#[allow(clippy::unwrap_used)]
// it should be safe to call `unwrap` here as the message is generated by the server
// so if it fails (and consequently panics) it's a bug that should be resolved
let str_req = serde_json::to_string(&req).unwrap();
Message::Text(str_req)
}
@@ -1,9 +1,7 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{
GatewayProtocolVersion, GatewayRequestsError, SimpleGatewayRequestsError, SymmetricKey,
};
use crate::{GatewayRequestsError, SimpleGatewayRequestsError, SymmetricKey};
use serde::{Deserialize, Serialize};
use tungstenite::Message;
@@ -28,7 +26,6 @@ impl SensitiveServerResponse {
// - the schema is self-describing which simplifies deserialisation
// SAFETY: the trait has been derived correctly with no weird variants
#[allow(clippy::unwrap_used)]
let plaintext = serde_json::to_vec(self).unwrap();
let nonce = key.random_nonce_or_iv();
let ciphertext = key.encrypt(&plaintext, Some(&nonce))?;
@@ -46,59 +43,31 @@ impl SensitiveServerResponse {
}
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "type", rename_all = "camelCase")]
pub struct BandwidthResponse {
pub available_total: i64,
/// Flag indicating whether the gateway has detected the system is undergoing the upgrade
/// (thus it will not meter bandwidth)
#[serde(default)]
pub upgrade_mode: bool,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "type", rename_all = "camelCase")]
pub struct SendResponse {
pub remaining_bandwidth: i64,
/// Flag indicating whether the gateway has detected the system is undergoing the upgrade
/// (thus it will not meter bandwidth)
#[serde(default)]
pub upgrade_mode: bool,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "type", rename_all = "camelCase")]
#[non_exhaustive]
pub enum ServerResponse {
Authenticate {
#[serde(default)]
protocol_version: Option<GatewayProtocolVersion>,
protocol_version: Option<u8>,
status: bool,
bandwidth_remaining: i64,
/// Flag indicating whether the gateway has detected the system is undergoing the upgrade
/// (thus it will not meter bandwidth)
#[serde(default)]
upgrade_mode: bool,
},
Register {
#[serde(default)]
protocol_version: Option<GatewayProtocolVersion>,
protocol_version: Option<u8>,
status: bool,
/// Flag indicating whether the gateway has detected the system is undergoing the upgrade
/// (thus it will not meter bandwidth)
#[serde(default)]
upgrade_mode: bool,
},
EncryptedResponse {
ciphertext: Vec<u8>,
nonce: Vec<u8>,
},
Bandwidth(BandwidthResponse),
Send(SendResponse),
Bandwidth {
available_total: i64,
},
Send {
remaining_bandwidth: i64,
},
SupportedProtocol {
version: u8,
},
@@ -153,7 +122,6 @@ impl From<ServerResponse> for Message {
fn from(res: ServerResponse) -> Self {
// it should be safe to call `unwrap` here as the message is generated by the server
// so if it fails (and consequently panics) it's a bug that should be resolved
#[allow(clippy::unwrap_used)]
let str_res = serde_json::to_string(&res).unwrap();
Message::Text(str_res)
}
-2
View File
@@ -902,8 +902,6 @@ impl Client {
if self.base_urls.len() > 1 {
let orig = self.current_idx.load(Ordering::Relaxed);
#[allow(unused_mut)]
let mut next = (orig + 1) % self.base_urls.len();
// if fronting is enabled we want to update to a host that has fronts configured
-11
View File
@@ -54,9 +54,6 @@ pub const NYM_APIS: &[ApiUrlConst] = &[
];
pub const NYM_VPN_API: &str = "https://nymvpn.com/api/";
pub const UPGRADE_MOST_ATTESTATION_URL: &str = "https://nym.com/upgrade-mode/attestation.json";
#[cfg(feature = "network")]
pub const NYM_VPN_APIS: &[ApiUrlConst] = &[
ApiUrlConst {
@@ -162,10 +159,6 @@ pub fn export_to_env() {
set_var_to_default(var_names::NYXD_WEBSOCKET, NYXD_WS);
set_var_to_default(var_names::EXIT_POLICY_URL, EXIT_POLICY_URL);
set_var_to_default(var_names::NYM_VPN_API, NYM_VPN_API);
set_var_to_default(
var_names::UPGRADE_MOST_ATTESTATION_URL,
UPGRADE_MOST_ATTESTATION_URL,
);
}
#[cfg(all(feature = "env", feature = "network"))]
@@ -206,8 +199,4 @@ pub fn export_to_env_if_not_set() {
set_var_conditionally_to_default(var_names::NYM_API, NYM_API);
set_var_conditionally_to_default(var_names::NYXD_WEBSOCKET, NYXD_WS);
set_var_conditionally_to_default(var_names::EXIT_POLICY_URL, EXIT_POLICY_URL);
set_var_conditionally_to_default(
var_names::UPGRADE_MOST_ATTESTATION_URL,
UPGRADE_MOST_ATTESTATION_URL,
);
}
-1
View File
@@ -25,7 +25,6 @@ pub const NYXD_WEBSOCKET: &str = "NYXD_WS";
pub const EXIT_POLICY_URL: &str = "EXIT_POLICY";
pub const NYM_VPN_API: &str = "NYM_VPN_API";
pub const CLIENT_STATS_COLLECTION_PROVIDER: &str = "CLIENT_STATS_COLLECTION_PROVIDER";
pub const UPGRADE_MOST_ATTESTATION_URL: &str = "UPGRADE_MOST_ATTESTATION_URL";
pub const DKG_TIME_CONFIGURATION: &str = "DKG_TIME_CONFIGURATION";
@@ -82,17 +82,6 @@ pub struct Protocol {
pub service_provider_type: ServiceProviderType,
}
impl Protocol {
pub const fn new(version: u8, service_provider_type: ServiceProviderType) -> Self {
Self {
version,
service_provider_type,
}
}
}
// NOTE: this only works under the assumption of using bincode for serialisation
// with the current field layout
impl TryFrom<&[u8; 2]> for Protocol {
type Error = ProtocolError;
+1 -8
View File
@@ -15,10 +15,9 @@ jwt-simple = { workspace = true }
reqwest = { workspace = true, features = ["rustls-tls"] }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
time = { workspace = true, features = ["serde", "formatting", "parsing"] }
time = { workspace = true, features = ["serde"] }
thiserror = { workspace = true }
tracing = { workspace = true }
utoipa = { workspace = true, optional = true }
nym-http-api-client = { path = "../http-api-client", default-features = false }
nym-crypto = { path = "../crypto", features = ["asymmetric", "serde", "naive_jwt"] }
@@ -26,12 +25,6 @@ nym-crypto = { path = "../crypto", features = ["asymmetric", "serde", "naive_jwt
[dev-dependencies]
anyhow = { workspace = true }
time = { workspace = true, features = ["macros"] }
nym-test-utils = { path = "../test-utils" }
nym-crypto = { path = "../crypto", features = ["rand"] }
[features]
openapi = ["utoipa"]
[lints]
workspace = true
+16 -46
View File
@@ -1,43 +1,31 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::UpgradeModeCheckError;
use nym_crypto::asymmetric::ed25519;
use nym_http_api_client::generate_user_agent;
use serde::{Deserialize, Serialize};
use std::time::Duration;
use time::OffsetDateTime;
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
pub struct UpgradeModeAttestation {
#[serde(flatten)]
pub content: UpgradeModeAttestationContent,
#[serde(with = "ed25519::bs58_ed25519_signature")]
#[cfg_attr(feature = "openapi", schema(value_type = String))]
pub signature: ed25519::Signature,
}
impl UpgradeModeAttestation {
pub fn authorised_to_issue_jwt(&self, key: &ed25519::PublicKey) -> bool {
self.content.authorised_jwt_issuers.contains(key)
}
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
#[serde(tag = "type")]
#[serde(rename = "upgrade_mode")]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct UpgradeModeAttestationContent {
#[serde(with = "time::serde::rfc3339")]
#[cfg_attr(feature = "openapi", schema(value_type = String))]
#[serde(with = "time::serde::timestamp")]
pub starting_time: OffsetDateTime,
#[serde(with = "ed25519::bs58_ed25519_pubkey")]
#[cfg_attr(feature = "openapi", schema(value_type = String))]
pub attester_public_key: ed25519::PublicKey,
#[serde(with = "ed25519::vec_bs58_ed25519_pubkey")]
#[cfg_attr(feature = "openapi", schema(value_type = Vec<String>))]
pub authorised_jwt_issuers: Vec<ed25519::PublicKey>,
}
impl UpgradeModeAttestation {
@@ -57,26 +45,17 @@ impl UpgradeModeAttestationContent {
}
}
pub fn generate_new_attestation(
key: &ed25519::PrivateKey,
authorised_jwt_issuers: Vec<ed25519::PublicKey>,
) -> UpgradeModeAttestation {
generate_new_attestation_with_starting_time(
key,
authorised_jwt_issuers,
OffsetDateTime::now_utc(),
)
pub fn generate_new_attestation(key: &ed25519::PrivateKey) -> UpgradeModeAttestation {
generate_new_attestation_with_starting_time(key, OffsetDateTime::now_utc())
}
pub fn generate_new_attestation_with_starting_time(
key: &ed25519::PrivateKey,
authorised_jwt_issuers: Vec<ed25519::PublicKey>,
starting_time: OffsetDateTime,
) -> UpgradeModeAttestation {
let content = UpgradeModeAttestationContent {
starting_time,
attester_public_key: key.into(),
authorised_jwt_issuers,
};
UpgradeModeAttestation {
signature: key.sign(content.as_json()),
@@ -84,19 +63,17 @@ pub fn generate_new_attestation_with_starting_time(
}
}
#[cfg(not(target_arch = "wasm32"))]
pub async fn attempt_retrieve_attestation(
pub async fn attempt_retrieve(
url: &str,
user_agent: Option<nym_http_api_client::UserAgent>,
) -> Result<Option<UpgradeModeAttestation>, crate::UpgradeModeCheckError> {
let retrieval_failure = |source| crate::UpgradeModeCheckError::AttestationRetrievalFailure {
) -> Result<Option<UpgradeModeAttestation>, UpgradeModeCheckError> {
let retrieval_failure = |source| UpgradeModeCheckError::AttestationRetrievalFailure {
url: url.to_string(),
source,
};
let attestation = reqwest::ClientBuilder::new()
.user_agent(user_agent.unwrap_or_else(|| nym_http_api_client::generate_user_agent!()))
.timeout(std::time::Duration::from_secs(5))
.user_agent(generate_user_agent!())
.timeout(Duration::from_secs(5))
.build()
.map_err(retrieval_failure)?
.get(url)
@@ -124,20 +101,13 @@ mod tests {
163, 122, 170, 79, 198, 87, 85, 36, 29, 243, 92, 64, 161,
])?;
let authorised_jwt_issuers = vec![ed25519::PublicKey::from_base58_string(
"Be9wH7xuXBRJAuV1pC7MALZv6a61RvWQ3SypsNarqTt",
)?];
let attestation = generate_new_attestation_with_starting_time(
&key,
authorised_jwt_issuers,
starting_time,
);
let attestation = generate_new_attestation_with_starting_time(&key, starting_time);
let attestation_json = serde_json::to_string(&attestation)?;
let attestation_content_json = attestation.content.as_json();
let expected_attestation = r#"{"type":"upgrade_mode","starting_time":"2021-08-23T12:00:00Z","attester_public_key":"3pkFcBXCEmbmXBT2G8CkFMuKisJcH54mbBGvncHaDibt","authorised_jwt_issuers":["Be9wH7xuXBRJAuV1pC7MALZv6a61RvWQ3SypsNarqTt"],"signature":"5Kt9dfwvnkdnDcENbwNyitrxghyckWUYycBv8jUUn7hJUMohWEMc6otb3scXQfCrAGSE7FD5m7kr6auBmkAmfczY"}"#;
let expected_content = r#"{"type":"upgrade_mode","starting_time":"2021-08-23T12:00:00Z","attester_public_key":"3pkFcBXCEmbmXBT2G8CkFMuKisJcH54mbBGvncHaDibt","authorised_jwt_issuers":["Be9wH7xuXBRJAuV1pC7MALZv6a61RvWQ3SypsNarqTt"]}"#;
let expected_attestation = r#"{"type":"upgrade_mode","starting_time":1629720000,"attester_public_key":"3pkFcBXCEmbmXBT2G8CkFMuKisJcH54mbBGvncHaDibt","signature":"5rWUr2ypaDTtrMKegMP3tQkkZGFAuhNTnEVCVe5Azv6QqvLzoGdQiMkFmeyhDd1XSfoXpL9fFM58rsdA1kf4GYMM"}"#;
let expected_content = r#"{"type":"upgrade_mode","starting_time":1629720000,"attester_public_key":"3pkFcBXCEmbmXBT2G8CkFMuKisJcH54mbBGvncHaDibt"}"#;
assert_eq!(attestation_content_json, expected_content);
assert_eq!(attestation_json, expected_attestation);
+2 -5
View File
@@ -6,21 +6,18 @@ use thiserror::Error;
#[derive(Debug, Error)]
pub enum UpgradeModeCheckError {
#[error("failed to decode jwt metadata: {source}")]
#[error("failed to decode jwt metadata")]
TokenMetadataDecodeFailure { source: jwt_simple::Error },
#[error("the jwt metadata didn't contain explicit public key")]
MissingTokenPublicKey,
#[error("the jwt signer does not appear in the authorised attestation set")]
UnauthorisedIssuer,
#[error("the attached public key was not valid ed25519 public key")]
MalformedEd25519PublicKey { source: Ed25519RecoveryError },
#[error("failed to verify the jwt: {source}")]
JwtVerificationFailure { source: jwt_simple::Error },
#[error("failed to retrieve attestation from {url}: {source}")]
#[error("failed to retrieve attestation from {url}:{source}")]
AttestationRetrievalFailure { url: String, source: reqwest::Error },
}
+7 -33
View File
@@ -10,8 +10,6 @@ use nym_crypto::asymmetric::ed25519;
use std::collections::HashSet;
use std::time::Duration;
pub const CREDENTIAL_PROXY_JWT_ISSUER: &str = "nym-credential-proxy";
// for now use static issuer such as "nym-credential-proxy"
pub fn generate_jwt_for_upgrade_mode_attestation(
attestation: UpgradeModeAttestation,
@@ -67,12 +65,6 @@ pub fn validate_upgrade_mode_jwt(
.map_err(|source| UpgradeModeCheckError::JwtVerificationFailure { source })?
.custom;
// jwt itself is cryptographically valid,
// but let's see if this entity has been permitted to issue the token in the first place
if !attestation.authorised_to_issue_jwt(&ed25519_pub_key) {
return Err(UpgradeModeCheckError::UnauthorisedIssuer);
}
Ok(attestation)
}
@@ -81,7 +73,6 @@ mod tests {
use super::*;
use crate::generate_new_attestation;
use nym_crypto::asymmetric::ed25519;
use nym_test_utils::helpers::deterministic_rng;
#[test]
fn generate_and_validate_jwt() {
@@ -95,27 +86,17 @@ mod tests {
2, 52, 215, 241, 219, 200, 18, 159, 241, 76, 111, 42, 32,
])
.unwrap();
let jwt_keys = ed25519::KeyPair::from(jwt_key);
let keys = ed25519::KeyPair::from(jwt_key);
let mut rng = deterministic_rng();
let unauthorised_jwt_keys = ed25519::KeyPair::new(&mut rng);
let attestation = generate_new_attestation(&attestation_key, vec![*jwt_keys.public_key()]);
let attestation = generate_new_attestation(&attestation_key);
let jwt_issuer = generate_jwt_for_upgrade_mode_attestation(
attestation.clone(),
attestation,
Duration::from_secs(60 * 60),
&jwt_keys,
&keys,
Some("nym-credential-proxy"),
);
let unauthorised_jwt = generate_jwt_for_upgrade_mode_attestation(
attestation.clone(),
Duration::from_secs(60 * 60),
&unauthorised_jwt_keys,
Some(CREDENTIAL_PROXY_JWT_ISSUER),
);
// we expect 'nym-credential-proxy' issuer
assert!(validate_upgrade_mode_jwt(&jwt_issuer, Some(CREDENTIAL_PROXY_JWT_ISSUER)).is_ok());
assert!(validate_upgrade_mode_jwt(&jwt_issuer, Some("nym-credential-proxy")).is_ok());
// we don't care about issuer
assert!(validate_upgrade_mode_jwt(&jwt_issuer, None).is_ok());
@@ -123,21 +104,14 @@ mod tests {
// we expect another-issuer
assert!(validate_upgrade_mode_jwt(&jwt_issuer, Some("another-issuer")).is_err());
// the key is not in the authorised set inside the attestation
assert!(
validate_upgrade_mode_jwt(&unauthorised_jwt, Some("nym-credential-proxy")).is_err()
);
let jwt_no_issuer = generate_jwt_for_upgrade_mode_attestation(
attestation,
Duration::from_secs(60 * 60),
&jwt_keys,
&keys,
None,
);
// we expect 'nym-credential-proxy' issuer
assert!(
validate_upgrade_mode_jwt(&jwt_no_issuer, Some(CREDENTIAL_PROXY_JWT_ISSUER)).is_err()
);
assert!(validate_upgrade_mode_jwt(&jwt_no_issuer, Some("nym-credential-proxy")).is_err());
// we don't care about issuer
assert!(validate_upgrade_mode_jwt(&jwt_no_issuer, None).is_ok());
+3 -8
View File
@@ -6,13 +6,8 @@ pub(crate) mod error;
pub(crate) mod jwt;
pub use attestation::{
UpgradeModeAttestation, generate_new_attestation, generate_new_attestation_with_starting_time,
UpgradeModeAttestation, attempt_retrieve, generate_new_attestation,
generate_new_attestation_with_starting_time,
};
pub use error::UpgradeModeCheckError;
pub use jwt::{
CREDENTIAL_PROXY_JWT_ISSUER, generate_jwt_for_upgrade_mode_attestation,
validate_upgrade_mode_jwt,
};
#[cfg(not(target_arch = "wasm32"))]
pub use attestation::attempt_retrieve_attestation;
pub use jwt::{generate_jwt_for_upgrade_mode_attestation, validate_upgrade_mode_jwt};
@@ -1,112 +1,35 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_credential_verification::upgrade_mode::UpgradeModeDetails;
use nym_credentials_interface::BandwidthCredential;
use std::cmp::max;
use std::net::IpAddr;
use nym_credentials_interface::CredentialSpendingData;
use crate::transceiver::PeerControllerTransceiver;
use nym_wireguard_private_metadata_shared::error::MetadataError;
use nym_wireguard_private_metadata_shared::interface::ResponseData;
// we need to be above MINIMUM_REMAINING_BANDWIDTH (500MB) plus we also have to trick the client
// its depletion is low enough to not require sending new tickets
const DEFAULT_WG_CLIENT_BANDWIDTH_THRESHOLD: i64 = 1024 * 1024 * 1024;
#[derive(Clone, axum::extract::FromRef)]
pub struct AppState {
transceiver: PeerControllerTransceiver,
#[from_ref(skip)]
upgrade_mode: UpgradeModeDetails,
}
impl AppState {
pub fn new(transceiver: PeerControllerTransceiver, upgrade_mode: UpgradeModeDetails) -> Self {
Self {
transceiver,
upgrade_mode,
}
pub fn new(transceiver: PeerControllerTransceiver) -> Self {
Self { transceiver }
}
fn upgrade_mode_bandwidth(&self, true_bandwidth: i64) -> i64 {
// if we're undergoing upgrade mode, we don't meter bandwidth,
// we simply return MAX of clients current bandwidth and minimum bandwidth before default
// client would have attempted to send new ticket (hopefully)
// the latter is to support older clients that will ignore `upgrade_mode` field in the response
// as they're not aware of its existence
max(DEFAULT_WG_CLIENT_BANDWIDTH_THRESHOLD, true_bandwidth)
}
pub async fn available_bandwidth(&self, ip: IpAddr) -> Result<ResponseData, MetadataError> {
let upgrade_mode = self.upgrade_mode.enabled();
let true_bandwidth = self.transceiver.query_bandwidth(ip).await?;
let available_bandwidth = if upgrade_mode {
self.upgrade_mode_bandwidth(true_bandwidth)
} else {
true_bandwidth
};
Ok(ResponseData::AvailableBandwidth {
amount: available_bandwidth,
upgrade_mode,
})
pub async fn available_bandwidth(&self, ip: IpAddr) -> Result<i64, MetadataError> {
self.transceiver.query_bandwidth(ip).await
}
// Top up with a credential and return the afterwards available bandwidth
pub async fn topup_bandwidth(
&self,
ip: IpAddr,
claim: Box<BandwidthCredential>,
) -> Result<ResponseData, MetadataError> {
match *claim {
BandwidthCredential::ZkNym(zk_nym) => {
// if we got zk-nym, we just try to verify it
let available_bandwidth = self.transceiver.topup_bandwidth(ip, zk_nym).await?;
// however, we still follow the same upgrade-mode logic,
// so that the client would not attempt to needlessly send more credentials
let upgrade_mode = self.upgrade_mode.enabled();
let available_bandwidth = if upgrade_mode {
self.upgrade_mode_bandwidth(available_bandwidth)
} else {
available_bandwidth
};
Ok(ResponseData::TopUpBandwidth {
available_bandwidth,
upgrade_mode,
})
}
BandwidthCredential::UpgradeModeJWT { token } => {
// if we're already in the upgrade mode, don't bother validating the token
if self.upgrade_mode.enabled() {
let true_bandwidth = self.transceiver.query_bandwidth(ip).await?;
return Ok(ResponseData::TopUpBandwidth {
available_bandwidth: self.upgrade_mode_bandwidth(true_bandwidth),
upgrade_mode: true,
});
}
// if the token is valid, try to check if we're behind
// and have to update our internal state
self.upgrade_mode
.try_enable_via_received_jwt(token)
.await
.map_err(|err| MetadataError::JWTVerification {
message: err.to_string(),
})?;
// if we didn't return an error, it means token got accepted
// and we have transitioned into the upgrade mode
let true_bandwidth = self.transceiver.query_bandwidth(ip).await?;
Ok(ResponseData::TopUpBandwidth {
available_bandwidth: self.upgrade_mode_bandwidth(true_bandwidth),
upgrade_mode: true,
})
}
}
credential: CredentialSpendingData,
) -> Result<i64, MetadataError> {
self.transceiver
.topup_bandwidth(ip, Box::new(credential))
.await
}
}
@@ -9,7 +9,8 @@ use axum::{
};
use nym_http_api_common::{FormattedResponse, OutputParams};
use nym_wireguard_private_metadata_shared::{
AxumErrorResponse, AxumResult, Construct, Extract, Request, Response, interface::RequestData,
AxumErrorResponse, AxumResult, Construct, Extract, Request, Response,
interface::{RequestData, ResponseData},
latest,
};
use tower_http::compression::CompressionLayer;
@@ -58,17 +59,20 @@ async fn available_bandwidth(
) -> AxumResult<FormattedResponse<Response>> {
let output = output.output.unwrap_or_default();
let (RequestData::AvailableBandwidth, version) =
let (RequestData::AvailableBandwidth(_), version) =
request.extract().map_err(AxumErrorResponse::bad_request)?
else {
return Err(AxumErrorResponse::bad_request("incorrect request type"));
};
let available_bandwidth_response = state
let available_bandwidth = state
.available_bandwidth(addr.ip())
.await
.map_err(AxumErrorResponse::bad_request)?;
let response = Response::construct(available_bandwidth_response, version)
.map_err(AxumErrorResponse::bad_request)?;
let response = Response::construct(
ResponseData::AvailableBandwidth(available_bandwidth),
version,
)
.map_err(AxumErrorResponse::bad_request)?;
Ok(output.to_response(response))
}
@@ -92,16 +96,16 @@ async fn topup_bandwidth(
) -> AxumResult<FormattedResponse<Response>> {
let output = output.output.unwrap_or_default();
let (RequestData::TopUpBandwidth { credential }, version) =
let (RequestData::TopUpBandwidth(credential), version) =
request.extract().map_err(AxumErrorResponse::bad_request)?
else {
return Err(AxumErrorResponse::bad_request("incorrect request type"));
};
let top_up_bandwidth_response = state
.topup_bandwidth(addr.ip(), credential)
let available_bandwidth = state
.topup_bandwidth(addr.ip(), *credential)
.await
.map_err(AxumErrorResponse::bad_request)?;
let response = Response::construct(top_up_bandwidth_response, version)
let response = Response::construct(ResponseData::TopUpBandwidth(available_bandwidth), version)
.map_err(AxumErrorResponse::bad_request)?;
Ok(output.to_response(response))
@@ -37,12 +37,12 @@ impl PeerControllerTransceiver {
})
}
pub async fn query_bandwidth(&self, ip: IpAddr) -> Result<i64, MetadataError> {
pub(crate) async fn query_bandwidth(&self, ip: IpAddr) -> Result<i64, MetadataError> {
Ok(self.get_client_bandwidth(ip).await?.available().await)
}
// Top up with a credential and return the afterwards available bandwidth
pub async fn topup_bandwidth(
pub(crate) async fn topup_bandwidth(
&self,
ip: IpAddr,
credential: Box<CredentialSpendingData>,
@@ -1,218 +0,0 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
/// A simple macro that given `TryFrom<&A> for B`, implements `TryFrom<A> for B`
/// using the former implementation
#[macro_export]
macro_rules! impl_tryfrom_ref {
($src:ty, $dst:ty, $err:ty) => {
impl TryFrom<$src> for $dst {
// can't use type Error = <Self as TryFrom<&$src>>::Error;
// due to lifetime interference within macros
type Error = $err;
fn try_from(value: $src) -> Result<Self, Self::Error> {
<Self as TryFrom<&$src>>::try_from(&value)
}
}
};
}
/// A simple macro that implements all required variants of `TryFrom`
/// between particular versioned `VersionedRequest` and given request variant
/// using default bincode serializer
#[macro_export]
macro_rules! impl_default_bincode_request_query_conversions {
// limitation of macros - need to pass the same underlying type twice,
// once as pattern and once as expression
($top_req_type:ty, $inner_req_type:ty, $query_type_pat:pat, $query_type_expr:expr) => {
$crate::impl_query_conversions!(
$crate::Request,
$top_req_type,
$inner_req_type,
$query_type_pat,
$query_type_expr
);
};
}
/// A simple macro that implements all required variants of `TryFrom`
/// between particular versioned `VersionedResponse` and given response variant
/// using default bincode serializer
#[macro_export]
macro_rules! impl_default_bincode_response_query_conversions {
// limitation of macros - need to pass the same underlying type twice,
// once as pattern and once as expression
($top_resp_type:ty, $inner_resp_type:ty, $query_type_pat:pat, $query_type_expr:expr) => {
$crate::impl_query_conversions!(
$crate::Response,
$top_resp_type,
$inner_resp_type,
$query_type_pat,
$query_type_expr
);
};
}
/// A simple macro that implements all required variants of `TryFrom`
/// between [crate::models::Request] and corresponding versioned `VersionedRequest`
/// using default bincode serializer
#[macro_export]
macro_rules! impl_default_bincode_request_conversions {
($req_type:ty, $version:expr) => {
$crate::impl_versioned_conversions!($crate::Request, $req_type, $version);
};
}
/// A simple macro that implements all required variants of `TryFrom`
/// between [crate::models::Response] and corresponding versioned `VersionedResponse`
/// using default bincode serializer
#[macro_export]
macro_rules! impl_default_bincode_response_conversions {
($req_type:ty, $version:expr) => {
$crate::impl_versioned_conversions!($crate::Response, $req_type, $version);
};
}
#[macro_export]
macro_rules! impl_versioned_conversions {
(
// is it Request or Response
$main_type_ty:ty,
// e.g. VersionedResponse
$top_type:ty,
// request/response version type
$version:expr
) => {
impl TryFrom<&$top_type> for $main_type_ty {
type Error = $crate::models::error::Error;
fn try_from(value: &$top_type) -> Result<Self, Self::Error> {
use ::bincode::Options;
let data = $crate::make_bincode_serializer().serialize(value)?;
Ok(<$main_type_ty>::new($version, data))
}
}
// automatically generate `impl TryFrom<$top_type> for $main_type`
$crate::impl_tryfrom_ref!($top_type, $main_type_ty, $crate::models::error::Error);
impl TryFrom<&$main_type_ty> for $top_type {
type Error = $crate::models::error::Error;
fn try_from(value: &$main_type_ty) -> Result<Self, Self::Error> {
use ::bincode::Options;
if value.version != $version {
return Err($crate::models::error::Error::InvalidVersion {
source_version: value.version,
target_version: $version,
});
}
Ok($crate::make_bincode_serializer().deserialize(&value.inner)?)
}
}
// automatically generate `impl TryFrom<$main_type> for $top_type`
$crate::impl_tryfrom_ref!($main_type_ty, $top_type, $crate::models::error::Error);
};
}
#[macro_export]
macro_rules! impl_query_conversions {
// limitation of macros - need to pass the same underlying type twice,
// once as pattern and once as expression
(
// is it Request or Response
$main_type:ty,
// e.g. VersionedResponse
$top_type:ty,
// e.g. InnerTopUpResponse
$inner_type:ty,
// e.g. QueryType::TopUpBandwidth,
$query_type_pat:pat,
// e.g. QueryType::TopUpBandwidth,
$query_type_expr:expr
) => {
// conversion from the versioned type into the particular typ,
// e.g. TryFrom<&VersionedResponse> for InnerTopUpResponse
impl TryFrom<&$top_type> for $inner_type {
type Error = $crate::models::error::Error;
fn try_from(value: &$top_type) -> Result<Self, Self::Error> {
use ::bincode::Options;
match value.query_type {
$query_type_pat => {
Ok($crate::make_bincode_serializer().deserialize(&value.inner)?)
}
other => Err($crate::models::error::Error::InvalidQueryType {
source_query_type: other.to_string(),
target_query_type: stringify!($query_type_pat).to_string(),
}),
}
}
}
// implementation of conversion without the referenced type, i.e.
// e.g. TryFrom<VersionedResponse> for InnerTopUpResponse
$crate::impl_tryfrom_ref!($top_type, $inner_type, $crate::models::error::Error);
// conversion back from the particular type into the versioned type, i.e.
// e.g. TryFrom<&InnerTopUpResponse> for VersionedResponse
impl TryFrom<&$inner_type> for $top_type {
type Error = $crate::models::error::Error;
fn try_from(value: &$inner_type) -> Result<Self, Self::Error> {
use ::bincode::Options;
Ok(Self {
query_type: $query_type_expr,
inner: $crate::make_bincode_serializer().serialize(value)?,
})
}
}
// implementation of conversion without the referenced type, i.e.
// e.g. TryFrom<InnerTopUpResponse> for VersionedResponse
$crate::impl_tryfrom_ref!($inner_type, $top_type, $crate::models::error::Error);
// conversion from the'main' type (Request/Response) into the particular type
// e.g. TryFrom<&Response> for InnerTopUpResponse
impl TryFrom<&$main_type> for $inner_type {
type Error = $crate::error::MetadataError;
fn try_from(value: &$main_type) -> Result<Self, Self::Error> {
<$top_type>::try_from(value)?.try_into().map_err(
|err: $crate::models::error::Error| $crate::error::MetadataError::Models {
message: err.to_string(),
},
)
}
}
// implementation of conversion without the referenced type, i.e.
// e.g. TryFrom<Response> for InnerTopUpResponse
$crate::impl_tryfrom_ref!($main_type, $inner_type, $crate::error::MetadataError);
// conversion from the particular type into the 'main' type (Request/Response)
// e.g. TryFrom<&InnerTopUpResponse> for Response
impl TryFrom<&$inner_type> for $main_type {
type Error = $crate::error::MetadataError;
fn try_from(value: &$inner_type) -> Result<Self, Self::Error> {
<$top_type>::try_from(value)?.try_into().map_err(
|err: $crate::models::error::Error| $crate::error::MetadataError::Models {
message: err.to_string(),
},
)
}
}
// implementation of conversion without the referenced type, i.e.
// e.g. TryFrom<InnerTopUpResponse> for Response
$crate::impl_tryfrom_ref!($inner_type, $main_type, $crate::error::MetadataError);
};
}
@@ -17,9 +17,6 @@ pub enum MetadataError {
#[error("Credential verification error: {message}")]
CredentialVerification { message: String },
#[error("Upgrade Mode JWT verification error: {message}")]
JWTVerification { message: String },
}
impl From<crate::models::error::Error> for MetadataError {
@@ -1,7 +1,6 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub(crate) mod conversion_helpers;
pub mod error;
mod models;
pub mod routes;
@@ -10,7 +9,7 @@ pub mod routes;
pub use models::v0;
pub use models::{
AxumErrorResponse, AxumResult, Construct, ErrorResponse, Extract, Request, Response, Version,
error::Error as ModelError, interface, latest, v1, v2,
error::Error as ModelError, interface, latest, v1,
};
fn make_bincode_serializer() -> impl bincode::Options {
@@ -15,7 +15,7 @@ pub enum Error {
},
#[error(
"trying to deserialize from query type {source_query_type} into query type {target_query_type}"
"trying to deserialize from query type {source_query_type} query type {target_query_type}"
)]
InvalidQueryType {
source_query_type: String,
@@ -1,25 +1,25 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_credentials_interface::BandwidthCredential;
use nym_credentials_interface::CredentialSpendingData;
#[cfg(feature = "testing")]
use crate::models::v0;
use crate::models::{Construct, Extract, Request, Response, Version, v1, v2};
use crate::models::{Construct, Extract, Request, Response, Version, v1};
pub enum RequestData {
AvailableBandwidth,
TopUpBandwidth {
credential: Box<BandwidthCredential>,
},
AvailableBandwidth(()),
TopUpBandwidth(Box<CredentialSpendingData>),
}
impl From<super::latest::interface::RequestData> for RequestData {
fn from(value: super::latest::interface::RequestData) -> Self {
match value {
super::latest::interface::RequestData::AvailableBandwidth => Self::AvailableBandwidth,
super::latest::interface::RequestData::TopUpBandwidth { credential } => {
Self::TopUpBandwidth { credential }
super::latest::interface::RequestData::AvailableBandwidth(inner) => {
Self::AvailableBandwidth(inner)
}
super::latest::interface::RequestData::TopUpBandwidth(credential_spending_data) => {
Self::TopUpBandwidth(credential_spending_data)
}
}
}
@@ -28,8 +28,10 @@ impl From<super::latest::interface::RequestData> for RequestData {
impl From<RequestData> for super::latest::interface::RequestData {
fn from(value: RequestData) -> Self {
match value {
RequestData::AvailableBandwidth => Self::AvailableBandwidth,
RequestData::TopUpBandwidth { credential } => Self::TopUpBandwidth { credential },
RequestData::AvailableBandwidth(inner) => Self::AvailableBandwidth(inner),
RequestData::TopUpBandwidth(credential_spending_data) => {
Self::TopUpBandwidth(credential_spending_data)
}
}
}
}
@@ -37,20 +39,12 @@ impl From<RequestData> for super::latest::interface::RequestData {
impl From<super::latest::interface::ResponseData> for ResponseData {
fn from(value: super::latest::interface::ResponseData) -> Self {
match value {
super::latest::interface::ResponseData::AvailableBandwidth {
amount,
upgrade_mode,
} => Self::AvailableBandwidth {
amount,
upgrade_mode,
},
super::latest::interface::ResponseData::TopUpBandwidth {
available_bandwidth,
upgrade_mode,
} => Self::TopUpBandwidth {
available_bandwidth,
upgrade_mode,
},
super::latest::interface::ResponseData::AvailableBandwidth(inner) => {
Self::AvailableBandwidth(inner)
}
super::latest::interface::ResponseData::TopUpBandwidth(credential_spending_data) => {
Self::TopUpBandwidth(credential_spending_data)
}
}
}
}
@@ -58,20 +52,10 @@ impl From<super::latest::interface::ResponseData> for ResponseData {
impl From<ResponseData> for super::latest::interface::ResponseData {
fn from(value: ResponseData) -> Self {
match value {
ResponseData::AvailableBandwidth {
amount,
upgrade_mode,
} => Self::AvailableBandwidth {
amount,
upgrade_mode,
},
ResponseData::TopUpBandwidth {
available_bandwidth,
upgrade_mode,
} => Self::TopUpBandwidth {
available_bandwidth,
upgrade_mode,
},
ResponseData::AvailableBandwidth(inner) => Self::AvailableBandwidth(inner),
ResponseData::TopUpBandwidth(credential_spending_data) => {
Self::TopUpBandwidth(credential_spending_data)
}
}
}
}
@@ -81,26 +65,13 @@ impl Construct<RequestData> for Request {
match version {
#[cfg(feature = "testing")]
Version::V0 => {
// attempt to go through conversion chain for `info`: v2 => v1 => v0
let v2_info = v2::interface::RequestData::from(info);
let v1_info = v1::interface::RequestData::try_from(v2_info)?;
let v0_info = v0::interface::RequestData::try_from(v1_info)?;
let versioned_request = v0::VersionedRequest::construct(v0_info, version)?;
let translate_info = super::latest::interface::RequestData::from(info);
let downgrade_info = v0::interface::RequestData::try_from(translate_info)?;
let versioned_request = v0::VersionedRequest::construct(downgrade_info, version)?;
Ok(versioned_request.try_into()?)
}
Version::V1 => {
// attempt to go through conversion chain for `info`: v2 => v1
let v2_info = v2::interface::RequestData::from(info);
let v1_info = v1::interface::RequestData::try_from(v2_info)?;
let versioned_request = v1::VersionedRequest::construct(v1_info, version)?;
Ok(versioned_request.try_into()?)
}
Version::V2 => {
let v2_info = v2::interface::RequestData::from(info);
let versioned_request = v2::VersionedRequest::construct(v2_info, version)?;
let versioned_request = v1::VersionedRequest::construct(info.into(), version)?;
Ok(versioned_request.try_into()?)
}
}
@@ -113,42 +84,24 @@ impl Extract<RequestData> for Request {
#[cfg(feature = "testing")]
super::Version::V0 => {
let versioned_request = v0::VersionedRequest::try_from(self.clone())?;
let (extracted_v0_info, version) = versioned_request.extract()?;
let (request, version) = versioned_request.extract()?;
let v1_info = v1::interface::RequestData::try_from(extracted_v0_info)?;
let v2_info = v2::interface::RequestData::try_from(v1_info)?;
let upgrade_request = super::latest::interface::RequestData::try_from(request)?;
let request_data = RequestData::from(v2_info);
Ok((request_data, version))
Ok((upgrade_request.into(), version))
}
super::Version::V1 => {
let versioned_request = v1::VersionedRequest::try_from(self)?;
let (extracted_v1_info, version) = versioned_request.extract()?;
let v2_info = v2::interface::RequestData::try_from(extracted_v1_info)?;
let request_data = RequestData::from(v2_info);
Ok((request_data, version))
}
super::Version::V2 => {
let versioned_request = v2::VersionedRequest::try_from(self)?;
let (extracted_v2_info, version) = versioned_request.extract()?;
let request_data = RequestData::from(extracted_v2_info);
Ok((request_data, version))
let versioned_request = v1::VersionedRequest::try_from(self.clone())?;
let (extracted, version) = versioned_request.extract()?;
Ok((extracted.into(), version))
}
}
}
}
pub enum ResponseData {
AvailableBandwidth {
amount: i64,
upgrade_mode: bool,
},
TopUpBandwidth {
available_bandwidth: i64,
upgrade_mode: bool,
},
AvailableBandwidth(i64),
TopUpBandwidth(i64),
}
impl Construct<ResponseData> for Response {
@@ -156,26 +109,14 @@ impl Construct<ResponseData> for Response {
match version {
#[cfg(feature = "testing")]
super::Version::V0 => {
// attempt to go through conversion chain for `info`: v2 => v1 => v0
let v2_info = v2::interface::ResponseData::from(info);
let v1_info = v1::interface::ResponseData::try_from(v2_info)?;
let v0_info = v0::interface::ResponseData::try_from(v1_info)?;
let versioned_response = v0::VersionedResponse::construct(v0_info, version)?;
let translate_response = super::latest::interface::ResponseData::from(info);
let downgrade_response = v0::interface::ResponseData::try_from(translate_response)?;
let versioned_response =
v0::VersionedResponse::construct(downgrade_response, version)?;
Ok(versioned_response.try_into()?)
}
Version::V1 => {
// attempt to go through conversion chain for `info`: v2 => v1
let v2_info = v2::interface::ResponseData::from(info);
let v1_info = v1::interface::ResponseData::try_from(v2_info)?;
let versioned_response = v1::VersionedResponse::construct(v1_info, version)?;
Ok(versioned_response.try_into()?)
}
Version::V2 => {
let v2_info = v2::interface::ResponseData::from(info);
let versioned_response = v2::VersionedResponse::construct(v2_info, version)?;
let versioned_response = v1::VersionedResponse::construct(info.into(), version)?;
Ok(versioned_response.try_into()?)
}
}
@@ -188,27 +129,16 @@ impl Extract<ResponseData> for Response {
#[cfg(feature = "testing")]
super::Version::V0 => {
let versioned_response = v0::VersionedResponse::try_from(self.clone())?;
let (extracted_v0_info, version) = versioned_response.extract()?;
let v1_info = v1::interface::ResponseData::try_from(extracted_v0_info)?;
let v2_info = v2::interface::ResponseData::try_from(v1_info)?;
let (response, version) = versioned_response.extract()?;
let response_data = ResponseData::from(v2_info);
Ok((response_data, version))
let upgrade_response = super::latest::interface::ResponseData::try_from(response)?;
Ok((upgrade_response.into(), version))
}
super::Version::V1 => {
let versioned_response = v1::VersionedResponse::try_from(self.clone())?;
let (extracted_v1_info, version) = versioned_response.extract()?;
let v2_info = v2::interface::ResponseData::try_from(extracted_v1_info)?;
let response_data = ResponseData::from(v2_info);
Ok((response_data, version))
}
super::Version::V2 => {
let versioned_response = v2::VersionedResponse::try_from(self.clone())?;
let (extracted_v2_info, version) = versioned_response.extract()?;
let response_data = ResponseData::from(extracted_v2_info);
Ok((response_data, version))
let (extracted, version) = versioned_response.extract()?;
Ok((extracted.into(), version))
}
}
}
@@ -14,10 +14,7 @@ pub mod interface;
pub mod v0; // dummy version, only for filling boilerplate code for update/downgrade and testing
pub mod v1;
// adds upgrade mode information to bandwidth response
pub mod v2;
pub use v2 as latest;
pub use v1 as latest;
use crate::models::error::Error;
@@ -27,7 +24,6 @@ pub enum Version {
/// only used for testing purposes, don't include it in your matching arms
V0,
V1,
V2,
}
impl From<u64> for Version {
@@ -39,7 +35,6 @@ impl From<u64> for Version {
match value {
0 => zero_version,
1 => Version::V1,
2 => Version::V2,
_ => latest::VERSION, // if unknown, it means we're behind, so we can use the latest we know about
}
}
@@ -52,7 +47,6 @@ impl From<Version> for u64 {
#[cfg(feature = "testing")]
Version::V0 => 0,
Version::V1 => 1,
Version::V2 => 2,
}
}
}
@@ -63,24 +57,12 @@ pub struct Request {
pub(crate) inner: Vec<u8>,
}
impl Request {
pub fn new(version: Version, inner: Vec<u8>) -> Self {
Request { version, inner }
}
}
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
#[derive(Clone, Serialize, Deserialize, ToSchema)]
pub struct Response {
pub version: Version,
pub(crate) inner: Vec<u8>,
}
impl Response {
pub fn new(version: Version, inner: Vec<u8>) -> Self {
Response { version, inner }
}
}
pub trait Extract<T> {
fn extract(&self) -> Result<(T, Version), Error>;
}
@@ -1,29 +1,66 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use bincode::Options;
use serde::{Deserialize, Serialize};
use super::super::{QueryType, VersionedRequest};
use crate::impl_default_bincode_request_query_conversions;
use crate::{make_bincode_serializer, models::Request};
use super::super::{Error, QueryType, VersionedRequest};
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct InnerAvailableBandwidthRequest {}
// Implements:
// - TryFrom<&VersionedRequest> for InnerTopUpRequest
// - TryFrom<VersionedRequest> for InnerTopUpRequest
// - TryFrom<&InnerTopUpRequest> for VersionedRequest
// - TryFrom<InnerTopUpRequest> for VersionedRequest
// - TryFrom<&Request> for InnerAvailableBandwidthRequest
// - TryFrom<Request> for InnerAvailableBandwidthRequest
// - TryFrom<&InnerTopUpRequest> for Request
// - TryFrom<InnerTopUpRequest> for Request
impl_default_bincode_request_query_conversions!(
VersionedRequest,
InnerAvailableBandwidthRequest,
QueryType::AvailableBandwidth,
QueryType::AvailableBandwidth
);
impl TryFrom<VersionedRequest> for InnerAvailableBandwidthRequest {
type Error = Error;
fn try_from(value: VersionedRequest) -> Result<Self, Self::Error> {
match value.query_type {
QueryType::AvailableBandwidth => {
Ok(make_bincode_serializer().deserialize(&value.inner)?)
}
QueryType::TopupBandwidth => Err(Error::InvalidQueryType {
source_query_type: value.query_type.to_string(),
target_query_type: QueryType::AvailableBandwidth.to_string(),
}),
}
}
}
impl TryFrom<InnerAvailableBandwidthRequest> for VersionedRequest {
type Error = Error;
fn try_from(value: InnerAvailableBandwidthRequest) -> Result<Self, Self::Error> {
Ok(Self {
query_type: QueryType::AvailableBandwidth,
inner: make_bincode_serializer().serialize(&value)?,
})
}
}
impl TryFrom<Request> for InnerAvailableBandwidthRequest {
type Error = crate::error::MetadataError;
fn try_from(value: Request) -> Result<Self, Self::Error> {
VersionedRequest::try_from(value)?
.try_into()
.map_err(|err: Error| crate::error::MetadataError::Models {
message: err.to_string(),
})
}
}
impl TryFrom<InnerAvailableBandwidthRequest> for Request {
type Error = crate::error::MetadataError;
fn try_from(value: InnerAvailableBandwidthRequest) -> Result<Self, Self::Error> {
VersionedRequest::try_from(value)?
.try_into()
.map_err(|err: Error| crate::error::MetadataError::Models {
message: err.to_string(),
})
}
}
#[cfg(test)]
mod tests {
@@ -1,30 +1,66 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use bincode::Options;
use serde::{Deserialize, Serialize};
use crate::impl_default_bincode_response_query_conversions;
use crate::{make_bincode_serializer, models::Response};
use super::super::{QueryType, VersionedResponse};
use super::super::{Error, QueryType, VersionedResponse};
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct InnerAvailableBandwidthResponse {}
// Implements:
// - TryFrom<&VersionedResponse> for InnerAvailableBandwidthResponse
// - TryFrom<VersionedResponse> for InnerAvailableBandwidthResponse
// - TryFrom<&InnerAvailableBandwidthResponse> for VersionedResponse
// - TryFrom<InnerAvailableBandwidthResponse> for VersionedResponse
// - TryFrom<&Response> for InnerAvailableBandwidthResponse
// - TryFrom<Response> for InnerAvailableBandwidthResponse
// - TryFrom<&InnerAvailableBandwidthResponse> for Response
// - TryFrom<InnerAvailableBandwidthResponse> for Response
impl_default_bincode_response_query_conversions!(
VersionedResponse,
InnerAvailableBandwidthResponse,
QueryType::AvailableBandwidth,
QueryType::AvailableBandwidth
);
impl TryFrom<VersionedResponse> for InnerAvailableBandwidthResponse {
type Error = Error;
fn try_from(value: VersionedResponse) -> Result<Self, Self::Error> {
match value.query_type {
QueryType::AvailableBandwidth => {
Ok(make_bincode_serializer().deserialize(&value.inner)?)
}
QueryType::TopupBandwidth => Err(Error::InvalidQueryType {
source_query_type: value.query_type.to_string(),
target_query_type: QueryType::AvailableBandwidth.to_string(),
}),
}
}
}
impl TryFrom<InnerAvailableBandwidthResponse> for VersionedResponse {
type Error = Error;
fn try_from(value: InnerAvailableBandwidthResponse) -> Result<Self, Self::Error> {
Ok(Self {
query_type: QueryType::AvailableBandwidth,
inner: make_bincode_serializer().serialize(&value)?,
})
}
}
impl TryFrom<Response> for InnerAvailableBandwidthResponse {
type Error = crate::error::MetadataError;
fn try_from(value: Response) -> Result<Self, Self::Error> {
VersionedResponse::try_from(value)?
.try_into()
.map_err(|err: Error| crate::error::MetadataError::Models {
message: err.to_string(),
})
}
}
impl TryFrom<InnerAvailableBandwidthResponse> for Response {
type Error = crate::error::MetadataError;
fn try_from(value: InnerAvailableBandwidthResponse) -> Result<Self, Self::Error> {
VersionedResponse::try_from(value)?
.try_into()
.map_err(|err: Error| crate::error::MetadataError::Models {
message: err.to_string(),
})
}
}
#[cfg(test)]
mod tests {
@@ -9,7 +9,6 @@ use super::{
topup_bandwidth::{request::InnerTopUpRequest, response::InnerTopUpResponse},
};
use crate::models::{Construct, Extract, Version, error::Error};
use crate::{Request, Response};
#[derive(Debug, Clone, PartialEq)]
pub enum RequestData {
@@ -36,11 +35,11 @@ impl Extract<RequestData> for VersionedRequest {
fn extract(&self) -> Result<(RequestData, Version), Error> {
match self.query_type {
QueryType::AvailableBandwidth => {
let _req = InnerAvailableBandwidthRequest::try_from(self)?;
let _req = InnerAvailableBandwidthRequest::try_from(self.clone())?;
Ok((RequestData::AvailableBandwidth(()), VERSION))
}
QueryType::TopUpBandwidth => {
let _req = InnerTopUpRequest::try_from(self)?;
QueryType::TopupBandwidth => {
let _req = InnerTopUpRequest::try_from(self.clone())?;
Ok((RequestData::TopUpBandwidth(()), VERSION))
}
}
@@ -62,46 +61,13 @@ impl Extract<ResponseData> for VersionedResponse {
fn extract(&self) -> Result<(ResponseData, Version), Error> {
match self.query_type {
QueryType::AvailableBandwidth => {
let _resp = InnerAvailableBandwidthResponse::try_from(self)?;
let _resp = InnerAvailableBandwidthResponse::try_from(self.clone())?;
Ok((ResponseData::AvailableBandwidth(()), VERSION))
}
QueryType::TopUpBandwidth => {
let _resp = InnerTopUpResponse::try_from(self)?;
QueryType::TopupBandwidth => {
let _resp = InnerTopUpResponse::try_from(self.clone())?;
Ok((ResponseData::TopUpBandwidth(()), VERSION))
}
}
}
}
#[cfg(feature = "testing")]
impl Extract<RequestData> for Request {
fn extract(&self) -> Result<(RequestData, Version), Error> {
match self.version {
Version::V0 => {
let versioned_request = VersionedRequest::try_from(self)?;
versioned_request.extract()
}
_ => Err(Error::UpdateNotPossible {
from: self.version,
to: VERSION,
}),
}
}
}
#[cfg(feature = "testing")]
impl Construct<ResponseData> for Response {
fn construct(info: ResponseData, version: Version) -> Result<Self, Error> {
match version {
Version::V0 => {
let translate_response = info;
let versioned_response = VersionedResponse::construct(translate_response, version)?;
Ok(versioned_response.try_into()?)
}
_ => Err(Error::DowngradeNotPossible {
from: version,
to: VERSION,
}),
}
}
}

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