Compare commits

..

143 Commits

Author SHA1 Message Date
dynco-nym 660463908d Expand /v3/nym-nodes with geodata (#5686)
* Expand /v3/nym-nodes
- includes node description and geodata
- expanded scope of included geodata

* Fetch geodata for all nodes

* Bump package version
2025-04-10 21:12:33 +02:00
dependabot[bot] 0be844e015 build(deps): bump crossbeam-channel from 0.5.14 to 0.5.15 (#5702) 2025-04-10 20:06:50 +02:00
Yana Matrosova efa6e7d7c7 Merge pull request #5669 from nymtech/yana/explorer-caching
Yana/explorer caching
2025-04-10 18:41:31 +03:00
dynco-nym 33c783bb7c Bump package version 2025-04-10 17:22:21 +02:00
Bogdan-Ștefan Neacşu 16059211b9 Add contains ticketbook data db query (#5670)
* Add contains ticketbook data db query

* Fix clippy

* Use exists for better performance
2025-04-10 18:21:50 +03:00
Yana bb6c920767 fix build 2025-04-10 17:24:40 +03:00
Yana 8c4df963c9 Fix switch button on mobile header 2025-04-10 17:23:04 +03:00
Yana af737596ca Fix tooltip 2025-04-10 16:50:45 +03:00
Jędrzej Stuczyński af2c4f50b6 Feature/updated sphinx payload keys (#5698)
* removed support for legacy packet types from NymCodec

I think nodes had plenty of time to upgrade given versioned variant was introduced in 2022

* temp: use local sphinx packet for development

* introduce new messages that use more efficient reply surbs encoding

* checks for incorrect encoding

* generate correct message depending on config value

* fixed current packet version

* made packet type selection configurable

* updated sphinx packet crate to the published version

* fixed wasm build

* fixes in outfox due to sphinx api changes

* additional tests

* clippy

* fixed log/tracing import
2025-04-10 13:43:29 +01:00
Jędrzej Stuczyński 02ed64557d chore: removed old explorer-api (#5701) 2025-04-10 11:26:24 +01:00
Yana 38dabd8d0d fix build 2025-04-10 11:38:43 +03:00
Yana d9de5cfa33 Rebase onto develop, fix lint error 2025-04-10 11:29:13 +03:00
Yana bdfbfde463 add fixes 2025-04-10 11:14:58 +03:00
Yana 5179f38ad2 fix build 2025-04-10 11:14:54 +03:00
Yana f4e9abcd22 fix build 2025-04-10 11:14:54 +03:00
Yana 46ebd84b02 Dark theme 2025-04-10 11:14:54 +03:00
Yana d8d2f99a18 clean up, optimise homepage layout 2025-04-10 11:14:49 +03:00
Yana cd3ec5f3bd comment out for good 2025-04-10 11:14:49 +03:00
Yana 32a16ef025 add getOfflineSigner dependency to the callback fn 2025-04-10 11:14:48 +03:00
Yana 6af4e44f55 comment out unused option 2025-04-10 11:14:48 +03:00
Yana 3cddc594b4 Add fees "auto" to the cosmWasm client with offline signer 2025-04-10 11:14:48 +03:00
Yana d11aaed392 Another try to fix gas fee for redeem rewards 2025-04-10 11:14:48 +03:00
Yana 1bead28150 Try fix gas fee for redeem all rewards 2025-04-10 11:14:48 +03:00
Yana 735bed5cd7 fix buggy data on landing page graphs 2025-04-10 11:14:48 +03:00
Yana 12e0d34885 fix build 2025-04-10 11:14:48 +03:00
Yana 43af3b8a3b fix build 2025-04-10 11:14:48 +03:00
Yana 8ff96b11c9 Refactor Node page to accept identity_key in params 2025-04-10 11:14:48 +03:00
Yana df453158d6 fix build 2025-04-10 11:14:36 +03:00
Yana abeeadb661 fix build 2025-04-10 11:14:36 +03:00
Yana 752fe7fa0f fix build 2025-04-10 11:14:36 +03:00
Yana c5ec682088 fix build 2025-04-10 11:14:36 +03:00
Yana 58a569cd26 clean up 2025-04-10 11:14:36 +03:00
Yana 2e767a2586 Another try 2025-04-10 11:14:35 +03:00
Yana dc772d8759 clean up 2025-04-10 11:14:35 +03:00
Yana 9e70c7a32d Add caching on tanstack queries 2025-04-10 11:14:35 +03:00
Jon Häggblad ba5e86e842 Bump the nym-vpn deb metapackage to 1.0 (#5697) 2025-04-09 18:07:55 +02:00
Tommy Verrall b7313656e9 Merge pull request #5699 from nymtech/fix/sign-in-page-wallet
Allow copy and paste on logins fields for the wallet
2025-04-09 15:15:28 +01:00
Tommy Verrall 2eb695088f linting and yarn
- modify log screen
2025-04-09 16:14:11 +02:00
Tommy Verrall eb612d47c0 Allow copy and paste on logins
- allow shell open for linking - some platforms it's not working as expected
2025-04-09 14:55:12 +02:00
benedetta davico 2ba7b26e5d Merge pull request #5659 from nymtech/benny/revamp-api-tests
Adding fresh nym-api tests and workflow
2025-04-09 13:13:24 +02:00
Tommy Verrall 4cd0f7b56f Merge pull request #5687 from nymtech/feature/test-v2
Tauri V2 - Wallet Migration
2025-04-09 12:09:41 +01:00
Tommy Verrall 600bf42a95 conflicts 2025-04-09 12:51:31 +02:00
Tommy Verrall 748e3e4248 fix remaining lint and cargo clippy errors 2025-04-09 12:46:03 +02:00
dependabot[bot] 8cf1b6427a build(deps): bump tokio from 1.44.0 to 1.44.2 in /nym-wallet (#5694) 2025-04-09 12:40:37 +02:00
Tommy Verrall 7a888c6fdf fix wallet ci 2025-04-09 12:17:02 +02:00
Tommy Verrall 9a9bb89d89 fix lint again 2025-04-09 12:14:49 +02:00
Tommy Verrall 4cc14ddcc4 cargo fmt
- hopefully the last
2025-04-09 11:53:47 +02:00
Tommy Verrall 2dbf9d97cb yarn lint fix 2025-04-09 11:47:10 +02:00
Tommy Verrall 91b6f3cc3e paste not working from currency form
- removed shellhelper too
2025-04-09 11:22:09 +02:00
Tommy Verrall 84cccffcbd Fix PR comments
- removed the shell open in favour of the tauri plugin for opening
- cleaned up some code
- added a few packages
2025-04-09 10:27:25 +02:00
benedetta davico 7de346cf89 add env 2025-04-09 10:07:55 +02:00
benedetta davico d6c40aee01 add env 2025-04-09 10:07:49 +02:00
Tommy Verrall af16b3f059 first code review comments 2025-04-09 09:12:21 +02:00
Tommy Verrall b1cde0716e Fix delegation list 2025-04-08 20:10:05 +02:00
Tommy Verrall 45bcdb03d8 fix delegations page - after overflow 2025-04-08 19:29:32 +02:00
benedetta davico 0841b8701d change path 2025-04-08 19:04:47 +02:00
benedetta davico 7ae228d8f4 change path 2025-04-08 19:03:58 +02:00
benedetta davico 916d33c8c0 Update nym-api-integration-tests.yml 2025-04-08 18:55:57 +02:00
benedetta davico 9b4b2d1a46 Update Makefile 2025-04-08 18:55:25 +02:00
benedettadavico aef0a52c4b fix workflow typo 2025-04-08 18:49:40 +02:00
Tommy Verrall 44682b5ef0 removed duplicates and reverted back to 1.2.18 as a version 2025-04-08 18:46:52 +02:00
benedettadavico f282ffd8a6 remove missed line 2025-04-08 18:42:44 +02:00
benedettadavico dfbeb8b1f8 reformatting, tidying up 2025-04-08 18:38:18 +02:00
benedettadavico fc06fe39a2 more clippy fixes 2025-04-08 17:43:36 +02:00
benedettadavico caa94c142f fix clippy 2025-04-08 17:15:47 +02:00
benedettadavico 1a5c54084e fmt 2025-04-08 17:01:46 +02:00
benedettadavico 49d203e18d better response handling 2025-04-08 16:59:30 +02:00
Tommy Verrall 51c9b012e2 merge conflicts 2025-04-08 16:50:45 +02:00
Tommy Verrall 50b1175622 Merge branch 'develop' into feature/test-v2 2025-04-08 16:40:00 +02:00
Tommy Verrall 29ee5984fb fix all workflows 2025-04-08 16:21:15 +02:00
Tommy Verrall e542b25ffc bump to version 2.0.0
- it's a big release therefore let's semver it correctly
2025-04-08 16:03:36 +02:00
Tommy Verrall 516d3f04cf No need to publish these to the build server just use the artifacts 2025-04-08 15:57:20 +02:00
benedetta davico 9225e0a630 Merge branch 'develop' into benny/revamp-api-tests 2025-04-08 15:43:31 +02:00
Tommy Verrall 08c09781c7 Fixing all yarn lint errors 2025-04-08 14:36:42 +02:00
benedettadavico 36a4d96f34 cargo fmt 2025-04-08 13:48:42 +02:00
benedettadavico 139c911350 use env var for api url and make asserts uniform 2025-04-08 13:40:17 +02:00
Tommy Verrall c92de832e4 remove arg 2025-04-08 12:12:13 +02:00
Tommy Verrall d9d62195cb try again 2025-04-08 12:05:28 +02:00
Tommy Verrall da9115d51b format 2025-04-08 11:58:48 +02:00
benedettadavico bfddc1e4c1 clean up the test dir 2025-04-08 11:56:45 +02:00
benedettadavico 080d75204e first commit to cleaning up nym-api tests 2025-04-08 11:56:45 +02:00
Tommy Verrall 1367cad99d another attempt 2025-04-08 11:54:47 +02:00
Tommy Verrall 4f6d65ab95 revert previous add more logging 2025-04-08 11:50:27 +02:00
Tommy Verrall 4292d8ac03 update windows build 2025-04-08 11:40:50 +02:00
Tommy Verrall dcb6de2421 tauri path 2025-04-08 11:22:38 +02:00
Tommy Verrall 1f5ed41bb3 correct tauri path 2025-04-08 11:21:53 +02:00
Tommy Verrall 091e98aa74 attempt windows build 2025-04-08 11:14:19 +02:00
Jędrzej Stuczyński 0e38126fc5 Feature/replay protection (#5682)
* remove old packettype + fix: apply routing filter BEFORE delaying

* updated sphinx crate for allow usage of reply tags

* full pipeline for placeholder checking of packet replay

* replay protection with batched insertion

* running background task for clearing/flushing the BF

* allow disabling the replay detection + cleanup

* allow unwrap in bench code
2025-04-08 09:50:25 +01:00
Tommy Verrall ecbe192a88 try 22.04 2025-04-08 10:20:50 +02:00
Tommy Verrall f0ee49788c test old runner first 2025-04-08 10:18:32 +02:00
Tommy Verrall d2ff3cb88d fix app deps 2025-04-08 10:15:27 +02:00
Tommy Verrall 873d15a5e1 update runner platform 2025-04-08 10:13:30 +02:00
Tommy Verrall 53792cc839 Update runner for linux 2025-04-08 10:00:22 +02:00
Tommy Verrall 415ef1bf13 attempt to push to ci 2025-04-08 09:53:35 +02:00
benedettadavico edfe29b738 bump versions 2025-04-08 09:46:48 +02:00
Tommy Verrall a4f6426bf9 Update account display 2025-04-08 09:32:46 +02:00
dependabot[bot] 0870911b3c build(deps): bump tokio from 1.44.1 to 1.44.2 (#5693) 2025-04-08 08:01:40 +02:00
Tommy Verrall 9f23887cc0 Input fields 2025-04-07 20:07:15 +02:00
Tommy Verrall 8ab269fa05 Jazz up receive modal 2025-04-07 17:16:22 +02:00
Tommy Verrall 7b75f22a8e Remove legacy 2025-04-07 15:27:54 +02:00
Tommy Verrall ca0449e03d Init clipboard manager 2025-04-07 14:22:55 +02:00
Tommy Verrall 224e63d275 Rename and update 2025-04-07 11:37:22 +02:00
Tommy Verrall 3d77283056 Add pruning warning errors 2025-04-07 10:29:03 +02:00
Tommy Verrall 7cc473005b More permissions errors
- fix more perm errors
- enabled the version in the wallet
2025-04-07 10:09:47 +02:00
Tommy Verrall f874284850 - Update beyond tauri v2
- use the latest and greatest
- fixed links to use the command shell
- app version changes, need to be fixed to allow the auto updater too work
2025-04-04 18:47:35 +02:00
Tommy Verrall 7b6077ba64 update to log in
- next up fix hyperlinks
2025-04-04 13:56:20 +02:00
dynco-nym 0d4188785b Fetch geodata for all nodes 2025-04-04 13:00:25 +02:00
Jędrzej Stuczyński 12026305d5 chore: clippy for 1.86 (#5685)
* chore: clippy for 1.86

* clippy inside wallet
2025-04-04 10:43:21 +01:00
import this 257e36ddcb Featrure: Bash scripts to init and configure VMs conveniently and update docs (#5681)
* create VM init and config scripts

* PR ready for review

* address review comments

* syntax fix
2025-04-04 09:17:30 +00:00
Jon Häggblad ad81c6d27e Move all workflows on ubuntu-20 to ubuntu-22 (#5455)
* Move all workflows on ubuntu-20 to ubuntu-22

* Add missing -y for installing rsync in ci-docs

* Install rsync with --yes

* Switch two jobs to github hosted free tier runners
2025-04-04 11:05:02 +02:00
Tommy Verrall ae52b7b71f Merge pull request #5483 from nymtech/dependabot/npm_and_yarn/elliptic-6.6.1
build(deps): bump elliptic from 6.5.5 to 6.6.1
2025-04-04 08:48:29 +00:00
Tommy Verrall 854d3cceac Merge pull request #5665 from nymtech/dependabot/npm_and_yarn/sdk/typescript/tests/integration-tests/mix-fetch/multi-eeeba236cb
build(deps): bump tar-fs and puppeteer in /sdk/typescript/tests/integration-tests/mix-fetch
2025-04-04 08:45:49 +00:00
benedetta davico 1bdf867fdb Merge pull request #5684 from nymtech/benny/fix-mac-build
Fix the mac build of the wallet
2025-04-04 10:45:05 +02:00
benedetta davico 5a88b5b6a8 upper case 2025-04-04 10:39:40 +02:00
benedettadavico 5ab4d3c22c bump wallet version 2025-04-04 10:12:00 +02:00
benedetta davico b529883b81 Update package.json 2025-04-04 10:11:03 +02:00
benedetta davico 07f624660c Update Cargo.toml 2025-04-04 10:10:37 +02:00
benedetta davico 71f8e736d8 Update publish-nym-wallet-macos.yml 2025-04-04 10:09:58 +02:00
benedetta davico d3573e78e0 Merge pull request #5677 from nymtech/benny/update-node-versions
Update node versions in CI
2025-04-04 09:26:47 +02:00
dynco-nym 86c05267c2 Expand /v3/nym-nodes
- includes node description and geodata
- expanded scope of included geodata
2025-04-03 22:45:28 +02:00
import this e6e74855af [DOCs/operators]: Release notes 2025.6-chuckles (#5678)
* release notes finished

* add explorer info
2025-04-02 14:28:31 +00:00
Tommy Verrall b4865520a4 Revert "add the base points back in"
This reverts commit 400aa6ba6d.
2025-04-02 15:36:49 +02:00
Tommy Verrall f52ebfb9c3 Merge remote-tracking branch 'origin/feature/test-v2' into feature/test-v2 2025-04-02 15:34:12 +02:00
Tommy Verrall 6ca2a3c539 migrate to v2
- lots to check and do
2025-04-02 15:22:27 +02:00
Tommy Verrall 717c9066d6 Merge remote-tracking branch 'origin/feature/test-v2' into feature/test-v2 2025-04-02 15:18:26 +02:00
Tommy Verrall 2760a17323 add the base points back in
- now i've reverted back to the original two here, as the compiler is failing around `tauri::api::path` however, looking into the new design for the path resolver in tower this tasks, requires pratically changing the whole wallet_strorage and config set up
- it seems pretty straight forward https://v2.tauri.app/start/migrate/from-tauri-1/#migrate-path-to-tauri-manager - however, I would need a second set of eyes on this
2025-04-02 15:18:11 +02:00
Tommy Verrall 4e9f1bc0ed migrate to v2
- lots to check and do
2025-04-02 15:17:44 +02:00
Tommy Verrall d35023d14b Merge remote-tracking branch 'origin/feature/test-v2' into feature/test-v2 2025-04-02 15:14:02 +02:00
Tommy Verrall 400aa6ba6d add the base points back in
- now i've reverted back to the original two here, as the compiler is failing around `tauri::api::path` however, looking into the new design for the path resolver in tower this tasks, requires pratically changing the whole wallet_strorage and config set up
- it seems pretty straight forward https://v2.tauri.app/start/migrate/from-tauri-1/#migrate-path-to-tauri-manager - however, I would need a second set of eyes on this
2025-04-02 15:13:42 +02:00
Tommy Verrall 2ba74ae120 migrate to v2
- lots to check and do
2025-04-02 15:13:42 +02:00
fmtabbara 99d8aebea9 fix build 2025-04-02 13:08:16 +01:00
benedettadavico 0bde4dfc84 update to node v20 2025-04-02 11:41:27 +02:00
benedetta davico a56068e28a Merge pull request #5671 from nymtech/release/2025.6-chuckles
Merge release/2025.6-chuckles into develop
2025-04-02 10:35:16 +02:00
Tommy Verrall 9a4293a5b9 add the base points back in
- now i've reverted back to the original two here, as the compiler is failing around `tauri::api::path` however, looking into the new design for the path resolver in tower this tasks, requires pratically changing the whole wallet_strorage and config set up
- it seems pretty straight forward https://v2.tauri.app/start/migrate/from-tauri-1/#migrate-path-to-tauri-manager - however, I would need a second set of eyes on this
2025-04-02 08:53:40 +02:00
Tommy Verrall cdddb44099 migrate to v2
- lots to check and do
2025-04-01 17:06:21 +02:00
Jack Wampler d309b44ad7 Minor fixes involving key cloning and hashing (#5664) 2025-04-01 08:34:39 -06:00
Jędrzej Stuczyński d062524d32 mix throughput tester (#5661)
* wip: sending with single client

* tag packets to measure latency

* constantly logging rates

* concurrency

* adjusting some values

* write results to files upon completion
2025-03-31 15:57:24 +01:00
dependabot[bot] 89eea3100e build(deps): bump the patch-updates group across 1 directory with 8 updates (#5668)
Bumps the patch-updates group with 7 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [clap](https://github.com/clap-rs/clap) | `4.5.32` | `4.5.34` |
| [clap_complete](https://github.com/clap-rs/clap) | `4.5.46` | `4.5.47` |
| [once_cell](https://github.com/matklad/once_cell) | `1.21.1` | `1.21.3` |
| [reqwest](https://github.com/seanmonstar/reqwest) | `0.12.4` | `0.12.15` |
| [tempfile](https://github.com/Stebalien/tempfile) | `3.19.0` | `3.19.1` |
| [time](https://github.com/time-rs/time) | `0.3.39` | `0.3.41` |
| [uniffi](https://github.com/mozilla/uniffi-rs) | `0.29.0` | `0.29.1` |



Updates `clap` from 4.5.32 to 4.5.34
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.32...clap_complete-v4.5.34)

Updates `clap_complete` from 4.5.46 to 4.5.47
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.46...clap_complete-v4.5.47)

Updates `once_cell` from 1.21.1 to 1.21.3
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.21.1...v1.21.3)

Updates `reqwest` from 0.12.4 to 0.12.15
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.12.4...v0.12.15)

Updates `tempfile` from 3.19.0 to 3.19.1
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stebalien/tempfile/compare/v3.19.0...v3.19.1)

Updates `time` from 0.3.39 to 0.3.41
- [Release notes](https://github.com/time-rs/time/releases)
- [Changelog](https://github.com/time-rs/time/blob/main/CHANGELOG.md)
- [Commits](https://github.com/time-rs/time/compare/v0.3.39...v0.3.41)

Updates `uniffi` from 0.29.0 to 0.29.1
- [Changelog](https://github.com/mozilla/uniffi-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mozilla/uniffi-rs/compare/v0.29.0...v0.29.1)

Updates `uniffi_build` from 0.29.0 to 0.29.1
- [Changelog](https://github.com/mozilla/uniffi-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mozilla/uniffi-rs/compare/v0.29.0...v0.29.1)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: clap_complete
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: once_cell
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: reqwest
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: tempfile
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: time
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: uniffi
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: uniffi_build
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-31 15:51:06 +02:00
Jon Häggblad d893c806c2 Update log crate (#5667) 2025-03-31 14:44:47 +02:00
dependabot[bot] 7846058802 build(deps): bump blake3 from 1.6.1 to 1.7.0 (#5658)
Bumps [blake3](https://github.com/BLAKE3-team/BLAKE3) from 1.6.1 to 1.7.0.
- [Release notes](https://github.com/BLAKE3-team/BLAKE3/releases)
- [Commits](https://github.com/BLAKE3-team/BLAKE3/compare/1.6.1...1.7.0)

---
updated-dependencies:
- dependency-name: blake3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-31 13:57:26 +02:00
dependabot[bot] 3c98c9021e build(deps): bump tar-fs and puppeteer
Bumps [tar-fs](https://github.com/mafintosh/tar-fs) to 3.0.8 and updates ancestor dependency [puppeteer](https://github.com/puppeteer/puppeteer). These dependencies need to be updated together.


Updates `tar-fs` from 3.0.4 to 3.0.8
- [Commits](https://github.com/mafintosh/tar-fs/compare/v3.0.4...v3.0.8)

Updates `puppeteer` from 21.1.1 to 24.4.0
- [Release notes](https://github.com/puppeteer/puppeteer/releases)
- [Changelog](https://github.com/puppeteer/puppeteer/blob/main/CHANGELOG.md)
- [Commits](https://github.com/puppeteer/puppeteer/compare/puppeteer-v21.1.1...puppeteer-v24.4.0)

---
updated-dependencies:
- dependency-name: tar-fs
  dependency-type: indirect
- dependency-name: puppeteer
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-28 22:42:24 +00:00
import this f705884a53 [DOCs/operators]: fix typo and add url (#5662) 2025-03-27 11:50:16 +00:00
dependabot[bot] 84b6068ac9 build(deps): bump elliptic from 6.5.5 to 6.6.1
Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.5 to 6.6.1.
- [Commits](https://github.com/indutny/elliptic/compare/v6.5.5...v6.6.1)

---
updated-dependencies:
- dependency-name: elliptic
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-17 23:26:04 +00:00
458 changed files with 22974 additions and 25585 deletions
+2 -2
View File
@@ -5,7 +5,7 @@ on:
jobs:
build:
runs-on: arc-ubuntu-20.04
runs-on: arc-ubuntu-22.04
defaults:
run:
working-directory: documentation/docs
@@ -18,7 +18,7 @@ jobs:
- name: Install Python3 modules
run: sudo pip3 install pandas tabulate
- name: Install rsync
run: sudo apt-get install rsync
run: sudo apt-get install -y rsync
- uses: rlespinasse/github-slug-action@v3.x
- name: Setup pnpm
uses: pnpm/action-setup@v4.0.0
@@ -33,7 +33,7 @@ jobs:
strategy:
fail-fast: false
matrix:
platform: [arc-ubuntu-20.04]
platform: [arc-ubuntu-22.04]
runs-on: ${{ matrix.platform }}
steps:
+2 -2
View File
@@ -10,7 +10,7 @@ on:
jobs:
build:
runs-on: arc-ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Install rsync
@@ -19,7 +19,7 @@ jobs:
- uses: rlespinasse/github-slug-action@v3.x
- uses: actions/setup-node@v4
with:
node-version: 18
node-version: 20
- name: Setup yarn
run: npm install -g yarn
- name: Build
@@ -21,7 +21,7 @@ jobs:
strategy:
fail-fast: false
matrix:
platform: [ arc-ubuntu-20.04 ]
platform: [ arc-ubuntu-22.04 ]
runs-on: ${{ matrix.platform }}
env:
+3 -1
View File
@@ -38,7 +38,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ arc-ubuntu-20.04, custom-windows-11, custom-runner-mac-m1 ]
os: [ arc-ubuntu-22.04, custom-windows-11, custom-runner-mac-m1 ]
runs-on: ${{ matrix.os }}
env:
CARGO_TERM_COLOR: always
@@ -102,6 +102,8 @@ jobs:
- name: Run all tests
if: contains(matrix.os, 'ubuntu')
uses: actions-rs/cargo@v1
env:
NYM_API: https://sandbox-nym-api1.nymtech.net/api
with:
command: test
args: --workspace
+1 -1
View File
@@ -11,7 +11,7 @@ on:
jobs:
check-schema:
name: Generate and check schema
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
env:
CARGO_TERM_COLOR: always
steps:
@@ -11,7 +11,7 @@ jobs:
strategy:
fail-fast: false
matrix:
platform: [ arc-ubuntu-20.04 ]
platform: [ arc-ubuntu-22.04 ]
runs-on: ${{ matrix.platform }}
env:
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
build:
# since it's going to be compiled into wasm, there's absolutely
# no point in running CI on different OS-es
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
env:
CARGO_TERM_COLOR: always
steps:
+2 -2
View File
@@ -10,7 +10,7 @@ on:
jobs:
build:
runs-on: arc-ubuntu-20.04
runs-on: arc-ubuntu-22.04
env:
RUSTUP_PERMIT_COPY_RENAME: 1
defaults:
@@ -25,7 +25,7 @@ jobs:
- name: Install Python3 modules
run: sudo pip3 install pandas tabulate
- name: Install rsync
run: sudo apt-get install rsync
run: sudo apt-get install -y rsync
- uses: rlespinasse/github-slug-action@v3.x
- name: Setup pnpm
uses: pnpm/action-setup@v4.0.0
+2 -2
View File
@@ -15,7 +15,7 @@ on:
jobs:
build:
runs-on: arc-ubuntu-20.04
runs-on: ubuntu-22.04
env:
RUSTUP_PERMIT_COPY_RENAME: 1
steps:
@@ -24,7 +24,7 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: 18
node-version: 20
- name: Setup yarn
run: npm install -g yarn
@@ -1,92 +0,0 @@
name: ci-nym-network-explorer
on:
workflow_dispatch:
push:
paths:
- 'explorer/**'
- '.github/workflows/ci-nym-network-explorer.yml'
defaults:
run:
working-directory: explorer
jobs:
build:
runs-on: custom-linux
steps:
- uses: actions/checkout@v4
- name: Install rsync
run: sudo apt-get install rsync
continue-on-error: true
- uses: rlespinasse/github-slug-action@v3.x
- uses: actions/setup-node@v4
with:
node-version: 18
- name: Setup yarn
run: npm install -g yarn
continue-on-error: true
- name: Build shared packages
run: cd .. && yarn && yarn build
- name: Set environment from the example
run: cp .env.prod .env
# - run: yarn test
# continue-on-error: true
- run: yarn && yarn build
continue-on-error: true
- run: yarn storybook:build
name: Build storybook
- name: Deploy branch to CI www
continue-on-error: true
uses: easingthemes/ssh-deploy@main
env:
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
ARGS: "-rltgoDzvO --delete"
SOURCE: "explorer/dist/"
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/network-explorer-${{ env.GITHUB_REF_SLUG }}
EXCLUDE: "/dist/, /node_modules/"
- name: Deploy storybook to CI www
continue-on-error: true
uses: easingthemes/ssh-deploy@main
env:
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
ARGS: "-rltgoDzvO --delete"
SOURCE: "explorer/storybook-static/"
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/ne-sb-${{ env.GITHUB_REF_SLUG }}
EXCLUDE: "/dist/, /node_modules/"
- name: Matrix - Node Install
run: npm install
working-directory: .github/workflows/support-files
- name: Matrix - Send Notification
env:
NYM_NOTIFICATION_KIND: network-explorer
NYM_PROJECT_NAME: "Network Explorer"
NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}"
NYM_CI_WWW_LOCATION: "network-explorer-${{ env.GITHUB_REF_SLUG }}"
NYM_CI_WWW_LOCATION_STORYBOOK: "ne-sb-${{ env.GITHUB_REF_SLUG }}"
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
GIT_BRANCH: "${GITHUB_REF##*/}"
IS_SUCCESS: "${{ job.status == 'success' }}"
MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}"
MATRIX_ROOM: "${{ secrets.MATRIX_ROOM }}"
MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}"
MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}"
MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}"
uses: docker://keybaseio/client:stable-node
with:
args: .github/workflows/support-files/notifications/entry_point.sh
- name: Deploy
if: github.event_name == 'workflow_dispatch'
uses: easingthemes/ssh-deploy@main
env:
SSH_PRIVATE_KEY: ${{ secrets.CD_PROD_NE_SSH_PRIVATE_KEY }}
ARGS: "-rltgoDzvO --delete"
SOURCE: "explorer/dist/"
REMOTE_HOST: ${{ secrets.CD_PROD_NE_REMOTE_HOST }}
REMOTE_USER: ${{ secrets.CD_PROD_NE_REMOTE_USER }}
TARGET: ${{ secrets.CD_PROD_NE_REMOTE_TARGET }}
EXCLUDE: "/dist/, /node_modules/"
+7 -3
View File
@@ -11,13 +11,17 @@ on:
jobs:
build:
runs-on: arc-ubuntu-20.04
runs-on: arc-ubuntu-22.04
env:
CARGO_TERM_COLOR: always
RUSTUP_PERMIT_COPY_RENAME: 1
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools
- name: Install system dependencies
run: |
sudo apt-get update && sudo apt-get install -y libdbus-1-dev libmnl-dev libnftnl-dev \
libwebkit2gtk-4.1-dev build-essential curl wget libssl-dev jq \
libgtk-3-dev squashfs-tools libayatana-appindicator3-dev make libfuse2 unzip librsvg2-dev file \
libsoup-3.0-dev libjavascriptcoregtk-4.1-dev
continue-on-error: true
- name: Check out repository code
+2 -2
View File
@@ -11,7 +11,7 @@ on:
jobs:
wasm:
runs-on: arc-ubuntu-20.04
runs-on: arc-ubuntu-22.04
env:
CARGO_TERM_COLOR: always
RUSTUP_PERMIT_COPY_RENAME: 1
@@ -20,7 +20,7 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: 18
node-version: 20
- uses: actions-rs/toolchain@v1
with:
+4 -4
View File
@@ -11,7 +11,7 @@ jobs:
fail-fast: false
matrix:
rust: [stable, beta]
os: [ubuntu-20.04, windows-latest, macos-latest]
os: [ubuntu-22.04, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
env:
CARGO_TERM_COLOR: always
@@ -23,7 +23,7 @@ jobs:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get install -y build-essential curl wget libssl-dev libudev-dev squashfs-tools protobuf-compiler
if: matrix.os == 'ubuntu-20.04'
if: matrix.os == 'ubuntu-22.04'
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
@@ -59,7 +59,7 @@ jobs:
# To avoid running out of disk space, skip generating debug symbols
- name: Set debug to false (unix)
if: matrix.os == 'ubuntu-20.04' || matrix.os == 'macos-latest'
if: matrix.os == 'ubuntu-22.04' || matrix.os == 'macos-latest'
run: |
sed -i.bak 's/\[profile.dev\]/\[profile.dev\]\ndebug = false/' Cargo.toml
git diff
@@ -106,7 +106,7 @@ jobs:
uses: actions/setup-node@v4
if: env.WORKFLOW_CONCLUSION == 'failure'
with:
node-version: 18
node-version: 20
- name: Matrix - Node Install
if: env.WORKFLOW_CONCLUSION == 'failure'
run: npm install
@@ -10,7 +10,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-20.04, macos-latest, windows-latest]
os: [ubuntu-22.04, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
env:
CARGO_TERM_COLOR: always
@@ -22,7 +22,7 @@ jobs:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get install -y libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools
if: matrix.os == 'ubuntu-20.04'
if: matrix.os == 'ubuntu-22.04'
- name: Install rust toolchain
uses: actions-rs/toolchain@v1
@@ -68,7 +68,7 @@ jobs:
uses: actions/setup-node@v4
if: env.WORKFLOW_CONCLUSION == 'failure'
with:
node-version: 18
node-version: 20
- name: Matrix - Node Install
if: env.WORKFLOW_CONCLUSION == 'failure'
run: npm install
+2 -2
View File
@@ -5,7 +5,7 @@ on:
- cron: '5 9 * * *'
jobs:
cargo-deny:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- name: Checkout repository code
uses: actions/checkout@v4
@@ -38,7 +38,7 @@ jobs:
- name: install npm
uses: actions/setup-node@v4
with:
node-version: 18
node-version: 20
- name: Matrix - Node Install
run: npm install
working-directory: .github/workflows/support-files
@@ -0,0 +1,47 @@
name: Integration Tests
on:
pull_request:
paths:
- "nym-api/**"
- "tests/**"
workflow_dispatch:
jobs:
integration-tests:
runs-on: ubuntu-latest
env:
API_BASE_URL: http://localhost:8000
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Install dependencies
run: sudo apt-get update && sudo apt-get install -y pkg-config libssl-dev
- name: Build nym-api
run: cargo build --package nym-api
- name: Run nym-api in the background
run: |
./target/debug/nym-api &
- name: Wait for nym-api to come alive
run: |
for i in {1..20}; do
curl -sSf http://localhost:8000/v1/status/config-score-details && break
echo "Waiting for nym-api to start..."
sleep 2
done
- name: Run integration tests
env:
NYM_API: https://sandbox-nym-api1.nymtech.net/api
run: cargo test --test public-api-tests -- --nocapture
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
strategy:
fail-fast: false
matrix:
platform: [custom-ubuntu-20.04]
platform: [custom-ubuntu-22.04]
runs-on: ${{ matrix.platform }}
outputs:
+41 -27
View File
@@ -18,11 +18,7 @@ jobs:
runs-on: ${{ matrix.platform }}
outputs:
release_id: ${{ steps.create-release.outputs.id }}
release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].created_at }}
version: ${{ steps.release-info.outputs.version }}
filename: ${{ steps.release-info.outputs.filename }}
file_hash: ${{ steps.release-info.outputs.file_hash }}
release_tag: ${{ github.ref_name }}
steps:
- uses: actions/checkout@v4
@@ -31,10 +27,18 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: 21
- name: Install Rust stable
uses: actions-rs/toolchain@v1
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
- name: Add Rust target for x86_64-apple-darwin
run: rustup target add x86_64-apple-darwin
- name: Set Cargo build target to x86_64
run: echo "CARGO_BUILD_TARGET=x86_64-apple-darwin" >> $GITHUB_ENV
- name: Install the Apple developer certificate for code signing
env:
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
@@ -64,11 +68,19 @@ jobs:
fileName: '.env'
encodedString: ${{ secrets.WALLET_ADMIN_ADDRESS }}
- name: Yarn cache clean
shell: bash
run: cd .. && yarn cache clean
- name: Install project dependencies
shell: bash
run: cd .. && yarn --network-timeout 100000
- name: Install app dependencies and build it
- name: Yarn build
shell: bash
run: cd .. && yarn build
- name: Install dependencies and build it
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ENABLE_CODE_SIGNING: ${{ secrets.APPLE_CERTIFICATE }}
@@ -78,46 +90,48 @@ jobs:
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_IDENTITY_ID }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
run: yarn && yarn build
# Tauri v2 specific environment variables
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
TAURI_NOTARIZATION_USERNAME: ${{ secrets.APPLE_ID }}
TAURI_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
TAURI_NOTARIZATION_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
yarn build-macx86
- name: Create app tarball
run: |
# Navigate to where the app bundle is and create the tarball
cd target/x86_64-apple-darwin/release/bundle/macos
echo "Creating tarball from app bundle"
tar -czf nym-wallet.app.tar.gz NymWallet.app
cd -
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: nym-wallet.app.tar.gz
path: nym-wallet/target/release/bundle/macos/nym-wallet.app.tar.gz
path: nym-wallet/target/x86_64-apple-darwin/release/bundle/macos/nym-wallet.app.tar.gz
retention-days: 5
- name: Clean up keychain
if: ${{ always() }}
run: |
security delete-keychain $RUNNER_TEMP/app-signing.keychain-db
- id: create-release
name: Upload to release based on tag name
uses: softprops/action-gh-release@v2
if: github.event_name == 'release'
with:
files: |
nym-wallet/target/release/bundle/dmg/*.dmg
nym-wallet/target/release/bundle/macos/*.app.tar.gz*
- name: Deploy artifacts to CI www
continue-on-error: true
uses: easingthemes/ssh-deploy@main
env:
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
ARGS: "-avzr"
SOURCE: "nym-wallet/target/release/bundle/macos/nym-wallet.app.tar.gz"
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/builds/${{ github.ref_name }}/nym-wallet
EXCLUDE: "/dist/, /node_modules/"
nym-wallet/target/x86_64-apple-darwin/release/bundle/dmg/*.dmg
nym-wallet/target/x86_64-apple-darwin/release/bundle/macos/*.app.tar.gz*
push-release-data:
if: ${{ (startsWith(github.ref, 'refs/tags/nym-wallet-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
uses: ./.github/workflows/release-calculate-hash.yml
needs: publish-tauri
with:
release_tag: ${{ github.ref_name }}
secrets: inherit
release_tag: ${{ needs.publish-tauri.outputs.release_tag || github.ref_name }}
secrets: inherit
+81 -42
View File
@@ -3,71 +3,108 @@ on:
workflow_dispatch:
release:
types: [created]
defaults:
run:
working-directory: nym-wallet
jobs:
publish-tauri:
if: ${{ (startsWith(github.ref, 'refs/tags/nym-wallet-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
strategy:
fail-fast: false
matrix:
platform: [custom-ubuntu-20.04]
platform: [ubuntu-22.04]
runs-on: ${{ matrix.platform }}
outputs:
release_id: ${{ steps.create-release.outputs.id }}
release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].created_at }}
version: ${{ steps.release-info.outputs.version }}
filename: ${{ steps.release-info.outputs.filename }}
file_hash: ${{ steps.release-info.outputs.file_hash }}
release_tag: ${{ github.ref_name }}
steps:
- uses: actions/checkout@v4
- name: Tauri dependencies
run: >
sudo apt-get update &&
sudo apt-get install -y webkit2gtk-4.0
continue-on-error: true
- name: Install system dependencies
run: |
sudo apt-get update && sudo apt-get install -y libdbus-1-dev libmnl-dev libnftnl-dev \
libwebkit2gtk-4.1-dev build-essential curl wget libssl-dev jq \
libgtk-3-dev squashfs-tools libayatana-appindicator3-dev make libfuse2 unzip librsvg2-dev file \
libsoup-3.0-dev libjavascriptcoregtk-4.1-dev
- name: Node
uses: actions/setup-node@v4
with:
node-version: 21
cache: 'yarn'
- name: Install Rust stable
uses: actions-rs/toolchain@v1
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
- name: Install project dependencies
shell: bash
run: cd .. && yarn --network-timeout 100000
- name: Install app dependencies
run: yarn
- name: Create env file
uses: timheuer/base64-to-file@v1.2
with:
fileName: '.env'
encodedString: ${{ secrets.WALLET_ADMIN_ADDRESS }}
- name: Build app
run: yarn build
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
- name: Check bundle directory
run: |
echo "Checking bundle directory structure"
ls -la target/release/bundle || echo "Bundle directory not found"
if [ -d "target/release/bundle/appimage" ]; then
echo "AppImage bundle directory exists, checking contents:"
ls -la target/release/bundle/appimage
else
echo "AppImage bundle directory not found, checking alternatives:"
find target/release/bundle -type d -name "*appimage*" -o -name "*AppImage*" || echo "No AppImage directories found"
find target/release/bundle -name "*.AppImage" -o -name "*.appimage" || echo "No AppImage files found"
fi
- name: Create AppImage tarball if needed
run: |
# Find the AppImage file
APPIMAGE_FILE=$(find target/release/bundle -name "*.AppImage" | head -n 1)
if [ -n "$APPIMAGE_FILE" ]; then
echo "Found AppImage file: $APPIMAGE_FILE"
APPIMAGE_DIR=$(dirname "$APPIMAGE_FILE")
APPIMAGE_NAME=$(basename "$APPIMAGE_FILE")
# Create tarball if it doesn't exist
if [ ! -f "${APPIMAGE_FILE}.tar.gz" ]; then
echo "Creating tarball for $APPIMAGE_NAME"
cd "$APPIMAGE_DIR"
tar -czf "${APPIMAGE_NAME}.tar.gz" "$APPIMAGE_NAME"
cd -
echo "Created tarball: ${APPIMAGE_FILE}.tar.gz"
else
echo "Tarball already exists: ${APPIMAGE_FILE}.tar.gz"
fi
else
echo "WARNING: No AppImage file found!"
fi
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: nym-wallet_1.0.0_amd64.AppImage.tar.gz
path: nym-wallet/target/release/bundle/appimage/nym-wallet*.AppImage.tar.gz
name: nym-wallet-appimage.tar.gz
path: |
nym-wallet/target/release/bundle/appimage/*.AppImage.tar.gz
nym-wallet/target/release/bundle/*/nym-wallet*.AppImage.tar.gz
retention-days: 30
- id: create-release
name: Upload to release based on tag name
uses: softprops/action-gh-release@v2
@@ -75,24 +112,26 @@ jobs:
with:
files: |
nym-wallet/target/release/bundle/appimage/*.AppImage
nym-wallet/target/release/bundle/appimage/*.AppImage.tar.gz*
- name: Deploy artifacts to CI www
continue-on-error: true
uses: easingthemes/ssh-deploy@main
env:
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
ARGS: "-avzr"
SOURCE: "nym-wallet/target/release/bundle/appimage/nym-wallet*.AppImage.tar.gz"
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/builds/${{ github.ref_name }}/nym-wallet
EXCLUDE: "/dist/, /node_modules/"
nym-wallet/target/release/bundle/appimage/*.AppImage.tar.gz
nym-wallet/target/release/bundle/*/nym-wallet*.AppImage
nym-wallet/target/release/bundle/*/nym-wallet*.AppImage.tar.gz
- name: Find AppImage tarball path for deployment
id: find-appimage
run: |
APPIMAGE_TARBALL=$(find target/release/bundle -name "*.AppImage.tar.gz" | head -n 1)
if [ -n "$APPIMAGE_TARBALL" ]; then
echo "Found AppImage tarball: $APPIMAGE_TARBALL"
echo "appimage_path=$APPIMAGE_TARBALL" >> $GITHUB_OUTPUT
else
echo "WARNING: No AppImage tarball found for deployment!"
echo "appimage_path=target/release/bundle/appimage/nym-wallet*.AppImage.tar.gz" >> $GITHUB_OUTPUT
fi
push-release-data:
if: ${{ (startsWith(github.ref, 'refs/tags/nym-wallet-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
uses: ./.github/workflows/release-calculate-hash.yml
needs: publish-tauri
with:
release_tag: ${{ github.ref_name }}
secrets: inherit
release_tag: ${{ needs.publish-tauri.outputs.release_tag || github.ref_name }}
secrets: inherit
+110 -61
View File
@@ -1,6 +1,12 @@
name: publish-nym-wallet-win11
on:
workflow_dispatch:
inputs:
sign:
description: "Sign this build using SSL.com. Signing is billed per signature so be careful"
required: false
type: boolean
default: true
release:
types: [created]
@@ -18,53 +24,61 @@ jobs:
runs-on: ${{ matrix.platform }}
outputs:
release_id: ${{ steps.create-release.outputs.id }}
release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].created_at }}
version: ${{ steps.release-info.outputs.version }}
filename: ${{ steps.release-info.outputs.filename }}
file_hash: ${{ steps.release-info.outputs.file_hash }}
release_tag: ${{ github.ref_name }}
steps:
- name: Clean up first
continue-on-error: true
working-directory: .
run: |
cd ..
del /s /q /A:H nym
rmdir /s /q nym
- uses: actions/checkout@v4
- name: Import signing certificate
env:
WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }}
WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
run: |
New-Item -ItemType directory -Path certificate
Set-Content -Path certificate/tempCert.txt -Value $env:WINDOWS_CERTIFICATE
certutil -decode certificate/tempCert.txt certificate/certificate.pfx
Remove-Item -path certificate -include tempCert.txt
Import-PfxCertificate -FilePath certificate/certificate.pfx -CertStoreLocation Cert:\CurrentUser\My -Password (ConvertTo-SecureString -String $env:WINDOWS_CERTIFICATE_PASSWORD -Force -AsPlainText)
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
- name: Setup MSBuild.exe
uses: microsoft/setup-msbuild@v2
- name: Node
uses: actions/setup-node@v4
with:
node-version: 21
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Create env file
uses: timheuer/base64-to-file@v1.2
with:
fileName: '.env'
encodedString: ${{ secrets.WALLET_ADMIN_ADDRESS }}
- name: Install Yarn
run: npm install -g yarn
- name: Download EV CodeSignTool from ssl.com
working-directory: nym-wallet/src-tauri
if: ${{ inputs.sign }}
shell: bash
run: |
curl -L0 https://www.ssl.com/download/codesigntool-for-linux-and-macos/ -o codesigntool.zip
unzip codesigntool.zip
- name: Get EV certificate credential id
working-directory: nym-wallet/src-tauri
if: ${{ inputs.sign }}
id: get_credential_ids
shell: bash
run: |
echo "SSL_COM_CREDENTIAL_ID=$(./CodeSignTool.sh get_credential_ids -username=${{ secrets.SSL_COM_USERNAME }} -password=${{ secrets.SSL_COM_PASSWORD }} | sed -n '1!p' | sed 's/- //')" >> "$GITHUB_OUTPUT"
- name: Add custom sign command to tauri.conf.json
working-directory: nym-wallet/src-tauri
if: ${{ inputs.sign }}
shell: bash
run: |
yq eval --inplace '.bundle.windows +=
{
"signCommand": {
"cmd": "C:\Program Files\Git\bin\bash.EXE",
"args": [
"/c/actions-runner/_work/nym/nym/nym-wallet/src-tauri/CodeSignTool.sh",
"sign",
"-username ${{ secrets.SSL_COM_USERNAME }}",
"-password ${{ secrets.SSL_COM_PASSWORD }}",
"-credential_id ${{ steps.get_credential_ids.outputs.SSL_COM_CREDENTIAL_ID }}",
"-totp_secret ${{ secrets.SSL_COM_TOTP_SECRET }}",
"-program_name NymWallet",
"-input_file_path",
"%1",
"-override"
]
}
}' tauri.conf.json
- name: Install project dependencies
shell: bash
run: cd .. && yarn --network-timeout 100000
@@ -77,18 +91,50 @@ jobs:
shell: bash
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ENABLE_CODE_SIGNING: ${{ secrets.WINDOWS_CERTIFICATE }}
WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }}
WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
run: yarn build
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
SSL_COM_USERNAME: ${{ inputs.sign && secrets.SSL_COM_USERNAME }}
SSL_COM_PASSWORD: ${{ inputs.sign && secrets.SSL_COM_PASSWORD }}
SSL_COM_CREDENTIAL_ID: ${{ inputs.sign && steps.get_credential_ids.outputs.SSL_COM_CREDENTIAL_ID }}
SSL_COM_TOTP_SECRET: ${{ inputs.sign && secrets.SSL_COM_TOTP_SECRET }}
run: |
echo "Starting build process..."
yarn build
- name: Check bundle directory
shell: bash
run: |
echo "Checking bundle directory structure"
# Check standard location
if [ -d "target/release/bundle" ]; then
echo "Found bundle directory at standard location"
ls -la target/release/bundle || echo "Failed to list bundle directory"
fi
# Check src-tauri location
if [ -d "src-tauri/target/release/bundle" ]; then
echo "Found bundle directory in src-tauri"
ls -la src-tauri/target/release/bundle || echo "Failed to list src-tauri bundle directory"
# Use this path for future steps
echo "BUNDLE_PATH=src-tauri/target/release/bundle" >> $GITHUB_ENV
else
echo "Using standard bundle path"
echo "BUNDLE_PATH=target/release/bundle" >> $GITHUB_ENV
fi
# Check for MSI files in any location
find . -name "*.msi" -type f
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: nym-wallet_1.0.0_x64_en-US.msi
path: nym-wallet/target/release/bundle/msi/nym-wallet_1.*.msi
name: nym-wallet.msi
path: |
nym-wallet/${{ env.BUNDLE_PATH }}/msi/*.msi
nym-wallet/${{ env.BUNDLE_PATH }}/*/nym-wallet*.msi
nym-wallet/src-tauri/target/release/bundle/msi/*.msi
retention-days: 30
- id: create-release
@@ -97,25 +143,28 @@ jobs:
if: github.event_name == 'release'
with:
files: |
nym-wallet/target/release/bundle/msi/*.msi
nym-wallet/target/release/bundle/msi/*.msi.zip*
- name: Deploy artifacts to CI www
continue-on-error: true
uses: easingthemes/ssh-deploy@main
env:
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
ARGS: "-avzr"
SOURCE: "nym-wallet/target/release/bundle/msi/nym-wallet_1.*.msi"
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/builds/${{ github.ref_name }}/nym-wallet
EXCLUDE: "/dist/, /node_modules/"
nym-wallet/${{ env.BUNDLE_PATH }}/msi/*.msi
nym-wallet/${{ env.BUNDLE_PATH }}/msi/*.msi.zip*
nym-wallet/${{ env.BUNDLE_PATH }}/*/nym-wallet*.msi
nym-wallet/src-tauri/target/release/bundle/msi/*.msi
- name: Find MSI path for deployment
id: find-msi
shell: bash
run: |
MSI_FILE=$(find . -name "*.msi" -type f | head -n 1)
if [ -n "$MSI_FILE" ]; then
echo "Found MSI file: $MSI_FILE"
echo "msi_path=$MSI_FILE" >> $GITHUB_OUTPUT
else
echo "WARNING: No MSI file found for deployment!"
echo "msi_path=${{ env.BUNDLE_PATH }}/msi/nym-wallet*.msi" >> $GITHUB_OUTPUT
fi
push-release-data:
if: ${{ (startsWith(github.ref, 'refs/tags/nym-wallet-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
uses: ./.github/workflows/release-calculate-hash.yml
needs: publish-tauri
with:
release_tag: ${{ github.ref_name }}
secrets: inherit
release_tag: ${{ needs.publish-tauri.outputs.release_tag || github.ref_name }}
secrets: inherit
@@ -12,7 +12,7 @@ on:
jobs:
build:
name: Build APK
runs-on: custom-ubuntu-20.04
runs-on: custom-ubuntu-22.04
env:
ANDROID_HOME: ${{ github.workspace }}/android-sdk
NDK_VERSION: 25.2.9519653
+2 -2
View File
@@ -4,14 +4,14 @@ on:
jobs:
publish:
runs-on: arc-ubuntu-20.04
runs-on: arc-ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Install Node
uses: actions/setup-node@v4
with:
node-version: 18
node-version: 20
registry-url: "https://registry.npmjs.org"
- name: Setup yarn
+1 -1
View File
@@ -23,7 +23,7 @@ jobs:
uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
node-version: 20
- uses: nymtech/nym/.github/actions/nym-hash-releases@develop
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Generated
+361 -142
View File
File diff suppressed because it is too large Load Diff
+14 -16
View File
@@ -39,7 +39,6 @@ members = [
"common/cosmwasm-smart-contracts/mixnet-contract",
"common/cosmwasm-smart-contracts/multisig-contract",
"common/cosmwasm-smart-contracts/vesting-contract",
"common/country-group",
"common/credential-storage",
"common/credential-utils",
"common/credential-verification",
@@ -97,9 +96,6 @@ members = [
"common/wireguard",
"common/wireguard-types",
"documentation/autodoc",
# "explorer-api",
# "explorer-api/explorer-api-requests",
# "explorer-api/explorer-client",
"gateway",
"integrations/bity",
"nym-api",
@@ -203,8 +199,8 @@ bincode = "1.3.3"
bip39 = { version = "2.0.0", features = ["zeroize"] }
bit-vec = "0.7.0" # can we unify those?
bitvec = "1.0.0"
blake3 = "1.6.1"
bloomfilter = "1.0.14"
blake3 = "1.7.0"
bloomfilter = "3.0.1"
bs58 = "0.5.1"
bytecodec = "0.4.15"
bytes = "1.10.1"
@@ -215,7 +211,7 @@ chacha20 = "0.9.0"
chacha20poly1305 = "0.10.1"
chrono = "0.4.40"
cipher = "0.4.3"
clap = "4.5.32"
clap = "4.5.34"
clap_complete = "4.5"
clap_complete_fig = "4.5"
colored = "2.2"
@@ -270,7 +266,6 @@ indicatif = "0.17.11"
inquire = "0.6.2"
ip_network = "0.4.1"
ipnetwork = "0.20"
isocountry = "0.3.2"
itertools = "0.14.0"
k256 = "0.13"
lazy_static = "1.5.0"
@@ -283,7 +278,7 @@ moka = { version = "0.12", features = ["future"] }
nix = "0.27.1"
notify = "5.1.0"
okapi = "0.7.0"
once_cell = "1.21.1"
once_cell = "1.21.3"
opentelemetry = "0.19.0"
opentelemetry-jaeger = "0.18.0"
parking_lot = "0.12.3"
@@ -302,10 +297,7 @@ rand_pcg = "0.3.1"
rand_seeder = "0.2.3"
rayon = "1.5.1"
regex = "1.10.6"
reqwest = { version = "0.12.4", default-features = false }
rocket = "0.5.0"
rocket_cors = "0.6.0"
rocket_okapi = "0.8.0"
reqwest = { version = "0.12.15", default-features = false }
rs_merkle = "1.5.0"
safer-ffi = "0.1.13"
schemars = "0.8.22"
@@ -320,7 +312,7 @@ serde_with = "3.9.0"
serde_yaml = "0.9.25"
sha2 = "0.10.8"
si-scale = "0.2.3"
sphinx-packet = "=0.3.2"
sphinx-packet = "=0.6.0"
sqlx = "0.7.4"
strum = "0.26"
strum_macros = "0.26"
@@ -331,7 +323,7 @@ tap = "1.0.1"
tar = "0.4.44"
tempfile = "3.19"
thiserror = "2.0"
time = "0.3.39"
time = "0.3.41"
tokio = "1.44"
tokio-postgres = "0.7"
tokio-stream = "0.1.17"
@@ -347,9 +339,10 @@ tracing-log = "0.2"
tracing-opentelemetry = "0.19.0"
tracing-subscriber = "0.3.19"
tracing-tree = "0.2.2"
tracing-indicatif = "0.3.9"
ts-rs = "10.1.0"
tungstenite = { version = "0.20.1", default-features = false }
uniffi = "0.29.0"
uniffi = "0.29.1"
uniffi_build = "0.29.0"
url = "2.5"
utoipa = "5.2"
@@ -409,6 +402,11 @@ wasm-bindgen-futures = "0.4.49"
wasmtimer = "0.4.1"
web-sys = "0.3.76"
# for local development:
#[patch.crates-io]
#sphinx-packet = { path = "../sphinx" }
# Profile settings for individual crates
# Compile-time verified queries do quite a bit of work at compile time. Incremental
+2 -1
View File
@@ -168,8 +168,9 @@ generate-typescript:
cd tools/ts-rs-cli && cargo run && cd ../..
yarn types:lint:fix
# Run the integration tests for public nym-api endpoints
run-api-tests:
cd nym-api/tests/functional_test && yarn test:qa
dotenv -f envs/sandbox.env -- cargo test --test public-api-tests
# Build debian package, and update PPA
deb-cli: build-nym-cli
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.52"
version = "1.1.53"
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.52"
version = "1.1.53"
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"
-1
View File
@@ -87,7 +87,6 @@ impl From<Init> for OverrideConfig {
use_anonymous_replies: init_config.use_reply_surbs,
fastmode: init_config.common_args.fastmode,
no_cover: init_config.common_args.no_cover,
geo_routing: None,
medium_toggle: false,
nyxd_urls: init_config.common_args.nyxd_urls,
enabled_credentials_mode: init_config.common_args.enabled_credentials_mode,
+1 -22
View File
@@ -16,8 +16,7 @@ use nym_bin_common::bin_info;
use nym_bin_common::completions::{fig_generate, ArgShell};
use nym_client_core::cli_helpers::CliClient;
use nym_client_core::client::base_client::storage::migration_helpers::v1_1_33;
use nym_client_core::client::topology_control::geo_aware_provider::CountryGroup;
use nym_client_core::config::{ForgetMe, GroupBy, TopologyStructure};
use nym_client_core::config::ForgetMe;
use nym_config::OptionalSet;
use nym_sphinx::addressing::Recipient;
use nym_sphinx::params::{PacketSize, PacketType};
@@ -107,7 +106,6 @@ pub(crate) struct OverrideConfig {
use_anonymous_replies: Option<bool>,
fastmode: bool,
no_cover: bool,
geo_routing: Option<CountryGroup>,
medium_toggle: bool,
nyxd_urls: Option<Vec<url::Url>>,
enabled_credentials_mode: Option<bool>,
@@ -138,21 +136,6 @@ pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config {
let secondary_packet_size = args.medium_toggle.then_some(PacketSize::ExtendedPacket16);
let no_per_hop_delays = args.medium_toggle;
let topology_structure = if args.medium_toggle {
// Use the location of the network-requester
let address = config
.core
.socks5
.provider_mix_address
.parse()
.expect("failed to parse provider mix address");
TopologyStructure::GeoAware(GroupBy::NymAddress(address))
} else if let Some(code) = args.geo_routing {
TopologyStructure::GeoAware(GroupBy::CountryGroup(code))
} else {
TopologyStructure::default()
};
let packet_type = if args.outfox {
PacketType::Outfox
} else {
@@ -176,10 +159,6 @@ pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config {
// NOTE: see comment above about the order of the other disble cover traffic config
.with_base(BaseClientConfig::with_disabled_cover_traffic, args.no_cover)
.with_base(BaseClientConfig::with_packet_type, packet_type)
.with_base(
BaseClientConfig::with_topology_structure,
topology_structure,
)
.with_base(BaseClientConfig::with_forget_me, args.forget_me)
.with_optional(Config::with_anonymous_replies, args.use_anonymous_replies)
.with_optional(Config::with_port, args.port)
-13
View File
@@ -6,7 +6,6 @@ use crate::commands::{override_config, OverrideConfig};
use clap::Args;
use nym_client_core::cli_helpers::client_run::CommonClientRunArgs;
use nym_client_core::client::base_client::storage::OnDiskPersistent;
use nym_client_core::client::topology_control::geo_aware_provider::CountryGroup;
use nym_socks5_client_core::NymClient;
use nym_sphinx::addressing::clients::Recipient;
use std::net::IpAddr;
@@ -37,10 +36,6 @@ pub(crate) struct Run {
#[clap(long)]
host: Option<IpAddr>,
/// Set geo-aware mixnode selection when sending mixnet traffic, for experiments only.
#[clap(long, hide = true, value_parser = validate_country_group, group="routing")]
geo_routing: Option<CountryGroup>,
/// Enable medium mixnet traffic, for experiments only.
/// This includes things like disabling cover traffic, no per hop delays, etc.
#[clap(long, hide = true)]
@@ -59,7 +54,6 @@ impl From<Run> for OverrideConfig {
use_anonymous_replies: run_config.use_anonymous_replies,
fastmode: run_config.common_args.fastmode,
no_cover: run_config.common_args.no_cover,
geo_routing: run_config.geo_routing,
medium_toggle: run_config.medium_toggle,
nyxd_urls: run_config.common_args.nyxd_urls,
enabled_credentials_mode: run_config.common_args.enabled_credentials_mode,
@@ -70,13 +64,6 @@ impl From<Run> for OverrideConfig {
}
}
fn validate_country_group(s: &str) -> Result<CountryGroup, String> {
match s.parse() {
Ok(cg) => Ok(cg),
Err(_) => Err(format!("failed to parse country group: {}", s)),
}
}
pub(crate) async fn execute(args: Run) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
eprintln!("Starting client {}...", args.common_args.id);
@@ -13,7 +13,7 @@ use std::{fmt, ops::Deref, str::FromStr};
#[cfg(feature = "verify")]
use hmac::{Hmac, Mac};
#[cfg(feature = "verify")]
use nym_crypto::asymmetric::encryption::PrivateKey;
use nym_crypto::asymmetric::encryption::{PrivateKey, PublicKey};
#[cfg(feature = "verify")]
use sha2::Sha256;
@@ -82,16 +82,14 @@ impl GatewayClient {
private_ip: IpAddr,
nonce: u64,
) -> Self {
// convert from 1.0 x25519-dalek private key into 2.0 x25519-dalek
#[allow(clippy::expect_used)]
let static_secret = x25519_dalek::StaticSecret::from(local_secret.to_bytes());
let local_public: x25519_dalek::PublicKey = (&static_secret).into();
let local_public = PublicKey::from(local_secret);
let remote_public = PublicKey::from(remote_public);
let dh = static_secret.diffie_hellman(&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.as_bytes())
let mut mac = HmacSha256::new_from_slice(&dh[..])
.expect("x25519 shared secret is always 32 bytes long");
mac.update(local_public.as_bytes());
@@ -99,7 +97,7 @@ impl GatewayClient {
mac.update(&nonce.to_le_bytes());
GatewayClient {
pub_key: PeerPublicKey::new(local_public),
pub_key: PeerPublicKey::new(local_public.into()),
private_ip,
mac: ClientMac(mac.finalize().into_bytes().to_vec()),
}
@@ -109,11 +107,8 @@ impl GatewayClient {
// 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> {
// convert from 1.0 x25519-dalek private key into 2.0 x25519-dalek
#[allow(clippy::expect_used)]
let static_secret = x25519_dalek::StaticSecret::from(gateway_key.to_bytes());
let dh = static_secret.diffie_hellman(&self.pub_key);
// use gateways key as a ref to an x25519_dalek key
let dh = (gateway_key.as_ref()).diffie_hellman(&self.pub_key);
// TODO: change that to use our nym_crypto::hmac module instead
#[allow(clippy::expect_used)]
@@ -14,7 +14,7 @@ use std::{fmt, ops::Deref, str::FromStr};
#[cfg(feature = "verify")]
use hmac::{Hmac, Mac};
#[cfg(feature = "verify")]
use nym_crypto::asymmetric::encryption::PrivateKey;
use nym_crypto::asymmetric::encryption::{PrivateKey, PublicKey};
#[cfg(feature = "verify")]
use sha2::Sha256;
@@ -91,16 +91,14 @@ impl GatewayClient {
private_ip: IpAddr,
nonce: u64,
) -> Self {
// convert from 1.0 x25519-dalek private key into 2.0 x25519-dalek
#[allow(clippy::expect_used)]
let static_secret = x25519_dalek::StaticSecret::from(local_secret.to_bytes());
let local_public: x25519_dalek::PublicKey = (&static_secret).into();
let local_public = PublicKey::from(local_secret);
let remote_public = PublicKey::from(remote_public);
let dh = static_secret.diffie_hellman(&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.as_bytes())
let mut mac = HmacSha256::new_from_slice(&dh[..])
.expect("x25519 shared secret is always 32 bytes long");
mac.update(local_public.as_bytes());
@@ -108,7 +106,7 @@ impl GatewayClient {
mac.update(&nonce.to_le_bytes());
GatewayClient {
pub_key: PeerPublicKey::new(local_public),
pub_key: PeerPublicKey::new(local_public.into()),
private_ip,
mac: ClientMac(mac.finalize().into_bytes().to_vec()),
}
@@ -118,11 +116,8 @@ impl GatewayClient {
// 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> {
// convert from 1.0 x25519-dalek private key into 2.0 x25519-dalek
#[allow(clippy::expect_used)]
let static_secret = x25519_dalek::StaticSecret::from(gateway_key.to_bytes());
let dh = static_secret.diffie_hellman(&self.pub_key);
// use gateways key as a ref to an x25519_dalek key
let dh = (gateway_key.as_ref()).diffie_hellman(&self.pub_key);
// TODO: change that to use our nym_crypto::hmac module instead
#[allow(clippy::expect_used)]
@@ -14,7 +14,7 @@ use std::{fmt, ops::Deref, str::FromStr};
#[cfg(feature = "verify")]
use hmac::{Hmac, Mac};
#[cfg(feature = "verify")]
use nym_crypto::asymmetric::encryption::PrivateKey;
use nym_crypto::asymmetric::encryption::{PrivateKey, PublicKey};
#[cfg(feature = "verify")]
use sha2::Sha256;
@@ -91,16 +91,14 @@ impl GatewayClient {
private_ip: IpAddr,
nonce: u64,
) -> Self {
// convert from 1.0 x25519-dalek private key into 2.0 x25519-dalek
#[allow(clippy::expect_used)]
let static_secret = x25519_dalek::StaticSecret::from(local_secret.to_bytes());
let local_public: x25519_dalek::PublicKey = (&static_secret).into();
let local_public = PublicKey::from(local_secret);
let remote_public = PublicKey::from(remote_public);
let dh = static_secret.diffie_hellman(&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.as_bytes())
let mut mac = HmacSha256::new_from_slice(&dh[..])
.expect("x25519 shared secret is always 32 bytes long");
mac.update(local_public.as_bytes());
@@ -108,7 +106,7 @@ impl GatewayClient {
mac.update(&nonce.to_le_bytes());
GatewayClient {
pub_key: PeerPublicKey::new(local_public),
pub_key: PeerPublicKey::new(local_public.into()),
private_ip,
mac: ClientMac(mac.finalize().into_bytes().to_vec()),
}
@@ -118,11 +116,8 @@ impl GatewayClient {
// 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> {
// convert from 1.0 x25519-dalek private key into 2.0 x25519-dalek
#[allow(clippy::expect_used)]
let static_secret = x25519_dalek::StaticSecret::from(gateway_key.to_bytes());
let dh = static_secret.diffie_hellman(&self.pub_key);
// use gateways key as a ref to an x25519_dalek key
let dh = (gateway_key.as_ref()).diffie_hellman(&self.pub_key);
// TODO: change that to use our nym_crypto::hmac module instead
#[allow(clippy::expect_used)]
@@ -15,7 +15,7 @@ use std::{fmt, ops::Deref, str::FromStr};
#[cfg(feature = "verify")]
use hmac::{Hmac, Mac};
#[cfg(feature = "verify")]
use nym_crypto::asymmetric::encryption::PrivateKey;
use nym_crypto::asymmetric::encryption::{PrivateKey, PublicKey};
#[cfg(feature = "verify")]
use sha2::Sha256;
@@ -143,16 +143,14 @@ impl GatewayClient {
private_ips: IpPair,
nonce: u64,
) -> Self {
// convert from 1.0 x25519-dalek private key into 2.0 x25519-dalek
#[allow(clippy::expect_used)]
let static_secret = x25519_dalek::StaticSecret::from(local_secret.to_bytes());
let local_public: x25519_dalek::PublicKey = (&static_secret).into();
let local_public = PublicKey::from(local_secret);
let remote_public = PublicKey::from(remote_public);
let dh = static_secret.diffie_hellman(&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.as_bytes())
let mut mac = HmacSha256::new_from_slice(&dh[..])
.expect("x25519 shared secret is always 32 bytes long");
mac.update(local_public.as_bytes());
@@ -160,7 +158,7 @@ impl GatewayClient {
mac.update(&nonce.to_le_bytes());
GatewayClient {
pub_key: PeerPublicKey::new(local_public),
pub_key: PeerPublicKey::new(local_public.into()),
private_ips,
mac: ClientMac(mac.finalize().into_bytes().to_vec()),
}
@@ -170,11 +168,8 @@ impl GatewayClient {
// 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> {
// convert from 1.0 x25519-dalek private key into 2.0 x25519-dalek
#[allow(clippy::expect_used)]
let static_secret = x25519_dalek::StaticSecret::from(gateway_key.to_bytes());
let dh = static_secret.diffie_hellman(&self.pub_key);
// use gateways key as a ref to an x25519_dalek key
let dh = (gateway_key.as_ref()).diffie_hellman(&self.pub_key);
// TODO: change that to use our nym_crypto::hmac module instead
#[allow(clippy::expect_used)]
@@ -15,7 +15,7 @@ use std::{fmt, ops::Deref, str::FromStr};
#[cfg(feature = "verify")]
use hmac::{Hmac, Mac};
#[cfg(feature = "verify")]
use nym_crypto::asymmetric::encryption::PrivateKey;
use nym_crypto::asymmetric::encryption::{PrivateKey, PublicKey};
#[cfg(feature = "verify")]
use sha2::Sha256;
@@ -143,16 +143,14 @@ impl GatewayClient {
private_ips: IpPair,
nonce: u64,
) -> Self {
// convert from 1.0 x25519-dalek private key into 2.0 x25519-dalek
#[allow(clippy::expect_used)]
let static_secret = x25519_dalek::StaticSecret::from(local_secret.to_bytes());
let local_public: x25519_dalek::PublicKey = (&static_secret).into();
let local_public = PublicKey::from(local_secret);
let remote_public = PublicKey::from(remote_public);
let dh = static_secret.diffie_hellman(&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.as_bytes())
let mut mac = HmacSha256::new_from_slice(&dh[..])
.expect("x25519 shared secret is always 32 bytes long");
mac.update(local_public.as_bytes());
@@ -160,7 +158,7 @@ impl GatewayClient {
mac.update(&nonce.to_le_bytes());
GatewayClient {
pub_key: PeerPublicKey::new(local_public),
pub_key: PeerPublicKey::new(local_public.into()),
private_ips,
mac: ClientMac(mac.finalize().into_bytes().to_vec()),
}
@@ -170,11 +168,8 @@ impl GatewayClient {
// 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> {
// convert from 1.0 x25519-dalek private key into 2.0 x25519-dalek
#[allow(clippy::expect_used)]
let static_secret = x25519_dalek::StaticSecret::from(gateway_key.to_bytes());
let dh = static_secret.diffie_hellman(&self.pub_key);
// use gateways key as a ref to an x25519_dalek key
let dh = (gateway_key.as_ref()).diffie_hellman(&self.pub_key);
// TODO: change that to use our nym_crypto::hmac module instead
#[allow(clippy::expect_used)]
+2 -1
View File
@@ -21,6 +21,7 @@ serde_json = { workspace = true, optional = true }
## tracing
tracing-subscriber = { workspace = true, features = ["env-filter"], optional = true }
tracing-tree = { workspace = true, optional = true }
tracing = { workspace = true, optional = true }
opentelemetry-jaeger = { workspace = true, features = ["rt-tokio", "collector_client", "isahc_collector_client"], optional = true }
tracing-opentelemetry = { workspace = true, optional = true }
utoipa = { workspace = true, optional = true }
@@ -35,7 +36,7 @@ default = []
openapi = ["utoipa"]
output_format = ["serde_json", "dep:clap"]
bin_info_schema = ["schemars"]
basic_tracing = ["tracing-subscriber"]
basic_tracing = ["dep:tracing", "tracing-subscriber"]
tracing = [
"basic_tracing",
"tracing-tree",
+37 -14
View File
@@ -44,10 +44,38 @@ pub fn setup_logging() {
.init();
}
// don't call init so that we could attach additional layers
#[cfg(feature = "basic_tracing")]
pub fn setup_tracing_logger() {
let log_builder = tracing_subscriber::fmt()
.with_writer(std::io::stderr)
pub fn build_tracing_logger() -> impl tracing_subscriber::layer::SubscriberExt {
use tracing_subscriber::prelude::*;
tracing_subscriber::registry()
.with(default_tracing_fmt_layer(std::io::stderr))
.with(default_tracing_env_filter())
}
#[cfg(feature = "basic_tracing")]
pub fn default_tracing_env_filter() -> tracing_subscriber::filter::EnvFilter {
if ::std::env::var("RUST_LOG").is_ok() {
tracing_subscriber::filter::EnvFilter::from_default_env()
} else {
// if the env value was not found, default to `INFO` level rather than `ERROR`
tracing_subscriber::filter::EnvFilter::builder()
.with_default_directive(tracing_subscriber::filter::LevelFilter::INFO.into())
.parse_lossy("")
}
}
#[cfg(feature = "basic_tracing")]
pub fn default_tracing_fmt_layer<S, W>(
writer: W,
) -> impl tracing_subscriber::Layer<S> + Sync + Send + 'static
where
S: tracing::Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>,
W: for<'writer> tracing_subscriber::fmt::MakeWriter<'writer> + Sync + Send + 'static,
{
tracing_subscriber::fmt::layer()
.with_writer(writer)
// Use a more compact, abbreviated log format
.compact()
// Display source code file paths
@@ -55,18 +83,13 @@ pub fn setup_tracing_logger() {
// Display source code line numbers
.with_line_number(true)
// Don't display the event's target (module path)
.with_target(false);
.with_target(false)
}
if ::std::env::var("RUST_LOG").is_ok() {
log_builder
.with_env_filter(tracing_subscriber::filter::EnvFilter::from_default_env())
.init()
} else {
// default to 'Info
log_builder
.with_max_level(tracing_subscriber::filter::LevelFilter::INFO)
.init()
}
#[cfg(feature = "basic_tracing")]
pub fn setup_tracing_logger() {
use tracing_subscriber::util::SubscriberInitExt;
build_tracing_logger().init()
}
// TODO: This has to be a macro, running it as a function does not work for the file_appender for some reason
-4
View File
@@ -12,7 +12,6 @@ license.workspace = true
async-trait = { workspace = true }
base64 = { workspace = true }
bs58 = { workspace = true }
cfg-if = { workspace = true }
clap = { workspace = true, optional = true }
comfy-table = { workspace = true, optional = true }
futures = { workspace = true }
@@ -24,7 +23,6 @@ serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
sha2 = { workspace = true }
si-scale = { workspace = true }
tap = { workspace = true }
thiserror = { workspace = true }
url = { workspace = true, features = ["serde"] }
tokio = { workspace = true, features = ["macros"] }
@@ -35,9 +33,7 @@ zeroize = { workspace = true }
nym-id = { path = "../nym-id" }
nym-bandwidth-controller = { path = "../bandwidth-controller" }
nym-config = { path = "../config" }
nym-country-group = { path = "../country-group" }
nym-crypto = { path = "../crypto" }
nym-explorer-client = { path = "../../explorer-api/explorer-client" }
nym-gateway-client = { path = "../client-libs/gateway-client" }
nym-gateway-requests = { path = "../gateway-requests" }
nym-http-api-client = { path = "../http-api-client" }
@@ -14,7 +14,6 @@ url = { workspace = true, features = ["serde"] }
nym-config = { path = "../../config" }
nym-country-group = { path = "../../country-group" }
nym-pemstore = { path = "../../pemstore", optional = true }
# those are pulling so many deps T.T
+13 -39
View File
@@ -65,11 +65,10 @@ const DEFAULT_MAXIMUM_REPLY_KEY_AGE: Duration = Duration::from_secs(24 * 60 * 60
// stats reporting related
/// Time interval between reporting statistics to the given provider if it exist
/// Time interval between reporting statistics to the given provider if it exists
const STATS_REPORT_INTERVAL_SECS: Duration = Duration::from_secs(300);
use crate::error::InvalidTrafficModeFailure;
pub use nym_country_group::CountryGroup;
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
@@ -258,15 +257,6 @@ impl Config {
self
}
pub fn with_topology_structure(mut self, topology_structure: TopologyStructure) -> Self {
self.set_topology_structure(topology_structure);
self
}
pub fn set_topology_structure(&mut self, topology_structure: TopologyStructure) {
self.debug.topology.topology_structure = topology_structure;
}
pub fn with_no_per_hop_delays(mut self, no_per_hop_delays: bool) -> Self {
if no_per_hop_delays {
self.set_no_per_hop_delays()
@@ -415,6 +405,14 @@ pub struct Traffic {
/// Do not set it unless you understand the consequences of that change.
pub secondary_packet_size: Option<PacketSize>,
/// Specify whether any constructed sphinx packets should use the legacy format,
/// where the payload keys are explicitly attached rather than using the seeds
/// this affects any forward packets, acks and reply surbs
/// this flag should remain disabled until sufficient number of nodes on the network has upgraded
/// and support updated format.
/// in the case of reply surbs, the recipient must also understand the new encoding
pub use_legacy_sphinx_format: bool,
pub packet_type: PacketType,
}
@@ -442,6 +440,10 @@ impl Default for Traffic {
primary_packet_size: PacketSize::RegularPacket,
secondary_packet_size: None,
packet_type: PacketType::Mix,
// we should use the legacy format until sufficient number of nodes understand the
// improved encoding
use_legacy_sphinx_format: true,
}
}
}
@@ -546,9 +548,6 @@ pub struct Topology {
#[serde(with = "humantime_serde")]
pub max_startup_gateway_waiting_period: Duration,
/// Specifies the mixnode topology to be used for sending packets.
pub topology_structure: TopologyStructure,
/// Specifies a minimum performance of a mixnode that is used on route construction.
/// This setting is only applicable when `NymApi` topology is used.
pub minimum_mixnode_performance: u8,
@@ -570,30 +569,6 @@ pub struct Topology {
pub ignore_ingress_epoch_role: bool,
}
#[allow(clippy::large_enum_variant)]
#[derive(Default, Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum TopologyStructure {
#[default]
NymApi,
GeoAware(GroupBy),
}
#[allow(clippy::large_enum_variant)]
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum GroupBy {
CountryGroup(CountryGroup),
NymAddress(Recipient),
}
impl std::fmt::Display for GroupBy {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
GroupBy::CountryGroup(group) => write!(f, "group: {group}"),
GroupBy::NymAddress(address) => write!(f, "address: {address}"),
}
}
}
impl Default for Topology {
fn default() -> Self {
Topology {
@@ -601,7 +576,6 @@ impl Default for Topology {
topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT,
disable_refreshing: false,
max_startup_gateway_waiting_period: DEFAULT_MAX_STARTUP_GATEWAY_WAITING_PERIOD,
topology_structure: TopologyStructure::default(),
minimum_mixnode_performance: DEFAULT_MIN_MIXNODE_PERFORMANCE,
minimum_gateway_performance: DEFAULT_MIN_GATEWAY_PERFORMANCE,
use_extended_topology: false,
+29 -14
View File
@@ -2,10 +2,9 @@
// SPDX-License-Identifier: Apache-2.0
use crate::old::v5::{
AcknowledgementsV5, ClientV5, ConfigV5, CoverTrafficV5, DebugConfigV5, GatewayConnectionV5,
GroupByV5, ReplySurbsV5, TopologyStructureV5, TopologyV5, TrafficV5,
AcknowledgementsV5, ClientV5, ConfigV5, CountryGroupV5, CoverTrafficV5, DebugConfigV5,
GatewayConnectionV5, GroupByV5, ReplySurbsV5, TopologyStructureV5, TopologyV5, TrafficV5,
};
use crate::CountryGroup;
use nym_sphinx_addressing::Recipient;
use nym_sphinx_params::{PacketSize, PacketType};
use serde::{Deserialize, Serialize};
@@ -369,31 +368,47 @@ impl From<TopologyStructureV4> for TopologyStructureV5 {
}
}
#[derive(Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, Debug)]
pub enum CountryGroupV4 {
Europe,
NorthAmerica,
SouthAmerica,
Oceania,
Asia,
Africa,
Unknown,
}
impl From<CountryGroupV4> for CountryGroupV5 {
fn from(value: CountryGroupV4) -> Self {
match value {
CountryGroupV4::Europe => CountryGroupV5::Europe,
CountryGroupV4::NorthAmerica => CountryGroupV5::NorthAmerica,
CountryGroupV4::SouthAmerica => CountryGroupV5::SouthAmerica,
CountryGroupV4::Oceania => CountryGroupV5::Oceania,
CountryGroupV4::Asia => CountryGroupV5::Asia,
CountryGroupV4::Africa => CountryGroupV5::Africa,
CountryGroupV4::Unknown => CountryGroupV5::Unknown,
}
}
}
#[allow(clippy::large_enum_variant)]
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum GroupByV4 {
CountryGroup(CountryGroup),
CountryGroup(CountryGroupV4),
NymAddress(Recipient),
}
impl From<GroupByV4> for GroupByV5 {
fn from(value: GroupByV4) -> Self {
match value {
GroupByV4::CountryGroup(country) => GroupByV5::CountryGroup(country),
GroupByV4::CountryGroup(country) => GroupByV5::CountryGroup(country.into()),
GroupByV4::NymAddress(addr) => GroupByV5::NymAddress(addr),
}
}
}
impl std::fmt::Display for GroupByV4 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
GroupByV4::CountryGroup(group) => write!(f, "group: {}", group),
GroupByV4::NymAddress(address) => write!(f, "address: {}", address),
}
}
}
impl Default for TopologyV4 {
fn default() -> Self {
TopologyV4 {
+12 -29
View File
@@ -2,8 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
use crate::{
Acknowledgements, Client, Config, CountryGroup, CoverTraffic, DebugConfig, GatewayConnection,
GroupBy, ReplySurbs, Topology, TopologyStructure, Traffic,
Acknowledgements, Client, Config, CoverTraffic, DebugConfig, GatewayConnection, ReplySurbs,
Topology, Traffic,
};
use nym_sphinx_addressing::Recipient;
use nym_sphinx_params::{PacketSize, PacketType};
@@ -146,7 +146,6 @@ impl From<ConfigV5> for Config {
.debug
.topology
.max_startup_gateway_waiting_period,
topology_structure: value.debug.topology.topology_structure.into(),
..Default::default()
},
reply_surbs: ReplySurbs {
@@ -372,40 +371,24 @@ pub enum TopologyStructureV5 {
GeoAware(GroupByV5),
}
impl From<TopologyStructureV5> for TopologyStructure {
fn from(value: TopologyStructureV5) -> Self {
match value {
TopologyStructureV5::NymApi => TopologyStructure::NymApi,
TopologyStructureV5::GeoAware(group_by) => TopologyStructure::GeoAware(group_by.into()),
}
}
#[derive(Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, Debug)]
pub enum CountryGroupV5 {
Europe,
NorthAmerica,
SouthAmerica,
Oceania,
Asia,
Africa,
Unknown,
}
#[allow(clippy::large_enum_variant)]
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum GroupByV5 {
CountryGroup(CountryGroup),
CountryGroup(CountryGroupV5),
NymAddress(Recipient),
}
impl From<GroupByV5> for GroupBy {
fn from(value: GroupByV5) -> Self {
match value {
GroupByV5::CountryGroup(country) => GroupBy::CountryGroup(country),
GroupByV5::NymAddress(addr) => GroupBy::NymAddress(addr),
}
}
}
impl std::fmt::Display for GroupByV5 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
GroupByV5::CountryGroup(group) => write!(f, "group: {}", group),
GroupByV5::NymAddress(address) => write!(f, "address: {}", address),
}
}
}
impl Default for TopologyV5 {
fn default() -> Self {
TopologyV5 {
@@ -552,18 +552,12 @@ where
user_agent: Option<UserAgent>,
) -> Box<dyn TopologyProvider + Send + Sync> {
// if no custom provider was ... provided ..., create one using nym-api
custom_provider.unwrap_or_else(|| match config_topology.topology_structure {
config::TopologyStructure::NymApi => Box::new(NymApiTopologyProvider::new(
custom_provider.unwrap_or_else(|| {
Box::new(NymApiTopologyProvider::new(
config_topology,
nym_api_urls,
user_agent,
)),
config::TopologyStructure::GeoAware(group_by) => {
warn!("using deprecated 'GeoAware' topology provider - this option will be removed very soon");
#[allow(deprecated)]
Box::new(crate::client::topology_control::GeoAwareTopologyProvider::new(nym_api_urls, group_by))
}
))
})
}
@@ -62,6 +62,10 @@ where
/// Optional secondary predefined packet size used for the loop cover messages.
secondary_packet_size: Option<PacketSize>,
/// Specify whether any constructed packets should use the legacy format,
/// where the payload keys are explicitly attached rather than using the seeds
use_legacy_sphinx_format: bool,
packet_type: PacketType,
stats_tx: ClientStatsSender,
@@ -130,6 +134,7 @@ impl LoopCoverTrafficStream<OsRng> {
topology_access,
primary_packet_size: traffic_config.primary_packet_size,
secondary_packet_size: traffic_config.secondary_packet_size,
use_legacy_sphinx_format: traffic_config.use_legacy_sphinx_format,
packet_type: traffic_config.packet_type,
stats_tx,
task_client,
@@ -182,6 +187,7 @@ impl LoopCoverTrafficStream<OsRng> {
let cover_message = match generate_loop_cover_packet(
&mut self.rng,
self.use_legacy_sphinx_format,
topology_ref,
&self.ack_key,
&self.our_full_destination,
@@ -109,6 +109,10 @@ pub(crate) struct Config {
/// Optional secondary predefined packet size used for the encapsulated messages.
secondary_packet_size: Option<PacketSize>,
/// Specify whether any constructed reply surbs should use the legacy format,
/// where the payload keys are explicitly attached rather than using the seeds
use_legacy_sphinx_format: bool,
}
impl Config {
@@ -118,6 +122,7 @@ impl Config {
average_packet_delay: Duration,
average_ack_delay: Duration,
deterministic_route_selection: bool,
use_legacy_reply_surb_format: bool,
) -> Self {
Config {
ack_key,
@@ -127,6 +132,7 @@ impl Config {
average_ack_delay,
primary_packet_size: PacketSize::default(),
secondary_packet_size: None,
use_legacy_sphinx_format: use_legacy_reply_surb_format,
}
}
@@ -186,6 +192,7 @@ where
config.sender_address,
config.average_packet_delay,
config.average_ack_delay,
config.use_legacy_sphinx_format,
);
MessageHandler {
config,
@@ -254,9 +261,11 @@ where
let topology_permit = self.topology_access.get_read_permit().await;
let topology = self.get_topology(&topology_permit)?;
let reply_surbs = self
.message_preparer
.generate_reply_surbs(amount, topology)?;
let reply_surbs = self.message_preparer.generate_reply_surbs(
self.config.use_legacy_sphinx_format,
amount,
topology,
)?;
let reply_keys = reply_surbs
.iter()
@@ -522,6 +531,7 @@ where
self.generate_reply_surbs_with_keys(amount as usize).await?;
let message = NymMessage::new_repliable(RepliableMessage::new_additional_surbs(
self.config.use_legacy_sphinx_format,
sender_tag,
reply_surbs,
));
@@ -559,8 +569,12 @@ where
.generate_reply_surbs_with_keys(num_reply_surbs as usize)
.await?;
let message =
NymMessage::new_repliable(RepliableMessage::new_data(message, sender_tag, reply_surbs));
let message = NymMessage::new_repliable(RepliableMessage::new_data(
self.config.use_legacy_sphinx_format,
message,
sender_tag,
reply_surbs,
));
self.try_split_and_send_non_reply_message(
message,
@@ -99,6 +99,7 @@ impl<'a> From<&'a Config> for message_handler::Config {
cfg.traffic.average_packet_delay,
cfg.acks.average_ack_delay,
cfg.traffic.deterministic_route_selection,
cfg.traffic.use_legacy_sphinx_format,
)
.with_custom_primary_packet_size(cfg.traffic.primary_packet_size)
.with_custom_secondary_packet_size(cfg.traffic.secondary_packet_size)
@@ -252,6 +252,7 @@ where
(
generate_loop_cover_packet(
&mut self.rng,
self.config.traffic.use_legacy_sphinx_format,
topology_ref,
&self.config.ack_key,
&self.config.our_full_destination,
@@ -250,10 +250,10 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
let mut reconstructed = Vec::new();
for msg in msgs {
let (reply_surbs, from_surb_request) = match msg.content {
RepliableMessageContent::Data {
message,
reply_surbs,
} => {
RepliableMessageContent::Data(content) => {
let reply_surbs = content.reply_surbs;
let message = content.message;
trace!(
"received message that also contained additional {} reply surbs from {:?}!",
reply_surbs.len(),
@@ -264,7 +264,9 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
(reply_surbs, false)
}
RepliableMessageContent::AdditionalSurbs { reply_surbs } => {
RepliableMessageContent::AdditionalSurbs(content) => {
let reply_surbs = content.reply_surbs;
trace!(
"received additional {} reply surbs from {:?}!",
reply_surbs.len(),
@@ -272,9 +274,37 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
);
(reply_surbs, true)
}
RepliableMessageContent::Heartbeat {
additional_reply_surbs,
} => {
RepliableMessageContent::Heartbeat(content) => {
let additional_reply_surbs = content.additional_reply_surbs;
error!("received a repliable heartbeat message - we don't know how to handle it yet (and we won't know until future PRs)");
(additional_reply_surbs, false)
}
RepliableMessageContent::DataV2(content) => {
let reply_surbs = content.reply_surbs;
let message = content.message;
trace!(
"received message that also contained additional {} reply surbs from {:?}!",
reply_surbs.len(),
msg.sender_tag
);
reconstructed.push(ReconstructedMessage::new(message, msg.sender_tag));
(reply_surbs, false)
}
RepliableMessageContent::AdditionalSurbsV2(content) => {
let reply_surbs = content.reply_surbs;
trace!(
"received additional {} reply surbs from {:?}!",
reply_surbs.len(),
msg.sender_tag
);
(reply_surbs, true)
}
RepliableMessageContent::HeartbeatV2(content) => {
let additional_reply_surbs = content.additional_reply_surbs;
error!("received a repliable heartbeat message - we don't know how to handle it yet (and we won't know until future PRs)");
(additional_reply_surbs, false)
}
@@ -1,214 +0,0 @@
use crate::config::GroupBy;
use log::{debug, error};
use nym_explorer_client::{ExplorerClient, PrettyDetailedMixNodeBond};
use nym_network_defaults::var_names::EXPLORER_API;
use nym_topology::{
provider_trait::{async_trait, TopologyProvider},
NymTopology,
};
use nym_validator_client::client::NodeId;
use rand::{prelude::SliceRandom, thread_rng};
use std::collections::HashMap;
use tap::TapOptional;
use url::Url;
pub use nym_country_group::CountryGroup;
fn create_explorer_client() -> Option<ExplorerClient> {
let Ok(explorer_api_url) = std::env::var(EXPLORER_API) else {
error!("Missing EXPLORER_API");
return None;
};
let Ok(explorer_api_url) = explorer_api_url.parse() else {
error!("Failed to parse EXPLORER_API");
return None;
};
log::debug!("Using explorer-api url: {}", explorer_api_url);
let Ok(client) = nym_explorer_client::ExplorerClient::new(explorer_api_url) else {
error!("Failed to create explorer-api client");
return None;
};
Some(client)
}
fn group_mixnodes_by_country_code(
mixnodes: Vec<PrettyDetailedMixNodeBond>,
) -> HashMap<CountryGroup, Vec<NodeId>> {
mixnodes
.into_iter()
.fold(HashMap::<CountryGroup, Vec<NodeId>>::new(), |mut acc, m| {
if let Some(ref location) = m.location {
let country_code = location.two_letter_iso_country_code.clone();
let group_code = CountryGroup::new(country_code.as_str());
let mixnodes = acc.entry(group_code).or_default();
mixnodes.push(m.mix_id);
}
acc
})
}
fn log_mixnode_distribution(mixnodes: &HashMap<CountryGroup, Vec<NodeId>>) {
let mixnode_distribution = mixnodes
.iter()
.map(|(k, v)| format!("{}: {}", k, v.len()))
.collect::<Vec<_>>()
.join(", ");
debug!("Mixnode distribution - {}", mixnode_distribution);
}
fn check_layer_integrity(topology: NymTopology) -> Result<(), ()> {
if topology.ensure_minimally_routable().is_err() {
error!("Layer is missing in topology!");
return Err(());
}
Ok(())
}
#[deprecated(note = "use NymApiTopologyProvider instead as explorer API will soon be removed")]
pub struct GeoAwareTopologyProvider {
validator_client: nym_validator_client::client::NymApiClient,
filter_on: GroupBy,
}
#[allow(deprecated)]
impl GeoAwareTopologyProvider {
pub fn new(mut nym_api_urls: Vec<Url>, filter_on: GroupBy) -> GeoAwareTopologyProvider {
log::info!(
"Creating geo-aware topology provider with filter on {}",
filter_on
);
nym_api_urls.shuffle(&mut thread_rng());
GeoAwareTopologyProvider {
validator_client: nym_validator_client::client::NymApiClient::new(
nym_api_urls[0].clone(),
),
filter_on,
}
}
async fn get_topology(&self) -> Option<NymTopology> {
let rewarded_set = self
.validator_client
.get_current_rewarded_set()
.await
.inspect_err(|err| error!("failed to get current rewarded set: {err}"))
.ok()?;
let mut topology = NymTopology::new_empty(rewarded_set);
let mixnodes = match self
.validator_client
.get_all_basic_active_mixing_assigned_nodes()
.await
{
Err(err) => {
error!("failed to get network mixnodes - {err}");
return None;
}
Ok(mixes) => mixes,
};
let gateways = match self
.validator_client
.get_all_basic_entry_assigned_nodes()
.await
{
Err(err) => {
error!("failed to get network gateways - {err}");
return None;
}
Ok(gateways) => gateways,
};
// Also fetch mixnodes cached by explorer-api, with the purpose of getting their
// geolocation.
debug!("Fetching mixnodes from explorer-api...");
let explorer_client = create_explorer_client()?;
let Ok(mixnodes_from_explorer_api) = explorer_client.get_mixnodes().await else {
error!("failed to get mixnodes from explorer-api");
return None;
};
debug!("Fetching gateways from explorer-api...");
let Ok(gateways_from_explorer_api) = explorer_client.get_gateways().await else {
error!("failed to get mixnodes from explorer-api");
return None;
};
// Determine what we should filter around
let filter_on = match self.filter_on {
GroupBy::CountryGroup(group) => group,
GroupBy::NymAddress(recipient) => {
// Convert recipient into a country group by extracting out the gateway part and
// using that as the country code.
let gateway = recipient.gateway().to_base58_string();
// Lookup the location of this gateway by using the location data from the
// explorer-api
let gateway_location = gateways_from_explorer_api
.iter()
.find(|g| g.gateway.identity_key == gateway)
.and_then(|g| g.location.clone())
.map(|location| location.two_letter_iso_country_code)
.tap_none(|| error!("No location found for the gateway: {}", gateway))?;
debug!(
"Filtering on nym-address: {}, with location: {}",
recipient, gateway_location
);
CountryGroup::new(&gateway_location)
}
};
debug!("Filter group: {}", filter_on);
// Partition mixnodes_from_explorer_api according to the value of
// two_letter_iso_country_code.
// NOTE: we construct the full distribution here, but only use the one we're interested in.
// The reason we this instead of a straight filter is that this opens up the possibility to
// complement a small grouping with mixnodes from adjecent countries.
let mixnode_distribution = group_mixnodes_by_country_code(mixnodes_from_explorer_api);
log_mixnode_distribution(&mixnode_distribution);
let Some(filtered_mixnode_ids) = mixnode_distribution.get(&filter_on) else {
error!("no mixnodes found for: {}", filter_on);
return None;
};
let mixnodes = mixnodes
.into_iter()
.filter(|m| filtered_mixnode_ids.contains(&m.node_id))
.collect::<Vec<_>>();
topology.add_skimmed_nodes(&mixnodes);
topology.add_skimmed_nodes(&gateways);
// TODO: return real error type
check_layer_integrity(topology.clone()).ok()?;
Some(topology)
}
}
#[allow(deprecated)]
#[cfg(not(target_arch = "wasm32"))]
#[async_trait]
impl TopologyProvider for GeoAwareTopologyProvider {
// this will be manually refreshed on a timer specified inside mixnet client config
async fn get_new_topology(&mut self) -> Option<NymTopology> {
self.get_topology().await
}
}
#[allow(deprecated)]
#[cfg(target_arch = "wasm32")]
#[async_trait(?Send)]
impl TopologyProvider for GeoAwareTopologyProvider {
// this will be manually refreshed on a timer specified inside mixnet client config
async fn get_new_topology(&mut self) -> Option<NymTopology> {
self.get_topology().await
}
}
@@ -17,11 +17,8 @@ use tokio::time::sleep;
use wasmtimer::tokio::sleep;
mod accessor;
pub mod geo_aware_provider;
pub mod nym_api_provider;
#[allow(deprecated)]
pub use geo_aware_provider::GeoAwareTopologyProvider;
pub use nym_api_provider::{Config as NymApiTopologyProviderConfig, NymApiTopologyProvider};
pub use nym_topology::provider_trait::TopologyProvider;
@@ -24,10 +24,10 @@ use tracing::*;
#[derive(Clone, Copy)]
pub struct Config {
initial_reconnection_backoff: Duration,
maximum_reconnection_backoff: Duration,
initial_connection_timeout: Duration,
maximum_connection_buffer_size: usize,
pub initial_reconnection_backoff: Duration,
pub maximum_reconnection_backoff: Duration,
pub initial_connection_timeout: Duration,
pub maximum_connection_buffer_size: usize,
}
impl Config {
@@ -50,7 +50,7 @@ pub trait SendWithoutResponse {
// Without response in this context means we will not listen for anything we might get back (not
// that we should get anything), including any possible io errors
fn send_without_response(
&mut self,
&self,
address: NymNodeRoutingAddress,
packet: NymPacket,
packet_type: PacketType,
@@ -196,7 +196,7 @@ impl Client {
}
}
fn make_connection(&mut self, address: NymNodeRoutingAddress, pending_packet: FramedNymPacket) {
fn make_connection(&self, address: NymNodeRoutingAddress, pending_packet: FramedNymPacket) {
let (sender, receiver) = mpsc::channel(self.config.maximum_connection_buffer_size);
// this CAN'T fail because we just created the channel which has a non-zero capacity
@@ -247,7 +247,7 @@ impl Client {
impl SendWithoutResponse for Client {
fn send_without_response(
&mut self,
&self,
address: NymNodeRoutingAddress,
packet: NymPacket,
packet_type: PacketType,
@@ -219,7 +219,7 @@ impl Epoch {
EpochState::VerificationKeyFinalization { .. } => {
time_configuration.verification_key_finalization_time_secs
}
EpochState::InProgress { .. } => 0,
EpochState::InProgress => 0,
};
finish += adding;
}
-15
View File
@@ -1,15 +0,0 @@
[package]
name = "nym-country-group"
version = "0.1.0"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serde = { workspace = true, features = ["derive"] }
tracing.workspace = true
-158
View File
@@ -1,158 +0,0 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use serde::{Deserialize, Serialize};
use std::fmt;
use tracing::info;
#[derive(Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, Debug)]
pub enum CountryGroup {
Europe,
NorthAmerica,
SouthAmerica,
Oceania,
Asia,
Africa,
Unknown,
}
impl CountryGroup {
// We map country codes into group, which initially are continent codes to a first approximation,
// but we do it manually to reserve the right to tweak this distribution for our purposes.
// NOTE: I did this quickly, and it's not a complete list of all countries, but only those that
// were present in the network at the time. Please add more as needed.
pub fn new(country_code: &str) -> Self {
let country_code = country_code.to_uppercase();
use CountryGroup::*;
match country_code.as_ref() {
// Europe
"AT" => Europe,
"BG" => Europe,
"CH" => Europe,
"CY" => Europe,
"CZ" => Europe,
"DE" => Europe,
"DK" => Europe,
"ES" => Europe,
"FI" => Europe,
"FR" => Europe,
"GB" => Europe,
"GR" => Europe,
"IE" => Europe,
"IT" => Europe,
"LT" => Europe,
"LU" => Europe,
"LV" => Europe,
"MD" => Europe,
"MT" => Europe,
"NL" => Europe,
"NO" => Europe,
"PL" => Europe,
"RO" => Europe,
"SE" => Europe,
"SK" => Europe,
"TR" => Europe,
"UA" => Europe,
// North America
"CA" => NorthAmerica,
"MX" => NorthAmerica,
"US" => NorthAmerica,
// South America
"AR" => SouthAmerica,
"BR" => SouthAmerica,
"CL" => SouthAmerica,
"CO" => SouthAmerica,
"CR" => SouthAmerica,
"GT" => SouthAmerica,
// Oceania
"AU" => Oceania,
// Asia
"AM" => Asia,
"BH" => Asia,
"CN" => Asia,
"GE" => Asia,
"HK" => Asia,
"ID" => Asia,
"IL" => Asia,
"IN" => Asia,
"JP" => Asia,
"KH" => Asia,
"KR" => Asia,
"KZ" => Asia,
"MY" => Asia,
"RU" => Asia,
"SG" => Asia,
"TH" => Asia,
"VN" => Asia,
// Africa
"SC" => Africa,
"UG" => Africa,
"ZA" => Africa,
// And group level codes work too
"EU" => Europe,
"NA" => NorthAmerica,
"SA" => SouthAmerica,
"OC" => Oceania,
"AS" => Asia,
"AF" => Africa,
// And some aliases
"EUROPE" => Europe,
"NORTHAMERICA" => NorthAmerica,
"SOUTHAMERICA" => SouthAmerica,
"OCEANIA" => Oceania,
"ASIA" => Asia,
"AFRICA" => Africa,
_ => {
info!("Unknown country code: {country_code}");
Unknown
}
}
}
}
impl fmt::Display for CountryGroup {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use CountryGroup::*;
match self {
Europe => write!(f, "EU"),
NorthAmerica => write!(f, "NA"),
SouthAmerica => write!(f, "SA"),
Oceania => write!(f, "OC"),
Asia => write!(f, "AS"),
Africa => write!(f, "AF"),
Unknown => write!(f, "Unknown"),
}
}
}
impl std::str::FromStr for CountryGroup {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
let group = CountryGroup::new(s);
if group == CountryGroup::Unknown {
Err(())
} else {
Ok(group)
}
}
}
impl CountryGroup {
#[allow(unused)]
fn known(self) -> Option<CountryGroup> {
use CountryGroup::*;
match self {
Europe | NorthAmerica | SouthAmerica | Oceania | Asia | Africa => Some(self),
Unknown => None,
}
}
}
@@ -172,6 +172,21 @@ impl MemoryEcachTicketbookManager {
);
}
pub(crate) async fn contains_ticketbook(&self, ticketbook: &IssuedTicketBook) -> bool {
let ser = ticketbook.pack();
let search_data = Zeroizing::new(ser.data);
self.inner
.read()
.await
.ticketbooks
.iter()
.any(|ticketbook| {
let ser = ticketbook.1.ticketbook.pack();
let data = Zeroizing::new(ser.data);
search_data.eq(&data)
})
}
pub(crate) async fn get_ticketbooks_info(&self) -> Vec<BasicTicketbookInformation> {
let guard = self.inner.read().await;
@@ -95,6 +95,24 @@ impl SqliteEcashTicketbookManager {
Ok(())
}
pub(crate) async fn contains_ticketbook_data(&self, data: &[u8]) -> Result<bool, sqlx::Error> {
let exists = sqlx::query(
r#"
SELECT EXISTS (
SELECT 1
FROM ecash_ticketbook
WHERE ticketbook_data = ?
)
"#,
)
.bind(data)
.fetch_optional(&self.connection_pool)
.await?
.is_some();
Ok(exists)
}
pub(crate) async fn get_ticketbooks_info(
&self,
) -> Result<Vec<BasicTicketbookInformation>, sqlx::Error> {
@@ -70,6 +70,13 @@ impl Storage for EphemeralStorage {
Ok(())
}
async fn contains_issued_ticketbook(
&self,
ticketbook: &IssuedTicketBook,
) -> Result<bool, StorageError> {
Ok(self.storage_manager.contains_ticketbook(ticketbook).await)
}
async fn get_ticketbooks_info(
&self,
) -> Result<Vec<BasicTicketbookInformation>, Self::StorageError> {
@@ -145,6 +145,16 @@ impl Storage for PersistentStorage {
Ok(())
}
async fn contains_issued_ticketbook(
&self,
ticketbook: &IssuedTicketBook,
) -> Result<bool, Self::StorageError> {
let ser = ticketbook.pack();
let data = Zeroizing::new(ser.data);
Ok(self.storage_manager.contains_ticketbook_data(&data).await?)
}
async fn get_ticketbooks_info(
&self,
) -> Result<Vec<BasicTicketbookInformation>, Self::StorageError> {
+5
View File
@@ -37,6 +37,11 @@ pub trait Storage: Clone + Send + Sync {
ticketbook: &IssuedTicketBook,
) -> Result<(), Self::StorageError>;
async fn contains_issued_ticketbook(
&self,
ticketbook: &IssuedTicketBook,
) -> Result<bool, Self::StorageError>;
async fn get_ticketbooks_info(
&self,
) -> Result<Vec<BasicTicketbookInformation>, Self::StorageError>;
@@ -132,6 +132,10 @@ impl PublicKey {
*self.0.as_bytes()
}
pub fn as_bytes(&self) -> &[u8; PUBLIC_KEY_SIZE] {
self.0.as_bytes()
}
pub fn from_bytes(b: &[u8]) -> Result<Self, KeyRecoveryError> {
if b.len() != PUBLIC_KEY_SIZE {
return Err(KeyRecoveryError::InvalidSizePublicKey {
@@ -228,7 +232,6 @@ impl<'a> From<&'a PrivateKey> for PublicKey {
PublicKey((&pk.0).into())
}
}
impl FromStr for PrivateKey {
type Err = KeyRecoveryError;
+1 -10
View File
@@ -6,7 +6,6 @@ use ed25519_dalek::{SecretKey, Signer, SigningKey};
pub use ed25519_dalek::{Verifier, PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH, SIGNATURE_LENGTH};
use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair};
use std::fmt::{self, Debug, Display, Formatter};
use std::hash::{Hash, Hasher};
use std::str::FromStr;
use thiserror::Error;
use zeroize::{Zeroize, ZeroizeOnDrop};
@@ -154,17 +153,9 @@ impl PemStorableKeyPair for KeyPair {
}
/// ed25519 EdDSA Public Key
#[derive(Copy, Clone, Eq, PartialEq)]
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct PublicKey(ed25519_dalek::VerifyingKey);
impl Hash for PublicKey {
fn hash<H: Hasher>(&self, state: &mut H) {
// each public key has unique bytes representation which can be used
// for the hash implementation
self.to_bytes().hash(state)
}
}
impl Display for PublicKey {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Display::fmt(&self.to_base58_string(), f)
@@ -10,9 +10,6 @@ pub enum MixProcessingError {
#[error("failed to recover the expected SURB-Ack packet: {0}")]
MalformedSurbAck(#[from] SurbAckRecoveryError),
#[error("the received packet was set to use the very old and very much deprecated 'VPN' mode")]
ReceivedOldTypeVpnPacket,
#[error("failed to process received Nym packet: {0}")]
NymPacketProcessingError(#[from] PacketProcessingError),
}
-29
View File
@@ -37,32 +37,3 @@ impl TicketTypeRepr {
}
}
}
// Constants for bloom filter for double spending detection
//Chosen for FP of
//Calculator at https://hur.st/bloomfilter/
pub const ECASH_DS_BLOOMFILTER_PARAMS: BloomfilterParameters = BloomfilterParameters {
num_hashes: 10,
bitmap_size: 1_500_000_000,
sip_keys: [
(12345678910111213141, 1415926535897932384),
(7182818284590452353, 3571113171923293137),
],
};
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct BloomfilterParameters {
pub num_hashes: u32,
pub bitmap_size: u64,
pub sip_keys: [(u64, u64); 2],
}
impl BloomfilterParameters {
pub const fn byte_size(&self) -> u64 {
self.bitmap_size / 8
}
pub const fn default_ecash() -> Self {
ECASH_DS_BLOOMFILTER_PARAMS
}
}
+1 -1
View File
@@ -64,7 +64,7 @@ pub enum NodeType {
impl NodeType {
pub fn is_mixnode(&self) -> bool {
matches!(self, NodeType::Mixnode { .. })
matches!(self, NodeType::Mixnode)
}
}
+10
View File
@@ -39,6 +39,10 @@ pub struct NodeTester<R> {
/// Average delay an acknowledgement packet is going to get delay at a single mixnode.
average_ack_delay: Duration,
/// Specify whether any constructed packets should use the legacy format,
/// where the payload keys are explicitly attached rather than using the seeds
use_legacy_sphinx_format: bool,
// while acks are going to be ignored they still need to be constructed
// so that the gateway would be able to correctly process and forward the message
ack_key: Arc<AckKey>,
@@ -57,6 +61,7 @@ where
deterministic_route_selection: bool,
average_packet_delay: Duration,
average_ack_delay: Duration,
use_legacy_sphinx_format: bool,
ack_key: Arc<AckKey>,
) -> Self {
Self {
@@ -67,6 +72,7 @@ where
deterministic_route_selection,
average_packet_delay,
average_ack_delay,
use_legacy_sphinx_format,
ack_key,
}
}
@@ -245,6 +251,10 @@ where
impl<R: CryptoRng + Rng> FragmentPreparer for NodeTester<R> {
type Rng = R;
fn use_legacy_sphinx_format(&self) -> bool {
self.use_legacy_sphinx_format
}
fn deterministic_route_selection(&self) -> bool {
self.deterministic_route_selection
}
+17
View File
@@ -363,6 +363,7 @@ impl MetricsController {
buffer
}
#[inline(always)]
pub fn to_writer(&self, writer: &mut dyn std::io::Write) {
let metrics = self.gather();
match writer.write_all(&metrics) {
@@ -371,6 +372,7 @@ impl MetricsController {
}
}
#[inline(always)]
pub fn register_int_gauge<'a>(&self, name: &str, help: impl Into<Option<&'a str>>) {
let Some(metric) = Metric::new_int_gauge(name, help.into().unwrap_or(name)) else {
return;
@@ -378,6 +380,7 @@ impl MetricsController {
self.register_metric(metric);
}
#[inline(always)]
pub fn register_float_gauge<'a>(&self, name: &str, help: impl Into<Option<&'a str>>) {
let Some(metric) = Metric::new_float_gauge(name, help.into().unwrap_or(name)) else {
return;
@@ -385,6 +388,7 @@ impl MetricsController {
self.register_metric(metric);
}
#[inline(always)]
pub fn register_int_counter<'a>(&self, name: &str, help: impl Into<Option<&'a str>>) {
let Some(metric) = Metric::new_int_counter(name, help.into().unwrap_or(name)) else {
return;
@@ -392,6 +396,7 @@ impl MetricsController {
self.register_metric(metric);
}
#[inline(always)]
pub fn register_histogram<'a>(
&self,
name: &str,
@@ -404,6 +409,7 @@ impl MetricsController {
self.register_metric(metric);
}
#[inline(always)]
pub fn set(&self, name: &str, value: i64) -> bool {
if let Some(metric) = self.registry_index.get(name) {
metric.set(value);
@@ -413,6 +419,7 @@ impl MetricsController {
}
}
#[inline(always)]
pub fn set_float(&self, name: &str, value: f64) -> bool {
if let Some(metric) = self.registry_index.get(name) {
metric.set_float(value);
@@ -422,6 +429,7 @@ impl MetricsController {
}
}
#[inline(always)]
pub fn add_to_histogram(&self, name: &str, value: f64) -> bool {
if let Some(metric) = self.registry_index.get(name) {
metric.add_histogram_observation(value);
@@ -431,12 +439,14 @@ impl MetricsController {
}
}
#[inline(always)]
pub fn start_timer(&self, name: &str) -> Option<HistogramTimer> {
self.registry_index
.get(name)
.and_then(|metric| metric.start_timer())
}
#[inline(always)]
pub fn inc(&self, name: &str) -> bool {
if let Some(metric) = self.registry_index.get(name) {
metric.inc();
@@ -446,6 +456,7 @@ impl MetricsController {
}
}
#[inline(always)]
pub fn inc_by(&self, name: &str, value: i64) -> bool {
if let Some(metric) = self.registry_index.get(name) {
metric.inc_by(value);
@@ -455,6 +466,7 @@ impl MetricsController {
}
}
#[inline(always)]
pub fn maybe_register_and_set<'a>(
&self,
name: &str,
@@ -468,6 +480,7 @@ impl MetricsController {
}
}
#[inline(always)]
pub fn maybe_register_and_set_float<'a>(
&self,
name: &str,
@@ -481,6 +494,7 @@ impl MetricsController {
}
}
#[inline(always)]
pub fn maybe_register_and_add_to_histogram<'a>(
&self,
name: &str,
@@ -495,6 +509,7 @@ impl MetricsController {
}
}
#[inline(always)]
pub fn maybe_register_and_inc<'a>(&self, name: &str, help: impl Into<Option<&'a str>>) {
if !self.inc(name) {
let help = help.into();
@@ -503,6 +518,7 @@ impl MetricsController {
}
}
#[inline(always)]
pub fn maybe_register_and_inc_by<'a>(
&self,
name: &str,
@@ -516,6 +532,7 @@ impl MetricsController {
}
}
#[inline(always)]
pub fn register_metric(&self, metric: impl Into<Metric>) {
let m = metric.into();
let fq_name = m.fq_name();
@@ -403,7 +403,7 @@ pub fn issue(
/// * `sk_user` - Secret key of the user.
/// * `blind_signature` - Blinded signature received from the authority.
/// * `req_info` - Information associated with the request, including the joined commitment hash,
/// private attributes openings, v, and expiration date.
/// private attributes openings, v, and expiration date.
///
/// # Returns
///
@@ -37,8 +37,10 @@ pub enum SurbAckRecoveryError {
}
impl SurbAck {
#[allow(clippy::too_many_arguments)]
pub fn construct<R>(
rng: &mut R,
use_legacy_sphinx_format: bool,
recipient: &Recipient,
ack_key: &AckKey,
marshaled_fragment_id: [u8; 5],
@@ -57,8 +59,6 @@ impl SurbAck {
let packet_size = match packet_type {
PacketType::Outfox => surb_ack_payload.len().max(MIN_PACKET_SIZE),
PacketType::Mix => PacketSize::AckPacket.payload_size(),
#[allow(deprecated)]
PacketType::Vpn => PacketSize::AckPacket.payload_size(),
};
let surb_ack_packet = match packet_type {
@@ -69,14 +69,7 @@ impl SurbAck {
Some(packet_size),
)?,
PacketType::Mix => NymPacket::sphinx_build(
packet_size,
surb_ack_payload,
&route,
&destination,
&delays,
)?,
#[allow(deprecated)]
PacketType::Vpn => NymPacket::sphinx_build(
use_legacy_sphinx_format,
packet_size,
surb_ack_payload,
&route,
@@ -106,8 +99,6 @@ impl SurbAck {
PacketSize::OutfoxAckPacket.size() + MAX_NODE_ADDRESS_UNPADDED_LEN
}
PacketType::Mix => PacketSize::AckPacket.size() + MAX_NODE_ADDRESS_UNPADDED_LEN,
#[allow(deprecated)]
PacketType::Vpn => PacketSize::AckPacket.size() + MAX_NODE_ADDRESS_UNPADDED_LEN,
}
}
@@ -139,8 +130,6 @@ impl SurbAck {
let packet = match packet_type {
PacketType::Outfox => NymPacket::outfox_from_bytes(&b[address_offset..])?,
PacketType::Mix => NymPacket::sphinx_from_bytes(&b[address_offset..])?,
#[allow(deprecated)]
PacketType::Vpn => NymPacket::sphinx_from_bytes(&b[address_offset..])?,
};
Ok((address, packet))
+1 -1
View File
@@ -199,7 +199,7 @@ impl TryFrom<NodeAddressBytes> for NymNodeRoutingAddress {
type Error = NymNodeRoutingAddressError;
fn try_from(value: NodeAddressBytes) -> Result<Self, Self::Error> {
Self::try_from_bytes(value.as_bytes_ref())
Self::try_from_bytes(value.as_bytes())
}
}
@@ -12,6 +12,7 @@ rand = { workspace = true }
bs58 = { workspace = true }
serde = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }
nym-crypto = { path = "../../crypto", features = ["stream_cipher", "rand"] }
nym-sphinx-addressing = { path = "../addressing" }
@@ -1,20 +1,25 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::encryption_key::{SurbEncryptionKey, SurbEncryptionKeyError, SurbEncryptionKeySize};
use nym_crypto::{generic_array::typenum::Unsigned, Digest};
use nym_sphinx_addressing::clients::Recipient;
use nym_sphinx_addressing::nodes::{NymNodeRoutingAddress, MAX_NODE_ADDRESS_UNPADDED_LEN};
use nym_sphinx_addressing::nodes::{
NymNodeRoutingAddress, NymNodeRoutingAddressError, MAX_NODE_ADDRESS_UNPADDED_LEN,
};
use nym_sphinx_params::packet_sizes::PacketSize;
use nym_sphinx_params::{PacketType, ReplySurbKeyDigestAlgorithm};
use nym_sphinx_types::{NymPacket, SURBMaterial, SphinxError, SURB};
use nym_sphinx_types::constants::PAYLOAD_KEY_SEED_SIZE;
use nym_sphinx_types::{
NymPacket, SURBMaterial, SphinxError, HEADER_SIZE, NODE_ADDRESS_LENGTH, SURB,
X25519_WITH_EXPLICIT_PAYLOAD_KEYS_VERSION,
};
use nym_topology::{NymRouteProvider, NymTopologyError};
use rand::{CryptoRng, RngCore};
use serde::de::{Error as SerdeError, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt::{self, Formatter};
use std::time;
use std::time::Duration;
use thiserror::Error;
#[derive(Debug, Error)]
@@ -31,6 +36,9 @@ pub enum ReplySurbError {
#[error("failed to recover reply SURB from bytes: {0}")]
RecoveryError(#[from] SphinxError),
#[error("failed to validate the first hop address of the recovered reply SURB: {0}")]
MalformedSurbFirstHop(#[from] NymNodeRoutingAddressError),
#[error("failed to recover reply SURB encryption key from bytes: {0}")]
InvalidEncryptionKeyData(#[from] SurbEncryptionKeyError),
}
@@ -80,6 +88,10 @@ impl<'de> Deserialize<'de> for ReplySurb {
}
impl ReplySurb {
/// base overhead of a reply surb that exists regardless of type or number of key materials.
pub(crate) const BASE_OVERHEAD: usize =
SurbEncryptionKeySize::USIZE + HEADER_SIZE + NODE_ADDRESS_LENGTH;
pub fn max_msg_len(packet_size: PacketSize) -> usize {
// For detailed explanation (of ack overhead) refer to common\nymsphinx\src\preparer.rs::available_plaintext_per_packet()
let ack_overhead = MAX_NODE_ADDRESS_UNPADDED_LEN + PacketSize::AckPacket.size();
@@ -91,7 +103,8 @@ impl ReplySurb {
pub fn construct<R>(
rng: &mut R,
recipient: &Recipient,
average_delay: time::Duration,
average_delay: Duration,
use_legacy_surb_format: bool,
topology: &NymRouteProvider,
) -> Result<Self, NymTopologyError>
where
@@ -101,7 +114,10 @@ impl ReplySurb {
let delays = nym_sphinx_routing::generate_hop_delays(average_delay, route.len());
let destination = recipient.as_sphinx_destination();
let surb_material = SURBMaterial::new(route, delays, destination);
let mut surb_material = SURBMaterial::new(route, delays, destination);
if use_legacy_surb_format {
surb_material = surb_material.with_version(X25519_WITH_EXPLICIT_PAYLOAD_KEYS_VERSION)
}
// this can't fail as we know we have a valid route to gateway and have correct number of delays
Ok(ReplySurb {
@@ -110,14 +126,10 @@ impl ReplySurb {
})
}
/// Returns the expected number of bytes the [`ReplySURB`] will take after serialization.
/// Returns the expected number of bytes the [`ReplySURB`] will take after serialization using the new encoding format.
/// Useful for deserialization from a bytes stream.
pub fn serialized_len() -> usize {
use nym_sphinx_types::{HEADER_SIZE, NODE_ADDRESS_LENGTH, PAYLOAD_KEY_SIZE};
// the SURB itself consists of SURB_header, first hop address and set of payload keys
// for each hop (3x mix + egress)
SurbEncryptionKeySize::USIZE + HEADER_SIZE + NODE_ADDRESS_LENGTH + 4 * PAYLOAD_KEY_SIZE
pub fn v2_serialised_len(num_hops: u8) -> usize {
Self::BASE_OVERHEAD + num_hops as usize * PAYLOAD_KEY_SEED_SIZE
}
pub fn encryption_key(&self) -> &SurbEncryptionKey {
@@ -143,7 +155,12 @@ impl ReplySurb {
let surb = match SURB::from_bytes(&bytes[SurbEncryptionKeySize::USIZE..]) {
Err(err) => return Err(ReplySurbError::RecoveryError(err)),
Ok(surb) => surb,
Ok(surb) => {
// we can't really check fully validity of the header, but at the very least we could make a sanity check
// to make sure the first hop address is a valid socket address
let _ = NymNodeRoutingAddress::try_from(surb.first_hop())?;
surb
}
};
Ok(ReplySurb {
@@ -8,9 +8,15 @@ use std::fmt::{Display, Formatter};
use std::mem;
use thiserror::Error;
use crate::requests::v1::{AdditionalSurbsV1, DataV1, HeartbeatV1};
use crate::requests::v2::{AdditionalSurbsV2, DataV2, HeartbeatV2};
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
pub(crate) mod v1;
pub(crate) mod v2;
pub const SENDER_TAG_SIZE: usize = 16;
#[derive(Debug, Error)]
@@ -103,31 +109,23 @@ pub struct RepliableMessage {
impl Display for RepliableMessage {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match &self.content {
RepliableMessageContent::Data {
message,
reply_surbs,
} => write!(
f,
"repliable {:.2} kiB data message with {} reply surbs attached from {}",
message.len() as f64 / 1024.0,
reply_surbs.len(),
self.sender_tag,
),
RepliableMessageContent::AdditionalSurbs { reply_surbs } => write!(
f,
"repliable additional surbs message ({} reply surbs attached) from {}",
reply_surbs.len(),
self.sender_tag,
),
RepliableMessageContent::Heartbeat {
additional_reply_surbs,
} => {
write!(
f,
"repliable heartbeat message ({} reply surbs attached) from {}",
additional_reply_surbs.len(),
self.sender_tag,
)
RepliableMessageContent::Data(content) => {
write!(f, "{content} from {}", self.sender_tag)
}
RepliableMessageContent::AdditionalSurbs(content) => {
write!(f, "{content} from {}", self.sender_tag)
}
RepliableMessageContent::Heartbeat(content) => {
write!(f, "{content} from {}", self.sender_tag)
}
RepliableMessageContent::DataV2(content) => {
write!(f, "{content} from {}", self.sender_tag)
}
RepliableMessageContent::AdditionalSurbsV2(content) => {
write!(f, "{content} from {}", self.sender_tag)
}
RepliableMessageContent::HeartbeatV2(content) => {
write!(f, "{content} from {}", self.sender_tag)
}
}
}
@@ -135,26 +133,43 @@ impl Display for RepliableMessage {
impl RepliableMessage {
pub fn new_data(
use_legacy_surb_format: bool,
data: Vec<u8>,
sender_tag: AnonymousSenderTag,
reply_surbs: Vec<ReplySurb>,
) -> Self {
RepliableMessage {
sender_tag,
content: RepliableMessageContent::Data {
let content = if use_legacy_surb_format {
RepliableMessageContent::Data(DataV1 {
message: data,
reply_surbs,
},
})
} else {
RepliableMessageContent::DataV2(DataV2 {
message: data,
reply_surbs,
})
};
RepliableMessage {
sender_tag,
content,
}
}
pub fn new_additional_surbs(
use_legacy_surb_format: bool,
sender_tag: AnonymousSenderTag,
reply_surbs: Vec<ReplySurb>,
) -> Self {
let content = if use_legacy_surb_format {
RepliableMessageContent::AdditionalSurbs(AdditionalSurbsV1 { reply_surbs })
} else {
RepliableMessageContent::AdditionalSurbsV2(AdditionalSurbsV2 { reply_surbs })
};
RepliableMessage {
sender_tag,
content: RepliableMessageContent::AdditionalSurbs { reply_surbs },
content,
}
}
@@ -192,35 +207,18 @@ impl RepliableMessage {
}
}
// this recovery code is shared between all variants containing reply surbs
fn recover_reply_surbs(bytes: &[u8]) -> Result<(Vec<ReplySurb>, usize), InvalidReplyRequestError> {
let mut consumed = mem::size_of::<u32>();
if bytes.len() < consumed {
return Err(InvalidReplyRequestError::RequestTooShortToDeserialize);
}
let num_surbs = u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
let surb_size = ReplySurb::serialized_len();
if bytes[consumed..].len() < num_surbs as usize * surb_size {
return Err(InvalidReplyRequestError::RequestTooShortToDeserialize);
}
let mut reply_surbs = Vec::with_capacity(num_surbs as usize);
for _ in 0..num_surbs as usize {
let surb_bytes = &bytes[consumed..consumed + surb_size];
let reply_surb = ReplySurb::from_bytes(surb_bytes)?;
reply_surbs.push(reply_surb);
consumed += surb_size;
}
Ok((reply_surbs, consumed))
}
#[derive(Debug)]
#[repr(u8)]
enum RepliableMessageContentTag {
Data = 0,
AdditionalSurbs = 1,
Heartbeat = 2,
// updated variants that slightly change SURB encoding
// to allow for variable number of hops as well as using payload key seeds
DataV2 = 3,
AdditionalSurbsV2 = 4,
HeartbeatV2 = 5,
}
impl TryFrom<u8> for RepliableMessageContentTag {
@@ -233,6 +231,11 @@ impl TryFrom<u8> for RepliableMessageContentTag {
Ok(Self::AdditionalSurbs)
}
_ if value == (RepliableMessageContentTag::Heartbeat as u8) => Ok(Self::Heartbeat),
_ if value == (RepliableMessageContentTag::DataV2 as u8) => Ok(Self::DataV2),
_ if value == (RepliableMessageContentTag::AdditionalSurbsV2 as u8) => {
Ok(Self::AdditionalSurbsV2)
}
_ if value == (RepliableMessageContentTag::HeartbeatV2 as u8) => Ok(Self::HeartbeatV2),
val => Err(InvalidReplyRequestError::InvalidRepliableContentTag { received: val }),
}
}
@@ -241,58 +244,24 @@ impl TryFrom<u8> for RepliableMessageContentTag {
// sent by original sender that initialised the communication that knows address of the remote
#[derive(Debug)]
pub enum RepliableMessageContent {
Data {
message: Vec<u8>,
reply_surbs: Vec<ReplySurb>,
},
AdditionalSurbs {
reply_surbs: Vec<ReplySurb>,
},
Heartbeat {
additional_reply_surbs: Vec<ReplySurb>,
},
Data(DataV1),
AdditionalSurbs(AdditionalSurbsV1),
Heartbeat(HeartbeatV1),
DataV2(DataV2),
AdditionalSurbsV2(AdditionalSurbsV2),
HeartbeatV2(HeartbeatV2),
}
impl RepliableMessageContent {
pub fn into_bytes(self) -> Vec<u8> {
match self {
RepliableMessageContent::Data {
message,
reply_surbs,
} => {
let num_surbs = reply_surbs.len() as u32;
num_surbs
.to_be_bytes()
.into_iter()
.chain(reply_surbs.into_iter().flat_map(|s| s.to_bytes()))
.chain(message)
.collect()
}
RepliableMessageContent::AdditionalSurbs { reply_surbs } => {
let num_surbs = reply_surbs.len() as u32;
num_surbs
.to_be_bytes()
.into_iter()
.chain(reply_surbs.into_iter().flat_map(|s| s.to_bytes()))
.collect()
}
RepliableMessageContent::Heartbeat {
additional_reply_surbs,
} => {
let num_surbs = additional_reply_surbs.len() as u32;
num_surbs
.to_be_bytes()
.into_iter()
.chain(
additional_reply_surbs
.into_iter()
.flat_map(|s| s.to_bytes()),
)
.collect()
}
RepliableMessageContent::Data(content) => content.into_bytes(),
RepliableMessageContent::AdditionalSurbs(content) => content.into_bytes(),
RepliableMessageContent::Heartbeat(content) => content.into_bytes(),
RepliableMessageContent::DataV2(content) => content.into_bytes(),
RepliableMessageContent::AdditionalSurbsV2(content) => content.into_bytes(),
RepliableMessageContent::HeartbeatV2(content) => content.into_bytes(),
}
}
@@ -304,19 +273,25 @@ impl RepliableMessageContent {
return Err(InvalidReplyRequestError::RequestTooShortToDeserialize);
}
let (reply_surbs, n) = recover_reply_surbs(bytes)?;
match tag {
RepliableMessageContentTag::Data => Ok(RepliableMessageContent::Data {
message: bytes[n..].to_vec(),
reply_surbs,
}),
RepliableMessageContentTag::AdditionalSurbs => {
Ok(RepliableMessageContent::AdditionalSurbs { reply_surbs })
RepliableMessageContentTag::Data => {
Ok(RepliableMessageContent::Data(DataV1::from_bytes(bytes)?))
}
RepliableMessageContentTag::Heartbeat => Ok(RepliableMessageContent::Heartbeat {
additional_reply_surbs: reply_surbs,
}),
RepliableMessageContentTag::AdditionalSurbs => Ok(
RepliableMessageContent::AdditionalSurbs(AdditionalSurbsV1::from_bytes(bytes)?),
),
RepliableMessageContentTag::Heartbeat => Ok(RepliableMessageContent::Heartbeat(
HeartbeatV1::from_bytes(bytes)?,
)),
RepliableMessageContentTag::DataV2 => {
Ok(RepliableMessageContent::DataV2(DataV2::from_bytes(bytes)?))
}
RepliableMessageContentTag::AdditionalSurbsV2 => Ok(
RepliableMessageContent::AdditionalSurbsV2(AdditionalSurbsV2::from_bytes(bytes)?),
),
RepliableMessageContentTag::HeartbeatV2 => Ok(RepliableMessageContent::HeartbeatV2(
HeartbeatV2::from_bytes(bytes)?,
)),
}
}
@@ -327,30 +302,22 @@ impl RepliableMessageContent {
RepliableMessageContentTag::AdditionalSurbs
}
RepliableMessageContent::Heartbeat { .. } => RepliableMessageContentTag::Heartbeat,
RepliableMessageContent::DataV2(_) => RepliableMessageContentTag::DataV2,
RepliableMessageContent::AdditionalSurbsV2(_) => {
RepliableMessageContentTag::AdditionalSurbsV2
}
RepliableMessageContent::HeartbeatV2(_) => RepliableMessageContentTag::HeartbeatV2,
}
}
fn serialized_size(&self) -> usize {
match self {
RepliableMessageContent::Data {
message,
reply_surbs,
} => {
let num_reply_surbs_tag = mem::size_of::<u32>();
num_reply_surbs_tag
+ reply_surbs.len() * ReplySurb::serialized_len()
+ message.len()
}
RepliableMessageContent::AdditionalSurbs { reply_surbs } => {
let num_reply_surbs_tag = mem::size_of::<u32>();
num_reply_surbs_tag + reply_surbs.len() * ReplySurb::serialized_len()
}
RepliableMessageContent::Heartbeat {
additional_reply_surbs,
} => {
let num_reply_surbs_tag = mem::size_of::<u32>();
num_reply_surbs_tag + additional_reply_surbs.len() * ReplySurb::serialized_len()
}
RepliableMessageContent::Data(content) => content.serialized_len(),
RepliableMessageContent::AdditionalSurbs(content) => content.serialized_len(),
RepliableMessageContent::Heartbeat(content) => content.serialized_len(),
RepliableMessageContent::DataV2(content) => content.serialized_len(),
RepliableMessageContent::AdditionalSurbsV2(content) => content.serialized_len(),
RepliableMessageContent::HeartbeatV2(content) => content.serialized_len(),
}
}
}
@@ -514,18 +481,22 @@ mod tests {
use super::*;
mod fixtures {
use crate::requests::v1::{AdditionalSurbsV1, DataV1, HeartbeatV1};
use crate::requests::v2::{AdditionalSurbsV2, DataV2, HeartbeatV2};
use crate::requests::{AnonymousSenderTag, RepliableMessageContent, ReplyMessageContent};
use crate::{ReplySurb, SurbEncryptionKey};
use nym_crypto::asymmetric::{encryption, identity};
use nym_sphinx_addressing::clients::Recipient;
use nym_sphinx_types::{
Delay, Destination, DestinationAddressBytes, Node, NodeAddressBytes, PrivateKey,
SURBMaterial, NODE_ADDRESS_LENGTH,
SURBMaterial, NODE_ADDRESS_LENGTH, X25519_WITH_EXPLICIT_PAYLOAD_KEYS_VERSION,
};
use rand::{Rng, RngCore};
use rand_chacha::rand_core::SeedableRng;
use rand_chacha::ChaCha20Rng;
pub(crate) const LEGACY_HOPS: u8 = 4;
pub(super) fn test_rng() -> ChaCha20Rng {
let dummy_seed = [42u8; 32];
ChaCha20Rng::from_seed(dummy_seed)
@@ -567,11 +538,9 @@ mod tests {
}
}
pub(super) fn reply_surb(rng: &mut ChaCha20Rng) -> ReplySurb {
// due to gateway
const HOPS: u8 = 4;
let route = (0..HOPS).map(|_| node(rng)).collect();
let delays = (0..HOPS)
pub(super) fn reply_surb(rng: &mut ChaCha20Rng, legacy: bool, hops: u8) -> ReplySurb {
let route = (0..hops).map(|_| node(rng)).collect();
let delays = (0..hops)
.map(|_| Delay::new_from_nanos(rng.next_u64()))
.collect();
let mut destination_bytes = [0u8; 32];
@@ -585,50 +554,58 @@ mod tests {
identifier_bytes,
);
let surb = SURBMaterial::new(route, delays, destination)
.construct_SURB()
.unwrap();
let mut surb_material = SURBMaterial::new(route, delays, destination);
if legacy {
surb_material =
surb_material.with_version(X25519_WITH_EXPLICIT_PAYLOAD_KEYS_VERSION);
}
ReplySurb {
surb,
surb: surb_material.construct_SURB().unwrap(),
encryption_key: SurbEncryptionKey::new(rng),
}
}
pub(super) fn reply_surbs(rng: &mut ChaCha20Rng, n: usize) -> Vec<ReplySurb> {
pub(super) fn reply_surbs(
rng: &mut ChaCha20Rng,
n: usize,
legacy: bool,
hops: u8,
) -> Vec<ReplySurb> {
let mut surbs = Vec::with_capacity(n);
for _ in 0..n {
surbs.push(reply_surb(rng))
surbs.push(reply_surb(rng, legacy, hops))
}
surbs
}
pub(super) fn repliable_content_data(
pub(super) fn repliable_content_data_v1(
rng: &mut ChaCha20Rng,
msg_len: usize,
surbs: usize,
) -> RepliableMessageContent {
RepliableMessageContent::Data {
RepliableMessageContent::Data(DataV1 {
message: random_vec_u8(rng, msg_len),
reply_surbs: reply_surbs(rng, surbs),
}
reply_surbs: reply_surbs(rng, surbs, true, LEGACY_HOPS),
})
}
pub(super) fn repliable_content_surbs(
pub(super) fn repliable_content_surbs_v1(
rng: &mut ChaCha20Rng,
surbs: usize,
) -> RepliableMessageContent {
RepliableMessageContent::AdditionalSurbs {
reply_surbs: reply_surbs(rng, surbs),
}
RepliableMessageContent::AdditionalSurbs(AdditionalSurbsV1 {
reply_surbs: reply_surbs(rng, surbs, true, LEGACY_HOPS),
})
}
pub(super) fn repliable_content_heartbeat(
pub(super) fn repliable_content_heartbeat_v1(
rng: &mut ChaCha20Rng,
surbs: usize,
) -> RepliableMessageContent {
RepliableMessageContent::Heartbeat {
additional_reply_surbs: reply_surbs(rng, surbs),
}
RepliableMessageContent::Heartbeat(HeartbeatV1 {
additional_reply_surbs: reply_surbs(rng, surbs, true, LEGACY_HOPS),
})
}
pub(super) fn reply_content_data(
@@ -649,37 +626,70 @@ mod tests {
amount: surbs,
}
}
pub(super) fn repliable_content_data_v2(
rng: &mut ChaCha20Rng,
msg_len: usize,
surbs: usize,
surb_hops: u8,
) -> RepliableMessageContent {
RepliableMessageContent::DataV2(DataV2 {
message: random_vec_u8(rng, msg_len),
reply_surbs: reply_surbs(rng, surbs, false, surb_hops),
})
}
pub(super) fn repliable_content_surbs_v2(
rng: &mut ChaCha20Rng,
surbs: usize,
surb_hops: u8,
) -> RepliableMessageContent {
RepliableMessageContent::AdditionalSurbsV2(AdditionalSurbsV2 {
reply_surbs: reply_surbs(rng, surbs, false, surb_hops),
})
}
pub(super) fn repliable_content_heartbeat_v2(
rng: &mut ChaCha20Rng,
surbs: usize,
surb_hops: u8,
) -> RepliableMessageContent {
RepliableMessageContent::HeartbeatV2(HeartbeatV2 {
additional_reply_surbs: reply_surbs(rng, surbs, false, surb_hops),
})
}
}
#[cfg(test)]
mod repliable_message {
use super::*;
use crate::requests::tests::fixtures::LEGACY_HOPS;
#[test]
fn serialized_size_matches_actual_serialization() {
fn serialized_size_matches_actual_serialization_for_v1_messages() {
let mut rng = fixtures::test_rng();
let data1 = RepliableMessage {
sender_tag: fixtures::sender_tag(&mut rng),
content: fixtures::repliable_content_data(&mut rng, 10000, 0),
content: fixtures::repliable_content_data_v1(&mut rng, 10000, 0),
};
assert_eq!(data1.serialized_size(), data1.into_bytes().len());
let data2 = RepliableMessage {
sender_tag: fixtures::sender_tag(&mut rng),
content: fixtures::repliable_content_data(&mut rng, 10, 100),
content: fixtures::repliable_content_data_v1(&mut rng, 10, 100),
};
assert_eq!(data2.serialized_size(), data2.into_bytes().len());
let data3 = RepliableMessage {
sender_tag: fixtures::sender_tag(&mut rng),
content: fixtures::repliable_content_data(&mut rng, 100000, 1000),
content: fixtures::repliable_content_data_v1(&mut rng, 100000, 1000),
};
assert_eq!(data3.serialized_size(), data3.into_bytes().len());
let additional_surbs1 = RepliableMessage {
sender_tag: fixtures::sender_tag(&mut rng),
content: fixtures::repliable_content_surbs(&mut rng, 1),
content: fixtures::repliable_content_surbs_v1(&mut rng, 1),
};
assert_eq!(
additional_surbs1.serialized_size(),
@@ -688,7 +698,7 @@ mod tests {
let additional_surbs2 = RepliableMessage {
sender_tag: fixtures::sender_tag(&mut rng),
content: fixtures::repliable_content_surbs(&mut rng, 1000),
content: fixtures::repliable_content_surbs_v1(&mut rng, 1000),
};
assert_eq!(
additional_surbs2.serialized_size(),
@@ -697,53 +707,173 @@ mod tests {
let heartbeat1 = RepliableMessage {
sender_tag: fixtures::sender_tag(&mut rng),
content: fixtures::repliable_content_heartbeat(&mut rng, 1),
content: fixtures::repliable_content_heartbeat_v1(&mut rng, 1),
};
assert_eq!(heartbeat1.serialized_size(), heartbeat1.into_bytes().len());
let heartbeat2 = RepliableMessage {
sender_tag: fixtures::sender_tag(&mut rng),
content: fixtures::repliable_content_heartbeat(&mut rng, 1000),
content: fixtures::repliable_content_heartbeat_v1(&mut rng, 1000),
};
assert_eq!(heartbeat2.serialized_size(), heartbeat2.into_bytes().len());
}
#[test]
fn serialized_size_matches_actual_serialization_for_v2_messages() {
let mut rng = fixtures::test_rng();
let data1 = RepliableMessage {
sender_tag: fixtures::sender_tag(&mut rng),
content: fixtures::repliable_content_data_v2(&mut rng, 10000, 0, LEGACY_HOPS),
};
assert_eq!(data1.serialized_size(), data1.into_bytes().len());
let data2 = RepliableMessage {
sender_tag: fixtures::sender_tag(&mut rng),
content: fixtures::repliable_content_data_v2(&mut rng, 10, 100, LEGACY_HOPS),
};
assert_eq!(data2.serialized_size(), data2.into_bytes().len());
let data3 = RepliableMessage {
sender_tag: fixtures::sender_tag(&mut rng),
content: fixtures::repliable_content_data_v2(&mut rng, 100000, 1000, LEGACY_HOPS),
};
assert_eq!(data3.serialized_size(), data3.into_bytes().len());
let data4 = RepliableMessage {
sender_tag: fixtures::sender_tag(&mut rng),
content: fixtures::repliable_content_data_v2(&mut rng, 100000, 1000, 1),
};
assert_eq!(data4.serialized_size(), data4.into_bytes().len());
let additional_surbs1 = RepliableMessage {
sender_tag: fixtures::sender_tag(&mut rng),
content: fixtures::repliable_content_surbs_v2(&mut rng, 1, LEGACY_HOPS),
};
assert_eq!(
additional_surbs1.serialized_size(),
additional_surbs1.into_bytes().len()
);
let additional_surbs2 = RepliableMessage {
sender_tag: fixtures::sender_tag(&mut rng),
content: fixtures::repliable_content_surbs_v2(&mut rng, 1000, LEGACY_HOPS),
};
assert_eq!(
additional_surbs2.serialized_size(),
additional_surbs2.into_bytes().len()
);
let additional_surbs3 = RepliableMessage {
sender_tag: fixtures::sender_tag(&mut rng),
content: fixtures::repliable_content_surbs_v2(&mut rng, 1000, 1),
};
assert_eq!(
additional_surbs3.serialized_size(),
additional_surbs3.into_bytes().len()
);
let heartbeat1 = RepliableMessage {
sender_tag: fixtures::sender_tag(&mut rng),
content: fixtures::repliable_content_heartbeat_v2(&mut rng, 1, LEGACY_HOPS),
};
assert_eq!(heartbeat1.serialized_size(), heartbeat1.into_bytes().len());
let heartbeat2 = RepliableMessage {
sender_tag: fixtures::sender_tag(&mut rng),
content: fixtures::repliable_content_heartbeat_v2(&mut rng, 1000, LEGACY_HOPS),
};
assert_eq!(heartbeat2.serialized_size(), heartbeat2.into_bytes().len());
let heartbeat3 = RepliableMessage {
sender_tag: fixtures::sender_tag(&mut rng),
content: fixtures::repliable_content_heartbeat_v2(&mut rng, 1000, 1),
};
assert_eq!(heartbeat3.serialized_size(), heartbeat3.into_bytes().len());
}
}
#[cfg(test)]
mod repliable_message_content {
use super::*;
use crate::requests::tests::fixtures::LEGACY_HOPS;
#[test]
fn serialized_size_matches_actual_serialization() {
fn serialized_size_matches_actual_serialization_for_v1_messages() {
let mut rng = fixtures::test_rng();
let data1 = fixtures::repliable_content_data(&mut rng, 10000, 0);
let data1 = fixtures::repliable_content_data_v1(&mut rng, 10000, 0);
assert_eq!(data1.serialized_size(), data1.into_bytes().len());
let data2 = fixtures::repliable_content_data(&mut rng, 10, 100);
let data2 = fixtures::repliable_content_data_v1(&mut rng, 10, 100);
assert_eq!(data2.serialized_size(), data2.into_bytes().len());
let data3 = fixtures::repliable_content_data(&mut rng, 100000, 1000);
let data3 = fixtures::repliable_content_data_v1(&mut rng, 100000, 1000);
assert_eq!(data3.serialized_size(), data3.into_bytes().len());
let additional_surbs1 = fixtures::repliable_content_surbs(&mut rng, 1);
let additional_surbs1 = fixtures::repliable_content_surbs_v1(&mut rng, 1);
assert_eq!(
additional_surbs1.serialized_size(),
additional_surbs1.into_bytes().len()
);
let additional_surbs2 = fixtures::repliable_content_surbs(&mut rng, 1000);
let additional_surbs2 = fixtures::repliable_content_surbs_v1(&mut rng, 1000);
assert_eq!(
additional_surbs2.serialized_size(),
additional_surbs2.into_bytes().len()
);
let heartbeat1 = fixtures::repliable_content_heartbeat(&mut rng, 1);
let heartbeat1 = fixtures::repliable_content_heartbeat_v1(&mut rng, 1);
assert_eq!(heartbeat1.serialized_size(), heartbeat1.into_bytes().len());
let heartbeat2 = fixtures::repliable_content_heartbeat(&mut rng, 1000);
let heartbeat2 = fixtures::repliable_content_heartbeat_v1(&mut rng, 1000);
assert_eq!(heartbeat2.serialized_size(), heartbeat2.into_bytes().len());
}
#[test]
fn serialized_size_matches_actual_serialization_for_v2_messages() {
let mut rng = fixtures::test_rng();
let data1 = fixtures::repliable_content_data_v2(&mut rng, 10000, 0, LEGACY_HOPS);
assert_eq!(data1.serialized_size(), data1.into_bytes().len());
let data2 = fixtures::repliable_content_data_v2(&mut rng, 10, 100, LEGACY_HOPS);
assert_eq!(data2.serialized_size(), data2.into_bytes().len());
let data3 = fixtures::repliable_content_data_v2(&mut rng, 100000, 1000, LEGACY_HOPS);
assert_eq!(data3.serialized_size(), data3.into_bytes().len());
let data4 = fixtures::repliable_content_data_v2(&mut rng, 100000, 1000, 1);
assert_eq!(data4.serialized_size(), data4.into_bytes().len());
let additional_surbs1 = fixtures::repliable_content_surbs_v2(&mut rng, 1, LEGACY_HOPS);
assert_eq!(
additional_surbs1.serialized_size(),
additional_surbs1.into_bytes().len()
);
let additional_surbs2 =
fixtures::repliable_content_surbs_v2(&mut rng, 1000, LEGACY_HOPS);
assert_eq!(
additional_surbs2.serialized_size(),
additional_surbs2.into_bytes().len()
);
let additional_surbs3 = fixtures::repliable_content_surbs_v2(&mut rng, 1000, 1);
assert_eq!(
additional_surbs3.serialized_size(),
additional_surbs3.into_bytes().len()
);
let heartbeat1 = fixtures::repliable_content_heartbeat_v2(&mut rng, 1, LEGACY_HOPS);
assert_eq!(heartbeat1.serialized_size(), heartbeat1.into_bytes().len());
let heartbeat2 = fixtures::repliable_content_heartbeat_v2(&mut rng, 1000, LEGACY_HOPS);
assert_eq!(heartbeat2.serialized_size(), heartbeat2.into_bytes().len());
let heartbeat3 = fixtures::repliable_content_heartbeat_v2(&mut rng, 1000, 1);
assert_eq!(heartbeat3.serialized_size(), heartbeat3.into_bytes().len());
}
}
#[cfg(test)]
@@ -0,0 +1,176 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::requests::InvalidReplyRequestError;
use crate::ReplySurb;
use nym_sphinx_types::PAYLOAD_KEY_SIZE;
use std::fmt::Display;
use std::mem;
use tracing::{error, warn};
const fn v1_reply_surb_serialised_len() -> usize {
// the SURB itself consists of SURB_header, first hop address and set of payload keys
// for each hop (3x mix + egress)
ReplySurb::BASE_OVERHEAD + 4 * PAYLOAD_KEY_SIZE
}
fn v1_reply_surbs_serialised_len(surbs: &[ReplySurb]) -> usize {
// sanity checks; this should probably be removed later on
if let Some(reply_surb) = surbs.first() {
if reply_surb.surb.uses_key_seeds() {
error!("using v1 surbs encoding with updated structure - the surbs will be unusable")
}
}
// when serialising surbs are always prepended with u32-encoded count
4 + surbs.len() * v1_reply_surb_serialised_len()
}
// this recovery code is shared between all legacy variants containing reply surbs
// NUM_SURBS (u32) || SURB_DATA
fn recover_reply_surbs_v1(
bytes: &[u8],
) -> Result<(Vec<ReplySurb>, usize), InvalidReplyRequestError> {
let mut consumed = mem::size_of::<u32>();
if bytes.len() < consumed {
return Err(InvalidReplyRequestError::RequestTooShortToDeserialize);
}
let num_surbs = u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
let surb_size = v1_reply_surb_serialised_len();
if bytes[consumed..].len() < num_surbs as usize * surb_size {
return Err(InvalidReplyRequestError::RequestTooShortToDeserialize);
}
let mut reply_surbs = Vec::with_capacity(num_surbs as usize);
for _ in 0..num_surbs as usize {
let surb_bytes = &bytes[consumed..consumed + surb_size];
let reply_surb = ReplySurb::from_bytes(surb_bytes)?;
reply_surbs.push(reply_surb);
consumed += surb_size;
}
Ok((reply_surbs, consumed))
}
// length (u32) prefixed reply surbs with legacy serialisation of 4 hops and full payload keys attached
fn reply_surbs_bytes_v1(reply_surbs: &[ReplySurb]) -> impl Iterator<Item = u8> + use<'_> {
let num_surbs = reply_surbs.len() as u32;
num_surbs
.to_be_bytes()
.into_iter()
.chain(reply_surbs.iter().flat_map(|s| s.to_bytes()))
}
#[derive(Debug)]
pub struct DataV1 {
pub message: Vec<u8>,
pub reply_surbs: Vec<ReplySurb>,
}
impl Display for DataV1 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(
f,
"V1 repliable {:.2} kiB data message with {} reply surbs attached",
self.message.len() as f64 / 1024.0,
self.reply_surbs.len(),
)
}
}
#[derive(Debug)]
pub struct AdditionalSurbsV1 {
pub reply_surbs: Vec<ReplySurb>,
}
impl Display for AdditionalSurbsV1 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(
f,
"V1 repliable additional surbs message ({} reply surbs attached)",
self.reply_surbs.len(),
)
}
}
#[derive(Debug)]
pub struct HeartbeatV1 {
pub additional_reply_surbs: Vec<ReplySurb>,
}
impl Display for HeartbeatV1 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(
f,
"V1 repliable heartbeat message ({} reply surbs attached)",
self.additional_reply_surbs.len(),
)
}
}
impl DataV1 {
pub fn into_bytes(self) -> Vec<u8> {
reply_surbs_bytes_v1(&self.reply_surbs)
.chain(self.message)
.collect()
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, InvalidReplyRequestError> {
let (reply_surbs, n) = recover_reply_surbs_v1(bytes)?;
Ok(DataV1 {
message: bytes[n..].to_vec(),
reply_surbs,
})
}
pub fn serialized_len(&self) -> usize {
v1_reply_surbs_serialised_len(&self.reply_surbs) + self.message.len()
}
}
impl AdditionalSurbsV1 {
pub fn into_bytes(self) -> Vec<u8> {
reply_surbs_bytes_v1(&self.reply_surbs).collect()
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, InvalidReplyRequestError> {
let (reply_surbs, n) = recover_reply_surbs_v1(bytes)?;
if n != bytes.len() {
let trailing = bytes.len() - n;
warn!("trailing {trailing} bytes after v1 additional surbs message");
}
Ok(AdditionalSurbsV1 { reply_surbs })
}
pub fn serialized_len(&self) -> usize {
v1_reply_surbs_serialised_len(&self.reply_surbs)
}
}
impl HeartbeatV1 {
pub fn into_bytes(self) -> Vec<u8> {
reply_surbs_bytes_v1(&self.additional_reply_surbs).collect()
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, InvalidReplyRequestError> {
let (additional_reply_surbs, n) = recover_reply_surbs_v1(bytes)?;
if n != bytes.len() {
let trailing = bytes.len() - n;
warn!("trailing {trailing} bytes after v1 heartbeat message");
}
Ok(HeartbeatV1 {
additional_reply_surbs,
})
}
pub fn serialized_len(&self) -> usize {
v1_reply_surbs_serialised_len(&self.additional_reply_surbs)
}
}
@@ -0,0 +1,187 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::requests::InvalidReplyRequestError;
use crate::ReplySurb;
use nym_sphinx_types::constants::PAYLOAD_KEY_SEED_SIZE;
use std::fmt::Display;
use std::iter::once;
use tracing::{error, warn};
const fn v2_reply_surb_serialised_len(num_hops: u8) -> usize {
ReplySurb::BASE_OVERHEAD + num_hops as usize * PAYLOAD_KEY_SEED_SIZE
}
// sphinx doesn't support more than 5 hops (so cast to u8 is safe)
// ASSUMPTION: all surbs are generated with the same parameters (if they're not, then the client is hurting itself)
fn reply_surbs_hops(reply_surbs: &[ReplySurb]) -> u8 {
reply_surbs
.first()
.map(|reply_surb| reply_surb.surb.materials_count() as u8)
.unwrap_or_default()
}
fn v2_reply_surbs_serialised_len(surbs: &[ReplySurb]) -> usize {
let num_surbs = surbs.len();
let num_hops = reply_surbs_hops(surbs);
// sanity checks; this should probably be removed later on
if let Some(reply_surb) = surbs.first() {
if !reply_surb.surb.uses_key_seeds() {
error!("using v2 surbs encoding with legacy structure - the surbs will be unusable")
}
}
// when serialising surbs are always prepended with u16-encoded count an u8-encoded number of hops
3 + num_surbs * v2_reply_surb_serialised_len(num_hops)
}
// NUM_SURBS (u16) || HOPS (u8) || SURB_DATA
fn recover_reply_surbs_v2(
bytes: &[u8],
) -> Result<(Vec<ReplySurb>, usize), InvalidReplyRequestError> {
if bytes.len() < 2 {
return Err(InvalidReplyRequestError::RequestTooShortToDeserialize);
}
// we're not attaching more than 65k surbs...
let num_surbs = u16::from_be_bytes([bytes[0], bytes[1]]);
let num_hops = bytes[2];
let mut consumed = 3;
let surb_size = ReplySurb::v2_serialised_len(num_hops);
if bytes[consumed..].len() < num_surbs as usize * surb_size {
return Err(InvalidReplyRequestError::RequestTooShortToDeserialize);
}
let mut reply_surbs = Vec::with_capacity(num_surbs as usize);
for _ in 0..num_surbs as usize {
let surb_bytes = &bytes[consumed..consumed + surb_size];
let reply_surb = ReplySurb::from_bytes(surb_bytes)?;
reply_surbs.push(reply_surb);
consumed += surb_size;
}
Ok((reply_surbs, consumed))
}
fn reply_surbs_bytes_v2(reply_surbs: &[ReplySurb]) -> impl Iterator<Item = u8> + use<'_> {
let num_surbs = reply_surbs.len() as u16;
let num_hops = reply_surbs_hops(reply_surbs);
num_surbs
.to_be_bytes()
.into_iter()
.chain(once(num_hops))
.chain(reply_surbs.iter().flat_map(|surb| surb.to_bytes()))
}
#[derive(Debug)]
pub struct DataV2 {
pub message: Vec<u8>,
pub reply_surbs: Vec<ReplySurb>,
}
impl Display for DataV2 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(
f,
"V2 repliable {:.2} kiB data message with {} reply surbs attached",
self.message.len() as f64 / 1024.0,
self.reply_surbs.len(),
)
}
}
#[derive(Debug)]
pub struct AdditionalSurbsV2 {
pub reply_surbs: Vec<ReplySurb>,
}
impl Display for AdditionalSurbsV2 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(
f,
"V2 repliable additional surbs message ({} reply surbs attached)",
self.reply_surbs.len(),
)
}
}
#[derive(Debug)]
pub struct HeartbeatV2 {
pub additional_reply_surbs: Vec<ReplySurb>,
}
impl Display for HeartbeatV2 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(
f,
"V2 repliable heartbeat message ({} reply surbs attached)",
self.additional_reply_surbs.len(),
)
}
}
impl DataV2 {
pub fn into_bytes(self) -> Vec<u8> {
reply_surbs_bytes_v2(&self.reply_surbs)
.chain(self.message)
.collect()
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, InvalidReplyRequestError> {
let (reply_surbs, n) = recover_reply_surbs_v2(bytes)?;
Ok(DataV2 {
message: bytes[n..].to_vec(),
reply_surbs,
})
}
pub fn serialized_len(&self) -> usize {
v2_reply_surbs_serialised_len(&self.reply_surbs) + self.message.len()
}
}
impl AdditionalSurbsV2 {
pub fn into_bytes(self) -> Vec<u8> {
reply_surbs_bytes_v2(&self.reply_surbs).collect()
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, InvalidReplyRequestError> {
let (reply_surbs, n) = recover_reply_surbs_v2(bytes)?;
if n != bytes.len() {
let trailing = bytes.len() - n;
warn!("trailing {trailing} bytes after v2 additional surbs message");
}
Ok(AdditionalSurbsV2 { reply_surbs })
}
pub fn serialized_len(&self) -> usize {
v2_reply_surbs_serialised_len(&self.reply_surbs)
}
}
impl HeartbeatV2 {
pub fn into_bytes(self) -> Vec<u8> {
reply_surbs_bytes_v2(&self.additional_reply_surbs).collect()
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, InvalidReplyRequestError> {
let (additional_reply_surbs, n) = recover_reply_surbs_v2(bytes)?;
if n != bytes.len() {
let trailing = bytes.len() - n;
warn!("trailing {trailing} bytes after v2 heartbeat message");
}
Ok(HeartbeatV2 {
additional_reply_surbs,
})
}
pub fn serialized_len(&self) -> usize {
v2_reply_surbs_serialised_len(&self.additional_reply_surbs)
}
}
+7 -9
View File
@@ -34,6 +34,7 @@ pub enum CoverMessageError {
pub fn generate_loop_cover_surb_ack<R>(
rng: &mut R,
use_legacy_sphinx_format: bool,
topology: &NymRouteProvider,
ack_key: &AckKey,
full_address: &Recipient,
@@ -45,6 +46,7 @@ where
{
Ok(SurbAck::construct(
rng,
use_legacy_sphinx_format,
full_address,
ack_key,
COVER_FRAG_ID.to_bytes(),
@@ -57,6 +59,7 @@ where
#[allow(clippy::too_many_arguments)]
pub fn generate_loop_cover_packet<R>(
rng: &mut R,
use_legacy_sphinx_format: bool,
topology: &NymRouteProvider,
ack_key: &AckKey,
full_address: &Recipient,
@@ -71,6 +74,7 @@ where
// we don't care about total ack delay - we will not be retransmitting it anyway
let (_, ack_bytes) = generate_loop_cover_surb_ack(
rng,
use_legacy_sphinx_format,
topology,
ack_key,
full_address,
@@ -126,14 +130,7 @@ where
// once merged, that's an easy rng injection point for sphinx packets : )
let packet = match packet_type {
PacketType::Mix => NymPacket::sphinx_build(
packet_size.payload_size(),
packet_payload,
&route,
&destination,
&delays,
)?,
#[allow(deprecated)]
PacketType::Vpn => NymPacket::sphinx_build(
use_legacy_sphinx_format,
packet_size.payload_size(),
packet_payload,
&route,
@@ -171,6 +168,7 @@ pub fn is_cover(data: &[u8]) -> bool {
#[cfg(test)]
mod tests {
use super::*;
use std::iter::repeat_n;
#[test]
fn is_cover_works_for_identical_input() {
@@ -182,7 +180,7 @@ mod tests {
let input: Vec<_> = LOOP_COVER_MESSAGE_PAYLOAD
.iter()
.cloned()
.chain(std::iter::repeat(42).take(100))
.chain(repeat_n(42, 100))
.collect();
assert!(is_cover(&input))
}
+1 -2
View File
@@ -11,12 +11,11 @@ repository = { workspace = true }
bytes = { workspace = true }
tokio-util = { workspace = true, features = ["codec"] }
thiserror = { workspace = true }
log = { workspace = true }
tracing = { workspace = true }
nym-sphinx-types = { path = "../types", features = ["sphinx", "outfox"] }
nym-sphinx-params = { path = "../params", features = ["sphinx", "outfox"] }
nym-sphinx-forwarding = { path = "../forwarding" }
nym-metrics = { path = "../../nym-metrics" }
nym-sphinx-addressing = { path = "../addressing" }
nym-sphinx-acknowledgements = { path = "../acknowledgements" }
+33 -95
View File
@@ -5,6 +5,7 @@ use crate::packet::{FramedNymPacket, Header};
use bytes::{Buf, BufMut, BytesMut};
use nym_sphinx_params::packet_sizes::{InvalidPacketSize, PacketSize};
use nym_sphinx_params::packet_types::InvalidPacketType;
use nym_sphinx_params::packet_version::{InvalidPacketVersion, PacketVersion};
use nym_sphinx_params::PacketType;
use nym_sphinx_types::{NymPacket, NymPacketError};
use std::io;
@@ -13,16 +14,25 @@ use tokio_util::codec::{Decoder, Encoder};
#[derive(Error, Debug)]
pub enum NymCodecError {
#[error("the packet size information was malformed - {0}")]
#[error("the packet size information was malformed: {0}")]
InvalidPacketSize(#[from] InvalidPacketSize),
#[error("the packet mode information was malformed - {0}")]
#[error("the packet mode information was malformed: {0}")]
InvalidPacketType(#[from] InvalidPacketType),
#[error("encountered an IO error - {0}")]
#[error("the packet version information was malformed: {0}")]
InvalidPacketVersion(#[from] InvalidPacketVersion),
#[error("received unsupported packet version {received}. max supported is {max_supported}")]
UnsupportedPacketVersion {
received: PacketVersion,
max_supported: PacketVersion,
},
#[error("encountered an IO error: {0}")]
IoError(#[from] io::Error),
#[error("encountered a packet error - {0}")]
#[error("encountered a packet error: {0}")]
NymPacket(#[from] NymPacketError),
#[error("could not convert to bytes")]
@@ -56,7 +66,7 @@ impl Decoder for NymCodec {
if src.is_empty() {
// can't do anything if we have no bytes, but let's reserve enough for the most
// conservative case, i.e. receiving an ack packet
src.reserve(Header::LEGACY_SIZE + PacketSize::AckPacket.size());
src.reserve(Header::SIZE + PacketSize::AckPacket.size());
return Ok(None);
}
@@ -68,7 +78,7 @@ impl Decoder for NymCodec {
};
let packet_size = header.packet_size.size();
let frame_len = header.size() + packet_size;
let frame_len = Header::SIZE + packet_size;
if src.len() < frame_len {
// we don't have enough bytes to read the rest of frame
@@ -77,7 +87,7 @@ impl Decoder for NymCodec {
}
// advance buffer past the header - at this point we have enough bytes
src.advance(header.size());
src.advance(Header::SIZE);
let packet_bytes = src.split_to(packet_size);
let packet = if let Some(slice) = packet_bytes.get(..) {
// here it could be debatable whether stream is corrupt or not,
@@ -85,8 +95,6 @@ impl Decoder for NymCodec {
match header.packet_type {
PacketType::Outfox => NymPacket::outfox_from_bytes(slice)?,
PacketType::Mix => NymPacket::sphinx_from_bytes(slice)?,
#[allow(deprecated)]
PacketType::Vpn => NymPacket::sphinx_from_bytes(slice)?,
}
} else {
return Ok(None);
@@ -106,11 +114,11 @@ impl Decoder for NymCodec {
// we also assume the next packet coming from the same client will use exactly the same versioning
// as the current packet
let mut allocate_for_next_packet = header.size() + PacketSize::AckPacket.size();
let mut allocate_for_next_packet = Header::SIZE + PacketSize::AckPacket.size();
if !src.is_empty() {
match Header::decode(src) {
Ok(Some(next_header)) => {
allocate_for_next_packet = next_header.size() + next_header.packet_size.size();
allocate_for_next_packet = Header::SIZE + next_header.packet_size.size();
}
Ok(None) => {
// we don't have enough information to know how much to reserve, fallback to the ack case
@@ -201,8 +209,15 @@ mod packet_encoding {
SphinxDelay::new_from_nanos(42),
SphinxDelay::new_from_nanos(42),
];
NymPacket::sphinx_build(size.payload_size(), b"foomp", &route, &destination, &delays)
.unwrap()
NymPacket::sphinx_build(
false,
size.payload_size(),
b"foomp",
&route,
&destination,
&delays,
)
.unwrap()
}
#[test]
@@ -254,34 +269,10 @@ mod packet_encoding {
assert!(NymCodec.decode(&mut empty_bytes).unwrap().is_none());
assert_eq!(
empty_bytes.capacity(),
Header::LEGACY_SIZE + PacketSize::AckPacket.size()
Header::SIZE + PacketSize::AckPacket.size()
);
}
#[test]
fn for_bytes_with_legacy_header() {
// if header gets decoded there should be enough bytes for the entire frame
let packet_sizes = vec![
PacketSize::AckPacket,
PacketSize::RegularPacket,
PacketSize::ExtendedPacket8,
PacketSize::ExtendedPacket16,
PacketSize::ExtendedPacket32,
];
for packet_size in packet_sizes {
let header = Header {
packet_version: PacketVersion::Legacy,
packet_size,
..Default::default()
};
let mut bytes = BytesMut::new();
header.encode(&mut bytes);
assert!(NymCodec.decode(&mut bytes).unwrap().is_none());
assert_eq!(bytes.capacity(), Header::LEGACY_SIZE + packet_size.size())
}
}
#[test]
fn for_bytes_with_versioned_header() {
// if header gets decoded there should be enough bytes for the entire frame
@@ -294,7 +285,7 @@ mod packet_encoding {
];
for packet_size in packet_sizes {
let header = Header {
packet_version: PacketVersion::Versioned(123),
packet_version: PacketVersion::new(),
packet_size,
..Default::default()
};
@@ -302,33 +293,10 @@ mod packet_encoding {
header.encode(&mut bytes);
assert!(NymCodec.decode(&mut bytes).unwrap().is_none());
assert_eq!(
bytes.capacity(),
Header::VERSIONED_SIZE + packet_size.size()
)
assert_eq!(bytes.capacity(), Header::SIZE + packet_size.size())
}
}
#[test]
fn for_full_frame_with_legacy_header() {
// if full frame is used exactly, there should be enough space for header + ack packet
let packet = FramedNymPacket {
header: Header {
packet_version: PacketVersion::Legacy,
..Default::default()
},
packet: make_valid_sphinx_packet(Default::default()),
};
let mut bytes = BytesMut::new();
NymCodec.encode(packet, &mut bytes).unwrap();
assert!(NymCodec.decode(&mut bytes).unwrap().is_some());
assert_eq!(
bytes.capacity(),
Header::LEGACY_SIZE + PacketSize::AckPacket.size()
);
}
#[test]
fn for_full_frame_with_versioned_header() {
// if full frame is used exactly, there should be enough space for header + ack packet
@@ -342,40 +310,10 @@ mod packet_encoding {
assert!(NymCodec.decode(&mut bytes).unwrap().is_some());
assert_eq!(
bytes.capacity(),
Header::VERSIONED_SIZE + PacketSize::AckPacket.size()
Header::SIZE + PacketSize::AckPacket.size()
);
}
#[test]
fn for_full_frame_with_extra_bytes_with_legacy_header() {
// if there was at least 2 byte left, there should be enough space for entire next frame
let packet_sizes = vec![
PacketSize::AckPacket,
PacketSize::RegularPacket,
PacketSize::ExtendedPacket8,
PacketSize::ExtendedPacket16,
PacketSize::ExtendedPacket32,
];
for packet_size in packet_sizes {
let first_packet = FramedNymPacket {
header: Header {
packet_version: PacketVersion::Legacy,
..Default::default()
},
packet: make_valid_sphinx_packet(Default::default()),
};
let mut bytes = BytesMut::new();
NymCodec.encode(first_packet, &mut bytes).unwrap();
bytes.put_u8(packet_size as u8);
bytes.put_u8(PacketType::default() as u8);
assert!(NymCodec.decode(&mut bytes).unwrap().is_some());
assert!(bytes.capacity() >= Header::LEGACY_SIZE + packet_size.size())
}
}
#[test]
fn for_full_frame_with_extra_bytes_with_versioned_header() {
// if there was at least 3 byte left, there should be enough space for entire next frame
@@ -395,7 +333,7 @@ mod packet_encoding {
let mut bytes = BytesMut::new();
NymCodec.encode(first_packet, &mut bytes).unwrap();
bytes.put_u8(PacketVersion::new_versioned(123).as_u8().unwrap());
bytes.put_u8(PacketVersion::new().as_u8());
bytes.put_u8(packet_size as u8);
bytes.put_u8(PacketType::default() as u8);
assert!(NymCodec.decode(&mut bytes).unwrap().is_some());
+62 -59
View File
@@ -4,7 +4,7 @@
use crate::codec::NymCodecError;
use bytes::{BufMut, BytesMut};
use nym_sphinx_params::packet_sizes::PacketSize;
use nym_sphinx_params::packet_version::PacketVersion;
use nym_sphinx_params::packet_version::{PacketVersion, CURRENT_PACKET_VERSION};
use nym_sphinx_params::PacketType;
use nym_sphinx_types::NymPacket;
@@ -47,6 +47,14 @@ impl FramedNymPacket {
pub fn into_inner(self) -> NymPacket {
self.packet
}
pub fn packet(&self) -> &NymPacket {
&self.packet
}
pub fn is_sphinx(&self) -> bool {
self.packet.is_sphinx()
}
}
// Contains any metadata that might be useful for sending between mix nodes.
@@ -73,8 +81,7 @@ pub struct Header {
}
impl Header {
pub(crate) const LEGACY_SIZE: usize = 2;
pub(crate) const VERSIONED_SIZE: usize = 3;
pub(crate) const SIZE: usize = 3;
pub fn outfox() -> Header {
Header {
@@ -84,53 +91,39 @@ impl Header {
}
}
pub(crate) fn size(&self) -> usize {
if self.packet_version.is_legacy() {
Self::LEGACY_SIZE
} else {
Self::VERSIONED_SIZE
}
}
pub(crate) fn encode(&self, dst: &mut BytesMut) {
// we reserve one byte for `packet_size` and the other for `mode`
dst.reserve(Self::LEGACY_SIZE);
if let Some(version) = self.packet_version.as_u8() {
dst.reserve(Self::VERSIONED_SIZE);
dst.put_u8(version)
}
dst.reserve(Self::SIZE);
dst.put_u8(self.packet_version.as_u8());
dst.put_u8(self.packet_size as u8);
dst.put_u8(self.packet_type as u8);
// reserve bytes for the actual packet
dst.reserve(self.packet_size.size());
}
pub(crate) fn decode(src: &mut BytesMut) -> Result<Option<Self>, NymCodecError> {
if src.len() < Self::LEGACY_SIZE {
if src.len() < Self::SIZE {
// can't do anything if we don't have enough bytes - but reserve enough for the next call
src.reserve(Self::LEGACY_SIZE);
src.reserve(Self::SIZE);
return Ok(None);
}
let packet_version = PacketVersion::from(src[0]);
if packet_version.is_legacy() {
Ok(Some(Header {
packet_version,
packet_size: PacketSize::try_from(src[0])?,
packet_type: PacketType::try_from(src[1])?,
}))
} else if src.len() < Self::VERSIONED_SIZE {
// we're missing that 1 byte to read the full header...
src.reserve(Self::VERSIONED_SIZE);
Ok(None)
} else {
Ok(Some(Header {
packet_version,
packet_size: PacketSize::try_from(src[1])?,
packet_type: PacketType::try_from(src[2])?,
}))
let packet_version = PacketVersion::try_from(src[0])?;
if packet_version > CURRENT_PACKET_VERSION {
// received an unsupported packet version - we don't know how it's meant to look like!
// (this is in preparation for the dual support of breaking sphinx changes)
return Err(NymCodecError::UnsupportedPacketVersion {
received: packet_version,
max_supported: CURRENT_PACKET_VERSION,
});
}
Ok(Some(Header {
packet_version,
packet_size: PacketSize::try_from(src[1])?,
packet_type: PacketType::try_from(src[2])?,
}))
}
}
@@ -157,7 +150,7 @@ mod header_encoding {
// due to the hack used to get legacy mode compatibility
let mut bytes = BytesMut::from(
[
PacketVersion::new_versioned(123).as_u8().unwrap(),
PacketVersion::new().as_u8(),
unknown_packet_size,
PacketType::default() as u8,
]
@@ -172,7 +165,14 @@ mod header_encoding {
// make sure this is still 'unknown' for if we make changes in the future
assert!(PacketType::try_from(unknown_packet_type).is_err());
let mut bytes = BytesMut::from([PacketSize::default() as u8, unknown_packet_type].as_ref());
let mut bytes = BytesMut::from(
[
PacketVersion::new().as_u8(),
PacketSize::default() as u8,
unknown_packet_type,
]
.as_ref(),
);
assert!(Header::decode(&mut bytes).is_err())
}
@@ -181,16 +181,16 @@ mod header_encoding {
let mut empty_bytes = BytesMut::new();
let decode_attempt_1 = Header::decode(&mut empty_bytes).unwrap();
assert!(decode_attempt_1.is_none());
assert!(empty_bytes.capacity() > Header::LEGACY_SIZE);
assert!(empty_bytes.capacity() > Header::SIZE);
let mut empty_bytes = BytesMut::with_capacity(1);
let decode_attempt_2 = Header::decode(&mut empty_bytes).unwrap();
assert!(decode_attempt_2.is_none());
assert!(empty_bytes.capacity() > Header::LEGACY_SIZE);
assert!(empty_bytes.capacity() > Header::SIZE);
}
#[test]
fn header_encoding_reserves_enough_bytes_for_full_sphinx_packet_in_legacy_mode() {
fn header_encoding_reserves_enough_bytes_for_full_sphinx_packet_() {
let packet_sizes = vec![
PacketSize::AckPacket,
PacketSize::RegularPacket,
@@ -200,7 +200,7 @@ mod header_encoding {
];
for packet_size in packet_sizes {
let header = Header {
packet_version: PacketVersion::Legacy,
packet_version: PacketVersion::new(),
packet_size,
..Default::default()
};
@@ -211,23 +211,26 @@ mod header_encoding {
}
#[test]
fn header_encoding_reserves_enough_bytes_for_full_sphinx_packet_in_versioned_mode() {
let packet_sizes = vec![
PacketSize::AckPacket,
PacketSize::RegularPacket,
PacketSize::ExtendedPacket8,
PacketSize::ExtendedPacket16,
PacketSize::ExtendedPacket32,
];
for packet_size in packet_sizes {
let header = Header {
packet_version: PacketVersion::Versioned(123),
packet_size,
..Default::default()
};
let mut bytes = BytesMut::new();
header.encode(&mut bytes);
assert_eq!(bytes.capacity(), bytes.len() + packet_size.size())
fn header_decoding_will_reject_future_versions() {
let future_version = PacketVersion::try_from(123).unwrap();
let unchecked_header = Header {
packet_version: future_version,
packet_size: PacketSize::RegularPacket,
packet_type: PacketType::Mix,
};
let mut bytes = BytesMut::new();
unchecked_header.encode(&mut bytes);
match Header::decode(&mut bytes).unwrap_err() {
NymCodecError::UnsupportedPacketVersion {
received,
max_supported,
} => {
assert_eq!(received, future_version);
assert_eq!(max_supported, CURRENT_PACKET_VERSION);
}
_ => panic!("unexpected error variant"),
}
}
}
+192 -91
View File
@@ -1,18 +1,20 @@
use log::{debug, error, info, trace};
// Copyright 2021-2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::packet::FramedNymPacket;
use nym_sphinx_acknowledgements::surb_ack::{SurbAck, SurbAckRecoveryError};
use nym_sphinx_addressing::nodes::{NymNodeRoutingAddress, NymNodeRoutingAddressError};
use nym_sphinx_forwarding::packet::MixPacket;
use nym_sphinx_params::{PacketSize, PacketType};
use nym_sphinx_types::header::shared_secret::ExpandedSharedSecret;
use nym_sphinx_types::{
Delay as SphinxDelay, DestinationAddressBytes, NodeAddressBytes, NymPacket, NymPacketError,
NymProcessedPacket, OutfoxError, PrivateKey, ProcessedPacketData, SphinxError,
Version as SphinxPacketVersion,
NymProcessedPacket, OutfoxError, OutfoxProcessedPacket, PrivateKey, ProcessedPacketData,
SphinxError, Version as SphinxPacketVersion, REPLAY_TAG_SIZE,
};
use std::fmt::Display;
use thiserror::Error;
use crate::packet::FramedNymPacket;
use nym_metrics::nanos;
use nym_sphinx_forwarding::packet::MixPacket;
use tracing::{debug, error, info, trace};
#[derive(Debug)]
pub enum MixProcessingResultData {
@@ -49,6 +51,26 @@ pub struct MixProcessingResult {
pub processing_data: MixProcessingResultData,
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug)]
pub enum PartialMixProcessingResult {
Sphinx {
expanded_shared_secret: ExpandedSharedSecret,
},
Outfox,
}
impl PartialMixProcessingResult {
pub fn replay_tag(&self) -> Option<&[u8; REPLAY_TAG_SIZE]> {
match self {
PartialMixProcessingResult::Sphinx {
expanded_shared_secret,
} => Some(expanded_shared_secret.replay_tag()),
PartialMixProcessingResult::Outfox => None,
}
}
}
type ForwardAck = MixPacket;
#[derive(Debug)]
@@ -75,59 +97,192 @@ pub enum PacketProcessingError {
#[error("failed to recover the expected SURB-Ack packet: {0}")]
MalformedSurbAck(#[from] SurbAckRecoveryError),
#[error("the received packet was set to use the very old and very much deprecated 'VPN' mode")]
ReceivedOldTypeVpnPacket,
#[error("failed to process received outfox packet: {0}")]
OutfoxProcessingError(#[from] OutfoxError),
#[error("attempted to partially process an outfox packet")]
PartialOutfoxProcessing,
#[error("this packet has already been processed before")]
PacketReplay,
}
pub struct PartiallyUnwrappedPacket {
received_data: FramedNymPacket,
partial_result: PartialMixProcessingResult,
}
impl PartiallyUnwrappedPacket {
/// Attempt to partially unwrap received packet to derive relevant keys
/// to allow us to reject it for obvious bad behaviour (like replay or invalid mac)
/// without performing full processing
pub fn new(
received_data: FramedNymPacket,
sphinx_key: &PrivateKey,
) -> Result<Self, PacketProcessingError> {
let partial_result = match received_data.packet() {
NymPacket::Sphinx(packet) => {
let expanded_shared_secret =
packet.header.compute_expanded_shared_secret(sphinx_key);
// don't continue if the header is malformed
packet
.header
.ensure_header_integrity(&expanded_shared_secret)?;
PartialMixProcessingResult::Sphinx {
expanded_shared_secret,
}
}
NymPacket::Outfox(_) => PartialMixProcessingResult::Outfox,
};
Ok(PartiallyUnwrappedPacket {
received_data,
partial_result,
})
}
pub fn finalise_unwrapping(self) -> Result<MixProcessingResult, PacketProcessingError> {
let packet_size = self.received_data.packet_size();
let packet_type = self.received_data.packet_type();
let packet = self.received_data.into_inner();
// currently partial unwrapping is only implemented for sphinx packets.
// attempting to call it for anything else should result in a failure
let (
NymPacket::Sphinx(packet),
PartialMixProcessingResult::Sphinx {
expanded_shared_secret,
},
) = (packet, self.partial_result)
else {
return Err(PacketProcessingError::PartialOutfoxProcessing);
};
let processed_packet = packet.process_with_expanded_secret(&expanded_shared_secret)?;
wrap_processed_sphinx_packet(processed_packet, packet_size, packet_type)
}
pub fn replay_tag(&self) -> Option<&[u8; REPLAY_TAG_SIZE]> {
self.partial_result.replay_tag()
}
}
impl From<(FramedNymPacket, PartialMixProcessingResult)> for PartiallyUnwrappedPacket {
fn from(
(received_data, partial_result): (FramedNymPacket, PartialMixProcessingResult),
) -> Self {
PartiallyUnwrappedPacket {
received_data,
partial_result,
}
}
}
pub fn process_framed_packet(
received: FramedNymPacket,
sphinx_key: &PrivateKey,
) -> Result<MixProcessingResult, PacketProcessingError> {
nanos!("process_received", {
let packet_size = received.packet_size();
let packet_type = received.packet_type();
let packet_size = received.packet_size();
let packet_type = received.packet_type();
// unwrap the sphinx packet and if possible and appropriate, cache keys
let processed_packet = perform_framed_unwrapping(received, sphinx_key)?;
// unwrap the sphinx packet
let processed_packet = perform_framed_unwrapping(received, sphinx_key)?;
// for forward packets, extract next hop and set delay (but do NOT delay here)
// for final packets, extract SURBAck
let final_processing_result =
perform_final_processing(processed_packet, packet_size, packet_type);
if final_processing_result.is_err() {
error!("{:?}", final_processing_result)
}
final_processing_result
})
// for forward packets, extract next hop and set delay (but do NOT delay here)
// for final packets, extract SURBAck
perform_final_processing(processed_packet, packet_size, packet_type)
}
fn perform_framed_unwrapping(
received: FramedNymPacket,
sphinx_key: &PrivateKey,
) -> Result<NymProcessedPacket, PacketProcessingError> {
nanos!("perform_initial_unwrapping", {
let packet = received.into_inner();
perform_framed_packet_processing(packet, sphinx_key)
})
let packet = received.into_inner();
perform_framed_packet_processing(packet, sphinx_key)
}
fn perform_framed_packet_processing(
packet: NymPacket,
sphinx_key: &PrivateKey,
) -> Result<NymProcessedPacket, PacketProcessingError> {
nanos!("perform_initial_packet_processing", {
packet.process(sphinx_key).map_err(|err| {
debug!("Failed to unwrap NymPacket packet: {err}");
PacketProcessingError::NymPacketProcessingError(err)
})
packet.process(sphinx_key).map_err(|err| {
debug!("Failed to unwrap NymPacket packet: {err}");
PacketProcessingError::NymPacketProcessingError(err)
})
}
fn wrap_processed_sphinx_packet(
packet: nym_sphinx_types::ProcessedPacket,
packet_size: PacketSize,
packet_type: PacketType,
) -> Result<MixProcessingResult, PacketProcessingError> {
let processing_data = match packet.data {
ProcessedPacketData::ForwardHop {
next_hop_packet,
next_hop_address,
delay,
} => process_forward_hop(
NymPacket::Sphinx(next_hop_packet),
next_hop_address,
delay,
packet_type,
),
// right now there's no use for the surb_id included in the header - probably it should get removed from the
// sphinx all together?
ProcessedPacketData::FinalHop {
destination,
identifier: _,
payload,
} => process_final_hop(
destination,
payload.recover_plaintext()?,
packet_size,
packet_type,
),
}?;
Ok(MixProcessingResult {
packet_version: MixPacketVersion::Sphinx(packet.version),
processing_data,
})
}
fn wrap_processed_outfox_packet(
packet: OutfoxProcessedPacket,
packet_size: PacketSize,
packet_type: PacketType,
) -> Result<MixProcessingResult, PacketProcessingError> {
let next_address = *packet.next_address();
let packet = packet.into_packet();
if packet.is_final_hop() {
let processing_data = process_final_hop(
DestinationAddressBytes::from_bytes(next_address),
packet.recover_plaintext()?.to_vec(),
packet_size,
packet_type,
)?;
Ok(MixProcessingResult {
packet_version: MixPacketVersion::Outfox,
processing_data,
})
} else {
let packet = MixPacket::new(
NymNodeRoutingAddress::try_from_bytes(&next_address)?,
NymPacket::Outfox(packet),
PacketType::Outfox,
);
Ok(MixProcessingResult {
packet_version: MixPacketVersion::Outfox,
processing_data: MixProcessingResultData::ForwardHop {
packet,
delay: None,
},
})
}
}
fn perform_final_processing(
packet: NymProcessedPacket,
packet_size: PacketSize,
@@ -135,64 +290,10 @@ fn perform_final_processing(
) -> Result<MixProcessingResult, PacketProcessingError> {
match packet {
NymProcessedPacket::Sphinx(packet) => {
let processing_data = match packet.data {
ProcessedPacketData::ForwardHop {
next_hop_packet,
next_hop_address,
delay,
} => process_forward_hop(
NymPacket::Sphinx(next_hop_packet),
next_hop_address,
delay,
packet_type,
),
// right now there's no use for the surb_id included in the header - probably it should get removed from the
// sphinx all together?
ProcessedPacketData::FinalHop {
destination,
identifier: _,
payload,
} => process_final_hop(
destination,
payload.recover_plaintext()?,
packet_size,
packet_type,
),
}?;
Ok(MixProcessingResult {
packet_version: MixPacketVersion::Sphinx(packet.version),
processing_data,
})
wrap_processed_sphinx_packet(packet, packet_size, packet_type)
}
NymProcessedPacket::Outfox(packet) => {
let next_address = *packet.next_address();
let packet = packet.into_packet();
if packet.is_final_hop() {
let processing_data = process_final_hop(
DestinationAddressBytes::from_bytes(next_address),
packet.recover_plaintext()?.to_vec(),
packet_size,
packet_type,
)?;
Ok(MixProcessingResult {
packet_version: MixPacketVersion::Outfox,
processing_data,
})
} else {
let packet = MixPacket::new(
NymNodeRoutingAddress::try_from_bytes(&next_address)?,
NymPacket::Outfox(packet),
PacketType::Outfox,
);
Ok(MixProcessingResult {
packet_version: MixPacketVersion::Outfox,
processing_data: MixProcessingResultData::ForwardHop {
packet,
delay: None,
},
})
}
wrap_processed_outfox_packet(packet, packet_size, packet_type)
}
}
}
-11
View File
@@ -22,17 +22,6 @@ pub mod packet_version;
pub const FRAG_ID_LEN: usize = 5;
pub type SerializedFragmentIdentifier = [u8; FRAG_ID_LEN];
// wait, wait, but why are we starting with version 7?
// when packet header gets serialized, the following bytes (in that order) are put onto the wire:
// - packet_version (starting with v1.1.0)
// - packet_size indicator
// - packet_type
// it also just so happens that the only valid values for packet_size indicator include values 1-6
// therefore if we receive byte `7` (or larger than that) we'll know we received a versioned packet,
// otherwise we should treat it as legacy
/// Increment it whenever we perform any breaking change in the wire format!
const CURRENT_PACKET_VERSION_NUMBER: u8 = 7;
// TODO: ask @AP about the choice of below algorithms
/// Hashing algorithm used during hkdf for ephemeral shared key generation per sphinx packet payload.
@@ -272,9 +272,6 @@ impl PacketSize {
let overhead = match packet_type {
#[cfg(feature = "sphinx")]
PacketType::Mix => SPHINX_PACKET_OVERHEAD,
#[allow(deprecated)]
#[cfg(feature = "sphinx")]
PacketType::Vpn => SPHINX_PACKET_OVERHEAD,
#[cfg(feature = "outfox")]
PacketType::Outfox => OUTFOX_PACKET_OVERHEAD,
_ => 0,
@@ -27,11 +27,6 @@ pub enum PacketType {
#[serde(alias = "sphinx")]
Mix = 0,
/// Represents a packet that should be sent through the network as fast as possible.
#[deprecated]
#[serde(rename = "unsupported-mix-vpn")]
Vpn = 1,
/// Abusing this to add Outfox support
#[serde(rename = "outfox")]
Outfox = 2,
@@ -41,8 +36,6 @@ impl fmt::Display for PacketType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
PacketType::Mix => write!(f, "Mix"),
#[allow(deprecated)]
PacketType::Vpn => write!(f, "Vpn"),
PacketType::Outfox => write!(f, "Outfox"),
}
}
+46 -32
View File
@@ -2,56 +2,70 @@
// SPDX-License-Identifier: Apache-2.0
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
use thiserror::Error;
use crate::{PacketSize, CURRENT_PACKET_VERSION_NUMBER};
// wait, wait, but why are we starting with version 7?
// when packet header gets serialized, the following bytes (in that order) are put onto the wire:
// - packet_version (starting with v1.1.0)
// - packet_size indicator
// - packet_type
// it also just so happens that the only valid values for packet_size indicator include values 1-6
// therefore if we receive byte `7` (or larger than that) we'll know we received a versioned packet,
// otherwise we should treat it as legacy
/// Increment it whenever we perform any breaking change in the wire format!
pub const INITIAL_PACKET_VERSION_NUMBER: u8 = 7;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum PacketVersion {
// this will allow updated mixnodes to still understand packets from before the update
Legacy,
Versioned(u8),
pub const CURRENT_PACKET_VERSION_NUMBER: u8 = INITIAL_PACKET_VERSION_NUMBER;
pub const CURRENT_PACKET_VERSION: PacketVersion =
PacketVersion::unchecked(CURRENT_PACKET_VERSION_NUMBER);
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct PacketVersion(u8);
impl Display for PacketVersion {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
#[derive(Debug, Error)]
#[error("attempted to use legacy packet version")]
pub struct InvalidPacketVersion;
impl PacketVersion {
pub fn new() -> Self {
Self::new_versioned(CURRENT_PACKET_VERSION_NUMBER)
PacketVersion(CURRENT_PACKET_VERSION_NUMBER)
}
pub fn new_legacy() -> Self {
PacketVersion::Legacy
const fn unchecked(version: u8) -> PacketVersion {
PacketVersion(version)
}
pub fn new_versioned(version: u8) -> Self {
PacketVersion::Versioned(version)
}
pub fn is_legacy(&self) -> bool {
matches!(self, PacketVersion::Legacy)
}
pub fn as_u8(&self) -> Option<u8> {
match self {
PacketVersion::Legacy => None,
PacketVersion::Versioned(version) => Some(*version),
}
pub fn as_u8(&self) -> u8 {
(*self).into()
}
}
impl From<u8> for PacketVersion {
fn from(v: u8) -> Self {
match v {
n if n == PacketSize::RegularPacket as u8 => PacketVersion::Legacy,
n if n == PacketSize::AckPacket as u8 => PacketVersion::Legacy,
n if n == PacketSize::ExtendedPacket8 as u8 => PacketVersion::Legacy,
n if n == PacketSize::ExtendedPacket16 as u8 => PacketVersion::Legacy,
n if n == PacketSize::ExtendedPacket32 as u8 => PacketVersion::Legacy,
n => PacketVersion::Versioned(n),
impl TryFrom<u8> for PacketVersion {
type Error = InvalidPacketVersion;
fn try_from(value: u8) -> Result<Self, Self::Error> {
if value < INITIAL_PACKET_VERSION_NUMBER {
return Err(InvalidPacketVersion);
}
Ok(PacketVersion(value))
}
}
impl From<PacketVersion> for u8 {
fn from(packet_version: PacketVersion) -> Self {
packet_version.0
}
}
impl Default for PacketVersion {
fn default() -> Self {
PacketVersion::Versioned(CURRENT_PACKET_VERSION_NUMBER)
PacketVersion::new()
}
}
+4 -2
View File
@@ -113,7 +113,8 @@ impl NymMessage {
match self {
NymMessage::Plain(data) => data,
NymMessage::Repliable(repliable) => match repliable.content {
RepliableMessageContent::Data { message, .. } => message,
RepliableMessageContent::Data(content) => content.message,
RepliableMessageContent::DataV2(content) => content.message,
_ => Vec::new(),
},
NymMessage::Reply(reply) => match reply.content {
@@ -252,7 +253,7 @@ impl NymMessage {
bytes
.into_iter()
.chain(std::iter::once(1u8))
.chain(std::iter::repeat(0u8).take(space_left))
.chain(std::iter::repeat_n(0u8, space_left))
.collect::<Vec<_>>()
.into()
}
@@ -309,6 +310,7 @@ mod tests {
// a single variant for each repliable and reply is enough as they are more thoroughly tested
// internally
let repliable = NymMessage::new_repliable(RepliableMessage::new_data(
true,
vec![1, 2, 3, 4, 5],
[42u8; 16].into(),
vec![],
+18 -26
View File
@@ -51,29 +51,14 @@ impl From<PreparedFragment> for MixPacket {
pub trait FragmentPreparer {
type Rng: CryptoRng + Rng;
fn use_legacy_sphinx_format(&self) -> bool;
fn deterministic_route_selection(&self) -> bool;
fn rng(&mut self) -> &mut Self::Rng;
fn nonce(&self) -> i32;
fn average_packet_delay(&self) -> Duration;
fn average_ack_delay(&self) -> Duration;
fn generate_reply_surbs(
&mut self,
amount: usize,
topology: &NymRouteProvider,
reply_recipient: &Recipient,
) -> Result<Vec<ReplySurb>, NymTopologyError> {
let mut reply_surbs = Vec::with_capacity(amount);
let packet_delay = self.average_packet_delay();
for _ in 0..amount {
let reply_surb =
ReplySurb::construct(self.rng(), reply_recipient, packet_delay, topology)?;
reply_surbs.push(reply_surb)
}
Ok(reply_surbs)
}
fn generate_surb_ack(
&mut self,
recipient: &Recipient,
@@ -83,9 +68,11 @@ pub trait FragmentPreparer {
packet_type: PacketType,
) -> Result<SurbAck, NymTopologyError> {
let ack_delay = self.average_ack_delay();
let use_legacy_sphinx_format = self.use_legacy_sphinx_format();
SurbAck::construct(
self.rng(),
use_legacy_sphinx_format,
recipient,
ack_key,
fragment_id.to_bytes(),
@@ -100,7 +87,7 @@ pub trait FragmentPreparer {
/// - compute SURB_ACK
/// - generate (x, g^x)
/// - obtain key k from the reply-surb which was computed as follows:
/// k = KDF(remote encryption key ^ x) this is equivalent to KDF( dh(remote, x) )
/// k = KDF(remote encryption key ^ x) this is equivalent to KDF( dh(remote, x) )
/// - compute v_b = AES-128-CTR(k, serialized_fragment)
/// - compute vk_b = H(k) || v_b
/// - compute sphinx_plaintext = SURB_ACK || H(k) || v_b
@@ -264,14 +251,7 @@ pub trait FragmentPreparer {
Some(packet_size.plaintext_size()),
)?,
PacketType::Mix => NymPacket::sphinx_build(
packet_size.payload_size(),
packet_payload,
&route,
&destination,
&delays,
)?,
#[allow(deprecated)]
PacketType::Vpn => NymPacket::sphinx_build(
self.use_legacy_sphinx_format(),
packet_size.payload_size(),
packet_payload,
&route,
@@ -331,6 +311,10 @@ pub struct MessagePreparer<R> {
/// Average delay an acknowledgement packet is going to get delay at a single mixnode.
average_ack_delay: Duration,
/// Specify whether any constructed packets should use the legacy format,
/// where the payload keys are explicitly attached rather than using the seeds
use_legacy_sphinx_format: bool,
nonce: i32,
}
@@ -344,6 +328,7 @@ where
sender_address: Recipient,
average_packet_delay: Duration,
average_ack_delay: Duration,
use_legacy_sphinx_format: bool,
) -> Self {
let mut rng = rng;
let nonce = rng.gen();
@@ -353,6 +338,7 @@ where
sender_address,
average_packet_delay,
average_ack_delay,
use_legacy_sphinx_format,
nonce,
}
}
@@ -364,6 +350,7 @@ where
pub fn generate_reply_surbs(
&mut self,
use_legacy_reply_surb_format: bool,
amount: usize,
topology: &NymRouteProvider,
) -> Result<Vec<ReplySurb>, NymTopologyError> {
@@ -373,6 +360,7 @@ where
&mut self.rng,
&self.sender_address,
self.average_packet_delay,
use_legacy_reply_surb_format,
topology,
)?;
reply_surbs.push(reply_surb)
@@ -454,6 +442,10 @@ where
impl<R: CryptoRng + Rng> FragmentPreparer for MessagePreparer<R> {
type Rng = R;
fn use_legacy_sphinx_format(&self) -> bool {
self.use_legacy_sphinx_format
}
fn deterministic_route_selection(&self) -> bool {
self.deterministic_route_selection
}
+42 -10
View File
@@ -5,10 +5,10 @@ use std::{array::TryFromSliceError, fmt};
use thiserror::Error;
#[cfg(feature = "outfox")]
use nym_outfox::packet::{OutfoxPacket, OutfoxProcessedPacket};
pub use nym_outfox::packet::{OutfoxPacket, OutfoxProcessedPacket};
#[cfg(feature = "sphinx")]
use sphinx_packet::{SphinxPacket, SphinxPacketBuilder};
pub use sphinx_packet::{SphinxPacket, SphinxPacketBuilder};
#[cfg(feature = "outfox")]
pub use nym_outfox::{
@@ -21,15 +21,18 @@ pub use nym_outfox::{
pub use sphinx_packet::{
constants::{
self, DESTINATION_ADDRESS_LENGTH, IDENTIFIER_LENGTH, MAX_PATH_LENGTH, NODE_ADDRESS_LENGTH,
PAYLOAD_KEY_SIZE,
PAYLOAD_KEY_SIZE, REPLAY_TAG_SIZE,
},
crypto::{self, PrivateKey, PublicKey},
header::{self, delays, delays::Delay, ProcessedHeader, SphinxHeader, HEADER_SIZE},
packet::builder::DEFAULT_PAYLOAD_SIZE,
payload::{Payload, PAYLOAD_OVERHEAD_SIZE},
payload::{
key::{PayloadKey, PayloadKeySeed},
Payload, PAYLOAD_OVERHEAD_SIZE,
},
route::{Destination, DestinationAddressBytes, Node, NodeAddressBytes, SURBIdentifier},
surb::{SURBMaterial, SURB},
version::Version,
version::*,
Error as SphinxError, ProcessedPacket, ProcessedPacketData,
};
@@ -84,17 +87,25 @@ impl fmt::Debug for NymPacket {
impl NymPacket {
#[cfg(feature = "sphinx")]
pub fn sphinx_build<M: AsRef<[u8]>>(
use_legacy_sphinx_format: bool,
size: usize,
message: M,
route: &[Node],
destination: &Destination,
delays: &[Delay],
) -> Result<NymPacket, NymPacketError> {
Ok(NymPacket::Sphinx(
SphinxPacketBuilder::new()
.with_payload_size(size)
.build_packet(message, route, destination, delays)?,
))
let mut builder = SphinxPacketBuilder::new().with_payload_size(size);
if use_legacy_sphinx_format {
builder = builder.with_version(X25519_WITH_EXPLICIT_PAYLOAD_KEYS_VERSION)
};
Ok(NymPacket::Sphinx(builder.build_packet(
message,
route,
destination,
delays,
)?))
}
#[cfg(feature = "sphinx")]
pub fn sphinx_from_bytes(bytes: &[u8]) -> Result<NymPacket, NymPacketError> {
@@ -166,4 +177,25 @@ impl NymPacket {
}
}
}
#[cfg(feature = "sphinx")]
pub fn sphinx_packet_ref(&self) -> Option<&SphinxPacket> {
match self {
NymPacket::Sphinx(packet) => Some(packet),
_ => None,
}
}
#[cfg(feature = "sphinx")]
pub fn to_sphinx_packet(self) -> Option<SphinxPacket> {
match self {
NymPacket::Sphinx(packet) => Some(packet),
_ => None,
}
}
#[cfg(feature = "sphinx")]
pub fn is_sphinx(&self) -> bool {
matches!(self, NymPacket::Sphinx(_))
}
}
+1 -1
View File
@@ -209,7 +209,7 @@ impl ShutdownManager {
legacy_task_manager: None,
shutdown_signals: Default::default(),
tracker: Default::default(),
max_shutdown_duration: Default::default(),
max_shutdown_duration: Duration::from_secs(10),
};
// we need to add an explicit watcher for the cancellation token being cancelled
+1 -1
View File
@@ -363,7 +363,7 @@ impl TaskClient {
"unnamed-TaskClient".to_string()
};
log!(target: target, level, "{}", format!("[{target}] {msg}"))
log!(target: target, level, "{}", format_args!("[{target}] {msg}"))
}
#[must_use]
+10 -1
View File
@@ -181,6 +181,14 @@ pub struct TrafficWasm {
/// Controls whether the sent sphinx packet use the NON-DEFAULT bigger size.
pub use_extended_packet_size: bool,
/// Specify whether any constructed sphinx packets should use the legacy format,
/// where the payload keys are explicitly attached rather than using the seeds
/// this affects any forward packets, acks and reply surbs
/// this flag should remain disabled until sufficient number of nodes on the network has upgraded
/// and support updated format.
/// in the case of reply surbs, the recipient must also understand the new encoding
pub use_legacy_sphinx_format: bool,
/// Controls whether the sent packets should use outfox as opposed to the default sphinx.
pub use_outfox: bool,
}
@@ -214,6 +222,7 @@ impl From<TrafficWasm> for ConfigTraffic {
.disable_main_poisson_packet_distribution,
primary_packet_size: PacketSize::RegularPacket,
secondary_packet_size: use_extended_packet_size,
use_legacy_sphinx_format: traffic.use_legacy_sphinx_format,
packet_type,
}
}
@@ -229,6 +238,7 @@ impl From<ConfigTraffic> for TrafficWasm {
maximum_number_of_retransmissions: traffic.maximum_number_of_retransmissions,
disable_main_poisson_packet_distribution: traffic
.disable_main_poisson_packet_distribution,
use_legacy_sphinx_format: traffic.use_legacy_sphinx_format,
use_extended_packet_size: traffic.secondary_packet_size.is_some(),
use_outfox: traffic.packet_type == PacketType::Outfox,
}
@@ -423,7 +433,6 @@ impl From<TopologyWasm> for ConfigTopology {
max_startup_gateway_waiting_period: Duration::from_millis(
topology.max_startup_gateway_waiting_period_ms as u64,
),
topology_structure: Default::default(),
minimum_mixnode_performance: topology.minimum_mixnode_performance,
minimum_gateway_performance: topology.minimum_gateway_performance,
use_extended_topology: topology.use_extended_topology,
@@ -109,6 +109,15 @@ pub struct TrafficWasmOverride {
#[tsify(optional)]
pub use_extended_packet_size: Option<bool>,
/// Specify whether any constructed sphinx packets should use the legacy format,
/// where the payload keys are explicitly attached rather than using the seeds
/// this affects any forward packets, acks and reply surbs
/// this flag should remain disabled until sufficient number of nodes on the network has upgraded
/// and support updated format.
/// in the case of reply surbs, the recipient must also understand the new encoding
#[tsify(optional)]
pub use_legacy_sphinx_format: Option<bool>,
/// Controls whether the sent packets should use outfox as opposed to the default sphinx.
#[tsify(optional)]
pub use_outfox: Option<bool>,
@@ -132,6 +141,9 @@ impl From<TrafficWasmOverride> for TrafficWasm {
disable_main_poisson_packet_distribution: value
.disable_main_poisson_packet_distribution
.unwrap_or(def.disable_main_poisson_packet_distribution),
use_legacy_sphinx_format: value
.use_legacy_sphinx_format
.unwrap_or(def.use_legacy_sphinx_format),
use_extended_packet_size: value
.use_extended_packet_size
.unwrap_or(def.use_extended_packet_size),
+1 -8
View File
@@ -6,13 +6,12 @@ use base64::engine::general_purpose;
use base64::Engine;
use serde::Serialize;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::ops::Deref;
use std::str::FromStr;
use x25519_dalek::PublicKey;
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub struct PeerPublicKey(PublicKey);
impl PeerPublicKey {
@@ -36,12 +35,6 @@ impl fmt::Display for PeerPublicKey {
}
}
impl Hash for PeerPublicKey {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.as_bytes().hash(state)
}
}
impl Deref for PeerPublicKey {
type Target = PublicKey;
+2 -2
View File
@@ -102,7 +102,7 @@ impl PeerHandle {
if SystemTime::now().duration_since(self.startup_timestamp)? >= AUTO_REMOVE_AFTER {
log::debug!(
"Peer {} has been present for 30 days, removing it",
self.public_key.to_string()
self.public_key
);
let success = self.remove_peer().await?;
return Ok(!success);
@@ -111,7 +111,7 @@ impl PeerHandle {
if spent_bandwidth >= BANDWIDTH_CAP_PER_DAY {
log::debug!(
"Peer {} doesn't have bandwidth anymore, removing it",
self.public_key.to_string()
self.public_key
);
let success = self.remove_peer().await?;
return Ok(!success);
+8 -8
View File
@@ -667,9 +667,9 @@ dependencies = [
[[package]]
name = "deranged"
version = "0.3.11"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
checksum = "28cfac68e08048ae1883171632c2aef3ebc555621ae56fbccce1cbf22dd7f058"
dependencies = [
"powerfmt",
]
@@ -1851,9 +1851,9 @@ dependencies = [
[[package]]
name = "time"
version = "0.3.39"
version = "0.3.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8"
checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40"
dependencies = [
"deranged",
"itoa",
@@ -1868,15 +1868,15 @@ dependencies = [
[[package]]
name = "time-core"
version = "0.1.3"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef"
checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c"
[[package]]
name = "time-macros"
version = "0.2.20"
version = "0.2.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c"
checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49"
dependencies = [
"num-conv",
"time-core",
@@ -1 +1 @@
Wednesday, March 19th 2025, 18:56:53 UTC
Thursday, April 3rd 2025, 13:40:32 UTC
@@ -47,6 +47,166 @@ This page displays a full list of all the changes during our release cycle from
<VarInfo />
## `v2025.6-chuckles`
- [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2025.8-chuckles)
- [`nym-node`](nodes/nym-node.mdx) version `1.8.0`
```shell
nym-node
Binary Name: nym-node
Build Timestamp: 2025-04-01T09:55:02.982234741Z
Build Version: 1.8.0
Commit SHA: a429d6528e99b878a310b71bdbe6d31923c52d84
Commit Date: 2025-04-01T11:41:15.000000000+02:00
Commit Branch: HEAD
rustc Version: 1.85.1
rustc Channel: stable
cargo Profile: release
```
### Operators Updates & Tools
- [**Nym Wallet v1.2.16**](https://github.com/nymtech/nym/releases/tag/nym-wallet-v1.2.16): Operators can change their parameters in the GUI interface again. Remember that while changing node settings will take effect in the next epoch (max 60 min), *Operator cost* and *Profit margin* will be updated with the change of interval (720 epochs). You can track the state of epochs and intervals [here](https://explorer.nym.spectredao.net/dashboard). Currently we are less than 24h from the end of interval, **if you were to change OC or PM, do it now!**
- [**Nym Explorer v2.1.0**](https://nym.com/explorer) is out as a beta release
- Since last release [**Wireguard exit policy is out!**](nodes/nym-node/configuration#wireguard-exit-policy-configuration). Make sure to apply it on the servers hosting `nym-node` in `exit-gateway` mode.
#### Community Tools
Nym operators are coming with diverse backgrounds. While for some running a `nym-node` is an introduction to sys-administration, others are well seasoned builders and hackers. It's amazing to see the effort people put in order to lift each other by day-to-day mutual problem solving support in [Matrix channel](https://matrix.to/#/#operators:nymtech.chat). More and more people take it further and build tools to improve network monitoring and node administration.
While we are planning to make a page aggregating these tools together, here are some tools released lately by the technical community:
- [SpectreDAO token dashboard](https://explorer.nym.spectredao.net/token): Including stats, distribution, alongside all fundamental stats
- [SpectreDAO privacy check](https://explorer.nym.spectredao.net/privacy-check): Testing users browser, IP info, WebRTC leaks, fingerprinting, extensions and most importantly whether you access via [NymVPN](https://nym.com/)
- Service status dashboards: Uptime dashboards showing status of Nym Nodes, either squads using self-hosted [Uptime Kuma](https://uptime.kuma.pet/) or a custom dashboard, like the one from [Oceanus](https://oceanus.p17o.com/)
- [Nym Node Widget](https://github.com/koutakou/NymNodeWidget): A MacOS widget following status of your nodes by [koutakou](https://github.com/koutakou/)
- [Nymesis](https://nymesis.vercel.app/): Beta version of an explorer tracking node rewards in a simple graph
### Features
- [Bump `http-proxy-middleware` from `2.0.6` to `2.0.7`](https://github.com/nymtech/nym/pull/5019): Bumps [`http-proxy-middleware`](https://github.com/chimurai/http-proxy-middleware)
- [Bump `next` from `13.5.7` to `14.2.15` in `/documentation/docs`](https://github.com/nymtech/nym/pull/5281): Bumps [`next`](https://github.com/vercel/next.js)
- [Bump `next` from `14.1.4` to `14.2.21` in `/explorer-nextjs`](https://github.com/nymtech/nym/pull/5308): Bumps [`next`](https://github.com/vercel/next.js)
- [Bump `nanoid` from `3.3.7` to `3.3.8` in `/documentation/docs`](https://github.com/nymtech/nym/pull/5335): Bumps [`nanoid`](https://github.com/ai/nanoid)
- [Bump `store2` from `2.14.3` to `2.14.4`](https://github.com/nymtech/nym/pull/5391): Bumps [`store2`](https://github.com/nbubna/store)
- [Bump `@octokit/plugin-paginate-rest` and `@actions/github` in `/.github/actions/nym-hash-releases/src`](https://github.com/nymtech/nym/pull/5488): Bumps [`@octokit/plugin-paginate-rest`](https://github.com/octokit/plugin-paginate-rest.js)
- [Clean stale partially received buffers](https://github.com/nymtech/nym/pull/5536): Clean up old `ReconstructionBuffer`s that have not received fragments in a long time. The motivation is that we want to allow the client to cap the number of re-transmissions done, meaning we can't assume that these will eventually arrive
- [Explorer V2](https://github.com/nymtech/nym/pull/5548)
- [Bump `braces` from `3.0.2` to `3.0.3` in `/sdk/typescript/packages/mix-fetch-node`](https://github.com/nymtech/nym/pull/5612): Bumps [`braces`](https://github.com/micromatch/braces)
- [Bump `golang.org/x/net` from `0.23.0` to `0.36.0` in `/wasm/mix-fetch/go-mix-conn`](https://github.com/nymtech/nym/pull/5613): Bumps [golang.org/x/net](https://github.com/golang/net)
- [Bump `ws` from `8.13.0` to `8.18.1` in `/wasm/client/internal-dev`](https://github.com/nymtech/nym/pull/5614): Bumps [`ws`](https://github.com/websockets/ws)
- [Bump webpack from `5.77.0` to `5.98.0` in `/wasm/client/internal-dev`](https://github.com/nymtech/nym/pull/5615): Bumps [`webpack`](https://github.com/webpack/webpack)
- [Feature/paginated ticketbooks challenge](https://github.com/nymtech/nym/pull/5619): This PR makes changes to how ticketbook rewarding works and in particular the challenge portion of the exchange. Rather than requesting merkle proof alongside all binary data of the associated ticketbooks in a single `/issued-ticketbooks-challenge` query (that could have potentially lead to ddos of nym-apis as it made them load all of the data into the memory at once) the query got split into `/issued-ticketbooks-challenge-commitment` for obtaining the merkle proof and `/issued-ticketbooks-data` for obtaining the binary data. Note that the latter query might have to be called multiple times with different arguments due to the enforced limit on the maximum number of values returned at once.
<AccordionTemplate name="nym-api routes">
- added:
- `/issued-ticketbooks-count`
- `/issued-ticketbooks-for-count/:expiration_date`
- `/issued-ticketbooks-on-count/:issuance_date`
- `/issued-ticketbooks-challenge-commitment`
- `/issued-ticketbooks-data`
- removed:
- `/issued-ticketbooks-challenge`
</AccordionTemplate>
<AccordionTemplate name={<TestingSteps/>}>
Testing Steps Performed:
New issue ticketbook related endpoints work - tickets are issued and consumed as before.
Redeeming tickets is happening as normal on the gateway side.
Notes (if any):
Chuckles will not require the rewarder binary to be updated; as there will be additional changes to rewarding, this will be extensively tested in the future.
</AccordionTemplate>
- [Bump `@babel/runtime` from `7.16.3` to `7.26.10` in `/testnet-faucet`](https://github.com/nymtech/nym/pull/5621): Bumps [`@babel/runtime`](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime)
- [Bump the `patch-updates` group with 8 updates](https://github.com/nymtech/nym/pull/5624):
| Package | From | To |
| --- | --- | --- |
| [`async-trait`](https://github.com/dtolnay/async-trait) | `0.1.87` | `0.1.88` |
| [`clap`](https://github.com/clap-rs/clap) | `4.5.31` | `4.5.32` |
| [`env_logger`](https://github.com/rust-cli/env_logger) | `0.11.6` | `0.11.7` |
| [`http-body-util`](https://github.com/hyperium/http-body) | `0.1.2` | `0.1.3` |
| [`quote`](https://github.com/dtolnay/quote) | `1.0.39` | `1.0.40` |
| [`tokio`](https://github.com/tokio-rs/tokio) | `1.44.0` | `1.44.1` |
| [`tokio-util`](https://github.com/tokio-rs/tokio) | `0.7.13` | `0.7.14` |
| [`indexed_db_futures`](https://github.com/Alorel/rust-indexed-db) | `0.6.0` | `0.6.1` |
- [Bump `humantime` from `2.1.0` to `2.2.0`](https://github.com/nymtech/nym/pull/5625): Bumps [`humantime`](https://github.com/chronotope/humantime)
- [Bump `http` from `1.2.0` to `1.3.1`](https://github.com/nymtech/nym/pull/5626): Bumps [`http`](https://github.com/hyperium/http)
- [Bump `celes` from `2.5.0` to `2.6.0`](https://github.com/nymtech/nym/pull/5627)
- [Bump `uuid` from `1.15.1` to `1.16.0`](https://github.com/nymtech/nym/pull/5628): Bumps [`uuid`](https://github.com/uuid-rs/uuid)
- [Bump `once_cell` from `1.20.3` to `1.21.1`](https://github.com/nymtech/nym/pull/5629): Bumps [`once_cell`](https://github.com/matklad/once_cell)
- [Bump `zeroize` from `1.6.0` to `1.8.1`](https://github.com/nymtech/nym/pull/5630): Bumps [`zeroize`](https://github.com/RustCrypto/utils)
- [Bump `tempfile` from `3.18.0` to `3.19.0`](https://github.com/nymtech/nym/pull/5631): Bumps [`tempfile`](https://github.com/Stebalien/tempfile)
- [Rework IPR codec to extract out timer and implement AsyncWrite](https://github.com/nymtech/nym/pull/5632): The goal is to be able to handle back pressure in the vpn client. For that we need to extract out the timer from the codec since we want to move control over that to the mixnet processor. Once the timer is removed we can reformulate the mixnet sender as a `Sink` wrapped in a `FramedWrite`
- Extract out timer from IPR codec
- Implement AsyncWrite on mixnet client sender
- Add functions to wait for lanes to clear on `LaneQueueLenghts`
- [Remove `explorer-api` from the main workspace](https://github.com/nymtech/nym/pull/5635)
- [Bump `dtolnay/rust-toolchain` from `1.90.0` to `1.100.0`](https://github.com/nymtech/nym/pull/5638): Bumps [`dtolnay/rust-toolchain`](https://github.com/dtolnay/rust-toolchain)
- [Bump `zip` from `2.2.2` to `2.4.1`](https://github.com/nymtech/nym/pull/5639): Bumps [`zip`](https://github.com/zip-rs/zip2)
- [Add `max_retransmissions` flag on each message](https://github.com/nymtech/nym/pull/5642): Add an optional `max_retransmissions` field on `InputMessage`, to be able to selectively disable or turn down the number of `retransmissions` done for specific packets
- [Upgrade `sha2` to workspace version for validator-client](https://github.com/nymtech/nym/pull/5644)
- [Add `RUSTUP_PERMIT_COPY_RENAME` in two workflows that we forgot about](https://github.com/nymtech/nym/pull/5646)
- [Remove `UNIQUE` constraint on node pubkey](https://github.com/nymtech/nym/pull/5649): This accounts for the case when a node rebonds with the same key remove nodes that weren't updated in a week
- [Add concurrency limit to CI](https://github.com/nymtech/nym/pull/5651): For `ci-build` workflow only
- [Update wallet to include Interval Operator Cost and Profit Margin](https://github.com/nymtech/nym/pull/5652): Created a new modal for Interval Operator Cost and Profit Margin changes
- Uses existing update node operating cost functionality for old mixnodes (If it works it works)
- Prompts a warning that a user "Can send the request as many times as they want but it will only update the last message when the interval changes"
- [Wallet-revamp to be in line with new nym-theming](https://github.com/nymtech/nym/pull/5653): Updating colour palette to match the [nym.com](https://nym.com) sites:
- Used the same font too
- Updated icons
- [Add `fd` callback for initial authentication](https://github.com/nymtech/nym/pull/5654): One thing that was missed was the registration `ws` connection that happens before the actual connection, if it's the first time connecting to a gateway. This PR extends connection `fd` callback to that initial connection, so that it can also be allowed by firewalls.
- [Revert using `AsyncWrite` sink in IPR](https://github.com/nymtech/nym/pull/5656): Revert the use of the `AsyncWrite` sink in the IPR until it's fully ready.
- [Remove Google public DNS](https://github.com/nymtech/nym/pull/5660): We had a lot of complaints about Google, so we're removing this in VPN client, Nym should follow to avoid issues with firewall.
### Bugfix
- [Corrected typos](https://github.com/nymtech/nym/pull/5497) in `CHANGELOG.md`
## `v2025.5-chokito`
- [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2025.5-chokito)
@@ -67,18 +227,14 @@ cargo Profile: release
### Release Summary
1. WebSocket Connection Counter Fix (this is the fix for which we have a [pre-release branch](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2025.4-dorina-nym-node-patch) out already which will be replaced by this release)
- Prevents incorrect session tracking for WebSockets
1. WebSocket Connection Counter Fix (this is the fix for which we have a [pre-release branch](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2025.4-dorina-nym-node-patch) out already which will be replaced by this release): Prevents incorrect session tracking for WebSockets
2. Security Fixes in Dependencies
- Address known security vulnerabilities, making updating highly recommended
2. Security Fixes in Dependencies: Address known security vulnerabilities, making updating highly recommended
3. Authorisation & Timestamp Changes
- Modifies how timestamps are validated in Auth v2 - this is going to be what the clients use. For example: the clients will start to use v2 variants of everything (prepping the network for the breaking change to come on April 1st which will force clients to only use v2).
3. Authorisation & Timestamp Changes: Modifies how timestamps are validated in Auth v2 - this is going to be what the clients use. For example: the clients will start to use v2 variants of everything (prepping the network for the breaking change to come on April 1st which will force clients to only use v2).
4. Wireguard exit policy [scripts](https://github.com/nymtech/nym/tree/develop/scripts/wireguard-exit-policy)
### Operators Updates & Tools
- [**Wireguard exit policy is out!**](nodes/nym-node/configuration#wireguard-exit-policy-configuration) Operators can use a new guide with our scripts to setup wireguard exit policy using IP tables rules.
@@ -20,12 +20,12 @@ This documentation page provides a guide on how to set up and run a [NYM NODE](.
```sh
nym-node
Binary Name: nym-node
Build Timestamp: 2025-03-18T12:17:10.782554174Z
Build Version: 1.7.0
Commit SHA: 82e82943aa14e530ab4eb3aaab28ce86d47e5298
Commit Date: 2025-03-18T10:39:55.000000000+01:00
Build Timestamp: 2025-04-01T09:55:02.982234741Z
Build Version: 1.8.0
Commit SHA: a429d6528e99b878a310b71bdbe6d31923c52d84
Commit Date: 2025-04-01T11:41:15.000000000+02:00
Commit Branch: HEAD
rustc Version: 1.85.0
rustc Version: 1.85.1
rustc Channel: stable
cargo Profile: release
```
@@ -23,13 +23,13 @@ This guide explains how to prepare your server in order to be able to host multi
This guide is based on Ubuntu 22.04, in case you prefer another OS, you may have to do a bit of your own research to troubleshoot networking configuration and other parameters.
</Callout>
### Installing KVM on a Server with Ubuntu 22.04
## Installing KVM on a Server with Ubuntu 22.04
**KVM** stands for **Kernel-based Virtual Machine**. It is a virtualization technology for Linux that allows a user to run multiple virtual machines (VMs) on a single physical machine. KVM turns the Linux kernel into a hypervisor, enabling it to manage multiple virtualised systems.
Follow the steps below to install KVM on Ubuntu 22.04 LTS.
#### Prerequisites
### Prerequisites
<Callout type="warning">
Operators aiming to run Nym node as mixnet [Exit Gateway](../../../community-counsel/exit-gateway) or with wireguard enabled should familiarize themselves with the challenges possibly coming along `nym-node` operation, described in our [community counsel](../../../community-counsel) and follow up with [legal suggestions](../../../community-counsel/legal). Particularly important is to [introduce yourself](../../../community-counsel/legal#introduce-nym-node-to-your-provider) and your intentions to run a Nym node to your provider.
@@ -510,16 +510,56 @@ Your KVM installation is now ready to deploy and manage VMs.
</Steps>
### Setting Up Virtual Machines
## Setting Up Virtual Machines
After finishing the [installation of KVM](#installing-kvm-on-a-server-with-ubuntu-2204), we can move to the virtualisation configuration.
**After finishing the [installation of KVM](#installing-kvm-on-a-server-with-ubuntu-2204), we can move to the virtualisation configuration.**
> **The steps below will guide you through a setup of one VM, therefore you will have to repeat this process for each VM**. That also means that you have to be mindful of space and memory allocation.
> **This tutorial will guide you through a setup of one VM, therefore you will have to repeat this process for each VM**.
<Callout>
A good practice before initialising any VMs, is to calculate space and memory allocation for each of them and your host machine.
</Callout>
You can choose in between using [bash scripts](#setting-up-vm-using-scripts) or [manual steps](#setting-up-vm-step-by-step) approach. While scripts may be faster, they give you much less flexibility. Therefore if you prefer to have room for customisation coming along installing software, do it step by step.
Remember, that anytime you execute a script on your computer, make sure to read and understand the script first.
### Seting up VM Using Scripts
<Callout type="info" emoji="️">
In case you want to initialise and configure your VMs manually - skip this chapter and go directly to the [*step-by-step* part](#setting-up-vm-step-by-step).
</Callout>
Using the scripts is a two-step process. First, initialisation part is done from the host root using [`initialise-vm.sh`](https://github.com/nymtech/nym/blob/develop/scripts/kvm-setup/initialise-vm.sh) and second, configuration part is done from the VM itself using [`configure-vm.sh`](https://github.com/nymtech/nym/blob/develop/scripts/kvm-setup/configure-vm.sh).
<Steps>
##### 1. Initialise VM from the host machine
- Log in to your host as `root`
- Run this block and follow the prompts carefully:
```bash
wget "https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/kvm-setup/initialise-vm.sh"
chmod +x ./initialise-vm.sh
./initialise-vm.sh
```
##### 2. Configure VM from within
- After logging into your VM run this block and follow the prompts carefully:
```bash
wget "https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/kvm-setup/configure-vm.sh"
chmod +x ./configure-vm.sh
./configure-vm.sh
```
</ Steps>
### Setting up VM Step-by-step
In case you did't use the scripts and prefer manual approach, follow the steps below carefully.
<Steps>
##### 1. Install OS for VMs
This is the OS on which the nodes themselves will run. You can chose any GNU/Linux of your preference. For this guide we are going to be using Ubuntu 24.04 LTS (Noble Numbat) cloud image from [cloud-images.ubuntu.com](https://cloud-images.ubuntu.com/noble/current/).
This is the OS on which the nodes themselves will run. You can chose any GNU/Linux of your preference. For this guide we are going to be using Ubuntu 24.04 LTS (Noble Numbat) cloud image from [cloud-images.ubuntu.com](https://cloud-images.ubuntu.com/noble/current/).
- Download Ubuntu Cloud image:
```bash
@@ -651,7 +691,7 @@ network:
addresses:
- 1.1.1.1 # Cloudflare IPv4 DNS
- 8.8.8.8 # Google IPv4 DNS
- 8.8.4.4 # Secondary Google IPv4 DNS
- 8.8.4.4 # Secondary Google IPv4 DNS
- 2606:4700:4700::1111 # Cloudflare IPv6 DNS
- 2001:4860:4860::8888 # Google IPv6 DNS
```
@@ -57,7 +57,7 @@ This is a quick summary, to understand the full picture, please see detailed [*R
* NymVPN users can chose to route through Nym Network in two ways:
- Mixnet: 5 layers routing and mixing - full privacy
- Wireguard: 2 layers routing, skipping 3 mixing layers - fast mode
* **The current reward system is *Native rewarding* - an intermediate step - where each layer get's rewarded the same**
* **The current reward system is [*Naive rewarding*](#naive-rewarding) - an intermediate step - where each layer get's rewarded the same**
* In the final model, nodes will get rewarded based on their layer position and the work they do (collected user tickets), where and the reward distribution per layer will be according to a [decision made by the operators](https://forum.nymtech.net/t/poll-what-should-be-the-split-of-mixmining-rewards-among-the-layers-of-the-nym-mixnet/407) as follows:
- 5-hop: 16%-16%-16%-16%-36%
- 2-hop: 33%-67%

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