Compare commits

..

160 Commits

Author SHA1 Message Date
Jon Häggblad 21f1fa94de wip 2025-03-21 17:23:56 +01:00
Jon Häggblad 8ed09d74b3 Add RUSTUP_PERMIT_COPY_RENAME to ci-lint-typescript 2025-03-21 11:39:58 +01:00
dynco-nym 3f05c0d4b9 Add concurrency limit to CI (#5651) 2025-03-20 20:13:41 +01:00
Jon Häggblad 1a37e60483 Add max_retransmissions flag on each message (#5642) 2025-03-20 19:54:07 +01:00
Yana Matrosova 0abc07c96f Merge pull request #5636 from nymtech/BugFix/explorer_styling_broken
/ regenerated yarn.lock
2025-03-20 19:08:02 +02:00
Jon Häggblad 04664c8ae1 Rework IPR codec to extract out timer and implement AsyncWrite (#5632)
* Update ipr codec

* Tweak conditional

* Fix sending empty packet for flush

* Remove unneeded log

* Bump mix_traffic and real_message channel from size 1 to 8
2025-03-20 15:59:44 +01:00
import this 4da68438c0 [DOCs/operators]: Monor fix (#5650) 2025-03-20 13:13:55 +01:00
import this 2b83442a6d [DOCs/operators]: Updates and release notes for v2025.5-chokito (#5648)
* replace dead token page with live dashboard

* add dev release notes

* fix urls

* add IPv6 KVM guide

* simplify node setup command

* add operator updates

* PR finished: add WG exit policy steps andfinish changelog

* PR finished: fix typo

* add components to the branch

* fix styling
2025-03-20 10:55:33 +00:00
Yana f982cb49c2 Fix NS api endpoint for dev and prod, add env variables 2025-03-20 11:57:50 +02:00
dependabot[bot] 0c05727e58 build(deps): bump dtolnay/rust-toolchain from 1.90.0 to 1.100.0 (#5638)
Bumps [dtolnay/rust-toolchain](https://github.com/dtolnay/rust-toolchain) from 1.90.0 to 1.100.0.
- [Release notes](https://github.com/dtolnay/rust-toolchain/releases)
- [Commits](https://github.com/dtolnay/rust-toolchain/compare/1.90.0...1.100.0)

---
updated-dependencies:
- dependency-name: dtolnay/rust-toolchain
  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-20 09:52:50 +00:00
Jon Häggblad 3c432ac073 Clean stale partially received buffers (#5536)
* Clean stale partially received buffers

* Tweak timeout

* Do cleanup after handling new messages instead of in the select loop

* Debug logging and remove unreachable

* Downgrade log

* Tweak logs

* tweak whitespace

* Only run the stale check every 10 sec
2025-03-20 10:01:42 +01:00
Yana 52ffd2e798 fix build 2025-03-19 15:30:39 +02:00
dependabot[bot] be8c7b4953 build(deps): bump golang.org/x/net from 0.23.0 to 0.36.0 in /wasm/mix-fetch/go-mix-conn (#5613)
* build(deps): bump golang.org/x/net in /wasm/mix-fetch/go-mix-conn

Bumps [golang.org/x/net](https://github.com/golang/net) from 0.23.0 to 0.36.0.
- [Commits](https://github.com/golang/net/compare/v0.23.0...v0.36.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* update used go compiler

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
2025-03-19 11:00:55 +00:00
dependabot[bot] 8e4bc12b87 Bump http-proxy-middleware from 2.0.6 to 2.0.7 (#5019)
Bumps [http-proxy-middleware](https://github.com/chimurai/http-proxy-middleware) from 2.0.6 to 2.0.7.
- [Release notes](https://github.com/chimurai/http-proxy-middleware/releases)
- [Changelog](https://github.com/chimurai/http-proxy-middleware/blob/v2.0.7/CHANGELOG.md)
- [Commits](https://github.com/chimurai/http-proxy-middleware/compare/v2.0.6...v2.0.7)

---
updated-dependencies:
- dependency-name: http-proxy-middleware
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-19 10:10:17 +00:00
dependabot[bot] 4895820985 build(deps): bump next from 13.5.7 to 14.2.15 in /documentation/docs (#5281)
Bumps [next](https://github.com/vercel/next.js) from 13.5.7 to 14.2.15.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v13.5.7...v14.2.15)

---
updated-dependencies:
- dependency-name: next
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-19 10:10:02 +00:00
dependabot[bot] 8500618fe9 build(deps): bump next from 14.1.4 to 14.2.21 in /explorer-nextjs (#5308)
Bumps [next](https://github.com/vercel/next.js) from 14.1.4 to 14.2.21.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v14.1.4...v14.2.21)

---
updated-dependencies:
- dependency-name: next
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-19 10:09:54 +00:00
dependabot[bot] a5b390b98f build(deps): bump nanoid from 3.3.7 to 3.3.8 in /documentation/docs (#5335)
Bumps [nanoid](https://github.com/ai/nanoid) from 3.3.7 to 3.3.8.
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/3.3.7...3.3.8)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-19 10:09:46 +00:00
dependabot[bot] ff66674f61 build(deps): bump store2 from 2.14.3 to 2.14.4 (#5391)
Bumps [store2](https://github.com/nbubna/store) from 2.14.3 to 2.14.4.
- [Commits](https://github.com/nbubna/store/compare/2.14.3...2.14.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-19 10:09:38 +00:00
dependabot[bot] a7cf34e812 build(deps): bump @octokit/plugin-paginate-rest and @actions/github (#5488)
Bumps [@octokit/plugin-paginate-rest](https://github.com/octokit/plugin-paginate-rest.js) to 9.2.2 and updates ancestor dependency [@actions/github](https://github.com/actions/toolkit/tree/HEAD/packages/github). These dependencies need to be updated together.


Updates `@octokit/plugin-paginate-rest` from 9.2.1 to 9.2.2
- [Release notes](https://github.com/octokit/plugin-paginate-rest.js/releases)
- [Commits](https://github.com/octokit/plugin-paginate-rest.js/compare/v9.2.1...v9.2.2)

Updates `@actions/github` from 5.1.1 to 6.0.0
- [Changelog](https://github.com/actions/toolkit/blob/main/packages/github/RELEASES.md)
- [Commits](https://github.com/actions/toolkit/commits/HEAD/packages/github)

---
updated-dependencies:
- dependency-name: "@octokit/plugin-paginate-rest"
  dependency-type: indirect
- dependency-name: "@actions/github"
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-19 10:09:05 +00:00
dependabot[bot] a85dad6bd7 build(deps): bump braces in /sdk/typescript/packages/mix-fetch-node (#5612)
Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3.
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-19 10:08:56 +00:00
dependabot[bot] 5b8a14f74b build(deps-dev): bump ws in /wasm/client/internal-dev (#5614)
Bumps [ws](https://github.com/websockets/ws) from 8.13.0 to 8.18.1.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.13.0...8.18.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-19 10:08:45 +00:00
dependabot[bot] 730c2efea6 build(deps-dev): bump webpack in /wasm/client/internal-dev (#5615)
Bumps [webpack](https://github.com/webpack/webpack) from 5.77.0 to 5.98.0.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.77.0...v5.98.0)

---
updated-dependencies:
- dependency-name: webpack
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-19 10:08:36 +00:00
dependabot[bot] c9d6a8cc25 build(deps): bump @babel/runtime in /testnet-faucet (#5621)
Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.16.3 to 7.26.10.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.26.10/packages/babel-runtime)

---
updated-dependencies:
- dependency-name: "@babel/runtime"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-19 10:06:52 +00:00
Jon Häggblad 230b2b1784 Upgrade sha2 to workspace version or validator-client (#5644) 2025-03-19 10:46:15 +01:00
Jon Häggblad e4e9615535 Add RUSTUP_PERMIT_COPY_RENAME in two workflows that we forgot about (#5646) 2025-03-19 09:18:25 +01:00
mfahampshire a19ee8f2aa fix accidental localhost link (#5643) 2025-03-18 17:23:22 +01:00
benedetta davico abfc68108a Merge pull request #5497 from helicopter-1/spelling
Corrected typos
2025-03-18 16:53:37 +01:00
Yana 7bf1adff28 Fixes 2025-03-18 17:45:38 +02:00
dependabot[bot] ed90e358fb build(deps): bump zeroize from 1.6.0 to 1.8.1 (#5630)
Bumps [zeroize](https://github.com/RustCrypto/utils) from 1.6.0 to 1.8.1.
- [Commits](https://github.com/RustCrypto/utils/compare/zeroize-v1.6.0...zeroize-v1.8.1)

---
updated-dependencies:
- dependency-name: zeroize
  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-18 15:23:16 +01:00
benedetta davico c7d0e26946 Merge pull request #5640 from nymtech/release/2025.5-chokito
Merge chokito to develop
2025-03-18 14:50:45 +01:00
Jon Häggblad 8d65c25986 Remove explorer-api from the main workspace (#5635) 2025-03-18 14:09:24 +01:00
benedetta davico a143d5f4f6 Merge pull request #5557 from nymtech/feature/exit-policies
Wireguard exit policies (and tests)
2025-03-18 12:29:40 +01:00
dependabot[bot] c041d11673 build(deps): bump zip from 2.2.2 to 2.4.1 (#5639)
Bumps [zip](https://github.com/zip-rs/zip2) from 2.2.2 to 2.4.1.
- [Release notes](https://github.com/zip-rs/zip2/releases)
- [Changelog](https://github.com/zip-rs/zip2/blob/master/CHANGELOG.md)
- [Commits](https://github.com/zip-rs/zip2/compare/v2.2.2...v2.4.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-18 10:59:08 +01:00
benedettadavico 82e82943aa update changelog 2025-03-18 10:39:55 +01:00
RadekSabacky e4fd87be2c / regenerated yarn.lock 2025-03-17 19:04:51 +01:00
dependabot[bot] 19ffe217f1 build(deps): bump http from 1.2.0 to 1.3.1 (#5626) 2025-03-17 18:47:40 +01:00
dependabot[bot] 079bfa52e7 build(deps): bump the patch-updates group with 8 updates (#5624)
Bumps the patch-updates group with 8 updates:

| 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` |


Updates `async-trait` from 0.1.87 to 0.1.88
- [Release notes](https://github.com/dtolnay/async-trait/releases)
- [Commits](https://github.com/dtolnay/async-trait/compare/0.1.87...0.1.88)

Updates `clap` from 4.5.31 to 4.5.32
- [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/v4.5.31...clap_complete-v4.5.32)

Updates `env_logger` from 0.11.6 to 0.11.7
- [Release notes](https://github.com/rust-cli/env_logger/releases)
- [Changelog](https://github.com/rust-cli/env_logger/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-cli/env_logger/compare/v0.11.6...v0.11.7)

Updates `http-body-util` from 0.1.2 to 0.1.3
- [Release notes](https://github.com/hyperium/http-body/releases)
- [Commits](https://github.com/hyperium/http-body/compare/http-body-util-v0.1.2...http-body-util-v0.1.3)

Updates `quote` from 1.0.39 to 1.0.40
- [Release notes](https://github.com/dtolnay/quote/releases)
- [Commits](https://github.com/dtolnay/quote/compare/1.0.39...1.0.40)

Updates `tokio` from 1.44.0 to 1.44.1
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.44.0...tokio-1.44.1)

Updates `tokio-util` from 0.7.13 to 0.7.14
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-util-0.7.13...tokio-util-0.7.14)

Updates `indexed_db_futures` from 0.6.0 to 0.6.1
- [Release notes](https://github.com/Alorel/rust-indexed-db/releases)
- [Commits](https://github.com/Alorel/rust-indexed-db/compare/v0.6.0...v0.6.1)

---
updated-dependencies:
- dependency-name: async-trait
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: env_logger
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: http-body-util
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: quote
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: tokio-util
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: indexed_db_futures
  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-17 16:30:29 +01:00
dependabot[bot] be9a2c26e7 build(deps): bump once_cell from 1.20.3 to 1.21.1 (#5629)
Bumps [once_cell](https://github.com/matklad/once_cell) from 1.20.3 to 1.21.1.
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.20.3...v1.21.1)

---
updated-dependencies:
- dependency-name: once_cell
  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-17 16:29:02 +01:00
mfahampshire d6f3eb6411 Max/new explorer url (#5522)
* new api link for explorer v2

* remove footer add explorer to navbar

* include image

* @ fix menu icons

* + explorer link in footer

---------

Co-authored-by: RadekSabacky <radek@nymtech.net>
2025-03-17 14:15:10 +00:00
dependabot[bot] 144f3bed9c build(deps): bump celes from 2.5.0 to 2.6.0 (#5627)
Bumps [celes](https://github.com/mikelodder7/celes) from 2.5.0 to 2.6.0.
- [Commits](https://github.com/mikelodder7/celes/commits)

---
updated-dependencies:
- dependency-name: celes
  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-17 13:46:33 +01:00
dependabot[bot] c1174e64d4 build(deps): bump humantime from 2.1.0 to 2.2.0 (#5625) 2025-03-17 12:59:56 +01:00
dependabot[bot] 312ecbe4dc build(deps): bump tempfile from 3.18.0 to 3.19.0 (#5631) 2025-03-17 12:53:24 +01:00
dependabot[bot] d2afa587e4 build(deps): bump uuid from 1.15.1 to 1.16.0 (#5628) 2025-03-17 12:52:17 +01:00
Tommy Verrall 224c4c1870 fix tests and ensure everything is working... 2025-03-17 11:07:54 +01:00
dynco-nym 3f8abdb74f Add /v3/nym-nodes (#5569)
* Add /v3/nym-nodes
- returns extended node info from local DB
- endpoint caching
- add bond_info & self_described to DB nym_nodes
- update mixnode & gateway bond status on data refresh
- add `active` column to DB nym_nodes
- use only active & bonded nodes in scraping/testrun tasks

* Improve log

* PR feedback
- remove active field from nym_nodes
- delete obsolete nym_nodes

* node-status-api: cargo sqlx prepare

* Remove guardrails in CI file

* Revert "node-status-api: cargo sqlx prepare"

This reverts commit 1fcd895f0d.

* Try to ignore sqlx files

* cargo sqlx prepare

* Repair harbor tag check

* Try without checkout action

* add awk

* Update log
2025-03-15 00:17:40 +01:00
Jędrzej Stuczyński 0f6ec8610e hotfix: correctly increment ws connection counter (#5620) 2025-03-14 15:47:17 +00:00
dynco-nym 3baac1292d Add workflow to check if tag exists (#5617)
* Add workflow

* Check harbor for tag

* Remove leftover comments

* Try out cargo metadata

* Revert "Try out cargo metadata"

This reverts commit b83fbad1ca.
2025-03-14 16:31:49 +01:00
benedetta davico c3b8c4b2f7 Merge pull request #5616 from nymtech/bd/remove-explorer-api-ci
Remove explorer-api from ci-build-binaries
2025-03-13 13:36:30 +01:00
benedettadavico 271b9e545c remove bump to explorer-api 2025-03-13 13:35:06 +01:00
benedetta davico 9641f01670 remove explorer-api from ci-build-binaries 2025-03-13 13:31:46 +01:00
benedettadavico a7bb3e8d91 bump versions for chokito 2025-03-13 13:19:37 +01:00
Fouad dc88650d6d Explorer V2 (#5548)
* remove pnpm lock file (should only be using yarn)

* Add lefthook configuration for pre-commit checks

* Add explorer-v2 to package.json dependencies

* add explorer v2

* update explorer v2 package name

* + basepath
+ redirect to basepath
+ blog icons refactor
+ icons refactor

* Add Getting Started instructions to README

* fix noise graph bug and line graph UI

* Delete unused translations, clean up console logs

* / test image url

* update yarn.lock

---------

Co-authored-by: RadekSabacky <radek@nymtech.net>
Co-authored-by: windy-ux <75579979+windy-ux@users.noreply.github.com>
Co-authored-by: Yana <iana.matrosova@gmail.com>
Co-authored-by: Mark Sinclair <mmsinclair@users.noreply.github.com>
2025-03-13 11:31:59 +00:00
Jack Wampler 79ce611d21 Server Side internal DoT/DoH opt out (#5577) 2025-03-12 10:14:04 -06:00
benedetta davico 960e817b8f Merge pull request #5578 from nymtech/yana/fix-double-memo
delete double memo field in send modal
2025-03-12 15:03:04 +01:00
dependabot[bot] 8b03e66ba7 build(deps): bump braces in /sdk/typescript/packages/nodejs-client (#5611)
Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3.
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-12 13:41:18 +00:00
dependabot[bot] 6a35581299 build(deps-dev): bump webpack-dev-middleware (#5610)
Bumps [webpack-dev-middleware](https://github.com/webpack/webpack-dev-middleware) from 5.3.3 to 5.3.4.
- [Release notes](https://github.com/webpack/webpack-dev-middleware/releases)
- [Changelog](https://github.com/webpack/webpack-dev-middleware/blob/v5.3.4/CHANGELOG.md)
- [Commits](https://github.com/webpack/webpack-dev-middleware/compare/v5.3.3...v5.3.4)

---
updated-dependencies:
- dependency-name: webpack-dev-middleware
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-12 13:40:54 +00:00
Jędrzej Stuczyński ce124a29a7 Chore/more payment watcher debug endpoints (#5608)
* add new endpoints for health and build information

* fixed timestamp serialisation in api responses

* status routes for price scraper

* state for processing bank msg

* clippy
2025-03-12 12:12:28 +00:00
Jędrzej Stuczyński f62d8813e0 chore: start sending v2 sphinx packets (#5554)
* chore: start sending v2 sphinx packets

* updated surb construction to use current format
2025-03-12 12:01:58 +00:00
dependabot[bot] a9cf016af2 build(deps-dev): bump ws in /wasm/mix-fetch/internal-dev (#5593)
Bumps [ws](https://github.com/websockets/ws) from 8.13.0 to 8.18.1.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.13.0...8.18.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-12 11:58:19 +00:00
dependabot[bot] a8403b585b build(deps-dev): bump webpack in /wasm/mix-fetch/internal-dev (#5597)
Bumps [webpack](https://github.com/webpack/webpack) from 5.77.0 to 5.98.0.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.77.0...v5.98.0)

---
updated-dependencies:
- dependency-name: webpack
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-12 11:58:12 +00:00
Jon Häggblad e9a7b48da0 Export lane queue lengths in sdk (#5609) 2025-03-12 12:57:17 +01:00
dependabot[bot] 66792f57ed build(deps): bump @babel/helpers from 7.24.4 to 7.26.10 (#5606)
Bumps [@babel/helpers](https://github.com/babel/babel/tree/HEAD/packages/babel-helpers) from 7.24.4 to 7.26.10.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.26.10/packages/babel-helpers)

---
updated-dependencies:
- dependency-name: "@babel/helpers"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-12 11:02:53 +00:00
Jędrzej Stuczyński f8d863249e Merge pull request #5605 from nymtech/chore/update-bls12_381-fork
Chore/update bls12 381 fork
2025-03-12 11:02:34 +00:00
Jędrzej Stuczyński 7d59a2477a chore: change auth v2 timestamp skew and allow values from the future (#5604)
* chore: change auth v2 timestamp skew and allow values from the future

* made the if statement more readable
2025-03-12 11:02:19 +00:00
Jędrzej Stuczyński eca88b0fa4 introduce internal tool for checking signer status (#5598)
* introduce internal tool for checking signer status

* fixed nym-api types due to moving values around

* added abci version
2025-03-12 11:02:03 +00:00
dependabot[bot] b80a4c8614 build(deps): bump body-parser and express (#5596)
Bumps [body-parser](https://github.com/expressjs/body-parser) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together.

Updates `body-parser` from 1.20.2 to 1.20.3
- [Release notes](https://github.com/expressjs/body-parser/releases)
- [Changelog](https://github.com/expressjs/body-parser/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/body-parser/compare/1.20.2...1.20.3)

Updates `express` from 4.19.2 to 4.21.2
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.2/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.21.2)

---
updated-dependencies:
- dependency-name: body-parser
  dependency-type: indirect
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-12 11:00:38 +00:00
dependabot[bot] ec5d342e3a build(deps): bump serve-static and express (#5594)
Bumps [serve-static](https://github.com/expressjs/serve-static) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together.

Updates `serve-static` from 1.15.0 to 1.16.2
- [Release notes](https://github.com/expressjs/serve-static/releases)
- [Changelog](https://github.com/expressjs/serve-static/blob/v1.16.2/HISTORY.md)
- [Commits](https://github.com/expressjs/serve-static/compare/v1.15.0...v1.16.2)

Updates `express` from 4.19.2 to 4.21.2
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.2/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.21.2)

---
updated-dependencies:
- dependency-name: serve-static
  dependency-type: indirect
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-12 11:00:21 +00:00
dependabot[bot] 6565655861 build(deps): bump cookie and express in /wasm/client/internal-dev (#5592)
Bumps [cookie](https://github.com/jshttp/cookie) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together.

Updates `cookie` from 0.6.0 to 0.7.1
- [Release notes](https://github.com/jshttp/cookie/releases)
- [Commits](https://github.com/jshttp/cookie/compare/v0.6.0...v0.7.1)

Updates `express` from 4.19.2 to 4.21.2
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.2/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.21.2)

---
updated-dependencies:
- dependency-name: cookie
  dependency-type: indirect
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-12 10:59:36 +00:00
dependabot[bot] 5aba886f14 build(deps): bump cookie and express in /wasm/mix-fetch/internal-dev (#5591)
Bumps [cookie](https://github.com/jshttp/cookie) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together.

Updates `cookie` from 0.6.0 to 0.7.1
- [Release notes](https://github.com/jshttp/cookie/releases)
- [Commits](https://github.com/jshttp/cookie/compare/v0.6.0...v0.7.1)

Updates `express` from 4.19.2 to 4.21.2
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.2/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.21.2)

---
updated-dependencies:
- dependency-name: cookie
  dependency-type: indirect
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-12 10:59:20 +00:00
dependabot[bot] 3ee73d541e build(deps): bump braces in /wasm/zknym-lib/internal-dev (#5590)
Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3.
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-12 10:58:56 +00:00
dependabot[bot] 4588a3036e build(deps): bump webpack-dev-middleware in /wasm/zknym-lib/internal-dev (#5589)
Bumps [webpack-dev-middleware](https://github.com/webpack/webpack-dev-middleware) from 5.3.3 to 5.3.4.
- [Release notes](https://github.com/webpack/webpack-dev-middleware/releases)
- [Changelog](https://github.com/webpack/webpack-dev-middleware/blob/v5.3.4/CHANGELOG.md)
- [Commits](https://github.com/webpack/webpack-dev-middleware/compare/v5.3.3...v5.3.4)

---
updated-dependencies:
- dependency-name: webpack-dev-middleware
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-12 10:58:41 +00:00
dependabot[bot] 6194ac07b8 build(deps): bump ring from 0.17.3 to 0.17.13 in /nym-wallet (#5582)
Bumps [ring](https://github.com/briansmith/ring) from 0.17.3 to 0.17.13.
- [Changelog](https://github.com/briansmith/ring/blob/main/RELEASES.md)
- [Commits](https://github.com/briansmith/ring/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-12 10:57:02 +00:00
Jędrzej Stuczyński a7fcfef5a3 Merge pull request #5601 from nymtech/chore/payment-watcher-debug-endpoints
Chore/payment watcher debug endpoints
2025-03-11 16:47:30 +00:00
dependabot[bot] fa927b82d8 Merge pull request #5541 from nymtech/dependabot/cargo/rs_merkle-1.5.0
build(deps): bump rs_merkle from 1.4.2 to 1.5.0
2025-03-11 16:02:00 +01:00
import this f724478763 [DOCs/operators]: Add steps to synchronize server time, using NTP (#5603) 2025-03-11 11:18:18 +00:00
Jędrzej Stuczyński 040f4f2500 Merge pull request #5602 from nymtech/merge/release/2025.4-dorina-patched
merge release/2025.4-dorina-patched into develop
2025-03-11 10:36:50 +00:00
Jędrzej Stuczyński 63002e784a Merge branch 'develop' into merge/release/2025.4-dorina-patched 2025-03-11 09:53:56 +00:00
Jon Häggblad 4a0b683b70 Merge pull request #5583 from nymtech/dependabot/cargo/ring-0.17.13
build(deps): bump ring from 0.17.9 to 0.17.13
2025-03-11 10:37:21 +01:00
Jędrzej Stuczyński 9e84b1f0c1 ci clippy 2025-03-11 09:33:44 +00:00
Jon Häggblad bf031ad6de Merge pull request #5587 from nymtech/dependabot/cargo/tokio-1.44.0
build(deps): bump tokio from 1.43.0 to 1.44.0
2025-03-11 09:36:43 +01:00
dependabot[bot] 933769401c build(deps): bump tokio from 1.43.0 to 1.44.0
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.43.0 to 1.44.0.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.43.0...tokio-1.44.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-11 08:07:30 +00:00
Jon Häggblad ddd85704bb Merge pull request #5576 from nymtech/max/update-surb-example-tempdir2
Rust SDK SURB example: change hardcoded file to tempdir
2025-03-11 09:05:25 +01:00
Jon Häggblad 17860c809f Merge pull request #5588 from nymtech/dependabot/cargo/tempfile-3.18.0
build(deps): bump tempfile from 3.17.1 to 3.18.0
2025-03-11 08:38:11 +01:00
Jon Häggblad 2d00fcd934 Allow resetting all SURB sender tags (#5600)
* Allow resetting all SURB sender tags

* wasm fixes

* More wasm fixes
2025-03-11 08:35:40 +01:00
Jędrzej Stuczyński c2c3df98cb updated payment watcher version 2025-03-10 17:28:24 +00:00
Jędrzej Stuczyński f429092e21 added basic payment listener information to status api 2025-03-10 17:28:12 +00:00
Jędrzej Stuczyński d7ef68d8d1 remove fallback to env values for watched addresses 2025-03-10 17:28:12 +00:00
Jędrzej Stuczyński 1a334b575d feat: make sure any terminated task kills the watcher and write run info to db (#5517)
* feat: make sure any terminated task kills the watcher and write run info to db

* updated chain watcher version
2025-03-10 13:34:08 +00:00
dependabot[bot] 2126736aff build(deps): bump tempfile from 3.17.1 to 3.18.0
Bumps [tempfile](https://github.com/Stebalien/tempfile) from 3.17.1 to 3.18.0.
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stebalien/tempfile/compare/v3.17.1...v3.18.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-10 10:37:03 +00:00
dependabot[bot] a69aa23609 build(deps): bump the patch-updates group with 8 updates (#5585)
Bumps the patch-updates group with 8 updates:

| Package | From | To |
| --- | --- | --- |
| [bytes](https://github.com/tokio-rs/bytes) | `1.10.0` | `1.10.1` |
| [semver](https://github.com/dtolnay/semver) | `1.0.25` | `1.0.26` |
| [serde](https://github.com/serde-rs/serde) | `1.0.218` | `1.0.219` |
| [serde_bytes](https://github.com/serde-rs/bytes) | `0.11.16` | `0.11.17` |
| [serde_derive](https://github.com/serde-rs/serde) | `1.0.218` | `1.0.219` |
| [serde_repr](https://github.com/dtolnay/serde-repr) | `0.1.19` | `0.1.20` |
| [time](https://github.com/time-rs/time) | `0.3.37` | `0.3.39` |
| [ff](https://github.com/zkcrypto/ff) | `0.13.0` | `0.13.1` |


Updates `bytes` from 1.10.0 to 1.10.1
- [Release notes](https://github.com/tokio-rs/bytes/releases)
- [Changelog](https://github.com/tokio-rs/bytes/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/bytes/compare/v1.10.0...v1.10.1)

Updates `semver` from 1.0.25 to 1.0.26
- [Release notes](https://github.com/dtolnay/semver/releases)
- [Commits](https://github.com/dtolnay/semver/compare/1.0.25...1.0.26)

Updates `serde` from 1.0.218 to 1.0.219
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.218...v1.0.219)

Updates `serde_bytes` from 0.11.16 to 0.11.17
- [Release notes](https://github.com/serde-rs/bytes/releases)
- [Commits](https://github.com/serde-rs/bytes/compare/0.11.16...0.11.17)

Updates `serde_derive` from 1.0.218 to 1.0.219
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.218...v1.0.219)

Updates `serde_repr` from 0.1.19 to 0.1.20
- [Release notes](https://github.com/dtolnay/serde-repr/releases)
- [Commits](https://github.com/dtolnay/serde-repr/compare/0.1.19...0.1.20)

Updates `time` from 0.3.37 to 0.3.39
- [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.37...v0.3.39)

Updates `ff` from 0.13.0 to 0.13.1
- [Changelog](https://github.com/zkcrypto/ff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/zkcrypto/ff/commits)

---
updated-dependencies:
- dependency-name: bytes
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: semver
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: serde_bytes
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: serde_derive
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: serde_repr
  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: ff
  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-10 11:35:14 +01:00
dependabot[bot] 8a2d98e3ce build(deps): bump ring from 0.17.9 to 0.17.13
Bumps [ring](https://github.com/briansmith/ring) from 0.17.9 to 0.17.13.
- [Changelog](https://github.com/briansmith/ring/blob/main/RELEASES.md)
- [Commits](https://github.com/briansmith/ring/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-07 17:16:05 +00:00
mfahampshire 9c4243914e Max/ns api docs (#5544)
* first pass

* cleanup

* added qu

* add readme

* more verbose err

* reword explainer @ top

* rename private-key.public to public-key

* move instructions to own file + add _meta.json files

* first pass probe

* remove unnecessary doubled notice to developers

* added extra debug log to version()

* include PR suggestions

* remove commented out function
2025-03-07 09:57:52 +00:00
import this 143ede268d [DOCs/operators]: Fix typo (#5581) 2025-03-07 09:56:45 +00:00
import this 81bddb5f6d [DOCs/operators]: Second patch version changelog (#5580) 2025-03-07 09:46:08 +00:00
benedettadavico 247ebb7c43 update changelog 2025-03-06 21:26:16 +01:00
Jędrzej Stuczyński 01c052e9a4 use legacy crypto for constructing SURB headers (#5579) 2025-03-06 20:13:16 +00:00
Yana 3880971e57 delete double memo field in send modal 2025-03-06 21:34:22 +02:00
benedettadavico 6bd31b9521 bump nym-node version 2025-03-06 18:08:58 +01:00
Jon Häggblad 430c33eb04 Set DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE to 50 2025-03-06 18:03:08 +01:00
mfahampshire d45d1eb313 change hardcoded file to tempdir 2025-03-06 17:37:19 +01:00
import this 3cb3ebd79b [DOCs/operators]: Release ntoes for patched version (#5573) 2025-03-06 14:56:40 +00:00
benedettadavico b42e5b063e bump api version 2025-03-06 15:45:02 +01:00
benedettadavico f6b30d0db6 update changelog for patched-dorina 2025-03-06 15:06:24 +01:00
benedettadavico c33e4c0836 bumping versions dorina patched 2025-03-06 15:03:43 +01:00
Jędrzej Stuczyński be92ccf0da bugfix: make sure to correctly decode response content when putting it into error message (#5571) 2025-03-06 11:24:16 +00:00
Jędrzej Stuczyński 35bf49c48c chore: additional logs when attempting to load ecash keys (#5567) 2025-03-06 11:24:03 +00:00
Jędrzej Stuczyński 7335a3dad4 fix: gateway protocol negotation for v3/v4 2025-03-06 11:08:52 +00:00
Jędrzej Stuczyński 698883c03f feature: v2 authentication request (#5537) (#5563)
* introduced v2 authentication request between clients and gateways

* client to send v2 auth when possible

* added persistence to last used authentication timestamp

* added clients identity to signed plaintext
2025-03-06 09:18:39 +00:00
Jon Häggblad 8ddef08c72 Tweak surb management to be more conservative (#5570)
To reduce the risk of the IPR DoS the client:

- Lower the timeout until the IPR will disconnect a client
- Reduce fewer surbs at a time. Large surb requests increases the
  latency until all fragments in the response have been delivered. The
  efficiency gains of having large surb requests dimishes quickly for
  large sizes as well
2025-03-06 10:09:15 +01:00
Jon Häggblad 0d8b3abc6f Deserialize v5 authenticator requests (#5568) 2025-03-05 23:07:32 +01:00
Jędrzej Stuczyński aa2f336904 hotfix: ensure we bail on merkle leaves insertion upon missing data (#5565)
* hotfix: ensure we bail on merkle leaves insertion upon missing data

* Update Cargo.toml

---------

Co-authored-by: benedetta davico <46782255+benedettadavico@users.noreply.github.com>
2025-03-05 16:44:35 +00:00
Jędrzej Stuczyński eacaf84430 add full response body to error message upon decoding failure (#5566) 2025-03-05 16:43:56 +00:00
Jon Häggblad c284b1e8b1 Create authenticator v5 request/response types (#5561)
* Create authenticator v5 request/response types

* Support v5 in the authenticator

* Fix tests

* Bump nym-node version
2025-03-05 15:41:44 +01:00
Jon Häggblad 7785d085cf Handle disconnect in IPR (#5547)
* Implement disconnect in the IPR

* Remove unused async
2025-03-05 15:17:51 +01:00
Jon Häggblad bb5b2eafcf Allow IPR reconnect to session (#5562) 2025-03-05 15:02:07 +01:00
mfahampshire 09ea406c02 DOCS v2025.4-dorina release notes (#5552)
* WIP changelog

* [DOCs/operators]: Adding operators notes to new changelog PR(#5564)

---------

Co-authored-by: import this <97586125+serinko@users.noreply.github.com>
2025-03-05 11:39:55 +00:00
Tommy Verrall 681c054890 rename file 2025-03-04 18:08:26 +01:00
Tommy Verrall f623bbd57c wireguard exit policy rules 2025-03-04 18:06:01 +01:00
Jędrzej Stuczyński 8c6f84b3fe Merge pull request #5550 from nymtech/merge/release/2025.4-dorina
Merge/release/2025.4 dorina
2025-03-04 12:55:45 +00:00
Jędrzej Stuczyński 27dc9c8024 Merge branch 'develop' into merge/release/2025.4-dorina 2025-03-04 11:00:24 +00:00
Jędrzej Stuczyński 42d559bc69 fix prometheus metric naming test due to changes to packet version scheme 2025-03-04 10:46:12 +00:00
benedettadavico 41b9b0e5bd update changelog 2025-03-04 10:40:08 +01:00
dependabot[bot] 6c781a0064 build(deps): bump itertools from 0.13.0 to 0.14.0 (#5509)
Bumps [itertools](https://github.com/rust-itertools/itertools) from 0.13.0 to 0.14.0.
- [Changelog](https://github.com/rust-itertools/itertools/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-itertools/itertools/compare/v0.13.0...v0.14.0)

---
updated-dependencies:
- dependency-name: itertools
  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-04 00:37:35 +01:00
dependabot[bot] 080ec80722 build(deps): bump uuid from 1.13.2 to 1.15.1 (#5542)
Bumps [uuid](https://github.com/uuid-rs/uuid) from 1.13.2 to 1.15.1.
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.13.2...v1.15.1)

---
updated-dependencies:
- dependency-name: uuid
  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-04 00:36:24 +01:00
dependabot[bot] 9c17239831 build(deps): bump flate2 from 1.0.35 to 1.1.0 (#5510)
Bumps [flate2](https://github.com/rust-lang/flate2-rs) from 1.0.35 to 1.1.0.
- [Release notes](https://github.com/rust-lang/flate2-rs/releases)
- [Changelog](https://github.com/rust-lang/flate2-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/flate2-rs/compare/1.0.35...1.1.0)

---
updated-dependencies:
- dependency-name: flate2
  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-04 00:35:12 +01:00
dependabot[bot] f6c19ec02b build(deps): bump the patch-updates group across 1 directory with 14 updates (#5549) 2025-03-03 20:05:21 +01:00
Jędrzej Stuczyński 94ff8a79ee feature: disallow routing mix packets to nodes not present in the topology (#5526)
* new NymNodeTopologyProvider to also keep track of ips of all nodes

* added nym-api endpoint for nodes existence by ip

* change behaviour of updating allowed nodes alongside the topology

* clippy

* license fix

* fix default filtering limit
2025-03-03 18:03:47 +00:00
Jędrzej Stuczyński 155c4d37ef feature: v2 authentication request (#5537)
* introduced v2 authentication request between clients and gateways

* client to send v2 auth when possible

* added persistence to last used authentication timestamp

* added clients identity to signed plaintext
2025-03-03 17:51:30 +00:00
Jędrzej Stuczyński 7060fa6dad fixed sphinx version metrics registration (#5546) 2025-03-03 17:24:10 +00:00
Jon Häggblad 9be9c04f52 Add SURBs soft threshold (#5535)
* Add surbs soft threshold

* wip

* Proactively request more SURBs than needed

* fmt

* cleanup

* wip logging

* wip

* debugging

* wip

* Tidy

* tidy

* Set threshold buffer default for IPR

* rustfmt

* wasm fixes

* debug

* Tweak debug message

* Set default min buffer to 0

* Tweak backlog message

* Restore debug message

* tweak

* tweak

* wasm
2025-03-03 14:06:20 +01:00
import this 2a6fe6624d [DOCs/operators]: Advanced server setup: install KVM, virtualise machines, prep VMs for nym-node (#5493)
* initialise KVM docs

* initialise steps for KVM installation and setup

* document guide to setup KVM network bridge

* add new page with KVM installation

* add disclaimer

* add VM configuration guide

* first version finalised, ready for testing and review

* finish VM guide

* setup guide finished

* add last sentence
2025-03-03 11:49:09 +00:00
Jędrzej Stuczyński 4f7124e661 Feature/chain status api (#5539)
* nym-api endpoint to return latest block information

* attached chain health to health query

* fixed serde casing

* one of the most nastiest work arounds in test code
2025-03-03 10:47:40 +00:00
mfahampshire f52f07f6ec Max/tcp proxy bin sdk readme (#5354)
* removed old todos
* add bin files to proxy
* add readme to sdk
* fmt
2025-03-03 07:39:17 +00:00
Fran Arbanas b709d3ba0b Fix/pull from harbor (#5521)
* fix: pull from harbor instead of dockerhub

* add remaining

* add comments saying that these changes will only work with VPN
2025-02-28 14:01:33 +01:00
Jon Häggblad 128f69a5d6 Simplify IPR v8 (#5532)
* Purge stuff from v8

* Adapt to v8 changes

* Use protocol in ipr header

* Remove commented out code

* Remove unused error
2025-02-28 13:04:53 +01:00
Jon Häggblad 40dd7dc95e Add RUSTUP_PERMIT_COPY_RENAME to ci-build (#5533) 2025-02-28 10:55:30 +01:00
Jack Wampler f13ce6bf2d HickoryDnsResolver use a shared instance by default to limit fd use (#5523) 2025-02-27 09:05:10 -07:00
Jon Häggblad 856dbfe1ac IPR request types v8 (#5498)
* IPR v8 request/response types

* Remove signature for when we use sender tags

* Remove unused

* Address some review comments

* Update license to GPL-3.0 for IPR

Since the IPR can run as a binary, make sure it's license is GPL-3.0

* update cargo deny

* Add back support for v6

* Tidy responses

* Clippy

* Fix compilation

* Conversions

* Conversions

* Split response conversion

* request split

* Complete conversion switch

* Remove commented out code

* rustfmt

* Remove unused conversions

* Remove unused TryFrom

* use from
2025-02-27 15:21:55 +01:00
Tommy Verrall b2f6836756 Merge pull request #5465 from pedrofaustino/patch-1
Display error messages if IPv4 or IPv6 address not found on nymtun0
2025-02-27 11:11:41 +01:00
Tommy Verrall 87e429d78a Merge pull request #5524 from nymtech/yana/memo-and-links
Make "Memo" visible per default on send NYM
2025-02-27 10:32:38 +01:00
Yana 4178809555 Make "Memo" visible per default on send NYM 2025-02-26 18:53:08 +02:00
benedetta davico e6f6e1342f Update ns-api version 2025-02-26 12:25:46 +01:00
Jędrzej Stuczyński 65175fee09 merge #5512 again after reverting due to incorrect rebase (#5520)
* setup workspace global lints to prevent needless panics

* removed sources of panic in nym-crypto, nym-node and nym-api

* adjusted test code
2025-02-26 10:52:09 +00:00
Jędrzej Stuczyński 69b2448500 chore: removed all old coconut code (#5500) 2025-02-26 10:02:55 +00:00
Jędrzej Stuczyński 8ba5322997 bugfix: bound check when recovering a reply SURB (#5502) 2025-02-26 09:48:21 +00:00
Jędrzej Stuczyński 2cb3817b2c feat: add config option for maximum number of client connections (#5513) 2025-02-26 09:48:13 +00:00
Jędrzej Stuczyński 80b395cd8e feat: use ct_eq for checking bearer token (#5501) (#5519) 2025-02-26 09:48:05 +00:00
Jędrzej Stuczyński 8f5457e698 feature: allow nym-nodes to understand future version of sphinx packets (#5496) (#5518)
* use updated sphinx crate

* updated outfox usage of keygen in tests

* use x25519 in outfox

* remove redundant constructor

* adjusted key convertion traits
2025-02-26 09:47:57 +00:00
dynco-nym 9de5d7213a Another total_stake SQL fix (#5516) 2025-02-24 18:06:03 +01:00
dynco-nym 94eb362a71 Fix total_stake on SQL update (#5514) 2025-02-24 20:50:42 +05:30
dependabot[bot] 0f615f48f2 build(deps): bump the patch-updates group with 2 updates (#5505) 2025-02-24 13:33:20 +01:00
Bogdan-Ștefan Neacşu d511611641 Connection fd callback before actual connection (#5494) 2025-02-24 14:23:43 +02:00
Jędrzej Stuczyński 26f97d3c34 dont query for ecash apis unless necessary (#5508) 2025-02-24 10:59:06 +00:00
Jędrzej Stuczyński 17d3ff2d77 feat: use ct_eq for checking bearer token (#5501) 2025-02-24 09:04:34 +00:00
dynco-nym dd3dcfa7fe Treat gateways as Nym Nodes (#5504)
* Generate GW moniker if missing

Beside that:
- clear up gw nomenclature
- adjust counting when legacy nodes are present in nym node APIs
- create utils module

* Store gatewy descriptions

* Clippy & version
2025-02-21 20:32:39 +01:00
dynco-nym 86ea2d23cb Update version in Cargo.toml (#5503) 2025-02-21 16:16:44 +01:00
dynco-nym 42a37442e8 Fix stats bug & remove HM caching (#5495)
* Fix stats bug & remove HM caching

* Use variable for better clarity

* Minor fixes
2025-02-21 16:05:26 +01:00
helicopter-1 d4d576f363 Fix typos in CHANGELOG.md 2025-02-20 21:28:47 +01:00
benedettadavico 63a8f96ea5 bump versions 2025-02-19 12:13:24 +01:00
pedrofaustino 0d397ab5cc Display error messages if IPv4 or IPv6 address not found on nymtun0 (issue #5461) 2025-02-14 12:47:34 +01:00
679 changed files with 83339 additions and 22603 deletions
+1
View File
@@ -1 +1,2 @@
nym-validator-rewarder/.sqlx/** diff=nodiff
nym-node-status-api/nym-node-status-api/.sqlx/** diff=nodiff
+149 -265
View File
@@ -9,7 +9,7 @@
"version": "1.0.0",
"dependencies": {
"@actions/core": "^1.10.1",
"@actions/github": "^5.1.1",
"@actions/github": "^6.0.0",
"@octokit/auth-action": "^4.0.1",
"@octokit/rest": "^20.0.2",
"hasha": "^5.2.0",
@@ -29,22 +29,34 @@
}
},
"node_modules/@actions/github": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/@actions/github/-/github-5.1.1.tgz",
"integrity": "sha512-Nk59rMDoJaV+mHCOJPXuvB1zIbomlKS0dmSIqPGxd0enAXBnOfn4VWF+CGtRCwXZG9Epa54tZA7VIRlJDS8A6g==",
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/@actions/github/-/github-6.0.0.tgz",
"integrity": "sha512-alScpSVnYmjNEXboZjarjukQEzgCRmjMv6Xj47fsdnqGS73bjJNDpiiXmp8jr0UZLdUB6d9jW63IcmddUP+l0g==",
"license": "MIT",
"dependencies": {
"@actions/http-client": "^2.0.1",
"@octokit/core": "^3.6.0",
"@octokit/plugin-paginate-rest": "^2.17.0",
"@octokit/plugin-rest-endpoint-methods": "^5.13.0"
"@actions/http-client": "^2.2.0",
"@octokit/core": "^5.0.1",
"@octokit/plugin-paginate-rest": "^9.0.0",
"@octokit/plugin-rest-endpoint-methods": "^10.0.0"
}
},
"node_modules/@actions/http-client": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.1.1.tgz",
"integrity": "sha512-qhrkRMB40bbbLo7gF+0vu+X+UawOvQQqNAA/5Unx774RS8poaOhThDOG6BGmxvAnxhQnDp2BG/ZUm65xZILTpw==",
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.3.tgz",
"integrity": "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==",
"license": "MIT",
"dependencies": {
"tunnel": "^0.0.6"
"tunnel": "^0.0.6",
"undici": "^5.25.4"
}
},
"node_modules/@fastify/busboy": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz",
"integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==",
"license": "MIT",
"engines": {
"node": ">=14"
}
},
"node_modules/@octokit/auth-action": {
@@ -59,14 +71,6 @@
"node": ">= 18"
}
},
"node_modules/@octokit/auth-action/node_modules/@octokit/auth-token": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz",
"integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==",
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/auth-action/node_modules/@octokit/openapi-types": {
"version": "20.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz",
@@ -81,115 +85,152 @@
}
},
"node_modules/@octokit/auth-token": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz",
"integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==",
"dependencies": {
"@octokit/types": "^6.0.3"
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz",
"integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==",
"license": "MIT",
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/core": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz",
"integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==",
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.0.tgz",
"integrity": "sha512-1LFfa/qnMQvEOAdzlQymH0ulepxbxnCYAKJZfMci/5XJyIHWgEYnDmgnKakbTh7CH2tFQ5O60oYDvns4i9RAIg==",
"license": "MIT",
"dependencies": {
"@octokit/auth-token": "^2.4.4",
"@octokit/graphql": "^4.5.8",
"@octokit/request": "^5.6.3",
"@octokit/request-error": "^2.0.5",
"@octokit/types": "^6.0.3",
"@octokit/auth-token": "^4.0.0",
"@octokit/graphql": "^7.1.0",
"@octokit/request": "^8.3.1",
"@octokit/request-error": "^5.1.0",
"@octokit/types": "^13.0.0",
"before-after-hook": "^2.2.0",
"universal-user-agent": "^6.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/endpoint": {
"version": "6.0.12",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz",
"integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==",
"version": "9.0.6",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.6.tgz",
"integrity": "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^6.0.3",
"is-plain-object": "^5.0.0",
"@octokit/types": "^13.1.0",
"universal-user-agent": "^6.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/graphql": {
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz",
"integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==",
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.1.0.tgz",
"integrity": "sha512-r+oZUH7aMFui1ypZnAvZmn0KSqAUgE1/tUXIWaqUCa1758ts/Jio84GZuzsvUkme98kv0WFY8//n0J1Z+vsIsQ==",
"license": "MIT",
"dependencies": {
"@octokit/request": "^5.6.0",
"@octokit/types": "^6.0.3",
"@octokit/request": "^8.3.0",
"@octokit/types": "^13.0.0",
"universal-user-agent": "^6.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/openapi-types": {
"version": "12.11.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-12.11.0.tgz",
"integrity": "sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ=="
"version": "23.0.1",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-23.0.1.tgz",
"integrity": "sha512-izFjMJ1sir0jn0ldEKhZ7xegCTj/ObmEDlEfpFrx4k/JyZSMRHbO3/rBwgE7f3m2DHt+RrNGIVw4wSmwnm3t/g==",
"license": "MIT"
},
"node_modules/@octokit/plugin-paginate-rest": {
"version": "2.21.3",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.21.3.tgz",
"integrity": "sha512-aCZTEf0y2h3OLbrgKkrfFdjRL6eSOo8komneVQJnYecAxIej7Bafor2xhuDJOIFau4pk0i/P28/XgtbyPF0ZHw==",
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.2.2.tgz",
"integrity": "sha512-u3KYkGF7GcZnSD/3UP0S7K5XUFT2FkOQdcfXZGZQPGv3lm4F2Xbf71lvjldr8c1H3nNbF+33cLEkWYbokGWqiQ==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^6.40.0"
"@octokit/types": "^12.6.0"
},
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"@octokit/core": ">=2"
"@octokit/core": "5"
}
},
"node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/openapi-types": {
"version": "20.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz",
"integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==",
"license": "MIT"
},
"node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": {
"version": "12.6.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz",
"integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^20.0.0"
}
},
"node_modules/@octokit/plugin-rest-endpoint-methods": {
"version": "5.16.2",
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.16.2.tgz",
"integrity": "sha512-8QFz29Fg5jDuTPXVtey05BLm7OB+M8fnvE64RNegzX7U+5NUXcOcnpTIK0YfSHBg8gYd0oxIq3IZTe9SfPZiRw==",
"version": "10.4.1",
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-10.4.1.tgz",
"integrity": "sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^6.39.0",
"deprecation": "^2.3.1"
"@octokit/types": "^12.6.0"
},
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"@octokit/core": ">=3"
"@octokit/core": "5"
}
},
"node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/openapi-types": {
"version": "20.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz",
"integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==",
"license": "MIT"
},
"node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/types": {
"version": "12.6.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz",
"integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^20.0.0"
}
},
"node_modules/@octokit/request": {
"version": "5.6.3",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz",
"integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==",
"version": "8.4.1",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.1.tgz",
"integrity": "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==",
"license": "MIT",
"dependencies": {
"@octokit/endpoint": "^6.0.1",
"@octokit/request-error": "^2.1.0",
"@octokit/types": "^6.16.1",
"is-plain-object": "^5.0.0",
"node-fetch": "^2.6.7",
"@octokit/endpoint": "^9.0.6",
"@octokit/request-error": "^5.1.1",
"@octokit/types": "^13.1.0",
"universal-user-agent": "^6.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/request-error": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz",
"integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==",
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.1.tgz",
"integrity": "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^6.0.3",
"@octokit/types": "^13.1.0",
"deprecation": "^2.0.0",
"once": "^1.4.0"
}
},
"node_modules/@octokit/request/node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
"node": ">= 18"
}
},
"node_modules/@octokit/rest": {
@@ -206,89 +247,6 @@
"node": ">= 18"
}
},
"node_modules/@octokit/rest/node_modules/@octokit/auth-token": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz",
"integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==",
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/rest/node_modules/@octokit/core": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.0.0.tgz",
"integrity": "sha512-YbAtMWIrbZ9FCXbLwT9wWB8TyLjq9mxpKdgB3dUNxQcIVTf9hJ70gRPwAcqGZdY6WdJPZ0I7jLaaNDCiloGN2A==",
"dependencies": {
"@octokit/auth-token": "^4.0.0",
"@octokit/graphql": "^7.0.0",
"@octokit/request": "^8.0.2",
"@octokit/request-error": "^5.0.0",
"@octokit/types": "^11.0.0",
"before-after-hook": "^2.2.0",
"universal-user-agent": "^6.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/rest/node_modules/@octokit/endpoint": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.0.tgz",
"integrity": "sha512-szrQhiqJ88gghWY2Htt8MqUDO6++E/EIXqJ2ZEp5ma3uGS46o7LZAzSLt49myB7rT+Hfw5Y6gO3LmOxGzHijAQ==",
"dependencies": {
"@octokit/types": "^11.0.0",
"is-plain-object": "^5.0.0",
"universal-user-agent": "^6.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/rest/node_modules/@octokit/graphql": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.0.1.tgz",
"integrity": "sha512-T5S3oZ1JOE58gom6MIcrgwZXzTaxRnxBso58xhozxHpOqSTgDS6YNeEUvZ/kRvXgPrRz/KHnZhtb7jUMRi9E6w==",
"dependencies": {
"@octokit/request": "^8.0.1",
"@octokit/types": "^11.0.0",
"universal-user-agent": "^6.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/rest/node_modules/@octokit/openapi-types": {
"version": "18.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-18.0.0.tgz",
"integrity": "sha512-V8GImKs3TeQRxRtXFpG2wl19V7444NIOTDF24AWuIbmNaNYOQMWRbjcGDXV5B+0n887fgDcuMNOmlul+k+oJtw=="
},
"node_modules/@octokit/rest/node_modules/@octokit/plugin-paginate-rest": {
"version": "9.2.1",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.2.1.tgz",
"integrity": "sha512-wfGhE/TAkXZRLjksFXuDZdmGnJQHvtU/joFQdweXUgzo1XwvBCD4o4+75NtFfjfLK5IwLf9vHTfSiU3sLRYpRw==",
"dependencies": {
"@octokit/types": "^12.6.0"
},
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"@octokit/core": "5"
}
},
"node_modules/@octokit/rest/node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/openapi-types": {
"version": "20.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz",
"integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA=="
},
"node_modules/@octokit/rest/node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": {
"version": "12.6.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz",
"integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==",
"dependencies": {
"@octokit/openapi-types": "^20.0.0"
}
},
"node_modules/@octokit/rest/node_modules/@octokit/plugin-request-log": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-4.0.0.tgz",
@@ -300,75 +258,13 @@
"@octokit/core": ">=5"
}
},
"node_modules/@octokit/rest/node_modules/@octokit/plugin-rest-endpoint-methods": {
"version": "10.4.1",
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-10.4.1.tgz",
"integrity": "sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg==",
"dependencies": {
"@octokit/types": "^12.6.0"
},
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"@octokit/core": "5"
}
},
"node_modules/@octokit/rest/node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/openapi-types": {
"version": "20.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz",
"integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA=="
},
"node_modules/@octokit/rest/node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/types": {
"version": "12.6.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz",
"integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==",
"dependencies": {
"@octokit/openapi-types": "^20.0.0"
}
},
"node_modules/@octokit/rest/node_modules/@octokit/request": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.1.1.tgz",
"integrity": "sha512-8N+tdUz4aCqQmXl8FpHYfKG9GelDFd7XGVzyN8rc6WxVlYcfpHECnuRkgquzz+WzvHTK62co5di8gSXnzASZPQ==",
"dependencies": {
"@octokit/endpoint": "^9.0.0",
"@octokit/request-error": "^5.0.0",
"@octokit/types": "^11.1.0",
"is-plain-object": "^5.0.0",
"universal-user-agent": "^6.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/rest/node_modules/@octokit/request-error": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.0.0.tgz",
"integrity": "sha512-1ue0DH0Lif5iEqT52+Rf/hf0RmGO9NWFjrzmrkArpG9trFfDM/efx00BJHdLGuro4BR/gECxCU2Twf5OKrRFsQ==",
"dependencies": {
"@octokit/types": "^11.0.0",
"deprecation": "^2.0.0",
"once": "^1.4.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/rest/node_modules/@octokit/types": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-11.1.0.tgz",
"integrity": "sha512-Fz0+7GyLm/bHt8fwEqgvRBWwIV1S6wRRyq+V6exRKLVWaKGsuy6H9QFYeBVDV7rK6fO3XwHgQOPxv+cLj2zpXQ==",
"dependencies": {
"@octokit/openapi-types": "^18.0.0"
}
},
"node_modules/@octokit/types": {
"version": "6.41.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz",
"integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==",
"version": "13.8.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.8.0.tgz",
"integrity": "sha512-x7DjTIbEpEWXK99DMd01QfWy0hd5h4EN+Q7shkdKds3otGQP+oWE/y0A76i1OvH9fygo4ddvNf7ZvF0t78P98A==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^12.11.0"
"@octokit/openapi-types": "^23.0.1"
}
},
"node_modules/@vercel/ncc": {
@@ -396,7 +292,8 @@
"node_modules/deprecation": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
"integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="
"integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==",
"license": "ISC"
},
"node_modules/fetch-blob": {
"version": "3.2.0",
@@ -446,14 +343,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-plain-object": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-stream": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
@@ -504,15 +393,11 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"license": "ISC",
"dependencies": {
"wrappy": "1"
}
},
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"node_modules/tunnel": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
@@ -529,6 +414,18 @@
"node": ">=8"
}
},
"node_modules/undici": {
"version": "5.28.5",
"resolved": "https://registry.npmjs.org/undici/-/undici-5.28.5.tgz",
"integrity": "sha512-zICwjrDrcrUE0pyyJc1I2QzBkLM8FINsgOrt6WjA+BgajVq9Nxu2PbFFXUrAggLfDXlZGZBVZYw7WNV5KiBiBA==",
"license": "MIT",
"dependencies": {
"@fastify/busboy": "^2.0.0"
},
"engines": {
"node": ">=14.0"
}
},
"node_modules/universal-user-agent": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz",
@@ -550,24 +447,11 @@
"node": ">= 8"
}
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC"
}
}
}
@@ -11,7 +11,7 @@
},
"dependencies": {
"@actions/core": "^1.10.1",
"@actions/github": "^5.1.1",
"@actions/github": "^6.0.0",
"@octokit/auth-action": "^4.0.1",
"@octokit/rest": "^20.0.2",
"hasha": "^5.2.0",
@@ -26,6 +26,7 @@ jobs:
runs-on: ${{ matrix.platform }}
env:
CARGO_TERM_COLOR: always
RUSTUP_PERMIT_COPY_RENAME: 1
steps:
- uses: actions/checkout@v4
@@ -99,7 +100,6 @@ jobs:
cp target/release/nymvisor $OUTPUT_DIR
cp target/release/nym-node $OUTPUT_DIR
cp target/release/nym-cli $OUTPUT_DIR
cp target/release/explorer-api $OUTPUT_DIR
if [ ${{ github.event_name == 'workflow_dispatch' && inputs.enable_deb == true }} = true ]; then
cp target/debian/*.deb $OUTPUT_DIR
fi
@@ -12,6 +12,7 @@ jobs:
runs-on: arc-ubuntu-22.04
env:
CARGO_TERM_COLOR: always
RUSTUP_PERMIT_COPY_RENAME: 1
steps:
- name: Check out repository code
uses: actions/checkout@v4
+7
View File
@@ -27,6 +27,12 @@ on:
- '.github/workflows/ci-build.yml'
workflow_dispatch:
concurrency:
# only 1 concurrent `ci-build` allowed per branch
# https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#example-using-concurrency-and-the-default-behavior
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
strategy:
@@ -37,6 +43,7 @@ jobs:
env:
CARGO_TERM_COLOR: always
IPINFO_API_TOKEN: ${{ secrets.IPINFO_API_TOKEN }}
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 libudev-dev squashfs-tools protobuf-compiler
@@ -0,0 +1,57 @@
name: ci-check-ns-api-version
on:
pull_request:
paths:
- "nym-node-status-api/**"
env:
WORKING_DIRECTORY: "nym-node-status-api/nym-node-status-api"
jobs:
check-if-tag-exists:
runs-on: arc-ubuntu-22.04-dind
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.45.1
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
- name: Check if git tag exists
run: |
TAG=${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}
if [[ -z "$TAG" ]]; then
echo "Tag is empty"
exit 1
fi
git ls-remote --tags origin | awk '{print $2}'
if git ls-remote --tags origin | awk '{print $2}' | grep -q "refs/tags/$TAG$" ; then
echo "Tag '$TAG' ALREADY EXISTS on the remote"
exit 1
else
echo "Tag '$TAG' does not exist on the remote"
fi
- name: Check if harbor tag exists
run: |
TAG=${{ steps.get_version.outputs.result }}
registry=https://harbor.nymte.ch
repo_name=nym/node-status-api
if [[ -z $TAG ]]; then
echo "Tag is empty"
exit 1
fi
curl -su ${{ secrets.HARBOR_ROBOT_USERNAME }}:${{ secrets.HARBOR_ROBOT_SECRET }} "$registry/v2/$repo_name/tags/list" | jq
exists=$(curl -su ${{ secrets.HARBOR_ROBOT_USERNAME }}:${{ secrets.HARBOR_ROBOT_SECRET }} "$registry/v2/$repo_name/tags/list" | jq --arg tag $TAG '.tags | contains([$tag])' )
if [[ $exists = "true" ]]; then
echo "Version '$TAG' defined in Cargo.toml ALREADY EXISTS as tag in harbor repo"
exit 1
elif [[ $exists = "false" ]]; then
echo "Version '$TAG' doesn't exist on the remote"
else
echo "Unknown output '$exists'"
exit 1
fi
+2
View File
@@ -11,6 +11,8 @@ on:
jobs:
build:
runs-on: arc-ubuntu-20.04
env:
RUSTUP_PERMIT_COPY_RENAME: 1
defaults:
run:
working-directory: documentation/docs
+3 -1
View File
@@ -16,6 +16,8 @@ on:
jobs:
build:
runs-on: arc-ubuntu-20.04
env:
RUSTUP_PERMIT_COPY_RENAME: 1
steps:
- uses: actions/checkout@v4
- uses: rlespinasse/github-slug-action@v3.x
@@ -42,7 +44,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.20'
go-version: "1.23.7"
- name: Install
run: yarn
+1
View File
@@ -14,6 +14,7 @@ jobs:
runs-on: arc-ubuntu-20.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
+2 -1
View File
@@ -14,6 +14,7 @@ jobs:
runs-on: arc-ubuntu-20.04
env:
CARGO_TERM_COLOR: always
RUSTUP_PERMIT_COPY_RENAME: 1
steps:
- uses: actions/checkout@v4
@@ -32,7 +33,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.20'
go-version: "1.23.7"
- name: Install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
@@ -49,7 +49,7 @@ jobs:
"build-tools;$SDK_BUILDTOOLS_VERSION"
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@1.90.0
uses: dtolnay/rust-toolchain@1.100.0
- name: Install rust android targets
run: |
+1 -6
View File
@@ -31,12 +31,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: "1.20"
- name: Install TinyGo
uses: acifani/setup-tinygo@v2
with:
tinygo-version: "0.27.0"
go-version: "1.23.7"
- name: Install dependencies
run: yarn
+220 -10
View File
@@ -4,6 +4,216 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [Unreleased]
## [2025.5-chokito] (2025-03-18)
- build(deps): bump braces from 3.0.2 to 3.0.3 in /sdk/typescript/packages/nodejs-client ([#5611])
- build(deps-dev): bump webpack-dev-middleware from 5.3.3 to 5.3.4 in /wasm/client/internal-dev ([#5610])
- Export lane queue lengths in sdk ([#5609])
- Chore/more payment watcher debug endpoints ([#5608])
- build(deps): bump @babel/helpers from 7.24.4 to 7.26.10 ([#5606])
- Chore/update bls12 381 fork ([#5605])
- chore: change auth v2 timestamp skew and allow values from the future ([#5604])
- Chore/payment watcher debug endpoints ([#5601])
- Allow resetting all SURB sender tags ([#5600])
- introduce internal tool for checking signer status ([#5598])
- build(deps-dev): bump webpack from 5.77.0 to 5.98.0 in /wasm/mix-fetch/internal-dev ([#5597])
- build(deps): bump body-parser and express in /wasm/mix-fetch/internal-dev ([#5596])
- build(deps): bump serve-static and express in /wasm/mix-fetch/internal-dev ([#5594])
- build(deps-dev): bump ws from 8.13.0 to 8.18.1 in /wasm/mix-fetch/internal-dev ([#5593])
- build(deps): bump cookie and express in /wasm/client/internal-dev ([#5592])
- build(deps): bump cookie and express in /wasm/mix-fetch/internal-dev ([#5591])
- build(deps): bump braces from 3.0.2 to 3.0.3 in /wasm/zknym-lib/internal-dev ([#5590])
- build(deps): bump webpack-dev-middleware from 5.3.3 to 5.3.4 in /wasm/zknym-lib/internal-dev ([#5589])
- build(deps): bump tempfile from 3.17.1 to 3.18.0 ([#5588])
- build(deps): bump tokio from 1.43.0 to 1.44.0 ([#5587])
- build(deps): bump the patch-updates group with 8 updates ([#5585])
- build(deps): bump ring from 0.17.9 to 0.17.13 ([#5583])
- delete double memo field in send modal ([#5578])
- Server Side internal DoT/DoH opt out ([#5577])
- Rust SDK SURB example: change hardcoded file to tempdir ([#5576])
- Add /v3/nym-nodes ([#5569])
- chore: start sending v2 sphinx packets ([#5554])
- build(deps): bump the patch-updates group across 1 directory with 14 updates ([#5549])
- build(deps): bump uuid from 1.13.2 to 1.15.1 ([#5542])
- build(deps): bump rs_merkle from 1.4.2 to 1.5.0 ([#5541])
- feature: v2 authentication request ([#5537])
- Set RUSTUP_PERMIT_COPY_RENAME ([#5533])
- feature: disallow routing mix packets to nodes not present in the topology ([#5526])
- Make "Memo" visible per default on send NYM ([#5524])
- feat: make sure any terminated task kills the watcher and write run info to db ([#5517])
- Another total_stake SQL fix ([#5516])
- Fix total_stake on SQL update ([#5514])
- build(deps): bump flate2 from 1.0.35 to 1.1.0 ([#5510])
- build(deps): bump itertools from 0.13.0 to 0.14.0 ([#5509])
- build(deps): bump the patch-updates group with 2 updates ([#5505])
- Treat gateways as Nym Nodes ([#5504])
- Update version in Cargo.toml ([#5503])
- feat: use ct_eq for checking bearer token ([#5501])
- Add extra args for the probe ([#5499])
- Fix stats bug & remove HM caching ([#5495])
- fix: Cargo.lock for contracts ([#5489])
- Display error messages if IPv4 or IPv6 address not found on nymtun0 ([#5465])
[#5611]: https://github.com/nymtech/nym/pull/5611
[#5610]: https://github.com/nymtech/nym/pull/5610
[#5609]: https://github.com/nymtech/nym/pull/5609
[#5608]: https://github.com/nymtech/nym/pull/5608
[#5606]: https://github.com/nymtech/nym/pull/5606
[#5605]: https://github.com/nymtech/nym/pull/5605
[#5604]: https://github.com/nymtech/nym/pull/5604
[#5601]: https://github.com/nymtech/nym/pull/5601
[#5600]: https://github.com/nymtech/nym/pull/5600
[#5598]: https://github.com/nymtech/nym/pull/5598
[#5597]: https://github.com/nymtech/nym/pull/5597
[#5596]: https://github.com/nymtech/nym/pull/5596
[#5594]: https://github.com/nymtech/nym/pull/5594
[#5593]: https://github.com/nymtech/nym/pull/5593
[#5592]: https://github.com/nymtech/nym/pull/5592
[#5591]: https://github.com/nymtech/nym/pull/5591
[#5590]: https://github.com/nymtech/nym/pull/5590
[#5589]: https://github.com/nymtech/nym/pull/5589
[#5588]: https://github.com/nymtech/nym/pull/5588
[#5587]: https://github.com/nymtech/nym/pull/5587
[#5585]: https://github.com/nymtech/nym/pull/5585
[#5583]: https://github.com/nymtech/nym/pull/5583
[#5578]: https://github.com/nymtech/nym/pull/5578
[#5577]: https://github.com/nymtech/nym/pull/5577
[#5576]: https://github.com/nymtech/nym/pull/5576
[#5569]: https://github.com/nymtech/nym/pull/5569
[#5554]: https://github.com/nymtech/nym/pull/5554
[#5549]: https://github.com/nymtech/nym/pull/5549
[#5542]: https://github.com/nymtech/nym/pull/5542
[#5541]: https://github.com/nymtech/nym/pull/5541
[#5537]: https://github.com/nymtech/nym/pull/5537
[#5533]: https://github.com/nymtech/nym/pull/5533
[#5526]: https://github.com/nymtech/nym/pull/5526
[#5524]: https://github.com/nymtech/nym/pull/5524
[#5517]: https://github.com/nymtech/nym/pull/5517
[#5516]: https://github.com/nymtech/nym/pull/5516
[#5514]: https://github.com/nymtech/nym/pull/5514
[#5510]: https://github.com/nymtech/nym/pull/5510
[#5509]: https://github.com/nymtech/nym/pull/5509
[#5505]: https://github.com/nymtech/nym/pull/5505
[#5504]: https://github.com/nymtech/nym/pull/5504
[#5503]: https://github.com/nymtech/nym/pull/5503
[#5501]: https://github.com/nymtech/nym/pull/5501
[#5499]: https://github.com/nymtech/nym/pull/5499
[#5495]: https://github.com/nymtech/nym/pull/5495
[#5489]: https://github.com/nymtech/nym/pull/5489
[#5465]: https://github.com/nymtech/nym/pull/5465
## [2025.4-dorina-patched] (2025-03-06)
- use legacy crypto for constructing SURB headers ([#5579])
- bugfix: make sure to correctly decode response content when putting it into error message ([#5571])
- Tweak surb management to be more conservative ([#5570])
- Deserialize v5 authenticator requests ([#5568])
- chore: additional logs when attempting to load ecash keys ([#5567])
- add full response body to error message upon decoding failure ([#5566])
- hotfix: ensure we bail on merkle leaves insertion upon missing data ([#5565])
- feature: v2 authentication request (#5537) ([#5563])
- Create authenticator v5 request/response types ([#5561])
[#5579]: https://github.com/nymtech/nym/pull/5579
[#5571]: https://github.com/nymtech/nym/pull/5571
[#5570]: https://github.com/nymtech/nym/pull/5570
[#5568]: https://github.com/nymtech/nym/pull/5568
[#5567]: https://github.com/nymtech/nym/pull/5567
[#5566]: https://github.com/nymtech/nym/pull/5566
[#5565]: https://github.com/nymtech/nym/pull/5565
[#5563]: https://github.com/nymtech/nym/pull/5563
[#5561]: https://github.com/nymtech/nym/pull/5561
## [2025.4-dorina] (2025-03-04)
- fixed sphinx version metrics registration ([#5546])
- Feature/chain status api ([#5539])
- Add SURBs soft threshold ([#5535])
- Simplify IPR v8 ([#5532])
- Shared instance for DNS AsyncResolver ([#5523])
- merge #5512 again after reverting due to incorrect rebase ([#5520])
- cherry-pick 17d3ff2d775f61aee381d90a304ed416c08f33fc onto dorina ([#5519])
- cherry-pick 6e5d0dac1b75413c5f09122b0d953f8ec6ef48df onto dorina ([#5518])
- chore: workspace global panic preventing lints ([#5512])
- bugfix: dont query for ecash apis unless necessary when spending ticketbooks ([#5508])
- bugfix: bound check when recovering a reply SURB ([#5502])
- chore: removed all old coconut code ([#5500])
- IPR request types v8 ([#5498])
- Support static routes for HTTP requests ([#5487])
- build(deps): bump the patch-updates group across 1 directory with 3 updates ([#5482])
- added missing import to doctest ([#5480])
- adjusted TestSetup::new_complex to ensure bonded node's existence ([#5478])
- Trigger contracts CI on main workspace Cargo changes ([#5477])
- build(deps): bump http from 1.1.0 to 1.2.0 ([#5472])
- build(deps): bump utoipa-swagger-ui from 8.0.3 to 8.1.0 ([#5471])
- build(deps): bump colored from 2.1.0 to 2.2.0 ([#5470])
- build(deps): bump celes from 2.4.0 to 2.5.0 ([#5469])
- build(deps): bump the patch-updates group with 2 updates ([#5467])
- build(deps): bump elliptic from 6.5.4 to 6.6.1 in /docker/typescript_client/upload_contract ([#5463])
- Run cargo autoinherit ([#5460])
- Fix clippy::precedence ([#5457])
- Provide Interval context with node descriptor endpoints ([#5456])
- fix: update fx average rate calcs to ignore 0 values ([#5454])
- Feature/add gbp currency ([#5453])
- Add helper to extract a list of sqlite files with journal files wal/shm ([#5452])
- Add a middleware layer to the nym api allowing for data compression ([#5451])
- Condense core API functionalities and enable gzip decompression for reqwest payloads ([#5450])
- build(deps): bump uniffi_build from 0.25.3 to 0.29.0 ([#5448])
- Upgrade tower to 0.5.2 ([#5446])
- build(deps): bump hickory-proto from 0.24.2 to 0.24.3 ([#5444])
- Seedable clients ([#5440])
- build(deps): bump the patch-updates group across 1 directory with 10 updates ([#5439])
- Remove all recv_with_delay and add shutdown condition to loops in client-core ([#5435])
- Disable the test for checking the remaining bandwidth in nym-node-status-api ([#5425])
- Dz nym node stats ([#5418])
- build(deps): bump hyper from 1.4.1 to 1.6.0 ([#5416])
- build(deps): bump publicsuffix from 2.2.3 to 2.3.0 ([#5367])
- Nymnode entrypoint docker ([#5300])
[#5546]: https://github.com/nymtech/nym/pull/5546
[#5539]: https://github.com/nymtech/nym/pull/5539
[#5535]: https://github.com/nymtech/nym/pull/5535
[#5532]: https://github.com/nymtech/nym/pull/5532
[#5523]: https://github.com/nymtech/nym/pull/5523
[#5520]: https://github.com/nymtech/nym/pull/5520
[#5519]: https://github.com/nymtech/nym/pull/5519
[#5518]: https://github.com/nymtech/nym/pull/5518
[#5512]: https://github.com/nymtech/nym/pull/5512
[#5508]: https://github.com/nymtech/nym/pull/5508
[#5502]: https://github.com/nymtech/nym/pull/5502
[#5500]: https://github.com/nymtech/nym/pull/5500
[#5498]: https://github.com/nymtech/nym/pull/5498
[#5487]: https://github.com/nymtech/nym/pull/5487
[#5482]: https://github.com/nymtech/nym/pull/5482
[#5480]: https://github.com/nymtech/nym/pull/5480
[#5478]: https://github.com/nymtech/nym/pull/5478
[#5477]: https://github.com/nymtech/nym/pull/5477
[#5472]: https://github.com/nymtech/nym/pull/5472
[#5471]: https://github.com/nymtech/nym/pull/5471
[#5470]: https://github.com/nymtech/nym/pull/5470
[#5469]: https://github.com/nymtech/nym/pull/5469
[#5467]: https://github.com/nymtech/nym/pull/5467
[#5463]: https://github.com/nymtech/nym/pull/5463
[#5460]: https://github.com/nymtech/nym/pull/5460
[#5457]: https://github.com/nymtech/nym/pull/5457
[#5456]: https://github.com/nymtech/nym/pull/5456
[#5454]: https://github.com/nymtech/nym/pull/5454
[#5453]: https://github.com/nymtech/nym/pull/5453
[#5452]: https://github.com/nymtech/nym/pull/5452
[#5451]: https://github.com/nymtech/nym/pull/5451
[#5450]: https://github.com/nymtech/nym/pull/5450
[#5448]: https://github.com/nymtech/nym/pull/5448
[#5446]: https://github.com/nymtech/nym/pull/5446
[#5444]: https://github.com/nymtech/nym/pull/5444
[#5440]: https://github.com/nymtech/nym/pull/5440
[#5439]: https://github.com/nymtech/nym/pull/5439
[#5435]: https://github.com/nymtech/nym/pull/5435
[#5425]: https://github.com/nymtech/nym/pull/5425
[#5418]: https://github.com/nymtech/nym/pull/5418
[#5416]: https://github.com/nymtech/nym/pull/5416
[#5367]: https://github.com/nymtech/nym/pull/5367
[#5300]: https://github.com/nymtech/nym/pull/5300
## [2025.3-ruta] (2025-02-10)
- Push down forget me to client configs ([#5431])
@@ -48,7 +258,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- Downgrade harmless log message from info to debug ([#5403])
- Redirect from mixnode page to nodes page ([#5397])
- chore :update version of chain watcher and validator rewarder ([#5394])
- bugfix: correctly handle ingore epoch roles flag ([#5390])
- bugfix: correctly handle ignore epoch roles flag ([#5390])
- bugfix: terminate mixnet socket listener on shutdown ([#5389])
- feat: make client ignore dual mode nodes by default ([#5388])
- Handle ecash network errors differently ([#5378])
@@ -69,7 +279,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- Use expect in geodata test to give error message on failure ([#5314])
- feature: periodically remove stale gateway messages ([#5312])
- build(deps): bump the patch-updates group across 1 directory with 35 updates ([#5310])
- Add dependabot assignes for the root cargo ecosystem ([#5297])
- Add dependabot assigns for the root cargo ecosystem ([#5297])
- Move tun constants to network defaults ([#5286])
- Include IPINFO_API_TOKEN in nightly CI ([#5285])
- Nyx Chain Watcher ([#5274])
@@ -122,7 +332,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [2025.1-reeses] (2025-01-15)
- Feture/legacy alert ([#5346])
- Feature, Future/legacy alert ([#5346])
- chore: readjusted --mode behaviour to fix the regression ([#5331])
- chore: apply 1.84 linter suggestions ([#5330])
- bugfix: make sure refresh data key matches bond info ([#5329])
@@ -202,7 +412,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [2024.14-crunch-patched] (2024-12-17)
- Fixes an issue to allow previously registred clients to connect to latest nym-nodes
- Fixes an issue to allow previously registered clients to connect to latest nym-nodes
- Fixes compatibility issues between nym-nodes and older clients
## [2024.14-crunch] (2024-12-11)
@@ -210,7 +420,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- Merge/release/2024.14-crunch ([#5242])
- bugfix: added explicit openapi servers to account for route prefixes ([#5237])
- Further config score adjustments ([#5225])
- feature: remve any filtering on node semver ([#5224])
- feature: remove any filtering on node semver ([#5224])
- Backport #5218 ([#5220])
- Derive serialize for UserAgent (#5210) ([#5217])
- dont consider legacy nodes for rewarded set selection ([#5215])
@@ -389,7 +599,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- bugfix/feature: added NymApiClient method to get all skimmed nodes ([#5062])
- Merge1/release/2024.13 magura ([#5061])
- added hacky routes to return nymnodes alongside legacy nodes ([#5051])
- bugfix: mark migrated gateways as rewarded in the previous epoch in case theyre in the rewarded set ([#5049])
- bugfix: mark migrated gateways as rewarded in the previous epoch in case they're, their, there in the rewarded set ([#5049])
- bugfix: adjust runtime storage migration ([#5047])
- bugfix: supersede 'cb13be27f8f61d9ae74d924e85d2e6787895eb14' by using… ([#5046])
- bugfix: restore default http port for nym-api ([#5045])
@@ -450,7 +660,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- Fix broken build after merge ([#4937])
- bugfix: correctly paginate through 'search_tx' endpoint ([#4936])
- Add more conversions for responses of authenticator messages ([#4929])
- Directory Sevices v2.1 ([#4903])
- Directory Services, Devices v2.1 ([#4903])
- Migrate Legacy Node (Frontend) ([#4826])
- Fix critical issues SI84 and SI85 from Cure53 ([#4758])
@@ -834,7 +1044,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- Remove stale peers ([#4640])
- Add generic wg private network routing ([#4636])
- Feature/new node endpoints ([#4635])
- standarised ContractBuildInformation and added it to all contracts ([#4631])
- standardised ContractBuildInformation and added it to all contracts ([#4631])
- validate nym-node public ips on startup ([#4630])
- Bump defguard wg ([#4625])
- Fix cargo warnings ([#4624])
@@ -1455,7 +1665,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- clean-up nym-api startup arguments/flags to use clap 3 and its macro-derived arguments ([#2772])
- renamed all references to validator_api to nym_api
- renamed all references to nymd to nyxd ([#2696])
- all-binaries: standarised argument names (note: old names should still be accepted) ([#2762]
- all-binaries: standardised argument names (note: old names should still be accepted) ([#2762]
### Fixed
@@ -1960,7 +2170,7 @@ The release also include some additional work for distributed key generation in
- Explorer UI tests missing data-testid [\#903](https://github.com/nymtech/nym/pull/903) ([tommyv1987](https://github.com/tommyv1987))
- Fix up Nym-Wallet README.md [\#899](https://github.com/nymtech/nym/pull/899) ([tommyv1987](https://github.com/tommyv1987))
- Feature/batch delegator rewarding [\#898](https://github.com/nymtech/nym/pull/898) ([jstuczyn](https://github.com/jstuczyn))
- Bug mapp nodemap [\#897](https://github.com/nymtech/nym/pull/897) ([Aid19801](https://github.com/Aid19801))
- Bug map nodemap [\#897](https://github.com/nymtech/nym/pull/897) ([Aid19801](https://github.com/Aid19801))
- Bug fix/macos keyboard shortcuts [\#896](https://github.com/nymtech/nym/pull/896) ([fmtabbara](https://github.com/fmtabbara))
- Add a Mobile Nav to the Network Explorer [\#895](https://github.com/nymtech/nym/pull/895) ([Aid19801](https://github.com/Aid19801))
- Only use ts-rs in tests [\#894](https://github.com/nymtech/nym/pull/894) ([durch](https://github.com/durch))
Generated
+388 -832
View File
File diff suppressed because it is too large Load Diff
+46 -37
View File
@@ -66,7 +66,6 @@ members = [
"common/nym-id",
"common/nym-metrics",
"common/nym_offline_compact_ecash",
"common/nymcoconut",
"common/nymsphinx",
"common/nymsphinx/acknowledgements",
"common/nymsphinx/addressing",
@@ -99,9 +98,9 @@ members = [
"common/wireguard",
"common/wireguard-types",
"documentation/autodoc",
"explorer-api",
"explorer-api/explorer-api-requests",
"explorer-api/explorer-client",
# "explorer-api",
# "explorer-api/explorer-api-requests",
# "explorer-api/explorer-client",
"gateway",
"integrations/bity",
"nym-api",
@@ -138,7 +137,7 @@ members = [
"tools/internal/testnet-manager",
"tools/internal/testnet-manager",
"tools/internal/testnet-manager/dkg-bypass-contract",
"tools/internal/testnet-manager/dkg-bypass-contract",
"tools/internal/testnet-manager/dkg-bypass-contract", "tools/internal/validator-status-check",
"tools/nym-cli",
"tools/nym-id-cli",
"tools/nym-nr-query",
@@ -154,7 +153,6 @@ members = [
default-members = [
"clients/native",
"clients/socks5",
"explorer-api",
"nym-api",
"nym-credential-proxy/nym-credential-proxy",
"nym-node",
@@ -192,10 +190,10 @@ aes = "0.8.1"
aes-gcm = "0.10.1"
aes-gcm-siv = "0.11.1"
ammonia = "4"
anyhow = "1.0.95"
anyhow = "1.0.97"
arc-swap = "1.7.1"
argon2 = "0.5.0"
async-trait = "0.1.86"
async-trait = "0.1.88"
axum = "0.7.5"
axum-client-ip = "0.6.1"
axum-extra = "0.9.4"
@@ -206,24 +204,24 @@ 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.5.5"
blake3 = "1.6.1"
bloomfilter = "1.0.14"
bs58 = "0.5.1"
bytecodec = "0.4.15"
bytes = "1.7.2"
bytes = "1.10.1"
cargo_metadata = "0.18.1"
celes = "2.5.0"
celes = "2.6.0"
cfg-if = "1.0.0"
chacha20 = "0.9.0"
chacha20poly1305 = "0.10.1"
chrono = "0.4.39"
chrono = "0.4.40"
cipher = "0.4.3"
clap = "4.5.30"
clap = "4.5.32"
clap_complete = "4.5"
clap_complete_fig = "4.5"
colored = "2.2"
comfy-table = "7.1.4"
console = "0.15.10"
console = "0.15.11"
console-subscriber = "0.1.1"
console_error_panic_hook = "0.1"
const-str = "0.5.6"
@@ -242,29 +240,30 @@ doc-comment = "0.3"
dotenvy = "0.15.6"
ecdsa = "0.16"
ed25519-dalek = "2.1"
env_logger = "0.11.6"
encoding_rs = "0.8.35"
env_logger = "0.11.7"
envy = "0.4"
etherparse = "0.13.0"
eyre = "0.6.9"
fastrand = "2.1.1"
flate2 = "1.0.35"
flate2 = "1.1.0"
futures = "0.3.31"
futures-util = "0.3"
generic-array = "0.14.7"
getrandom = "0.2.10"
getset = "0.1.4"
getset = "0.1.5"
handlebars = "3.5.5"
headers = "0.4.0"
hex = "0.4.3"
hex-literal = "0.3.3"
hickory-resolver = "0.24.3"
hickory-resolver = "0.24.4"
hkdf = "0.12.3"
hmac = "0.12.1"
http = "1"
http-body-util = "0.1"
httpcodec = "0.2.3"
human-repr = "1.1.0"
humantime = "2.1.0"
humantime = "2.2.0"
humantime-serde = "1.1.1"
hyper = "1.6.0"
hyper-util = "0.1"
@@ -273,7 +272,7 @@ inquire = "0.6.2"
ip_network = "0.4.1"
ipnetwork = "0.20"
isocountry = "0.3.2"
itertools = "0.13.0"
itertools = "0.14.0"
k256 = "0.13"
lazy_static = "1.5.0"
ledger-transport = "0.10.0"
@@ -285,7 +284,7 @@ moka = { version = "0.12", features = ["future"] }
nix = "0.27.1"
notify = "5.1.0"
okapi = "0.7.0"
once_cell = "1.20.3"
once_cell = "1.21.1"
opentelemetry = "0.19.0"
opentelemetry-jaeger = "0.18.0"
parking_lot = "0.12.3"
@@ -308,21 +307,21 @@ reqwest = { version = "0.12.4", default-features = false }
rocket = "0.5.0"
rocket_cors = "0.6.0"
rocket_okapi = "0.8.0"
rs_merkle = "1.4.2"
rs_merkle = "1.5.0"
safer-ffi = "0.1.13"
schemars = "0.8.21"
semver = "1.0.25"
serde = "1.0.217"
serde_bytes = "0.11.15"
schemars = "0.8.22"
semver = "1.0.26"
serde = "1.0.219"
serde_bytes = "0.11.17"
serde_derive = "1.0"
serde_json = "1.0.138"
serde_json = "1.0.140"
serde_json_path = "0.7.2"
serde_repr = "0.1"
serde_with = "3.9.0"
serde_yaml = "0.9.25"
sha2 = "0.10.8"
si-scale = "0.2.3"
sphinx-packet = "0.3.1"
sphinx-packet = "=0.3.2"
sqlx = "0.7.4"
strum = "0.26"
strum_macros = "0.26"
@@ -330,17 +329,17 @@ subtle-encoding = "0.5"
syn = "1"
sysinfo = "0.33.0"
tap = "1.0.1"
tar = "0.4.43"
tempfile = "3.15"
tar = "0.4.44"
tempfile = "3.19"
thiserror = "2.0"
time = "0.3.37"
tokio = "1.43"
time = "0.3.39"
tokio = "1.44"
tokio-postgres = "0.7"
tokio-stream = "0.1.17"
tokio-test = "0.4.4"
tokio-tun = "0.11.5"
tokio-tungstenite = { version = "0.20.1" }
tokio-util = "0.7.13"
tokio-util = "0.7.14"
toml = "0.8.20"
tower = "0.5.2"
tower-http = "0.5.2"
@@ -362,7 +361,7 @@ vergen = { version = "=8.3.1", default-features = false }
walkdir = "2"
wasm-bindgen-test = "0.3.49"
x25519-dalek = "2.0.0"
zeroize = "1.6.0"
zeroize = "1.8.1"
prometheus = { version = "0.13.0" }
@@ -370,9 +369,9 @@ prometheus = { version = "0.13.0" }
# unfortunately until https://github.com/zkcrypto/bls12_381/issues/10 is resolved, we have to rely on the fork
# as we need to be able to serialize Gt so that we could create the lookup table for baby-step-giant-step algorithm
# plus to make our live easier we need serde support from https://github.com/zkcrypto/bls12_381/pull/125
bls12_381 = { git = "https://github.com/jstuczyn/bls12_381", default-features = false, branch = "temp/experimental-serdect" }
bls12_381 = { git = "https://github.com/jstuczyn/bls12_381", default-features = false, branch = "temp/experimental-serdect-updated" }
group = { version = "0.13.0", default-features = false }
ff = { version = "0.13.0", default-features = false }
ff = { version = "0.13.1", default-features = false }
subtle = "2.5.0"
# cosmwasm-related
@@ -403,7 +402,7 @@ prost = { version = "0.13", default-features = false }
gloo-utils = "0.2.0"
gloo-net = "0.6.0"
indexed_db_futures = "0.6.0"
indexed_db_futures = "0.6.1"
js-sys = "0.3.76"
serde-wasm-bindgen = "0.6.5"
tsify = "0.4.5"
@@ -438,3 +437,13 @@ opt-level = 'z'
[profile.release.package.mix-fetch-wasm]
# lto = true
opt-level = 'z'
[workspace.lints.clippy]
unwrap_used = "deny"
expect_used = "deny"
todo = "deny"
dbg_macro = "deny"
exit = "deny"
panic = "deny"
unimplemented = "deny"
unreachable = "deny"
+10
View File
@@ -67,3 +67,13 @@ As a general approach, licensing is as follows this pattern:
- documentation is Apache 2.0 or CC0-1.0
Nym Node Operators and Validators Terms and Conditions can be found [here](https://nym.com/operators-validators-terms).
## Getting Started
```bash
yarn install
```
```bash
yarn build
```
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.48"
version = "1.1.51"
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.48"
version = "1.1.51"
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
@@ -1,2 +1,3 @@
allow-unwrap-in-tests = true
allow-expect-in-tests = true
allow-panic-in-tests = true
+3 -2
View File
@@ -6,14 +6,15 @@ pub mod v1;
pub mod v2;
pub mod v3;
pub mod v4;
pub mod v5;
mod error;
mod util;
pub use error::Error;
pub use v4 as latest;
pub use v5 as latest;
pub const CURRENT_VERSION: u8 = 4;
pub const CURRENT_VERSION: u8 = 5;
fn make_bincode_serializer() -> impl bincode::Options {
use bincode::Options;
+92 -20
View File
@@ -8,8 +8,8 @@ use nym_sphinx::addressing::clients::Recipient;
use nym_wireguard_types::PeerPublicKey;
use crate::{
v1, v2, v3,
v4::{self, registration::IpPair},
v1, v2, v3, v4,
v5::{self, registration::IpPair},
Error,
};
@@ -19,6 +19,7 @@ pub enum AuthenticatorVersion {
V2,
V3,
V4,
V5,
UNKNOWN,
}
@@ -34,6 +35,8 @@ impl From<Protocol> for AuthenticatorVersion {
AuthenticatorVersion::V3
} else if value.version == v4::VERSION {
AuthenticatorVersion::V4
} else if value.version == v5::VERSION {
AuthenticatorVersion::V5
} else {
AuthenticatorVersion::UNKNOWN
}
@@ -68,6 +71,12 @@ impl InitMessage for v4::registration::InitMessage {
}
}
impl InitMessage for v5::registration::InitMessage {
fn pub_key(&self) -> PeerPublicKey {
self.pub_key
}
}
pub trait FinalMessage {
fn pub_key(&self) -> PeerPublicKey;
fn verify(&self, private_key: &PrivateKey, nonce: u64) -> Result<(), Error>;
@@ -138,6 +147,24 @@ impl FinalMessage for v4::registration::FinalMessage {
self.gateway_client.verify(private_key, nonce)
}
fn private_ips(&self) -> IpPair {
self.gateway_client.private_ips.into()
}
fn credential(&self) -> Option<CredentialSpendingData> {
self.credential.clone()
}
}
impl FinalMessage for v5::registration::FinalMessage {
fn pub_key(&self) -> PeerPublicKey {
self.gateway_client.pub_key
}
fn verify(&self, private_key: &PrivateKey, nonce: u64) -> Result<(), Error> {
self.gateway_client.verify(private_key, nonce)
}
fn private_ips(&self) -> IpPair {
self.gateway_client.private_ips
}
@@ -182,29 +209,39 @@ impl TopUpMessage for v4::topup::TopUpMessage {
}
}
impl TopUpMessage for v5::topup::TopUpMessage {
fn pub_key(&self) -> PeerPublicKey {
self.pub_key
}
fn credential(&self) -> CredentialSpendingData {
self.credential.clone()
}
}
pub enum AuthenticatorRequest {
Initial {
msg: Box<dyn InitMessage + Send + Sync + 'static>,
protocol: Protocol,
reply_to: Recipient,
reply_to: Option<Recipient>,
request_id: u64,
},
Final {
msg: Box<dyn FinalMessage + Send + Sync + 'static>,
protocol: Protocol,
reply_to: Recipient,
reply_to: Option<Recipient>,
request_id: u64,
},
QueryBandwidth {
msg: Box<dyn QueryBandwidthMessage + Send + Sync + 'static>,
protocol: Protocol,
reply_to: Recipient,
reply_to: Option<Recipient>,
request_id: u64,
},
TopUpBandwidth {
msg: Box<dyn TopUpMessage + Send + Sync + 'static>,
protocol: Protocol,
reply_to: Recipient,
reply_to: Option<Recipient>,
request_id: u64,
},
}
@@ -218,7 +255,7 @@ impl From<v1::request::AuthenticatorRequest> for AuthenticatorRequest {
version: value.version,
service_provider_type: ServiceProviderType::Authenticator,
},
reply_to: value.reply_to,
reply_to: Some(value.reply_to),
request_id: value.request_id,
},
v1::request::AuthenticatorRequestData::Final(gateway_client) => Self::Final {
@@ -227,7 +264,7 @@ impl From<v1::request::AuthenticatorRequest> for AuthenticatorRequest {
version: value.version,
service_provider_type: ServiceProviderType::Authenticator,
},
reply_to: value.reply_to,
reply_to: Some(value.reply_to),
request_id: value.request_id,
},
v1::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
@@ -237,7 +274,7 @@ impl From<v1::request::AuthenticatorRequest> for AuthenticatorRequest {
version: value.version,
service_provider_type: ServiceProviderType::Authenticator,
},
reply_to: value.reply_to,
reply_to: Some(value.reply_to),
request_id: value.request_id,
}
}
@@ -251,20 +288,20 @@ impl From<v2::request::AuthenticatorRequest> for AuthenticatorRequest {
v2::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
msg: Box::new(init_message),
protocol: value.protocol,
reply_to: value.reply_to,
reply_to: Some(value.reply_to),
request_id: value.request_id,
},
v2::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
msg: final_message,
protocol: value.protocol,
reply_to: value.reply_to,
reply_to: Some(value.reply_to),
request_id: value.request_id,
},
v2::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
Self::QueryBandwidth {
msg: Box::new(peer_public_key),
protocol: value.protocol,
reply_to: value.reply_to,
reply_to: Some(value.reply_to),
request_id: value.request_id,
}
}
@@ -278,20 +315,20 @@ impl From<v3::request::AuthenticatorRequest> for AuthenticatorRequest {
v3::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
msg: Box::new(init_message),
protocol: value.protocol,
reply_to: value.reply_to,
reply_to: Some(value.reply_to),
request_id: value.request_id,
},
v3::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
msg: final_message,
protocol: value.protocol,
reply_to: value.reply_to,
reply_to: Some(value.reply_to),
request_id: value.request_id,
},
v3::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
Self::QueryBandwidth {
msg: Box::new(peer_public_key),
protocol: value.protocol,
reply_to: value.reply_to,
reply_to: Some(value.reply_to),
request_id: value.request_id,
}
}
@@ -299,7 +336,7 @@ impl From<v3::request::AuthenticatorRequest> for AuthenticatorRequest {
Self::TopUpBandwidth {
msg: top_up_message,
protocol: value.protocol,
reply_to: value.reply_to,
reply_to: Some(value.reply_to),
request_id: value.request_id,
}
}
@@ -313,20 +350,20 @@ impl From<v4::request::AuthenticatorRequest> for AuthenticatorRequest {
v4::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
msg: Box::new(init_message),
protocol: value.protocol,
reply_to: value.reply_to,
reply_to: Some(value.reply_to),
request_id: value.request_id,
},
v4::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
msg: final_message,
protocol: value.protocol,
reply_to: value.reply_to,
reply_to: Some(value.reply_to),
request_id: value.request_id,
},
v4::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
Self::QueryBandwidth {
msg: Box::new(peer_public_key),
protocol: value.protocol,
reply_to: value.reply_to,
reply_to: Some(value.reply_to),
request_id: value.request_id,
}
}
@@ -334,7 +371,42 @@ impl From<v4::request::AuthenticatorRequest> for AuthenticatorRequest {
Self::TopUpBandwidth {
msg: top_up_message,
protocol: value.protocol,
reply_to: value.reply_to,
reply_to: Some(value.reply_to),
request_id: value.request_id,
}
}
}
}
}
impl From<v5::request::AuthenticatorRequest> for AuthenticatorRequest {
fn from(value: v5::request::AuthenticatorRequest) -> Self {
match value.data {
v5::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
msg: Box::new(init_message),
protocol: value.protocol,
reply_to: None,
request_id: value.request_id,
},
v5::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
msg: final_message,
protocol: value.protocol,
reply_to: None,
request_id: value.request_id,
},
v5::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
Self::QueryBandwidth {
msg: Box::new(peer_public_key),
protocol: value.protocol,
reply_to: None,
request_id: value.request_id,
}
}
v5::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message) => {
Self::TopUpBandwidth {
msg: top_up_message,
protocol: value.protocol,
reply_to: None,
request_id: value.request_id,
}
}
@@ -0,0 +1,478 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
use crate::{v4, v5};
impl From<v4::request::AuthenticatorRequest> for v5::request::AuthenticatorRequest {
fn from(authenticator_request: v4::request::AuthenticatorRequest) -> Self {
Self {
protocol: Protocol {
version: 5,
service_provider_type: ServiceProviderType::Authenticator,
},
data: authenticator_request.data.into(),
request_id: authenticator_request.request_id,
}
}
}
impl From<v4::request::AuthenticatorRequestData> for v5::request::AuthenticatorRequestData {
fn from(authenticator_request_data: v4::request::AuthenticatorRequestData) -> Self {
match authenticator_request_data {
v4::request::AuthenticatorRequestData::Initial(init_msg) => {
v5::request::AuthenticatorRequestData::Initial(init_msg.into())
}
v4::request::AuthenticatorRequestData::Final(final_msg) => {
v5::request::AuthenticatorRequestData::Final(Box::new((*final_msg).into()))
}
v4::request::AuthenticatorRequestData::QueryBandwidth(pub_key) => {
v5::request::AuthenticatorRequestData::QueryBandwidth(pub_key)
}
v4::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message) => {
v5::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message.into())
}
}
}
}
impl From<v4::registration::InitMessage> for v5::registration::InitMessage {
fn from(init_msg: v4::registration::InitMessage) -> Self {
Self {
pub_key: init_msg.pub_key,
}
}
}
impl From<v4::registration::FinalMessage> for v5::registration::FinalMessage {
fn from(final_msg: v4::registration::FinalMessage) -> Self {
Self {
gateway_client: final_msg.gateway_client.into(),
credential: final_msg.credential,
}
}
}
impl From<v4::registration::GatewayClient> for v5::registration::GatewayClient {
fn from(gateway_client: v4::registration::GatewayClient) -> Self {
Self {
pub_key: gateway_client.pub_key,
private_ips: gateway_client.private_ips.into(),
mac: gateway_client.mac.into(),
}
}
}
impl From<v5::registration::GatewayClient> for v4::registration::GatewayClient {
fn from(gateway_client: v5::registration::GatewayClient) -> Self {
Self {
pub_key: gateway_client.pub_key,
private_ips: gateway_client.private_ips.into(),
mac: gateway_client.mac.into(),
}
}
}
impl From<v4::registration::ClientMac> for v5::registration::ClientMac {
fn from(client_mac: v4::registration::ClientMac) -> Self {
Self::new((*client_mac).clone())
}
}
impl From<v5::registration::ClientMac> for v4::registration::ClientMac {
fn from(client_mac: v5::registration::ClientMac) -> Self {
Self::new((*client_mac).clone())
}
}
impl From<Box<v4::topup::TopUpMessage>> for Box<v5::topup::TopUpMessage> {
fn from(top_up_message: Box<v4::topup::TopUpMessage>) -> Self {
Box::new(v5::topup::TopUpMessage {
pub_key: top_up_message.pub_key,
credential: top_up_message.credential,
})
}
}
impl From<v4::response::AuthenticatorResponse> for v5::response::AuthenticatorResponse {
fn from(value: v4::response::AuthenticatorResponse) -> Self {
Self {
protocol: Protocol {
version: 5,
service_provider_type: value.protocol.service_provider_type,
},
data: value.data.into(),
}
}
}
impl From<v4::response::AuthenticatorResponseData> for v5::response::AuthenticatorResponseData {
fn from(authenticator_response_data: v4::response::AuthenticatorResponseData) -> Self {
match authenticator_response_data {
v4::response::AuthenticatorResponseData::PendingRegistration(pending_response) => {
v5::response::AuthenticatorResponseData::PendingRegistration(
pending_response.into(),
)
}
v4::response::AuthenticatorResponseData::Registered(registered_response) => {
v5::response::AuthenticatorResponseData::Registered(registered_response.into())
}
v4::response::AuthenticatorResponseData::RemainingBandwidth(
remaining_bandwidth_response,
) => v5::response::AuthenticatorResponseData::RemainingBandwidth(
remaining_bandwidth_response.into(),
),
v4::response::AuthenticatorResponseData::TopUpBandwidth(top_up_response) => {
v5::response::AuthenticatorResponseData::TopUpBandwidth(top_up_response.into())
}
}
}
}
impl From<v4::response::RegisteredResponse> for v5::response::RegisteredResponse {
fn from(value: v4::response::RegisteredResponse) -> Self {
Self {
request_id: value.request_id,
reply: value.reply.into(),
}
}
}
impl From<v4::response::PendingRegistrationResponse> for v5::response::PendingRegistrationResponse {
fn from(value: v4::response::PendingRegistrationResponse) -> Self {
Self {
request_id: value.request_id,
reply: value.reply.into(),
}
}
}
impl From<v4::registration::RegistrationData> for v5::registration::RegistrationData {
fn from(value: v4::registration::RegistrationData) -> Self {
Self {
nonce: value.nonce,
gateway_data: value.gateway_data.into(),
wg_port: value.wg_port,
}
}
}
impl From<v5::registration::RegistrationData> for v4::registration::RegistrationData {
fn from(value: v5::registration::RegistrationData) -> Self {
Self {
nonce: value.nonce,
gateway_data: value.gateway_data.into(),
wg_port: value.wg_port,
}
}
}
impl From<v4::response::RemainingBandwidthResponse> for v5::response::RemainingBandwidthResponse {
fn from(value: v4::response::RemainingBandwidthResponse) -> Self {
Self {
request_id: value.request_id,
reply: value.reply.map(Into::into),
}
}
}
impl From<v4::response::TopUpBandwidthResponse> for v5::response::TopUpBandwidthResponse {
fn from(value: v4::response::TopUpBandwidthResponse) -> Self {
Self {
request_id: value.request_id,
reply: value.reply.into(),
}
}
}
impl From<v4::registration::RegistredData> for v5::registration::RegistredData {
fn from(value: v4::registration::RegistredData) -> Self {
Self {
pub_key: value.pub_key,
private_ips: value.private_ips.into(),
wg_port: value.wg_port,
}
}
}
impl From<v4::registration::RemainingBandwidthData> for v5::registration::RemainingBandwidthData {
fn from(value: v4::registration::RemainingBandwidthData) -> Self {
Self {
available_bandwidth: value.available_bandwidth,
}
}
}
impl From<v4::registration::IpPair> for v5::registration::IpPair {
fn from(value: v4::registration::IpPair) -> Self {
Self {
ipv4: value.ipv4,
ipv6: value.ipv6,
}
}
}
impl From<v5::registration::IpPair> for v4::registration::IpPair {
fn from(value: v5::registration::IpPair) -> Self {
Self {
ipv4: value.ipv4,
ipv6: value.ipv6,
}
}
}
#[cfg(test)]
mod tests {
use std::{
net::{Ipv4Addr, Ipv6Addr},
str::FromStr,
};
use nym_credentials_interface::CredentialSpendingData;
use nym_crypto::asymmetric::encryption::PrivateKey;
use nym_sphinx::addressing::Recipient;
use nym_wireguard_types::PeerPublicKey;
use x25519_dalek::PublicKey;
use super::*;
use crate::{
util::tests::{CREDENTIAL_BYTES, RECIPIENT},
v4,
};
#[test]
fn upgrade_initial_req() {
let pub_key = PeerPublicKey::new(PublicKey::from([0; 32]));
let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap();
let (msg, _) = v4::request::AuthenticatorRequest::new_initial_request(
v4::registration::InitMessage::new(pub_key),
reply_to,
);
let upgraded_msg = v5::request::AuthenticatorRequest::from(msg);
assert_eq!(
upgraded_msg.protocol,
Protocol {
version: 5,
service_provider_type: ServiceProviderType::Authenticator
}
);
assert_eq!(
upgraded_msg.data,
v5::request::AuthenticatorRequestData::Initial(v5::registration::InitMessage {
pub_key
})
);
}
#[test]
fn upgrade_final_req() {
let mut rng = rand::thread_rng();
let local_secret = PrivateKey::new(&mut rng);
let remote_secret = x25519_dalek::StaticSecret::random_from_rng(&mut rng);
let ipv4 = Ipv4Addr::from_str("10.10.10.10").unwrap();
let ipv6 = Ipv6Addr::from_str("fc01::a0a").unwrap();
let ips = v4::registration::IpPair::new(ipv4, ipv6);
let nonce = 42;
let gateway_client = v4::registration::GatewayClient::new(
&local_secret,
(&remote_secret).into(),
ips,
nonce,
);
let credential = Some(CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap());
let final_message = v4::registration::FinalMessage {
gateway_client: gateway_client.clone(),
credential: credential.clone(),
};
let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap();
let (msg, _) =
v4::request::AuthenticatorRequest::new_final_request(final_message, reply_to);
let upgraded_msg = v5::request::AuthenticatorRequest::from(msg);
assert_eq!(
upgraded_msg.protocol,
Protocol {
version: 5,
service_provider_type: ServiceProviderType::Authenticator
}
);
assert_eq!(
upgraded_msg.data,
v5::request::AuthenticatorRequestData::Final(Box::new(
v5::registration::FinalMessage {
gateway_client: v5::registration::GatewayClient::new(
&local_secret,
(&remote_secret).into(),
v5::registration::IpPair::new(ipv4, ipv6),
nonce
),
credential
}
))
);
}
#[test]
fn upgrade_query_req() {
let pub_key = PeerPublicKey::new(PublicKey::from([0; 32]));
let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap();
let (msg, _) = v4::request::AuthenticatorRequest::new_query_request(pub_key, reply_to);
let upgraded_msg = v5::request::AuthenticatorRequest::from(msg);
assert_eq!(
upgraded_msg.protocol,
Protocol {
version: 5,
service_provider_type: ServiceProviderType::Authenticator
}
);
assert_eq!(
upgraded_msg.data,
v5::request::AuthenticatorRequestData::QueryBandwidth(pub_key)
);
}
#[test]
fn upgrade_pending_reg_resp() {
let mut rng = rand::thread_rng();
let local_secret = PrivateKey::new(&mut rng);
let remote_secret = x25519_dalek::StaticSecret::random_from_rng(&mut rng);
let ipv4 = Ipv4Addr::from_str("10.10.10.10").unwrap();
let ipv6 = Ipv6Addr::from_str("fc01::a0a").unwrap();
let ips = v4::registration::IpPair::new(ipv4, ipv6);
let nonce = 42;
let wg_port = 51822;
let gateway_data = v4::registration::GatewayClient::new(
&local_secret,
(&remote_secret).into(),
ips,
nonce,
);
let registration_data = v4::registration::RegistrationData {
nonce,
gateway_data,
wg_port,
};
let request_id = 123;
let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap();
let msg = v4::response::AuthenticatorResponse::new_pending_registration_success(
registration_data,
request_id,
reply_to,
);
let upgraded_msg = v5::response::AuthenticatorResponse::from(msg);
assert_eq!(
upgraded_msg.protocol,
Protocol {
version: 5,
service_provider_type: ServiceProviderType::Authenticator
}
);
assert_eq!(
upgraded_msg.data,
v5::response::AuthenticatorResponseData::PendingRegistration(
v5::response::PendingRegistrationResponse {
request_id,
reply: v5::registration::RegistrationData {
nonce,
gateway_data: v5::registration::GatewayClient::new(
&local_secret,
(&remote_secret).into(),
v5::registration::IpPair::new(ipv4, ipv6),
nonce
),
wg_port
}
}
)
);
}
#[test]
fn upgrade_registered_resp() {
let pub_key = PeerPublicKey::new(PublicKey::from([0; 32]));
let ipv4 = Ipv4Addr::from_str("10.1.10.10").unwrap();
let ipv6 = Ipv6Addr::from_str("fc01::a0a").unwrap();
let private_ips = v4::registration::IpPair::new(ipv4, ipv6);
let wg_port = 51822;
let registred_data = v4::registration::RegistredData {
pub_key,
private_ips,
wg_port,
};
let request_id = 123;
let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap();
let msg = v4::response::AuthenticatorResponse::new_registered(
registred_data,
reply_to,
request_id,
);
let upgraded_msg = v5::response::AuthenticatorResponse::from(msg);
assert_eq!(
upgraded_msg.protocol,
Protocol {
version: 5,
service_provider_type: ServiceProviderType::Authenticator
}
);
assert_eq!(
upgraded_msg.data,
v5::response::AuthenticatorResponseData::Registered(v5::response::RegisteredResponse {
request_id,
reply: v5::registration::RegistredData {
wg_port,
pub_key,
private_ips: v5::registration::IpPair::new(ipv4, ipv6)
}
})
);
}
#[test]
fn upgrade_remaining_bandwidth_resp() {
let available_bandwidth = 42;
let remaining_bandwidth_data = Some(v4::registration::RemainingBandwidthData {
available_bandwidth,
});
let request_id = 123;
let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap();
let msg = v4::response::AuthenticatorResponse::new_remaining_bandwidth(
remaining_bandwidth_data,
reply_to,
request_id,
);
let upgraded_msg = v5::response::AuthenticatorResponse::from(msg);
assert_eq!(
upgraded_msg.protocol,
Protocol {
version: 5,
service_provider_type: ServiceProviderType::Authenticator
}
);
assert_eq!(
upgraded_msg.data,
v5::response::AuthenticatorResponseData::RemainingBandwidth(
v5::response::RemainingBandwidthResponse {
request_id,
reply: Some(v5::registration::RemainingBandwidthData {
available_bandwidth,
})
}
)
);
}
}
@@ -0,0 +1,10 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod conversion;
pub mod registration;
pub mod request;
pub mod response;
pub mod topup;
pub const VERSION: u8 = 5;
@@ -0,0 +1,287 @@
// -2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::Error;
use base64::{engine::general_purpose, Engine};
use nym_credentials_interface::CredentialSpendingData;
use nym_network_defaults::constants::{WG_TUN_DEVICE_IP_ADDRESS_V4, WG_TUN_DEVICE_IP_ADDRESS_V6};
use nym_wireguard_types::PeerPublicKey;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::time::SystemTime;
use std::{fmt, ops::Deref, str::FromStr};
#[cfg(feature = "verify")]
use hmac::{Hmac, Mac};
#[cfg(feature = "verify")]
use nym_crypto::asymmetric::encryption::PrivateKey;
#[cfg(feature = "verify")]
use sha2::Sha256;
pub type PendingRegistrations = HashMap<PeerPublicKey, RegistrationData>;
pub type PrivateIPs = HashMap<IpPair, Taken>;
#[cfg(feature = "verify")]
pub type HmacSha256 = Hmac<Sha256>;
pub type Nonce = u64;
pub type Taken = Option<SystemTime>;
pub const BANDWIDTH_CAP_PER_DAY: u64 = 250 * 1024 * 1024 * 1024; // 250 GB
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct IpPair {
pub ipv4: Ipv4Addr,
pub ipv6: Ipv6Addr,
}
impl IpPair {
pub fn new(ipv4: Ipv4Addr, ipv6: Ipv6Addr) -> Self {
IpPair { ipv4, ipv6 }
}
}
impl From<(Ipv4Addr, Ipv6Addr)> for IpPair {
fn from((ipv4, ipv6): (Ipv4Addr, Ipv6Addr)) -> Self {
IpPair { ipv4, ipv6 }
}
}
impl fmt::Display for IpPair {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({}, {})", self.ipv4, self.ipv6)
}
}
impl From<IpAddr> for IpPair {
fn from(value: IpAddr) -> Self {
let (before_last_byte, last_byte) = match value {
std::net::IpAddr::V4(ipv4_addr) => (ipv4_addr.octets()[2], ipv4_addr.octets()[3]),
std::net::IpAddr::V6(ipv6_addr) => (ipv6_addr.octets()[14], ipv6_addr.octets()[15]),
};
let last_bytes = ((before_last_byte as u16) << 8) | last_byte as u16;
let ipv4 = Ipv4Addr::new(
WG_TUN_DEVICE_IP_ADDRESS_V4.octets()[0],
WG_TUN_DEVICE_IP_ADDRESS_V4.octets()[1],
before_last_byte,
last_byte,
);
let ipv6 = Ipv6Addr::new(
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[0],
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[1],
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[2],
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[3],
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[4],
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[5],
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[6],
last_bytes,
);
IpPair::new(ipv4, ipv6)
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct InitMessage {
/// Base64 encoded x25519 public key
pub pub_key: PeerPublicKey,
}
impl InitMessage {
pub fn new(pub_key: PeerPublicKey) -> Self {
InitMessage { pub_key }
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct FinalMessage {
/// Gateway client data
pub gateway_client: GatewayClient,
/// Ecash credential
pub credential: Option<CredentialSpendingData>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct RegistrationData {
pub nonce: u64,
pub gateway_data: GatewayClient,
pub wg_port: u16,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct RegistredData {
pub pub_key: PeerPublicKey,
pub private_ips: IpPair,
pub wg_port: u16,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct RemainingBandwidthData {
pub available_bandwidth: i64,
}
/// Client that wants to register sends its PublicKey bytes mac digest encrypted with a DH shared secret.
/// Gateway/Nym node can then verify pub_key payload using the same process
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct GatewayClient {
/// Base64 encoded x25519 public key
pub pub_key: PeerPublicKey,
/// Assigned private IPs (v4 and v6)
pub private_ips: IpPair,
/// Sha256 hmac on the data (alongside the prior nonce)
pub mac: ClientMac,
}
impl GatewayClient {
#[cfg(feature = "verify")]
pub fn new(
local_secret: &PrivateKey,
remote_public: x25519_dalek::PublicKey,
private_ips: IpPair,
nonce: u64,
) -> Self {
// 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 dh = static_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())
.expect("x25519 shared secret is always 32 bytes long");
mac.update(local_public.as_bytes());
mac.update(private_ips.to_string().as_bytes());
mac.update(&nonce.to_le_bytes());
GatewayClient {
pub_key: PeerPublicKey::new(local_public),
private_ips,
mac: ClientMac(mac.finalize().into_bytes().to_vec()),
}
}
// Reusable secret should be gateways Wireguard PK
// Client should perform this step when generating its payload, using its own WG PK
#[cfg(feature = "verify")]
pub fn verify(&self, gateway_key: &PrivateKey, nonce: u64) -> Result<(), Error> {
// 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);
// TODO: change that to use our nym_crypto::hmac module instead
#[allow(clippy::expect_used)]
let mut mac = HmacSha256::new_from_slice(dh.as_bytes())
.expect("x25519 shared secret is always 32 bytes long");
mac.update(self.pub_key.as_bytes());
mac.update(self.private_ips.to_string().as_bytes());
mac.update(&nonce.to_le_bytes());
mac.verify_slice(&self.mac)
.map_err(|source| Error::FailedClientMacVerification {
client: self.pub_key.to_string(),
source,
})
}
pub fn pub_key(&self) -> PeerPublicKey {
self.pub_key
}
}
// TODO: change the inner type into generic array of size HmacSha256::OutputSize
// TODO2: rely on our internal crypto/hmac
#[derive(Debug, Clone, PartialEq)]
pub struct ClientMac(Vec<u8>);
impl fmt::Display for ClientMac {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", general_purpose::STANDARD.encode(&self.0))
}
}
impl ClientMac {
#[allow(dead_code)]
pub fn new(mac: Vec<u8>) -> Self {
ClientMac(mac)
}
}
impl Deref for ClientMac {
type Target = Vec<u8>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl FromStr for ClientMac {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mac_bytes: Vec<u8> =
general_purpose::STANDARD
.decode(s)
.map_err(|source| Error::MalformedClientMac {
mac: s.to_string(),
source,
})?;
Ok(ClientMac(mac_bytes))
}
}
impl Serialize for ClientMac {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let encoded_key = general_purpose::STANDARD.encode(self.0.clone());
serializer.serialize_str(&encoded_key)
}
}
impl<'de> Deserialize<'de> for ClientMac {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let encoded_key = String::deserialize(deserializer)?;
ClientMac::from_str(&encoded_key).map_err(serde::de::Error::custom)
}
}
#[cfg(test)]
mod tests {
use super::*;
use nym_crypto::asymmetric::encryption;
#[test]
fn create_ip_pair() {
let ipv4: IpAddr = Ipv4Addr::from_str("10.1.10.50").unwrap().into();
let ipv6: IpAddr = Ipv6Addr::from_str("fc01::0a32").unwrap().into();
assert_eq!(IpPair::from(ipv4), IpPair::from(ipv6));
}
#[test]
#[cfg(feature = "verify")]
fn client_request_roundtrip() {
let mut rng = rand::thread_rng();
let gateway_key_pair = encryption::KeyPair::new(&mut rng);
let client_key_pair = encryption::KeyPair::new(&mut rng);
let nonce = 1234567890;
let client = GatewayClient::new(
client_key_pair.private_key(),
x25519_dalek::PublicKey::from(gateway_key_pair.public_key().to_bytes()),
IpPair::new("10.0.0.42".parse().unwrap(), "fc00::42".parse().unwrap()),
nonce,
);
assert!(client.verify(gateway_key_pair.private_key(), nonce).is_ok())
}
}
@@ -0,0 +1,132 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::{
registration::{FinalMessage, InitMessage},
topup::TopUpMessage,
};
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
use nym_wireguard_types::PeerPublicKey;
use serde::{Deserialize, Serialize};
use crate::make_bincode_serializer;
use super::VERSION;
fn generate_random() -> u64 {
use rand::RngCore;
let mut rng = rand::rngs::OsRng;
rng.next_u64()
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct AuthenticatorRequest {
pub protocol: Protocol,
pub data: AuthenticatorRequestData,
pub request_id: u64,
}
impl AuthenticatorRequest {
pub fn from_reconstructed_message(
message: &nym_sphinx::receiver::ReconstructedMessage,
) -> Result<Self, bincode::Error> {
use bincode::Options;
make_bincode_serializer().deserialize(&message.message)
}
pub fn new_initial_request(init_message: InitMessage) -> (Self, u64) {
let request_id = generate_random();
(
Self {
protocol: Protocol {
service_provider_type: ServiceProviderType::Authenticator,
version: VERSION,
},
data: AuthenticatorRequestData::Initial(init_message),
request_id,
},
request_id,
)
}
pub fn new_final_request(final_message: FinalMessage) -> (Self, u64) {
let request_id = generate_random();
(
Self {
protocol: Protocol {
service_provider_type: ServiceProviderType::Authenticator,
version: VERSION,
},
data: AuthenticatorRequestData::Final(Box::new(final_message)),
request_id,
},
request_id,
)
}
pub fn new_query_request(peer_public_key: PeerPublicKey) -> (Self, u64) {
let request_id = generate_random();
(
Self {
protocol: Protocol {
service_provider_type: ServiceProviderType::Authenticator,
version: VERSION,
},
data: AuthenticatorRequestData::QueryBandwidth(peer_public_key),
request_id,
},
request_id,
)
}
pub fn new_topup_request(top_up_message: TopUpMessage) -> (Self, u64) {
let request_id = generate_random();
(
Self {
protocol: Protocol {
service_provider_type: ServiceProviderType::Authenticator,
version: VERSION,
},
data: AuthenticatorRequestData::TopUpBandwidth(Box::new(top_up_message)),
request_id,
},
request_id,
)
}
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
use bincode::Options;
make_bincode_serializer().serialize(self)
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum AuthenticatorRequestData {
Initial(InitMessage),
Final(Box<FinalMessage>),
QueryBandwidth(PeerPublicKey),
TopUpBandwidth(Box<TopUpMessage>),
}
#[cfg(test)]
mod tests {
use super::*;
use std::str::FromStr;
#[test]
fn check_first_bytes_protocol() {
let version = 5;
let data = AuthenticatorRequest {
protocol: Protocol {
version,
service_provider_type: ServiceProviderType::Authenticator,
},
data: AuthenticatorRequestData::Initial(InitMessage::new(
PeerPublicKey::from_str("yvNUDpT5l7W/xDhiu6HkqTHDQwbs/B3J5UrLmORl1EQ=").unwrap(),
)),
request_id: 1,
};
let bytes = *data.to_bytes().unwrap().first_chunk::<2>().unwrap();
assert_eq!(bytes, [version, ServiceProviderType::Authenticator as u8]);
}
}
@@ -0,0 +1,132 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::registration::{RegistrationData, RegistredData, RemainingBandwidthData};
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
use serde::{Deserialize, Serialize};
use crate::make_bincode_serializer;
use super::VERSION;
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct AuthenticatorResponse {
pub protocol: Protocol,
pub data: AuthenticatorResponseData,
}
impl AuthenticatorResponse {
pub fn new_pending_registration_success(
registration_data: RegistrationData,
request_id: u64,
) -> Self {
Self {
protocol: Protocol {
service_provider_type: ServiceProviderType::Authenticator,
version: VERSION,
},
data: AuthenticatorResponseData::PendingRegistration(PendingRegistrationResponse {
reply: registration_data,
request_id,
}),
}
}
pub fn new_registered(registred_data: RegistredData, request_id: u64) -> Self {
Self {
protocol: Protocol {
service_provider_type: ServiceProviderType::Authenticator,
version: VERSION,
},
data: AuthenticatorResponseData::Registered(RegisteredResponse {
reply: registred_data,
request_id,
}),
}
}
pub fn new_remaining_bandwidth(
remaining_bandwidth_data: Option<RemainingBandwidthData>,
request_id: u64,
) -> Self {
Self {
protocol: Protocol {
service_provider_type: ServiceProviderType::Authenticator,
version: VERSION,
},
data: AuthenticatorResponseData::RemainingBandwidth(RemainingBandwidthResponse {
reply: remaining_bandwidth_data,
request_id,
}),
}
}
pub fn new_topup_bandwidth(
remaining_bandwidth_data: RemainingBandwidthData,
request_id: u64,
) -> Self {
Self {
protocol: Protocol {
service_provider_type: ServiceProviderType::Authenticator,
version: VERSION,
},
data: AuthenticatorResponseData::TopUpBandwidth(TopUpBandwidthResponse {
reply: remaining_bandwidth_data,
request_id,
}),
}
}
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
use bincode::Options;
make_bincode_serializer().serialize(self)
}
pub fn from_reconstructed_message(
message: &nym_sphinx::receiver::ReconstructedMessage,
) -> Result<Self, bincode::Error> {
use bincode::Options;
make_bincode_serializer().deserialize(&message.message)
}
pub fn id(&self) -> Option<u64> {
match &self.data {
AuthenticatorResponseData::PendingRegistration(response) => Some(response.request_id),
AuthenticatorResponseData::Registered(response) => Some(response.request_id),
AuthenticatorResponseData::RemainingBandwidth(response) => Some(response.request_id),
AuthenticatorResponseData::TopUpBandwidth(response) => Some(response.request_id),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum AuthenticatorResponseData {
PendingRegistration(PendingRegistrationResponse),
Registered(RegisteredResponse),
RemainingBandwidth(RemainingBandwidthResponse),
TopUpBandwidth(TopUpBandwidthResponse),
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct PendingRegistrationResponse {
pub request_id: u64,
pub reply: RegistrationData,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct RegisteredResponse {
pub request_id: u64,
pub reply: RegistredData,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct RemainingBandwidthResponse {
pub request_id: u64,
pub reply: Option<RemainingBandwidthData>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct TopUpBandwidthResponse {
pub request_id: u64,
pub reply: RemainingBandwidthData,
}
@@ -0,0 +1,15 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_credentials_interface::CredentialSpendingData;
use nym_wireguard_types::PeerPublicKey;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct TopUpMessage {
/// Base64 encoded x25519 public key
pub pub_key: PeerPublicKey,
/// Ecash credential
pub credential: CredentialSpendingData,
}
+4 -7
View File
@@ -105,26 +105,24 @@ impl<C, St: Storage> BandwidthController<C, St> {
async fn get_aggregate_verification_key(
&self,
epoch_id: EpochId,
apis: &mut ApiClientsWrapper,
ecash_apis: &mut ApiClientsWrapper<'_, C>,
) -> Result<VerificationKeyAuth, BandwidthControllerError>
where
C: DkgQueryClient + Sync + Send,
<St as Storage>::StorageError: Send + Sync + 'static,
{
let ecash_apis = apis.get_or_init(epoch_id, &self.client).await?;
get_aggregate_verification_key(&self.storage, epoch_id, ecash_apis).await
}
async fn get_coin_index_signatures(
&self,
epoch_id: EpochId,
apis: &mut ApiClientsWrapper,
ecash_apis: &mut ApiClientsWrapper<'_, C>,
) -> Result<Vec<AnnotatedCoinIndexSignature>, BandwidthControllerError>
where
C: DkgQueryClient + Sync + Send,
<St as Storage>::StorageError: Send + Sync + 'static,
{
let ecash_apis = apis.get_or_init(epoch_id, &self.client).await?;
get_coin_index_signatures(&self.storage, epoch_id, ecash_apis).await
}
@@ -132,13 +130,12 @@ impl<C, St: Storage> BandwidthController<C, St> {
&self,
epoch_id: EpochId,
expiration_date: Date,
apis: &mut ApiClientsWrapper,
ecash_apis: &mut ApiClientsWrapper<'_, C>,
) -> Result<Vec<AnnotatedExpirationDateSignature>, BandwidthControllerError>
where
C: DkgQueryClient + Sync + Send,
<St as Storage>::StorageError: Send + Sync + 'static,
{
let ecash_apis = apis.get_or_init(epoch_id, &self.client).await?;
get_expiration_date_signatures(&self.storage, epoch_id, expiration_date, ecash_apis).await
}
@@ -154,7 +151,7 @@ impl<C, St: Storage> BandwidthController<C, St> {
{
let epoch_id = retrieved_ticketbook.ticketbook.epoch_id();
let expiration_date = retrieved_ticketbook.ticketbook.expiration_date();
let mut api_clients = Default::default();
let mut api_clients = ApiClientsWrapper::new(&self.client, epoch_id);
let verification_key = self
.get_aggregate_verification_key(epoch_id, &mut api_clients)
+64 -21
View File
@@ -21,30 +21,67 @@ use rand::thread_rng;
use std::fmt::Display;
use std::future::Future;
// it really doesn't need the RwLock because it's never moved across tasks,
// but we need all the Send/Sync action
#[derive(Default)]
pub(crate) struct ApiClientsWrapper(Option<Vec<EcashApiClient>>);
impl ApiClientsWrapper {
pub(crate) async fn get_or_init<C>(
pub(crate) trait EcashClientsProvider {
async fn try_get_ecash_clients(
&mut self,
) -> Result<Vec<EcashApiClient>, BandwidthControllerError>;
}
impl EcashClientsProvider for Vec<EcashApiClient> {
async fn try_get_ecash_clients(
&mut self,
) -> Result<Vec<EcashApiClient>, BandwidthControllerError> {
Ok(self.clone())
}
}
impl<C> EcashClientsProvider for &mut ApiClientsWrapper<'_, C>
where
C: DkgQueryClient + Sync + Send,
{
async fn try_get_ecash_clients(
&mut self,
) -> Result<Vec<EcashApiClient>, BandwidthControllerError> {
self.clients().await
}
}
pub(crate) enum ApiClientsWrapper<'a, C> {
Uninitialised {
query_client: &'a C,
epoch_id: EpochId,
dkg_client: &C,
) -> Result<Vec<EcashApiClient>, BandwidthControllerError>
},
Cached {
clients: Vec<EcashApiClient>,
},
}
impl<'a, C> ApiClientsWrapper<'a, C> {
pub(crate) fn new(query_client: &'a C, epoch_id: EpochId) -> Self {
ApiClientsWrapper::Uninitialised {
query_client,
epoch_id,
}
}
async fn clients(&mut self) -> Result<Vec<EcashApiClient>, BandwidthControllerError>
where
C: DkgQueryClient + Sync + Send,
{
if let Some(cached) = &self.0 {
return Ok(cached.clone());
match self {
ApiClientsWrapper::Uninitialised {
query_client,
epoch_id,
} => {
let clients = all_ecash_api_clients(*query_client, *epoch_id).await?;
*self = ApiClientsWrapper::Cached {
clients: clients.clone(),
};
Ok(clients)
}
ApiClientsWrapper::Cached { clients } => Ok(clients.clone()),
}
let clients = all_ecash_api_clients(dkg_client, epoch_id).await?;
// technically we don't have to be cloning all the clients here, but it's way simpler than
// dealing with locking and whatnot given the performance penalty is negligible
self.0 = Some(clients.clone());
Ok(clients)
}
}
@@ -76,7 +113,7 @@ where
pub(crate) async fn get_aggregate_verification_key<St>(
storage: &St,
epoch_id: EpochId,
ecash_apis: Vec<EcashApiClient>,
mut ecash_apis: impl EcashClientsProvider,
) -> Result<VerificationKeyAuth, BandwidthControllerError>
where
St: Storage,
@@ -90,6 +127,8 @@ where
return Ok(stored);
};
let ecash_apis = ecash_apis.try_get_ecash_clients().await?;
let master_vk = query_random_apis_until_success(
ecash_apis,
|api| async move { api.api_client.master_verification_key(Some(epoch_id)).await },
@@ -115,7 +154,7 @@ where
pub(crate) async fn get_coin_index_signatures<St>(
storage: &St,
epoch_id: EpochId,
ecash_apis: Vec<EcashApiClient>,
mut ecash_apis: impl EcashClientsProvider,
) -> Result<Vec<AnnotatedCoinIndexSignature>, BandwidthControllerError>
where
St: Storage,
@@ -129,6 +168,8 @@ where
return Ok(stored);
};
let ecash_apis = ecash_apis.try_get_ecash_clients().await?;
let index_sigs = query_random_apis_until_success(
ecash_apis,
|api| async move {
@@ -159,7 +200,7 @@ pub(crate) async fn get_expiration_date_signatures<St>(
storage: &St,
epoch_id: EpochId,
expiration_date: Date,
ecash_apis: Vec<EcashApiClient>,
mut ecash_apis: impl EcashClientsProvider,
) -> Result<Vec<AnnotatedExpirationDateSignature>, BandwidthControllerError>
where
St: Storage,
@@ -173,6 +214,8 @@ where
return Ok(stored);
};
let ecash_apis = ecash_apis.try_get_ecash_clients().await?;
let expiration_sigs = query_random_apis_until_success(
ecash_apis,
|api| async move {
+11 -1
View File
@@ -45,11 +45,12 @@ const DEFAULT_COVER_TRAFFIC_PRIMARY_SIZE_RATIO: f64 = 0.70;
// clients/client-core/src/client/replies/reply_storage/surb_storage.rs
const DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 10;
const DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 200;
const DEFAULT_MINIMUM_REPLY_SURB_THRESHOLD_BUFFER: usize = 0;
// define how much to request at once
// clients/client-core/src/client/replies/reply_controller.rs
const DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 10;
const DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 100;
const DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 50;
const DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE: u32 = 500;
@@ -621,6 +622,10 @@ pub struct ReplySurbs {
/// Defines the maximum number of reply surbs the client wants to keep in its storage at any times.
pub maximum_reply_surb_storage_threshold: usize,
/// Defines the soft threshold ontop of the minimum reply surb storage threshold for when the client
/// should proactively request additional reply surbs.
pub minimum_reply_surb_threshold_buffer: usize,
/// Defines the minimum number of reply surbs the client would request.
pub minimum_reply_surb_request_size: u32,
@@ -653,6 +658,9 @@ pub struct ReplySurbs {
/// Specifies the number of mixnet hops the packet should go through. If not specified, then
/// the default value is used.
pub surb_mix_hops: Option<u8>,
/// Specifies if we should reset all the sender tags on startup
pub fresh_sender_tags: bool,
}
impl Default for ReplySurbs {
@@ -660,6 +668,7 @@ impl Default for ReplySurbs {
ReplySurbs {
minimum_reply_surb_storage_threshold: DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD,
maximum_reply_surb_storage_threshold: DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD,
minimum_reply_surb_threshold_buffer: DEFAULT_MINIMUM_REPLY_SURB_THRESHOLD_BUFFER,
minimum_reply_surb_request_size: DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE,
maximum_reply_surb_request_size: DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE,
maximum_allowed_reply_surb_request_size: DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE,
@@ -669,6 +678,7 @@ impl Default for ReplySurbs {
maximum_reply_surb_age: DEFAULT_MAXIMUM_REPLY_SURB_AGE,
maximum_reply_key_age: DEFAULT_MAXIMUM_REPLY_KEY_AGE,
surb_mix_hops: None,
fresh_sender_tags: false,
}
}
}
@@ -181,6 +181,7 @@ impl From<ConfigV5> for Config {
maximum_reply_surb_age: value.debug.reply_surbs.maximum_reply_surb_age,
maximum_reply_key_age: value.debug.reply_surbs.maximum_reply_key_age,
surb_mix_hops: value.debug.reply_surbs.surb_mix_hops,
..Default::default()
},
..Default::default()
},
@@ -88,7 +88,7 @@ pub async fn setup_fs_reply_surb_backend<P: AsRef<Path>>(
let db_path = db_path.as_ref();
if db_path.exists() {
info!("loading existing surb database");
match fs_backend::Backend::try_load(db_path).await {
match fs_backend::Backend::try_load(db_path, surb_config.fresh_sender_tags).await {
Ok(backend) => Ok(backend),
Err(err) => {
error!("failed to setup persistent storage backend for our reply needs: {err}. We're going to create a fresh database instead. This behaviour might change in the future");
@@ -28,6 +28,7 @@ pub enum InputMessage {
recipient: Recipient,
data: Vec<u8>,
lane: TransmissionLane,
max_retransmissions: Option<u32>,
},
/// Creates a message used for a duplex anonymous communication where the recipient
@@ -43,6 +44,7 @@ pub enum InputMessage {
data: Vec<u8>,
reply_surbs: u32,
lane: TransmissionLane,
max_retransmissions: Option<u32>,
},
/// Attempt to use our internally received and stored `ReplySurb` to send the message back
@@ -53,6 +55,7 @@ pub enum InputMessage {
recipient_tag: AnonymousSenderTag,
data: Vec<u8>,
lane: TransmissionLane,
max_retransmissions: Option<u32>,
},
MessageWrapper {
@@ -92,6 +95,7 @@ impl InputMessage {
recipient,
data,
lane,
max_retransmissions: None,
};
if let Some(packet_type) = packet_type {
InputMessage::new_wrapper(message, packet_type)
@@ -112,28 +116,7 @@ impl InputMessage {
data,
reply_surbs,
lane,
};
if let Some(packet_type) = packet_type {
InputMessage::new_wrapper(message, packet_type)
} else {
message
}
}
// IMHO `new_anonymous` should take `mix_hops: Option<u8>` as an argument instead of creating
// this function, but that would potentially break backwards compatibility with the current API
pub fn new_anonymous_with_custom_hops(
recipient: Recipient,
data: Vec<u8>,
reply_surbs: u32,
lane: TransmissionLane,
packet_type: Option<PacketType>,
) -> Self {
let message = InputMessage::Anonymous {
recipient,
data,
reply_surbs,
lane,
max_retransmissions: None,
};
if let Some(packet_type) = packet_type {
InputMessage::new_wrapper(message, packet_type)
@@ -152,6 +135,7 @@ impl InputMessage {
recipient_tag,
data,
lane,
max_retransmissions: None,
};
if let Some(packet_type) = packet_type {
InputMessage::new_wrapper(message, packet_type)
@@ -169,4 +153,34 @@ impl InputMessage {
InputMessage::MessageWrapper { message, .. } => message.lane(),
}
}
pub fn set_max_retransmissions(&mut self, max_retransmissions: u32) -> &mut Self {
match self {
InputMessage::Regular {
max_retransmissions: m,
..
}
| InputMessage::Anonymous {
max_retransmissions: m,
..
}
| InputMessage::Reply {
max_retransmissions: m,
..
} => {
*m = Some(max_retransmissions);
}
InputMessage::Premade { .. } => {}
InputMessage::MessageWrapper { message, .. } => {
message.set_max_retransmissions(max_retransmissions);
}
}
self
}
pub fn with_max_retransmissions(mut self, max_retransmissions: u32) -> Self {
self.set_max_retransmissions(max_retransmissions);
self
}
}
@@ -52,7 +52,7 @@ impl MixTrafficController {
let (message_sender, message_receiver) =
tokio::sync::mpsc::channel(MIX_MESSAGE_RECEIVER_BUFFER_SIZE);
let (client_sender, client_receiver) = tokio::sync::mpsc::channel(1);
let (client_sender, client_receiver) = tokio::sync::mpsc::channel(8);
(
MixTrafficController {
@@ -77,7 +77,7 @@ impl MixTrafficController {
) {
let (message_sender, message_receiver) =
tokio::sync::mpsc::channel(MIX_MESSAGE_RECEIVER_BUFFER_SIZE);
let (client_sender, client_receiver) = tokio::sync::mpsc::channel(1);
let (client_sender, client_receiver) = tokio::sync::mpsc::channel(8);
(
MixTrafficController {
gateway_transceiver,
@@ -222,9 +222,6 @@ impl ActionController {
// note: when the entry expires it's automatically removed from pending_acks_timers
fn handle_expired_ack_timer(&mut self, expired_ack: Expired<FragmentIdentifier>) {
// I'm honestly not sure how to handle it, because getting it means other things in our
// system are already misbehaving. If we ever see this panic, then I guess we should worry
// about it. Perhaps just reschedule it at later point?
let frag_id = expired_ack.into_inner();
trace!("{frag_id} has expired");
@@ -65,11 +65,12 @@ where
recipient_tag: AnonymousSenderTag,
data: Vec<u8>,
lane: TransmissionLane,
max_retransmissions: Option<u32>,
) {
// offload reply handling to the dedicated task
if let Err(err) = self
.reply_controller_sender
.send_reply(recipient_tag, data, lane)
if let Err(err) =
self.reply_controller_sender
.send_reply(recipient_tag, data, lane, max_retransmissions)
{
if !self.task_client.is_shutdown_poll() {
error!("failed to send a reply - {err}");
@@ -83,10 +84,11 @@ where
content: Vec<u8>,
lane: TransmissionLane,
packet_type: PacketType,
max_retransmissions: Option<u32>,
) {
if let Err(err) = self
.message_handler
.try_send_plain_message(recipient, content, lane, packet_type)
.try_send_plain_message(recipient, content, lane, packet_type, max_retransmissions)
.await
{
warn!("failed to send a plain message - {err}")
@@ -100,10 +102,18 @@ where
reply_surbs: u32,
lane: TransmissionLane,
packet_type: PacketType,
max_retransmissions: Option<u32>,
) {
if let Err(err) = self
.message_handler
.try_send_message_with_reply_surbs(recipient, content, reply_surbs, lane, packet_type)
.try_send_message_with_reply_surbs(
recipient,
content,
reply_surbs,
lane,
packet_type,
max_retransmissions,
)
.await
{
warn!("failed to send a repliable message - {err}")
@@ -116,25 +126,42 @@ where
recipient,
data,
lane,
max_retransmissions,
} => {
self.handle_plain_message(recipient, data, lane, PacketType::Mix)
.await
self.handle_plain_message(
recipient,
data,
lane,
PacketType::Mix,
max_retransmissions,
)
.await
}
InputMessage::Anonymous {
recipient,
data,
reply_surbs,
lane,
max_retransmissions,
} => {
self.handle_repliable_message(recipient, data, reply_surbs, lane, PacketType::Mix)
.await
self.handle_repliable_message(
recipient,
data,
reply_surbs,
lane,
PacketType::Mix,
max_retransmissions,
)
.await
}
InputMessage::Reply {
recipient_tag,
data,
lane,
max_retransmissions,
} => {
self.handle_reply(recipient_tag, data, lane).await;
self.handle_reply(recipient_tag, data, lane, max_retransmissions)
.await;
}
InputMessage::Premade { msgs, lane } => self.handle_premade_packets(msgs, lane).await,
InputMessage::MessageWrapper {
@@ -145,25 +172,42 @@ where
recipient,
data,
lane,
max_retransmissions,
} => {
self.handle_plain_message(recipient, data, lane, packet_type)
.await
self.handle_plain_message(
recipient,
data,
lane,
packet_type,
max_retransmissions,
)
.await
}
InputMessage::Anonymous {
recipient,
data,
reply_surbs,
lane,
max_retransmissions,
} => {
self.handle_repliable_message(recipient, data, reply_surbs, lane, packet_type)
.await
self.handle_repliable_message(
recipient,
data,
reply_surbs,
lane,
packet_type,
max_retransmissions,
)
.await
}
InputMessage::Reply {
recipient_tag,
data,
lane,
max_retransmissions,
} => {
self.handle_reply(recipient_tag, data, lane).await;
self.handle_reply(recipient_tag, data, lane, max_retransmissions)
.await;
}
InputMessage::Premade { msgs, lane } => {
self.handle_premade_packets(msgs, lane).await
@@ -72,6 +72,7 @@ pub struct PendingAcknowledgement {
delay: SphinxDelay,
destination: PacketDestination,
retransmissions: u32,
max_retransmissions: Option<u32>,
}
impl PendingAcknowledgement {
@@ -80,12 +81,14 @@ impl PendingAcknowledgement {
message_chunk: Fragment,
delay: SphinxDelay,
recipient: Recipient,
max_retransmissions: Option<u32>,
) -> Self {
PendingAcknowledgement {
message_chunk,
delay,
destination: PacketDestination::KnownRecipient(recipient.into()),
retransmissions: 0,
max_retransmissions,
}
}
@@ -94,6 +97,7 @@ impl PendingAcknowledgement {
delay: SphinxDelay,
recipient_tag: AnonymousSenderTag,
extra_surb_request: bool,
max_retransmissions: Option<u32>,
) -> Self {
PendingAcknowledgement {
message_chunk,
@@ -103,6 +107,7 @@ impl PendingAcknowledgement {
extra_surb_request,
},
retransmissions: 0,
max_retransmissions,
}
}
@@ -118,6 +123,18 @@ impl PendingAcknowledgement {
self.delay = new_delay;
self.retransmissions += 1;
}
pub(crate) fn reached_max_retransmissions(
&self,
global_max_retransmissions: Option<u32>,
) -> bool {
let reached_local_max = self
.max_retransmissions
.is_some_and(|limit| self.retransmissions >= limit);
let reached_global_max =
global_max_retransmissions.is_some_and(|limit| self.retransmissions >= limit);
reached_local_max || reached_global_max
}
}
/// AcknowledgementControllerConnectors represents set of channels for communication with
@@ -79,17 +79,15 @@ where
let frag_id = timed_out_ack.message_chunk.fragment_identifier();
if let Some(limit) = self.maximum_retransmissions {
if timed_out_ack.retransmissions >= limit {
warn!("reached maximum number of allowed retransmissions for the packet");
if let Err(err) = self
.action_sender
.unbounded_send(Action::new_remove(frag_id))
{
error!("Failed to send remove action to the controller: {err}");
}
return;
if timed_out_ack.reached_max_retransmissions(self.maximum_retransmissions) {
debug!("reached maximum number of allowed retransmissions for the packet");
if let Err(err) = self
.action_sender
.unbounded_send(Action::new_remove(frag_id))
{
error!("Failed to send remove action to the controller: {err}");
}
return;
}
let maybe_prepared_fragment = match &timed_out_ack.destination {
@@ -6,6 +6,7 @@ use crate::client::real_messages_control::real_traffic_stream::{
BatchRealMessageSender, RealMessage,
};
use crate::client::real_messages_control::{AckActionSender, Action};
use crate::client::replies::reply_controller::MaxRetransmissions;
use crate::client::replies::reply_storage::{ReceivedReplySurbsMap, SentReplyKeys, UsedSenderTags};
use crate::client::topology_control::{TopologyAccessor, TopologyReadPermit};
use log::{debug, error, info, trace, warn};
@@ -33,10 +34,12 @@ pub enum PreparationError {
#[error(transparent)]
NymTopologyError(#[from] NymTopologyError),
#[error("The received message cannot be sent using a single reply surb. It ended up getting split into {fragments} fragments.")]
#[error("message too long for a single SURB, splitting into {fragments} fragments.")]
MessageTooLongForSingleSurb { fragments: usize },
#[error("Not enough reply SURBs to send the message. We have {available} available and require at least {required}.")]
#[error(
"not enough reply SURBs to send the message, available: {available} required: {required}."
)]
NotEnoughSurbs { available: usize, required: usize },
}
@@ -140,6 +143,12 @@ impl Config {
}
}
#[derive(Clone)]
pub(crate) struct FragmentWithMaxRetransmissions {
pub(crate) fragment: Fragment,
pub(crate) max_retransmissions: MaxRetransmissions,
}
#[derive(Clone)]
pub(crate) struct MessageHandler<R> {
config: Config,
@@ -196,10 +205,10 @@ where
trace!("we already had sender tag for {recipient}");
existing
} else {
info!("creating new sender tag for {recipient}");
debug!("creating new sender tag for {recipient}");
let new_tag = AnonymousSenderTag::new_random(&mut self.rng);
self.tag_storage.insert_new(recipient, new_tag);
info!("we'll be using {new_tag} for all anonymous messages sent to {recipient}");
info!("using {new_tag} for all anonymous messages sent to {recipient}");
new_tag
}
}
@@ -292,8 +301,14 @@ where
Some(chunk.fragment_identifier()),
);
let delay = prepared_fragment.total_delay;
let pending_ack =
PendingAcknowledgement::new_anonymous(chunk, delay, target, is_extra_surb_request);
let max_retransmissions = None;
let pending_ack = PendingAcknowledgement::new_anonymous(
chunk,
delay,
target,
is_extra_surb_request,
max_retransmissions,
);
let lane = if is_extra_surb_request {
TransmissionLane::ReplySurbRequest
@@ -348,7 +363,7 @@ where
pub(crate) async fn try_send_reply_chunks_on_lane(
&mut self,
target: AnonymousSenderTag,
fragments: Vec<Fragment>,
fragments: Vec<FragmentWithMaxRetransmissions>,
reply_surbs: Vec<ReplySurb>,
lane: TransmissionLane,
) -> Result<(), SurbWrappedPreparationError> {
@@ -365,12 +380,12 @@ where
pub(crate) async fn try_send_reply_chunks(
&mut self,
target: AnonymousSenderTag,
fragments: Vec<(TransmissionLane, Fragment)>,
fragments: Vec<(TransmissionLane, FragmentWithMaxRetransmissions)>,
reply_surbs: Vec<ReplySurb>,
) -> Result<(), SurbWrappedPreparationError> {
let prepared_fragments = self
.prepare_reply_chunks_for_sending(
fragments.iter().map(|(_, f)| f.clone()).collect(),
fragments.iter().map(|(_, f)| f.fragment.clone()).collect(),
reply_surbs,
)
.await?;
@@ -380,12 +395,21 @@ where
for (raw, prepared) in fragments.into_iter().zip(prepared_fragments.into_iter()) {
let lane = raw.0;
let fragment = raw.1;
let FragmentWithMaxRetransmissions {
fragment,
max_retransmissions,
} = raw.1;
let real_message =
RealMessage::new(prepared.mix_packet, Some(prepared.fragment_identifier));
let delay = prepared.total_delay;
let pending_ack = PendingAcknowledgement::new_anonymous(fragment, delay, target, false);
let pending_ack = PendingAcknowledgement::new_anonymous(
fragment,
delay,
target,
false,
max_retransmissions,
);
let entry = to_forward.entry(lane).or_default();
entry.push(real_message);
@@ -414,10 +438,17 @@ where
message: Vec<u8>,
lane: TransmissionLane,
packet_type: PacketType,
max_retransmissions: Option<u32>,
) -> Result<(), PreparationError> {
let message = NymMessage::new_plain(message);
self.try_split_and_send_non_reply_message(message, recipient, lane, packet_type)
.await
self.try_split_and_send_non_reply_message(
message,
recipient,
lane,
packet_type,
max_retransmissions,
)
.await
}
pub(crate) async fn try_split_and_send_non_reply_message(
@@ -426,6 +457,7 @@ where
recipient: Recipient,
lane: TransmissionLane,
packet_type: PacketType,
max_retransmissions: Option<u32>,
) -> Result<(), PreparationError> {
debug!("Sending non-reply message with packet type {packet_type}");
// TODO: I really dislike existence of this assertion, it implies code has to be re-organised
@@ -465,7 +497,8 @@ where
Some(fragment.fragment_identifier()),
);
let delay = prepared_fragment.total_delay;
let pending_ack = PendingAcknowledgement::new_known(fragment, delay, recipient);
let pending_ack =
PendingAcknowledgement::new_known(fragment, delay, recipient, max_retransmissions);
real_messages.push(real_message);
pending_acks.push(pending_ack);
@@ -493,11 +526,15 @@ where
reply_surbs,
));
// When sending SURBs we want to retransmit
let max_retransmissions = None;
self.try_split_and_send_non_reply_message(
message,
recipient,
TransmissionLane::AdditionalReplySurbs,
packet_type,
max_retransmissions,
)
.await?;
@@ -514,6 +551,7 @@ where
num_reply_surbs: u32,
lane: TransmissionLane,
packet_type: PacketType,
max_retransmissions: Option<u32>,
) -> Result<(), SurbWrappedPreparationError> {
debug!("Sending message with reply SURBs with packet type {packet_type}");
let sender_tag = self.get_or_create_sender_tag(&recipient);
@@ -524,8 +562,14 @@ where
let message =
NymMessage::new_repliable(RepliableMessage::new_data(message, sender_tag, reply_surbs));
self.try_split_and_send_non_reply_message(message, recipient, lane, packet_type)
.await?;
self.try_split_and_send_non_reply_message(
message,
recipient,
lane,
packet_type,
max_retransmissions,
)
.await?;
log::trace!("storing {} reply keys", reply_keys.len());
self.reply_key_storage.insert_multiple(reply_keys);
@@ -153,7 +153,7 @@ impl RealMessagesController<OsRng> {
let rng = OsRng;
// create channels for inter-task communication
let (real_message_sender, real_message_receiver) = tokio::sync::mpsc::channel(1);
let (real_message_sender, real_message_receiver) = tokio::sync::mpsc::channel(8);
let (sent_notifier_tx, sent_notifier_rx) = mpsc::unbounded();
let (ack_action_tx, ack_action_rx) = mpsc::unbounded();
let ack_controller_connectors = AcknowledgementControllerConnectors::new(
@@ -517,17 +517,25 @@ where
use crate::error::ClientCoreStatusMessage;
let packets = self.transmission_buffer.total_size();
let backlog = self.transmission_buffer.total_size_in_bytes() as f64 / 1024.0;
let lanes = self.transmission_buffer.num_lanes();
let lanes = self.transmission_buffer.lanes();
let mult = self.sending_delay_controller.current_multiplier();
let delay = self.current_average_message_sending_delay().as_millis();
let lane_status = lanes
.iter()
.map(|lane_name| {
let lane_length = self.transmission_buffer.lane_length(lane_name).unwrap_or(0);
format!("{lane_name:?}: {lane_length}")
})
.collect::<Vec<String>>()
.join(", ");
let status_str = if self.config.traffic.disable_main_poisson_packet_distribution {
format!("Packet backlog: {backlog:.2} kiB ({packets}), {lanes} lanes, no delay")
format!("Packet backlog: {lane_status}, no delay")
} else {
format!(
"Packet backlog: {backlog:.2} kiB ({packets}), {lanes} lanes, avg delay: {delay}ms ({mult})"
)
format!("Packet backlog: {lane_status}, avg delay: {delay}ms ({mult})")
};
if packets > 1000 {
log::warn!("{status_str}");
} else if packets > 0 {
@@ -23,6 +23,10 @@ use nym_statistics_common::clients::{packet_statistics::PacketStatisticsEvent, C
use nym_task::TaskClient;
use std::collections::HashSet;
use std::sync::Arc;
use std::time::{Duration, Instant};
// The interval at which we check for stale buffers
const STALE_BUFFER_CHECK_INTERVAL: Duration = Duration::from_secs(10);
// Buffer Requests to say "hey, send any reconstructed messages to this channel"
// or to say "hey, I'm going offline, don't send anything more to me. Just buffer them instead"
@@ -48,6 +52,9 @@ struct ReceivedMessagesBufferInner<R: MessageReceiver> {
recently_reconstructed: HashSet<i32>,
stats_tx: ClientStatsSender,
// Periodically check for stale buffers to clean up
last_stale_check: Instant,
}
impl<R: MessageReceiver> ReceivedMessagesBufferInner<R> {
@@ -96,9 +103,10 @@ impl<R: MessageReceiver> ReceivedMessagesBufferInner<R> {
}
None
}
_ => unreachable!(
"no other error kind should have been returned here! If so, it's a bug!"
),
_ => {
error!("unexpected error occurred during message reconstruction: {err}");
None
}
},
Ok(reconstruction_result) => match reconstruction_result {
Some((reconstructed_message, used_sets)) => {
@@ -144,6 +152,16 @@ impl<R: MessageReceiver> ReceivedMessagesBufferInner<R> {
self.recover_from_fragment(fragment_data, raw_fragment_size)
}
fn cleanup_stale_buffers(&mut self) {
let now = Instant::now();
if now - self.last_stale_check > STALE_BUFFER_CHECK_INTERVAL {
self.last_stale_check = now;
self.message_receiver
.reconstructor()
.cleanup_stale_buffers();
}
}
}
#[derive(Debug, Clone)]
@@ -172,6 +190,7 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
message_sender: None,
recently_reconstructed: HashSet::new(),
stats_tx,
last_stale_check: Instant::now(),
})),
reply_key_storage,
reply_controller_sender,
@@ -392,6 +411,11 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
}
}
// Cleanup stale buffers, if there are any fragments that simply never arrived.
// We do this here as part of handling new received fragments so that we can keep the event
// loop focused on processing new messages.
inner_guard.cleanup_stale_buffers();
drop(inner_guard);
if !completed_messages.is_empty() {
@@ -2,7 +2,9 @@
// SPDX-License-Identifier: Apache-2.0
use crate::client::real_messages_control::acknowledgement_control::PendingAcknowledgement;
use crate::client::real_messages_control::message_handler::{MessageHandler, PreparationError};
use crate::client::real_messages_control::message_handler::{
FragmentWithMaxRetransmissions, MessageHandler, PreparationError,
};
use crate::client::replies::reply_storage::CombinedReplyStorage;
use futures::channel::oneshot;
use futures::StreamExt;
@@ -10,7 +12,7 @@ use log::{debug, error, info, trace, warn};
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_sphinx::anonymous_replies::ReplySurb;
use nym_sphinx::chunking::fragment::{Fragment, FragmentIdentifier};
use nym_sphinx::chunking::fragment::FragmentIdentifier;
use nym_task::connections::{ConnectionId, TransmissionLane};
use nym_task::TaskClient;
use rand::{CryptoRng, Rng};
@@ -49,6 +51,8 @@ impl Config {
// - replies to "give additional surbs" requests
// - will reply to future heartbeats
pub type MaxRetransmissions = Option<u32>;
// TODO: this should be split into ingress and egress controllers
// because currently its trying to perform two distinct jobs
pub struct ReplyController<R> {
@@ -59,7 +63,8 @@ pub struct ReplyController<R> {
// of surbs required to send the message through
// expected_reliability: f32,
request_receiver: ReplyControllerReceiver,
pending_replies: HashMap<AnonymousSenderTag, TransmissionBuffer<Fragment>>,
pending_replies:
HashMap<AnonymousSenderTag, TransmissionBuffer<FragmentWithMaxRetransmissions>>,
/// Retransmission packets that have already timed out and are waiting for additional reply SURBs
/// so that they could be sent back to the network. Once we receive more SURBs, we should send them ASAP.
@@ -96,12 +101,13 @@ where
}
}
fn insert_pending_replies<I: IntoIterator<Item = Fragment>>(
fn insert_pending_replies<I: IntoIterator<Item = FragmentWithMaxRetransmissions>>(
&mut self,
recipient: &AnonymousSenderTag,
fragments: I,
lane: TransmissionLane,
) {
trace!("buffering pending replies for {recipient}");
self.pending_replies
.entry(*recipient)
.or_insert_with(TransmissionBuffer::new)
@@ -111,8 +117,9 @@ where
fn re_insert_pending_replies(
&mut self,
recipient: &AnonymousSenderTag,
fragments: Vec<(TransmissionLane, Fragment)>,
fragments: Vec<(TransmissionLane, FragmentWithMaxRetransmissions)>,
) {
trace!("re-inserting pending replies for {recipient}");
// the buffer should ALWAYS exist at this point, if it doesn't, it's a bug...
self.pending_replies
.entry(*recipient)
@@ -125,6 +132,7 @@ where
recipient: &AnonymousSenderTag,
data: Vec<Arc<PendingAcknowledgement>>,
) {
trace!("re-inserting pending retransmissions for {recipient}");
// the underlying entry MUST exist as we've just got data from there
let map_entry = self
.pending_retransmissions
@@ -142,7 +150,7 @@ where
}
fn should_request_more_surbs(&self, target: &AnonymousSenderTag) -> bool {
trace!("checking if we should request more surbs from {:?}", target);
trace!("checking if we should request more surbs from {target}");
let pending_queue_size = self
.pending_replies
@@ -158,11 +166,6 @@ where
let total_queue = pending_queue_size + retransmission_queue;
// simple as that - there's absolutely nothing to retransmit
if total_queue == 0 {
return false;
}
let available_surbs = self
.full_reply_storage
.surbs_storage_ref()
@@ -179,11 +182,27 @@ where
.full_reply_storage
.surbs_storage_ref()
.max_surb_threshold();
let min_surbs_threshold_buffer =
self.config.reply_surbs.minimum_reply_surb_threshold_buffer;
debug!("total queue size: {total_queue} = pending data {pending_queue_size} + pending retransmission {retransmission_queue}, available surbs: {available_surbs} pending surbs: {pending_surbs} threshold range: {min_surbs_threshold}..{max_surbs_threshold}");
// After clearing the queue, we want to have at least `min_surbs_threshold` surbs available
// and reserved for requesting additional surbs, and in addition to that we also want to
// have `min_surbs_threshold_buffer` surbs available proactively.
let target_surbs_after_clearing_queue = min_surbs_threshold + min_surbs_threshold_buffer;
(pending_surbs + available_surbs) < max_surbs_threshold
&& (pending_surbs + available_surbs) < (total_queue + min_surbs_threshold)
// Check if we have enough surbs to handle the total queue and maintain minimum thresholds
let total_required_surbs = total_queue + target_surbs_after_clearing_queue;
let total_available_surbs = pending_surbs + available_surbs;
debug!("total queue size: {total_queue} = pending data {pending_queue_size} + pending retransmission {retransmission_queue}, available surbs: {available_surbs} pending surbs: {pending_surbs} threshold range: {min_surbs_threshold}..+{min_surbs_threshold_buffer}..{max_surbs_threshold}");
// We should request more surbs if:
// 1. We haven't hit the maximum surb threshold, and
// 2. We don't have enough surbs to handle the queue plus minimum thresholds
let is_below_max_threshold = total_available_surbs < max_surbs_threshold;
let is_below_required_surbs = total_available_surbs < total_required_surbs;
is_below_max_threshold && is_below_required_surbs
}
async fn handle_send_reply(
@@ -191,6 +210,7 @@ where
recipient_tag: AnonymousSenderTag,
data: Vec<u8>,
lane: TransmissionLane,
max_retransmissions: Option<u32>,
) {
if !self
.full_reply_storage
@@ -228,7 +248,14 @@ where
.get_reply_surbs(&recipient_tag, max_to_send);
if let Some(reply_surbs) = surbs {
let to_send = fragments.drain(..max_to_send).collect::<Vec<_>>();
let to_send = fragments
.drain(..max_to_send)
.map(|f| FragmentWithMaxRetransmissions {
fragment: f,
max_retransmissions,
})
.collect::<Vec<_>>();
if let Err(err) = self
.message_handler
.try_send_reply_chunks_on_lane(
@@ -244,6 +271,10 @@ where
&recipient_tag,
);
warn!("failed to send reply to {recipient_tag}: {err}");
info!(
"buffering {no_fragments} fragments for {recipient_tag}",
no_fragments = to_send.len()
);
self.insert_pending_replies(&recipient_tag, to_send, lane);
}
}
@@ -251,6 +282,20 @@ where
// if there's leftover data we didn't send because we didn't have enough (or any) surbs - buffer it
if !fragments.is_empty() {
// Ideally we should have enough surbs above the minimum threshold to handle sending
// new replies without having to first request more surbs. That's why I'd like to log
// these cases as they might indicate a problem with the surb management.
debug!(
"buffering {no_fragments} fragments for {recipient_tag}",
no_fragments = fragments.len()
);
let fragments: Vec<_> = fragments
.into_iter()
.map(|fragment| FragmentWithMaxRetransmissions {
fragment,
max_retransmissions,
})
.collect();
self.insert_pending_replies(&recipient_tag, fragments, lane);
}
@@ -265,6 +310,7 @@ where
target: AnonymousSenderTag,
amount: u32,
) -> Result<(), PreparationError> {
debug!("requesting {amount} additional reply surbs for {target}");
let reply_surb = self
.full_reply_storage
.surbs_storage_ref()
@@ -383,7 +429,7 @@ where
&mut self,
from: &AnonymousSenderTag,
amount: usize,
) -> Option<Vec<(TransmissionLane, Fragment)>> {
) -> Option<Vec<(TransmissionLane, FragmentWithMaxRetransmissions)>> {
// if possible, pop all pending replies, if not, pop only entries for which we'd have a reply surb
let total = self.pending_replies.get(from)?.total_size();
trace!("pending queue has {total} elements");
@@ -663,7 +709,11 @@ where
recipient,
message,
lane,
} => self.handle_send_reply(recipient, message, lane).await,
max_retransmissions,
} => {
self.handle_send_reply(recipient, message, lane, max_retransmissions)
.await
}
ReplyControllerMessage::AdditionalSurbs {
sender_tag,
reply_surbs,
@@ -686,7 +736,7 @@ where
// it should take into consideration the average latency, sending rate and queue size.
// it should request as many surbs as it takes to saturate its sending rate before next batch arrives
async fn request_reply_surbs_for_queue_clearing(&mut self, target: AnonymousSenderTag) {
trace!("requesting surbs for queues clearing");
trace!("requesting surbs for queue clearing");
let pending_queue_size = self
.pending_replies
@@ -700,17 +750,18 @@ where
.map(|pending_queue| pending_queue.len())
.unwrap_or_default();
let min_surbs_buffer = self.config.reply_surbs.minimum_reply_surb_threshold_buffer as u32;
let total_queue = (pending_queue_size + retransmission_queue) as u32;
if total_queue == 0 {
trace!("the pending queues for {:?} are already empty", target);
return;
}
// To proactively request additional surbs, we aim to have a buffer of extra surbs in our
// storage.
let total_queue_with_buffer = total_queue + min_surbs_buffer;
let request_size = min(
self.config.reply_surbs.maximum_reply_surb_request_size,
max(
total_queue,
total_queue_with_buffer,
self.config.reply_surbs.minimum_reply_surb_request_size,
),
);
@@ -719,7 +770,7 @@ where
.request_additional_reply_surbs(target, request_size)
.await
{
warn!("failed to request additional surbs... - {err}")
info!("{err}")
}
}
@@ -66,12 +66,14 @@ impl ReplyControllerSender {
recipient: AnonymousSenderTag,
message: Vec<u8>,
lane: TransmissionLane,
max_retransmissions: Option<u32>,
) -> Result<(), ReplyControllerSenderError> {
self.0
.unbounded_send(ReplyControllerMessage::SendReply {
recipient,
message,
lane,
max_retransmissions,
})
.map_err(ReplyControllerSenderError::SendReply)
}
@@ -160,6 +162,7 @@ pub enum ReplyControllerMessage {
recipient: AnonymousSenderTag,
message: Vec<u8>,
lane: TransmissionLane,
max_retransmissions: Option<u32>,
},
AdditionalSurbs {
@@ -58,8 +58,8 @@ impl<T> TransmissionBuffer<T> {
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn num_lanes(&self) -> usize {
self.buffer.keys().count()
pub(crate) fn lanes(&self) -> Vec<TransmissionLane> {
self.buffer.keys().cloned().collect()
}
pub(crate) fn lane_length(&self, lane: &TransmissionLane) -> Option<usize> {
@@ -83,6 +83,7 @@ impl<T> TransmissionBuffer<T> {
}
#[cfg(not(target_arch = "wasm32"))]
#[allow(unused)]
pub(crate) fn total_size_in_bytes(&self) -> usize
where
T: SizedData,
@@ -10,7 +10,7 @@ use crate::{
CombinedReplyStorage, ReceivedReplySurbsMap, ReplyStorageBackend, SentReplyKeys, UsedSenderTags,
};
use async_trait::async_trait;
use log::{error, info, warn};
use log::{debug, error, info, warn};
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use std::fs;
use std::path::{Path, PathBuf};
@@ -52,7 +52,10 @@ impl Backend {
Ok(backend)
}
pub async fn try_load<P: AsRef<Path>>(database_path: P) -> Result<Self, StorageError> {
pub async fn try_load<P: AsRef<Path>>(
database_path: P,
fresh_sender_tags: bool,
) -> Result<Self, StorageError> {
let owned_path: PathBuf = database_path.as_ref().into();
if owned_path.file_name().is_none() {
return Err(StorageError::DatabasePathWithoutFilename {
@@ -118,6 +121,9 @@ impl Backend {
if days > 2 {
info!("it's been over {days} days and {hours} hours since we last used our data store. our used sender tags are already outdated - we're going to purge them now.");
manager.delete_all_tags().await?;
} else if fresh_sender_tags {
debug!("starting with fresh sender tags");
manager.delete_all_tags().await?;
}
Ok(Backend {
@@ -20,8 +20,8 @@ use nym_credentials_interface::TicketType;
use nym_crypto::asymmetric::identity;
use nym_gateway_requests::registration::handshake::client_handshake;
use nym_gateway_requests::{
BinaryRequest, ClientControlRequest, ClientRequest, SensitiveServerResponse, ServerResponse,
SharedGatewayKey, SharedSymmetricKey, AES_GCM_SIV_PROTOCOL_VERSION,
BinaryRequest, ClientControlRequest, ClientRequest, GatewayProtocolVersionExt,
SensitiveServerResponse, ServerResponse, SharedGatewayKey, SharedSymmetricKey,
CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION, CURRENT_PROTOCOL_VERSION,
};
use nym_sphinx::forwarding::packet::MixPacket;
@@ -204,15 +204,15 @@ impl<C, St> GatewayClient<C, St> {
"Attemting to establish connection to gateway at: {}",
self.gateway_address
);
let (ws_stream, _) = connect_async(&self.gateway_address).await?;
let (ws_stream, _) = connect_async(
&self.gateway_address,
#[cfg(unix)]
self.connection_fd_callback.clone(),
)
.await?;
self.connection = SocketState::Available(Box::new(ws_stream));
#[cfg(unix)]
if let (Some(callback), Some(fd)) = (self.connection_fd_callback.as_ref(), self.ws_fd()) {
callback.as_ref()(fd);
}
Ok(())
}
@@ -563,28 +563,10 @@ impl<C, St> GatewayClient<C, St> {
Ok(zeroizing_updated_key)
}
async fn authenticate(&mut self) -> Result<(), GatewayClientError> {
let Some(shared_key) = self.shared_key.as_ref() else {
return Err(GatewayClientError::NoSharedKeyAvailable);
};
if !self.connection.is_established() {
return Err(GatewayClientError::ConnectionNotEstablished);
}
debug!("authenticating with gateway");
let self_address = self
.local_identity
.as_ref()
.public_key()
.derive_destination_address();
let msg = ClientControlRequest::new_authenticate(
self_address,
shared_key,
self.cfg.bandwidth.require_tickets,
)?;
async fn send_authenticate_request_and_handle_response(
&mut self,
msg: ClientControlRequest,
) -> Result<(), GatewayClientError> {
match self.send_websocket_message(msg).await? {
ServerResponse::Authenticate {
protocol_version,
@@ -608,6 +590,51 @@ impl<C, St> GatewayClient<C, St> {
}
}
async fn authenticate_v1(&mut self) -> Result<(), GatewayClientError> {
debug!("using v1 authentication");
let Some(shared_key) = self.shared_key.as_ref() else {
return Err(GatewayClientError::NoSharedKeyAvailable);
};
let self_address = self
.local_identity
.public_key()
.derive_destination_address();
let msg = ClientControlRequest::new_authenticate(
self_address,
shared_key,
self.cfg.bandwidth.require_tickets,
)?;
self.send_authenticate_request_and_handle_response(msg)
.await
}
async fn authenticate_v2(&mut self) -> Result<(), GatewayClientError> {
debug!("using v2 authentication");
let Some(shared_key) = self.shared_key.as_ref() else {
return Err(GatewayClientError::NoSharedKeyAvailable);
};
let msg = ClientControlRequest::new_authenticate_v2(shared_key, &self.local_identity)?;
self.send_authenticate_request_and_handle_response(msg)
.await
}
async fn authenticate(&mut self, use_v2: bool) -> Result<(), GatewayClientError> {
if !self.connection.is_established() {
return Err(GatewayClientError::ConnectionNotEstablished);
}
debug!("authenticating with gateway");
if use_v2 {
self.authenticate_v2().await
} else {
self.authenticate_v1().await
}
}
/// Helper method to either call register or authenticate based on self.shared_key value
#[instrument(skip_all,
fields(
@@ -623,19 +650,25 @@ impl<C, St> GatewayClient<C, St> {
}
// 1. check gateway's protocol version
let supports_aes_gcm_siv = match self.get_gateway_protocol().await {
Ok(protocol) => protocol >= AES_GCM_SIV_PROTOCOL_VERSION,
let gw_protocol = match self.get_gateway_protocol().await {
Ok(protocol) => Some(protocol),
Err(_) => {
// if we failed to send the request, it means the gateway is running the old binary,
// so it has reset our connection - we have to reconnect
self.establish_connection().await?;
false
None
}
};
let supports_aes_gcm_siv = gw_protocol.supports_aes256_gcm_siv();
let supports_auth_v2 = gw_protocol.supports_authenticate_v2();
if !supports_aes_gcm_siv {
warn!("this gateway is on an old version that doesn't support AES256-GCM-SIV");
}
if !supports_auth_v2 {
warn!("this gateway is on an old version that doesn't support authentication v2")
}
if self.authenticated {
debug!("Already authenticated");
@@ -650,7 +683,7 @@ impl<C, St> GatewayClient<C, St> {
}
if self.shared_key.is_some() {
self.authenticate().await?;
self.authenticate(supports_auth_v2).await?;
if self.authenticated {
// if we are authenticated it means we MUST have an associated shared_key
@@ -983,7 +1016,8 @@ impl<C, St> GatewayClient<C, St> {
}
// if we're reconnecting, because we lost connection, we need to re-authenticate the connection
self.authenticate().await?;
self.authenticate(self.negotiated_protocol.supports_authenticate_v2())
.await?;
// this call is NON-blocking
self.start_listening_for_mixnet_messages()?;
@@ -1,6 +1,11 @@
use crate::error::GatewayClientError;
use nym_http_api_client::HickoryDnsResolver;
#[cfg(unix)]
use std::{
os::fd::{AsRawFd, RawFd},
sync::Arc,
};
use tokio::net::TcpStream;
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
use tungstenite::handshake::client::Response;
@@ -11,7 +16,10 @@ use std::net::SocketAddr;
#[cfg(not(target_arch = "wasm32"))]
pub(crate) async fn connect_async(
endpoint: &str,
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
) -> Result<(WebSocketStream<MaybeTlsStream<TcpStream>>, Response), GatewayClientError> {
use tokio::net::TcpSocket;
let resolver = HickoryDnsResolver::default();
let uri =
Url::parse(endpoint).map_err(|_| GatewayClientError::InvalidUrl(endpoint.to_owned()))?;
@@ -37,14 +45,41 @@ pub(crate) async fn connect_async(
}
};
let stream = TcpStream::connect(&sock_addrs[..]).await.map_err(|error| {
GatewayClientError::NetworkConnectionFailed {
address: endpoint.to_owned(),
source: error.into(),
let mut stream = Err(GatewayClientError::NoEndpointForConnection {
address: endpoint.to_owned(),
});
for sock_addr in sock_addrs {
let socket = if sock_addr.is_ipv4() {
TcpSocket::new_v4()
} else {
TcpSocket::new_v6()
}
})?;
.map_err(|err| GatewayClientError::NetworkConnectionFailed {
address: endpoint.to_owned(),
source: err.into(),
})?;
tokio_tungstenite::client_async_tls(endpoint, stream)
#[cfg(unix)]
if let Some(callback) = connection_fd_callback.as_ref() {
callback.as_ref()(socket.as_raw_fd());
}
match socket.connect(sock_addr).await {
Ok(s) => {
stream = Ok(s);
break;
}
Err(err) => {
stream = Err(GatewayClientError::NetworkConnectionFailed {
address: endpoint.to_owned(),
source: err.into(),
});
continue;
}
}
}
tokio_tungstenite::client_async_tls(endpoint, stream?)
.await
.map_err(|error| GatewayClientError::NetworkConnectionFailed {
address: endpoint.to_owned(),
@@ -43,6 +43,9 @@ pub enum GatewayClientError {
#[error("connection failed: {address}: {source}")]
NetworkConnectionFailed { address: String, source: WsError },
#[error("no socket address for endpoint: {address}")]
NoEndpointForConnection { address: String },
#[error("Invalid URL: {0}")]
InvalidUrl(String),
@@ -56,7 +56,7 @@ cw4 = { workspace = true }
cw-controllers = { workspace = true }
prost = { workspace = true, default-features = false }
flate2 = { workspace = true }
sha2 = { version = "0.9.5" }
sha2 = { workspace = true }
itertools = { workspace = true }
zeroize = { workspace = true, features = ["zeroize_derive"] }
cosmwasm-std = { workspace = true }
@@ -23,11 +23,12 @@ use nym_api_requests::models::{
NymNodeDescription, RewardEstimationResponse, StakeSaturationResponse,
};
use nym_api_requests::models::{LegacyDescribedGateway, MixNodeBondAnnotated};
use nym_api_requests::nym_nodes::SkimmedNode;
use nym_api_requests::nym_nodes::{NodesByAddressesResponse, SkimmedNode};
use nym_coconut_dkg_common::types::EpochId;
use nym_ecash_contract_common::deposit::DepositId;
use nym_http_api_client::UserAgent;
use nym_network_defaults::NymNetworkDetails;
use std::net::IpAddr;
use time::Date;
use url::Url;
@@ -710,4 +711,11 @@ impl NymApiClient {
.issued_ticketbooks_challenge(expiration_date, deposits)
.await?)
}
pub async fn nodes_by_addresses(
&self,
addresses: Vec<IpAddr>,
) -> Result<NodesByAddressesResponse, ValidatorClientError> {
Ok(self.nym_api.nodes_by_addresses(addresses).await?)
}
}
@@ -83,6 +83,12 @@ impl TryFrom<ContractVKShare> for EcashApiClient {
let url_address = Url::parse(&share.announce_address)?;
// The NymApiClient constructed here uses the default (hickory DoT/DoH) resolver because
// this EcashApiClient is used by both client and non-client applications.
//
// In non-client applications this resolver can cause warning logs about H2 connection
// failure. This indicates that the long lived https connection was closed by the remote
// peer and the resolver will have to reconnect. It should not impact actual functionality
Ok(EcashApiClient {
api_client: NymApiClient::new(url_address),
verification_key: VerificationKeyAuth::try_from_bs58(&share.share)?,
@@ -12,10 +12,13 @@ use nym_api_requests::ecash::models::{
};
use nym_api_requests::ecash::VerificationKeyResponse;
use nym_api_requests::models::{
AnnotationResponse, ApiHealthResponse, LegacyDescribedMixNode, NodePerformanceResponse,
NodeRefreshBody, NymNodeDescription, PerformanceHistoryResponse, RewardedSetResponse,
AnnotationResponse, ApiHealthResponse, BinaryBuildInformationOwned, ChainStatusResponse,
LegacyDescribedMixNode, NodePerformanceResponse, NodeRefreshBody, NymNodeDescription,
PerformanceHistoryResponse, RewardedSetResponse,
};
use nym_api_requests::nym_nodes::{
NodesByAddressesRequestBody, NodesByAddressesResponse, PaginatedCachedNodesResponse,
};
use nym_api_requests::nym_nodes::PaginatedCachedNodesResponse;
use nym_api_requests::pagination::PaginatedResponse;
pub use nym_api_requests::{
ecash::{
@@ -40,6 +43,7 @@ pub use nym_http_api_client::Client;
use nym_http_api_client::{ApiClient, NO_PARAMS};
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, NodeId, NymNodeDetails};
use std::net::IpAddr;
use time::format_description::BorrowedFormatItem;
use time::Date;
use tracing::instrument;
@@ -66,6 +70,19 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[instrument(level = "debug", skip(self))]
async fn build_information(&self) -> Result<BinaryBuildInformationOwned, NymAPIError> {
self.get_json(
&[
routes::API_VERSION,
routes::API_STATUS_ROUTES,
routes::BUILD_INFORMATION,
],
NO_PARAMS,
)
.await
}
#[deprecated]
#[instrument(level = "debug", skip(self))]
async fn get_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
@@ -1015,6 +1032,23 @@ pub trait NymApiClientExt: ApiClient {
.await
}
async fn nodes_by_addresses(
&self,
addresses: Vec<IpAddr>,
) -> Result<NodesByAddressesResponse, NymAPIError> {
self.post_json(
&[
routes::API_VERSION,
"unstable",
routes::NYM_NODES_ROUTES,
routes::nym_nodes::BY_ADDRESSES,
],
NO_PARAMS,
&NodesByAddressesRequestBody { addresses },
)
.await
}
#[instrument(level = "debug", skip(self))]
async fn get_network_details(&self) -> Result<NymNetworkDetailsResponse, NymAPIError> {
self.get_json(
@@ -1023,6 +1057,15 @@ pub trait NymApiClientExt: ApiClient {
)
.await
}
#[instrument(level = "debug", skip(self))]
async fn get_chain_status(&self) -> Result<ChainStatusResponse, NymAPIError> {
self.get_json(
&[routes::API_VERSION, routes::NETWORK, routes::CHAIN_STATUS],
NO_PARAMS,
)
.await
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
@@ -43,11 +43,14 @@ pub mod nym_nodes {
pub const NYM_NODES_BONDED: &str = "bonded";
pub const NYM_NODES_REWARDED_SET: &str = "rewarded-set";
pub const NYM_NODES_REFRESH_DESCRIBED: &str = "refresh-described";
pub const BY_ADDRESSES: &str = "by-addresses";
}
pub const STATUS_ROUTES: &str = "status";
pub const API_STATUS_ROUTES: &str = "api-status";
pub const HEALTH: &str = "health";
pub const BUILD_INFORMATION: &str = "build-information";
pub const MIXNODE: &str = "mixnode";
pub const GATEWAY: &str = "gateway";
pub const NYM_NODES: &str = "nym-nodes";
@@ -69,4 +72,5 @@ pub const SUBMIT_NODE: &str = "submit-node-monitoring-results";
pub const SERVICE_PROVIDERS: &str = "services";
pub const DETAILS: &str = "details";
pub const CHAIN_STATUS: &str = "chain-status";
pub const NETWORK: &str = "network";
@@ -28,7 +28,6 @@ use nym_network_defaults::{ChainDetails, NymNetworkDetails};
use serde::{de::DeserializeOwned, Serialize};
use std::fmt::Debug;
use std::time::SystemTime;
use tendermint_rpc::endpoint::block::Response as BlockResponse;
use tendermint_rpc::endpoint::*;
use tendermint_rpc::{Error as TendermintRpcError, Order};
use url::Url;
@@ -63,6 +62,8 @@ pub use cw3;
pub use cw4;
pub use cw_controllers;
pub use fee::{gas_price::GasPrice, GasAdjustable, GasAdjustment};
pub use prost::Name;
pub use tendermint_rpc::endpoint::block::Response as BlockResponse;
pub use tendermint_rpc::{
endpoint::{tx::Response as TxResponse, validators::Response as ValidatorResponse},
query::Query,
-9
View File
@@ -25,15 +25,6 @@ pub fn in6addr_any_init() -> IpAddr {
IpAddr::V6(Ipv6Addr::UNSPECIFIED)
}
/// Helper for providing binding warnings if node tries to bind to any of those
pub const SPECIAL_ADDRESSES: &[IpAddr] = &[
IpAddr::V4(Ipv4Addr::LOCALHOST),
IpAddr::V4(Ipv4Addr::UNSPECIFIED),
IpAddr::V4(Ipv4Addr::BROADCAST),
IpAddr::V6(Ipv6Addr::LOCALHOST),
IpAddr::V6(Ipv6Addr::UNSPECIFIED),
];
// TODO: is it really part of 'Config'?
pub trait OptionalSet {
/// If the value is available (i.e. `Some`), the provided closure is applied.
+4 -1
View File
@@ -43,4 +43,7 @@ serde = ["dep:serde", "serde_bytes", "ed25519-dalek/serde", "x25519-dalek/serde"
asymmetric = ["x25519-dalek", "ed25519-dalek", "zeroize"]
hashing = ["blake3", "digest", "hkdf", "hmac", "generic-array", "sha2"]
stream_cipher = ["aes", "ctr", "cipher", "generic-array"]
sphinx = ["nym-sphinx-types/sphinx"]
sphinx = ["nym-sphinx-types/sphinx"]
[lints]
workspace = true
+10 -4
View File
@@ -16,8 +16,11 @@ pub fn compute_keyed_hmac<D>(key: &[u8], data: &[u8]) -> HmacOutput<D>
where
D: Digest + BlockSizeUser,
{
let mut hmac = SimpleHmac::<D>::new_from_slice(key)
.expect("HMAC was instantiated with a key of an invalid size!");
// SAFETY: hmac is fine with keys of any size; if they're smaller than the block size of the underlying
// digest, they're padded with 0. if they're larger they're hashed and padded
// the reason for `Result` return type is due to the trait definition
#[allow(clippy::unwrap_used)]
let mut hmac = SimpleHmac::<D>::new_from_slice(key).unwrap();
hmac.update(data);
hmac.finalize()
}
@@ -27,8 +30,11 @@ pub fn recompute_keyed_hmac_and_verify_tag<D>(key: &[u8], data: &[u8], tag: &[u8
where
D: Digest + BlockSizeUser,
{
let mut hmac = SimpleHmac::<D>::new_from_slice(key)
.expect("HMAC was instantiated with a key of an invalid size!");
// SAFETY: hmac is fine with keys of any size; if they're smaller than the block size of the underlying
// digest, they're padded with 0. if they're larger they're hashed and padded
// the reason for `Result` return type is due to the trait definition
#[allow(clippy::unwrap_used)]
let mut hmac = SimpleHmac::<D>::new_from_slice(key).unwrap();
hmac.update(data);
let tag_arr = Output::<D>::from_slice(tag);
+14 -5
View File
@@ -27,12 +27,16 @@ where
// after performing diffie-hellman we don't care about the private component anymore
let dh_result = ephemeral_keypair.private_key().diffie_hellman(remote_key);
// there is no reason for this to fail as our okm is expected to be only C::KeySize bytes
// SAFETY: while this is a relatively weak assumption, it's unlikely that any stream cipher has `C::key_size()`
// larger than 255 * chunk_size of the digest (so for example keys larger than 8160 bytes if sh256 is used)
#[allow(clippy::expect_used)]
let okm = hkdf::extract_then_expand::<D>(None, &dh_result, None, C::key_size())
.expect("somehow too long okm was provided");
let derived_shared_key =
Key::<C>::from_exact_iter(okm).expect("okm was expanded to incorrect length!");
// SAFETY: the generated okm has exactly `C::key_size()` elements,
// so this call is safe
#[allow(clippy::unwrap_used)]
let derived_shared_key = Key::<C>::from_exact_iter(okm).unwrap();
(ephemeral_keypair, derived_shared_key)
}
@@ -48,9 +52,14 @@ where
{
let dh_result = local_key.diffie_hellman(remote_key);
// there is no reason for this to fail as our okm is expected to be only C::KeySize bytes
// SAFETY: while this is a relatively weak assumption, it's unlikely that any stream cipher has `C::key_size()`
// larger than 255 * chunk_size of the digest (so for example keys larger than 8160 bytes if sh256 is used)
#[allow(clippy::expect_used)]
let okm = hkdf::extract_then_expand::<D>(None, &dh_result, None, C::key_size())
.expect("somehow too long okm was provided");
Key::<C>::from_exact_iter(okm).expect("okm was expanded to incorrect length!")
// SAFETY: the generated okm has exactly `C::key_size()` elements,
// so this call is safe
#[allow(clippy::unwrap_used)]
Key::<C>::from_exact_iter(okm).unwrap()
}
+4 -9
View File
@@ -60,20 +60,15 @@ where
Iv::<C>::default()
}
pub fn iv_from_slice<C>(b: &[u8]) -> &IV<C>
pub fn try_iv_from_slice<C>(b: &[u8]) -> Option<&IV<C>>
where
C: IvSizeUser,
{
if b.len() != C::iv_size() {
// `from_slice` would have caused a panic about this issue anyway.
// Now we at least have slightly more information
panic!(
"Tried to convert {} bytes to IV. Expected {}",
b.len(),
C::iv_size()
)
None
} else {
Some(IV::<C>::from_slice(b))
}
IV::<C>::from_slice(b)
}
// TODO: there's really no way to use more parts of the keystream if it was required at some point.
+1 -1
View File
@@ -21,7 +21,7 @@ lazy_static = { workspace = true }
rand = { workspace = true }
rand_chacha = { workspace = true }
rand_core = { workspace = true }
sha2 = "0.9"
sha2 = { workspace = true }
serde = { workspace = true }
serde_derive = { workspace = true }
thiserror = { workspace = true }
+96 -2
View File
@@ -54,12 +54,12 @@ pub(crate) fn hash_to_scalar<M: AsRef<[u8]>>(msg: M, domain: &[u8]) -> Scalar {
pub(crate) fn hash_to_scalars<M: AsRef<[u8]>>(msg: M, domain: &[u8], n: usize) -> Vec<Scalar> {
let mut output = vec![Scalar::zero(); n];
Scalar::hash_to_field::<ExpandMsgXmd<Sha256>>(msg.as_ref(), domain, &mut output);
Scalar::hash_to_field::<ExpandMsgXmd<Sha256>, _>([msg], domain, &mut output);
output
}
pub(crate) fn hash_g2<M: AsRef<[u8]>>(msg: M, domain: &[u8]) -> G2Projective {
<G2Projective as HashToCurve<ExpandMsgXmd<Sha256>>>::hash_to_curve(msg, domain)
<G2Projective as HashToCurve<ExpandMsgXmd<Sha256>>>::hash_to_curve([msg], domain)
}
pub(crate) fn combine_scalar_chunks(chunks: &[Scalar]) -> Scalar {
@@ -112,3 +112,97 @@ pub(crate) fn deserialize_g2(b: &[u8]) -> Option<G2Projective> {
G2Projective::from_bytes(&encoding).into()
}
}
#[cfg(test)]
mod tests {
use super::*;
use bls12_381::G2Affine;
#[test]
fn test_hash_to_scalar() {
let msg1 = "foo";
let expected1 = Scalar::from_bytes(&[
253, 57, 224, 227, 175, 195, 226, 82, 46, 175, 33, 126, 171, 239, 255, 92, 108, 168, 6,
79, 90, 11, 235, 236, 221, 10, 85, 133, 42, 81, 95, 30,
])
.unwrap();
let msg2 = "bar";
let expected2 = Scalar::from_bytes(&[
48, 83, 69, 52, 42, 18, 135, 244, 211, 190, 160, 196, 118, 154, 24, 126, 0, 125, 72,
201, 170, 225, 123, 201, 52, 120, 171, 132, 235, 182, 20, 26,
])
.unwrap();
let msg3 = [
33, 135, 76, 234, 71, 35, 247, 216, 39, 242, 42, 88, 152, 29, 74, 135, 9, 29, 216, 123,
250, 87, 108, 29, 245, 126, 109, 102, 84, 71, 158, 224, 145, 243, 49, 121, 244, 27,
115, 121, 25, 66, 216, 67, 97, 101, 140, 160, 77, 239, 114, 215, 152, 48, 15, 231, 101,
60, 42, 92, 128, 131, 161, 43,
];
let expected3 = Scalar::from_bytes(&[
128, 189, 8, 43, 186, 55, 52, 61, 171, 196, 159, 177, 162, 100, 27, 143, 85, 83, 218,
171, 91, 220, 155, 25, 7, 38, 2, 36, 4, 93, 136, 4,
])
.unwrap();
assert_eq!(
hash_to_scalar(msg1, b"NYMECASH-V01-CS02-with-expander-SHA256"),
expected1
);
assert_eq!(
hash_to_scalar(msg2, b"NYMECASH-V01-CS02-with-expander-SHA256"),
expected2
);
assert_eq!(
hash_to_scalar(msg3, b"NYMECASH-V01-CS02-with-expander-SHA256"),
expected3
);
}
#[test]
fn test_hash_g2() {
let msg1 = "foo";
let expected1 = G2Affine::from_compressed(&[
175, 187, 62, 7, 29, 17, 42, 93, 28, 93, 234, 253, 101, 166, 158, 187, 153, 82, 93, 18,
11, 233, 36, 107, 51, 117, 30, 127, 32, 254, 210, 77, 133, 12, 253, 255, 84, 128, 36,
214, 234, 103, 50, 21, 26, 78, 112, 49, 20, 69, 19, 109, 7, 78, 33, 227, 196, 180, 168,
219, 73, 251, 192, 221, 41, 138, 160, 131, 191, 186, 156, 117, 179, 179, 191, 235, 171,
26, 219, 148, 170, 179, 11, 38, 137, 14, 95, 115, 171, 186, 163, 82, 158, 6, 239, 88,
])
.unwrap()
.into();
let msg2 = "bar";
let expected2 = G2Affine::from_compressed(&[
183, 25, 90, 187, 34, 184, 30, 182, 215, 242, 158, 83, 116, 34, 210, 96, 188, 79, 83,
255, 100, 122, 90, 188, 196, 93, 164, 253, 20, 106, 205, 33, 48, 140, 60, 149, 66, 246,
121, 244, 146, 66, 170, 60, 113, 95, 102, 237, 25, 231, 8, 42, 121, 124, 180, 140, 34,
104, 173, 251, 89, 189, 28, 196, 49, 66, 101, 38, 68, 44, 40, 235, 21, 35, 204, 123,
218, 238, 216, 92, 134, 217, 212, 246, 176, 77, 187, 0, 245, 134, 132, 73, 31, 44, 137,
197,
])
.unwrap()
.into();
let msg3 = [
33, 135, 76, 234, 71, 35, 247, 216, 39, 242, 42, 88, 152, 29, 74, 135, 9, 29, 216, 123,
250, 87, 108, 29, 245, 126, 109, 102, 84, 71, 158, 224, 145, 243, 49, 121, 244, 27,
115, 121, 25, 66, 216, 67, 97, 101, 140, 160, 77, 239, 114, 215, 152, 48, 15, 231, 101,
60, 42, 92, 128, 131, 161, 43,
];
let expected3 = G2Affine::from_compressed(&[
151, 185, 8, 123, 223, 150, 192, 192, 115, 10, 3, 129, 49, 179, 31, 108, 0, 17, 46,
231, 184, 164, 247, 228, 22, 142, 87, 70, 120, 111, 154, 15, 245, 110, 32, 84, 53, 117,
239, 93, 89, 119, 32, 17, 39, 250, 198, 137, 6, 95, 137, 202, 54, 244, 238, 190, 11,
217, 237, 95, 72, 59, 140, 56, 3, 42, 61, 195, 192, 101, 46, 204, 207, 75, 70, 176,
207, 48, 24, 195, 248, 234, 178, 168, 54, 109, 19, 189, 51, 52, 120, 69, 248, 226, 102,
91,
])
.unwrap()
.into();
assert_eq!(hash_g2(msg1, b"DUMMY_TEST_DOMAIN"), expected1);
assert_eq!(hash_g2(msg2, b"DUMMY_TEST_DOMAIN"), expected2);
assert_eq!(hash_g2(msg3, b"DUMMY_TEST_DOMAIN"), expected3);
}
}
+3
View File
@@ -20,11 +20,14 @@ serde_json = { workspace = true }
strum = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true, features = ["log"] }
time = { workspace = true }
subtle = { workspace = true }
zeroize = { workspace = true }
nym-crypto = { path = "../crypto", features = ["aead", "hashing"] }
nym-pemstore = { path = "../pemstore" }
nym-sphinx = { path = "../nymsphinx" }
nym-serde-helpers = { path = "../serde-helpers", features = ["base64"] }
nym-task = { path = "../task" }
nym-credentials = { path = "../credentials" }
@@ -15,6 +15,12 @@ use thiserror::Error;
// this is no longer constant size due to the differences in ciphertext between aes128ctr and aes256gcm-siv (inclusion of tag)
pub struct EncryptedAddressBytes(Vec<u8>);
impl From<Vec<u8>> for EncryptedAddressBytes {
fn from(encrypted_address: Vec<u8>) -> Self {
EncryptedAddressBytes(encrypted_address)
}
}
#[derive(Debug, Error)]
pub enum EncryptedAddressConversionError {
#[error("Failed to decode the encrypted address - {0}")]
+20 -1
View File
@@ -19,7 +19,7 @@ pub use shared_key::{
SharedGatewayKey, SharedKeyConversionError, SharedKeyUsageError, SharedSymmetricKey,
};
pub const CURRENT_PROTOCOL_VERSION: u8 = AES_GCM_SIV_PROTOCOL_VERSION;
pub const CURRENT_PROTOCOL_VERSION: u8 = AUTHENTICATE_V2_PROTOCOL_VERSION;
/// Defines the current version of the communication protocol between gateway and clients.
/// It has to be incremented for any breaking change.
@@ -27,10 +27,29 @@ pub const CURRENT_PROTOCOL_VERSION: u8 = AES_GCM_SIV_PROTOCOL_VERSION;
// 1 - initial release
// 2 - changes to client credentials structure
// 3 - change to AES-GCM-SIV and non-zero IVs
// 4 - introduction of v2 authentication protocol to prevent reply attacks
pub const INITIAL_PROTOCOL_VERSION: u8 = 1;
pub const CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION: u8 = 2;
pub const AES_GCM_SIV_PROTOCOL_VERSION: u8 = 3;
pub const AUTHENTICATE_V2_PROTOCOL_VERSION: u8 = 4;
// TODO: could using `Mac` trait here for OutputSize backfire?
// Should hmac itself be exposed, imported and used instead?
pub type LegacyGatewayMacSize = <GatewayIntegrityHmacAlgorithm as OutputSizeUser>::OutputSize;
pub trait GatewayProtocolVersionExt {
fn supports_aes256_gcm_siv(&self) -> bool;
fn supports_authenticate_v2(&self) -> bool;
}
impl GatewayProtocolVersionExt for Option<u8> {
fn supports_aes256_gcm_siv(&self) -> bool {
let Some(protocol) = *self else { return false };
protocol >= AES_GCM_SIV_PROTOCOL_VERSION
}
fn supports_authenticate_v2(&self) -> bool {
let Some(protocol) = *self else { return false };
protocol >= AUTHENTICATE_V2_PROTOCOL_VERSION
}
}
@@ -3,12 +3,14 @@
use crate::SharedKeyUsageError;
use nym_credentials_interface::CompactEcashError;
use nym_crypto::asymmetric::ed25519::SignatureError;
use nym_sphinx::addressing::nodes::NymNodeRoutingAddressError;
use nym_sphinx::forwarding::packet::MixPacketFormattingError;
use nym_sphinx::params::packet_sizes::PacketSize;
use serde::{Deserialize, Serialize};
use std::string::FromUtf8Error;
use thiserror::Error;
use time::OffsetDateTime;
// specific errors (that should not be nested!!) for clients to match on
#[derive(Debug, Copy, Clone, Error, Serialize, Deserialize)]
@@ -92,7 +94,34 @@ pub enum GatewayRequestsError {
#[error("the provided [v1] credential has invalid number of parameters - {0}")]
InvalidNumberOfEmbededParameters(u32),
#[error("failed to authenticate the client: {0}")]
Authentication(#[from] AuthenticationFailure),
// variant to catch legacy errors
#[error("{0}")]
Other(String),
}
#[derive(Debug, Error)]
pub enum AuthenticationFailure {
#[error(transparent)]
KeyUsageFailure(#[from] SharedKeyUsageError),
#[error("failed to verify provided address ciphertext")]
MalformedCiphertext,
#[error("failed to verify request signature")]
InvalidSignature(#[from] SignatureError),
#[error("the client is not registered")]
NotRegistered,
#[error("the provided request timestamp is excessively skewed. got {received} whilst the server time is {server}")]
ExcessiveTimestampSkew {
received: OffsetDateTime,
server: OffsetDateTime,
},
#[error("the provided request timestamp is smaller or equal to one previously used")]
RequestReuse,
}
@@ -0,0 +1,151 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::{AuthenticationFailure, GatewayRequestsError, SharedGatewayKey};
use nym_crypto::asymmetric::ed25519;
use serde::{Deserialize, Serialize};
use std::iter;
use std::time::Duration;
use subtle::ConstantTimeEq;
use time::OffsetDateTime;
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct AuthenticateRequest {
#[serde(flatten)]
pub content: AuthenticateRequestContent,
pub request_signature: ed25519::Signature,
}
impl AuthenticateRequest {
pub fn new(
protocol_version: u8,
shared_key: &SharedGatewayKey,
identity_keys: &ed25519::KeyPair,
) -> Result<AuthenticateRequest, GatewayRequestsError> {
let content = AuthenticateRequestContent::new(
protocol_version,
shared_key,
*identity_keys.public_key(),
)?;
let plaintext = content.plaintext();
let request_signature = identity_keys.private_key().sign(&plaintext);
Ok(AuthenticateRequest {
content,
request_signature,
})
}
pub fn verify_timestamp(
&self,
max_request_timestamp_skew: Duration,
) -> Result<(), AuthenticationFailure> {
let now = OffsetDateTime::now_utc();
if self.content.request_timestamp() < now - max_request_timestamp_skew {
return Err(AuthenticationFailure::ExcessiveTimestampSkew {
received: self.content.request_timestamp(),
server: now,
});
}
if self.content.request_timestamp() - max_request_timestamp_skew > now {
return Err(AuthenticationFailure::ExcessiveTimestampSkew {
received: self.content.request_timestamp(),
server: now,
});
}
Ok(())
}
pub fn ensure_timestamp_not_reused(
&self,
previous: OffsetDateTime,
) -> Result<(), AuthenticationFailure> {
if self.content.request_timestamp() <= previous {
return Err(AuthenticationFailure::RequestReuse);
}
Ok(())
}
pub fn verify_ciphertext(
&self,
shared_key: &SharedGatewayKey,
) -> Result<(), AuthenticationFailure> {
let expected = shared_key.encrypt(
self.content
.client_identity
.derive_destination_address()
.as_bytes_ref(),
Some(&self.content.nonce),
)?;
if !bool::from(expected.ct_eq(&self.content.address_ciphertext)) {
return Err(AuthenticationFailure::MalformedCiphertext);
}
Ok(())
}
pub fn verify_signature(&self) -> Result<(), AuthenticationFailure> {
let plaintext = self.content.plaintext();
self.content
.client_identity
.verify(plaintext, &self.request_signature)
.map_err(Into::into)
}
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct AuthenticateRequestContent {
pub protocol_version: u8,
// this is identical to the client's address
pub client_identity: ed25519::PublicKey,
#[serde(with = "nym_serde_helpers::base64")]
pub address_ciphertext: Vec<u8>,
#[serde(with = "nym_serde_helpers::base64")]
pub nonce: Vec<u8>,
pub request_unix_timestamp: u64,
}
impl AuthenticateRequestContent {
fn new(
protocol_version: u8,
shared_key: &SharedGatewayKey,
client_identity: ed25519::PublicKey,
) -> Result<AuthenticateRequestContent, GatewayRequestsError> {
let nonce = shared_key.random_nonce_or_iv();
let destination_address = client_identity.derive_destination_address();
let address_ciphertext =
shared_key.encrypt(destination_address.as_bytes_ref(), Some(&nonce))?;
let now = OffsetDateTime::now_utc();
Ok(AuthenticateRequestContent {
protocol_version,
client_identity,
address_ciphertext,
nonce,
request_unix_timestamp: now.unix_timestamp() as u64, // SAFETY: we're running this in post 1970...
})
}
}
impl AuthenticateRequestContent {
pub fn plaintext(&self) -> Vec<u8> {
iter::once(self.protocol_version)
.chain(self.client_identity.to_bytes())
.chain(self.address_ciphertext.iter().copied())
.chain(self.nonce.iter().copied())
.chain(self.request_unix_timestamp.to_be_bytes())
.collect()
}
pub fn request_timestamp(&self) -> OffsetDateTime {
OffsetDateTime::from_unix_timestamp(self.request_unix_timestamp as i64)
.unwrap_or(OffsetDateTime::UNIX_EPOCH)
}
}
@@ -2,16 +2,21 @@
// SPDX-License-Identifier: Apache-2.0
use crate::models::CredentialSpendingRequest;
use crate::text_request::authenticate::AuthenticateRequest;
use crate::{
GatewayRequestsError, SharedGatewayKey, SymmetricKey, AES_GCM_SIV_PROTOCOL_VERSION,
CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION, INITIAL_PROTOCOL_VERSION,
AUTHENTICATE_V2_PROTOCOL_VERSION, CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION,
INITIAL_PROTOCOL_VERSION,
};
use nym_credentials_interface::CredentialSpendingData;
use nym_crypto::asymmetric::ed25519;
use nym_sphinx::DestinationAddressBytes;
use serde::{Deserialize, Serialize};
use std::str::FromStr;
use tungstenite::Message;
pub mod authenticate;
// wrapper for all encrypted requests for ease of use
#[derive(Serialize, Deserialize, Debug, Clone)]
#[non_exhaustive]
@@ -68,6 +73,9 @@ pub enum ClientControlRequest {
enc_address: String,
iv: String,
},
AuthenticateV2(Box<AuthenticateRequest>),
#[serde(alias = "handshakePayload")]
RegisterHandshakeInitRequest {
#[serde(default)]
@@ -123,9 +131,22 @@ impl ClientControlRequest {
})
}
pub fn new_authenticate_v2(
shared_key: &SharedGatewayKey,
identity_keys: &ed25519::KeyPair,
) -> Result<Self, GatewayRequestsError> {
// if we're using v2 authentication, we must announce at least that protocol version
let protocol_version = AUTHENTICATE_V2_PROTOCOL_VERSION;
Ok(ClientControlRequest::AuthenticateV2(Box::new(
AuthenticateRequest::new(protocol_version, shared_key, identity_keys)?,
)))
}
pub fn name(&self) -> String {
match self {
ClientControlRequest::Authenticate { .. } => "Authenticate".to_string(),
ClientControlRequest::AuthenticateV2(..) => "AuthenticateV2".to_string(),
ClientControlRequest::RegisterHandshakeInitRequest { .. } => {
"RegisterHandshakeInitRequest".to_string()
}
@@ -0,0 +1,7 @@
/*
* Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
* SPDX-License-Identifier: GPL-3.0-only
*/
ALTER TABLE shared_keys
ADD COLUMN last_used_authentication TIMESTAMP WITHOUT TIME ZONE;
+14
View File
@@ -200,6 +200,20 @@ impl GatewayStorage {
Ok(())
}
pub async fn update_last_used_authentication_timestamp(
&self,
client_id: i64,
last_used_authentication_timestamp: OffsetDateTime,
) -> Result<(), GatewayStorageError> {
self.shared_key_manager
.update_last_used_authentication_timestamp(
client_id,
last_used_authentication_timestamp,
)
.await?;
Ok(())
}
pub async fn get_client(&self, client_id: i64) -> Result<Option<Client>, GatewayStorageError> {
let client = self.client_manager.get_client(client_id).await?;
Ok(client)
+1 -1
View File
@@ -14,13 +14,13 @@ pub struct Client {
#[derive(FromRow)]
pub struct PersistedSharedKeys {
#[allow(dead_code)]
pub client_id: i64,
#[allow(dead_code)]
pub client_address_bs58: String,
pub derived_aes128_ctr_blake3_hmac_keys_bs58: Option<String>,
pub derived_aes256_gcm_siv_key: Option<Vec<u8>>,
pub last_used_authentication: Option<OffsetDateTime>,
}
impl TryFrom<PersistedSharedKeys> for SharedGatewayKey {
+21 -7
View File
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::models::PersistedSharedKeys;
use time::OffsetDateTime;
#[derive(Clone)]
pub(crate) struct SharedKeysManager {
@@ -68,6 +69,22 @@ impl SharedKeysManager {
Ok(())
}
pub(crate) async fn update_last_used_authentication_timestamp(
&self,
client_id: i64,
last_used: OffsetDateTime,
) -> Result<(), sqlx::Error> {
sqlx::query!(
"UPDATE shared_keys SET last_used_authentication = ? WHERE client_id = ?;",
last_used,
client_id
)
.execute(&self.connection_pool)
.await?;
Ok(())
}
/// Tries to retrieve shared keys stored for the particular client.
///
/// # Arguments
@@ -77,13 +94,10 @@ impl SharedKeysManager {
&self,
client_address_bs58: &str,
) -> Result<Option<PersistedSharedKeys>, sqlx::Error> {
sqlx::query_as!(
PersistedSharedKeys,
"SELECT * FROM shared_keys WHERE client_address_bs58 = ?",
client_address_bs58
)
.fetch_optional(&self.connection_pool)
.await
sqlx::query_as("SELECT * FROM shared_keys WHERE client_address_bs58 = ?")
.bind(client_address_bs58)
.fetch_optional(&self.connection_pool)
.await
}
/// Removes from the database shared keys derived with the particular client.
+7 -1
View File
@@ -21,6 +21,12 @@ serde_json = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }
# used for decoding text responses (they were already implicitly included)
bytes = { workspace = true }
encoding_rs = { workspace = true }
mime = { workspace = true }
nym-bin-common = { path = "../bin-common" }
[target."cfg(not(target_arch = \"wasm32\"))".dependencies]
@@ -32,4 +38,4 @@ workspace = true
features = ["tokio"]
[dev-dependencies]
tokio = { workspace = true, features=["rt", "macros"] }
tokio = { workspace = true, features = ["rt", "macros"] }
+112 -11
View File
@@ -1,3 +1,6 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
//! DNS resolver configuration for internal lookups.
//!
//! The resolver itself is the set combination of the google, cloudflare, and quad9 endpoints
@@ -9,19 +12,35 @@
//!
//! Requires the `dns-over-https-rustls`, `webpki-roots` feature for the
//! `hickory-resolver` crate
//!
//!
//! Note: The hickory DoH resolver can cause warning logs about H2 connection failure. This
//! indicates that the long lived https connection was closed by the remote peer and the resolver
//! will have to reconnect. It should not impact actual functionality.
//!
//! code ref: https://github.com/hickory-dns/hickory-dns/blob/06a8b1ce9bd9322d8e6accf857d30257e1274427/crates/proto/src/h2/h2_client_stream.rs#L534
//!
//! example log:
//!
//! ```txt
//! WARN /home/ubuntu/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/hickory-proto-0.24.3/src/h2/h2_client_stream.rs:493: h2 connection failed: unexpected end of file
//! ```
#![deny(missing_docs)]
use crate::ClientBuilder;
use std::{net::SocketAddr, sync::Arc};
use std::{
net::SocketAddr,
sync::{Arc, LazyLock},
};
use hickory_resolver::lookup_ip::LookupIp;
use hickory_resolver::{
config::{LookupIpStrategy, NameServerConfigGroup, ResolverConfig, ResolverOpts},
error::ResolveError,
lookup_ip::LookupIpIntoIter,
TokioAsyncResolver,
};
use hickory_resolver::{error::ResolveErrorKind, lookup_ip::LookupIp};
use once_cell::sync::OnceCell;
use reqwest::dns::{Addrs, Name, Resolve, Resolving};
use tracing::warn;
@@ -30,6 +49,13 @@ impl ClientBuilder {
/// Override the DNS resolver implementation used by the underlying http client.
pub fn dns_resolver<R: Resolve + 'static>(mut self, resolver: Arc<R>) -> Self {
self.reqwest_client_builder = self.reqwest_client_builder.dns_resolver(resolver);
self.use_secure_dns = false;
self
}
/// Override the DNS resolver implementation used by the underlying http client.
pub fn no_hickory_dns(mut self) -> Self {
self.use_secure_dns = false;
self
}
}
@@ -38,6 +64,14 @@ struct SocketAddrs {
iter: LookupIpIntoIter,
}
// n.b. static items do not call [`Drop`] on program termination, so this won't be deallocated.
// this is fine, as the OS can deallocate the terminated program faster than we can free memory
// but tools like valgrind might report "memory leaks" as it isn't obvious this is intentional.
static SHARED_RESOLVER: LazyLock<HickoryDnsResolver> = LazyLock::new(|| {
tracing::debug!("Initializing shared DNS resolver");
HickoryDnsResolver::default()
});
#[derive(Debug, thiserror::Error)]
#[error("hickory-dns resolver error: {hickory_error}")]
/// Error occurring while resolving a hostname into an IP address.
@@ -47,29 +81,62 @@ pub struct HickoryDnsError {
}
/// Wrapper around an `AsyncResolver`, which implements the `Resolve` trait.
///
/// Typical use involves instantiating using the `Default` implementation and then resolving using
/// methods or trait implementations.
///
/// The default initialization uses a shared underlying `AsyncResolver`. If a thread local resolver
/// is required use `thread_resolver()` to build a resolver with an independently instantiated
/// internal `AsyncResolver`.
#[derive(Debug, Default, Clone)]
pub struct HickoryDnsResolver {
/// Since we might not have been called in the context of a
/// Tokio Runtime in initialization, so we must delay the actual
/// construction of the resolver.
// Since we might not have been called in the context of a
// Tokio Runtime in initialization, so we must delay the actual
// construction of the resolver.
state: Arc<OnceCell<TokioAsyncResolver>>,
fallback: Arc<OnceCell<TokioAsyncResolver>>,
dont_use_shared: bool,
}
impl Resolve for HickoryDnsResolver {
fn resolve(&self, name: Name) -> Resolving {
let resolver = self.state.clone();
let fallback = self.fallback.clone();
let independent = self.dont_use_shared;
Box::pin(async move {
let resolver = resolver.get_or_try_init(new_resolver)?;
let resolver = resolver.get_or_try_init(|| {
// using a closure here is slightly gross, but this makes sure that if the
// lazy-init returns an error it can be handled by the client
if independent {
new_resolver()
} else {
Ok(SHARED_RESOLVER.state.get_or_try_init(new_resolver)?.clone())
}
})?;
// try the primary DNS resolver that we set up (DoH or DoT or whatever)
let lookup = match resolver.lookup_ip(name.as_str()).await {
Ok(res) => res,
Err(e) => {
// on failure use the fall back system configured DNS resolver
warn!("primary DNS failed w/ error {e}: using system fallback");
let resolver = fallback.get_or_try_init(new_resolver_system)?;
match e.kind() {
ResolveErrorKind::NoRecordsFound { .. } => {}
_ => {
warn!("primary DNS failed w/ error {e}: using system fallback");
}
}
let resolver = fallback.get_or_try_init(|| {
// using a closure here is slightly gross, but this makes sure that if the
// lazy-init returns an error it can be handled by the client
if independent {
new_resolver_system()
} else {
Ok(SHARED_RESOLVER
.fallback
.get_or_try_init(new_resolver_system)?
.clone())
}
})?;
resolver.lookup_ip(name.as_str()).await?
}
};
@@ -93,21 +160,55 @@ impl Iterator for SocketAddrs {
impl HickoryDnsResolver {
/// Attempt to resolve a domain name to a set of ['IpAddr']s
pub async fn resolve_str(&self, name: &str) -> Result<LookupIp, HickoryDnsError> {
let resolver = self.state.get_or_try_init(new_resolver)?;
let resolver = self.state.get_or_try_init(|| self.new_resolver())?;
// try the primary DNS resolver that we set up (DoH or DoT or whatever)
let lookup = match resolver.lookup_ip(name).await {
Ok(res) => res,
Err(e) => {
// on failure use the fall back system configured DNS resolver
warn!("primary DNS failed w/ error {e}: using system fallback");
let resolver = self.fallback.get_or_try_init(new_resolver_system)?;
match e.kind() {
ResolveErrorKind::NoRecordsFound { .. } => {}
_ => {
warn!("primary DNS failed w/ error {e}: using system fallback");
}
}
let resolver = self
.fallback
.get_or_try_init(|| self.new_resolver_system())?;
resolver.lookup_ip(name).await?
}
};
Ok(lookup)
}
/// Create a (lazy-initialized) resolver that is not shared across threads.
pub fn thread_resolver() -> Self {
Self {
dont_use_shared: true,
..Default::default()
}
}
fn new_resolver(&self) -> Result<TokioAsyncResolver, HickoryDnsError> {
if self.dont_use_shared {
new_resolver()
} else {
Ok(SHARED_RESOLVER.state.get_or_try_init(new_resolver)?.clone())
}
}
fn new_resolver_system(&self) -> Result<TokioAsyncResolver, HickoryDnsError> {
if self.dont_use_shared {
new_resolver_system()
} else {
Ok(SHARED_RESOLVER
.fallback
.get_or_try_init(new_resolver_system)?
.clone())
}
}
}
/// Create a new resolver with a custom DoT based configuration. The options are overridden to look
+84 -34
View File
@@ -147,13 +147,13 @@ use thiserror::Error;
use tracing::{instrument, warn};
use url::Url;
use http::HeaderMap;
pub use reqwest::IntoUrl;
#[cfg(not(target_arch = "wasm32"))]
use std::net::SocketAddr;
#[cfg(not(target_arch = "wasm32"))]
use std::sync::Arc;
pub use reqwest::IntoUrl;
mod user_agent;
pub use user_agent::UserAgent;
@@ -210,6 +210,12 @@ pub enum HttpClientError<E: Display = String> {
#[error("failed to resolve request. status: '{status}', additional error message: {error}")]
EndpointFailure { status: StatusCode, error: E },
#[error("failed to decode response body: {source} from {content}")]
ResponseDecodeFailure {
source: serde_json::Error,
content: String,
},
#[cfg(target_arch = "wasm32")]
#[error("the request has timed out")]
RequestTimeout,
@@ -222,6 +228,8 @@ pub struct ClientBuilder {
timeout: Option<Duration>,
custom_user_agent: bool,
reqwest_client_builder: reqwest::ClientBuilder,
#[allow(dead_code)] // not dead code, just unused in wasm
use_secure_dns: bool,
}
impl ClientBuilder {
@@ -233,37 +241,46 @@ impl ClientBuilder {
U: IntoUrl,
E: Display,
{
// a naive check: if the provided URL does not start with http(s), add that scheme
let str_url = url.as_str();
// a naive check: if the provided URL does not start with http(s), add that scheme
if !str_url.starts_with("http") {
let alt = format!("http://{str_url}");
warn!("the provided url ('{str_url}') does not contain scheme information. Changing it to '{alt}' ...");
// TODO: or should we maybe default to https?
Self::new(alt)
} else {
#[cfg(target_arch = "wasm32")]
let reqwest_client_builder = reqwest::ClientBuilder::new();
Ok(Self::new_with_url(url.into_url()?))
}
}
#[cfg(not(target_arch = "wasm32"))]
let reqwest_client_builder = {
let r = reqwest::ClientBuilder::new()
.dns_resolver(Arc::new(HickoryDnsResolver::default()));
/// Constructs a new http `ClientBuilder` from a valid url.
pub fn new_with_url(url: Url) -> Self {
if !url.scheme().starts_with("http") {
warn!("the provided url ('{url}') does not use HTTP / HTTPS scheme");
}
// Note this is extra as the `gzip` feature for `reqwest` crate should be enabled which
// `"Enable[s] auto gzip decompression by checking the Content-Encoding response header."`
//
// I am going to leave it here anyways so that gzip decompression is attempted even if
// that feature is removed.
r.gzip(true)
};
#[cfg(target_arch = "wasm32")]
let reqwest_client_builder = reqwest::ClientBuilder::new();
Ok(ClientBuilder {
url: url.into_url()?,
timeout: None,
custom_user_agent: false,
reqwest_client_builder,
})
#[cfg(not(target_arch = "wasm32"))]
let reqwest_client_builder = {
let r = reqwest::ClientBuilder::new();
// Note this is extra as the `gzip` feature for `reqwest` crate should be enabled which
// `"Enable[s] auto gzip decompression by checking the Content-Encoding response header."`
//
// I am going to leave it here anyways so that gzip decompression is attempted even if
// that feature is removed.
r.gzip(true)
};
ClientBuilder {
url,
timeout: None,
custom_user_agent: false,
reqwest_client_builder,
use_secure_dns: true,
}
}
@@ -319,10 +336,18 @@ impl ClientBuilder {
let mut builder = self
.reqwest_client_builder
.timeout(self.timeout.unwrap_or(DEFAULT_TIMEOUT));
// if no custom user agent was set, use a default
if !self.custom_user_agent {
builder =
builder.user_agent(format!("nym-http-api-client/{}", env!("CARGO_PKG_VERSION")))
}
// unless explicitly disabled use the DoT/DoH enabled resolver
if self.use_secure_dns {
builder = builder.dns_resolver(Arc::new(HickoryDnsResolver::default()));
}
builder.build()?
};
@@ -349,6 +374,9 @@ pub struct Client {
impl Client {
/// Create a new http `Client`
// no timeout until https://github.com/seanmonstar/reqwest/issues/1135 is fixed
//
// In order to prevent interference in API requests at the DNS phase we default to a resolver
// that uses DoT and DoH.
pub fn new(base_url: Url, timeout: Option<Duration>) -> Self {
Self::new_url::<_, String>(base_url, timeout).expect(
"we provided valid url and we were unwrapping previous construction errors anyway",
@@ -849,6 +877,26 @@ fn sanitize_url<K: AsRef<str>, V: AsRef<str>>(
url
}
fn decode_as_text(bytes: &bytes::Bytes, headers: HeaderMap) -> String {
use encoding_rs::{Encoding, UTF_8};
use mime::Mime;
let content_type = headers
.get(http::header::CONTENT_TYPE)
.and_then(|value| value.to_str().ok())
.and_then(|value| value.parse::<Mime>().ok());
let encoding_name = content_type
.as_ref()
.and_then(|mime| mime.get_param("charset").map(|charset| charset.as_str()))
.unwrap_or("utf-8");
let encoding = Encoding::for_label(encoding_name.as_bytes()).unwrap_or(UTF_8);
let (text, _, _) = encoding.decode(bytes);
text.into_owned()
}
/// Attempt to parse a json object from an HTTP response
#[instrument(level = "debug", skip_all)]
pub async fn parse_response<T, E>(res: Response, allow_empty: bool) -> Result<T, HttpClientError<E>>
@@ -864,21 +912,23 @@ where
return Err(HttpClientError::EmptyResponse { status });
}
}
let headers = res.headers().clone();
tracing::trace!("headers: {:?}", headers);
if res.status().is_success() {
#[cfg(debug_assertions)]
{
let text = res.text().await.inspect_err(|err| {
tracing::error!("Couldn't even get response text: {err}");
})?;
tracing::trace!("Result:\n{:#?}", text);
serde_json::from_str(&text)
.map_err(|err| HttpClientError::GenericRequestFailure(err.to_string()))
// internally reqwest is first retrieving bytes and then performing parsing via serde_json
// (and similarly does the same thing for text())
let full = res.bytes().await?;
match serde_json::from_slice(&full) {
Ok(data) => Ok(data),
Err(err) => {
let content = decode_as_text(&full, headers);
Err(HttpClientError::ResponseDecodeFailure {
source: err,
content,
})
}
}
#[cfg(not(debug_assertions))]
Ok(res.json().await?)
} else if res.status() == StatusCode::NOT_FOUND {
Err(HttpClientError::NotFound)
} else {
+1
View File
@@ -20,6 +20,7 @@ mime = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true
serde_yaml = { workspace = true }
subtle.workspace = true
tower = { workspace = true }
tracing.workspace = true
utoipa = { workspace = true, optional = true }
@@ -7,6 +7,7 @@ use axum::{extract::Request, response::Response};
use futures::future::BoxFuture;
use std::sync::Arc;
use std::task::{Context, Poll};
use subtle::ConstantTimeEq;
use tower::{Layer, Service};
use tracing::{debug, instrument, trace};
use zeroize::Zeroizing;
@@ -76,7 +77,7 @@ impl<S> RequireAuth<S> {
return Err("`Authorization` header must contain non-empty `Bearer` token");
}
if self.bearer_token.as_str() != bearer_token {
if bool::from(self.bearer_token.as_bytes().ct_ne(bearer_token.as_bytes())) {
return Err("`Authorization` header does not contain the correct `Bearer` token");
}
+1
View File
@@ -13,6 +13,7 @@ bincode = { workspace = true }
bytes = { workspace = true }
nym-bin-common = { path = "../bin-common" }
nym-crypto = { path = "../crypto" }
nym-service-provider-requests-common = { path = "../service-provider-requests-common" }
nym-sphinx = { path = "../nymsphinx" }
rand = { workspace = true }
serde = { workspace = true, features = ["derive"] }
+151 -40
View File
@@ -23,14 +23,14 @@ const LENGTH_PREFIX_SIZE: usize = 2;
// long for the buffer to fill up, since this kills latency.
pub struct MultiIpPacketCodec {
buffer: BytesMut,
buffer_timeout: tokio::time::Interval,
pub counter: u64,
}
impl MultiIpPacketCodec {
pub fn new(buffer_timeout: Duration) -> Self {
pub fn new() -> Self {
MultiIpPacketCodec {
buffer: BytesMut::new(),
buffer_timeout: tokio::time::interval(buffer_timeout),
counter: 0,
}
}
@@ -40,56 +40,87 @@ impl MultiIpPacketCodec {
bundled_packets.extend_from_slice(&packet);
bundled_packets.freeze()
}
}
// Append a packet to the buffer and return the buffer if it's full
pub fn append_packet(&mut self, packet: Bytes) -> Option<Bytes> {
let mut bundled_packets = BytesMut::new();
self.encode(packet, &mut bundled_packets).unwrap();
if bundled_packets.is_empty() {
None
} else {
// log::info!("Sphinx packet utilization: {:.2}", self.buffer.len() as f64 / MAX_PACKET_SIZE as f64);
Some(bundled_packets.freeze())
impl Default for MultiIpPacketCodec {
fn default() -> Self {
Self::new()
}
}
/// The packet that we encode and decode with the MultiIpPacketCodec into bundled multi-ip packets.
/// The data here is the actual IP packet that we want to send, not the bundled packets.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum IprPacket {
Data(Bytes),
Flush,
}
impl IprPacket {
pub fn as_bytes(&self) -> &[u8] {
match self {
IprPacket::Data(bytes) => bytes.as_ref(),
IprPacket::Flush => &[],
}
}
// Flush the current buffer and return it.
pub fn flush_current_buffer(&mut self) -> Bytes {
let mut output_buffer = BytesMut::new();
std::mem::swap(&mut output_buffer, &mut self.buffer);
output_buffer.freeze()
}
// Wait for the buffer_timeout to tick and then flush the buffer.
// This is useful when we want to send the buffer even if it's not full.
pub async fn buffer_timeout(&mut self) -> Option<Bytes> {
// Wait for buffer_timeout to tick
let _ = self.buffer_timeout.tick().await;
// Flush the buffer and return it
let packets = self.flush_current_buffer();
if packets.is_empty() {
None
} else {
Some(packets)
pub fn into_bytes(self) -> Bytes {
match self {
IprPacket::Data(bytes) => bytes,
IprPacket::Flush => Bytes::new(),
}
}
}
impl Encoder<Bytes> for MultiIpPacketCodec {
impl From<Bytes> for IprPacket {
fn from(bytes: Bytes) -> Self {
IprPacket::Data(bytes)
}
}
impl From<Vec<u8>> for IprPacket {
fn from(bytes: Vec<u8>) -> Self {
IprPacket::Data(Bytes::from(bytes))
}
}
impl Encoder<IprPacket> for MultiIpPacketCodec {
type Error = Error;
fn encode(&mut self, packet: Bytes, dst: &mut BytesMut) -> Result<(), Self::Error> {
if self.buffer.is_empty() {
self.buffer_timeout.reset();
}
fn encode(&mut self, packet: IprPacket, dst: &mut BytesMut) -> Result<(), Self::Error> {
let packet = match packet {
IprPacket::Flush => {
dst.extend_from_slice(&self.buffer);
self.counter += 1;
println!("Encoding packet: {}", self.counter);
self.buffer = BytesMut::new();
return Ok(());
}
IprPacket::Data(packet) => packet,
};
let packet_size = packet.len();
// If the existing buffer is empty, and the packet is too large, send it directly
if self.buffer.is_empty() && packet_size + LENGTH_PREFIX_SIZE > MAX_PACKET_SIZE {
// Add the packet size
dst.extend_from_slice(&(packet_size as u16).to_be_bytes());
// Add the packet to the buffer
dst.extend_from_slice(&packet);
self.counter += 1;
println!("Encoding packet: {}", self.counter);
return Ok(());
}
// If the packet doesn't fit in the existing buffer, send what we have now in the buffer
// and then add it to the next buffer
if self.buffer.len() + packet_size + LENGTH_PREFIX_SIZE > MAX_PACKET_SIZE {
// If the packet doesn't fit in the buffer, send the buffer and then add it to the buffer
// Send the existing buffer
dst.extend_from_slice(&self.buffer);
self.counter += 1;
println!("Encoding packet: {}", self.counter);
// Start a new buffer
self.buffer = BytesMut::new();
self.buffer_timeout.reset();
}
// Add the packet size
@@ -103,7 +134,7 @@ impl Encoder<Bytes> for MultiIpPacketCodec {
}
impl Decoder for MultiIpPacketCodec {
type Item = Bytes;
type Item = IprPacket;
type Error = Error;
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
@@ -125,6 +156,86 @@ impl Decoder for MultiIpPacketCodec {
// Read the packet
let packet = src.split_to(packet_size);
Ok(Some(packet.freeze()))
Ok(Some(IprPacket::Data(packet.freeze())))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_multi_ip_packet_codec_max_packet_size() {
let mut codec = MultiIpPacketCodec::new();
let mut buffer = BytesMut::new();
// A packet size that is large enough that two packets won't fit in the buffer
const PACKET_SIZE: usize = MAX_PACKET_SIZE - 100;
let packet1 = IprPacket::from(Bytes::from_static(&[0u8; PACKET_SIZE]));
let packet2 = IprPacket::from(Bytes::from_static(&[0u8; PACKET_SIZE]));
codec.encode(packet1.clone(), &mut buffer).unwrap();
assert_eq!(buffer.len(), 0);
codec.encode(packet2.clone(), &mut buffer).unwrap();
assert_eq!(buffer.len(), LENGTH_PREFIX_SIZE + PACKET_SIZE);
// First is the length prefix
assert_eq!(buffer[..2], (PACKET_SIZE as u16).to_be_bytes());
// Next is the packet
assert_eq!(&buffer[2..], packet1.as_bytes());
}
#[test]
fn encode_and_then_decode() {
let mut codec = MultiIpPacketCodec::new();
let mut buffer = BytesMut::new();
let packet = IprPacket::from(Bytes::from_static(&[0u8; 1000]));
codec.encode(packet.clone(), &mut buffer).unwrap();
codec.encode(packet.clone(), &mut buffer).unwrap();
let mut decoded_packets = Vec::new();
while let Some(decoded_packet) = codec.decode(&mut buffer).unwrap() {
decoded_packets.push(decoded_packet);
}
assert_eq!(decoded_packets.len(), 1);
assert_eq!(decoded_packets[0].as_bytes(), packet.as_bytes());
}
#[test]
fn encode_a_packat_that_is_too_large() {
let mut codec = MultiIpPacketCodec::new();
let mut buffer = BytesMut::new();
let packet = IprPacket::from(Bytes::from_static(
&[0u8; MAX_PACKET_SIZE + MAX_PACKET_SIZE],
));
codec.encode(packet, &mut buffer).unwrap();
assert_eq!(
buffer.len(),
MAX_PACKET_SIZE + MAX_PACKET_SIZE + LENGTH_PREFIX_SIZE
);
codec.encode(IprPacket::Flush, &mut buffer).unwrap();
assert_eq!(
buffer.len(),
MAX_PACKET_SIZE + MAX_PACKET_SIZE + LENGTH_PREFIX_SIZE
);
}
#[test]
fn check_that_max_size_does_not_flush() {
let mut codec = MultiIpPacketCodec::new();
let mut buffer = BytesMut::new();
let packet = IprPacket::from(Bytes::from_static(&[0u8; MAX_PACKET_SIZE - 2]));
codec.encode(packet.clone(), &mut buffer).unwrap();
assert_eq!(buffer.len(), 0);
let packet = IprPacket::from(Bytes::from_static(&[0u8; MAX_PACKET_SIZE - 2]));
codec.encode(packet.clone(), &mut buffer).unwrap();
assert_eq!(buffer.len(), MAX_PACKET_SIZE);
}
}
+9 -9
View File
@@ -2,24 +2,18 @@ use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
use std::net::{Ipv4Addr, Ipv6Addr};
// The current version of the protocol.
// The idea here is that we add new request response types at least one version before we start
// using them.
// Also, depending on the version in the client connect message the IPR could respond with a
// matching older version.
pub use v6::request;
pub use v6::response;
pub mod codec;
pub mod sign;
pub mod v6;
pub mod v7;
pub mod v8;
// version 3: initial version
// version 4: IPv6 support
// version 5: Add severity level to info response
// version 6: Increase the available IPs
// version 7: Add signature support (for the future)
pub const CURRENT_VERSION: u8 = 6;
// version 8: Anonymous sends
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct IpPair {
@@ -45,3 +39,9 @@ fn make_bincode_serializer() -> impl bincode::Options {
.with_big_endian()
.with_varint_encoding()
}
fn generate_random() -> u64 {
use rand::RngCore;
let mut rng = rand::rngs::OsRng;
rng.next_u64()
}
@@ -1,6 +1,7 @@
use std::time::Duration;
use nym_crypto::asymmetric::identity;
use nym_crypto::asymmetric::ed25519;
use time::OffsetDateTime;
// For reply protection, if a request is older than this, it will be rejected
const MAX_REQUEST_AGE: Duration = Duration::from_secs(10);
@@ -22,29 +23,37 @@ pub enum SignatureError {
#[error("signature verification failed")]
VerificationFailed {
message: String,
error: identity::SignatureError,
error: ed25519::SignatureError,
},
}
pub trait SignedRequest {
fn identity(&self) -> &identity::PublicKey;
fn identity(&self) -> Option<&ed25519::PublicKey>;
fn request(&self) -> Result<Vec<u8>, SignatureError>;
fn request_as_bytes(&self) -> Result<Vec<u8>, SignatureError>;
fn signature(&self) -> Option<&identity::Signature>;
fn signature(&self) -> Option<&ed25519::Signature>;
fn timestamp(&self) -> time::OffsetDateTime;
fn timestamp(&self) -> OffsetDateTime;
fn verify(&self) -> Result<(), SignatureError> {
let identity = match self.identity() {
Some(identity) => identity,
None => {
// If we are not revealing our identity, we don't need to verify anything
return Ok(());
}
};
if let Some(signature) = self.signature() {
// First check that the request is recent enough
if time::OffsetDateTime::now_utc() - self.timestamp() > MAX_REQUEST_AGE {
if OffsetDateTime::now_utc() - self.timestamp() > MAX_REQUEST_AGE {
return Err(SignatureError::RequestOutOfDate);
}
let request_as_bytes = self.request()?;
let request_as_bytes = self.request_as_bytes()?;
self.identity()
identity
.verify(request_as_bytes, signature)
.map_err(|error| SignatureError::VerificationFailed {
message: "signature verification failed".to_string(),
@@ -1,69 +0,0 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{v6, v7};
impl From<v7::response::StaticConnectFailureReason> for v6::response::StaticConnectFailureReason {
fn from(failure: v7::response::StaticConnectFailureReason) -> Self {
match failure {
v7::response::StaticConnectFailureReason::RequestedIpAlreadyInUse => {
v6::response::StaticConnectFailureReason::RequestedIpAlreadyInUse
}
v7::response::StaticConnectFailureReason::RequestedNymAddressAlreadyInUse => {
v6::response::StaticConnectFailureReason::RequestedNymAddressAlreadyInUse
}
v7::response::StaticConnectFailureReason::OutOfDateTimestamp => {
v6::response::StaticConnectFailureReason::Other("out of date timestamp".to_string())
}
v7::response::StaticConnectFailureReason::Other(reason) => {
v6::response::StaticConnectFailureReason::Other(reason)
}
}
}
}
impl From<v7::response::DynamicConnectFailureReason> for v6::response::DynamicConnectFailureReason {
fn from(failure: v7::response::DynamicConnectFailureReason) -> Self {
match failure {
v7::response::DynamicConnectFailureReason::RequestedNymAddressAlreadyInUse => {
v6::response::DynamicConnectFailureReason::RequestedNymAddressAlreadyInUse
}
v7::response::DynamicConnectFailureReason::NoAvailableIp => {
v6::response::DynamicConnectFailureReason::NoAvailableIp
}
v7::response::DynamicConnectFailureReason::Other(err) => {
v6::response::DynamicConnectFailureReason::Other(err)
}
}
}
}
impl From<v7::response::InfoResponseReply> for v6::response::InfoResponseReply {
fn from(reply: v7::response::InfoResponseReply) -> Self {
match reply {
v7::response::InfoResponseReply::Generic { msg } => {
v6::response::InfoResponseReply::Generic { msg }
}
v7::response::InfoResponseReply::VersionMismatch {
request_version,
response_version,
} => v6::response::InfoResponseReply::VersionMismatch {
request_version,
response_version,
},
v7::response::InfoResponseReply::ExitPolicyFilterCheckFailed { dst } => {
v6::response::InfoResponseReply::ExitPolicyFilterCheckFailed { dst }
}
}
}
}
impl From<v7::response::InfoLevel> for v6::response::InfoLevel {
fn from(level: v7::response::InfoLevel) -> Self {
match level {
v7::response::InfoLevel::Info => v6::response::InfoLevel::Info,
v7::response::InfoLevel::Warn => v6::response::InfoLevel::Warn,
v7::response::InfoLevel::Error => v6::response::InfoLevel::Error,
}
}
}
-1
View File
@@ -1,4 +1,3 @@
pub mod conversion;
pub mod request;
pub mod response;
@@ -1,125 +0,0 @@
use time::OffsetDateTime;
use crate::{v6, v7};
impl From<v6::request::IpPacketRequest> for v7::request::IpPacketRequest {
fn from(ip_packet_request: v6::request::IpPacketRequest) -> Self {
Self {
version: 7,
data: ip_packet_request.data.into(),
}
}
}
impl From<v6::request::IpPacketRequestData> for v7::request::IpPacketRequestData {
fn from(ip_packet_request_data: v6::request::IpPacketRequestData) -> Self {
match ip_packet_request_data {
v6::request::IpPacketRequestData::StaticConnect(r) => {
v7::request::IpPacketRequestData::StaticConnect(
v7::request::SignedStaticConnectRequest {
request: r.into(),
signature: None,
},
)
}
v6::request::IpPacketRequestData::DynamicConnect(r) => {
v7::request::IpPacketRequestData::DynamicConnect(
v7::request::SignedDynamicConnectRequest {
request: r.into(),
signature: None,
},
)
}
v6::request::IpPacketRequestData::Disconnect(r) => {
v7::request::IpPacketRequestData::Disconnect(v7::request::SignedDisconnectRequest {
request: r.into(),
signature: None,
})
}
v6::request::IpPacketRequestData::Data(r) => {
v7::request::IpPacketRequestData::Data(r.into())
}
v6::request::IpPacketRequestData::Ping(r) => {
v7::request::IpPacketRequestData::Ping(r.into())
}
v6::request::IpPacketRequestData::Health(r) => {
v7::request::IpPacketRequestData::Health(r.into())
}
}
}
}
impl From<v6::request::StaticConnectRequest> for v7::request::StaticConnectRequest {
fn from(static_connect_request: v6::request::StaticConnectRequest) -> Self {
Self {
request_id: static_connect_request.request_id,
ips: static_connect_request.ips,
reply_to: static_connect_request.reply_to,
reply_to_hops: static_connect_request.reply_to_hops,
reply_to_avg_mix_delays: static_connect_request.reply_to_avg_mix_delays,
buffer_timeout: static_connect_request.buffer_timeout,
timestamp: OffsetDateTime::now_utc(),
}
}
}
#[allow(deprecated)]
impl From<v6::request::DynamicConnectRequest> for v7::request::DynamicConnectRequest {
fn from(dynamic_connect_request: v6::request::DynamicConnectRequest) -> Self {
Self {
request_id: dynamic_connect_request.request_id,
reply_to: dynamic_connect_request.reply_to,
reply_to_hops: dynamic_connect_request.reply_to_hops,
reply_to_avg_mix_delays: dynamic_connect_request.reply_to_avg_mix_delays,
buffer_timeout: dynamic_connect_request.buffer_timeout,
timestamp: OffsetDateTime::now_utc(),
}
}
}
impl From<v6::request::DisconnectRequest> for v7::request::SignedDisconnectRequest {
fn from(disconnect_request: v6::request::DisconnectRequest) -> Self {
Self {
request: disconnect_request.into(),
signature: None,
}
}
}
impl From<v6::request::DisconnectRequest> for v7::request::DisconnectRequest {
fn from(disconnect_request: v6::request::DisconnectRequest) -> Self {
Self {
request_id: disconnect_request.request_id,
reply_to: disconnect_request.reply_to,
timestamp: OffsetDateTime::now_utc(),
}
}
}
impl From<v6::request::DataRequest> for v7::request::DataRequest {
fn from(data_request: v6::request::DataRequest) -> Self {
Self {
ip_packets: data_request.ip_packets,
}
}
}
impl From<v6::request::PingRequest> for v7::request::PingRequest {
fn from(ping_request: v6::request::PingRequest) -> Self {
Self {
request_id: ping_request.request_id,
reply_to: ping_request.reply_to,
timestamp: OffsetDateTime::now_utc(),
}
}
}
impl From<v6::request::HealthRequest> for v7::request::HealthRequest {
fn from(health_request: v6::request::HealthRequest) -> Self {
Self {
request_id: health_request.request_id,
reply_to: health_request.reply_to,
timestamp: OffsetDateTime::now_utc(),
}
}
}
-2
View File
@@ -1,6 +1,4 @@
pub mod conversion;
pub mod request;
pub mod response;
pub mod signature;
pub const VERSION: u8 = 7;
+53 -33
View File
@@ -1,22 +1,18 @@
use std::fmt;
use nym_crypto::asymmetric::identity;
use nym_sphinx::addressing::clients::Recipient;
use serde::{Deserialize, Serialize};
use time::OffsetDateTime;
use crate::{make_bincode_serializer, IpPair};
use super::{
signature::{SignatureError, SignedRequest},
VERSION,
use crate::{
sign::{SignatureError, SignedRequest},
IpPair,
};
fn generate_random() -> u64 {
use rand::RngCore;
let mut rng = rand::rngs::OsRng;
rng.next_u64()
}
use super::VERSION;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct IpPacketRequest {
pub version: u8,
pub data: IpPacketRequestData,
@@ -30,7 +26,7 @@ impl IpPacketRequest {
reply_to_avg_mix_delays: Option<f64>,
buffer_timeout: Option<u64>,
) -> (Self, u64) {
let request_id = generate_random();
let request_id = crate::generate_random();
(
Self {
version: VERSION,
@@ -58,7 +54,7 @@ impl IpPacketRequest {
reply_to_avg_mix_delays: Option<f64>,
buffer_timeout: Option<u64>,
) -> (Self, u64) {
let request_id = generate_random();
let request_id = crate::generate_random();
(
Self {
version: VERSION,
@@ -79,7 +75,7 @@ impl IpPacketRequest {
}
pub fn new_disconnect_request(reply_to: Recipient) -> (Self, u64) {
let request_id = generate_random();
let request_id = crate::generate_random();
(
Self {
version: VERSION,
@@ -104,7 +100,7 @@ impl IpPacketRequest {
}
pub fn new_ping(reply_to: Recipient) -> (Self, u64) {
let request_id = generate_random();
let request_id = crate::generate_random();
(
Self {
version: VERSION,
@@ -119,7 +115,7 @@ impl IpPacketRequest {
}
pub fn new_health_request(reply_to: Recipient) -> (Self, u64) {
let request_id = generate_random();
let request_id = crate::generate_random();
(
Self {
version: VERSION,
@@ -155,16 +151,27 @@ impl IpPacketRequest {
}
}
pub fn verify(&self) -> Result<(), SignatureError> {
match &self.data {
IpPacketRequestData::StaticConnect(request) => request.verify(),
IpPacketRequestData::DynamicConnect(request) => request.verify(),
IpPacketRequestData::Disconnect(request) => request.verify(),
IpPacketRequestData::Data(_) => Ok(()),
IpPacketRequestData::Ping(_) => Ok(()),
IpPacketRequestData::Health(_) => Ok(()),
}
}
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
use bincode::Options;
make_bincode_serializer().serialize(self)
crate::make_bincode_serializer().serialize(self)
}
pub fn from_reconstructed_message(
message: &nym_sphinx::receiver::ReconstructedMessage,
) -> Result<Self, bincode::Error> {
use bincode::Options;
make_bincode_serializer().deserialize(&message.message)
crate::make_bincode_serializer().deserialize(&message.message)
}
}
@@ -179,6 +186,19 @@ pub enum IpPacketRequestData {
Health(HealthRequest),
}
impl fmt::Display for IpPacketRequestData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
IpPacketRequestData::StaticConnect(_) => write!(f, "StaticConnect"),
IpPacketRequestData::DynamicConnect(_) => write!(f, "DynamicConnect"),
IpPacketRequestData::Disconnect(_) => write!(f, "Disconnect"),
IpPacketRequestData::Data(_) => write!(f, "Data"),
IpPacketRequestData::Ping(_) => write!(f, "Ping"),
IpPacketRequestData::Health(_) => write!(f, "Health"),
}
}
}
impl IpPacketRequestData {
pub fn add_signature(&mut self, signature: identity::Signature) -> Option<identity::Signature> {
match self {
@@ -202,9 +222,9 @@ impl IpPacketRequestData {
pub fn signable_request(&self) -> Option<Result<Vec<u8>, SignatureError>> {
match self {
IpPacketRequestData::StaticConnect(request) => Some(request.request()),
IpPacketRequestData::DynamicConnect(request) => Some(request.request()),
IpPacketRequestData::Disconnect(request) => Some(request.request()),
IpPacketRequestData::StaticConnect(request) => Some(request.request_as_bytes()),
IpPacketRequestData::DynamicConnect(request) => Some(request.request_as_bytes()),
IpPacketRequestData::Disconnect(request) => Some(request.request_as_bytes()),
IpPacketRequestData::Data(_) => None,
IpPacketRequestData::Ping(_) => None,
IpPacketRequestData::Health(_) => None,
@@ -242,7 +262,7 @@ pub struct StaticConnectRequest {
impl StaticConnectRequest {
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
use bincode::Options;
make_bincode_serializer().serialize(self)
crate::make_bincode_serializer().serialize(self)
}
}
@@ -253,11 +273,11 @@ pub struct SignedStaticConnectRequest {
}
impl SignedRequest for SignedStaticConnectRequest {
fn identity(&self) -> &identity::PublicKey {
self.request.reply_to.identity()
fn identity(&self) -> Option<&identity::PublicKey> {
Some(self.request.reply_to.identity())
}
fn request(&self) -> Result<Vec<u8>, SignatureError> {
fn request_as_bytes(&self) -> Result<Vec<u8>, SignatureError> {
self.request
.to_bytes()
.map_err(|error| SignatureError::RequestSerializationError {
@@ -306,7 +326,7 @@ pub struct DynamicConnectRequest {
impl DynamicConnectRequest {
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
use bincode::Options;
make_bincode_serializer().serialize(self)
crate::make_bincode_serializer().serialize(self)
}
}
@@ -317,11 +337,11 @@ pub struct SignedDynamicConnectRequest {
}
impl SignedRequest for SignedDynamicConnectRequest {
fn identity(&self) -> &identity::PublicKey {
self.request.reply_to.identity()
fn identity(&self) -> Option<&identity::PublicKey> {
Some(self.request.reply_to.identity())
}
fn request(&self) -> Result<Vec<u8>, SignatureError> {
fn request_as_bytes(&self) -> Result<Vec<u8>, SignatureError> {
self.request
.to_bytes()
.map_err(|error| SignatureError::RequestSerializationError {
@@ -355,7 +375,7 @@ pub struct DisconnectRequest {
impl DisconnectRequest {
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
use bincode::Options;
make_bincode_serializer().serialize(self)
crate::make_bincode_serializer().serialize(self)
}
}
@@ -366,11 +386,11 @@ pub struct SignedDisconnectRequest {
}
impl SignedRequest for SignedDisconnectRequest {
fn identity(&self) -> &identity::PublicKey {
self.request.reply_to.identity()
fn identity(&self) -> Option<&identity::PublicKey> {
Some(self.request.reply_to.identity())
}
fn request(&self) -> Result<Vec<u8>, SignatureError> {
fn request_as_bytes(&self) -> Result<Vec<u8>, SignatureError> {
self.request
.to_bytes()
.map_err(|error| SignatureError::RequestSerializationError {
+4
View File
@@ -0,0 +1,4 @@
pub mod request;
pub mod response;
pub const VERSION: u8 = 8;
+304
View File
@@ -0,0 +1,304 @@
use std::fmt;
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
use serde::{Deserialize, Serialize};
use time::OffsetDateTime;
use super::VERSION;
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct IpPacketRequest {
pub protocol: Protocol,
pub data: IpPacketRequestData,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum IpPacketRequestData {
Data(DataRequest),
Control(Box<ControlRequest>),
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum ControlRequest {
Connect(ConnectRequest),
Disconnect(DisconnectRequest),
Ping(PingRequest),
Health(HealthRequest),
}
// A data request is when the client wants to send an IP packet to a destination.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct DataRequest {
pub ip_packets: bytes::Bytes,
}
// A dynamic connect request is when the client does not provide the internal IP address it will use
// on the ip packet router, and instead requests one to be assigned to it.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct ConnectRequest {
pub request_id: u64,
// The maximum time in milliseconds the IPR should wait when filling up a mix packet
// with ip packets.
pub buffer_timeout: Option<u64>,
// Timestamp of when the request was sent by the client.
pub timestamp: OffsetDateTime,
}
// A disconnect request is when the client wants to disconnect from the ip packet router and free
// up the allocated IP address.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct DisconnectRequest {
pub request_id: u64,
// Timestamp of when the request was sent by the client.
pub timestamp: OffsetDateTime,
}
// A ping request is when the client wants to check if the ip packet router is still alive.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct PingRequest {
pub request_id: u64,
// Timestamp of when the request was sent by the client.
pub timestamp: OffsetDateTime,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct HealthRequest {
pub request_id: u64,
// Timestamp of when the request was sent by the client.
pub timestamp: OffsetDateTime,
}
impl IpPacketRequest {
pub fn new_connect_request(buffer_timeout: Option<u64>) -> (Self, u64) {
let protocol = Protocol {
version: VERSION,
service_provider_type: ServiceProviderType::IpPacketRouter,
};
let request_id = rand::random();
let timestamp = OffsetDateTime::now_utc();
let connect = ConnectRequest {
request_id,
buffer_timeout,
timestamp,
};
let request = Self {
protocol,
data: IpPacketRequestData::Control(Box::new(ControlRequest::Connect(connect))),
};
(request, request_id)
}
pub fn new_disconnect_request() -> (Self, u64) {
let protocol = Protocol {
version: VERSION,
service_provider_type: ServiceProviderType::IpPacketRouter,
};
let request_id = rand::random();
let timestamp = OffsetDateTime::now_utc();
let disconnect = DisconnectRequest {
request_id,
timestamp,
};
let request = Self {
protocol,
data: IpPacketRequestData::Control(Box::new(ControlRequest::Disconnect(disconnect))),
};
(request, request_id)
}
pub fn new_data_request(ip_packets: bytes::Bytes) -> Self {
Self {
protocol: Protocol {
version: VERSION,
service_provider_type: ServiceProviderType::IpPacketRouter,
},
data: IpPacketRequestData::Data(DataRequest { ip_packets }),
}
}
pub fn new_ping() -> (Self, u64) {
let protocol = Protocol {
version: VERSION,
service_provider_type: ServiceProviderType::IpPacketRouter,
};
let request_id = rand::random();
let timestamp = OffsetDateTime::now_utc();
let ping_request = PingRequest {
request_id,
timestamp,
};
let request = Self {
protocol,
data: IpPacketRequestData::Control(Box::new(ControlRequest::Ping(ping_request))),
};
(request, request_id)
}
pub fn new_health_request() -> (Self, u64) {
let protocol = Protocol {
version: VERSION,
service_provider_type: ServiceProviderType::IpPacketRouter,
};
let request_id = rand::random();
let timestamp = OffsetDateTime::now_utc();
let health_request = HealthRequest {
request_id,
timestamp,
};
let request = Self {
protocol,
data: IpPacketRequestData::Control(Box::new(ControlRequest::Health(health_request))),
};
(request, request_id)
}
pub fn id(&self) -> Option<u64> {
match self.data {
IpPacketRequestData::Control(ref c) => Some(c.id()),
IpPacketRequestData::Data(_) => None,
}
}
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
use bincode::Options;
crate::make_bincode_serializer().serialize(self)
}
pub fn from_reconstructed_message(
message: &nym_sphinx::receiver::ReconstructedMessage,
) -> Result<Self, bincode::Error> {
use bincode::Options;
crate::make_bincode_serializer().deserialize(&message.message)
}
}
impl fmt::Display for IpPacketRequest {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"IpPacketRequest {{ version: {}, data: {} }}",
self.protocol.version, self.data
)
}
}
impl fmt::Display for IpPacketRequestData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
IpPacketRequestData::Data(_) => write!(f, "Data"),
IpPacketRequestData::Control(c) => write!(f, "Control({})", c),
}
}
}
impl ControlRequest {
fn id(&self) -> u64 {
match self {
ControlRequest::Connect(request) => request.request_id,
ControlRequest::Disconnect(request) => request.request_id,
ControlRequest::Ping(request) => request.request_id,
ControlRequest::Health(request) => request.request_id,
}
}
}
impl fmt::Display for ControlRequest {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ControlRequest::Connect(_) => write!(f, "Connect"),
ControlRequest::Disconnect(_) => write!(f, "Disconnect"),
ControlRequest::Ping(_) => write!(f, "Ping"),
ControlRequest::Health(_) => write!(f, "Health"),
}
}
}
impl ConnectRequest {
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
use bincode::Options;
crate::make_bincode_serializer().serialize(self)
}
}
impl DisconnectRequest {
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
use bincode::Options;
crate::make_bincode_serializer().serialize(self)
}
}
#[cfg(test)]
mod tests {
use time::macros::datetime;
use super::*;
#[test]
fn check_size_of_request() {
let connect = IpPacketRequest {
protocol: Protocol {
version: 4,
service_provider_type: ServiceProviderType::IpPacketRouter,
},
data: IpPacketRequestData::Control(Box::new(ControlRequest::Connect(ConnectRequest {
request_id: 123,
buffer_timeout: None,
timestamp: datetime!(2024-01-01 12:59:59.5 UTC),
}))),
};
assert_eq!(connect.to_bytes().unwrap().len(), 21);
}
#[test]
fn check_size_of_data() {
let data = IpPacketRequest {
protocol: Protocol {
version: 4,
service_provider_type: ServiceProviderType::IpPacketRouter,
},
data: IpPacketRequestData::Data(DataRequest {
ip_packets: bytes::Bytes::from(vec![1u8; 32]),
}),
};
assert_eq!(data.to_bytes().unwrap().len(), 36);
}
#[test]
fn serialize_and_deserialize_data_request() {
let data = IpPacketRequest {
protocol: Protocol {
version: 4,
service_provider_type: ServiceProviderType::IpPacketRouter,
},
data: IpPacketRequestData::Data(DataRequest {
ip_packets: bytes::Bytes::from(vec![1, 2, 4, 2, 5]),
}),
};
let serialized = data.to_bytes().unwrap();
let deserialized = IpPacketRequest::from_reconstructed_message(
&nym_sphinx::receiver::ReconstructedMessage {
message: serialized,
sender_tag: None,
},
)
.unwrap();
assert_eq!(deserialized.protocol.version, 4);
assert_eq!(
deserialized.protocol.service_provider_type,
ServiceProviderType::IpPacketRouter
);
assert_eq!(
deserialized.data,
IpPacketRequestData::Data(DataRequest {
ip_packets: bytes::Bytes::from(vec![1, 2, 4, 2, 5]),
})
);
}
}
@@ -0,0 +1,219 @@
use nym_bin_common::build_information::BinaryBuildInformationOwned;
use serde::{Deserialize, Serialize};
use crate::{make_bincode_serializer, IpPair};
use super::VERSION;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct IpPacketResponse {
pub version: u8,
pub data: IpPacketResponseData,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum IpPacketResponseData {
Data(DataResponse),
Control(Box<ControlResponse>),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DataResponse {
pub ip_packet: bytes::Bytes,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ControlResponse {
// Response for a connect request
Connect(ConnectResponse),
// Response for a disconnect initiqated by the client
Disconnect(DisconnectResponse),
// Message from the server that the client got disconnected without the client initiating it
UnrequestedDisconnect(UnrequestedDisconnect),
// Response to ping request
Pong(PongResponse),
// Response for a health request
Health(Box<HealthResponse>),
// Info response. This can be anything from informative messages to errors
Info(InfoResponse),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ConnectResponse {
pub request_id: u64,
pub reply: ConnectResponseReply,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ConnectResponseReply {
Success(ConnectSuccess),
Failure(ConnectFailureReason),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ConnectSuccess {
pub ips: IpPair,
}
#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)]
pub enum ConnectFailureReason {
#[error("client is already connected")]
ClientAlreadyConnected,
#[error("no available ip address")]
NoAvailableIp,
#[error("{0}")]
Other(String),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DisconnectResponse {
pub request_id: u64,
pub reply: DisconnectResponseReply,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum DisconnectResponseReply {
Success,
Failure(DisconnectFailureReason),
}
#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)]
pub enum DisconnectFailureReason {
#[error("client is not connected")]
ClientNotConnected,
#[error("{0}")]
Other(String),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct UnrequestedDisconnect {
pub reason: UnrequestedDisconnectReason,
}
#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)]
pub enum UnrequestedDisconnectReason {
#[error("client mixnet traffic timeout")]
ClientMixnetTrafficTimeout,
#[error("client tun traffic timeout")]
ClientTunTrafficTimeout,
#[error("{0}")]
Other(String),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PongResponse {
pub request_id: u64,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct HealthResponse {
pub request_id: u64,
pub reply: HealthResponseReply,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct HealthResponseReply {
// Return the binary build information of the IPR
pub build_info: BinaryBuildInformationOwned,
// Return if the IPR has performed a successful routing test.
pub routable: Option<bool>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct InfoResponse {
pub request_id: u64,
pub reply: InfoResponseReply,
pub level: InfoLevel,
}
#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)]
pub enum InfoResponseReply {
#[error("{msg}")]
Generic { msg: String },
#[error(
"version mismatch: response is v{request_version} and response is v{response_version}"
)]
VersionMismatch {
request_version: u8,
response_version: u8,
},
#[error("destination failed exit policy filter check: {dst}")]
ExitPolicyFilterCheckFailed { dst: String },
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum InfoLevel {
Info,
Warn,
Error,
}
impl IpPacketResponse {
pub fn new_ip_packet(ip_packet: bytes::Bytes) -> Self {
Self {
version: VERSION,
data: IpPacketResponseData::Data(DataResponse { ip_packet }),
}
}
pub fn id(&self) -> Option<u64> {
match &self.data {
IpPacketResponseData::Data(_) => None,
IpPacketResponseData::Control(response) => response.id(),
}
}
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
use bincode::Options;
make_bincode_serializer().serialize(self)
}
pub fn from_reconstructed_message(
message: &nym_sphinx::receiver::ReconstructedMessage,
) -> Result<Self, bincode::Error> {
use bincode::Options;
make_bincode_serializer().deserialize(&message.message)
}
}
impl IpPacketResponseData {
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
use bincode::Options;
make_bincode_serializer().serialize(self)
}
}
impl ControlResponse {
fn id(&self) -> Option<u64> {
match self {
ControlResponse::Connect(response) => Some(response.request_id),
ControlResponse::Disconnect(response) => Some(response.request_id),
ControlResponse::UnrequestedDisconnect(_) => None,
ControlResponse::Pong(response) => Some(response.request_id),
ControlResponse::Health(response) => Some(response.request_id),
ControlResponse::Info(response) => Some(response.request_id),
}
}
}
impl ConnectResponseReply {
pub fn is_success(&self) -> bool {
match self {
ConnectResponseReply::Success(_) => true,
ConnectResponseReply::Failure(_) => false,
}
}
}
+2 -2
View File
@@ -15,10 +15,10 @@ bls12_381 = { workspace = true, features = ["alloc", "pairings", "experimental",
bincode.workspace = true
cfg-if.workspace = true
itertools = { workspace = true }
digest = "0.9"
digest = { workspace = true }
rand = { workspace = true }
thiserror = { workspace = true }
sha2 = "0.9"
sha2 = { workspace = true }
bs58 = { workspace = true }
serde = { workspace = true, features = ["derive"] }
rayon = { workspace = true, optional = true }
+73 -6
View File
@@ -113,17 +113,13 @@ const G1_HASH_DOMAIN: &[u8] = b"NYMECASH-V01-CS02-with-BLS12381G1_XMD:SHA-256_SS
const SCALAR_HASH_DOMAIN: &[u8] = b"NYMECASH-V01-CS02-with-expander-SHA256";
pub fn hash_g1<M: AsRef<[u8]>>(msg: M) -> G1Projective {
<G1Projective as HashToCurve<ExpandMsgXmd<sha2::Sha256>>>::hash_to_curve(msg, G1_HASH_DOMAIN)
<G1Projective as HashToCurve<ExpandMsgXmd<sha2::Sha256>>>::hash_to_curve([msg], G1_HASH_DOMAIN)
}
pub fn hash_to_scalar<M: AsRef<[u8]>>(msg: M) -> Scalar {
let mut output = vec![Scalar::zero()];
Scalar::hash_to_field::<ExpandMsgXmd<sha2::Sha256>>(
msg.as_ref(),
SCALAR_HASH_DOMAIN,
&mut output,
);
Scalar::hash_to_field::<ExpandMsgXmd<sha2::Sha256>, _>([msg], SCALAR_HASH_DOMAIN, &mut output);
output[0]
}
@@ -401,4 +397,75 @@ mod tests {
assert_eq!(hash_to_scalar(msg2), hash_to_scalar(msg2));
assert_ne!(hash_to_scalar(msg1), hash_to_scalar(msg2));
}
#[test]
fn test_hash_to_scalar() {
let msg1 = "foo";
let expected1 = Scalar::from_bytes(&[
253, 57, 224, 227, 175, 195, 226, 82, 46, 175, 33, 126, 171, 239, 255, 92, 108, 168, 6,
79, 90, 11, 235, 236, 221, 10, 85, 133, 42, 81, 95, 30,
])
.unwrap();
let msg2 = "bar";
let expected2 = Scalar::from_bytes(&[
48, 83, 69, 52, 42, 18, 135, 244, 211, 190, 160, 196, 118, 154, 24, 126, 0, 125, 72,
201, 170, 225, 123, 201, 52, 120, 171, 132, 235, 182, 20, 26,
])
.unwrap();
let msg3 = [
33, 135, 76, 234, 71, 35, 247, 216, 39, 242, 42, 88, 152, 29, 74, 135, 9, 29, 216, 123,
250, 87, 108, 29, 245, 126, 109, 102, 84, 71, 158, 224, 145, 243, 49, 121, 244, 27,
115, 121, 25, 66, 216, 67, 97, 101, 140, 160, 77, 239, 114, 215, 152, 48, 15, 231, 101,
60, 42, 92, 128, 131, 161, 43,
];
let expected3 = Scalar::from_bytes(&[
128, 189, 8, 43, 186, 55, 52, 61, 171, 196, 159, 177, 162, 100, 27, 143, 85, 83, 218,
171, 91, 220, 155, 25, 7, 38, 2, 36, 4, 93, 136, 4,
])
.unwrap();
assert_eq!(hash_to_scalar(msg1), expected1);
assert_eq!(hash_to_scalar(msg2), expected2);
assert_eq!(hash_to_scalar(msg3), expected3);
}
#[test]
fn test_hash_to_g1() {
let msg1 = "foo";
let expected1 = G1Affine::from_compressed(&[
161, 109, 186, 0, 192, 221, 83, 87, 71, 31, 120, 201, 185, 35, 62, 239, 46, 120, 117,
150, 191, 227, 128, 161, 78, 201, 207, 167, 86, 181, 229, 115, 2, 6, 178, 16, 251, 118,
219, 115, 184, 96, 2, 10, 31, 63, 150, 70,
])
.unwrap()
.into();
let msg2 = "bar";
let expected2 = G1Affine::from_compressed(&[
135, 102, 204, 42, 221, 49, 209, 192, 250, 87, 59, 255, 197, 93, 37, 113, 38, 2, 154,
233, 68, 234, 206, 182, 121, 212, 166, 210, 74, 155, 190, 33, 203, 237, 176, 60, 249,
241, 53, 170, 18, 168, 49, 35, 1, 151, 205, 174,
])
.unwrap()
.into();
let msg3 = [
33, 135, 76, 234, 71, 35, 247, 216, 39, 242, 42, 88, 152, 29, 74, 135, 9, 29, 216, 123,
250, 87, 108, 29, 245, 126, 109, 102, 84, 71, 158, 224, 145, 243, 49, 121, 244, 27,
115, 121, 25, 66, 216, 67, 97, 101, 140, 160, 77, 239, 114, 215, 152, 48, 15, 231, 101,
60, 42, 92, 128, 131, 161, 43,
];
let expected3 = G1Affine::from_compressed(&[
184, 200, 211, 115, 47, 45, 39, 185, 105, 9, 222, 247, 132, 241, 121, 130, 238, 224,
155, 109, 105, 201, 137, 154, 132, 149, 214, 233, 136, 69, 77, 132, 174, 30, 46, 123,
20, 92, 219, 18, 45, 29, 208, 127, 158, 145, 130, 41,
])
.unwrap()
.into();
assert_eq!(hash_g1(msg1), expected1);
assert_eq!(hash_g1(msg2), expected2);
assert_eq!(hash_g1(msg3), expected3);
}
}
-48
View File
@@ -1,48 +0,0 @@
[package]
name = "nym-coconut"
version = "0.5.0"
authors = ["Jedrzej Stuczynski <andrew@nymtech.net>", "Ania Piotrowska <ania@nymtech.net>"]
edition = "2021"
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bls12_381 = { workspace = true, default-features = false, features = ["pairings", "alloc", "experimental"] }
itertools = { workspace = true }
digest = "0.9"
rand = { workspace = true }
thiserror = { workspace = true }
serde = { workspace = true }
serde_derive = { workspace = true }
bs58 = { workspace = true }
sha2 = "0.9"
zeroize = { workspace = true, optional = true }
nym-dkg = { path = "../dkg" }
nym-pemstore = { path = "../pemstore" }
[dependencies.ff]
workspace = true
default-features = false
[dependencies.group]
workspace = true
default-features = false
[dev-dependencies]
criterion = { workspace = true, features = ["html_reports"] }
doc-comment = { workspace = true }
rand_chacha = { workspace = true }
[[bench]]
name = "benchmarks"
harness = false
[features]
key-zeroize = ["zeroize", "bls12_381/zeroize"]
default = []
[target.'cfg(target_env = "wasm32-unknown-unknown")'.dependencies]
getrandom = { version="0.2", features=["js"] }
-1
View File
@@ -1 +0,0 @@
This project was partially funded through the NGI0 PET Fund, a fund established by NL.net with financial support from the European Commission's NGI programme, under the aegis of DG Communications Networks, Content and Technology under grant agreement No 825310.
-360
View File
@@ -1,360 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use bls12_381::{multi_miller_loop, G1Affine, G1Projective, G2Affine, G2Prepared, Scalar};
use criterion::{criterion_group, criterion_main, Criterion};
use ff::Field;
use group::{Curve, Group};
use nym_coconut::{
aggregate_signature_shares_and_verify, aggregate_verification_keys, blind_sign,
prepare_blind_sign, prove_bandwidth_credential, random_scalars_refs, setup, ttp_keygen,
verify_credential, verify_partial_blind_signature, Attribute, BlindedSignature, Parameters,
Signature, SignatureShare, VerificationKey,
};
use rand::seq::SliceRandom;
use std::ops::Neg;
use std::time::Duration;
#[allow(unused)]
fn double_pairing(g11: &G1Affine, g21: &G2Affine, g12: &G1Affine, g22: &G2Affine) {
let gt1 = bls12_381::pairing(g11, g21);
let gt2 = bls12_381::pairing(g12, g22);
assert_eq!(gt1, gt2)
}
#[allow(unused)]
fn multi_miller_pairing_affine(g11: &G1Affine, g21: &G2Affine, g12: &G1Affine, g22: &G2Affine) {
let miller_loop_result = multi_miller_loop(&[
(g11, &G2Prepared::from(*g21)),
(&g12.neg(), &G2Prepared::from(*g22)),
]);
assert!(bool::from(
miller_loop_result.final_exponentiation().is_identity()
))
}
#[allow(unused)]
fn bench_pairings(c: &mut Criterion) {
let mut rng = rand::thread_rng();
let g1 = G1Affine::generator();
let g2 = G2Affine::generator();
let r = Scalar::random(&mut rng);
let s = Scalar::random(&mut rng);
let g11 = (g1 * r).to_affine();
let g21 = (g2 * s).to_affine();
let g21_prep = G2Prepared::from(g21);
let g12 = (g1 * s).to_affine();
let g22 = (g2 * r).to_affine();
let g22_prep = G2Prepared::from(g22);
c.bench_function("double pairing", |b| {
b.iter(|| double_pairing(&g11, &g21, &g12, &g22))
});
c.bench_function("multi miller in affine", |b| {
b.iter(|| multi_miller_pairing_affine(&g11, &g21, &g12, &g22))
});
c.bench_function("multi miller with prepared g2", |b| {
b.iter(|| multi_miller_pairing_with_prepared(&g11, &g21_prep, &g12, &g22_prep))
});
c.bench_function("multi miller with semi-prepared g2", |b| {
b.iter(|| multi_miller_pairing_with_semi_prepared(&g11, &g21, &g12, &g22_prep))
});
}
#[allow(unused)]
fn multi_miller_pairing_with_prepared(
g11: &G1Affine,
g21: &G2Prepared,
g12: &G1Affine,
g22: &G2Prepared,
) {
let miller_loop_result = multi_miller_loop(&[(g11, g21), (&g12.neg(), g22)]);
assert!(bool::from(
miller_loop_result.final_exponentiation().is_identity()
))
}
// the case of being able to prepare G2 generator
#[allow(unused)]
fn multi_miller_pairing_with_semi_prepared(
g11: &G1Affine,
g21: &G2Affine,
g12: &G1Affine,
g22: &G2Prepared,
) {
let miller_loop_result =
multi_miller_loop(&[(g11, &G2Prepared::from(*g21)), (&g12.neg(), g22)]);
assert!(bool::from(
miller_loop_result.final_exponentiation().is_identity()
))
}
#[allow(clippy::too_many_arguments)]
fn unblind_and_aggregate(
params: &Parameters,
blinded_signatures: &[BlindedSignature],
partial_verification_keys: &[VerificationKey],
private_attributes: &[&Attribute],
public_attributes: &[&Attribute],
commitment_hash: &G1Projective,
pedersen_commitments_openings: &[Scalar],
verification_key: &VerificationKey,
) -> Signature {
// Unblind all partial signatures
let unblinded_signatures: Vec<Signature> = blinded_signatures
.iter()
.zip(partial_verification_keys.iter())
.map(|(signature, partial_verification_key)| {
signature
.unblind_and_verify(
params,
partial_verification_key,
private_attributes,
public_attributes,
commitment_hash,
pedersen_commitments_openings,
)
.unwrap()
})
.collect();
let unblinded_signature_shares: Vec<SignatureShare> = unblinded_signatures
.iter()
.enumerate()
.map(|(idx, signature)| SignatureShare::new(*signature, (idx + 1) as u64))
.collect();
let mut attributes = vec![];
attributes.extend_from_slice(private_attributes);
attributes.extend_from_slice(public_attributes);
aggregate_signature_shares_and_verify(
params,
verification_key,
&attributes,
&unblinded_signature_shares,
)
.unwrap()
}
struct BenchCase {
num_authorities: u64,
threshold_p: f32,
num_public_attrs: u32,
num_private_attrs: u32,
}
impl BenchCase {
fn threshold(&self) -> u64 {
(self.num_authorities as f32 * self.threshold_p).round() as u64
}
fn num_attrs(&self) -> u32 {
self.num_public_attrs + self.num_private_attrs
}
}
fn bench_coconut(c: &mut Criterion) {
let mut group = c.benchmark_group("benchmark-coconut");
group.measurement_time(Duration::from_secs(1000));
let case = BenchCase {
num_authorities: 100,
threshold_p: 0.7,
num_public_attrs: 2,
num_private_attrs: 2,
};
let params = setup(case.num_public_attrs + case.num_private_attrs).unwrap();
random_scalars_refs!(public_attributes, params, case.num_public_attrs as usize);
let serial_number = params.random_scalar();
let binding_number = params.random_scalar();
let private_attributes = vec![&serial_number, &binding_number];
// The prepare blind sign is performed by the user
let (pedersen_commitments_openings, blind_sign_request) =
prepare_blind_sign(&params, &private_attributes, &public_attributes).unwrap();
// CLIENT BENCHMARK: Data needed to ask for a credential
// Let's benchmark the operations the client has to perform
// to ask for a credential
group.bench_function(
format!(
"[Client] prepare_blind_sign_{}_authorities_{}_attributes_{}_threshold",
case.num_authorities,
case.num_attrs(),
case.threshold_p,
),
|b| {
b.iter(|| prepare_blind_sign(&params, &private_attributes, &public_attributes).unwrap())
},
);
// keys for the validators
let coconut_keypairs = ttp_keygen(&params, case.threshold(), case.num_authorities).unwrap();
// VALIDATOR BENCHMARK: Issue partial credential
// we pick only one key pair, as we want to validate how much does it
// take for a single validator to issue a partial credential
let mut rng = rand::thread_rng();
let keypair = coconut_keypairs.choose(&mut rng).unwrap();
group.bench_function(
format!(
"[Validator] compute_single_blind_sign_for_credential_with_{}_attributes",
case.num_attrs(),
),
|b| {
b.iter(|| {
blind_sign(
&params,
keypair.secret_key(),
&blind_sign_request,
&public_attributes,
)
.unwrap()
})
},
);
// computing all partial credentials
// NOTE: in reality, each validator computes only single signature
let mut blinded_signatures = Vec::new();
for keypair in coconut_keypairs.iter() {
let blinded_signature = blind_sign(
&params,
keypair.secret_key(),
&blind_sign_request,
&public_attributes,
)
.unwrap();
blinded_signatures.push(blinded_signature)
}
let verification_keys: Vec<VerificationKey> = coconut_keypairs
.iter()
.map(|keypair| keypair.verification_key().clone())
.collect();
// verify a random partial blind signature
let rand_idx = 1;
let random_blind_signature = blinded_signatures.get(rand_idx).unwrap();
let partial_verification_key = verification_keys.get(rand_idx).unwrap();
group.bench_function(
format!(
"verify_partial_blind_signature_{}_private_attributes_{}_public_attributes",
case.num_private_attrs, case.num_public_attrs
),
|b| {
b.iter(|| {
verify_partial_blind_signature(
&params,
blind_sign_request.get_private_attributes_pedersen_commitments(),
&public_attributes,
random_blind_signature,
partial_verification_key,
)
})
},
);
// Lets bench worse case, ie aggregating all
let indices: Vec<u64> = (1..=case.num_authorities).collect();
// aggregate verification keys
let aggr_verification_key =
aggregate_verification_keys(&verification_keys, Some(&indices)).unwrap();
// CLIENT OPERATION: Unblind partial singatures and aggregate into single signature
let aggregated_signature = unblind_and_aggregate(
&params,
&blinded_signatures,
&verification_keys,
&private_attributes,
&public_attributes,
&blind_sign_request.get_commitment_hash(),
&pedersen_commitments_openings,
&aggr_verification_key,
);
// CLIENT BENCHMARK: aggregate all partial credentials
group.bench_function(
format!(
"[Client] unblind_and_aggregate_partial_credentials_{}_authorities_{}_attributes_{}_threshold",
case.num_authorities,
case.num_attrs(),
case.threshold_p,
),
|b| {
b.iter(|| {
unblind_and_aggregate(
&params,
&blinded_signatures,
&verification_keys,
&private_attributes,
&public_attributes,
&blind_sign_request.get_commitment_hash(),
&pedersen_commitments_openings,
&aggr_verification_key)
})
},
);
// CLIENT OPERATION: Randomize credentials and generate any cryptographic material to verify them
let theta = prove_bandwidth_credential(
&params,
&aggr_verification_key,
&aggregated_signature,
&serial_number,
&binding_number,
)
.unwrap();
// CLIENT BENCHMARK
group.bench_function(
format!(
"[Client] randomize_and_prove_credential_{}_authorities_{}_attributes_{}_threshold",
case.num_authorities,
case.num_attrs(),
case.threshold_p,
),
|b| {
b.iter(|| {
prove_bandwidth_credential(
&params,
&aggr_verification_key,
&aggregated_signature,
&serial_number,
&binding_number,
)
.unwrap()
})
},
);
// VERIFIER OPERATION
// Verify credentials
verify_credential(&params, &aggr_verification_key, &theta, &public_attributes);
// VERIFICATION BENCHMARK
group.bench_function(
format!(
"[Verifier] verify_credentials_{}_authorities_{}_attributes_{}_threshold",
case.num_authorities,
case.num_attrs(),
case.threshold_p,
),
|b| {
b.iter(|| {
verify_credential(&params, &aggr_verification_key, &theta, &public_attributes)
})
},
);
}
criterion_group!(benches, bench_coconut);
criterion_main!(benches);
-354
View File
@@ -1,354 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use core::ops::{Deref, Mul};
use bls12_381::{G1Projective, Scalar};
use group::Curve;
use serde_derive::{Deserialize, Serialize};
use crate::error::{CoconutError, Result};
use crate::scheme::setup::Parameters;
use crate::traits::{Base58, Bytable};
use crate::utils::{try_deserialize_g1_projective, try_deserialize_scalar};
use crate::Attribute;
/// Type alias for the ephemeral key generated during ElGamal encryption
pub type EphemeralKey = Scalar;
/// Two G1 points representing ElGamal ciphertext
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub struct Ciphertext(pub(crate) G1Projective, pub(crate) G1Projective);
impl TryFrom<&[u8]> for Ciphertext {
type Error = CoconutError;
fn try_from(bytes: &[u8]) -> Result<Ciphertext> {
if bytes.len() != 96 {
return Err(CoconutError::Deserialization(format!(
"Ciphertext must be exactly 96 bytes, got {}",
bytes.len()
)));
}
// safety: we just checked for the length so the unwraps are fine
#[allow(clippy::unwrap_used)]
let c1_bytes: &[u8; 48] = &bytes[..48].try_into().unwrap();
#[allow(clippy::unwrap_used)]
let c2_bytes: &[u8; 48] = &bytes[48..].try_into().unwrap();
let c1 = try_deserialize_g1_projective(
c1_bytes,
CoconutError::Deserialization("Failed to deserialize compressed c1".to_string()),
)?;
let c2 = try_deserialize_g1_projective(
c2_bytes,
CoconutError::Deserialization("Failed to deserialize compressed c2".to_string()),
)?;
Ok(Ciphertext(c1, c2))
}
}
impl Ciphertext {
pub fn c1(&self) -> &G1Projective {
&self.0
}
pub fn c2(&self) -> &G1Projective {
&self.1
}
pub fn to_bytes(&self) -> [u8; 96] {
let mut bytes = [0u8; 96];
bytes[..48].copy_from_slice(&self.0.to_affine().to_compressed());
bytes[48..].copy_from_slice(&self.1.to_affine().to_compressed());
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Result<Ciphertext> {
Ciphertext::try_from(bytes)
}
}
/// PrivateKey used in the ElGamal encryption scheme to recover the plaintext
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub struct PrivateKey(pub(crate) Scalar);
impl PrivateKey {
/// Decrypt takes the ElGamal encryption of a message and returns a point on the G1 curve
/// that represents original h^m.
pub fn decrypt(&self, ciphertext: &Ciphertext) -> G1Projective {
let (c1, c2) = &(ciphertext.0, ciphertext.1);
// (gamma^k * h^m) / (g1^{d * k}) | note: gamma = g1^d
c2 - c1 * self.0
}
pub fn public_key(&self, params: &Parameters) -> PublicKey {
PublicKey(params.gen1() * self.0)
}
pub fn to_bytes(&self) -> [u8; 32] {
self.0.to_bytes()
}
pub fn from_bytes(bytes: &[u8; 32]) -> Result<PrivateKey> {
try_deserialize_scalar(
bytes,
CoconutError::Deserialization(
"Failed to deserialize ElGamal private key - it was not in the canonical form"
.to_string(),
),
)
.map(PrivateKey)
}
}
impl Bytable for PrivateKey {
fn to_byte_vec(&self) -> Vec<u8> {
self.to_bytes().to_vec()
}
fn try_from_byte_slice(slice: &[u8]) -> Result<Self> {
let received = slice.len();
let Ok(arr) = slice.try_into() else {
return Err(CoconutError::UnexpectedArrayLength {
typ: "elgamal::PrivateKey".to_string(),
received,
expected: 32,
});
};
PrivateKey::from_bytes(arr)
}
}
impl Base58 for PrivateKey {}
// TODO: perhaps be more explicit and apart from gamma also store generator and group order?
/// PublicKey used in the ElGamal encryption scheme to produce the ciphertext
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub struct PublicKey(G1Projective);
impl PublicKey {
/// Encrypt encrypts the given message in the form of h^m,
/// where h is a point on the G1 curve using the given public key.
/// The random k is returned alongside the encryption
/// as it is required by the Coconut Scheme to create proofs of knowledge.
pub fn encrypt(
&self,
params: &Parameters,
h: &G1Projective,
msg: &Scalar,
) -> (Ciphertext, EphemeralKey) {
let k = params.random_scalar();
// c1 = g1^k
let c1 = params.gen1() * k;
// c2 = gamma^k * h^m
let c2 = self.0 * k + h * msg;
(Ciphertext(c1, c2), k)
}
pub fn to_bytes(&self) -> [u8; 48] {
self.0.to_affine().to_compressed()
}
pub fn from_bytes(bytes: &[u8; 48]) -> Result<PublicKey> {
try_deserialize_g1_projective(
bytes,
CoconutError::Deserialization(
"Failed to deserialize compressed ElGamal public key".to_string(),
),
)
.map(PublicKey)
}
}
impl Bytable for PublicKey {
fn to_byte_vec(&self) -> Vec<u8> {
self.to_bytes().into()
}
fn try_from_byte_slice(slice: &[u8]) -> Result<Self> {
let received = slice.len();
let Ok(arr) = slice.try_into() else {
return Err(CoconutError::UnexpectedArrayLength {
typ: "elgamal::PublicKey".to_string(),
received,
expected: 48,
});
};
PublicKey::from_bytes(arr)
}
}
impl TryFrom<&[u8]> for PublicKey {
type Error = CoconutError;
fn try_from(slice: &[u8]) -> Result<PublicKey> {
PublicKey::try_from_byte_slice(slice)
}
}
impl Base58 for PublicKey {}
impl Deref for PublicKey {
type Target = G1Projective;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<'a> Mul<&'a Scalar> for &PublicKey {
type Output = G1Projective;
fn mul(self, rhs: &'a Scalar) -> Self::Output {
self.0 * rhs
}
}
#[derive(Serialize, Deserialize)]
/// A convenient wrapper for both keys of the ElGamal keypair
pub struct ElGamalKeyPair {
private_key: PrivateKey,
public_key: PublicKey,
}
impl ElGamalKeyPair {
pub fn public_key(&self) -> &PublicKey {
&self.public_key
}
pub fn private_key(&self) -> &PrivateKey {
&self.private_key
}
}
/// Generate a fresh ElGamal keypair using the group generator specified by the provided [Parameters]
pub fn elgamal_keygen(params: &Parameters) -> ElGamalKeyPair {
let private_key = params.random_scalar();
let gamma = params.gen1() * private_key;
ElGamalKeyPair {
private_key: PrivateKey(private_key),
public_key: PublicKey(gamma),
}
}
pub fn compute_attribute_encryption(
params: &Parameters,
private_attributes: &[&Attribute],
pub_key: &PublicKey,
commitment_hash: G1Projective,
) -> (Vec<Ciphertext>, Vec<EphemeralKey>) {
private_attributes
.iter()
.map(|m| pub_key.encrypt(params, &commitment_hash, m))
.unzip()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn keygen() {
let params = Parameters::default();
let keypair = super::elgamal_keygen(&params);
let expected = params.gen1() * keypair.private_key.0;
let gamma = keypair.public_key.0;
assert_eq!(
expected, gamma,
"Public key, gamma, should be equal to g1^d, where d is the private key"
);
}
#[test]
fn encryption() {
let params = Parameters::default();
let keypair = super::elgamal_keygen(&params);
let r = params.random_scalar();
let h = params.gen1() * r;
let m = params.random_scalar();
let (ciphertext, ephemeral_key) = keypair.public_key.encrypt(&params, &h, &m);
let expected_c1 = params.gen1() * ephemeral_key;
assert_eq!(expected_c1, ciphertext.0, "c1 should be equal to g1^k");
let expected_c2 = keypair.public_key.0 * ephemeral_key + h * m;
assert_eq!(
expected_c2, ciphertext.1,
"c2 should be equal to gamma^k * h^m"
);
}
#[test]
fn decryption() {
let params = Parameters::default();
let keypair = super::elgamal_keygen(&params);
let r = params.random_scalar();
let h = params.gen1() * r;
let m = params.random_scalar();
let (ciphertext, _) = keypair.public_key.encrypt(&params, &h, &m);
let dec = keypair.private_key.decrypt(&ciphertext);
let expected = h * m;
assert_eq!(
expected, dec,
"after ElGamal decryption, original h^m should be obtained"
);
}
#[test]
fn private_key_bytes_roundtrip() {
let params = Parameters::default();
let private_key = PrivateKey(params.random_scalar());
let bytes = private_key.to_bytes();
// also make sure it is equivalent to the internal scalar's bytes
assert_eq!(private_key.0.to_bytes(), bytes);
assert_eq!(private_key, PrivateKey::from_bytes(&bytes).unwrap())
}
#[test]
fn public_key_bytes_roundtrip() {
let params = Parameters::default();
let r = params.random_scalar();
let public_key = PublicKey(params.gen1() * r);
let bytes = public_key.to_bytes();
// also make sure it is equivalent to the internal g1 compressed bytes
assert_eq!(public_key.0.to_affine().to_compressed(), bytes);
assert_eq!(public_key, PublicKey::from_bytes(&bytes).unwrap())
}
#[test]
fn ciphertext_bytes_roundtrip() {
let params = Parameters::default();
let r = params.random_scalar();
let s = params.random_scalar();
let ciphertext = Ciphertext(params.gen1() * r, params.gen1() * s);
let bytes = ciphertext.to_bytes();
// also make sure it is equivalent to the internal g1 compressed bytes concatenated
let expected_bytes = [
ciphertext.0.to_affine().to_compressed(),
ciphertext.1.to_affine().to_compressed(),
]
.concat();
assert_eq!(expected_bytes, bytes);
assert_eq!(ciphertext, Ciphertext::try_from(&bytes[..]).unwrap())
}
}
-69
View File
@@ -1,69 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use thiserror::Error;
/// A `Result` alias where the `Err` case is `coconut_rs::Error`.
pub type Result<T> = std::result::Result<T, CoconutError>;
#[derive(Error, Debug)]
pub enum CoconutError {
#[error("Setup error: {0}")]
Setup(String),
#[error("encountered error during keygen")]
Keygen,
#[error("Issuance related error: {0}")]
Issuance(String),
#[error("Tried to prepare blind sign request for higher than specified number of attributes (max: {}, requested: {})", max, requested)]
IssuanceMaxAttributes { max: usize, requested: usize },
#[error("Interpolation error: {0}")]
Interpolation(String),
#[error("Aggregation error: {0}")]
Aggregation(String),
#[error("Unblind error: {0}")]
Unblind(String),
#[error("Verification error: {0}")]
Verification(String),
#[error("Deserialization error: {0}")]
Deserialization(String),
#[error(
"Deserailization error, expected at least {} bytes, got {}",
min,
actual
)]
DeserializationMinLength { min: usize, actual: usize },
#[error("Tried to deserialize {object} with bytes of invalid length. Expected {actual} < {object} or {modulus_target} % {modulus} == 0")]
DeserializationInvalidLength {
actual: usize,
target: usize,
modulus_target: usize,
modulus: usize,
object: String,
},
#[error("received an array of unexpected size for deserialization of {typ}. got {received} but expected {expected}")]
UnexpectedArrayLength {
typ: String,
received: usize,
expected: usize,
},
#[error("failed to decode the base58 representation: {0}")]
Base58DecodingFailure(#[from] bs58::decode::Error),
#[error("failed to deserialize scalar from the received bytes - it might not have been canonically encoded")]
ScalarDeserializationFailure,
#[error("failed to deserialize G1Projective point from the received bytes - it might not have been canonically encoded")]
G1ProjectiveDeserializationFailure,
}
-15
View File
@@ -1,15 +0,0 @@
use crate::{BlindSignRequest, BlindedSignature, Bytable, VerifyCredentialRequest};
macro_rules! impl_clone {
($struct:ident) => {
impl Clone for $struct {
fn clone(&self) -> Self {
Self::try_from_byte_slice(&self.to_byte_vec()).unwrap()
}
}
};
}
impl_clone!(BlindSignRequest);
impl_clone!(BlindedSignature);
impl_clone!(VerifyCredentialRequest);
-2
View File
@@ -1,2 +0,0 @@
mod clone;
mod serde;
-57
View File
@@ -1,57 +0,0 @@
use crate::elgamal::PrivateKey;
use crate::scheme::SecretKey;
use crate::{
Base58, BlindSignRequest, BlindedSignature, PublicKey, Signature, VerificationKey,
VerifyCredentialRequest,
};
use serde::de::Unexpected;
use serde::{de::Error, de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
macro_rules! impl_serde {
($struct:ident, $visitor:ident) => {
pub struct $visitor {}
impl Serialize for $struct {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_bs58())
}
}
impl<'de> Visitor<'de> for $visitor {
type Value = $struct;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "A base58 encoded struct")
}
fn visit_str<E: Error>(self, s: &str) -> Result<Self::Value, E> {
match $struct::try_from_bs58(s) {
Ok(x) => Ok(x),
Err(_) => Err(Error::invalid_value(Unexpected::Str(s), &self)),
}
}
}
impl<'de> Deserialize<'de> for $struct {
fn deserialize<D>(deserializer: D) -> Result<$struct, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_str($visitor {})
}
}
};
}
impl_serde!(SecretKey, V1);
impl_serde!(VerificationKey, V2);
impl_serde!(PublicKey, V3);
impl_serde!(PrivateKey, V4);
impl_serde!(BlindSignRequest, V5);
impl_serde!(BlindedSignature, V6);
impl_serde!(Signature, V7);
impl_serde!(VerifyCredentialRequest, V8);

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