diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index a5b069dccb..063c6e242c 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -10,6 +10,7 @@ on: - 'nym-api/**' - 'nym-authenticator-client/**' - 'nym-credential-proxy/**' + - 'nym-gateway-probe/**' - 'nym-ip-packet-client/**' - 'nym-network-monitor/**' - 'nym-node/**' @@ -89,7 +90,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: clippy - args: --workspace --all-targets --exclude nym-gateway-probe -- -D warnings + args: --workspace --all-targets --exclude nym-gateway-probe --exclude nym-node-status-api -- -D warnings - name: Clippy (non-macos) if: contains(matrix.os, 'linux') || contains(matrix.os, 'windows') @@ -104,6 +105,14 @@ jobs: with: command: build + # only build on linux because of wg FFI bindings of its dependency (network probe) + - name: Build nym-node-status-api (linux only) + if: runner.os == 'Linux' + uses: actions-rs/cargo@v1 + with: + command: build + args: -p nym-node-status-api + - name: Build all examples if: contains(matrix.os, 'linux') uses: actions-rs/cargo@v1 diff --git a/.github/workflows/ci-check-ns-api-version.yml b/.github/workflows/ci-check-ns-api-version.yml index f449750764..e767c26112 100644 --- a/.github/workflows/ci-check-ns-api-version.yml +++ b/.github/workflows/ci-check-ns-api-version.yml @@ -3,7 +3,7 @@ name: ci-check-ns-api-version on: pull_request: paths: - - "nym-node-status-api/**" + - "nym-node-status-api/nym-node-status-api/**" env: WORKING_DIRECTORY: "nym-node-status-api/nym-node-status-api" diff --git a/.github/workflows/publish-crates-io-dry-run.yml b/.github/workflows/publish-crates-io-dry-run.yml new file mode 100644 index 0000000000..2025df7bc4 --- /dev/null +++ b/.github/workflows/publish-crates-io-dry-run.yml @@ -0,0 +1,43 @@ +name: Publish to crates.io (dry run) + +on: + workflow_dispatch: + inputs: + version: + description: "Version to publish (e.g. 1.21.0)" + required: true + type: string + +jobs: + publish-dry-run: + runs-on: arc-ubuntu-22.04-dind + steps: + - name: Checkout repo + uses: actions/checkout@v6 + + - name: Install rust toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + + - name: Install cargo-workspaces + run: cargo install cargo-workspaces + + - name: Bump versions (local only) + run: | + cargo workspaces version ${{ inputs.version }} \ + --no-git-commit \ + --no-git-tag \ + --no-git-push \ + --yes + + # Note: Dry run may show cascading dependency errors because packages + # aren't actually uploaded. Check if the missing dependency has an + # "aborting upload due to dry run" message earlier in the output - if so, + # it would succeed in a real publish since cargo-workspaces publishes in + # dependency order. cargo-workspaces doesn't fail on err, so there isn't + # a good way to check this at the moment. + - name: Publish (dry run) + run: cargo workspaces publish --from-git --dry-run --allow-dirty diff --git a/.github/workflows/publish-crates-io.yml b/.github/workflows/publish-crates-io.yml new file mode 100644 index 0000000000..68a0cd064c --- /dev/null +++ b/.github/workflows/publish-crates-io.yml @@ -0,0 +1,47 @@ +name: Publish to crates.io + +on: + workflow_dispatch: + inputs: + version: + description: "Version to publish (e.g. 1.21.0)" + required: true + type: string + +jobs: + publish: + runs-on: arc-ubuntu-22.04-dind + steps: + - name: Checkout repo + uses: actions/checkout@v6 + + - name: Install rust toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + + - name: Install cargo-workspaces + run: cargo install cargo-workspaces + + # - name: Configure git + # run: | + # git config user.name "github-actions[bot]" + # git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Bump versions + run: | + cargo workspaces version ${{ inputs.version }} \ + --no-git-push \ + --no-git-tag \ + --yes + + - name: Publish + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + run: cargo workspaces publish --from-git --no-git-commit + + # - name: Push version commit + # run: | + # git push origin HEAD diff --git a/.gitignore b/.gitignore index 5f3826e012..fa4df4495d 100644 --- a/.gitignore +++ b/.gitignore @@ -75,3 +75,5 @@ docs .superego/ .claude/hooks/superego/ .claude/settings.json + +/notes diff --git a/CHANGELOG.md b/CHANGELOG.md index e0ea936754..d58c32a7e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,86 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// ## [Unreleased] +## [2026.3-parmigiano] (2026-02-10) + +- chore: disable LP on parmigiano branch ([#6422]) +- revert mixnet-based client fautly changes from LP ([#6420]) +- [LP fix] Registration client with fallback ([#6419]) +- Lp/ip pool fixes ([#6412]) +- [LP-fix] expose wg psk for the vpn-client ([#6411]) +- LP-fix : configurable LP timeouts ([#6409]) +- LP-fix : add LP x25519 key to the description ([#6408]) +- use rng that is Send ([#6404]) +- use local kem key instead of local x25519 ([#6402]) +- [LP Gateway Probe] CLI and behavior improvements ([#6400]) +- lp: attempt to negotiate (and use) protocol version ([#6399]) +- bugfix: use correct reserved bytes when parsing LpHeader ([#6398]) +- Lp/bugfix/share ip allocation ([#6395]) +- feat: use hex-encoding for lp key digests ([#6394]) +- Add socks5 test to gateway-probe ([#6393]) +- [LP Gateway probe] Improve file structure ([#6391]) +- Reduce the size of `HttpClientError` ([#6390]) +- Lp/two step dvpn reg ([#6386]) +- Add extra configured nym api url to env ([#6382]) +- Lp/dvpn psk injection ([#6378]) +- LP: include signing key digests to LP responses ([#6373]) +- Lp/use noise x25519 ([#6372]) +- Topology fallback ([#6363]) +- NS API socks5 support ([#6361]) +- LP: modified LPRemotePeer to dynamically choose required KEM key hash ([#6358]) +- Fix KKT Integration into LP ([#6357]) +- LP: mixnet reg fixes ([#6356]) +- LP: announced KEM key hashes ([#6349]) +- revert faulty drop changes ([#6346]) +- small qol changes ([#6340]) +- Apply configured api urls via env ([#6337]) +- lp chore: make sure to take reserved bytes straight from the header ([#6336]) +- LP: x25519/ed22519 cleanup round ([#6335]) +- Lp/encrypted kkt ([#6331]) +- ensure packets with incompatible versions are rejected ([#6326]) +- standarise lp serialisation: ([#6324]) +- Upgrade to def_guard_wireguard v0.8.0 ([#6315]) +- Max/crates io prep v2 ([#6270]) + +[#6422]: https://github.com/nymtech/nym/pull/6422 +[#6420]: https://github.com/nymtech/nym/pull/6420 +[#6419]: https://github.com/nymtech/nym/pull/6419 +[#6412]: https://github.com/nymtech/nym/pull/6412 +[#6411]: https://github.com/nymtech/nym/pull/6411 +[#6409]: https://github.com/nymtech/nym/pull/6409 +[#6408]: https://github.com/nymtech/nym/pull/6408 +[#6404]: https://github.com/nymtech/nym/pull/6404 +[#6402]: https://github.com/nymtech/nym/pull/6402 +[#6400]: https://github.com/nymtech/nym/pull/6400 +[#6399]: https://github.com/nymtech/nym/pull/6399 +[#6398]: https://github.com/nymtech/nym/pull/6398 +[#6395]: https://github.com/nymtech/nym/pull/6395 +[#6394]: https://github.com/nymtech/nym/pull/6394 +[#6393]: https://github.com/nymtech/nym/pull/6393 +[#6391]: https://github.com/nymtech/nym/pull/6391 +[#6390]: https://github.com/nymtech/nym/pull/6390 +[#6386]: https://github.com/nymtech/nym/pull/6386 +[#6382]: https://github.com/nymtech/nym/pull/6382 +[#6378]: https://github.com/nymtech/nym/pull/6378 +[#6373]: https://github.com/nymtech/nym/pull/6373 +[#6372]: https://github.com/nymtech/nym/pull/6372 +[#6363]: https://github.com/nymtech/nym/pull/6363 +[#6361]: https://github.com/nymtech/nym/pull/6361 +[#6358]: https://github.com/nymtech/nym/pull/6358 +[#6357]: https://github.com/nymtech/nym/pull/6357 +[#6356]: https://github.com/nymtech/nym/pull/6356 +[#6349]: https://github.com/nymtech/nym/pull/6349 +[#6346]: https://github.com/nymtech/nym/pull/6346 +[#6340]: https://github.com/nymtech/nym/pull/6340 +[#6337]: https://github.com/nymtech/nym/pull/6337 +[#6336]: https://github.com/nymtech/nym/pull/6336 +[#6335]: https://github.com/nymtech/nym/pull/6335 +[#6331]: https://github.com/nymtech/nym/pull/6331 +[#6326]: https://github.com/nymtech/nym/pull/6326 +[#6324]: https://github.com/nymtech/nym/pull/6324 +[#6315]: https://github.com/nymtech/nym/pull/6315 +[#6270]: https://github.com/nymtech/nym/pull/6270 + ## [2026.2-oscypek] (2026-01-27) - bugfix: downgrade gateway protocol to clients proposed version ([#6377]) diff --git a/Cargo.lock b/Cargo.lock index 1dda35940c..dbc4c27625 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,7 +11,7 @@ dependencies = [ "macroific", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -24,15 +24,6 @@ dependencies = [ "psl-types", ] -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - [[package]] name = "adler2" version = "2.0.1" @@ -45,7 +36,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "generic-array 0.14.7", ] @@ -56,7 +47,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", - "cipher", + "cipher 0.4.4", + "cpufeatures", +] + +[[package]] +name = "aes" +version = "0.9.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd9e1c818b25efb32214df89b0ec22f01aa397aaeb718d1022bf0635a3bfd1a8" +dependencies = [ + "cfg-if", + "cipher 0.5.0-rc.3", "cpufeatures", ] @@ -67,8 +69,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" dependencies = [ "aead", - "aes", - "cipher", + "aes 0.8.4", + "cipher 0.4.4", "ctr", "ghash", "subtle 2.6.1", @@ -81,14 +83,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae0784134ba9375416d469ec31e7c5f9fa94405049cf08c5ce5b4698be673e0d" dependencies = [ "aead", - "aes", - "cipher", + "aes 0.8.4", + "cipher 0.4.4", "ctr", "polyval", "subtle 2.6.1", "zeroize", ] +[[package]] +name = "aes-keywrap" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10b6f24a1f796bc46415a1d0d18dc0a8203ccba088acf5def3291c4f61225522" +dependencies = [ + "aes 0.9.0-rc.2", + "byteorder", +] + [[package]] name = "ahash" version = "0.8.12" @@ -103,9 +115,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -144,12 +156,6 @@ dependencies = [ "url", ] -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - [[package]] name = "android_system_properties" version = "0.1.5" @@ -176,9 +182,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.19" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", @@ -191,9 +197,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.11" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anstyle-parse" @@ -206,44 +212,47 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.9" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "anyhow" -version = "1.0.98" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "arbitrary" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" dependencies = [ "derive_arbitrary", ] [[package]] name = "arc-swap" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" +checksum = "51d03449bb8ca2cc2ef70869af31463d1ae5ccc8fa3e334b307203fbf815207e" +dependencies = [ + "rustversion", +] [[package]] name = "argon2" @@ -417,7 +426,7 @@ dependencies = [ "rustc-hash", "serde", "serde_derive", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -455,27 +464,24 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.27" +version = "0.4.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddb939d66e4ae03cee6091612804ba446b12878410cfa17f785f4dd67d4014e8" +checksum = "98ec5f6c2f8bc326c994cb9e241cc257ddaba9afa8555a43cffbb5dd86efaa37" dependencies = [ - "brotli", - "flate2", + "compression-codecs", + "compression-core", "futures-core", - "memchr", "pin-project-lite", "tokio", - "zstd", - "zstd-safe", ] [[package]] name = "async-lock" -version = "3.4.0" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" dependencies = [ - "event-listener 5.4.0", + "event-listener 5.4.1", "event-listener-strategy", "pin-project-lite", ] @@ -499,18 +505,18 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "async-trait" -version = "0.1.88" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -576,10 +582,10 @@ dependencies = [ "axum-macros", "bytes", "futures-util", - "http 1.3.1", + "http 1.4.0", "http-body 1.0.1", "http-body-util", - "hyper 1.6.0", + "hyper 1.8.1", "hyper-util", "itoa", "matchit", @@ -620,7 +626,7 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http 1.3.1", + "http 1.4.0", "http-body 1.0.1", "http-body-util", "mime", @@ -644,7 +650,7 @@ dependencies = [ "fastrand 2.3.0", "futures-util", "headers", - "http 1.3.1", + "http 1.4.0", "http-body 1.0.1", "http-body-util", "mime", @@ -664,7 +670,7 @@ checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -680,9 +686,9 @@ dependencies = [ "bytes", "bytesize", "cookie", - "http 1.3.1", + "http 1.4.0", "http-body-util", - "hyper 1.6.0", + "hyper 1.8.1", "hyper-util", "mime", "pretty_assertions", @@ -697,21 +703,6 @@ dependencies = [ "url", ] -[[package]] -name = "backtrace" -version = "0.3.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets 0.52.6", -] - [[package]] name = "base16ct" version = "0.2.0" @@ -738,9 +729,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.8.0" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" +checksum = "7d809780667f4410e7c41b07f52439b94d2bdf8528eeedc287fa38d3b7f95d82" [[package]] name = "base85rs" @@ -759,9 +750,9 @@ dependencies = [ [[package]] name = "bech32" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" +checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f" [[package]] name = "bincode" @@ -797,9 +788,9 @@ dependencies = [ [[package]] name = "bip39" -version = "2.2.0" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d193de1f7487df1914d3a568b772458861d33f9c54249612cc2893d6915054" +checksum = "90dbd31c98227229239363921e60fcf5e558e43ec69094d46fc4996f08d1d5bc" dependencies = [ "bitcoin_hashes", "rand 0.8.5", @@ -809,19 +800,12 @@ dependencies = [ "zeroize", ] -[[package]] -name = "bitcoin-internals" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb" - [[package]] name = "bitcoin_hashes" -version = "0.13.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b" +checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" dependencies = [ - "bitcoin-internals", "hex-conservative", ] @@ -833,11 +817,11 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.1" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -935,22 +919,6 @@ dependencies = [ "siphasher 1.0.1", ] -[[package]] -name = "bls12_381" -version = "0.8.0" -source = "git+https://github.com/jstuczyn/bls12_381?branch=temp%2Fexperimental-serdect-updated#9bf520059cb28323fc51469cae86868ef4fa6fbd" -dependencies = [ - "digest 0.10.7", - "ff", - "group", - "pairing", - "rand_core 0.6.4", - "serde", - "serdect 0.3.0", - "subtle 2.6.1", - "zeroize", -] - [[package]] name = "bnum" version = "0.11.0" @@ -959,9 +927,9 @@ checksum = "3e31ea183f6ee62ac8b8a8cf7feddd766317adfb13ff469de57ce033efd6a790" [[package]] name = "brotli" -version = "8.0.1" +version = "8.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9991eea70ea4f293524138648e41ee89b0b2b12ddef3b255effa43c8056e0e0d" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -990,9 +958,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "byte-tools" @@ -1018,9 +986,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" dependencies = [ "serde", ] @@ -1033,11 +1001,11 @@ checksum = "2e93abca9e28e0a1b9877922aacb20576e05d4679ffa78c3d6dc22a26a216659" [[package]] name = "camino" -version = "1.1.10" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0da45bc31171d8d6960122e222a67740df867c1dd53b4d51caa297084c185cab" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -1057,7 +1025,7 @@ checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" dependencies = [ "camino", "cargo-platform", - "semver 1.0.26", + "semver 1.0.27", "serde", "serde_json", "thiserror 1.0.69", @@ -1071,10 +1039,10 @@ checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" dependencies = [ "camino", "cargo-platform", - "semver 1.0.26", + "semver 1.0.27", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -1091,10 +1059,11 @@ checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" [[package]] name = "cc" -version = "1.2.30" +version = "1.2.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" +checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" dependencies = [ + "find-msvc-tools", "jobserver", "libc", "shlex", @@ -1106,15 +1075,15 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc3ba3c5408fae183329e0d1ac8f8eed3cb7b647590fd93e6d6288f6b09db0be" dependencies = [ - "phf", + "phf 0.11.3", "serde", ] [[package]] name = "cfg-if" -version = "1.0.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" @@ -1139,7 +1108,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" dependencies = [ "cfg-if", - "cipher", + "cipher 0.4.4", "cpufeatures", ] @@ -1151,24 +1120,23 @@ checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" dependencies = [ "aead", "chacha20", - "cipher", + "cipher 0.4.4", "poly1305", "zeroize", ] [[package]] name = "chrono" -version = "0.4.41" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" dependencies = [ - "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "serde", "wasm-bindgen", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -1204,16 +1172,26 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "crypto-common", - "inout", + "crypto-common 0.1.7", + "inout 0.1.4", "zeroize", ] [[package]] -name = "clap" -version = "4.5.41" +name = "cipher" +version = "0.5.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9" +checksum = "98d708bac5451350d56398433b19a7889022fa9187df1a769c0edbc3b2c03167" +dependencies = [ + "crypto-common 0.2.0-rc.9", + "inout 0.2.2", +] + +[[package]] +name = "clap" +version = "4.5.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" dependencies = [ "clap_builder", "clap_derive", @@ -1221,9 +1199,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.41" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d" +checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" dependencies = [ "anstream", "anstyle", @@ -1233,9 +1211,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.55" +version = "4.5.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5abde44486daf70c5be8b8f8f1b66c49f86236edf6fa2abadb4d961c4c6229a" +checksum = "430b4dc2b5e3861848de79627b2bedc9f3342c7da5173a14eaa5d0f8dc18ae5d" dependencies = [ "clap", ] @@ -1252,26 +1230,26 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.41" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "clap_lex" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "classic-mceliece-rust" version = "3.2.0" -source = "git+https://github.com/georgio/classic-mceliece-rust#f2f27048b621df103bbe64369a18174ffec04ae1" +source = "git+https://github.com/georgio/classic-mceliece-rust#7bdf0c3c8c727cbc4784e05b0972641ad0791ab9" dependencies = [ "rand 0.9.2", "sha3", @@ -1307,15 +1285,35 @@ dependencies = [ [[package]] name = "comfy-table" -version = "7.1.4" +version = "7.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a65ebfec4fb190b6f90e944a817d60499ee0744e582530e2c9900a22e591d9a" +checksum = "b03b7db8e0b4b2fdad6c551e634134e99ec000e5c8c3b6856c65e8bbaded7a3b" dependencies = [ - "crossterm 0.28.1", + "crossterm 0.29.0", "unicode-segmentation", - "unicode-width 0.2.1", + "unicode-width 0.2.2", ] +[[package]] +name = "compression-codecs" +version = "0.4.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0f7ac3e5b97fdce45e8922fb05cae2c37f7bbd63d30dd94821dacfd8f3f2bf2" +dependencies = [ + "brotli", + "compression-core", + "flate2", + "memchr", + "zstd", + "zstd-safe", +] + +[[package]] +name = "compression-core" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -1327,15 +1325,15 @@ dependencies = [ [[package]] name = "console" -version = "0.16.0" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e09ced7ebbccb63b4c65413d821f2e00ce54c5ca4514ddc6b3c892fdbcbc69d" +checksum = "03e45a4a8926227e4197636ba97a9fc9b00477e9f4bd711395687c5f0734bec4" dependencies = [ "encode_unicode", "libc", "once_cell", - "unicode-width 0.2.1", - "windows-sys 0.60.2", + "unicode-width 0.2.2", + "windows-sys 0.61.2", ] [[package]] @@ -1401,9 +1399,9 @@ checksum = "3618cccc083bb987a415d85c02ca6c9994ea5b44731ec28b9ecf09658655fba9" [[package]] name = "const_format" -version = "0.2.34" +version = "0.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd" +checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" dependencies = [ "const_format_proc_macros", ] @@ -1454,10 +1452,10 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "core-models" version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#f63bb67ead59297560edf523a3b29b21489c17ea" +source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" dependencies = [ "hax-lib", - "pastey", + "pastey 0.2.1", "rand 0.9.2", ] @@ -1497,15 +1495,15 @@ dependencies = [ [[package]] name = "cosmwasm-core" -version = "2.2.2" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35b6dc17e7fd89d0a0a58f12ef33f0bbdf09a6a14c3dfb383eae665e5889250e" +checksum = "63efd882086668c3bad621b5b897a0e058d0b5fae027c984e1a6230bf284ab85" [[package]] name = "cosmwasm-crypto" -version = "2.2.2" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2f53285517db3e33d825b3e46301efe845135778527e1295154413b2f0469e" +checksum = "0969e6f569f867725bf89e672058ae336e130c1fcf914def4f7c6c0380b2bc83" dependencies = [ "ark-bls12-381", "ark-ec", @@ -1527,13 +1525,13 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "2.2.2" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a782b93fae93e57ca8ad3e9e994e784583f5933aeaaa5c80a545c4b437be2047" +checksum = "d0da0db99715fc86e6a5ad7be9c19d0d9e5a92179862b17cae7406b729a402c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -1557,7 +1555,7 @@ checksum = "e01c9214319017f6ebd8e299036e1f717fa9bb6724e758f7d6fb2477599d1a29" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -1595,9 +1593,9 @@ dependencies = [ [[package]] name = "crc" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" dependencies = [ "crc-catalog", ] @@ -1722,14 +1720,15 @@ dependencies = [ [[package]] name = "crossterm" -version = "0.28.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "crossterm_winapi", + "document-features", "parking_lot", - "rustix 0.38.44", + "rustix", "winapi", ] @@ -1762,15 +1761,24 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array 0.14.7", "rand_core 0.6.4", "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.0-rc.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41b8986f836d4aeb30ccf4c9d3bd562fd716074cfd7fc4a2948359fbd21ed809" +dependencies = [ + "hybrid-array", +] + [[package]] name = "crypto-mac" version = "0.7.0" @@ -1790,7 +1798,7 @@ dependencies = [ "cssparser-macros", "dtoa-short", "itoa", - "phf", + "phf 0.11.3", "smallvec", ] @@ -1801,26 +1809,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "csv" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf" +checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938" dependencies = [ "csv-core", "itoa", "ryu", - "serde", + "serde_core", ] [[package]] name = "csv-core" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d" +checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782" dependencies = [ "memchr", ] @@ -1837,7 +1845,7 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" dependencies = [ - "cipher", + "cipher 0.4.4", ] [[package]] @@ -1852,24 +1860,24 @@ dependencies = [ [[package]] name = "curl" -version = "0.4.48" +version = "0.4.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e2d5c8f48d9c0c23250e52b55e82a6ab4fdba6650c931f5a0a57a43abda812b" +checksum = "79fc3b6dd0b87ba36e565715bf9a2ced221311db47bd18011676f24a6066edbc" dependencies = [ "curl-sys", "libc", "openssl-probe", "openssl-sys", "schannel", - "socket2 0.5.10", + "socket2 0.6.1", "windows-sys 0.59.0", ] [[package]] name = "curl-sys" -version = "0.4.82+curl-8.14.1" +version = "0.4.84+curl-8.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4d63638b5ec65f1a4ae945287b3fd035be4554bbaf211901159c9a2a74fb5be" +checksum = "abc4294dc41b882eaff37973c2ec3ae203d0091341ee68fbadd1d06e0c18a73b" dependencies = [ "cc", "libc", @@ -1905,7 +1913,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -1953,7 +1961,7 @@ dependencies = [ "schemars 0.8.22", "serde", "sha2 0.10.9", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -1990,7 +1998,7 @@ dependencies = [ "cosmwasm-std", "cw-storage-plus", "schemars 0.8.22", - "semver 1.0.26", + "semver 1.0.27", "serde", "thiserror 1.0.69", ] @@ -2042,8 +2050,18 @@ version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.20.11", + "darling_macro 0.20.11", +] + +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core 0.21.3", + "darling_macro 0.21.3", ] [[package]] @@ -2057,7 +2075,21 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.106", + "syn 2.0.114", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.114", ] [[package]] @@ -2066,9 +2098,20 @@ version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ - "darling_core", + "darling_core 0.20.11", "quote", - "syn 2.0.106", + "syn 2.0.114", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core 0.21.3", + "quote", + "syn 2.0.114", ] [[package]] @@ -2091,12 +2134,41 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" +[[package]] +name = "defguard_boringtun" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e226ae51c414f475137460063382d0bb6b5a1b1b1a2b135d4b3b830ab43f06ec" +dependencies = [ + "aead", + "base64 0.22.1", + "blake2 0.10.6", + "chacha20poly1305", + "hex", + "hmac", + "ip_network", + "ip_network_table", + "libc", + "nix 0.30.1", + "parking_lot", + "ring", + "socket2 0.6.1", + "thiserror 2.0.17", + "tracing", + "uniffi 0.30.0", + "untrusted", + "x25519-dalek", +] + [[package]] name = "defguard_wireguard_rs" -version = "0.4.7" -source = "git+https://github.com/DefGuard/wireguard-rs.git?rev=v0.4.7#ef1cf3714629bf5016fb38cbb7320451dc69fb09" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf853e6ffe9d6ab91aa1f706cbbf84a446c002cdf469ae0eb085fbcca07648e4" dependencies = [ "base64 0.22.1", + "defguard_boringtun", + "ipnet", "libc", "log", "netlink-packet-core", @@ -2105,9 +2177,13 @@ dependencies = [ "netlink-packet-utils", "netlink-packet-wireguard", "netlink-sys", - "nix 0.29.0", + "nix 0.30.1", + "regex", "serde", - "thiserror 1.0.69", + "thiserror 2.0.17", + "windows 0.62.2", + "wireguard-nt", + "x25519-dalek", ] [[package]] @@ -2121,7 +2197,7 @@ dependencies = [ "macroific", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -2137,12 +2213,12 @@ dependencies = [ [[package]] name = "deranged" -version = "0.4.0" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ "powerfmt", - "serde", + "serde_core", ] [[package]] @@ -2158,13 +2234,13 @@ dependencies = [ [[package]] name = "derive_arbitrary" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -2182,10 +2258,10 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" dependencies = [ - "darling", + "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -2195,7 +2271,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -2209,11 +2285,11 @@ dependencies = [ [[package]] name = "derive_more" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" dependencies = [ - "derive_more-impl 2.0.1", + "derive_more-impl 2.1.1", ] [[package]] @@ -2224,19 +2300,20 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", "unicode-xid", ] [[package]] name = "derive_more-impl" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "rustc_version 0.4.1", + "syn 2.0.114", "unicode-xid", ] @@ -2272,7 +2349,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", "const-oid", - "crypto-common", + "crypto-common 0.1.7", "subtle 2.6.1", ] @@ -2294,7 +2371,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -2305,7 +2382,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -2321,9 +2398,18 @@ dependencies = [ [[package]] name = "doc-comment" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +checksum = "780955b8b195a21ab8e4ac6b60dd1dbdcec1dc6c51c0617964b08c81785e12c9" + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] [[package]] name = "dotenvy" @@ -2333,9 +2419,9 @@ checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "dtoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04" +checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" [[package]] name = "dtoa-short" @@ -2348,17 +2434,17 @@ dependencies = [ [[package]] name = "dyn-clone" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "easy-addr" -version = "0.1.0" +version = "1.20.1" dependencies = [ "cosmwasm-std", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -2414,12 +2500,12 @@ dependencies = [ [[package]] name = "ed25519-compact" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9b3460f44bea8cd47f45a0c70892f1eff856d97cd55358b2f73f663789f6190" +checksum = "33ce99a9e19c84beb4cc35ece85374335ccc398240712114c85038319ed709bd" dependencies = [ "ct-codecs", - "getrandom 0.2.16", + "getrandom 0.3.4", ] [[package]] @@ -2520,14 +2606,14 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "env_filter" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" dependencies = [ "log", "regex", @@ -2554,12 +2640,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -2590,9 +2676,9 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "event-listener" -version = "5.4.0" +version = "5.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" dependencies = [ "concurrent-queue", "parking", @@ -2605,7 +2691,7 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" dependencies = [ - "event-listener 5.4.0", + "event-listener 5.4.1", "pin-project-lite", ] @@ -2616,12 +2702,12 @@ dependencies = [ "bip39", "console_error_panic_hook", "js-sys", + "nym-wasm-storage", + "nym-wasm-utils", "serde-wasm-bindgen 0.6.5", - "thiserror 2.0.12", + "thiserror 2.0.17", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-storage", - "wasm-utils", "zeroize", ] @@ -2650,7 +2736,7 @@ dependencies = [ "macroific", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -2686,16 +2772,22 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "filetime" -version = "0.2.25" +version = "0.2.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" dependencies = [ "cfg-if", "libc", "libredox", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" + [[package]] name = "fixedbitset" version = "0.4.2" @@ -2704,9 +2796,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.1.2" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" dependencies = [ "crc32fast", "miniz_oxide", @@ -2747,9 +2839,9 @@ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "form_urlencoded" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] @@ -2880,7 +2972,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -2919,20 +3011,6 @@ version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" -[[package]] -name = "generator" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d18470a76cb7f8ff746cf1f7470914f900252ec36bbc40b569d74b1258446827" -dependencies = [ - "cc", - "cfg-if", - "libc", - "log", - "rustversion", - "windows", -] - [[package]] name = "generic-array" version = "0.12.4" @@ -2963,21 +3041,21 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "js-sys", "libc", "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "wasip2", "wasm-bindgen", ] @@ -2991,17 +3069,11 @@ dependencies = [ "polyval", ] -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - [[package]] name = "glob" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "gloo-net" @@ -3013,7 +3085,7 @@ dependencies = [ "futures-core", "futures-sink", "gloo-utils 0.2.0", - "http 1.3.1", + "http 1.4.0", "js-sys", "pin-project", "serde", @@ -3096,7 +3168,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.10.0", + "indexmap 2.13.0", "slab", "tokio", "tokio-util", @@ -3105,17 +3177,17 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.11" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "http 1.3.1", - "indexmap 2.10.0", + "http 1.4.0", + "indexmap 2.13.0", "slab", "tokio", "tokio-util", @@ -3124,12 +3196,13 @@ dependencies = [ [[package]] name = "half" -version = "2.6.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", + "zerocopy", ] [[package]] @@ -3173,22 +3246,28 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.4" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", "foldhash", ] +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + [[package]] name = "hashlink" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" dependencies = [ - "hashbrown 0.15.4", + "hashbrown 0.15.5", ] [[package]] @@ -3212,7 +3291,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -3250,7 +3329,7 @@ dependencies = [ "base64 0.22.1", "bytes", "headers-core", - "http 1.3.1", + "http 1.4.0", "httpdate", "mime", "sha1", @@ -3262,7 +3341,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" dependencies = [ - "http 1.3.1", + "http 1.4.0", ] [[package]] @@ -3291,9 +3370,12 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hex-conservative" -version = "0.1.2" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212ab92002354b4819390025006c897e8140934349e8635c9b077f47b4dcbd20" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" +dependencies = [ + "arrayvec", +] [[package]] name = "hex-literal" @@ -3315,18 +3397,18 @@ dependencies = [ "futures-channel", "futures-io", "futures-util", - "h2 0.4.11", - "http 1.3.1", + "h2 0.4.13", + "http 1.4.0", "idna", "ipnet", "once_cell", "rand 0.9.2", "ring", - "rustls 0.23.29", - "thiserror 2.0.12", + "rustls 0.23.36", + "thiserror 2.0.17", "tinyvec", "tokio", - "tokio-rustls 0.26.2", + "tokio-rustls 0.26.4", "tracing", "url", "webpki-roots 0.26.11", @@ -3347,11 +3429,11 @@ dependencies = [ "parking_lot", "rand 0.9.2", "resolv-conf", - "rustls 0.23.29", + "rustls 0.23.36", "smallvec", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", - "tokio-rustls 0.26.2", + "tokio-rustls 0.26.4", "tracing", "webpki-roots 0.26.11", ] @@ -3388,9 +3470,9 @@ dependencies = [ [[package]] name = "hmac-sha1-compact" -version = "1.1.5" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18492c9f6f9a560e0d346369b665ad2bdbc89fa9bceca75796584e79042694c3" +checksum = "d1a4e188bd5d537801721276d1771f1cea235cd94961ddb2828eb76e16688356" [[package]] name = "hmac-sha256" @@ -3412,11 +3494,11 @@ dependencies = [ [[package]] name = "home" -version = "0.5.11" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3443,12 +3525,11 @@ dependencies = [ [[package]] name = "http" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ "bytes", - "fnv", "itoa", ] @@ -3470,7 +3551,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.3.1", + "http 1.4.0", ] [[package]] @@ -3481,7 +3562,7 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http 1.3.1", + "http 1.4.0", "http-body 1.0.1", "pin-project-lite", ] @@ -3522,9 +3603,9 @@ checksum = "f58b778a5761513caf593693f8951c97a5b610841e754788400f32102eefdff1" [[package]] name = "humantime" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" +checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" [[package]] name = "humantime-serde" @@ -3536,6 +3617,15 @@ dependencies = [ "serde", ] +[[package]] +name = "hybrid-array" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f471e0a81b2f90ffc0cb2f951ae04da57de8baa46fa99112b062a5173a5088d0" +dependencies = [ + "typenum", +] + [[package]] name = "hyper" version = "0.14.32" @@ -3562,20 +3652,22 @@ dependencies = [ [[package]] name = "hyper" -version = "1.6.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" dependencies = [ + "atomic-waker", "bytes", "futures-channel", - "futures-util", - "h2 0.4.11", - "http 1.3.1", + "futures-core", + "h2 0.4.13", + "http 1.4.0", "http-body 1.0.1", "httparse", "httpdate", "itoa", "pin-project-lite", + "pin-utils", "smallvec", "tokio", "want", @@ -3601,15 +3693,15 @@ version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ - "http 1.3.1", - "hyper 1.6.0", + "http 1.4.0", + "hyper 1.8.1", "hyper-util", - "rustls 0.23.29", + "rustls 0.23.36", "rustls-pki-types", "tokio", - "tokio-rustls 0.26.2", + "tokio-rustls 0.26.4", "tower-service", - "webpki-roots 1.0.2", + "webpki-roots 1.0.5", ] [[package]] @@ -3618,7 +3710,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" dependencies = [ - "hyper 1.6.0", + "hyper 1.8.1", "hyper-util", "pin-project-lite", "tokio", @@ -3627,23 +3719,23 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.15" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" +checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" dependencies = [ "base64 0.22.1", "bytes", "futures-channel", "futures-core", "futures-util", - "http 1.3.1", + "http 1.4.0", "http-body 1.0.1", - "hyper 1.6.0", + "hyper 1.8.1", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2 0.5.10", + "socket2 0.6.1", "tokio", "tower-service", "tracing", @@ -3651,9 +3743,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.63" +version = "0.1.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -3661,7 +3753,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core", + "windows-core 0.62.2", ] [[package]] @@ -3708,9 +3800,9 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" dependencies = [ "displaydoc", "potential_utf", @@ -3721,9 +3813,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" dependencies = [ "displaydoc", "litemap", @@ -3734,11 +3826,10 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" dependencies = [ - "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", @@ -3749,42 +3840,38 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "2.0.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" dependencies = [ - "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", "icu_provider", - "potential_utf", "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "2.0.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" [[package]] name = "icu_provider" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" dependencies = [ "displaydoc", "icu_locale_core", - "stable_deref_trait", - "tinystr", "writeable", "yoke", "zerofrom", @@ -3800,9 +3887,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", @@ -3827,7 +3914,7 @@ checksum = "0ab604ee7085efba6efc65e4ebca0e9533e3aff6cb501d7d77b211e3a781c6d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -3861,9 +3948,9 @@ dependencies = [ [[package]] name = "indenter" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" +checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" [[package]] name = "indexed_db_futures" @@ -3874,13 +3961,13 @@ dependencies = [ "accessory", "cfg-if", "delegate-display", - "derive_more 2.0.1", + "derive_more 2.1.1", "fancy_constructor", "indexed_db_futures_macros_internal", "js-sys", "sealed", "smallvec", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "wasm-bindgen", "wasm-bindgen-futures", @@ -3896,7 +3983,7 @@ dependencies = [ "macroific", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -3912,24 +3999,25 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.10.0" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", - "hashbrown 0.15.4", + "hashbrown 0.16.1", "serde", + "serde_core", ] [[package]] name = "indicatif" -version = "0.18.0" +version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a646d946d06bedbbc4cac4c218acf4bbf2d87757a784857025f4d447e4e1cd" +checksum = "9375e112e4b463ec1b1c6c011953545c65a30164fbab5b581df32b3abf0dcb88" dependencies = [ "console", "portable-atomic", - "unicode-width 0.2.1", + "unicode-width 0.2.2", "unit-prefix", "vt100", "web-time", @@ -3975,6 +4063,15 @@ dependencies = [ "generic-array 0.14.7", ] +[[package]] +name = "inout" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" +dependencies = [ + "hybrid-array", +] + [[package]] name = "inquire" version = "0.6.2" @@ -4011,6 +4108,7 @@ name = "integration-tests" version = "0.1.0" dependencies = [ "anyhow", + "futures", "nym-bandwidth-controller", "nym-credential-verification", "nym-credentials-interface", @@ -4035,23 +4133,28 @@ dependencies = [ "rustversion", ] -[[package]] -name = "io-uring" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" -dependencies = [ - "bitflags 2.9.1", - "cfg-if", - "libc", -] - [[package]] name = "ip_network" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa2f047c0a98b2f299aa5d6d7088443570faae494e9ae1305e48be000c9e0eb1" +[[package]] +name = "ip_network_table" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4099b7cfc5c5e2fe8c5edf3f6f7adf7a714c9cc697534f63a5a5da30397cb2c0" +dependencies = [ + "ip_network", + "ip_network_table-deps-treebitmap", +] + +[[package]] +name = "ip_network_table-deps-treebitmap" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e537132deb99c0eb4b752f0346b6a836200eaaa3516dd7e5514b63930a09e5d" + [[package]] name = "ipconfig" version = "0.3.2" @@ -4059,7 +4162,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" dependencies = [ "socket2 0.5.10", - "widestring", + "widestring 1.2.1", "windows-sys 0.48.0", "winreg", ] @@ -4081,9 +4184,9 @@ dependencies = [ [[package]] name = "iri-string" -version = "0.7.8" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" dependencies = [ "memchr", "serde", @@ -4091,20 +4194,20 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.16" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "isahc" @@ -4151,49 +4254,49 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jiff" -version = "0.2.15" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" +checksum = "e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50" dependencies = [ "jiff-static", "log", "portable-atomic", "portable-atomic-util", - "serde", + "serde_core", ] [[package]] name = "jiff-static" -version = "0.2.15" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" +checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "jobserver" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", "libc", ] [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" dependencies = [ "once_cell", "wasm-bindgen", @@ -4201,9 +4304,9 @@ dependencies = [ [[package]] name = "jwt-simple" -version = "0.12.12" +version = "0.12.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "731011e9647a71ff4f8474176ff6ce6e0d2de87a0173f15613af3a84c3e3401a" +checksum = "6ad8761f175784dfbb83709f322fc4daf6b27afd5bf375492f2876f9e925ef5a" dependencies = [ "anyhow", "binstring", @@ -4221,7 +4324,7 @@ dependencies = [ "serde", "serde_json", "superboring", - "thiserror 2.0.12", + "thiserror 2.0.17", "zeroize", ] @@ -4322,14 +4425,25 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.174" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" + +[[package]] +name = "libcrux-aesgcm" +version = "0.0.5" +source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" +dependencies = [ + "libcrux-intrinsics", + "libcrux-platform", + "libcrux-secrets", + "libcrux-traits", +] [[package]] name = "libcrux-chacha20poly1305" version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#f63bb67ead59297560edf523a3b29b21489c17ea" +source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" dependencies = [ "libcrux-hacl-rs", "libcrux-macros", @@ -4341,7 +4455,7 @@ dependencies = [ [[package]] name = "libcrux-curve25519" version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#f63bb67ead59297560edf523a3b29b21489c17ea" +source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" dependencies = [ "libcrux-hacl-rs", "libcrux-macros", @@ -4352,7 +4466,7 @@ dependencies = [ [[package]] name = "libcrux-ecdh" version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#f63bb67ead59297560edf523a3b29b21489c17ea" +source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" dependencies = [ "libcrux-curve25519", "libcrux-p256", @@ -4363,7 +4477,7 @@ dependencies = [ [[package]] name = "libcrux-ed25519" version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#f63bb67ead59297560edf523a3b29b21489c17ea" +source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" dependencies = [ "libcrux-hacl-rs", "libcrux-macros", @@ -4375,7 +4489,7 @@ dependencies = [ [[package]] name = "libcrux-hacl-rs" version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#f63bb67ead59297560edf523a3b29b21489c17ea" +source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" dependencies = [ "libcrux-macros", ] @@ -4383,7 +4497,7 @@ dependencies = [ [[package]] name = "libcrux-hkdf" version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#f63bb67ead59297560edf523a3b29b21489c17ea" +source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" dependencies = [ "libcrux-hacl-rs", "libcrux-hmac", @@ -4393,7 +4507,7 @@ dependencies = [ [[package]] name = "libcrux-hmac" version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#f63bb67ead59297560edf523a3b29b21489c17ea" +source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" dependencies = [ "libcrux-hacl-rs", "libcrux-macros", @@ -4403,7 +4517,7 @@ dependencies = [ [[package]] name = "libcrux-intrinsics" version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#f63bb67ead59297560edf523a3b29b21489c17ea" +source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" dependencies = [ "core-models", "hax-lib", @@ -4412,7 +4526,7 @@ dependencies = [ [[package]] name = "libcrux-kem" version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#f63bb67ead59297560edf523a3b29b21489c17ea" +source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" dependencies = [ "libcrux-curve25519", "libcrux-ecdh", @@ -4427,16 +4541,30 @@ dependencies = [ [[package]] name = "libcrux-macros" version = "0.0.3" -source = "git+https://github.com/cryspen/libcrux#f63bb67ead59297560edf523a3b29b21489c17ea" +source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" dependencies = [ "quote", - "syn 2.0.106", + "syn 2.0.114", +] + +[[package]] +name = "libcrux-ml-dsa" +version = "0.0.5" +source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" +dependencies = [ + "core-models", + "hax-lib", + "libcrux-intrinsics", + "libcrux-macros", + "libcrux-platform", + "libcrux-sha3", + "tls_codec", ] [[package]] name = "libcrux-ml-kem" -version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#f63bb67ead59297560edf523a3b29b21489c17ea" +version = "0.0.5" +source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" dependencies = [ "hax-lib", "libcrux-intrinsics", @@ -4451,7 +4579,7 @@ dependencies = [ [[package]] name = "libcrux-p256" version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#f63bb67ead59297560edf523a3b29b21489c17ea" +source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" dependencies = [ "libcrux-hacl-rs", "libcrux-macros", @@ -4462,8 +4590,8 @@ dependencies = [ [[package]] name = "libcrux-platform" -version = "0.0.2" -source = "git+https://github.com/cryspen/libcrux#f63bb67ead59297560edf523a3b29b21489c17ea" +version = "0.0.3" +source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" dependencies = [ "libc", ] @@ -4471,7 +4599,7 @@ dependencies = [ [[package]] name = "libcrux-poly1305" version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#f63bb67ead59297560edf523a3b29b21489c17ea" +source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" dependencies = [ "libcrux-hacl-rs", "libcrux-macros", @@ -4480,14 +4608,16 @@ dependencies = [ [[package]] name = "libcrux-psq" version = "0.0.5" -source = "git+https://github.com/cryspen/libcrux#f63bb67ead59297560edf523a3b29b21489c17ea" +source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" dependencies = [ + "libcrux-aesgcm", "libcrux-chacha20poly1305", "libcrux-ecdh", "libcrux-ed25519", "libcrux-hkdf", "libcrux-hmac", "libcrux-kem", + "libcrux-ml-dsa", "libcrux-ml-kem", "libcrux-sha2", "libcrux-traits", @@ -4498,7 +4628,7 @@ dependencies = [ [[package]] name = "libcrux-secrets" version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#f63bb67ead59297560edf523a3b29b21489c17ea" +source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" dependencies = [ "hax-lib", ] @@ -4506,7 +4636,7 @@ dependencies = [ [[package]] name = "libcrux-sha2" version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#f63bb67ead59297560edf523a3b29b21489c17ea" +source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" dependencies = [ "libcrux-hacl-rs", "libcrux-macros", @@ -4515,8 +4645,8 @@ dependencies = [ [[package]] name = "libcrux-sha3" -version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#f63bb67ead59297560edf523a3b29b21489c17ea" +version = "0.0.5" +source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" dependencies = [ "hax-lib", "libcrux-intrinsics", @@ -4527,12 +4657,22 @@ dependencies = [ [[package]] name = "libcrux-traits" version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#f63bb67ead59297560edf523a3b29b21489c17ea" +source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" dependencies = [ "libcrux-secrets", "rand 0.9.2", ] +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link 0.2.1", +] + [[package]] name = "libm" version = "0.2.15" @@ -4541,13 +4681,13 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" -version = "0.1.6" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4488594b9328dee448adb906d8b126d9b7deb7cf5c22161ee591610bb1be83c0" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "libc", - "redox_syscall", + "redox_syscall 0.7.0", ] [[package]] @@ -4563,9 +4703,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.22" +version = "1.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b70e7a7df205e92a1a4cd9aaae7898dac0aa555503cc0a649494d0d60e7651d" +checksum = "15d118bbf3771060e7311cc7bb0545b01d08a8b4a7de949198dec1fa0ca1c0f7" dependencies = [ "cc", "libc", @@ -4575,15 +4715,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.15" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" - -[[package]] -name = "linux-raw-sys" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "lioness" @@ -4599,38 +4733,30 @@ dependencies = [ [[package]] name = "litemap" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" [[package]] name = "lock_api" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.27" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" - -[[package]] -name = "loom" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" -dependencies = [ - "cfg-if", - "generator", - "scoped-tls", - "tracing", - "tracing-subscriber", -] +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "lru-slab" @@ -4664,7 +4790,7 @@ dependencies = [ "proc-macro2", "quote", "sealed", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -4676,7 +4802,7 @@ dependencies = [ "proc-macro2", "quote", "sealed", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -4689,7 +4815,7 @@ dependencies = [ "macroific_core", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -4717,7 +4843,7 @@ checksum = "ac84fd3f360fcc43dc5f5d186f02a94192761a080e8bc58621ad4d12296a58cf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -4747,9 +4873,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "memoffset" @@ -4778,9 +4904,9 @@ dependencies = [ [[package]] name = "minicov" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f27fe9f1cc3c22e1687f9446c2083c4c5fc7f0bcf1c7a86bdbded14985895b4b" +checksum = "4869b6a491569605d66d3952bcdf03df789e5b536e5f0cf7758a7f08a55ae24d" dependencies = [ "cc", "walkdir", @@ -4799,6 +4925,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", + "simd-adler32", ] [[package]] @@ -4809,19 +4936,19 @@ checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "log", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "windows-sys 0.48.0", ] [[package]] name = "mio" -version = "1.0.4" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.59.0", + "wasi", + "windows-sys 0.61.2", ] [[package]] @@ -4837,17 +4964,17 @@ dependencies = [ "nym-service-providers-common", "nym-socks5-requests", "nym-validator-client", + "nym-wasm-client-core", + "nym-wasm-utils", "rand 0.8.5", "serde", "serde-wasm-bindgen 0.6.5", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tsify", "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-client-core", - "wasm-utils", ] [[package]] @@ -4873,23 +5000,21 @@ checksum = "dce6dd36094cac388f119d2e9dc82dc730ef91c32a6222170d630e5414b956e6" [[package]] name = "moka" -version = "0.12.10" +version = "0.12.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9321642ca94a4282428e6ea4af8cc2ca4eac48ac7a6a4ea8f33f76d0ce70926" +checksum = "a3dec6bd31b08944e08b58fd99373893a6c17054d6f3ea5006cc894f4f4eee2a" dependencies = [ "async-lock", "crossbeam-channel", "crossbeam-epoch", "crossbeam-utils", - "event-listener 5.4.0", + "equivalent", + "event-listener 5.4.1", "futures-util", - "loom", "parking_lot", "portable-atomic", - "rustc_version 0.4.1", "smallvec", "tagptr", - "thiserror 1.0.69", "uuid", ] @@ -4902,7 +5027,7 @@ dependencies = [ "bytes", "encoding_rs", "futures-util", - "http 1.3.1", + "http 1.4.0", "httparse", "memchr", "mime", @@ -4912,66 +5037,55 @@ dependencies = [ [[package]] name = "netlink-packet-core" -version = "0.7.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72724faf704479d67b388da142b186f916188505e7e0b26719019c525882eda4" +checksum = "3463cbb78394cb0141e2c926b93fc2197e473394b761986eca3b9da2c63ae0f4" dependencies = [ - "anyhow", - "byteorder", - "netlink-packet-utils", + "paste", ] [[package]] name = "netlink-packet-generic" -version = "0.3.3" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd7eb8ad331c84c6b8cb7f685b448133e5ad82e1ffd5acafac374af4a5a308b" +checksum = "2f891b2e0054cac5a684a06628f59568f841c93da4e551239da6e518f539e775" dependencies = [ - "anyhow", - "byteorder", "netlink-packet-core", - "netlink-packet-utils", ] [[package]] name = "netlink-packet-route" -version = "0.20.1" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55e5bda7ca0f9ac5e75b5debac3b75e29a8ac8e2171106a2c3bb466389a8dd83" +checksum = "3ec2f5b6839be2a19d7fa5aab5bc444380f6311c2b693551cb80f45caaa7b5ef" dependencies = [ - "anyhow", - "bitflags 2.9.1", - "byteorder", + "bitflags 2.10.0", "libc", "log", "netlink-packet-core", - "netlink-packet-utils", ] [[package]] name = "netlink-packet-utils" -version = "0.5.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ede8a08c71ad5a95cdd0e4e52facd37190977039a4704eb82a283f713747d34" +checksum = "3176f18d11a1ae46053e59ec89d46ba318ae1343615bd3f8c908bfc84edae35c" dependencies = [ - "anyhow", "byteorder", - "paste", - "thiserror 1.0.69", + "pastey 0.1.1", + "thiserror 2.0.17", ] [[package]] name = "netlink-packet-wireguard" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b25b050ff1f6a1e23c6777b72db22790fe5b6b5ccfd3858672587a79876c8f" +checksum = "598962d9067d3153a00106da10e7b8276cea68f396f4a22f5b4a079270d92e29" dependencies = [ - "anyhow", - "byteorder", "libc", "log", + "netlink-packet-core", "netlink-packet-generic", - "netlink-packet-utils", ] [[package]] @@ -5006,11 +5120,10 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "cfg-if", "cfg_aliases", "libc", - "memoffset", ] [[package]] @@ -5019,10 +5132,11 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "cfg-if", "cfg_aliases", "libc", + "memoffset", ] [[package]] @@ -5073,9 +5187,9 @@ dependencies = [ [[package]] name = "ntapi" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +checksum = "c70f219e21142367c70c0b30c6a9e3a14d55b4d12a204d897fbec83a0363f081" dependencies = [ "winapi", ] @@ -5096,7 +5210,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -5111,11 +5225,10 @@ dependencies = [ [[package]] name = "num-bigint-dig" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" dependencies = [ - "byteorder", "lazy_static", "libm", "num-integer", @@ -5191,7 +5304,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -5205,7 +5318,7 @@ dependencies = [ [[package]] name = "nym-api" -version = "1.1.72" +version = "1.1.73" dependencies = [ "anyhow", "async-trait", @@ -5261,9 +5374,9 @@ dependencies = [ "pin-project", "rand 0.8.5", "rand_chacha 0.3.1", - "reqwest 0.12.22", + "reqwest 0.12.28", "schemars 0.8.22", - "semver 1.0.26", + "semver 1.0.27", "serde", "serde_json", "sha2 0.10.9", @@ -5271,7 +5384,7 @@ dependencies = [ "tempfile", "tendermint", "test-with", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-stream", @@ -5288,9 +5401,10 @@ dependencies = [ [[package]] name = "nym-api-requests" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bs58", + "celes", "cosmrs", "cosmwasm-std", "ecdsa", @@ -5304,20 +5418,24 @@ dependencies = [ "nym-crypto", "nym-ecash-signer-check-types", "nym-ecash-time", + "nym-kkt-ciphersuite", "nym-mixnet-contract-common", "nym-network-defaults", "nym-node-requests", "nym-noise-keys", "nym-serde-helpers", + "nym-test-utils", "nym-ticketbooks-merkle", "rand_chacha 0.3.1", "schemars 0.8.22", "serde", "serde_json", "sha2 0.10.9", + "strum", + "strum_macros", "tendermint", "tendermint-rpc", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tracing", "ts-rs", @@ -5326,7 +5444,7 @@ dependencies = [ [[package]] name = "nym-async-file-watcher" -version = "0.1.0" +version = "1.20.1" dependencies = [ "futures", "log", @@ -5336,7 +5454,7 @@ dependencies = [ [[package]] name = "nym-authenticator-client" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bincode", "futures", @@ -5349,8 +5467,8 @@ dependencies = [ "nym-service-provider-requests-common", "nym-validator-client", "nym-wireguard-types", - "semver 1.0.26", - "thiserror 2.0.12", + "semver 1.0.27", + "thiserror 2.0.17", "tokio", "tokio-util", "tracing", @@ -5358,7 +5476,7 @@ dependencies = [ [[package]] name = "nym-authenticator-requests" -version = "0.1.0" +version = "1.20.1" dependencies = [ "base64 0.22.1", "bincode", @@ -5371,18 +5489,18 @@ dependencies = [ "nym-test-utils", "nym-wireguard-types", "rand 0.8.5", - "semver 1.0.26", + "semver 1.0.27", "serde", "sha2 0.10.9", "strum_macros", - "thiserror 2.0.12", + "thiserror 2.0.17", "tracing", "x25519-dalek", ] [[package]] name = "nym-bandwidth-controller" -version = "0.1.0" +version = "1.20.1" dependencies = [ "async-trait", "log", @@ -5394,12 +5512,12 @@ dependencies = [ "nym-task", "nym-validator-client", "rand 0.8.5", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] name = "nym-bin-common" -version = "0.6.0" +version = "1.20.1" dependencies = [ "clap", "clap_complete", @@ -5419,16 +5537,33 @@ dependencies = [ "vergen 8.3.1", ] +[[package]] +name = "nym-bls12_381-fork" +version = "0.8.0-forked" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce84633751030f960a2fd167b5270ec21da4c40d9b6400e1b56676a682fe6f3d" +dependencies = [ + "digest 0.10.7", + "ff", + "group", + "pairing", + "rand_core 0.6.4", + "serde", + "serdect 0.3.0", + "subtle 2.6.1", + "zeroize", +] + [[package]] name = "nym-cache" -version = "0.1.0" +version = "1.20.1" dependencies = [ "tokio", ] [[package]] name = "nym-cli" -version = "1.1.69" +version = "1.1.70" dependencies = [ "anyhow", "base64 0.22.1", @@ -5452,7 +5587,7 @@ dependencies = [ [[package]] name = "nym-cli-commands" -version = "1.0.0" +version = "1.20.1" dependencies = [ "anyhow", "base64 0.22.1", @@ -5501,7 +5636,7 @@ dependencies = [ "serde_json", "tap", "tempfile", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "toml 0.8.23", @@ -5511,7 +5646,7 @@ dependencies = [ [[package]] name = "nym-client" -version = "1.1.69" +version = "1.1.70" dependencies = [ "bs58", "clap", @@ -5538,7 +5673,7 @@ dependencies = [ "serde", "serde_json", "tap", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-tungstenite", @@ -5548,7 +5683,7 @@ dependencies = [ [[package]] name = "nym-client-core" -version = "1.1.15" +version = "1.20.1" dependencies = [ "async-trait", "base64 0.22.1", @@ -5560,7 +5695,7 @@ dependencies = [ "gloo-timers", "http-body-util", "humantime", - "hyper 1.6.0", + "hyper 1.8.1", "hyper-util", "nym-bandwidth-controller", "nym-client-core-config-types", @@ -5584,6 +5719,7 @@ dependencies = [ "nym-task", "nym-topology", "nym-validator-client", + "nym-wasm-utils", "rand 0.8.5", "rand_chacha 0.3.1", "serde", @@ -5591,7 +5727,7 @@ dependencies = [ "sha2 0.10.9", "si-scale", "tempfile", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-stream", @@ -5602,14 +5738,13 @@ dependencies = [ "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-utils", "wasmtimer", "zeroize", ] [[package]] name = "nym-client-core-config-types" -version = "0.1.0" +version = "1.20.1" dependencies = [ "humantime-serde", "nym-config", @@ -5618,13 +5753,13 @@ dependencies = [ "nym-sphinx-params", "nym-statistics-common", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", "url", ] [[package]] name = "nym-client-core-gateways-storage" -version = "0.1.0" +version = "1.20.1" dependencies = [ "anyhow", "async-trait", @@ -5633,7 +5768,7 @@ dependencies = [ "nym-gateway-requests", "serde", "sqlx", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tracing", @@ -5643,17 +5778,17 @@ dependencies = [ [[package]] name = "nym-client-core-surb-storage" -version = "0.1.0" +version = "1.20.1" dependencies = [ "anyhow", "async-trait", "dashmap", "nym-crypto", "nym-sphinx", + "nym-sqlx-pool-guard", "nym-task", "sqlx", - "sqlx-pool-guard", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tracing", @@ -5671,25 +5806,25 @@ dependencies = [ "nym-gateway-requests", "nym-node-tester-utils", "nym-node-tester-wasm", + "nym-wasm-client-core", + "nym-wasm-utils", "once_cell", "rand 0.8.5", "serde", "serde-wasm-bindgen 0.6.5", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio_with_wasm", "tsify", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test", - "wasm-client-core", - "wasm-utils", "web-sys", ] [[package]] name = "nym-client-websocket-requests" -version = "0.1.0" +version = "1.20.1" dependencies = [ "nym-sphinx", "serde", @@ -5698,7 +5833,7 @@ dependencies = [ [[package]] name = "nym-coconut-dkg-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -5711,7 +5846,7 @@ dependencies = [ [[package]] name = "nym-common" -version = "1.18.0" +version = "1.20.1" dependencies = [ "tracing", "tracing-test", @@ -5719,10 +5854,9 @@ dependencies = [ [[package]] name = "nym-compact-ecash" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bincode", - "bls12_381", "bs58", "cfg-if", "criterion", @@ -5730,6 +5864,7 @@ dependencies = [ "ff", "group", "itertools 0.14.0", + "nym-bls12_381-fork", "nym-network-defaults", "nym-pemstore", "rand 0.8.5", @@ -5737,20 +5872,20 @@ dependencies = [ "serde", "sha2 0.10.9", "subtle 2.6.1", - "thiserror 2.0.12", + "thiserror 2.0.17", "zeroize", ] [[package]] name = "nym-config" -version = "0.1.0" +version = "1.20.1" dependencies = [ "dirs", "handlebars", "log", "nym-network-defaults", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", "toml 0.8.23", "url", ] @@ -5767,7 +5902,7 @@ dependencies = [ "nym-ip-packet-requests", "nym-sdk", "pnet_packet", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tokio-util", "tracing", @@ -5775,7 +5910,7 @@ dependencies = [ [[package]] name = "nym-contracts-common" -version = "0.5.0" +version = "1.20.1" dependencies = [ "anyhow", "bs58", @@ -5785,14 +5920,14 @@ dependencies = [ "schemars 0.8.22", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.17", "utoipa", "vergen 8.3.1", ] [[package]] name = "nym-contracts-common-testing" -version = "0.1.0" +version = "1.20.1" dependencies = [ "anyhow", "cosmwasm-std", @@ -5806,7 +5941,7 @@ dependencies = [ [[package]] name = "nym-cpp-ffi" -version = "0.1.2" +version = "1.20.1" dependencies = [ "anyhow", "bs58", @@ -5847,14 +5982,14 @@ dependencies = [ "nym-upgrade-mode-check", "nym-validator-client", "rand 0.8.5", - "reqwest 0.12.22", + "reqwest 0.12.28", "serde", "serde_json", "sqlx", "strum", "strum_macros", "tempfile", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-util", @@ -5869,7 +6004,7 @@ dependencies = [ [[package]] name = "nym-credential-proxy-lib" -version = "0.1.0" +version = "1.20.1" dependencies = [ "anyhow", "axum", @@ -5888,14 +6023,14 @@ dependencies = [ "nym-network-defaults", "nym-validator-client", "rand 0.8.5", - "reqwest 0.12.22", + "reqwest 0.12.28", "serde", "serde_json", "sqlx", "strum", "strum_macros", "tempfile", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-util", @@ -5907,7 +6042,7 @@ dependencies = [ [[package]] name = "nym-credential-proxy-requests" -version = "0.1.0" +version = "1.20.1" dependencies = [ "async-trait", "nym-credentials", @@ -5916,7 +6051,7 @@ dependencies = [ "nym-http-api-common", "nym-serde-helpers", "nym-upgrade-mode-check", - "reqwest 0.12.22", + "reqwest 0.12.28", "schemars 0.8.22", "serde", "serde_json", @@ -5931,7 +6066,7 @@ dependencies = [ [[package]] name = "nym-credential-storage" -version = "0.1.0" +version = "1.20.1" dependencies = [ "anyhow", "async-trait", @@ -5942,11 +6077,11 @@ dependencies = [ "nym-credentials-interface", "nym-crypto", "nym-ecash-time", + "nym-sqlx-pool-guard", "nym-test-utils", "serde", "sqlx", - "sqlx-pool-guard", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "zeroize", @@ -5954,7 +6089,7 @@ dependencies = [ [[package]] name = "nym-credential-utils" -version = "0.1.0" +version = "1.20.1" dependencies = [ "log", "nym-bandwidth-controller", @@ -5965,14 +6100,14 @@ dependencies = [ "nym-credentials-interface", "nym-ecash-time", "nym-validator-client", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", ] [[package]] name = "nym-credential-verification" -version = "0.1.0" +version = "1.20.1" dependencies = [ "async-trait", "bs58", @@ -5992,7 +6127,7 @@ dependencies = [ "nym-upgrade-mode-check", "nym-validator-client", "si-scale", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tracing", @@ -6000,13 +6135,13 @@ dependencies = [ [[package]] name = "nym-credentials" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bincode", - "bls12_381", "cosmrs", "log", "nym-api-requests", + "nym-bls12_381-fork", "nym-credentials-interface", "nym-crypto", "nym-ecash-contract-common", @@ -6017,16 +6152,16 @@ dependencies = [ "nym-validator-client", "rand 0.8.5", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "zeroize", ] [[package]] name = "nym-credentials-interface" -version = "0.1.0" +version = "1.20.1" dependencies = [ - "bls12_381", + "nym-bls12_381-fork", "nym-compact-ecash", "nym-ecash-time", "nym-network-defaults", @@ -6035,23 +6170,23 @@ dependencies = [ "serde", "strum", "strum_macros", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "utoipa", ] [[package]] name = "nym-crypto" -version = "0.4.0" +version = "1.20.1" dependencies = [ "aead", - "aes", + "aes 0.8.4", "aes-gcm-siv", "anyhow", "base64 0.22.1", "blake3", "bs58", - "cipher", + "cipher 0.4.4", "ctr", "curve25519-dalek", "digest 0.10.7", @@ -6070,7 +6205,7 @@ dependencies = [ "serde_json", "sha2 0.10.9", "subtle-encoding", - "thiserror 2.0.12", + "thiserror 2.0.17", "x25519-dalek", "zeroize", ] @@ -6094,12 +6229,12 @@ dependencies = [ "nym-validator-client", "nyxd-scraper-psql", "nyxd-scraper-shared", - "reqwest 0.12.22", + "reqwest 0.12.28", "schemars 0.8.22", "serde", "serde_json", "sqlx", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-util", @@ -6114,14 +6249,14 @@ dependencies = [ [[package]] name = "nym-dkg" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bitvec", - "bls12_381", "bs58", "criterion", "ff", "group", + "nym-bls12_381-fork", "nym-contracts-common", "nym-pemstore", "rand 0.8.5", @@ -6130,13 +6265,13 @@ dependencies = [ "serde", "serde_derive", "sha2 0.10.9", - "thiserror 2.0.12", + "thiserror 2.0.17", "zeroize", ] [[package]] name = "nym-ecash-contract-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bs58", "cosmwasm-schema", @@ -6145,20 +6280,20 @@ dependencies = [ "cw-utils", "cw2", "nym-multisig-contract-common", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] name = "nym-ecash-signer-check" -version = "0.1.0" +version = "1.20.1" dependencies = [ "futures", "nym-ecash-signer-check-types", "nym-http-api-client", "nym-network-defaults", "nym-validator-client", - "semver 1.0.26", - "thiserror 2.0.12", + "semver 1.0.27", + "thiserror 2.0.17", "tokio", "tracing", "url", @@ -6166,13 +6301,13 @@ dependencies = [ [[package]] name = "nym-ecash-signer-check-types" -version = "0.1.0" +version = "1.20.1" dependencies = [ "nym-coconut-dkg-common", "nym-crypto", - "semver 1.0.26", + "semver 1.0.27", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tracing", "url", @@ -6181,7 +6316,7 @@ dependencies = [ [[package]] name = "nym-ecash-time" -version = "0.1.0" +version = "1.20.1" dependencies = [ "nym-compact-ecash", "time", @@ -6189,19 +6324,19 @@ dependencies = [ [[package]] name = "nym-exit-policy" -version = "0.1.0" +version = "1.20.1" dependencies = [ - "reqwest 0.12.22", + "reqwest 0.12.28", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.17", "tracing", "utoipa", ] [[package]] name = "nym-ffi-shared" -version = "0.2.1" +version = "1.20.1" dependencies = [ "anyhow", "bs58", @@ -6211,14 +6346,15 @@ dependencies = [ "nym-sdk", "nym-sphinx-anonymous-replies", "tokio", - "uniffi", - "uniffi_build", + "uniffi 0.29.5", + "uniffi_build 0.29.5", ] [[package]] name = "nym-gateway" version = "1.1.36" dependencies = [ + "anyhow", "async-trait", "bincode", "bip39", @@ -6257,6 +6393,7 @@ dependencies = [ "nym-sphinx", "nym-statistics-common", "nym-task", + "nym-test-utils", "nym-topology", "nym-upgrade-mode-check", "nym-validator-client", @@ -6265,7 +6402,7 @@ dependencies = [ "nym-wireguard-types", "rand 0.8.5", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-stream", @@ -6277,7 +6414,7 @@ dependencies = [ [[package]] name = "nym-gateway-client" -version = "0.1.0" +version = "1.20.1" dependencies = [ "futures", "getrandom 0.2.16", @@ -6295,10 +6432,11 @@ dependencies = [ "nym-statistics-common", "nym-task", "nym-validator-client", + "nym-wasm-utils", "rand 0.8.5", "serde", "si-scale", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-stream", @@ -6308,7 +6446,6 @@ dependencies = [ "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-utils", "wasmtimer", "zeroize", ] @@ -6319,8 +6456,6 @@ version = "1.18.0" dependencies = [ "anyhow", "base64 0.22.1", - "bincode", - "bs58", "bytes", "clap", "futures", @@ -6341,8 +6476,8 @@ dependencies = [ "nym-http-api-client-macro", "nym-ip-packet-client", "nym-ip-packet-requests", + "nym-kkt-ciphersuite", "nym-lp", - "nym-mixnet-contract-common", "nym-network-defaults", "nym-node-requests", "nym-node-status-client", @@ -6353,22 +6488,22 @@ dependencies = [ "nym-validator-client", "pnet_packet", "rand 0.8.5", + "reqwest 0.12.28", "serde", "serde_json", - "thiserror 2.0.12", "time", "tokio", "tokio-util", "tracing", "tracing-subscriber", "url", + "utoipa", "vergen-gitcl", - "x25519-dalek", ] [[package]] name = "nym-gateway-requests" -version = "0.1.0" +version = "1.20.1" dependencies = [ "anyhow", "bs58", @@ -6389,7 +6524,7 @@ dependencies = [ "serde_json", "strum", "subtle 2.6.1", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tracing", @@ -6400,7 +6535,7 @@ dependencies = [ [[package]] name = "nym-gateway-stats-storage" -version = "0.1.0" +version = "1.20.1" dependencies = [ "anyhow", "nym-node-metrics", @@ -6408,7 +6543,7 @@ dependencies = [ "nym-statistics-common", "sqlx", "strum", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tracing", @@ -6416,7 +6551,7 @@ dependencies = [ [[package]] name = "nym-gateway-storage" -version = "0.1.0" +version = "1.20.1" dependencies = [ "anyhow", "async-trait", @@ -6427,7 +6562,7 @@ dependencies = [ "nym-gateway-requests", "nym-sphinx", "sqlx", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tracing", @@ -6435,7 +6570,7 @@ dependencies = [ [[package]] name = "nym-go-ffi" -version = "0.2.2" +version = "1.20.1" dependencies = [ "anyhow", "lazy_static", @@ -6444,15 +6579,15 @@ dependencies = [ "nym-ffi-shared", "nym-sdk", "nym-sphinx-anonymous-replies", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", - "uniffi", - "uniffi_build", + "uniffi 0.29.5", + "uniffi_build 0.29.5", ] [[package]] name = "nym-group-contract-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "cosmwasm-schema", "cw-controllers", @@ -6463,7 +6598,7 @@ dependencies = [ [[package]] name = "nym-http-api-client" -version = "0.1.0" +version = "1.20.1" dependencies = [ "async-trait", "bincode", @@ -6471,7 +6606,7 @@ dependencies = [ "cfg-if", "encoding_rs", "hickory-resolver", - "http 1.3.1", + "http 1.4.0", "inventory", "itertools 0.14.0", "mime", @@ -6480,12 +6615,12 @@ dependencies = [ "nym-http-api-common", "nym-network-defaults", "once_cell", - "reqwest 0.12.22", + "reqwest 0.12.28", "serde", "serde_json", "serde_plain", "serde_yaml", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tracing", "tracing-subscriber", @@ -6495,20 +6630,20 @@ dependencies = [ [[package]] name = "nym-http-api-client-macro" -version = "0.1.0" +version = "1.20.1" dependencies = [ "nym-http-api-client", "proc-macro-crate", "proc-macro2", "quote", - "reqwest 0.12.22", - "syn 2.0.106", + "reqwest 0.12.28", + "syn 2.0.114", "uuid", ] [[package]] name = "nym-http-api-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "axum", "axum-client-ip", @@ -6530,11 +6665,11 @@ dependencies = [ [[package]] name = "nym-id" -version = "0.1.0" +version = "1.20.1" dependencies = [ "nym-credential-storage", "nym-credentials", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tracing", "zeroize", @@ -6556,23 +6691,23 @@ dependencies = [ [[package]] name = "nym-inclusion-probability" -version = "0.1.0" +version = "1.20.1" dependencies = [ "log", "rand 0.8.5", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] name = "nym-ip-packet-client" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bincode", "bytes", "futures", "nym-ip-packet-requests", "nym-sdk", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tokio-util", "tracing", @@ -6580,7 +6715,7 @@ dependencies = [ [[package]] name = "nym-ip-packet-requests" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bincode", "bytes", @@ -6590,7 +6725,7 @@ dependencies = [ "nym-sphinx", "rand 0.8.5", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-util", @@ -6598,7 +6733,7 @@ dependencies = [ [[package]] name = "nym-ip-packet-router" -version = "0.1.0" +version = "1.20.1" dependencies = [ "anyhow", "async-trait", @@ -6629,10 +6764,10 @@ dependencies = [ "nym-wireguard", "nym-wireguard-types", "rand 0.8.5", - "reqwest 0.12.22", + "reqwest 0.12.28", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-tun", @@ -6648,7 +6783,7 @@ dependencies = [ "bytes", "env_logger", "log", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio-util", ] @@ -6656,37 +6791,50 @@ dependencies = [ name = "nym-kkt" version = "0.1.0" dependencies = [ + "anyhow", "blake3", "classic-mceliece-rust", "criterion", + "libcrux-chacha20poly1305", "libcrux-ecdh", "libcrux-kem", - "libcrux-ml-kem", - "libcrux-psq", - "libcrux-sha3", - "libcrux-traits", + "num_enum", "nym-crypto", + "nym-kkt-ciphersuite", "rand 0.9.2", - "thiserror 2.0.12", + "rand_chacha 0.9.0", + "strum", + "thiserror 2.0.17", + "zeroize", +] + +[[package]] +name = "nym-kkt-ciphersuite" +version = "1.20.1" +dependencies = [ + "blake3", + "libcrux-sha3", + "num_enum", + "strum", + "strum_macros", + "thiserror 2.0.17", ] [[package]] name = "nym-ledger" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bip32", "k256", "ledger-transport", "ledger-transport-hid", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] name = "nym-lp" version = "0.1.0" dependencies = [ - "ansi_term", - "bincode", "bs58", "bytes", "chacha20poly1305", @@ -6699,7 +6847,7 @@ dependencies = [ "nym-crypto", "nym-kkt", "nym-lp-common", - "nym-sphinx", + "nym-test-utils", "parking_lot", "rand 0.8.5", "rand 0.9.2", @@ -6707,10 +6855,9 @@ dependencies = [ "serde", "sha2 0.10.9", "snow", - "thiserror 2.0.12", + "thiserror 2.0.17", "tls_codec", "tracing", - "utoipa", "zeroize", ] @@ -6726,6 +6873,7 @@ dependencies = [ "nym-http-api-client", "nym-ip-packet-requests", "nym-kcp", + "nym-kkt-ciphersuite", "nym-lp", "nym-registration-client", "nym-sphinx", @@ -6758,11 +6906,12 @@ version = "0.1.0" dependencies = [ "nym-test-utils", "tokio", + "tracing", ] [[package]] name = "nym-metrics" -version = "0.1.0" +version = "1.20.1" dependencies = [ "dashmap", "lazy_static", @@ -6772,7 +6921,7 @@ dependencies = [ [[package]] name = "nym-mixnet-client" -version = "0.1.0" +version = "1.20.1" dependencies = [ "dashmap", "futures", @@ -6789,7 +6938,7 @@ dependencies = [ [[package]] name = "nym-mixnet-contract-common" -version = "0.6.0" +version = "1.20.1" dependencies = [ "bs58", "cosmwasm-schema", @@ -6801,10 +6950,10 @@ dependencies = [ "nym-contracts-common", "rand_chacha 0.3.1", "schemars 0.8.22", - "semver 1.0.26", + "semver 1.0.27", "serde", "serde_repr", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "ts-rs", "utoipa", @@ -6812,7 +6961,7 @@ dependencies = [ [[package]] name = "nym-mixnode-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bytes", "futures", @@ -6830,7 +6979,7 @@ dependencies = [ "nym-task", "rand 0.8.5", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-util", @@ -6839,7 +6988,7 @@ dependencies = [ [[package]] name = "nym-multisig-contract-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -6849,12 +6998,12 @@ dependencies = [ "cw4", "schemars 0.8.22", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] name = "nym-network-defaults" -version = "0.1.0" +version = "1.20.1" dependencies = [ "cargo_metadata 0.19.2", "dotenvy", @@ -6893,7 +7042,7 @@ dependencies = [ "petgraph", "rand 0.8.5", "rand_chacha 0.3.1", - "reqwest 0.12.22", + "reqwest 0.12.28", "serde", "serde_json", "tokio", @@ -6905,7 +7054,7 @@ dependencies = [ [[package]] name = "nym-network-requester" -version = "1.1.70" +version = "1.1.71" dependencies = [ "addr", "anyhow", @@ -6939,13 +7088,13 @@ dependencies = [ "publicsuffix", "rand 0.8.5", "regex", - "reqwest 0.12.22", + "reqwest 0.12.28", "serde", "serde_json", "sqlx", "tap", "tempfile", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-tungstenite", @@ -6955,7 +7104,7 @@ dependencies = [ [[package]] name = "nym-node" -version = "1.24.0" +version = "1.25.0" dependencies = [ "anyhow", "arc-swap", @@ -6977,6 +7126,7 @@ dependencies = [ "csv", "cupid", "futures", + "hex", "hkdf", "human-repr", "humantime-serde", @@ -6993,6 +7143,7 @@ dependencies = [ "nym-http-api-client", "nym-http-api-common", "nym-ip-packet-router", + "nym-kkt", "nym-metrics", "nym-mixnet-client", "nym-network-requester", @@ -7023,7 +7174,7 @@ dependencies = [ "serde_json", "sha2 0.10.9", "sysinfo", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-stream", @@ -7041,7 +7192,7 @@ dependencies = [ [[package]] name = "nym-node-metrics" -version = "0.1.0" +version = "1.20.1" dependencies = [ "dashmap", "futures", @@ -7055,7 +7206,7 @@ dependencies = [ [[package]] name = "nym-node-requests" -version = "0.1.0" +version = "1.20.1" dependencies = [ "async-trait", "celes", @@ -7064,6 +7215,7 @@ dependencies = [ "nym-crypto", "nym-exit-policy", "nym-http-api-client", + "nym-kkt-ciphersuite", "nym-noise-keys", "nym-upgrade-mode-check", "nym-wireguard-types", @@ -7073,7 +7225,7 @@ dependencies = [ "serde_json", "strum", "strum_macros", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "url", @@ -7082,7 +7234,7 @@ dependencies = [ [[package]] name = "nym-node-status-agent" -version = "1.0.7" +version = "1.1.2" dependencies = [ "anyhow", "clap", @@ -7091,6 +7243,8 @@ dependencies = [ "nym-crypto", "nym-node-status-client", "rand 0.8.5", + "regex", + "serde_json", "tempfile", "tokio", "tracing", @@ -7099,7 +7253,7 @@ dependencies = [ [[package]] name = "nym-node-status-api" -version = "4.0.12" +version = "4.0.14" dependencies = [ "ammonia", "anyhow", @@ -7120,6 +7274,7 @@ dependencies = [ "nym-credentials", "nym-crypto", "nym-ecash-time", + "nym-gateway-probe", "nym-http-api-client", "nym-http-api-common", "nym-mixnet-contract-common", @@ -7134,15 +7289,15 @@ dependencies = [ "rand 0.8.5", "rand_chacha 0.3.1", "regex", - "reqwest 0.12.22", - "semver 1.0.26", + "reqwest 0.12.28", + "semver 1.0.27", "serde", "serde_json", "serde_json_path", "sqlx", "strum", "strum_macros", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-stream", @@ -7166,7 +7321,7 @@ dependencies = [ "bs58", "nym-credentials", "nym-crypto", - "reqwest 0.12.22", + "reqwest 0.12.28", "serde", "serde_json", "time", @@ -7175,7 +7330,7 @@ dependencies = [ [[package]] name = "nym-node-tester-utils" -version = "0.1.0" +version = "1.20.1" dependencies = [ "futures", "log", @@ -7184,13 +7339,13 @@ dependencies = [ "nym-sphinx-params", "nym-task", "nym-topology", + "nym-wasm-utils", "rand 0.8.5", "rand_chacha 0.3.1", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", - "wasm-utils", ] [[package]] @@ -7200,22 +7355,22 @@ dependencies = [ "futures", "js-sys", "nym-node-tester-utils", + "nym-wasm-client-core", + "nym-wasm-utils", "rand 0.8.5", "serde", "serde-wasm-bindgen 0.6.5", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tsify", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-client-core", - "wasm-utils", "wasmtimer", ] [[package]] name = "nym-noise" -version = "0.1.0" +version = "1.20.1" dependencies = [ "anyhow", "arc-swap", @@ -7230,7 +7385,7 @@ dependencies = [ "snow", "strum", "strum_macros", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tokio-util", "tracing", @@ -7238,7 +7393,7 @@ dependencies = [ [[package]] name = "nym-noise-keys" -version = "0.1.0" +version = "1.20.1" dependencies = [ "nym-crypto", "schemars 0.8.22", @@ -7248,7 +7403,7 @@ dependencies = [ [[package]] name = "nym-nonexhaustive-delayqueue" -version = "0.1.0" +version = "1.20.1" dependencies = [ "tokio", "tokio-stream", @@ -7274,15 +7429,15 @@ dependencies = [ [[package]] name = "nym-ordered-buffer" -version = "0.1.0" +version = "1.20.1" dependencies = [ "log", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] name = "nym-outfox" -version = "0.1.0" +version = "1.20.1" dependencies = [ "blake3", "chacha20", @@ -7294,14 +7449,14 @@ dependencies = [ "rand 0.8.5", "rayon", "sphinx-packet", - "thiserror 2.0.12", + "thiserror 2.0.17", "x25519-dalek", "zeroize", ] [[package]] name = "nym-pemstore" -version = "0.3.0" +version = "1.20.1" dependencies = [ "pem", "tracing", @@ -7310,7 +7465,7 @@ dependencies = [ [[package]] name = "nym-performance-contract-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -7318,25 +7473,25 @@ dependencies = [ "nym-contracts-common", "schemars 0.8.22", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] name = "nym-pool-contract-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-controllers", "schemars 0.8.22", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", ] [[package]] name = "nym-registration-client" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bytes", "futures", @@ -7353,7 +7508,7 @@ dependencies = [ "nym-validator-client", "nym-wireguard-types", "rand 0.8.5", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tokio-util", "tracing", @@ -7363,23 +7518,24 @@ dependencies = [ [[package]] name = "nym-registration-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bincode", "nym-authenticator-requests", "nym-credentials-interface", "nym-crypto", "nym-ip-packet-requests", + "nym-kkt-ciphersuite", "nym-sphinx", + "nym-test-utils", "nym-wireguard-types", "serde", - "time", - "tokio-util", + "tracing", ] [[package]] name = "nym-sdk" -version = "0.1.0" +version = "1.20.1" dependencies = [ "anyhow", "async-trait", @@ -7393,7 +7549,7 @@ dependencies = [ "dotenvy", "futures", "hex", - "http 1.3.1", + "http 1.4.0", "httpcodec", "log", "nym-bandwidth-controller", @@ -7419,11 +7575,11 @@ dependencies = [ "nym-validator-client", "parking_lot", "rand 0.8.5", - "reqwest 0.12.22", + "reqwest 0.12.28", "serde", "tap", "tempfile", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-stream", @@ -7438,7 +7594,7 @@ dependencies = [ [[package]] name = "nym-serde-helpers" -version = "0.1.0" +version = "1.20.1" dependencies = [ "base64 0.22.1", "bs58", @@ -7449,16 +7605,16 @@ dependencies = [ [[package]] name = "nym-service-provider-requests-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bincode", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] name = "nym-service-providers-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "anyhow", "async-trait", @@ -7469,7 +7625,7 @@ dependencies = [ "nym-sphinx-anonymous-replies", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", ] @@ -7495,7 +7651,7 @@ dependencies = [ [[package]] name = "nym-socks5-client" -version = "1.1.69" +version = "1.1.70" dependencies = [ "bs58", "clap", @@ -7519,7 +7675,7 @@ dependencies = [ "serde", "serde_json", "tap", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "url", @@ -7528,7 +7684,7 @@ dependencies = [ [[package]] name = "nym-socks5-client-core" -version = "0.1.0" +version = "1.20.1" dependencies = [ "anyhow", "dirs", @@ -7549,18 +7705,18 @@ dependencies = [ "nym-validator-client", "pin-project", "rand 0.8.5", - "reqwest 0.12.22", + "reqwest 0.12.28", "schemars 0.8.22", "serde", "tap", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "url", ] [[package]] name = "nym-socks5-proxy-helpers" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bytes", "futures", @@ -7575,7 +7731,7 @@ dependencies = [ [[package]] name = "nym-socks5-requests" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bincode", "log", @@ -7585,12 +7741,12 @@ dependencies = [ "serde", "serde_json", "tap", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] name = "nym-sphinx" -version = "0.1.0" +version = "1.20.1" dependencies = [ "nym-crypto", "nym-metrics", @@ -7609,14 +7765,14 @@ dependencies = [ "rand 0.8.5", "rand_chacha 0.3.1", "rand_distr", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tracing", ] [[package]] name = "nym-sphinx-acknowledgements" -version = "0.1.0" +version = "1.20.1" dependencies = [ "generic-array 0.14.7", "nym-crypto", @@ -7628,13 +7784,13 @@ dependencies = [ "nym-topology", "rand 0.8.5", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", "zeroize", ] [[package]] name = "nym-sphinx-addressing" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bincode", "nym-crypto", @@ -7642,12 +7798,12 @@ dependencies = [ "rand 0.8.5", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] name = "nym-sphinx-anonymous-replies" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bs58", "nym-crypto", @@ -7658,14 +7814,14 @@ dependencies = [ "nym-topology", "rand 0.8.5", "rand_chacha 0.3.1", - "thiserror 2.0.12", + "thiserror 2.0.17", "tracing", "wasm-bindgen", ] [[package]] name = "nym-sphinx-chunking" -version = "0.1.0" +version = "1.20.1" dependencies = [ "dashmap", "log", @@ -7676,14 +7832,14 @@ dependencies = [ "nym-sphinx-types", "rand 0.8.5", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", "utoipa", "wasmtimer", ] [[package]] name = "nym-sphinx-cover" -version = "0.1.0" +version = "1.20.1" dependencies = [ "nym-crypto", "nym-sphinx-acknowledgements", @@ -7695,23 +7851,23 @@ dependencies = [ "nym-sphinx-types", "nym-topology", "rand 0.8.5", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] name = "nym-sphinx-forwarding" -version = "0.1.0" +version = "1.20.1" dependencies = [ "nym-sphinx-addressing", "nym-sphinx-anonymous-replies", "nym-sphinx-params", "nym-sphinx-types", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] name = "nym-sphinx-framing" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bytes", "cfg-if", @@ -7720,7 +7876,7 @@ dependencies = [ "nym-sphinx-forwarding", "nym-sphinx-params", "nym-sphinx-types", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tokio-util", "tracing", @@ -7728,30 +7884,43 @@ dependencies = [ [[package]] name = "nym-sphinx-params" -version = "0.1.0" +version = "1.20.1" dependencies = [ "nym-crypto", "nym-sphinx-types", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] name = "nym-sphinx-routing" -version = "0.1.0" +version = "1.20.1" dependencies = [ "nym-sphinx-addressing", "nym-sphinx-types", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] name = "nym-sphinx-types" -version = "0.2.0" +version = "1.20.1" dependencies = [ "nym-outfox", "sphinx-packet", - "thiserror 2.0.12", + "thiserror 2.0.17", +] + +[[package]] +name = "nym-sqlx-pool-guard" +version = "1.20.1" +dependencies = [ + "proc_pidinfo", + "sqlx", + "tempfile", + "tokio", + "tracing", + "tracing-subscriber", + "windows 0.61.3", ] [[package]] @@ -7786,7 +7955,7 @@ dependencies = [ [[package]] name = "nym-statistics-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "futures", "log", @@ -7802,7 +7971,7 @@ dependencies = [ "strum", "strum_macros", "sysinfo", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "utoipa", @@ -7811,7 +7980,7 @@ dependencies = [ [[package]] name = "nym-store-cipher" -version = "0.1.0" +version = "1.20.1" dependencies = [ "aes-gcm", "argon2", @@ -7820,20 +7989,20 @@ dependencies = [ "rand 0.8.5", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.17", "zeroize", ] [[package]] name = "nym-task" -version = "0.1.0" +version = "1.20.1" dependencies = [ "anyhow", "cfg-if", "futures", "log", "nym-test-utils", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tokio-util", "tracing", @@ -7844,7 +8013,7 @@ dependencies = [ [[package]] name = "nym-test-utils" -version = "0.1.0" +version = "1.20.1" dependencies = [ "anyhow", "futures", @@ -7856,7 +8025,7 @@ dependencies = [ [[package]] name = "nym-ticketbooks-merkle" -version = "0.1.0" +version = "1.20.1" dependencies = [ "nym-credentials-interface", "nym-serde-helpers", @@ -7873,7 +8042,7 @@ dependencies = [ [[package]] name = "nym-topology" -version = "0.1.0" +version = "1.20.1" dependencies = [ "async-trait", "nym-api-requests", @@ -7881,33 +8050,33 @@ dependencies = [ "nym-mixnet-contract-common", "nym-sphinx-addressing", "nym-sphinx-types", + "nym-wasm-utils", "rand 0.8.5", - "reqwest 0.12.22", + "reqwest 0.12.28", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tracing", "tsify", "wasm-bindgen", - "wasm-utils", ] [[package]] name = "nym-tun" -version = "0.1.0" +version = "1.20.1" dependencies = [ "etherparse", "log", "nym-wireguard-types", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tokio-tun", ] [[package]] name = "nym-types" -version = "1.0.0" +version = "1.20.1" dependencies = [ "base64 0.22.1", "cosmrs", @@ -7921,7 +8090,7 @@ dependencies = [ "nym-mixnet-contract-common", "nym-validator-client", "nym-vesting-contract-common", - "reqwest 0.12.22", + "reqwest 0.12.28", "schemars 0.8.22", "serde", "serde_json", @@ -7929,7 +8098,7 @@ dependencies = [ "strum", "strum_macros", "tempfile", - "thiserror 2.0.12", + "thiserror 2.0.17", "ts-rs", "url", "utoipa", @@ -7938,17 +8107,17 @@ dependencies = [ [[package]] name = "nym-upgrade-mode-check" -version = "0.1.0" +version = "1.20.1" dependencies = [ "anyhow", "jwt-simple", "nym-crypto", "nym-http-api-client", "nym-test-utils", - "reqwest 0.12.22", + "reqwest 0.12.28", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tracing", "utoipa", @@ -7956,7 +8125,7 @@ dependencies = [ [[package]] name = "nym-validator-client" -version = "0.1.0" +version = "1.20.1" dependencies = [ "anyhow", "async-trait", @@ -7990,12 +8159,12 @@ dependencies = [ "nym-serde-helpers", "nym-vesting-contract-common", "prost", - "reqwest 0.12.22", + "reqwest 0.12.28", "serde", "serde_json", "sha2 0.10.9", "tendermint-rpc", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tracing", @@ -8040,7 +8209,7 @@ dependencies = [ "serde_with", "sha2 0.10.9", "sqlx", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tracing", @@ -8050,7 +8219,7 @@ dependencies = [ [[package]] name = "nym-verloc" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bytes", "futures", @@ -8061,7 +8230,7 @@ dependencies = [ "nym-task", "nym-validator-client", "rand 0.8.5", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-util", @@ -8071,7 +8240,7 @@ dependencies = [ [[package]] name = "nym-vesting-contract-common" -version = "0.7.0" +version = "1.20.1" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -8079,13 +8248,13 @@ dependencies = [ "nym-contracts-common", "nym-mixnet-contract-common", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", "ts-rs", ] [[package]] name = "nym-vpn-api-lib-wasm" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bs58", "getrandom 0.2.16", @@ -8097,14 +8266,14 @@ dependencies = [ "nym-credentials-interface", "nym-crypto", "nym-ecash-time", + "nym-wasm-utils", "serde", "serde-wasm-bindgen 0.6.5", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tsify", "wasm-bindgen", - "wasm-utils", "zeroize", ] @@ -8129,27 +8298,96 @@ dependencies = [ ] [[package]] -name = "nym-wireguard" -version = "0.1.0" +name = "nym-wasm-client-core" +version = "1.20.1" dependencies = [ + "async-trait", + "console_error_panic_hook", + "js-sys", + "nym-bandwidth-controller", + "nym-client-core", + "nym-config", + "nym-credential-storage", + "nym-crypto", + "nym-gateway-client", + "nym-http-api-client", + "nym-sphinx", + "nym-sphinx-acknowledgements", + "nym-statistics-common", + "nym-task", + "nym-topology", + "nym-validator-client", + "nym-wasm-storage", + "nym-wasm-utils", + "rand 0.8.5", + "serde", + "serde-wasm-bindgen 0.6.5", + "thiserror 2.0.17", + "time", + "tsify", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "zeroize", +] + +[[package]] +name = "nym-wasm-storage" +version = "1.20.1" +dependencies = [ + "async-trait", + "getrandom 0.2.16", + "indexed_db_futures", + "js-sys", + "nym-store-cipher", + "nym-wasm-utils", + "serde", + "serde-wasm-bindgen 0.6.5", + "thiserror 2.0.17", + "wasm-bindgen", +] + +[[package]] +name = "nym-wasm-utils" +version = "1.20.1" +dependencies = [ + "console_error_panic_hook", + "futures", + "getrandom 0.2.16", + "gloo-net", + "gloo-utils 0.2.0", + "js-sys", + "tungstenite 0.20.1", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "nym-wireguard" +version = "1.20.1" +dependencies = [ + "anyhow", "base64 0.22.1", "defguard_wireguard_rs", "futures", "ip_network", "ipnetwork", + "mock_instant", + "nym-authenticator-requests", "nym-credential-verification", "nym-credentials-interface", "nym-crypto", "nym-gateway-requests", "nym-gateway-storage", - "nym-ip-packet-requests", "nym-metrics", "nym-network-defaults", "nym-node-metrics", "nym-task", + "nym-test-utils", "nym-wireguard-types", "rand 0.8.5", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tokio-stream", "tracing", @@ -8157,7 +8395,7 @@ dependencies = [ [[package]] name = "nym-wireguard-private-metadata-client" -version = "1.0.0" +version = "1.20.1" dependencies = [ "async-trait", "nym-http-api-client", @@ -8167,7 +8405,7 @@ dependencies = [ [[package]] name = "nym-wireguard-private-metadata-server" -version = "1.0.0" +version = "1.20.1" dependencies = [ "anyhow", "async-trait", @@ -8187,20 +8425,20 @@ dependencies = [ [[package]] name = "nym-wireguard-private-metadata-shared" -version = "1.0.0" +version = "1.20.1" dependencies = [ "axum", "bincode", "nym-credentials-interface", "schemars 0.8.22", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", "utoipa", ] [[package]] name = "nym-wireguard-private-metadata-tests" -version = "1.0.0" +version = "1.20.1" dependencies = [ "async-trait", "axum", @@ -8224,19 +8462,19 @@ dependencies = [ [[package]] name = "nym-wireguard-types" -version = "0.1.0" +version = "1.20.1" dependencies = [ "base64 0.22.1", "nym-crypto", "rand 0.8.5", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", "x25519-dalek", ] [[package]] name = "nymvisor" -version = "0.1.34" +version = "0.1.35" dependencies = [ "anyhow", "bytes", @@ -8252,12 +8490,12 @@ dependencies = [ "nym-bin-common", "nym-config", "nym-task", - "reqwest 0.12.22", + "reqwest 0.12.28", "serde", "serde_json", "sha2 0.10.9", "tar", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tracing", @@ -8279,11 +8517,11 @@ dependencies = [ "nym-task", "nym-validator-client", "nyxd-scraper-sqlite", - "reqwest 0.12.22", + "reqwest 0.12.28", "schemars 0.8.22", "serde", "sqlx", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-util", @@ -8307,14 +8545,14 @@ dependencies = [ "serde", "serde_json", "sqlx", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tracing", ] [[package]] name = "nyxd-scraper-shared" -version = "0.1.0" +version = "1.20.1" dependencies = [ "async-trait", "base64 0.22.1", @@ -8331,7 +8569,7 @@ dependencies = [ "sha2 0.10.9", "tendermint", "tendermint-rpc", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-stream", @@ -8348,39 +8586,30 @@ dependencies = [ "async-trait", "nyxd-scraper-shared", "sqlx", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tracing", ] [[package]] name = "objc2-core-foundation" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", ] [[package]] name = "objc2-io-kit" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71c1c64d6120e51cd86033f67176b1cb66780c2efe34dec55176f77befd93c0a" +checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" dependencies = [ "libc", "objc2-core-foundation", ] -[[package]] -name = "object" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" version = "1.21.3" @@ -8393,9 +8622,9 @@ dependencies = [ [[package]] name = "once_cell_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "oorandom" @@ -8423,9 +8652,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.110" +version = "0.9.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a9f0075ba3c21b09f8e8b2026584b1d18d49388648f2fbbf3c97ea8deced8e2" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" dependencies = [ "cc", "libc", @@ -8585,9 +8814,9 @@ checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -8595,15 +8824,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.11" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.18", "smallvec", - "windows-targets 0.52.6", + "windows-link 0.2.1", ] [[package]] @@ -8629,6 +8858,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" +[[package]] +name = "pastey" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b867cad97c0791bbd3aaa6472142568c6c9e8f71937e98379f584cfb0cf35bec" + [[package]] name = "peg" version = "0.8.5" @@ -8678,26 +8913,25 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.1" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" +checksum = "2c9eb05c21a464ea704b53158d358a31e6425db2f63a1a7312268b05fe2b75f7" dependencies = [ "memchr", - "thiserror 2.0.12", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.8.1" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb056d9e8ea77922845ec74a1c4e8fb17e7c218cc4fc11a15c5d25e189aa40bc" +checksum = "68f9dbced329c441fa79d80472764b1a2c7e57123553b8519b36663a2fb234ed" dependencies = [ "pest", "pest_generator", @@ -8705,22 +8939,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.1" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966" +checksum = "3bb96d5051a78f44f43c8f712d8e810adb0ebf923fc9ed2655a7f66f63ba8ee5" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "pest_meta" -version = "2.8.1" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5" +checksum = "602113b5b5e8621770cfd490cfd90b9f84ab29bd2b0e49ad83eb6d186cef2365" dependencies = [ "pest", "sha2 0.10.9", @@ -8733,7 +8967,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.10.0", + "indexmap 2.13.0", ] [[package]] @@ -8743,7 +8977,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ "phf_macros", - "phf_shared", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_shared 0.13.1", + "serde", ] [[package]] @@ -8753,7 +8997,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" dependencies = [ "phf_generator", - "phf_shared", + "phf_shared 0.11.3", ] [[package]] @@ -8762,7 +9006,7 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ - "phf_shared", + "phf_shared 0.11.3", "rand 0.8.5", ] @@ -8773,10 +9017,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" dependencies = [ "phf_generator", - "phf_shared", + "phf_shared 0.11.3", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -8788,6 +9032,15 @@ dependencies = [ "siphasher 1.0.1", ] +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher 1.0.1", +] + [[package]] name = "pin-project" version = "1.1.10" @@ -8805,7 +9058,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -8899,7 +9152,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -8964,9 +9217,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.11.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" [[package]] name = "portable-atomic-util" @@ -8979,9 +9232,9 @@ dependencies = [ [[package]] name = "postgres-protocol" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76ff0abab4a9b844b93ef7b81f1efc0a366062aaef2cd702c76256b5dc075c54" +checksum = "fbef655056b916eb868048276cfd5d6a7dea4f81560dfd047f97c8c6fe3fcfd4" dependencies = [ "base64 0.22.1", "byteorder", @@ -8997,9 +9250,9 @@ dependencies = [ [[package]] name = "postgres-types" -version = "0.2.9" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613283563cd90e1dfc3518d548caee47e0e725455ed619881f5cf21f36de4b48" +checksum = "ef4605b7c057056dd35baeb6ac0c0338e4975b1f2bef0f65da953285eb007095" dependencies = [ "bytes", "fallible-iterator", @@ -9008,9 +9261,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" dependencies = [ "zerovec", ] @@ -9057,11 +9310,11 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ - "toml_edit", + "toml_edit 0.23.10+spec-1.0.0", ] [[package]] @@ -9083,14 +9336,14 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" dependencies = [ "unicode-ident", ] @@ -9116,7 +9369,7 @@ dependencies = [ "memchr", "parking_lot", "protobuf", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -9139,7 +9392,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -9173,9 +9426,9 @@ dependencies = [ [[package]] name = "psl" -version = "2.1.126" +version = "2.1.176" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45f621acfbd2ca5670eee9a95270747dfa9a29e63e1937d7e6a1ac6994331966" +checksum = "5f06bb516febf92008f7e4ebdc1129a75e07905eadc4601af27093cb83928c78" dependencies = [ "psl-types", ] @@ -9204,9 +9457,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quinn" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" dependencies = [ "bytes", "cfg_aliases", @@ -9214,9 +9467,9 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls 0.23.29", - "socket2 0.5.10", - "thiserror 2.0.12", + "rustls 0.23.36", + "socket2 0.6.1", + "thiserror 2.0.17", "tokio", "tracing", "web-time", @@ -9224,20 +9477,20 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.12" +version = "0.11.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" dependencies = [ "bytes", - "getrandom 0.3.3", + "getrandom 0.3.4", "lru-slab", "rand 0.9.2", "ring", "rustc-hash", - "rustls 0.23.29", + "rustls 0.23.36", "rustls-pki-types", "slab", - "thiserror 2.0.12", + "thiserror 2.0.17", "tinyvec", "tracing", "web-time", @@ -9245,23 +9498,23 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.13" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.5.10", + "socket2 0.6.1", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" dependencies = [ "proc-macro2", ] @@ -9334,7 +9587,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", ] [[package]] @@ -9349,9 +9602,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" dependencies = [ "either", "rayon-core", @@ -9359,9 +9612,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", @@ -9369,11 +9622,20 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.15" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8af0dde094006011e6a740d4879319439489813bd0bcdc7d821beaeeff48ec" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", +] + +[[package]] +name = "redox_syscall" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27" +dependencies = [ + "bitflags 2.10.0", ] [[package]] @@ -9384,34 +9646,34 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.16", "libredox", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] name = "ref-cast" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "regex" -version = "1.11.1" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", @@ -9421,9 +9683,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", @@ -9432,9 +9694,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "reqwest" @@ -9479,19 +9741,18 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.22" +version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ - "async-compression", "base64 0.22.1", "bytes", "futures-core", "futures-util", - "http 1.3.1", + "http 1.4.0", "http-body 1.0.1", "http-body-util", - "hyper 1.6.0", + "hyper 1.8.1", "hyper-rustls 0.27.7", "hyper-util", "js-sys", @@ -9499,14 +9760,14 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.29", + "rustls 0.23.36", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper 1.0.2", "tokio", - "tokio-rustls 0.26.2", + "tokio-rustls 0.26.4", "tokio-util", "tower 0.5.2", "tower-http", @@ -9516,7 +9777,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots 1.0.2", + "webpki-roots 1.0.5", ] [[package]] @@ -9525,14 +9786,14 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21918d6644020c6f6ef1993242989bf6d4952d2e025617744f184c02df51c356" dependencies = [ - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] name = "resolv-conf" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95325155c684b1c89f7765e30bc1c42e4a6da51ca513615660cb8a62ef9a88e3" +checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" [[package]] name = "rfc6979" @@ -9569,22 +9830,19 @@ dependencies = [ [[package]] name = "rmp" -version = "0.8.14" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4" +checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c" dependencies = [ - "byteorder", "num-traits", - "paste", ] [[package]] name = "rmp-serde" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db" +checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155" dependencies = [ - "byteorder", "rmp", "serde", ] @@ -9600,9 +9858,9 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.8" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" dependencies = [ "const-oid", "digest 0.10.7", @@ -9621,9 +9879,9 @@ dependencies = [ [[package]] name = "rust-embed" -version = "8.7.2" +version = "8.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "025908b8682a26ba8d12f6f2d66b987584a4a87bc024abc5bbc12553a8cd178a" +checksum = "947d7f3fad52b283d261c4c99a084937e2fe492248cb9a68a8435a861b8798ca" dependencies = [ "rust-embed-impl", "rust-embed-utils", @@ -9632,22 +9890,22 @@ dependencies = [ [[package]] name = "rust-embed-impl" -version = "8.7.2" +version = "8.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6065f1a4392b71819ec1ea1df1120673418bf386f50de1d6f54204d836d4349c" +checksum = "5fa2c8c9e8711e10f9c4fd2d64317ef13feaab820a4c51541f1a8c8e2e851ab2" dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.106", + "syn 2.0.114", "walkdir", ] [[package]] name = "rust-embed-utils" -version = "8.7.2" +version = "8.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6cc0c81648b20b70c491ff8cce00c1c3b223bb8ed2b5d41f0e54c6c4c0a3594" +checksum = "60b161f275cb337fe0a44d924a5f4df0ed69c2c39519858f931ce61c779d3475" dependencies = [ "sha2 0.10.9", "walkdir", @@ -9669,12 +9927,6 @@ dependencies = [ "thiserror 1.0.69", ] -[[package]] -name = "rustc-demangle" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" - [[package]] name = "rustc-hash" version = "2.1.1" @@ -9696,33 +9948,20 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver 1.0.26", + "semver 1.0.27", ] [[package]] name = "rustix" -version = "0.38.44" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "errno", "libc", - "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", -] - -[[package]] -name = "rustix" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" -dependencies = [ - "bitflags 2.9.1", - "errno", - "libc", - "linux-raw-sys 0.9.4", - "windows-sys 0.60.2", + "linux-raw-sys", + "windows-sys 0.61.2", ] [[package]] @@ -9753,15 +9992,15 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.29" +version = "0.23.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" dependencies = [ "log", "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.103.4", + "rustls-webpki 0.103.8", "subtle 2.6.1", "zeroize", ] @@ -9811,9 +10050,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.12.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" dependencies = [ "web-time", "zeroize", @@ -9842,9 +10081,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.4" +version = "0.103.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" dependencies = [ "ring", "rustls-pki-types", @@ -9853,15 +10092,15 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" [[package]] name = "same-file" @@ -9874,11 +10113,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -9909,9 +10148,9 @@ dependencies = [ [[package]] name = "schemars" -version = "1.0.4" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" +checksum = "54e910108742c57a770f492731f99be216a52fadd361b06c8fb59d74ccc267d2" dependencies = [ "dyn-clone", "ref-cast", @@ -9928,15 +10167,9 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals 0.29.1", - "syn 2.0.106", + "syn 2.0.114", ] -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - [[package]] name = "scopeguard" version = "1.2.0" @@ -9960,7 +10193,7 @@ checksum = "1783eabc414609e28a5ba76aee5ddd52199f7107a0b24c2e9746a1ecc34a683d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -9981,7 +10214,7 @@ checksum = "22f968c5ea23d555e670b449c1c5e7b2fc399fdaec1d304a17cd48e288abc107" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -10023,7 +10256,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "core-foundation", "core-foundation-sys", "libc", @@ -10032,9 +10265,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.14.0" +version = "2.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" dependencies = [ "core-foundation-sys", "libc", @@ -10051,11 +10284,12 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" dependencies = [ "serde", + "serde_core", ] [[package]] @@ -10066,10 +10300,11 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ + "serde_core", "serde_derive", ] @@ -10106,22 +10341,32 @@ dependencies = [ [[package]] name = "serde_bytes" -version = "0.11.17" +version = "0.11.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8437fd221bde2d4ca316d61b90e337e9e702b3820b87d63caa9ba6c02bd06d96" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" dependencies = [ "serde", + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -10132,7 +10377,7 @@ checksum = "e578a843d40b4189a4d66bba51d7684f57da5bd7c304c64e14bd63efbef49509" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -10143,19 +10388,20 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "serde_json" -version = "1.0.141" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", + "serde_core", + "zmij", ] [[package]] @@ -10171,7 +10417,7 @@ dependencies = [ "serde_json", "serde_json_path_core", "serde_json_path_macros", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -10183,7 +10429,7 @@ dependencies = [ "inventory", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -10205,17 +10451,18 @@ checksum = "aafbefbe175fa9bf03ca83ef89beecff7d2a95aaacd5732325b90ac8c3bd7b90" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "serde_path_to_error" -version = "0.1.17" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" dependencies = [ "itoa", "serde", + "serde_core", ] [[package]] @@ -10235,7 +10482,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -10261,19 +10508,18 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.14.0" +version = "3.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5" +checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" dependencies = [ "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.10.0", + "indexmap 2.13.0", "schemars 0.9.0", - "schemars 1.0.4", - "serde", - "serde_derive", + "schemars 1.2.0", + "serde_core", "serde_json", "serde_with_macros", "time", @@ -10281,14 +10527,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.14.0" +version = "3.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f" +checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" dependencies = [ - "darling", + "darling 0.21.3", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -10297,7 +10543,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.10.0", + "indexmap 2.13.0", "itoa", "ryu", "serde", @@ -10402,9 +10648,9 @@ dependencies = [ [[package]] name = "signal-hook-mio" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" dependencies = [ "libc", "mio 0.8.11", @@ -10413,10 +10659,11 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.5" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] @@ -10432,9 +10679,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" [[package]] name = "siphasher" @@ -10530,12 +10777,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -10544,7 +10791,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c26f0c20d909fdda1c5d0ece3973127ca421984d55b000215df365e93722fc6e" dependencies = [ - "aes", + "aes 0.8.4", "arrayref", "blake2 0.8.1", "bs58", @@ -10608,24 +10855,24 @@ dependencies = [ "crc", "crossbeam-queue", "either", - "event-listener 5.4.0", + "event-listener 5.4.1", "futures-core", "futures-intrusive", "futures-io", "futures-util", - "hashbrown 0.15.4", + "hashbrown 0.15.5", "hashlink", - "indexmap 2.10.0", + "indexmap 2.13.0", "log", "memchr", "once_cell", "percent-encoding", - "rustls 0.23.29", + "rustls 0.23.36", "serde", "serde_json", "sha2 0.10.9", "smallvec", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-stream", @@ -10644,7 +10891,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -10667,7 +10914,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.106", + "syn 2.0.114", "tokio", "url", ] @@ -10680,7 +10927,7 @@ checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", "base64 0.22.1", - "bitflags 2.9.1", + "bitflags 2.10.0", "byteorder", "bytes", "chrono", @@ -10710,25 +10957,12 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tracing", "whoami", ] -[[package]] -name = "sqlx-pool-guard" -version = "0.1.0" -dependencies = [ - "proc_pidinfo", - "sqlx", - "tempfile", - "tokio", - "tracing", - "tracing-subscriber", - "windows", -] - [[package]] name = "sqlx-postgres" version = "0.8.6" @@ -10737,7 +10971,7 @@ checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", "base64 0.22.1", - "bitflags 2.9.1", + "bitflags 2.10.0", "byteorder", "chrono", "crc", @@ -10762,7 +10996,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tracing", "whoami", @@ -10788,7 +11022,7 @@ dependencies = [ "serde", "serde_urlencoded", "sqlx-core", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tracing", "url", @@ -10806,9 +11040,9 @@ dependencies = [ [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "static_assertions" @@ -10824,7 +11058,7 @@ checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" dependencies = [ "new_debug_unreachable", "parking_lot", - "phf_shared", + "phf_shared 0.11.3", "precomputed-hash", "serde", ] @@ -10836,7 +11070,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" dependencies = [ "phf_generator", - "phf_shared", + "phf_shared 0.11.3", "proc-macro2", "quote", ] @@ -10876,7 +11110,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -10908,10 +11142,12 @@ checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" [[package]] name = "superboring" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "515cce34a781d7250b8a65706e0f2a5b99236ea605cb235d4baed6685820478f" +checksum = "10d8c985e81c88f5694d5dfc232691b2aa34f3e1f66e860db9cd1ddf2bb6dc64" dependencies = [ + "aes-gcm", + "aes-keywrap", "getrandom 0.2.16", "hmac-sha256", "hmac-sha512", @@ -10932,9 +11168,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.106" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -10964,21 +11200,21 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "sysinfo" -version = "0.37.0" +version = "0.37.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07cec4dc2d2e357ca1e610cfb07de2fa7a10fc3e9fe89f72545f3d244ea87753" +checksum = "16607d5caffd1c07ce073528f9ed972d88db15dd44023fa57142963be3feb11f" dependencies = [ "libc", "memchr", "ntapi", "objc2-core-foundation", "objc2-io-kit", - "windows", + "windows 0.61.3", ] [[package]] @@ -11027,15 +11263,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.20.0" +version = "3.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" dependencies = [ "fastrand 2.3.0", - "getrandom 0.3.3", + "getrandom 0.3.4", "once_cell", - "rustix 1.0.8", - "windows-sys 0.59.0", + "rustix", + "windows-sys 0.61.2", ] [[package]] @@ -11113,7 +11349,7 @@ dependencies = [ "pin-project", "rand 0.8.5", "reqwest 0.11.27", - "semver 1.0.26", + "semver 1.0.27", "serde", "serde_bytes", "serde_json", @@ -11153,15 +11389,15 @@ dependencies = [ [[package]] name = "test-with" -version = "0.15.4" +version = "0.15.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0f370b9efbfbbc5f057cbce9888373eaeb146a3095bb8cc869b199c94d15559" +checksum = "57ec38bac967f54b11756d3b8b42069f5b30c3ff449972c516683b98340a4e8e" dependencies = [ "proc-macro-error2", "proc-macro2", "quote", "regex", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -11197,7 +11433,7 @@ dependencies = [ "serde_json", "sqlx", "tempfile", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "toml 0.8.23", @@ -11226,11 +11462,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl 2.0.12", + "thiserror-impl 2.0.17", ] [[package]] @@ -11241,18 +11477,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -11288,9 +11524,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.41" +version = "0.3.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" dependencies = [ "deranged", "itoa", @@ -11306,15 +11542,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" [[package]] name = "time-macros" -version = "0.2.22" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" dependencies = [ "num-conv", "time-core", @@ -11322,9 +11558,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" dependencies = [ "displaydoc", "zerovec", @@ -11342,9 +11578,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" dependencies = [ "tinyvec_macros", ] @@ -11373,46 +11609,43 @@ checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "tokio" -version = "1.47.1" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ - "backtrace", "bytes", - "io-uring", "libc", - "mio 1.0.4", + "mio 1.1.1", "parking_lot", "pin-project-lite", "signal-hook-registry", - "slab", - "socket2 0.6.0", + "socket2 0.6.1", "tokio-macros", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "tokio-postgres" -version = "0.7.13" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c95d533c83082bb6490e0189acaa0bbeef9084e60471b696ca6988cd0541fb0" +checksum = "2b40d66d9b2cfe04b628173409368e58247e8eddbbd3b0e6c6ba1d09f20f6c9e" dependencies = [ "async-trait", "byteorder", @@ -11423,12 +11656,12 @@ dependencies = [ "log", "parking_lot", "percent-encoding", - "phf", + "phf 0.13.1", "pin-project-lite", "postgres-protocol", "postgres-types", "rand 0.9.2", - "socket2 0.5.10", + "socket2 0.6.1", "tokio", "tokio-util", "whoami", @@ -11457,19 +11690,19 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.26.2" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls 0.23.29", + "rustls 0.23.36", "tokio", ] [[package]] name = "tokio-stream" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" dependencies = [ "futures-core", "pin-project-lite", @@ -11479,12 +11712,10 @@ dependencies = [ [[package]] name = "tokio-test" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2468baabc3311435b55dd935f702f42cd1b8abb7e754fb7dfb16bd36aa88f9f7" +checksum = "3f6d24790a10a7af737693a3e8f1d03faef7e6ca0cc99aae5066f533766de545" dependencies = [ - "async-stream", - "bytes", "futures-core", "tokio", "tokio-stream", @@ -11519,15 +11750,14 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.15" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", "futures-sink", "futures-util", - "hashbrown 0.15.4", "pin-project-lite", "slab", "tokio", @@ -11554,7 +11784,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37e04c1865c281139e5ccf633cb9f76ffdaabeebfe53b703984cf82878e2aabb" dependencies = [ "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -11574,8 +11804,8 @@ checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", "serde_spanned", - "toml_datetime", - "toml_edit", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", ] [[package]] @@ -11587,20 +11817,50 @@ dependencies = [ "serde", ] +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + [[package]] name = "toml_edit" version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.10.0", + "indexmap 2.13.0", "serde", "serde_spanned", - "toml_datetime", + "toml_datetime 0.6.11", "toml_write", "winnow", ] +[[package]] +name = "toml_edit" +version = "0.23.10+spec-1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" +dependencies = [ + "indexmap 2.13.0", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +dependencies = [ + "winnow", +] + [[package]] name = "toml_write" version = "0.1.2" @@ -11618,11 +11878,11 @@ dependencies = [ "axum", "base64 0.22.1", "bytes", - "h2 0.4.11", - "http 1.3.1", + "h2 0.4.13", + "http 1.4.0", "http-body 1.0.1", "http-body-util", - "hyper 1.6.0", + "hyper 1.8.1", "hyper-timeout", "hyper-util", "percent-encoding", @@ -11646,11 +11906,11 @@ dependencies = [ "async-trait", "base64 0.22.1", "bytes", - "h2 0.4.11", - "http 1.3.1", + "h2 0.4.13", + "http 1.4.0", "http-body 1.0.1", "http-body-util", - "hyper 1.6.0", + "hyper 1.8.1", "hyper-timeout", "hyper-util", "percent-encoding", @@ -11693,7 +11953,7 @@ checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", - "indexmap 2.10.0", + "indexmap 2.13.0", "pin-project-lite", "slab", "sync_wrapper 1.0.2", @@ -11706,16 +11966,16 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.6" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ "async-compression", - "bitflags 2.9.1", + "bitflags 2.10.0", "bytes", "futures-core", "futures-util", - "http 1.3.1", + "http 1.4.0", "http-body 1.0.1", "http-body-util", "http-range-header", @@ -11765,7 +12025,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -11790,9 +12050,9 @@ dependencies = [ [[package]] name = "tracing-indicatif" -version = "0.3.11" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c714cc8fc46db04fcfddbd274c6ef59bebb1b435155984e7c6e89c3ce66f200" +checksum = "e1ef6990e0438749f0080573248e96631171a0b5ddfddde119aa5ba8c3a9c47e" dependencies = [ "indicatif", "tracing", @@ -11872,7 +12132,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04659ddb06c87d233c566112c1c9c5b9e98256d9af50ec3bc9c8327f873a7568" dependencies = [ "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -11929,7 +12189,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e640d9b0964e9d39df633548591090ab92f7a4567bc31d3891af23471a3365c6" dependencies = [ "lazy_static", - "thiserror 2.0.12", + "thiserror 2.0.17", "ts-rs-macros", ] @@ -11956,7 +12216,7 @@ checksum = "0e9d8656589772eeec2cf7a8264d9cda40fb28b9bc53118ceb9e8c07f8f38730" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", "termcolor", ] @@ -11983,7 +12243,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals 0.28.0", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -12016,7 +12276,7 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http 1.3.1", + "http 1.4.0", "httparse", "log", "rand 0.8.5", @@ -12030,29 +12290,29 @@ dependencies = [ [[package]] name = "typed-builder" -version = "0.23.0" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d0dd654273fc253fde1df4172c31fb6615cf8b041d3a4008a028ef8b1119e66" +checksum = "31aa81521b70f94402501d848ccc0ecaa8f93c8eb6999eb9747e72287757ffda" dependencies = [ "typed-builder-macro", ] [[package]] name = "typed-builder-macro" -version = "0.23.0" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016c26257f448222014296978b2c8456e2cad4de308c35bdb1e383acd569ef5b" +checksum = "076a02dc54dd46795c2e9c8282ed40bcfb1e22747e955de9389a1de28190fb26" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "typenum" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "ucd-trie" @@ -12062,9 +12322,9 @@ checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "unicase" -version = "2.8.1" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" [[package]] name = "unicode-bidi" @@ -12074,24 +12334,24 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-normalization" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" dependencies = [ "tinyvec", ] [[package]] name = "unicode-properties" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" [[package]] name = "unicode-segmentation" @@ -12107,9 +12367,9 @@ checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-width" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] name = "unicode-xid" @@ -12119,26 +12379,43 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "uniffi" -version = "0.29.3" +version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b334fd69b3cf198b63616c096aabf9820ab21ed9b2aa1367ddd4b411068bf520" +checksum = "3291800a6b06569f7d3e15bdb6dc235e0f0c8bd3eb07177f430057feb076415f" dependencies = [ "anyhow", "camino", "cargo_metadata 0.19.2", "clap", - "uniffi_bindgen", - "uniffi_build", - "uniffi_core", - "uniffi_macros", - "uniffi_pipeline", + "uniffi_bindgen 0.29.5", + "uniffi_build 0.29.5", + "uniffi_core 0.29.5", + "uniffi_macros 0.29.5", + "uniffi_pipeline 0.29.5", +] + +[[package]] +name = "uniffi" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c866f627c3f04c3df068b68bb2d725492caaa539dd313e2a9d26bb85b1a32f4e" +dependencies = [ + "anyhow", + "camino", + "cargo_metadata 0.19.2", + "clap", + "uniffi_bindgen 0.30.0", + "uniffi_build 0.30.0", + "uniffi_core 0.30.0", + "uniffi_macros 0.30.0", + "uniffi_pipeline 0.30.0", ] [[package]] name = "uniffi_bindgen" -version = "0.29.3" +version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ff0132b533483cf19abb30bba5c72c24d9f3e4d9a2ff71cb3e22e73899fd46e" +checksum = "a04b99fa7796eaaa7b87976a0dbdd1178dc1ee702ea00aca2642003aef9b669e" dependencies = [ "anyhow", "askama", @@ -12148,34 +12425,83 @@ dependencies = [ "glob", "goblin", "heck 0.5.0", - "indexmap 2.10.0", + "indexmap 2.13.0", "once_cell", "serde", "tempfile", "textwrap", "toml 0.5.11", - "uniffi_internal_macros", - "uniffi_meta", - "uniffi_pipeline", - "uniffi_udl", + "uniffi_internal_macros 0.29.5", + "uniffi_meta 0.29.5", + "uniffi_pipeline 0.29.5", + "uniffi_udl 0.29.5", +] + +[[package]] +name = "uniffi_bindgen" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c8ca600167641ebe7c8ba9254af40492dda3397c528cc3b2f511bd23e8541a5" +dependencies = [ + "anyhow", + "askama", + "camino", + "cargo_metadata 0.19.2", + "fs-err", + "glob", + "goblin", + "heck 0.5.0", + "indexmap 2.13.0", + "once_cell", + "serde", + "tempfile", + "textwrap", + "toml 0.8.23", + "uniffi_internal_macros 0.30.0", + "uniffi_meta 0.30.0", + "uniffi_pipeline 0.30.0", + "uniffi_udl 0.30.0", ] [[package]] name = "uniffi_build" -version = "0.29.3" +version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d84d607076008df3c32dd2100ee4e727269f11d3faa35691af70d144598f666" +checksum = "025a05cba02ee22b6624ac3d257e59c7395319ea8fe1aae33a7cdb4e2a3016cc" dependencies = [ "anyhow", "camino", - "uniffi_bindgen", + "uniffi_bindgen 0.29.5", +] + +[[package]] +name = "uniffi_build" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e55c05228f4858bb258f651d21d743fcc1fe5a2ec20d3c0f9daefddb105ee4d" +dependencies = [ + "anyhow", + "camino", + "uniffi_bindgen 0.30.0", ] [[package]] name = "uniffi_core" -version = "0.29.3" +version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e3b997192dc15ef1778c842001811ec7f241a093a693ac864e1fc938e64fa9" +checksum = "f38a9a27529ccff732f8efddb831b65b1e07f7dea3fd4cacd4a35a8c4b253b98" +dependencies = [ + "anyhow", + "bytes", + "once_cell", + "static_assertions", +] + +[[package]] +name = "uniffi_core" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e7a5a038ebffe8f4cf91416b154ef3c2468b18e828b7009e01b1b99938089f9" dependencies = [ "anyhow", "bytes", @@ -12185,22 +12511,35 @@ dependencies = [ [[package]] name = "uniffi_internal_macros" -version = "0.29.3" +version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f64bec2f3a33f2f08df8150e67fa45ba59a2ca740bf20c1beb010d4d791f9a1b" +checksum = "09acd2ce09c777dd65ee97c251d33c8a972afc04873f1e3b21eb3492ade16933" dependencies = [ "anyhow", - "indexmap 2.10.0", + "indexmap 2.13.0", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", +] + +[[package]] +name = "uniffi_internal_macros" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c2a6f93e7b73726e2015696ece25ca0ac5a5f1cf8d6a7ab5214dd0a01d2edf" +dependencies = [ + "anyhow", + "indexmap 2.13.0", + "proc-macro2", + "quote", + "syn 2.0.114", ] [[package]] name = "uniffi_macros" -version = "0.29.3" +version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d8708716d2582e4f3d7e9f320290b5966eb951ca421d7630571183615453efc" +checksum = "5596f178c4f7aafa1a501c4e0b96236a96bc2ef92bdb453d83e609dad0040152" dependencies = [ "camino", "fs-err", @@ -12208,53 +12547,107 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.106", + "syn 2.0.114", "toml 0.5.11", - "uniffi_meta", + "uniffi_meta 0.29.5", +] + +[[package]] +name = "uniffi_macros" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64c6309fc36c7992afc03bc0c5b059c656bccbef3f2a4bc362980017f8936141" +dependencies = [ + "camino", + "fs-err", + "once_cell", + "proc-macro2", + "quote", + "serde", + "syn 2.0.114", + "toml 0.8.23", + "uniffi_meta 0.30.0", ] [[package]] name = "uniffi_meta" -version = "0.29.3" +version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d226fc167754ce548c5ece9828c8a06f03bf1eea525d2659ba6bd648bd8e2f3" +checksum = "beadc1f460eb2e209263c49c4f5b19e9a02e00a3b2b393f78ad10d766346ecff" dependencies = [ "anyhow", "siphasher 0.3.11", - "uniffi_internal_macros", - "uniffi_pipeline", + "uniffi_internal_macros 0.29.5", + "uniffi_pipeline 0.29.5", +] + +[[package]] +name = "uniffi_meta" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a138823392dba19b0aa494872689f97d0ee157de5852e2bec157ce6de9cdc22" +dependencies = [ + "anyhow", + "siphasher 0.3.11", + "uniffi_internal_macros 0.30.0", + "uniffi_pipeline 0.30.0", ] [[package]] name = "uniffi_pipeline" -version = "0.29.3" +version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b925b6421df15cf4bedee27714022cd9626fb4d7eee0923522a608b274ba4371" +checksum = "dd76b3ac8a2d964ca9fce7df21c755afb4c77b054a85ad7a029ad179cc5abb8a" dependencies = [ "anyhow", "heck 0.5.0", - "indexmap 2.10.0", + "indexmap 2.13.0", "tempfile", - "uniffi_internal_macros", + "uniffi_internal_macros 0.29.5", +] + +[[package]] +name = "uniffi_pipeline" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c27c4b515d25f8e53cc918e238c39a79c3144a40eaf2e51c4a7958973422c29" +dependencies = [ + "anyhow", + "heck 0.5.0", + "indexmap 2.13.0", + "tempfile", + "uniffi_internal_macros 0.30.0", ] [[package]] name = "uniffi_udl" -version = "0.29.3" +version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c42649b721df759d9d4692a376b82b62ce3028ec9fc466f4780fb8cdf728996" +checksum = "4319cf905911d70d5b97ce0f46f101619a22e9a189c8c46d797a9955e9233716" dependencies = [ "anyhow", "textwrap", - "uniffi_meta", + "uniffi_meta 0.29.5", + "weedle2", +] + +[[package]] +name = "uniffi_udl" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0adacdd848aeed7af4f5af7d2f621d5e82531325d405e29463482becfdeafca" +dependencies = [ + "anyhow", + "textwrap", + "uniffi_meta 0.30.0", "weedle2", ] [[package]] name = "unit-prefix" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "323402cff2dd658f39ca17c789b502021b3f18707c91cdf22e3838e1b4023817" +checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3" [[package]] name = "universal-hash" @@ -12262,7 +12655,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "subtle 2.6.1", ] @@ -12280,14 +12673,15 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.4" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", "percent-encoding", "serde", + "serde_derive", ] [[package]] @@ -12320,7 +12714,7 @@ version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fcc29c80c21c31608227e0912b2d7fddba57ad76b606890627ba8ee7964e993" dependencies = [ - "indexmap 2.10.0", + "indexmap 2.13.0", "serde", "serde_json", "utoipa-gen", @@ -12335,7 +12729,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.106", + "syn 2.0.114", "uuid", ] @@ -12374,7 +12768,7 @@ checksum = "268d76aaebb80eba79240b805972e52d7d410d4bcc52321b951318b0f440cd60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -12385,19 +12779,19 @@ checksum = "382673bda1d05c85b4550d32fd4192ccd4cffe9a908543a0795d1e7682b36246" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", "utoipauto-core", ] [[package]] name = "uuid" -version = "1.17.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", "js-sys", - "serde", + "serde_core", "wasm-bindgen", ] @@ -12496,35 +12890,23 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "vt100" -version = "0.15.2" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84cd863bf0db7e392ba3bd04994be3473491b31e66340672af5d11943c6274de" +checksum = "054ff75fb8fa83e609e685106df4faeffdf3a735d3c74ebce97ec557d5d36fd9" dependencies = [ "itoa", - "log", - "unicode-width 0.1.14", + "unicode-width 0.2.2", "vte", ] [[package]] name = "vte" -version = "0.11.1" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5022b5fbf9407086c180e9557be968742d839e68346af7792b8592489732197" +checksum = "a5924018406ce0063cd67f8e008104968b74b563ee1b85dde3ed1f7cb87d3dbd" dependencies = [ "arrayvec", - "utf8parse", - "vte_generate_state_changes", -] - -[[package]] -name = "vte_generate_state_changes" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e369bee1b05d510a7b4ed645f5faa90619e05437111783ea5848f28d97d3c2e" -dependencies = [ - "proc-macro2", - "quote", + "memchr", ] [[package]] @@ -12559,12 +12941,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" +name = "wasip2" +version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen", ] [[package]] @@ -12579,40 +12961,27 @@ version = "0.12.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1fbb4ef9bbca0c1170e0b00dd28abc9e3b68669821600cad1caaed606583c6d" dependencies = [ - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", ] [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn 2.0.106", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.50" +version = "0.4.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" dependencies = [ "cfg-if", "js-sys", @@ -12623,9 +12992,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -12633,34 +13002,42 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" dependencies = [ + "bumpalo", "proc-macro2", "quote", - "syn 2.0.106", - "wasm-bindgen-backend", + "syn 2.0.114", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" dependencies = [ "unicode-ident", ] [[package]] name = "wasm-bindgen-test" -version = "0.3.50" +version = "0.3.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66c8d5e33ca3b6d9fa3b4676d774c5778031d27a578c2b007f905acf816152c3" +checksum = "25e90e66d265d3a1efc0e72a54809ab90b9c0c515915c67cdf658689d2c22c6c" dependencies = [ + "async-trait", + "cast", "js-sys", + "libm", "minicov", + "nu-ansi-term 0.50.3", + "num-traits", + "oorandom", + "serde", + "serde_json", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test-macro", @@ -12668,63 +13045,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.50" +version = "0.3.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b" +checksum = "7150335716dce6028bead2b848e72f47b45e7b9422f64cccdc23bedca89affc1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", -] - -[[package]] -name = "wasm-client-core" -version = "0.1.0" -dependencies = [ - "async-trait", - "console_error_panic_hook", - "js-sys", - "nym-bandwidth-controller", - "nym-client-core", - "nym-config", - "nym-credential-storage", - "nym-crypto", - "nym-gateway-client", - "nym-http-api-client", - "nym-sphinx", - "nym-sphinx-acknowledgements", - "nym-statistics-common", - "nym-task", - "nym-topology", - "nym-validator-client", - "rand 0.8.5", - "serde", - "serde-wasm-bindgen 0.6.5", - "thiserror 2.0.12", - "time", - "tsify", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-storage", - "wasm-utils", - "zeroize", -] - -[[package]] -name = "wasm-storage" -version = "0.1.0" -dependencies = [ - "async-trait", - "getrandom 0.2.16", - "indexed_db_futures", - "js-sys", - "nym-store-cipher", - "serde", - "serde-wasm-bindgen 0.6.5", - "thiserror 2.0.12", - "wasm-bindgen", - "wasm-utils", + "syn 2.0.114", ] [[package]] @@ -12740,27 +13067,11 @@ dependencies = [ "web-sys", ] -[[package]] -name = "wasm-utils" -version = "0.1.0" -dependencies = [ - "console_error_panic_hook", - "futures", - "getrandom 0.2.16", - "gloo-net", - "gloo-utils 0.2.0", - "js-sys", - "tungstenite 0.20.1", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "wasmtimer" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8d49b5d6c64e8558d9b1b065014426f35c18de636895d24893dbbd329743446" +checksum = "1c598d6b99ea013e35844697fc4670d08339d5cda15588f193c6beedd12f644b" dependencies = [ "futures", "js-sys", @@ -12772,9 +13083,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.77" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" dependencies = [ "js-sys", "wasm-bindgen", @@ -12796,7 +13107,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57ffde1dc01240bdf9992e3205668b235e59421fd085e8a317ed98da0178d414" dependencies = [ - "phf", + "phf 0.11.3", "phf_codegen", "string_cache", "string_cache_codegen", @@ -12823,14 +13134,14 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "webpki-roots 1.0.2", + "webpki-roots 1.0.5", ] [[package]] name = "webpki-roots" -version = "1.0.2" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" +checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" dependencies = [ "rustls-pki-types", ] @@ -12846,20 +13157,26 @@ dependencies = [ [[package]] name = "whoami" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7" +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" dependencies = [ - "redox_syscall", + "libredox", "wasite", "web-sys", ] [[package]] name = "widestring" -version = "1.2.0" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d" +checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c" + +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" [[package]] name = "winapi" @@ -12879,11 +13196,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -12898,11 +13215,23 @@ version = "0.61.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ - "windows-collections", - "windows-core", - "windows-future", - "windows-link", - "windows-numerics", + "windows-collections 0.2.0", + "windows-core 0.61.2", + "windows-future 0.2.1", + "windows-link 0.1.3", + "windows-numerics 0.2.0", +] + +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections 0.3.2", + "windows-core 0.62.2", + "windows-future 0.3.2", + "windows-numerics 0.3.1", ] [[package]] @@ -12911,7 +13240,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" dependencies = [ - "windows-core", + "windows-core 0.61.2", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core 0.62.2", ] [[package]] @@ -12922,9 +13260,22 @@ checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ "windows-implement", "windows-interface", - "windows-link", - "windows-result", - "windows-strings", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", ] [[package]] @@ -12933,31 +13284,42 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ - "windows-core", - "windows-link", - "windows-threading", + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading 0.1.0", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", + "windows-threading 0.2.1", ] [[package]] name = "windows-implement" -version = "0.60.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "windows-interface" -version = "0.59.1" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -12966,14 +13328,30 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-numerics" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ - "windows-core", - "windows-link", + "windows-core 0.61.2", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", ] [[package]] @@ -12982,7 +13360,16 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ - "windows-link", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", ] [[package]] @@ -12991,7 +13378,16 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ - "windows-link", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", ] [[package]] @@ -13036,7 +13432,16 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.2", + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", ] [[package]] @@ -13087,18 +13492,19 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.2" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] [[package]] @@ -13107,7 +13513,16 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" dependencies = [ - "windows-link", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link 0.2.1", ] [[package]] @@ -13130,9 +13545,9 @@ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" @@ -13154,9 +13569,9 @@ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" @@ -13178,9 +13593,9 @@ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" @@ -13190,9 +13605,9 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" @@ -13214,9 +13629,9 @@ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" @@ -13238,9 +13653,9 @@ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" @@ -13262,9 +13677,9 @@ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" @@ -13286,15 +13701,15 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.12" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" dependencies = [ "memchr", ] @@ -13310,19 +13725,32 @@ dependencies = [ ] [[package]] -name = "wit-bindgen-rt" -version = "0.39.0" +name = "wireguard-nt" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +checksum = "22b4dbcc6c93786cf22e420ef96e8976bfb92a455070282302b74de5848191f4" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", + "getrandom 0.2.16", + "ipnet", + "libloading", + "log", + "thiserror 1.0.69", + "widestring 0.4.3", + "windows-sys 0.59.0", ] [[package]] -name = "writeable" -version = "0.6.1" +name = "wit-bindgen" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" [[package]] name = "wyz" @@ -13347,12 +13775,12 @@ dependencies = [ [[package]] name = "xattr" -version = "1.5.1" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af3a19837351dc82ba89f8a125e22a3c475f05aba604acc023d62b2739ae2909" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" dependencies = [ "libc", - "rustix 1.0.8", + "rustix", ] [[package]] @@ -13363,11 +13791,10 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "yoke" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" dependencies = [ - "serde", "stable_deref_trait", "yoke-derive", "zerofrom", @@ -13375,34 +13802,34 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.26" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.26" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -13422,35 +13849,35 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", "synstructure", ] [[package]] name = "zeroize" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "zerotrie" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" dependencies = [ "displaydoc", "yoke", @@ -13459,9 +13886,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.2" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" dependencies = [ "yoke", "zerofrom", @@ -13470,13 +13897,13 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -13490,15 +13917,15 @@ dependencies = [ "crossbeam-utils", "displaydoc", "flate2", - "indexmap 2.10.0", + "indexmap 2.13.0", "memchr", - "thiserror 2.0.12", + "thiserror 2.0.17", "zopfli", ] [[package]] name = "zknym-lib" -version = "0.1.0" +version = "1.20.1" dependencies = [ "anyhow", "async-trait", @@ -13510,24 +13937,30 @@ dependencies = [ "nym-credentials", "nym-crypto", "nym-http-api-client", + "nym-wasm-utils", "rand 0.8.5", - "reqwest 0.12.22", + "reqwest 0.12.28", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tsify", "uuid", "wasm-bindgen", - "wasm-utils", "wasmtimer", "zeroize", ] [[package]] -name = "zopfli" -version = "0.8.2" +name = "zmij" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edfc5ee405f504cd4984ecc6f14d02d55cfda60fa4b689434ef4102aae150cd7" +checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8" + +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" dependencies = [ "bumpalo", "crc32fast", @@ -13555,9 +13988,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.15+zstd.1.5.7" +version = "2.0.16+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" dependencies = [ "cc", "pkg-config", @@ -13565,15 +13998,15 @@ dependencies = [ [[package]] name = "zulip-client" -version = "0.1.0" +version = "1.20.1" dependencies = [ "itertools 0.14.0", "nym-bin-common", "nym-http-api-client", - "reqwest 0.12.22", + "reqwest 0.12.28", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tracing", "url", diff --git a/Cargo.toml b/Cargo.toml index dfc6650e8f..c6b1bc64a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -152,7 +152,7 @@ members = [ "service-providers/common", "service-providers/ip-packet-router", "service-providers/network-requester", - "sqlx-pool-guard", + "nym-sqlx-pool-guard", "tools/echo-server", "tools/internal/contract-state-importer/importer-cli", "tools/internal/contract-state-importer/importer-contract", @@ -174,7 +174,7 @@ members = [ "wasm/node-tester", "wasm/zknym-lib", "nym-gateway-probe", - "integration-tests", "common/nym-lp-transport", + "integration-tests", "common/nym-lp-transport", "common/nym-kkt-ciphersuite", ] default-members = [ @@ -185,7 +185,6 @@ default-members = [ "nym-credential-proxy/nym-credential-proxy", "nym-node", "nym-node-status-api/nym-node-status-agent", - "nym-node-status-api/nym-node-status-api", "nym-statistics-api", "nym-validator-rewarder", "nyx-chain-watcher", @@ -206,6 +205,7 @@ edition = "2024" license = "Apache-2.0" rust-version = "1.85" readme = "README.md" +version = "1.20.1" [workspace.dependencies] addr = "0.15.6" @@ -256,8 +256,7 @@ ctr = "0.9.1" cupid = "0.6.1" curve25519-dalek = "4.1.3" dashmap = "5.5.3" -# We want https://github.com/DefGuard/wireguard-rs/pull/64 , but there's no crates.io release being pushed out anymore -defguard_wireguard_rs = { git = "https://github.com/DefGuard/wireguard-rs.git", rev = "v0.4.7" } +defguard_wireguard_rs = "0.8.0" digest = "0.10.7" dirs = "6.0" dotenvy = "0.15.6" @@ -304,6 +303,7 @@ ledger-transport = "0.10.0" ledger-transport-hid = "0.10.0" log = "0.4" mime = "0.3.17" +mock_instant = "0.6.0" moka = { version = "0.12", features = ["future"] } nix = "0.30.1" notify = "5.1.0" @@ -390,11 +390,113 @@ zeroize = "1.7.0" prometheus = { version = "0.14.0" } +# Workspace dep definitions required by crates.io publication - we need a workspace version since `cargo workspaces` doesn't work with path imports from crate manifests +nym-api-requests = { version = "1.20.1", path = "nym-api/nym-api-requests" } +nym-authenticator-requests = { version = "1.20.1", path = "common/authenticator-requests" } +nym-async-file-watcher = { version = "1.20.1", path = "common/async-file-watcher" } +nym-authenticator-client = { version = "1.20.1", path = "nym-authenticator-client" } +nym-bandwidth-controller = { version = "1.20.1", path = "common/bandwidth-controller" } +nym-bin-common = { version = "1.20.1", path = "common/bin-common" } +nym-cache = { version = "1.20.1", path = "common/nym-cache" } +nym-client-core = { version = "1.20.1", path = "common/client-core", default-features = false } +nym-client-core-config-types = { version = "1.20.1", path = "common/client-core/config-types" } +nym-client-core-gateways-storage = { version = "1.20.1", path = "common/client-core/gateways-storage" } +nym-client-core-surb-storage = { version = "1.20.1", path = "common/client-core/surb-storage" } +nym-client-websocket-requests = { version = "1.20.1", path = "clients/native/websocket-requests" } +nym-common = { version = "1.20.1", path = "common/nym-common" } +nym-compact-ecash = { version = "1.20.1", path = "common/nym_offline_compact_ecash" } +nym-config = { version = "1.20.1", path = "common/config" } +nym-contracts-common = { version = "1.20.1", path = "common/cosmwasm-smart-contracts/contracts-common" } +nym-coconut-dkg-common = { version = "1.20.1", path = "common/cosmwasm-smart-contracts/coconut-dkg" } +nym-credential-storage = { version = "1.20.1", path = "common/credential-storage" } +nym-credential-utils = { version = "1.20.1", path = "common/credential-utils" } +nym-credential-proxy-lib = { version = "1.20.1", path = "common/credential-proxy" } +nym-credentials = { version = "1.20.1", path = "common/credentials", default-features = false } +nym-credentials-interface = { version = "1.20.1", path = "common/credentials-interface" } +nym-credential-proxy-requests = { version = "1.20.1", path = "nym-credential-proxy/nym-credential-proxy-requests", default-features = false } +nym-credential-verification = { version = "1.20.1", path = "common/credential-verification" } +nym-crypto = { version = "1.20.1", path = "common/crypto", default-features = false } +nym-dkg = { version = "1.20.1", path = "common/dkg" } +nym-ecash-contract-common = { version = "1.20.1", path = "common/cosmwasm-smart-contracts/ecash-contract" } +nym-ecash-signer-check = { version = "1.20.1", path = "common/ecash-signer-check" } +nym-ecash-signer-check-types = { version = "1.20.1", path = "common/ecash-signer-check-types" } +nym-ecash-time = { version = "1.20.1", path = "common/ecash-time" } +nym-exit-policy = { version = "1.20.1", path = "common/exit-policy" } +nym-ffi-shared = { version = "1.20.1", path = "sdk/ffi/shared" } +nym-gateway-client = { version = "1.20.1", path = "common/client-libs/gateway-client", default-features = false } +nym-gateway-requests = { version = "1.20.1", path = "common/gateway-requests" } +nym-gateway-storage = { version = "1.20.1", path = "common/gateway-storage" } +nym-gateway-stats-storage = { version = "1.20.1", path = "common/gateway-stats-storage" } +nym-group-contract-common = { version = "1.20.1", path = "common/cosmwasm-smart-contracts/group-contract" } +nym-http-api-client = { version = "1.20.1", path = "common/http-api-client" } +nym-http-api-client-macro = { version = "1.20.1", path = "common/http-api-client-macro" } +nym-http-api-common = { version = "1.20.1", path = "common/http-api-common", default-features = false } +nym-id = { version = "1.20.1", path = "common/nym-id" } +nym-kkt-ciphersuite = { path = "common/nym-kkt-ciphersuite" } +nym-ip-packet-client = { version = "1.20.1", path = "nym-ip-packet-client" } +nym-ip-packet-requests = { version = "1.20.1", path = "common/ip-packet-requests" } +nym-metrics = { version = "1.20.1", path = "common/nym-metrics" } +nym-mixnet-client = { version = "1.20.1", path = "common/client-libs/mixnet-client" } +nym-mixnet-contract-common = { version = "1.20.1", path = "common/cosmwasm-smart-contracts/mixnet-contract" } +nym-multisig-contract-common = { version = "1.20.1", path = "common/cosmwasm-smart-contracts/multisig-contract" } +nym-network-defaults = { version = "1.20.1", path = "common/network-defaults" } +nym-node-tester-utils = { version = "1.20.1", path = "common/node-tester-utils" } +nym-noise = { version = "1.20.1", path = "common/nymnoise" } +nym-noise-keys = { version = "1.20.1", path = "common/nymnoise/keys" } +nym-nonexhaustive-delayqueue = { version = "1.20.1", path = "common/nonexhaustive-delayqueue" } +nym-node-requests = { version = "1.20.1", path = "nym-node/nym-node-requests", default-features = false } +nym-node-metrics = { version = "1.20.1", path = "nym-node/nym-node-metrics" } +nym-ordered-buffer = { version = "1.20.1", path = "common/socks5/ordered-buffer" } +nym-outfox = { version = "1.20.1", path = "nym-outfox" } +nym-registration-common = { version = "1.20.1", path = "common/registration" } +nym-pemstore = { version = "1.20.1", path = "common/pemstore" } +nym-performance-contract-common = { version = "1.20.1", path = "common/cosmwasm-smart-contracts/nym-performance-contract" } +nym-sdk = { version = "1.20.1", path = "sdk/rust/nym-sdk" } +nym-serde-helpers = { version = "1.20.1", path = "common/serde-helpers" } +nym-service-providers-common = { version = "1.20.1", path = "service-providers/common" } +nym-service-provider-requests-common = { version = "1.20.1", path = "common/service-provider-requests-common" } +nym-socks5-client-core = { version = "1.20.1", path = "common/socks5-client-core" } +nym-socks5-proxy-helpers = { version = "1.20.1", path = "common/socks5/proxy-helpers" } +nym-socks5-requests = { version = "1.20.1", path = "common/socks5/requests" } +nym-sphinx = { version = "1.20.1", path = "common/nymsphinx" } +nym-sphinx-acknowledgements = { version = "1.20.1", path = "common/nymsphinx/acknowledgements" } +nym-sphinx-addressing = { version = "1.20.1", path = "common/nymsphinx/addressing" } +nym-sphinx-anonymous-replies = { version = "1.20.1", path = "common/nymsphinx/anonymous-replies" } +nym-sphinx-chunking = { version = "1.20.1", path = "common/nymsphinx/chunking" } +nym-sphinx-cover = { version = "1.20.1", path = "common/nymsphinx/cover" } +nym-sphinx-forwarding = { version = "1.20.1", path = "common/nymsphinx/forwarding" } +nym-sphinx-framing = { version = "1.20.1", path = "common/nymsphinx/framing" } +nym-sphinx-params = { version = "1.20.1", path = "common/nymsphinx/params" } +nym-sphinx-routing = { version = "1.20.1", path = "common/nymsphinx/routing" } +nym-sphinx-types = { version = "1.20.1", path = "common/nymsphinx/types" } +nym-statistics-common = { version = "1.20.1", path = "common/statistics" } +nym-store-cipher = { version = "1.20.1", path = "common/store-cipher" } +nym-task = { version = "1.20.1", path = "common/task" } +nym-tun = { version = "1.20.1", path = "common/tun" } +nym-test-utils = { version = "1.20.1", path = "common/test-utils" } +nym-ticketbooks-merkle = { version = "1.20.1", path = "common/ticketbooks-merkle" } +nym-topology = { version = "1.20.1", path = "common/topology" } +nym-types = { version = "1.20.1", path = "common/types" } +nym-upgrade-mode-check = { version = "1.20.1", path = "common/upgrade-mode-check" } +nym-validator-client = { version = "1.20.1", path = "common/client-libs/validator-client", default-features = false } +nym-vesting-contract-common = { version = "1.20.1", path = "common/cosmwasm-smart-contracts/vesting-contract" } +nym-verloc = { version = "1.20.1", path = "common/verloc" } +nym-wireguard = { version = "1.20.1", path = "common/wireguard" } +nym-wireguard-types = { version = "1.20.1", path = "common/wireguard-types" } +nym-wireguard-private-metadata-shared = { version = "1.20.1", path = "common/wireguard-private-metadata/shared" } +nym-wireguard-private-metadata-client = { version = "1.20.1", path = "common/wireguard-private-metadata/client" } +nym-wireguard-private-metadata-server = { version = "1.20.1", path = "common/wireguard-private-metadata/server" } +nym-sqlx-pool-guard = { version = "1.2.0", path = "nym-sqlx-pool-guard" } +nym-wasm-client-core = { version = "1.20.1", path = "common/wasm/client-core" } +nym-wasm-storage = { version = "1.20.1", path = "common/wasm/storage" } +nym-wasm-utils = { version = "1.20.1", path = "common/wasm/utils", default-features = false } +nyxd-scraper-shared = { version = "1.20.1", path = "common/nyxd-scraper-shared" } + # coconut/DKG related -# unfortunately until https://github.com/zkcrypto/bls12_381/issues/10 is resolved, we have to rely on the fork +# unfortunately until https://github.com/zkcrypto/nym-bls12_381-fork/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-updated" } +# plus to make our live easier we need serde support from https://github.com/zkcrypto/nym-bls12_381-fork/pull/125 +nym-bls12_381-fork = { version = "0.8.0-forked", default-features = false } group = { version = "0.13.0", default-features = false } ff = { version = "0.13.1", default-features = false } subtle = "2.5.0" diff --git a/clients/native/Cargo.toml b/clients/native/Cargo.toml index 55617ac5ba..e5b4f09d84 100644 --- a/clients/native/Cargo.toml +++ b/clients/native/Cargo.toml @@ -1,11 +1,12 @@ [package] name = "nym-client" -version = "1.1.69" +version = "1.1.70" authors = ["Dave Hrycyszyn ", "Jędrzej Stuczyński "] description = "Implementation of the Nym Client" edition = "2021" rust-version = "1.85" license.workspace = true +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -42,32 +43,32 @@ tokio-tungstenite = { workspace = true } zeroize = { workspace = true } ## internal -nym-bandwidth-controller = { path = "../../common/bandwidth-controller" } -nym-bin-common = { path = "../../common/bin-common", features = [ +nym-bandwidth-controller = { workspace = true } +nym-bin-common = { workspace = true, features = [ "output_format", "clap", "basic_tracing", ] } -nym-client-core = { path = "../../common/client-core", features = [ +nym-client-core = { workspace = true, features = [ "fs-credentials-storage", "fs-surb-storage", "fs-gateways-storage", "cli", ] } -nym-config = { path = "../../common/config" } -nym-credential-storage = { path = "../../common/credential-storage" } -nym-credentials = { path = "../../common/credentials" } -nym-crypto = { path = "../../common/crypto" } -nym-gateway-requests = { path = "../../common/gateway-requests" } -nym-network-defaults = { path = "../../common/network-defaults" } -nym-sphinx = { path = "../../common/nymsphinx" } -nym-pemstore = { path = "../../common/pemstore" } -nym-task = { path = "../../common/task" } -nym-topology = { path = "../../common/topology" } -nym-validator-client = { path = "../../common/client-libs/validator-client", features = [ +nym-config = { workspace = true } +nym-credential-storage = { workspace = true } +nym-credentials = { workspace = true } +nym-crypto = { workspace = true } +nym-gateway-requests = { workspace = true } +nym-network-defaults = { workspace = true } +nym-sphinx = { workspace = true } +nym-pemstore = { workspace = true } +nym-task = { workspace = true } +nym-topology = { workspace = true } +nym-validator-client = { workspace = true, features = [ "http-client", ] } -nym-client-websocket-requests = { path = "websocket-requests" } -nym-id = { path = "../../common/nym-id" } +nym-client-websocket-requests = { workspace = true } +nym-id = { workspace = true } [dev-dependencies] diff --git a/clients/native/websocket-requests/Cargo.toml b/clients/native/websocket-requests/Cargo.toml index a3ef6a1f28..25defd82ff 100644 --- a/clients/native/websocket-requests/Cargo.toml +++ b/clients/native/websocket-requests/Cargo.toml @@ -1,9 +1,13 @@ [package] name = "nym-client-websocket-requests" -version = "0.1.0" +version.workspace = true authors = ["Jędrzej Stuczyński "] edition = "2021" license.workspace = true +description = "Request and response definitions for Nym client websocket connections" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -11,4 +15,4 @@ license.workspace = true serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } -nym-sphinx = { path = "../../../common/nymsphinx" } +nym-sphinx = { workspace = true } diff --git a/clients/socks5/Cargo.toml b/clients/socks5/Cargo.toml index 1a5a431f50..aa09c09ca3 100644 --- a/clients/socks5/Cargo.toml +++ b/clients/socks5/Cargo.toml @@ -1,11 +1,12 @@ [package] name = "nym-socks5-client" -version = "1.1.69" +version = "1.1.70" authors = ["Dave Hrycyszyn "] description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address" edition = "2021" rust-version = "1.85" license.workspace = true +publish = false [dependencies] bs58 = { workspace = true } @@ -24,30 +25,30 @@ url = { workspace = true } zeroize = { workspace = true } # internal -nym-bin-common = { path = "../../common/bin-common", features = [ +nym-bin-common = { workspace = true, features = [ "output_format", "clap", "basic_tracing", ] } -nym-client-core = { path = "../../common/client-core", features = [ +nym-client-core = { workspace = true, features = [ "fs-credentials-storage", "fs-surb-storage", "fs-gateways-storage", "cli", ] } -nym-config = { path = "../../common/config" } -nym-credential-storage = { path = "../../common/credential-storage" } -nym-credentials = { path = "../../common/credentials" } -nym-crypto = { path = "../../common/crypto" } -nym-gateway-requests = { path = "../../common/gateway-requests" } -nym-id = { path = "../../common/nym-id" } -nym-network-defaults = { path = "../../common/network-defaults" } -nym-ordered-buffer = { path = "../../common/socks5/ordered-buffer" } -nym-pemstore = { path = "../../common/pemstore" } -nym-socks5-client-core = { path = "../../common/socks5-client-core" } -nym-sphinx = { path = "../../common/nymsphinx" } -nym-topology = { path = "../../common/topology" } -nym-validator-client = { path = "../../common/client-libs/validator-client", features = [ +nym-config = { workspace = true } +nym-credential-storage = { workspace = true } +nym-credentials = { workspace = true } +nym-crypto = { workspace = true } +nym-gateway-requests = { workspace = true } +nym-id = { workspace = true } +nym-network-defaults = { workspace = true } +nym-ordered-buffer = { workspace = true } +nym-pemstore = { workspace = true } +nym-socks5-client-core = { workspace = true } +nym-sphinx = { workspace = true } +nym-topology = { workspace = true } +nym-validator-client = { workspace = true, features = [ "http-client", ] } diff --git a/common/async-file-watcher/Cargo.toml b/common/async-file-watcher/Cargo.toml index cde165eaab..4c4af2ab4c 100644 --- a/common/async-file-watcher/Cargo.toml +++ b/common/async-file-watcher/Cargo.toml @@ -1,8 +1,12 @@ [package] name = "nym-async-file-watcher" -version = "0.1.0" +version.workspace = true edition.workspace = true license.workspace = true +description = "Simple file watcher that sends a notification whenever there was any change in the watched file" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/common/authenticator-requests/Cargo.toml b/common/authenticator-requests/Cargo.toml index 6126a18f80..89873c5c20 100644 --- a/common/authenticator-requests/Cargo.toml +++ b/common/authenticator-requests/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "nym-authenticator-requests" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "Crate defining requests and responses for the Nym authenticator client" [dependencies] base64 = { workspace = true } @@ -18,12 +19,12 @@ strum_macros = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true } -nym-credentials-interface = { path = "../credentials-interface" } -nym-crypto = { path = "../crypto", features = ["asymmetric"] } -nym-network-defaults = { path = "../network-defaults" } -nym-service-provider-requests-common = { path = "../service-provider-requests-common" } -nym-sphinx = { path = "../nymsphinx" } -nym-wireguard-types = { path = "../wireguard-types" } +nym-credentials-interface = { workspace = true } +nym-crypto = { workspace = true, features = ["asymmetric"] } +nym-network-defaults = { workspace = true } +nym-service-provider-requests-common = { workspace = true } +nym-sphinx = { workspace = true } +nym-wireguard-types = { workspace = true } ## verify: hmac = { workspace = true, optional = true } @@ -31,7 +32,7 @@ sha2 = { workspace = true, optional = true } x25519-dalek = { workspace = true, features = ["static_secrets"] } [dev-dependencies] -nym-test-utils = { path = "../test-utils" } +nym-test-utils = { workspace = true } [features] default = ["verify"] @@ -39,4 +40,4 @@ default = ["verify"] verify = ["hmac", "sha2"] [lints] -workspace = true \ No newline at end of file +workspace = true diff --git a/common/authenticator-requests/src/lib.rs b/common/authenticator-requests/src/lib.rs index 226c78adee..eb5b14b5df 100644 --- a/common/authenticator-requests/src/lib.rs +++ b/common/authenticator-requests/src/lib.rs @@ -18,6 +18,7 @@ mod util; mod version; pub use error::Error; +pub use util::{authenticator_ipv4_to_ipv6, authenticator_ipv6_to_ipv4}; pub use v6 as latest; pub use version::AuthenticatorVersion; diff --git a/common/authenticator-requests/src/response.rs b/common/authenticator-requests/src/response.rs index 84b6ae8526..467fd25514 100644 --- a/common/authenticator-requests/src/response.rs +++ b/common/authenticator-requests/src/response.rs @@ -7,6 +7,7 @@ use crate::traits::{ TopUpBandwidthResponse, UpgradeModeStatus, }; use crate::{v2, v3, v4, v5, v6}; +use nym_sphinx::addressing::Recipient; #[derive(Debug)] pub enum AuthenticatorResponse { @@ -17,6 +18,17 @@ pub enum AuthenticatorResponse { UpgradeMode(Box), } +pub struct SerialisedResponse { + pub bytes: Vec, + pub reply_to: Option, +} + +impl SerialisedResponse { + pub fn new(bytes: Vec, reply_to: Option) -> Self { + Self { bytes, reply_to } + } +} + impl UpgradeModeStatus for AuthenticatorResponse { fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus { match self { diff --git a/common/authenticator-requests/src/util.rs b/common/authenticator-requests/src/util.rs index cb1269f10c..451b4a77b5 100644 --- a/common/authenticator-requests/src/util.rs +++ b/common/authenticator-requests/src/util.rs @@ -1,6 +1,38 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use nym_network_defaults::{WG_TUN_DEVICE_IP_ADDRESS_V4, WG_TUN_DEVICE_IP_ADDRESS_V6}; +use std::net::{Ipv4Addr, Ipv6Addr}; + +pub fn authenticator_ipv6_to_ipv4(addr: Ipv6Addr) -> Ipv4Addr { + let before_last_byte = addr.octets()[14]; + let last_byte = addr.octets()[15]; + + Ipv4Addr::new( + WG_TUN_DEVICE_IP_ADDRESS_V4.octets()[0], + WG_TUN_DEVICE_IP_ADDRESS_V4.octets()[1], + before_last_byte, + last_byte, + ) +} + +pub fn authenticator_ipv4_to_ipv6(addr: Ipv4Addr) -> Ipv6Addr { + let before_last_byte = addr.octets()[2]; + let last_byte = addr.octets()[3]; + + let last_bytes = ((before_last_byte as u16) << 8) | last_byte as u16; + 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, + ) +} + #[cfg(test)] pub(crate) mod tests { pub(crate) const CREDENTIAL_BYTES: [u8; 1245] = [ diff --git a/common/authenticator-requests/src/v4/registration.rs b/common/authenticator-requests/src/v4/registration.rs index b1ee074dfd..e718bbab5c 100644 --- a/common/authenticator-requests/src/v4/registration.rs +++ b/common/authenticator-requests/src/v4/registration.rs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use crate::error::Error; +use crate::util::{authenticator_ipv4_to_ipv6, authenticator_ipv6_to_ipv4}; use base64::{Engine, engine::general_purpose}; 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; @@ -56,27 +56,11 @@ impl fmt::Display for IpPair { impl From 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 (ipv4, ipv6) = match value { + IpAddr::V4(ipv4) => (ipv4, authenticator_ipv4_to_ipv6(ipv4)), + IpAddr::V6(ipv6_addr) => (authenticator_ipv6_to_ipv4(ipv6_addr), ipv6_addr), }; - 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) } } diff --git a/common/authenticator-requests/src/v5/registration.rs b/common/authenticator-requests/src/v5/registration.rs index 5154400f93..6000dd6760 100644 --- a/common/authenticator-requests/src/v5/registration.rs +++ b/common/authenticator-requests/src/v5/registration.rs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use crate::error::Error; +use crate::util::{authenticator_ipv4_to_ipv6, authenticator_ipv6_to_ipv4}; use base64::{Engine, engine::general_purpose}; 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; @@ -54,27 +54,11 @@ impl fmt::Display for IpPair { impl From 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 (ipv4, ipv6) = match value { + IpAddr::V4(ipv4) => (ipv4, authenticator_ipv4_to_ipv6(ipv4)), + IpAddr::V6(ipv6_addr) => (authenticator_ipv6_to_ipv4(ipv6_addr), ipv6_addr), }; - 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) } } diff --git a/common/authenticator-requests/src/v6/registration.rs b/common/authenticator-requests/src/v6/registration.rs index 11fcf34116..a433a4e7e9 100644 --- a/common/authenticator-requests/src/v6/registration.rs +++ b/common/authenticator-requests/src/v6/registration.rs @@ -3,13 +3,12 @@ use crate::error::Error; use crate::models::BandwidthClaim; +use crate::util::{authenticator_ipv4_to_ipv6, authenticator_ipv6_to_ipv4}; use base64::{Engine, engine::general_purpose}; -use nym_network_defaults::constants::{WG_TUN_DEVICE_IP_ADDRESS_V4, WG_TUN_DEVICE_IP_ADDRESS_V6}; use nym_wireguard_types::PeerPublicKey; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; -use std::time::SystemTime; use std::{fmt, ops::Deref, str::FromStr}; #[cfg(feature = "verify")] @@ -20,13 +19,11 @@ use nym_crypto::asymmetric::x25519::{PrivateKey, PublicKey}; use sha2::Sha256; pub type PendingRegistrations = HashMap; -pub type PrivateIPs = HashMap; #[cfg(feature = "verify")] pub type HmacSha256 = Hmac; pub type Nonce = u64; -pub type Taken = Option; #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct IpPair { @@ -54,27 +51,11 @@ impl fmt::Display for IpPair { impl From for IpPair { fn from(value: IpAddr) -> Self { - let (before_last_byte, last_byte) = match value { - IpAddr::V4(ipv4_addr) => (ipv4_addr.octets()[2], ipv4_addr.octets()[3]), - IpAddr::V6(ipv6_addr) => (ipv6_addr.octets()[14], ipv6_addr.octets()[15]), + let (ipv4, ipv6) = match value { + IpAddr::V4(ipv4) => (ipv4, authenticator_ipv4_to_ipv6(ipv4)), + IpAddr::V6(ipv6_addr) => (authenticator_ipv6_to_ipv4(ipv6_addr), ipv6_addr), }; - 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) } } diff --git a/common/bandwidth-controller/Cargo.toml b/common/bandwidth-controller/Cargo.toml index a94abcc1cb..d93ddbab85 100644 --- a/common/bandwidth-controller/Cargo.toml +++ b/common/bandwidth-controller/Cargo.toml @@ -1,8 +1,12 @@ [package] name = "nym-bandwidth-controller" -version = "0.1.0" +version.workspace = true edition = "2021" license.workspace = true +description = "Crate for controlling the use of zknym credentials to ensure constant bandwidth availability for NymVPN app" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -12,14 +16,14 @@ log = { workspace = true } rand = { workspace = true } thiserror = { workspace = true } -nym-credential-storage = { path = "../credential-storage" } -nym-credentials = { path = "../credentials" } -nym-credentials-interface = { path = "../credentials-interface" } -nym-crypto = { path = "../crypto", features = ["rand", "asymmetric", "stream_cipher", "aes", "hashing"] } -nym-ecash-time = { path = "../ecash-time" } -nym-task = { path = "../task" } -nym-validator-client = { path = "../client-libs/validator-client", default-features = false } +nym-credential-storage = { workspace = true } +nym-credentials = { workspace = true } +nym-credentials-interface = { workspace = true } +nym-crypto = { workspace = true, features = ["rand", "asymmetric", "stream_cipher", "aes", "hashing"] } +nym-ecash-time = { workspace = true } +nym-task = { workspace = true } +nym-validator-client = { workspace = true } [target."cfg(not(target_arch = \"wasm32\"))".dependencies.nym-validator-client] -path = "../client-libs/validator-client" +workspace = true features = ["http-client"] diff --git a/common/bandwidth-controller/src/mock.rs b/common/bandwidth-controller/src/mock.rs index d889c83987..5bd7a04ebe 100644 --- a/common/bandwidth-controller/src/mock.rs +++ b/common/bandwidth-controller/src/mock.rs @@ -21,7 +21,7 @@ pub struct MockBandwidthController { impl BandwidthTicketProvider for MockBandwidthController { async fn get_ecash_ticket( &self, - _ticket_type: TicketType, + ticket_type: TicketType, _gateway_id: PublicKey, tickets_to_spend: u32, ) -> Result { @@ -100,6 +100,10 @@ impl BandwidthTicketProvider for MockBandwidthController { let mut credential = CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES) .expect("Failed to deserialize test credential - this is a bug in the test harness"); + // change the ticket type to the requested ticket + // note that verification outside mocks is going to fail + credential.payment.t_type = ticket_type.to_repr() as u8; + // Update spend_date to today to pass validation credential.spend_date = OffsetDateTime::now_utc().date(); diff --git a/common/bandwidth-controller/src/traits.rs b/common/bandwidth-controller/src/traits.rs index d611649004..bb958f482b 100644 --- a/common/bandwidth-controller/src/traits.rs +++ b/common/bandwidth-controller/src/traits.rs @@ -57,3 +57,22 @@ where Ok(Some(token)) } } + +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +impl BandwidthTicketProvider for Box { + async fn get_ecash_ticket( + &self, + ticket_type: TicketType, + gateway_id: ed25519::PublicKey, + tickets_to_spend: u32, + ) -> Result { + (**self) + .get_ecash_ticket(ticket_type, gateway_id, tickets_to_spend) + .await + } + + async fn get_upgrade_mode_token(&self) -> Result, BandwidthControllerError> { + (**self).get_upgrade_mode_token().await + } +} diff --git a/common/bin-common/Cargo.toml b/common/bin-common/Cargo.toml index aa75898ec4..8220494794 100644 --- a/common/bin-common/Cargo.toml +++ b/common/bin-common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-bin-common" -version = "0.6.0" +version.workspace = true description = "Common code for nym binaries" edition = { workspace = true } authors = { workspace = true } diff --git a/common/bin-common/src/build_information/mod.rs b/common/bin-common/src/build_information/mod.rs index 6c5d13d4f1..5ed969e9e9 100644 --- a/common/bin-common/src/build_information/mod.rs +++ b/common/bin-common/src/build_information/mod.rs @@ -124,6 +124,10 @@ impl BinaryBuildInformation { } } +// to whoever is thinking of modifying this struct. +// you MUST NOT change its structure in any way - adding, removing or changing fields +// otherwise, it will break old clients as bincode serialisation is not backwards compatible +// even if you put `#[serde(default)]` all over the place #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] #[cfg_attr(feature = "bin_info_schema", derive(schemars::JsonSchema))] diff --git a/common/client-core/Cargo.toml b/common/client-core/Cargo.toml index 2f6d87dbc4..77cdf19c98 100644 --- a/common/client-core/Cargo.toml +++ b/common/client-core/Cargo.toml @@ -1,10 +1,14 @@ [package] name = "nym-client-core" -version = "1.1.15" +version.workspace = true authors = ["Dave Hrycyszyn "] edition = "2021" rust-version = "1.85" license.workspace = true +description = "Crate containing core client functionality and configs, used by all other Nym client implentations" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -31,32 +35,32 @@ tracing = { workspace = true } zeroize = { workspace = true } # internal -nym-id = { path = "../nym-id" } -nym-bandwidth-controller = { path = "../bandwidth-controller" } -nym-crypto = { path = "../crypto" } -nym-gateway-client = { path = "../client-libs/gateway-client" } -nym-gateway-requests = { path = "../gateway-requests" } -nym-http-api-client = { path = "../http-api-client", features = ["network-defaults"] } -nym-nonexhaustive-delayqueue = { path = "../nonexhaustive-delayqueue" } -nym-sphinx = { path = "../nymsphinx" } -nym-statistics-common = { path = "../statistics" } -nym-pemstore = { path = "../pemstore" } -nym-topology = { path = "../topology", features = ["persistence"] } -nym-validator-client = { path = "../client-libs/validator-client", default-features = false } -nym-task = { path = "../task" } -nym-credentials-interface = { path = "../credentials-interface" } -nym-credential-storage = { path = "../credential-storage" } -nym-network-defaults = { path = "../network-defaults" } -nym-client-core-config-types = { path = "./config-types", features = [ +nym-id = { workspace = true } +nym-bandwidth-controller = { workspace = true } +nym-crypto = { workspace = true } +nym-gateway-client = { workspace = true } +nym-gateway-requests = { workspace = true } +nym-http-api-client = { workspace = true, features = ["network-defaults"] } +nym-nonexhaustive-delayqueue = { workspace = true } +nym-sphinx = { workspace = true } +nym-statistics-common = { workspace = true } +nym-pemstore = { workspace = true } +nym-topology = { workspace = true, features = ["persistence"] } +nym-validator-client = { workspace = true } +nym-task = { workspace = true } +nym-credentials-interface = { workspace = true } +nym-credential-storage = { workspace = true } +nym-network-defaults = { workspace = true } +nym-client-core-config-types = { workspace = true, features = [ "disk-persistence", ] } -nym-client-core-surb-storage = { path = "./surb-storage" } -nym-client-core-gateways-storage = { path = "./gateways-storage" } -nym-ecash-time = { path = "../ecash-time" } -nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" } +nym-client-core-surb-storage = { workspace = true } +nym-client-core-gateways-storage = { workspace = true } +nym-ecash-time = { workspace = true } +nym-mixnet-contract-common = { workspace = true } [target."cfg(not(target_arch = \"wasm32\"))".dependencies] -nym-mixnet-client = { path = "../client-libs/mixnet-client", default-features = false } +nym-mixnet-client = { workspace = true } ### For serving prometheus metrics [target."cfg(not(target_arch = \"wasm32\"))".dependencies.hyper] @@ -105,8 +109,8 @@ features = ["tokio"] workspace = true features = ["futures"] -[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-utils] -path = "../wasm/utils" +[target."cfg(target_arch = \"wasm32\")".dependencies.nym-wasm-utils] +workspace = true features = ["websocket"] [target."cfg(target_arch = \"wasm32\")".dependencies.time] diff --git a/common/client-core/config-types/Cargo.toml b/common/client-core/config-types/Cargo.toml index 31184905f5..4c586ec1c3 100644 --- a/common/client-core/config-types/Cargo.toml +++ b/common/client-core/config-types/Cargo.toml @@ -1,8 +1,12 @@ [package] name = "nym-client-core-config-types" -version = "0.1.0" +version.workspace = true edition = "2021" license.workspace = true +description = "Low level configs and constants used by Nym clients and nodes" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -12,14 +16,14 @@ serde = { workspace = true, features = ["derive"] } thiserror.workspace = true url = { workspace = true, features = ["serde"] } -nym-config = { path = "../../config" } +nym-config = { workspace = true } -nym-pemstore = { path = "../../pemstore", optional = true } +nym-pemstore = { workspace = true , optional = true } # those are pulling so many deps T.T -nym-sphinx-params = { path = "../../nymsphinx/params" } -nym-sphinx-addressing = { path = "../../nymsphinx/addressing" } -nym-statistics-common = { path = "../../statistics" } +nym-sphinx-params = { workspace = true } +nym-sphinx-addressing = { workspace = true } +nym-statistics-common = { workspace = true } [features] diff --git a/common/client-core/gateways-storage/Cargo.toml b/common/client-core/gateways-storage/Cargo.toml index dc25a2b748..1a1e9f7c2d 100644 --- a/common/client-core/gateways-storage/Cargo.toml +++ b/common/client-core/gateways-storage/Cargo.toml @@ -1,9 +1,13 @@ [package] name = "nym-client-core-gateways-storage" -version = "0.1.0" +version.workspace = true edition = "2021" license.workspace = true rust-version.workspace = true +description = "Functionality for Nym clients to store and retrive Gateway connections" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -17,9 +21,9 @@ tracing.workspace = true url.workspace = true zeroize = { workspace = true, features = ["zeroize_derive"] } -nym-crypto = { path = "../../crypto", features = ["asymmetric"] } -nym-gateway-requests = { path = "../../gateway-requests" } -nym-gateway-client = { path = "../../client-libs/gateway-client" } +nym-crypto = { workspace = true, features = ["asymmetric"] } +nym-gateway-requests = { workspace = true} +nym-gateway-client = { workspace = true} [target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx] workspace = true diff --git a/common/client-core/src/client/base_client/mod.rs b/common/client-core/src/client/base_client/mod.rs index 600085b4cf..92565c144b 100644 --- a/common/client-core/src/client/base_client/mod.rs +++ b/common/client-core/src/client/base_client/mod.rs @@ -71,7 +71,7 @@ use url::Url; #[cfg(target_arch = "wasm32")] #[cfg(debug_assertions)] -use wasm_utils::console_log; +use nym_wasm_utils::console_log; /// Default number of retries for Nym API requests when using network details with domain fronting. /// This allows the client to try alternative URLs if the primary endpoint is unavailable. diff --git a/common/client-core/src/client/base_client/storage/mod.rs b/common/client-core/src/client/base_client/storage/mod.rs index 5f24c8ef7f..bc92fb86bc 100644 --- a/common/client-core/src/client/base_client/storage/mod.rs +++ b/common/client-core/src/client/base_client/storage/mod.rs @@ -26,7 +26,7 @@ use crate::{ error::ClientCoreError, }; #[cfg(all(not(target_arch = "wasm32"), feature = "fs-credentials-storage"))] -use nym_credential_storage::persistent_storage::PersistentStorage as PersistentCredentialStorage; +pub use nym_credential_storage::persistent_storage::PersistentStorage as PersistentCredentialStorage; pub use nym_client_core_gateways_storage as gateways_storage; pub use nym_client_core_gateways_storage::{GatewaysDetailsStore, InMemGatewaysDetails}; diff --git a/common/client-core/src/client/real_messages_control/real_traffic_stream.rs b/common/client-core/src/client/real_messages_control/real_traffic_stream.rs index 1b90208b6d..12fa62e3ad 100644 --- a/common/client-core/src/client/real_messages_control/real_traffic_stream.rs +++ b/common/client-core/src/client/real_messages_control/real_traffic_stream.rs @@ -31,7 +31,7 @@ use tracing::*; #[cfg(not(target_arch = "wasm32"))] use tokio::time::{sleep, Sleep}; -// use wasm_utils::console_log; +// use nym_wasm_utils::console_log; #[cfg(target_arch = "wasm32")] use wasmtimer::tokio::{sleep, Sleep}; mod sending_delay_controller; diff --git a/common/client-core/src/init/helpers.rs b/common/client-core/src/init/helpers.rs index 8cb926fe55..3400981309 100644 --- a/common/client-core/src/init/helpers.rs +++ b/common/client-core/src/init/helpers.rs @@ -23,6 +23,8 @@ use url::Url; use crate::init::websockets::connect_async; use nym_topology::NodeId; +#[cfg(target_arch = "wasm32")] +use nym_wasm_utils::websocket::JSWebsocket; #[cfg(not(target_arch = "wasm32"))] use tokio::net::TcpStream; #[cfg(not(target_arch = "wasm32"))] @@ -32,8 +34,6 @@ use tokio::time::Instant; #[cfg(not(target_arch = "wasm32"))] use tokio_tungstenite::{MaybeTlsStream, WebSocketStream}; #[cfg(target_arch = "wasm32")] -use wasm_utils::websocket::JSWebsocket; -#[cfg(target_arch = "wasm32")] use wasmtimer::std::Instant; #[cfg(target_arch = "wasm32")] use wasmtimer::tokio::sleep; diff --git a/common/client-core/surb-storage/Cargo.toml b/common/client-core/surb-storage/Cargo.toml index be03487d95..04168ec19e 100644 --- a/common/client-core/surb-storage/Cargo.toml +++ b/common/client-core/surb-storage/Cargo.toml @@ -1,8 +1,12 @@ [package] name = "nym-client-core-surb-storage" -version = "0.1.0" +version.workspace = true edition = "2021" license.workspace = true +description = "Functionality for Nym clients to generate and use Single Use Reply Blocks (SURBs)" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -13,9 +17,9 @@ tracing.workspace = true thiserror.workspace = true time.workspace = true -nym-crypto = { path = "../../crypto", optional = true, default-features = false } -nym-sphinx = { path = "../../nymsphinx" } -nym-task = { path = "../../task" } +nym-crypto = { workspace = true, optional = true, default-features = false } +nym-sphinx = { workspace = true } +nym-task = { workspace = true } [target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio] workspace = true @@ -26,8 +30,8 @@ workspace = true features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate", "time"] optional = true -[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx-pool-guard] -path = "../../../sqlx-pool-guard" +[target."cfg(not(target_arch = \"wasm32\"))".dependencies.nym-sqlx-pool-guard] +workspace = true [build-dependencies] anyhow = { workspace = true } diff --git a/common/client-core/surb-storage/src/backend/fs_backend/manager.rs b/common/client-core/surb-storage/src/backend/fs_backend/manager.rs index 6edde091ce..b0a6ec68f7 100644 --- a/common/client-core/surb-storage/src/backend/fs_backend/manager.rs +++ b/common/client-core/surb-storage/src/backend/fs_backend/manager.rs @@ -13,7 +13,7 @@ use std::path::Path; use time::OffsetDateTime; use tracing::{error, info}; -use sqlx_pool_guard::SqlitePoolGuard; +use nym_sqlx_pool_guard::SqlitePoolGuard; #[derive(Debug, Clone)] pub struct StorageManager { diff --git a/common/client-libs/gateway-client/Cargo.toml b/common/client-libs/gateway-client/Cargo.toml index 969d94807e..914c23a45e 100644 --- a/common/client-libs/gateway-client/Cargo.toml +++ b/common/client-libs/gateway-client/Cargo.toml @@ -1,9 +1,13 @@ [package] name = "nym-gateway-client" -version = "0.1.0" +version.workspace = true authors = ["Jędrzej Stuczyński "] edition = "2021" license.workspace = true +description = "Functions and types for Nym client <> Gateway connections" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -21,19 +25,19 @@ time.workspace = true zeroize.workspace = true # internal -nym-bandwidth-controller = { path = "../../bandwidth-controller" } -nym-credentials = { path = "../../credentials" } -nym-credential-storage = { path = "../../credential-storage" } -nym-credentials-interface = { path = "../../credentials-interface" } -nym-crypto = { path = "../../crypto" } -nym-gateway-requests = { path = "../../gateway-requests" } -nym-http-api-client = { path = "../../http-api-client" } -nym-network-defaults = { path = "../../network-defaults" } -nym-sphinx = { path = "../../nymsphinx" } -nym-statistics-common = { path = "../../statistics" } -nym-pemstore = { path = "../../pemstore" } -nym-validator-client = { path = "../validator-client", default-features = false } -nym-task = { path = "../../task" } +nym-bandwidth-controller = { workspace = true } +nym-credentials = { workspace = true } +nym-credential-storage = { workspace = true } +nym-credentials-interface = { workspace = true } +nym-crypto = { workspace = true } +nym-gateway-requests = { workspace = true } +nym-http-api-client = { workspace = true } +nym-network-defaults = { workspace = true } +nym-sphinx = { workspace = true } +nym-statistics-common = { workspace = true } +nym-pemstore = { workspace = true } +nym-validator-client = { workspace = true, default-features = false } +nym-task = { workspace = true } serde = { workspace = true, features = ["derive"] } @@ -61,8 +65,8 @@ workspace = true [target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen-futures] workspace = true -[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-utils] -path = "../../wasm/utils" +[target."cfg(target_arch = \"wasm32\")".dependencies.nym-wasm-utils] +workspace = true features = ["websocket"] [target."cfg(target_arch = \"wasm32\")".dependencies.gloo-utils] @@ -90,4 +94,4 @@ features = ["js"] wasm = [] [lints] -workspace = true \ No newline at end of file +workspace = true diff --git a/common/client-libs/gateway-client/src/client/mod.rs b/common/client-libs/gateway-client/src/client/mod.rs index 1d75b31835..a657617a8c 100644 --- a/common/client-libs/gateway-client/src/client/mod.rs +++ b/common/client-libs/gateway-client/src/client/mod.rs @@ -41,11 +41,11 @@ use std::os::fd::RawFd; #[cfg(not(target_arch = "wasm32"))] use tokio::time::sleep; +#[cfg(target_arch = "wasm32")] +use nym_wasm_utils::websocket::JSWebsocket; #[cfg(not(unix))] use std::os::raw::c_int as RawFd; #[cfg(target_arch = "wasm32")] -use wasm_utils::websocket::JSWebsocket; -#[cfg(target_arch = "wasm32")] use wasmtimer::tokio::sleep; pub mod config; diff --git a/common/client-libs/gateway-client/src/socket_state.rs b/common/client-libs/gateway-client/src/socket_state.rs index 1a464aa304..c07b360be8 100644 --- a/common/client-libs/gateway-client/src/socket_state.rs +++ b/common/client-libs/gateway-client/src/socket_state.rs @@ -29,7 +29,7 @@ use tokio::net::TcpStream; use tokio_tungstenite::{MaybeTlsStream, WebSocketStream}; #[cfg(target_arch = "wasm32")] -use wasm_utils::websocket::JSWebsocket; +use nym_wasm_utils::websocket::JSWebsocket; // type alias for not having to type the whole thing every single time (and now it makes it easier // to use different types based on compilation target) diff --git a/common/client-libs/mixnet-client/Cargo.toml b/common/client-libs/mixnet-client/Cargo.toml index 2a8a39383b..72f838e50b 100644 --- a/common/client-libs/mixnet-client/Cargo.toml +++ b/common/client-libs/mixnet-client/Cargo.toml @@ -1,9 +1,13 @@ [package] name = "nym-mixnet-client" -version = "0.1.0" +version.workspace = true authors = ["Jedrzej Stuczynski "] edition = "2021" license.workspace = true +description = "Client for Mix Node <> Mix Node & Mix Node <> Gateway communication" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -16,14 +20,14 @@ tokio-util = { workspace = true, features = ["codec"], optional = true } tokio-stream = { workspace = true } # internal -nym-noise = { path = "../../nymnoise" } -nym-sphinx = { path = "../../nymsphinx" } -nym-task = { path = "../../task", optional = true } +nym-noise = { workspace = true } +nym-sphinx = { workspace = true } +nym-task = { workspace = true, optional = true } [features] default = ["client"] client = ["tokio-util", "nym-task", "tokio/net", "tokio/rt"] [dev-dependencies] -nym-crypto = { path = "../../crypto" } +nym-crypto = { workspace = true } rand = { workspace = true } diff --git a/common/client-libs/validator-client/Cargo.toml b/common/client-libs/validator-client/Cargo.toml index ec4eb62bec..4faa1a0943 100644 --- a/common/client-libs/validator-client/Cargo.toml +++ b/common/client-libs/validator-client/Cargo.toml @@ -1,10 +1,14 @@ [package] name = "nym-validator-client" -version = "0.1.0" +version.workspace = true authors = ["Jędrzej Stuczyński "] edition = "2021" rust-version = "1.85" license.workspace = true +description = "Client for interacting with Nyx Cosmos SDK blockchain" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -12,18 +16,18 @@ license.workspace = true base64 = { workspace = true } colored = { workspace = true } -nym-coconut-dkg-common = { path = "../../cosmwasm-smart-contracts/coconut-dkg" } -nym-contracts-common = { path = "../../cosmwasm-smart-contracts/contracts-common" } -nym-mixnet-contract-common = { path = "../../cosmwasm-smart-contracts/mixnet-contract" } -nym-vesting-contract-common = { path = "../../cosmwasm-smart-contracts/vesting-contract" } -nym-ecash-contract-common = { path = "../../cosmwasm-smart-contracts/ecash-contract" } -nym-multisig-contract-common = { path = "../../cosmwasm-smart-contracts/multisig-contract" } -nym-group-contract-common = { path = "../../cosmwasm-smart-contracts/group-contract" } -nym-performance-contract-common = { path = "../../cosmwasm-smart-contracts/nym-performance-contract" } -nym-serde-helpers = { path = "../../serde-helpers", features = ["hex", "base64"] } +nym-coconut-dkg-common = { workspace = true } +nym-contracts-common = { workspace = true } +nym-mixnet-contract-common = { workspace = true } +nym-vesting-contract-common = { workspace = true } +nym-ecash-contract-common = { workspace = true } +nym-multisig-contract-common = { workspace = true } +nym-group-contract-common = { workspace = true } +nym-performance-contract-common = { workspace = true } +nym-serde-helpers = { workspace = true, features = ["hex", "base64"] } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } -nym-http-api-client = { path = "../../../common/http-api-client" } +nym-http-api-client = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true } url = { workspace = true, features = ["serde"] } @@ -31,13 +35,13 @@ tokio = { workspace = true, features = ["sync", "time"] } time = { workspace = true, features = ["formatting"] } futures = { workspace = true } -nym-compact-ecash = { path = "../../nym_offline_compact_ecash" } -nym-network-defaults = { path = "../../network-defaults" } -nym-api-requests = { path = "../../../nym-api/nym-api-requests" } +nym-compact-ecash = { workspace = true } +nym-network-defaults = { workspace = true } +nym-api-requests = { workspace = true } async-trait = { workspace = true } bip39 = { workspace = true, features = ["rand"] } -nym-config = { path = "../../config" } +nym-config = { workspace = true } cosmrs = { workspace = true, features = ["bip32", "cosmwasm"] } # note that this has the same version as used by cosmrs diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs index eb23ba60ca..d461157f52 100644 --- a/common/client-libs/validator-client/src/client.rs +++ b/common/client-libs/validator-client/src/client.rs @@ -20,7 +20,7 @@ use nym_api_requests::ecash::{ }; use nym_api_requests::models::{ ApiHealthResponse, GatewayCoreStatusResponse, HistoricalPerformanceResponse, - MixnodeCoreStatusResponse, NymNodeDescription, + MixnodeCoreStatusResponse, NymNodeDescriptionV1, }; use nym_api_requests::nym_nodes::{ NodesByAddressesResponse, SemiSkimmedNodesWithMetadata, SkimmedNode, SkimmedNodesWithMetadata, @@ -273,48 +273,23 @@ impl Client { Ok(history) } - // TODO: combine with NymApiClient... + // #[deprecated(note = "use get_all_cached_described_nodes_v2 instead")] pub async fn get_all_cached_described_nodes( &self, - ) -> Result, ValidatorClientError> { - // TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere - let mut page = 0; - let mut descriptions = Vec::new(); - - loop { - let mut res = self.nym_api.get_nodes_described(Some(page), None).await?; - - descriptions.append(&mut res.data); - if descriptions.len() < res.pagination.total { - page += 1 - } else { - break; - } - } - - Ok(descriptions) + ) -> Result, ValidatorClientError> { + Ok(self.nym_api.get_all_described_nodes().await?) } - // TODO: combine with NymApiClient... + // pub async fn get_all_cached_described_nodes_v2( + // &self, + // ) -> Result, ValidatorClientError> { + // Ok(self.nym_api.get_all_described_nodes_v2().await?) + // } + pub async fn get_all_cached_bonded_nym_nodes( &self, ) -> Result, ValidatorClientError> { - // TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere - let mut page = 0; - let mut bonds = Vec::new(); - - loop { - let mut res = self.nym_api.get_nym_nodes(Some(page), None).await?; - - bonds.append(&mut res.data); - if bonds.len() < res.pagination.total { - page += 1 - } else { - break; - } - } - - Ok(bonds) + self.nym_api.get_all_bonded_nym_nodes().await } pub async fn blind_sign( @@ -498,9 +473,10 @@ impl NymApiClient { Ok(self.nym_api.health().await?) } + // #[deprecated(note = "use .get_all_described_nodes_v2 instead")] pub async fn get_all_described_nodes( &self, - ) -> Result, ValidatorClientError> { + ) -> Result, ValidatorClientError> { // TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere let mut page = 0; let mut descriptions = Vec::new(); @@ -519,6 +495,30 @@ impl NymApiClient { Ok(descriptions) } + // pub async fn get_all_described_nodes_v2( + // &self, + // ) -> Result, ValidatorClientError> { + // // TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere + // let mut page = 0; + // let mut descriptions = Vec::new(); + // + // loop { + // let mut res = self + // .nym_api + // .get_nodes_described_v2(Some(page), None) + // .await?; + // + // descriptions.append(&mut res.data); + // if descriptions.len() < res.pagination.total { + // page += 1 + // } else { + // break; + // } + // } + // + // Ok(descriptions) + // } + pub async fn get_all_bonded_nym_nodes( &self, ) -> Result, ValidatorClientError> { diff --git a/common/client-libs/validator-client/src/nym_api/mod.rs b/common/client-libs/validator-client/src/nym_api/mod.rs index 8c549491e9..08bcd7e7f3 100644 --- a/common/client-libs/validator-client/src/nym_api/mod.rs +++ b/common/client-libs/validator-client/src/nym_api/mod.rs @@ -17,7 +17,8 @@ use nym_api_requests::ecash::VerificationKeyResponse; use nym_api_requests::models::{ AnnotationResponse, ApiHealthResponse, BinaryBuildInformationOwned, ChainBlocksStatusResponse, ChainStatusResponse, KeyRotationInfoResponse, NodePerformanceResponse, NodeRefreshBody, - NymNodeDescription, PerformanceHistoryResponse, RewardedSetResponse, SignerInformationResponse, + NymNodeDescriptionV1, PerformanceHistoryResponse, RewardedSetResponse, + SignerInformationResponse, }; use nym_api_requests::nym_nodes::{ NodesByAddressesRequestBody, NodesByAddressesResponse, PaginatedCachedNodesResponseV1, @@ -116,11 +117,12 @@ pub trait NymApiClientExt: ApiClient { } #[tracing::instrument(level = "debug", skip_all)] + // #[deprecated(note = "use .get_nodes_described_v2 instead")] async fn get_nodes_described( &self, page: Option, per_page: Option, - ) -> Result, NymAPIError> { + ) -> Result, NymAPIError> { let mut params = Vec::new(); if let Some(page) = page { @@ -142,6 +144,33 @@ pub trait NymApiClientExt: ApiClient { .await } + // #[tracing::instrument(level = "debug", skip_all)] + // async fn get_nodes_described_v2( + // &self, + // page: Option, + // per_page: Option, + // ) -> Result, NymAPIError> { + // let mut params = Vec::new(); + // + // if let Some(page) = page { + // params.push(("page", page.to_string())) + // } + // + // if let Some(per_page) = per_page { + // params.push(("per_page", per_page.to_string())) + // } + // + // self.get_json( + // &[ + // routes::V2_API_VERSION, + // routes::NYM_NODES_ROUTES, + // routes::NYM_NODES_DESCRIBED, + // ], + // ¶ms, + // ) + // .await + // } + async fn get_current_rewarded_set(&self) -> Result { self.get_rewarded_set().await } @@ -273,7 +302,9 @@ pub trait NymApiClientExt: ApiClient { Ok(SkimmedNodesWithMetadata::new(nodes, metadata)) } - async fn get_all_described_nodes(&self) -> Result, NymAPIError> { + // #[deprecated(note = "use .get_all_described_nodes_v2 instead")] + // #[allow(deprecated)] + async fn get_all_described_nodes(&self) -> Result, NymAPIError> { // TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere let mut page = 0; let mut descriptions = Vec::new(); @@ -292,6 +323,25 @@ pub trait NymApiClientExt: ApiClient { Ok(descriptions) } + // async fn (&self) -> Result, NymAPIError> { + // // TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere + // let mut page = 0; + // let mut descriptions = Vec::new(); + // + // loop { + // let mut res = self.get_nodes_described_v2(Some(page), None).await?; + // + // descriptions.append(&mut res.data); + // if descriptions.len() < res.pagination.total { + // page += 1 + // } else { + // break; + // } + // } + // + // Ok(descriptions) + // } + #[tracing::instrument(level = "debug", skip_all)] async fn get_nym_nodes( &self, diff --git a/common/commands/Cargo.toml b/common/commands/Cargo.toml index 4398342d4c..1c7d1f8da8 100644 --- a/common/commands/Cargo.toml +++ b/common/commands/Cargo.toml @@ -1,9 +1,13 @@ [package] name = "nym-cli-commands" -version = "1.0.0" +version.workspace = true authors.workspace = true edition = "2021" license.workspace = true +description = "Common commands crate used by the nym-cli tool for interacting with the Nyx Cosmos SDK blockchain and Mixnet endpoints" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true [dependencies] anyhow = { workspace = true } @@ -37,28 +41,28 @@ zeroize = { workspace = true } cosmrs = { workspace = true } cosmwasm-std = { workspace = true } -nym-validator-client = { path = "../client-libs/validator-client" } -nym-http-api-client = { path = "../http-api-client" } -nym-bin-common = { path = "../../common/bin-common", features = ["output_format"] } -nym-crypto = { path = "../../common/crypto", features = ["asymmetric"] } -nym-network-defaults = { path = "../network-defaults" } -nym-contracts-common = { path = "../cosmwasm-smart-contracts/contracts-common" } -nym-bandwidth-controller = { path = "../../common/bandwidth-controller" } -nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" } -nym-vesting-contract-common = { path = "../cosmwasm-smart-contracts/vesting-contract" } -nym-coconut-dkg-common = { path = "../cosmwasm-smart-contracts/coconut-dkg" } -nym-multisig-contract-common = { path = "../cosmwasm-smart-contracts/multisig-contract" } -nym-ecash-contract-common = { path = "../cosmwasm-smart-contracts/ecash-contract" } -nym-ecash-time = { path = "../../common/ecash-time" } -nym-sphinx = { path = "../../common/nymsphinx" } -nym-client-core = { path = "../../common/client-core" } -nym-config = { path = "../../common/config" } -nym-credentials = { path = "../../common/credentials" } -nym-credentials-interface = { path = "../../common/credentials-interface" } -nym-credential-storage = { path = "../../common/credential-storage" } -nym-credential-utils = { path = "../../common/credential-utils" } -nym-id = { path = "../nym-id" } -nym-credential-proxy-requests = { path = "../../nym-credential-proxy/nym-credential-proxy-requests" } +nym-validator-client = { workspace = true} +nym-http-api-client = { workspace = true} +nym-bin-common = { workspace = true, features = ["output_format"] } +nym-crypto = { workspace = true, features = ["asymmetric"] } +nym-network-defaults = { workspace = true } +nym-contracts-common = { workspace = true } +nym-bandwidth-controller = { workspace = true } +nym-mixnet-contract-common = { workspace = true } +nym-vesting-contract-common = { workspace = true } +nym-coconut-dkg-common = { workspace = true } +nym-multisig-contract-common = { workspace = true } +nym-ecash-contract-common = { workspace = true } +nym-ecash-time = { workspace = true } +nym-sphinx = { workspace = true } +nym-client-core = { workspace = true } +nym-config = { workspace = true } +nym-credentials = { workspace = true } +nym-credentials-interface = { workspace = true } +nym-credential-storage = { workspace = true } +nym-credential-utils = { workspace = true } +nym-id = { workspace = true } +nym-credential-proxy-requests = { workspace = true } -nym-pemstore = { path = "../../common/pemstore", version = "0.3.0" } -nym-types = { path = "../../common/types" } +nym-pemstore = { workspace = true } +nym-types = { workspace = true } diff --git a/common/config/Cargo.toml b/common/config/Cargo.toml index d978fe4aee..4c364f9292 100644 --- a/common/config/Cargo.toml +++ b/common/config/Cargo.toml @@ -1,9 +1,11 @@ [package] name = "nym-config" -version = "0.1.0" +version.workspace = true authors = ["Jedrzej Stuczynski "] edition = "2021" license.workspace = true +homepage.workspace = true +description = "Config related helpers and functions" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -16,7 +18,7 @@ thiserror = { workspace = true } toml = { workspace = true, features = ["display"] } url = { workspace = true } -nym-network-defaults = { path = "../network-defaults", features = ["utoipa"] } +nym-network-defaults = { workspace = true, features = ["utoipa"] } [features] default = ["dirs"] diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/Cargo.toml b/common/cosmwasm-smart-contracts/coconut-dkg/Cargo.toml index d2a9cbd50f..158b920e24 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/Cargo.toml +++ b/common/cosmwasm-smart-contracts/coconut-dkg/Cargo.toml @@ -1,8 +1,12 @@ [package] name = "nym-coconut-dkg-common" -version = "0.1.0" +version.workspace = true edition = "2021" license.workspace = true +description = "Common crate for Nym's DKG cosmwasm contract" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -13,8 +17,8 @@ cw-utils = { workspace = true } cw2 = { workspace = true } cw4 = { workspace = true } -contracts-common = { path = "../contracts-common", package = "nym-contracts-common" } -nym-multisig-contract-common = { path = "../multisig-contract" } +nym-contracts-common = { workspace = true } +nym-multisig-contract-common = { workspace = true } [features] schema = [] diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/dealing.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/dealing.rs index 347a158a55..b65e4109e1 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/src/dealing.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/dealing.rs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use crate::types::{ChunkIndex, DealingIndex, EpochId, PartialContractDealingData}; -use contracts_common::dealings::ContractSafeBytes; use cosmwasm_schema::cw_serde; use cosmwasm_std::Addr; +use nym_contracts_common::dealings::ContractSafeBytes; use std::collections::{BTreeMap, HashMap}; /// Defines the maximum size of a dealing chunk. Currently set to 2kB diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs index d0c8972117..1783dfaf78 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs @@ -6,8 +6,8 @@ use crate::types::{ ChunkIndex, DealingIndex, EncodedBTEPublicKeyWithProof, EpochId, TimeConfiguration, }; use crate::verification_key::VerificationKeyShare; -use contracts_common::IdentityKey; use cosmwasm_schema::cw_serde; +use nym_contracts_common::IdentityKey; #[cfg(feature = "schema")] use crate::{ diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs index 5cf5fd780e..6e4fdef06a 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs @@ -6,9 +6,9 @@ use std::fmt::{Display, Formatter}; use std::str::FromStr; pub use crate::dealer::{DealerDetails, DealerRegistrationDetails, PagedDealerResponse}; -pub use contracts_common::dealings::ContractSafeBytes; pub use cosmwasm_std::{Addr, Coin, Timestamp}; pub use cw4::Cw4Contract; +pub use nym_contracts_common::dealings::ContractSafeBytes; pub type EncodedBTEPublicKeyWithProof = String; pub type EncodedBTEPublicKeyWithProofRef<'a> = &'a str; diff --git a/common/cosmwasm-smart-contracts/contracts-common-testing/Cargo.toml b/common/cosmwasm-smart-contracts/contracts-common-testing/Cargo.toml index 0bb81f1792..69f429b24d 100644 --- a/common/cosmwasm-smart-contracts/contracts-common-testing/Cargo.toml +++ b/common/cosmwasm-smart-contracts/contracts-common-testing/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-contracts-common-testing" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true @@ -9,6 +9,7 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +description = "Common crate for cosmwasm contract tests" [dependencies] anyhow = { workspace = true } @@ -20,7 +21,7 @@ rand_chacha = { workspace = true } rand = { workspace = true } cw-multi-test = { workspace = true } -nym-contracts-common = { path = "../contracts-common" } +nym-contracts-common = { workspace = true } [lints] workspace = true diff --git a/common/cosmwasm-smart-contracts/contracts-common/Cargo.toml b/common/cosmwasm-smart-contracts/contracts-common/Cargo.toml index c13d29a7ec..277351d5d0 100644 --- a/common/cosmwasm-smart-contracts/contracts-common/Cargo.toml +++ b/common/cosmwasm-smart-contracts/contracts-common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-contracts-common" -version = "0.5.0" +version.workspace = true description = "Common library for Nym cosmwasm contracts" edition = { workspace = true } authors = { workspace = true } diff --git a/common/cosmwasm-smart-contracts/easy_addr/Cargo.toml b/common/cosmwasm-smart-contracts/easy_addr/Cargo.toml index 992378fbe0..b8a48b40a7 100644 --- a/common/cosmwasm-smart-contracts/easy_addr/Cargo.toml +++ b/common/cosmwasm-smart-contracts/easy_addr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "easy-addr" -version = "0.1.0" +version.workspace = true edition = "2021" publish = false license.workspace = true @@ -11,4 +11,4 @@ proc-macro = true [dependencies] cosmwasm-std = { workspace = true } quote = { workspace = true } -syn = { workspace = true, features = ["full", "printing", "extra-traits"] } \ No newline at end of file +syn = { workspace = true, features = ["full", "printing", "extra-traits"] } diff --git a/common/cosmwasm-smart-contracts/ecash-contract/Cargo.toml b/common/cosmwasm-smart-contracts/ecash-contract/Cargo.toml index 54bc53fe18..dce59308ce 100644 --- a/common/cosmwasm-smart-contracts/ecash-contract/Cargo.toml +++ b/common/cosmwasm-smart-contracts/ecash-contract/Cargo.toml @@ -1,8 +1,12 @@ [package] name = "nym-ecash-contract-common" -version = "0.1.0" +version.workspace = true edition = "2021" license.workspace = true +description = "Common crate for Nym's ecash/zknym cosmwasm contract" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -11,7 +15,7 @@ bs58.workspace = true cosmwasm-std = { workspace = true } cosmwasm-schema = { workspace = true } cw2 = { workspace = true, optional = true } -nym-multisig-contract-common = { path = "../multisig-contract" } +nym-multisig-contract-common = { workspace = true } thiserror.workspace = true cw-utils = { workspace = true } cw-controllers = { workspace = true } diff --git a/common/cosmwasm-smart-contracts/group-contract/Cargo.toml b/common/cosmwasm-smart-contracts/group-contract/Cargo.toml index e9a942139f..17cb3e83db 100644 --- a/common/cosmwasm-smart-contracts/group-contract/Cargo.toml +++ b/common/cosmwasm-smart-contracts/group-contract/Cargo.toml @@ -1,8 +1,12 @@ [package] name = "nym-group-contract-common" -version = "0.1.0" +version.workspace = true edition = "2021" license.workspace = true +description = "Common crate for Nym's group cosmwasm contract" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true [dependencies] cosmwasm-schema = { workspace = true } diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml b/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml index ed9b7ac125..fb7143723d 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml +++ b/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-mixnet-contract-common" -version = "0.6.0" +version.workspace = true description = "Common library for the Nym mixnet contract" rust-version = "1.85" edition = { workspace = true } @@ -22,7 +22,7 @@ semver = { workspace = true, features = ["serde"] } # we still have to preserve that import for `JsonSchema` for `Layer` type (since we can't use cw_serde macro due to custom serde impl) schemars = { workspace = true } thiserror = { workspace = true } -contracts-common = { path = "../contracts-common", package = "nym-contracts-common", version = "0.5.0" } +nym-contracts-common = { workspace = true } humantime-serde = { workspace = true } utoipa = { workspace = true, optional = true } @@ -41,4 +41,4 @@ schema = ["cw2"] generate-ts = ['ts-rs'] [lints] -workspace = true \ No newline at end of file +workspace = true diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs index 9298b38523..1d27cd69d0 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs @@ -5,10 +5,10 @@ use crate::nym_node::Role; use crate::{ EpochEventId, EpochState, IntervalEventId, NodeId, OperatingCostRange, ProfitMarginRange, }; -use contracts_common::Percent; -use contracts_common::signing::verifier::ApiVerifierError; use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; use cw_controllers::AdminError; +use nym_contracts_common::Percent; +use nym_contracts_common::signing::verifier::ApiVerifierError; use thiserror::Error; #[derive(Error, Debug, PartialEq)] diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/events.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/events.rs index a53c5dc1a9..572b8131a6 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/events.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/events.rs @@ -8,8 +8,8 @@ use crate::nym_node::Role; use crate::reward_params::{ActiveSetUpdate, IntervalRewardParams, IntervalRewardingParamsUpdate}; use crate::rewarding::RewardDistribution; use crate::{BlockHeight, ContractStateParamsUpdate, EpochId, IdentityKeyRef, Interval, NodeId}; -pub use contracts_common::events::*; use cosmwasm_std::{Addr, Coin, Decimal, Event, attr}; +pub use nym_contracts_common::events::*; use std::fmt::Display; pub const EVENT_VERSION_PREFIX: &str = "v2_"; diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/helpers.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/helpers.rs index b5032e64be..dc56386238 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/helpers.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/helpers.rs @@ -7,8 +7,8 @@ use crate::{ EpochEventId, IntervalEventId, MixNodeBond, MixNodeDetails, NodeId, NodeRewarding, NymNodeBond, NymNodeDetails, PendingNodeChanges, }; -use contracts_common::IdentityKeyRef; use cosmwasm_std::{Coin, Decimal, StdError, StdResult, Uint128}; +use nym_contracts_common::IdentityKeyRef; #[track_caller] pub fn compare_decimals(a: Decimal, b: Decimal, epsilon: Option) { diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/lib.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/lib.rs index eb1d533270..3c6f4b6b08 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/lib.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/lib.rs @@ -21,7 +21,6 @@ pub mod types; pub use config_score::*; pub use constants::*; -pub use contracts_common::types::*; pub use cosmwasm_std::{Addr, Coin, Decimal, Fraction}; pub use delegation::{ Delegation, PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse, @@ -41,6 +40,7 @@ pub use mixnode::{ NodeRewarding, PagedMixnodeBondsResponse, UnbondedMixnode, }; pub use msg::*; +pub use nym_contracts_common::types::*; pub use nym_node::{NymNode, NymNodeBond, NymNodeDetails, PendingNodeChanges}; pub use pending_events::{ EpochEventId, IntervalEventId, NumberOfPendingEventsResponse, PendingEpochEvent, diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs index a85d86fedf..4b0d31016d 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs @@ -18,9 +18,9 @@ use crate::{ VersionScoreFormulaParams, }; use crate::{OperatingCostRange, ProfitMarginRange}; -use contracts_common::{IdentityKey, Percent, signing::MessageSignature}; use cosmwasm_schema::cw_serde; use cosmwasm_std::{Coin, Decimal}; +use nym_contracts_common::{IdentityKey, Percent, signing::MessageSignature}; use std::time::Duration; #[cfg(feature = "schema")] @@ -55,9 +55,9 @@ use crate::{ types::{ContractState, ContractStateParams}, }; #[cfg(feature = "schema")] -use contracts_common::{ContractBuildInformation, signing::Nonce}; -#[cfg(feature = "schema")] use cosmwasm_schema::QueryResponses; +#[cfg(feature = "schema")] +use nym_contracts_common::{ContractBuildInformation, signing::Nonce}; #[cw_serde] pub struct InstantiateMsg { diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/nym_node.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/nym_node.rs index 6fc172db09..d82ca38907 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/nym_node.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/nym_node.rs @@ -3,10 +3,10 @@ use crate::error::MixnetContractError; use crate::{EpochEventId, EpochId, Gateway, IntervalEventId, MixNode, NodeId, NodeRewarding}; -use contracts_common::IdentityKey; use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Coin, Decimal, StdError, StdResult}; use cw_storage_plus::{IntKey, Key, KeyDeserialize, PrimaryKey}; +use nym_contracts_common::IdentityKey; use std::fmt::{Display, Formatter}; #[cw_serde] diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/rewarding/helpers.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/rewarding/helpers.rs index 4d9ca75459..f128a9c2c6 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/rewarding/helpers.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/rewarding/helpers.rs @@ -1,8 +1,8 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use contracts_common::truncate_decimal; use cosmwasm_std::{Coin, Decimal, Uint128}; +use nym_contracts_common::truncate_decimal; /// Truncates all decimal points so that the reward would fit in a `Coin` and so that we would /// never attempt to reward more than the owner is due diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/signing_types.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/signing_types.rs index 00130d52d8..6cd247d18c 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/signing_types.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/signing_types.rs @@ -3,11 +3,11 @@ use crate::nym_node::NymNode; use crate::{Gateway, MixNode, NodeCostParams}; -use contracts_common::signing::{ +use cosmwasm_std::{Addr, Coin}; +use nym_contracts_common::signing::{ ContractMessageContent, LegacyContractMessageContent, MessageType, Nonce, SignableMessage, SigningPurpose, }; -use cosmwasm_std::{Addr, Coin}; use serde::Serialize; pub type SignableMixNodeBondingMsg = SignableMessage>; diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs index ebe198186d..1b0590df1f 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs @@ -5,10 +5,10 @@ use crate::EpochId; use crate::config_score::{ConfigScoreParams, OutdatedVersionWeights, VersionScoreFormulaParams}; use crate::nym_node::Role; use crate::reward_params::RewardedSetParams; -use contracts_common::Percent; use cosmwasm_schema::cw_serde; use cosmwasm_std::Coin; use cosmwasm_std::{Addr, Uint128}; +use nym_contracts_common::Percent; use std::fmt::{Display, Formatter}; // type aliases for better reasoning about available data diff --git a/common/cosmwasm-smart-contracts/multisig-contract/Cargo.toml b/common/cosmwasm-smart-contracts/multisig-contract/Cargo.toml index 89e039cd1f..31aef542af 100644 --- a/common/cosmwasm-smart-contracts/multisig-contract/Cargo.toml +++ b/common/cosmwasm-smart-contracts/multisig-contract/Cargo.toml @@ -1,8 +1,10 @@ [package] name = "nym-multisig-contract-common" -version = "0.1.0" +version.workspace = true edition = "2021" license.workspace = true +description = "Common code for the Nym multisig CosmWasm smart contract" +homepage.workspace = true [dependencies] cosmwasm-schema = { workspace = true } diff --git a/common/cosmwasm-smart-contracts/nym-performance-contract/Cargo.toml b/common/cosmwasm-smart-contracts/nym-performance-contract/Cargo.toml index 078351a348..8fd19ff838 100644 --- a/common/cosmwasm-smart-contracts/nym-performance-contract/Cargo.toml +++ b/common/cosmwasm-smart-contracts/nym-performance-contract/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-performance-contract-common" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true @@ -9,6 +9,7 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +description = "Common crate for Nym's group performance contract" [dependencies] thiserror = { workspace = true } @@ -19,7 +20,7 @@ cosmwasm-std = { workspace = true } cosmwasm-schema = { workspace = true } cw-controllers = { workspace = true } -nym-contracts-common = { path = "../contracts-common" } +nym-contracts-common = { workspace = true } [features] diff --git a/common/cosmwasm-smart-contracts/nym-pool-contract/Cargo.toml b/common/cosmwasm-smart-contracts/nym-pool-contract/Cargo.toml index 38d7659ece..a4567fb0c2 100644 --- a/common/cosmwasm-smart-contracts/nym-pool-contract/Cargo.toml +++ b/common/cosmwasm-smart-contracts/nym-pool-contract/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-pool-contract-common" -version = "0.1.0" +version.workspace = true description = "Common library for the Nym Pool contract" authors.workspace = true repository.workspace = true diff --git a/common/cosmwasm-smart-contracts/vesting-contract/Cargo.toml b/common/cosmwasm-smart-contracts/vesting-contract/Cargo.toml index 9edf0ce49c..cc29aebe12 100644 --- a/common/cosmwasm-smart-contracts/vesting-contract/Cargo.toml +++ b/common/cosmwasm-smart-contracts/vesting-contract/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-vesting-contract-common" -version = "0.7.0" +version.workspace = true description = "Common library for the Nym vesting contract" edition = { workspace = true } authors = { workspace = true } @@ -11,8 +11,8 @@ repository = { workspace = true } cosmwasm-std = { workspace = true } cosmwasm-schema = { workspace = true } cw2 = { workspace = true, optional = true } -mixnet-contract-common = { path = "../mixnet-contract", package = "nym-mixnet-contract-common", version = "0.6.0" } -contracts-common = { path = "../contracts-common", package = "nym-contracts-common", version = "0.5.0" } +nym-mixnet-contract-common = { workspace = true } +nym-contracts-common = { workspace = true } serde = { workspace = true, features = ["derive"] } thiserror = { workspace = true } # without this feature, cargo clippy emits a ton of incompatibility warnings diff --git a/common/cosmwasm-smart-contracts/vesting-contract/src/error.rs b/common/cosmwasm-smart-contracts/vesting-contract/src/error.rs index c8b1c03594..996a666f37 100644 --- a/common/cosmwasm-smart-contracts/vesting-contract/src/error.rs +++ b/common/cosmwasm-smart-contracts/vesting-contract/src/error.rs @@ -3,7 +3,7 @@ use crate::account::VestingAccountStorageKey; use cosmwasm_std::{Addr, Coin, OverflowError, StdError, Uint128}; -use mixnet_contract_common::NodeId; +use nym_mixnet_contract_common::NodeId; use thiserror::Error; #[derive(Error, Debug, PartialEq)] diff --git a/common/cosmwasm-smart-contracts/vesting-contract/src/lib.rs b/common/cosmwasm-smart-contracts/vesting-contract/src/lib.rs index 741fde4f7b..8e462fb4bc 100644 --- a/common/cosmwasm-smart-contracts/vesting-contract/src/lib.rs +++ b/common/cosmwasm-smart-contracts/vesting-contract/src/lib.rs @@ -6,7 +6,7 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Coin}; -use mixnet_contract_common::NodeId; +use nym_mixnet_contract_common::NodeId; pub mod account; pub mod error; @@ -126,8 +126,8 @@ pub struct AccountsResponse { #[cfg(test)] mod test { - use contracts_common::Percent; use cosmwasm_std::Uint128; + use nym_contracts_common::Percent; use std::str::FromStr; use crate::PledgeCap; diff --git a/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs b/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs index 9b2d4aa6e9..cade5dec81 100644 --- a/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs +++ b/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs @@ -2,19 +2,19 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{PledgeCap, VestingSpecification}; -use contracts_common::signing::MessageSignature; use cosmwasm_schema::cw_serde; use cosmwasm_std::{Coin, Timestamp}; -use mixnet_contract_common::{ +use nym_contracts_common::signing::MessageSignature; +use nym_mixnet_contract_common::{ Gateway, MixNode, NodeId, gateway::GatewayConfigUpdate, mixnode::{MixNodeConfigUpdate, NodeCostParams}, }; -#[cfg(feature = "schema")] -use contracts_common::ContractBuildInformation; #[cfg(feature = "schema")] use cosmwasm_schema::QueryResponses; +#[cfg(feature = "schema")] +use nym_contracts_common::ContractBuildInformation; #[cfg(feature = "schema")] use crate::{ diff --git a/common/cosmwasm-smart-contracts/vesting-contract/src/types.rs b/common/cosmwasm-smart-contracts/vesting-contract/src/types.rs index 0b15346804..35ba50a97f 100644 --- a/common/cosmwasm-smart-contracts/vesting-contract/src/types.rs +++ b/common/cosmwasm-smart-contracts/vesting-contract/src/types.rs @@ -1,10 +1,10 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use contracts_common::Percent; use cosmwasm_schema::cw_serde; use cosmwasm_std::{Coin, Timestamp, Uint128}; -use mixnet_contract_common::NodeId; +use nym_contracts_common::Percent; +use nym_mixnet_contract_common::NodeId; use std::str::FromStr; #[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] diff --git a/common/credential-proxy/Cargo.toml b/common/credential-proxy/Cargo.toml index e06fd612e3..2141e37c42 100644 --- a/common/credential-proxy/Cargo.toml +++ b/common/credential-proxy/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-credential-proxy-lib" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true @@ -9,6 +9,7 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +description = "Build script and core functionality of the Nym Credential Proxy" [dependencies] anyhow = { workspace = true } @@ -33,16 +34,16 @@ uuid = { workspace = true, features = ["serde"] } url = { workspace = true } zeroize = { workspace = true } -nym-credentials = { path = "../credentials" } -nym-crypto = { path = "../crypto", features = ["asymmetric", "rand", "serde"] } -nym-credentials-interface = { path = "../credentials-interface" } -nym-credential-proxy-requests = { path = "../../nym-credential-proxy/nym-credential-proxy-requests" } -nym-ecash-signer-check = { path = "../ecash-signer-check" } -nym-ecash-contract-common = { path = "../cosmwasm-smart-contracts/ecash-contract" } -nym-compact-ecash = { path = "../nym_offline_compact_ecash" } -nym-validator-client = { path = "../client-libs/validator-client" } -nym-network-defaults = { path = "../network-defaults" } -nym-cache = { path = "../nym-cache" } +nym-credentials = { workspace = true } +nym-crypto = { workspace = true, features = ["asymmetric", "rand", "serde"] } +nym-credentials-interface = { workspace = true } +nym-credential-proxy-requests = { workspace = true, features = ["query-types"] } +nym-ecash-signer-check = { workspace = true } +nym-ecash-contract-common = { workspace = true } +nym-compact-ecash = { workspace = true } +nym-validator-client = { workspace = true } +nym-network-defaults = { workspace = true } +nym-cache = { workspace = true } [dev-dependencies] tempfile = { workspace = true } diff --git a/common/credential-storage/Cargo.toml b/common/credential-storage/Cargo.toml index 9ca0eab6f3..42da92cedc 100644 --- a/common/credential-storage/Cargo.toml +++ b/common/credential-storage/Cargo.toml @@ -1,9 +1,13 @@ [package] name = "nym-credential-storage" -version = "0.1.0" +version.workspace = true edition = "2021" license.workspace = true rust-version.workspace = true +description = "Crate for handling and storing spent and unspent zknym ticketbooks" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -18,12 +22,12 @@ time = { workspace = true } tokio = { workspace = true, features = ["sync"] } zeroize = { workspace = true, features = ["zeroize_derive"] } -nym-credentials = { path = "../credentials" } -nym-compact-ecash = { path = "../nym_offline_compact_ecash" } -nym-ecash-time = { path = "../ecash-time" } +nym-credentials = { workspace = true } +nym-compact-ecash = { workspace = true } +nym-ecash-time = { workspace = true } -[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx-pool-guard] -path = "../../sqlx-pool-guard" +[target."cfg(not(target_arch = \"wasm32\"))".dependencies.nym-sqlx-pool-guard] +workspace = true [target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx] workspace = true @@ -36,10 +40,10 @@ features = ["rt-multi-thread", "net", "signal", "fs"] [dev-dependencies] anyhow = { workspace = true } -nym-crypto = { path = "../crypto", features = ["asymmetric", "rand"] } -nym-test-utils = { path = "../test-utils" } -nym-credentials-interface = { path = "../credentials-interface" } -nym-compact-ecash = { path = "../nym_offline_compact_ecash" } +nym-crypto = { workspace = true, features = ["asymmetric", "rand"] } +nym-test-utils = { workspace = true } +nym-credentials-interface = { workspace = true } +nym-compact-ecash = { workspace = true } [build-dependencies] diff --git a/common/credential-storage/src/backends/sqlite.rs b/common/credential-storage/src/backends/sqlite.rs index 960e555e1b..25c7512a9b 100644 --- a/common/credential-storage/src/backends/sqlite.rs +++ b/common/credential-storage/src/backends/sqlite.rs @@ -7,8 +7,8 @@ use crate::models::{ StoredIssuedTicketbook, StoredPendingTicketbook, }; use nym_ecash_time::Date; +use nym_sqlx_pool_guard::SqlitePoolGuard; use sqlx::{Executor, Sqlite, Transaction}; -use sqlx_pool_guard::SqlitePoolGuard; #[derive(Clone)] pub struct SqliteEcashTicketbookManager { diff --git a/common/credential-storage/src/persistent_storage/mod.rs b/common/credential-storage/src/persistent_storage/mod.rs index 4596bea255..6f6665dedc 100644 --- a/common/credential-storage/src/persistent_storage/mod.rs +++ b/common/credential-storage/src/persistent_storage/mod.rs @@ -34,11 +34,11 @@ use nym_credentials::{ IssuanceTicketBook, IssuedTicketBook, }; use nym_ecash_time::{ecash_today, Date, EcashTime}; +use nym_sqlx_pool_guard::SqlitePoolGuard; use sqlx::{ sqlite::{SqliteAutoVacuum, SqliteSynchronous}, ConnectOptions, }; -use sqlx_pool_guard::SqlitePoolGuard; use std::path::Path; use zeroize::Zeroizing; diff --git a/common/credential-utils/Cargo.toml b/common/credential-utils/Cargo.toml index 81ba19a85b..f3d6986d1d 100644 --- a/common/credential-utils/Cargo.toml +++ b/common/credential-utils/Cargo.toml @@ -1,9 +1,12 @@ [package] name = "nym-credential-utils" -version = "0.1.0" +version.workspace = true edition = "2021" license.workspace = true - +description = "Utils crate for dealing with zknym credentials" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] @@ -12,11 +15,11 @@ thiserror = { workspace = true } tokio = { workspace = true } time.workspace = true -nym-bandwidth-controller = { path = "../../common/bandwidth-controller" } -nym-credentials = { path = "../../common/credentials" } -nym-credentials-interface = { path = "../../common/credentials-interface" } -nym-credential-storage = { path = "../../common/credential-storage", features = ["persistent-storage"] } -nym-validator-client = { path = "../../common/client-libs/validator-client" } -nym-config = { path = "../../common/config" } -nym-client-core = { path = "../../common/client-core" } -nym-ecash-time = { path = "../../common/ecash-time" } \ No newline at end of file +nym-bandwidth-controller = { workspace = true } +nym-credentials = { workspace = true } +nym-credentials-interface = { workspace = true } +nym-credential-storage = { workspace = true, features = ["persistent-storage"] } +nym-validator-client = { workspace = true } +nym-config = { workspace = true } +nym-client-core = { workspace = true } +nym-ecash-time = { workspace = true } diff --git a/common/credential-verification/Cargo.toml b/common/credential-verification/Cargo.toml index 07c41a49bf..d5af1fff19 100644 --- a/common/credential-verification/Cargo.toml +++ b/common/credential-verification/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-credential-verification" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true @@ -9,6 +9,7 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +description = "Store and verify zknym credentials" [dependencies] async-trait = { workspace = true } @@ -23,14 +24,14 @@ tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } time = { workspace = true } tracing = { workspace = true } -nym-api-requests = { path = "../../nym-api/nym-api-requests" } -nym-credentials = { path = "../credentials" } -nym-credentials-interface = { path = "../credentials-interface" } -nym-crypto = { path = "../crypto", features = ["asymmetric"] } -nym-ecash-contract-common = { path = "../cosmwasm-smart-contracts/ecash-contract" } -nym-gateway-requests = { path = "../gateway-requests" } -nym-gateway-storage = { path = "../gateway-storage" } -nym-metrics = { path = "../nym-metrics" } -nym-task = { path = "../task" } -nym-validator-client = { path = "../client-libs/validator-client" } -nym-upgrade-mode-check = { path = "../upgrade-mode-check" } +nym-api-requests = { workspace = true } +nym-credentials = { workspace = true } +nym-credentials-interface = { workspace = true } +nym-crypto = { workspace = true, features = ["asymmetric"] } +nym-ecash-contract-common = { workspace = true } +nym-gateway-requests = { workspace = true } +nym-gateway-storage = { workspace = true } +nym-metrics = { workspace = true } +nym-task = { workspace = true } +nym-validator-client = { workspace = true, features = ["http-client"] } +nym-upgrade-mode-check = { workspace = true } diff --git a/common/credential-verification/src/lib.rs b/common/credential-verification/src/lib.rs index cafc5bfcfd..fbf8152c8c 100644 --- a/common/credential-verification/src/lib.rs +++ b/common/credential-verification/src/lib.rs @@ -22,6 +22,8 @@ pub mod ecash; pub mod error; pub mod upgrade_mode; +const MOCK_BANDWIDTH: i64 = 2024 * 1024 * 1024; + // Histogram buckets for ecash verification duration (in seconds) const ECASH_VERIFICATION_DURATION_BUCKETS: &[f64] = &[0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1.0, 2.0, 5.0]; @@ -111,6 +113,13 @@ impl CredentialVerifier { } pub async fn verify(&mut self) -> Result { + if self.ecash_verifier.is_mock() { + // if we're in the mock mode (local testing), skip cryptographic verification + // and just return a dummy bandwidth value since we don't have blockchain access + // Return a reasonable test bandwidth value (e.g., 1GB in bytes) + return Ok(MOCK_BANDWIDTH); + } + let start = Instant::now(); nym_metrics::inc!("ecash_verification_attempts"); diff --git a/common/credential-verification/src/upgrade_mode.rs b/common/credential-verification/src/upgrade_mode.rs index 7dec1010f7..93177c688e 100644 --- a/common/credential-verification/src/upgrade_mode.rs +++ b/common/credential-verification/src/upgrade_mode.rs @@ -291,3 +291,40 @@ struct UpgradeModeStateInner { // (and dealing with the async consequences of that) status: UpgradeModeStatus, } + +pub mod testing { + use crate::UpgradeModeState; + use crate::upgrade_mode::{ + CheckRequest, UpgradeModeCheckConfig, UpgradeModeCheckRequestSender, UpgradeModeDetails, + }; + use futures::channel::mpsc::UnboundedReceiver; + use nym_crypto::asymmetric::ed25519; + use std::time::Duration; + + pub fn mock_dummy_upgrade_mode_details() -> (UpgradeModeDetails, UnboundedReceiver) + { + let (um_recheck_tx, um_recheck_rx) = futures::channel::mpsc::unbounded(); + + const DUMMY_ATTESTER_ED25519_PRIVATE_KEY: [u8; 32] = [ + 108, 49, 193, 21, 126, 161, 249, 85, 242, 207, 74, 195, 238, 6, 64, 149, 201, 140, 248, + 163, 122, 170, 79, 198, 87, 85, 36, 29, 243, 92, 64, 161, + ]; + + pub(crate) fn dummy_attester_public_key() -> ed25519::PublicKey { + let private_key = + ed25519::PrivateKey::from_bytes(&DUMMY_ATTESTER_ED25519_PRIVATE_KEY).unwrap(); + private_key.public_key() + } + + let upgrade_mode_state = UpgradeModeState::new(dummy_attester_public_key()); + let upgrade_mode_details = UpgradeModeDetails::new( + UpgradeModeCheckConfig { + // essentially we never want to trigger this in our tests + min_staleness_recheck: Duration::from_nanos(1), + }, + UpgradeModeCheckRequestSender::new(um_recheck_tx), + upgrade_mode_state.clone(), + ); + (upgrade_mode_details, um_recheck_rx) + } +} diff --git a/common/credentials-interface/Cargo.toml b/common/credentials-interface/Cargo.toml index 57a88ea603..fd2f6d895d 100644 --- a/common/credentials-interface/Cargo.toml +++ b/common/credentials-interface/Cargo.toml @@ -1,17 +1,18 @@ [package] name = "nym-credentials-interface" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "Interface for Nym's compact eacash / zknym credential scheme" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bls12_381 = { workspace = true, default-features = false } +nym-bls12_381-fork = { workspace = true } serde = { workspace = true, features = ["derive"] } thiserror = { workspace = true } strum = { workspace = true, features = ["derive"] } @@ -20,8 +21,7 @@ time = { workspace = true, features = ["serde"] } utoipa = { workspace = true } rand = { workspace = true } -nym-compact-ecash = { path = "../nym_offline_compact_ecash" } -nym-ecash-time = { path = "../ecash-time" } -nym-network-defaults = { path = "../network-defaults" } -nym-upgrade-mode-check = { path = "../upgrade-mode-check" } - +nym-compact-ecash = { workspace = true } +nym-ecash-time = { workspace = true } +nym-network-defaults = { workspace = true } +nym-upgrade-mode-check = { workspace = true } diff --git a/common/credentials/Cargo.toml b/common/credentials/Cargo.toml index e3cb06ec40..1a429bc24c 100644 --- a/common/credentials/Cargo.toml +++ b/common/credentials/Cargo.toml @@ -1,13 +1,17 @@ [package] name = "nym-credentials" -version = "0.1.0" +version.workspace = true edition = "2021" license.workspace = true +description = "Crate for using Nym's zknym credentials" +repository.workspace = true +homepage.workspace = true +documentation.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"] } +nym-bls12_381-fork = { workspace = true, default-features = false, features = ["pairings", "alloc", "experimental"] } bincode = { workspace = true } cosmrs = { workspace = true } thiserror = { workspace = true } @@ -16,18 +20,17 @@ time = { workspace = true, features = ["serde"] } serde = { workspace = true, features = ["derive"] } zeroize = { workspace = true } -nym-ecash-time = { path = "../ecash-time", features = ["expiration"] } +nym-ecash-time = { workspace = true, features = ["expiration"] } # I guess temporarily until we get serde support in coconut up and running -nym-credentials-interface = { path = "../credentials-interface" } -nym-crypto = { path = "../crypto" } -nym-api-requests = { path = "../../nym-api/nym-api-requests" } -nym-http-api-client = { path = "../http-api-client" } -nym-validator-client = { path = "../client-libs/validator-client", default-features = false } -nym-ecash-contract-common = { path = "../cosmwasm-smart-contracts/ecash-contract" } -nym-network-defaults = { path = "../network-defaults" } -nym-serde-helpers = { path = "../serde-helpers", features = ["date"] } +nym-credentials-interface = { workspace = true } +nym-crypto = { workspace = true } +nym-api-requests = { workspace = true } +nym-http-api-client = { workspace = true } +nym-validator-client = { workspace = true, default-features = false } +nym-ecash-contract-common = { workspace = true } +nym-network-defaults = { workspace = true } +nym-serde-helpers = { workspace = true, features = ["date"] } [dev-dependencies] rand = { workspace = true } - diff --git a/common/crypto/Cargo.toml b/common/crypto/Cargo.toml index 03b785494c..8b08749512 100644 --- a/common/crypto/Cargo.toml +++ b/common/crypto/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-crypto" -version = "0.4.0" +version.workspace = true description = "Crypto library for the nym mixnet" edition = { workspace = true } authors = { workspace = true } @@ -33,14 +33,14 @@ thiserror = { workspace = true } zeroize = { workspace = true, optional = true, features = ["zeroize_derive"] } # internal -nym-sphinx-types = { path = "../nymsphinx/types", version = "0.2.0", default-features = false } -nym-pemstore = { path = "../../common/pemstore", version = "0.3.0" } +nym-sphinx-types = { workspace = true } +nym-pemstore = { workspace = true } [dev-dependencies] anyhow = { workspace = true } rand_chacha = { workspace = true } serde_json = { workspace = true } -nym-test-utils = { path = "../test-utils" } +nym-test-utils = { workspace = true } [features] @@ -49,9 +49,9 @@ aead = ["dep:aead", "aead/std", "aes-gcm-siv", "generic-array"] naive_jwt = ["asymmetric", "jwt-simple"] serde = ["dep:serde", "serde_bytes", "ed25519-dalek/serde", "x25519-dalek/serde"] asymmetric = ["x25519-dalek", "ed25519-dalek", "curve25519-dalek", "sha2", "zeroize"] -hashing = ["blake3", "digest", "hkdf", "hmac", "generic-array", "sha2"] +hashing = ["blake3", "digest", "hkdf", "hmac", "generic-array", "sha2", "zeroize"] stream_cipher = ["aes", "ctr", "cipher", "generic-array"] sphinx = ["nym-sphinx-types/sphinx"] [lints] -workspace = true \ No newline at end of file +workspace = true diff --git a/common/crypto/src/asymmetric/ed25519/mod.rs b/common/crypto/src/asymmetric/ed25519/mod.rs index 313fb7e5d1..9706697b67 100644 --- a/common/crypto/src/asymmetric/ed25519/mod.rs +++ b/common/crypto/src/asymmetric/ed25519/mod.rs @@ -20,6 +20,7 @@ pub use serde_helpers::*; #[cfg(feature = "sphinx")] use nym_sphinx_types::{DESTINATION_ADDRESS_LENGTH, DestinationAddressBytes}; +use crate::asymmetric::x25519; #[cfg(feature = "rand")] use rand::{CryptoRng, Rng, RngCore}; #[cfg(feature = "serde")] @@ -110,6 +111,18 @@ impl KeyPair { index: fake_index(pub_bytes), }) } + + /// Converts this Ed25519 keypair to an X25519 keypair for ECDH. + /// + /// Uses the standard ed25519→x25519 conversion via SHA-512 hash and clamping. + /// This is the same approach as libsodium's `crypto_sign_ed25519_sk_to_curve25519`. + /// + /// # Returns + /// The converted X25519 keypair + pub fn to_x25519(&self) -> x25519::KeyPair { + let private_key = self.private_key.to_x25519(); + x25519::KeyPair::from(private_key) + } } /// Reduces a byte slice into a u32 value by XOR-ing all its bytes into a 4-byte accumulator. @@ -136,6 +149,16 @@ impl From for KeyPair { } } +impl From<(PrivateKey, PublicKey)> for KeyPair { + fn from((private_key, public_key): (PrivateKey, PublicKey)) -> Self { + KeyPair { + private_key, + public_key, + index: fake_index(public_key.to_bytes().as_ref()), + } + } +} + impl PemStorableKeyPair for KeyPair { type PrivatePemKey = PrivateKey; type PublicPemKey = PublicKey; @@ -185,14 +208,25 @@ impl PublicKey { } /// Convert this public key to a byte array. + #[inline] pub fn to_bytes(self) -> [u8; PUBLIC_KEY_LENGTH] { self.0.to_bytes() } + /// View this public key as a byte array. + #[inline] + pub fn as_bytes(&self) -> &[u8; PUBLIC_KEY_LENGTH] { + self.0.as_bytes() + } + + #[inline] pub fn from_bytes(b: &[u8]) -> Result { - Ok(PublicKey(ed25519_dalek::VerifyingKey::from_bytes( - b.try_into()?, - )?)) + Self::from_byte_array(b.try_into()?) + } + + #[inline] + pub fn from_byte_array(b: &[u8; PUBLIC_KEY_LENGTH]) -> Result { + Ok(PublicKey(ed25519_dalek::VerifyingKey::from_bytes(b)?)) } pub fn to_base58_string(self) -> String { diff --git a/common/crypto/src/asymmetric/x25519/mod.rs b/common/crypto/src/asymmetric/x25519/mod.rs index c01e437ef4..e717df3d92 100644 --- a/common/crypto/src/asymmetric/x25519/mod.rs +++ b/common/crypto/src/asymmetric/x25519/mod.rs @@ -4,6 +4,7 @@ use base64::Engine; use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair}; use std::fmt::{self, Debug, Display, Formatter}; +use std::ops::Deref; use std::str::FromStr; use thiserror::Error; use zeroize::{Zeroize, ZeroizeOnDrop}; @@ -56,6 +57,15 @@ pub struct KeyPair { pub(crate) public_key: PublicKey, } +impl Debug for KeyPair { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("KeyPair") + .field("private_key", &"") + .field("public_key", &self.public_key.to_base58_string()) + .finish() + } +} + impl KeyPair { #[cfg(feature = "rand")] pub fn new(rng: &mut R) -> Self { @@ -93,6 +103,15 @@ impl From for KeyPair { } } +impl From<(PrivateKey, PublicKey)> for KeyPair { + fn from((private_key, public_key): (PrivateKey, PublicKey)) -> Self { + KeyPair { + private_key, + public_key, + } + } +} + impl PemStorableKeyPair for KeyPair { type PrivatePemKey = PrivateKey; type PublicPemKey = PublicKey; @@ -116,6 +135,13 @@ impl PemStorableKeyPair for KeyPair { #[derive(PartialEq, Eq, Hash, Copy, Clone)] pub struct PublicKey(x25519_dalek::PublicKey); +impl Deref for PublicKey { + type Target = x25519_dalek::PublicKey; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + impl Display for PublicKey { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { Display::fmt(&self.to_base58_string(), f) @@ -129,14 +155,17 @@ impl Debug for PublicKey { } impl PublicKey { + #[inline] pub fn to_bytes(self) -> [u8; PUBLIC_KEY_SIZE] { *self.0.as_bytes() } + #[inline] pub fn as_bytes(&self) -> &[u8; PUBLIC_KEY_SIZE] { self.0.as_bytes() } + #[inline] pub fn from_bytes(b: &[u8]) -> Result { if b.len() != PUBLIC_KEY_SIZE { return Err(KeyRecoveryError::InvalidSizePublicKey { @@ -146,7 +175,12 @@ impl PublicKey { } let mut bytes = [0; PUBLIC_KEY_SIZE]; bytes.copy_from_slice(&b[..PUBLIC_KEY_SIZE]); - Ok(Self(x25519_dalek::PublicKey::from(bytes))) + Ok(Self::from_byte_array(&bytes)) + } + + #[inline] + pub fn from_byte_array(b: &[u8; PUBLIC_KEY_SIZE]) -> Self { + Self(x25519_dalek::PublicKey::from(*b)) } pub fn to_base58_string(self) -> String { @@ -174,6 +208,12 @@ impl PublicKey { } } +impl From<[u8; PUBLIC_KEY_SIZE]> for PublicKey { + fn from(bytes: [u8; PUBLIC_KEY_SIZE]) -> Self { + PublicKey(x25519_dalek::PublicKey::from(bytes)) + } +} + impl FromStr for PublicKey { type Err = KeyRecoveryError; @@ -296,6 +336,10 @@ impl PrivateKey { Ok(Self(x25519_dalek::StaticSecret::from(bytes))) } + pub fn from_secret(secret: [u8; PRIVATE_KEY_SIZE]) -> Self { + Self(x25519_dalek::StaticSecret::from(secret)) + } + pub fn to_base58_string(&self) -> String { bs58::encode(&self.to_bytes()).into_string() } diff --git a/common/dkg/Cargo.toml b/common/dkg/Cargo.toml index b8e4f55731..e45a2a8d7f 100644 --- a/common/dkg/Cargo.toml +++ b/common/dkg/Cargo.toml @@ -1,19 +1,23 @@ [package] name = "nym-dkg" -version = "0.1.0" +version.workspace = true edition = "2021" resolver = "2" license.workspace = true +description = "Nym's Distributed Key Generation functionality" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] bitvec = { workspace = true } -# unfortunately until https://github.com/zkcrypto/bls12_381/issues/10 is resolved, we have to rely on the fork +# unfortunately until https://github.com/zkcrypto/nym-bls12_381-fork/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 -bls12_381 = { workspace = true, default-features = false, features = ["alloc", "pairings", "experimental", "zeroize"] } -nym-contracts-common = { path = "../cosmwasm-smart-contracts/contracts-common", optional = true } +nym-bls12_381-fork = { workspace = true, features = ["alloc", "pairings", "experimental", "zeroize"] } +nym-contracts-common = { workspace = true, optional = true } bs58 = { workspace = true } @@ -26,7 +30,7 @@ serde_derive = { workspace = true } thiserror = { workspace = true } zeroize = { workspace = true, features = ["zeroize_derive"] } -nym-pemstore = { path = "../pemstore" } +nym-pemstore = { workspace = true } [dependencies.group] workspace = true diff --git a/common/dkg/benches/benchmarks.rs b/common/dkg/benches/benchmarks.rs index d12305bba7..a360268890 100644 --- a/common/dkg/benches/benchmarks.rs +++ b/common/dkg/benches/benchmarks.rs @@ -1,9 +1,9 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use bls12_381::{G1Projective, G2Affine, G2Prepared, Scalar}; use criterion::{black_box, criterion_group, criterion_main, Criterion}; use ff::Field; +use nym_bls12_381_fork::{G1Projective, G2Affine, G2Prepared, Scalar}; use nym_dkg::bte::encryption::BabyStepGiantStepLookup; use nym_dkg::bte::proof_chunking::ProofOfChunking; use nym_dkg::bte::proof_discrete_log::ProofOfDiscreteLog; diff --git a/common/dkg/src/bte/encryption.rs b/common/dkg/src/bte/encryption.rs index 438192757f..9cc11f989d 100644 --- a/common/dkg/src/bte/encryption.rs +++ b/common/dkg/src/bte/encryption.rs @@ -6,9 +6,9 @@ use crate::bte::{evaluate_f, Params, CHUNK_SIZE, G2_GENERATOR_PREPARED, NUM_CHUN use crate::error::DkgError; use crate::utils::{combine_g1_chunks, combine_scalar_chunks, deserialize_g1, deserialize_g2}; use crate::{Chunk, ChunkedShare, Share}; -use bls12_381::{G1Affine, G1Projective, G2Prepared, G2Projective, Gt, Scalar}; use ff::Field; use group::{Curve, Group, GroupEncoding}; +use nym_bls12_381_fork::{G1Affine, G1Projective, G2Prepared, G2Projective, Gt, Scalar}; use rand::CryptoRng; use rand_core::RngCore; use std::collections::HashMap; @@ -43,7 +43,7 @@ impl Ciphertexts { // which is equivalent to checking whether e(R_j, f) • e(S_i, h) • e(g1, Z_i)^-1 == id // and due to bilinear property whether e(R_j, f) • e(S_i, h) • e(g1^-1, Z_i) == id for i in 0..self.rr.len() { - let miller = bls12_381::multi_miller_loop(&[ + let miller = nym_bls12_381_fork::multi_miller_loop(&[ (&self.rr[i].to_affine(), &f_prepared), (&self.ss[i].to_affine(), ¶ms._h_prepared), (&g1_neg, &G2Prepared::from(self.zz[i].to_affine())), @@ -294,7 +294,7 @@ pub fn decrypt_share( let zz_j = ciphertext.zz[j].to_affine(); let cc_ij = &ciphertext.ciphertext_chunks[i][j]; - let miller = bls12_381::multi_miller_loop(&[ + let miller = nym_bls12_381_fork::multi_miller_loop(&[ (&cc_ij.to_affine(), &G2_GENERATOR_PREPARED), (&rr_j.to_affine(), &G2Prepared::from(b_neg)), (&dk.a.to_affine(), &G2Prepared::from(zz_j)), diff --git a/common/dkg/src/bte/keys.rs b/common/dkg/src/bte/keys.rs index b81d6cbf77..9bd1406115 100644 --- a/common/dkg/src/bte/keys.rs +++ b/common/dkg/src/bte/keys.rs @@ -5,9 +5,9 @@ use crate::bte::proof_discrete_log::ProofOfDiscreteLog; use crate::bte::Params; use crate::error::DkgError; use crate::utils::{deserialize_g1, deserialize_g2, deserialize_scalar}; -use bls12_381::{G1Projective, G2Projective, Scalar}; use ff::Field; use group::GroupEncoding; +use nym_bls12_381_fork::{G1Projective, G2Projective, Scalar}; use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair}; use rand::CryptoRng; use rand_core::RngCore; diff --git a/common/dkg/src/bte/mod.rs b/common/dkg/src/bte/mod.rs index ea5c9a70c6..e1d7285565 100644 --- a/common/dkg/src/bte/mod.rs +++ b/common/dkg/src/bte/mod.rs @@ -6,8 +6,8 @@ use std::sync::LazyLock; use crate::bte::encryption::BabyStepGiantStepLookup; use crate::utils::hash_g2; use crate::{Chunk, Share}; -use bls12_381::{G1Affine, G2Affine, G2Prepared, G2Projective, Gt}; use group::Curve; +use nym_bls12_381_fork::{G1Affine, G2Affine, G2Prepared, G2Projective, Gt}; pub mod encryption; pub mod keys; @@ -19,7 +19,7 @@ pub use encryption::{decrypt_share, encrypt_shares, Ciphertexts}; pub use keys::{keygen, DecryptionKey, PublicKey, PublicKeyWithProof}; pub(crate) static PAIRING_BASE: LazyLock = - LazyLock::new(|| bls12_381::pairing(&G1Affine::generator(), &G2Affine::generator())); + LazyLock::new(|| nym_bls12_381_fork::pairing(&G1Affine::generator(), &G2Affine::generator())); pub(crate) static G2_GENERATOR_PREPARED: LazyLock = LazyLock::new(|| G2Prepared::from(G2Affine::generator())); pub static BSGS_TABLE: LazyLock = diff --git a/common/dkg/src/bte/proof_chunking.rs b/common/dkg/src/bte/proof_chunking.rs index 7e5b5cec2f..cfa4e6262e 100644 --- a/common/dkg/src/bte/proof_chunking.rs +++ b/common/dkg/src/bte/proof_chunking.rs @@ -7,9 +7,9 @@ use crate::ensure_len; use crate::error::DkgError; use crate::utils::{deserialize_g1, hash_to_scalar}; use crate::utils::{deserialize_scalar, RandomOracleBuilder}; -use bls12_381::{G1Projective, Scalar}; use ff::Field; use group::{Group, GroupEncoding}; +use nym_bls12_381_fork::{G1Projective, Scalar}; use rand::{CryptoRng, Rng}; use rand_core::{RngCore, SeedableRng}; use zeroize::{Zeroize, ZeroizeOnDrop}; diff --git a/common/dkg/src/bte/proof_discrete_log.rs b/common/dkg/src/bte/proof_discrete_log.rs index de186f8fb6..59746de721 100644 --- a/common/dkg/src/bte/proof_discrete_log.rs +++ b/common/dkg/src/bte/proof_discrete_log.rs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use crate::utils::hash_to_scalar; -use bls12_381::{G1Projective, Scalar}; use ff::Field; use group::GroupEncoding; +use nym_bls12_381_fork::{G1Projective, Scalar}; use rand::CryptoRng; use rand_core::RngCore; use zeroize::{Zeroize, ZeroizeOnDrop}; diff --git a/common/dkg/src/bte/proof_sharing.rs b/common/dkg/src/bte/proof_sharing.rs index 5659ec9235..2cf8f29352 100644 --- a/common/dkg/src/bte/proof_sharing.rs +++ b/common/dkg/src/bte/proof_sharing.rs @@ -6,9 +6,9 @@ use crate::error::DkgError; use crate::interpolation::polynomial::PublicCoefficients; use crate::utils::{deserialize_g1, deserialize_g2, deserialize_scalar, hash_to_scalar}; use crate::{NodeIndex, Share}; -use bls12_381::{G1Projective, G2Projective, Scalar}; use ff::Field; use group::GroupEncoding; +use nym_bls12_381_fork::{G1Projective, G2Projective, Scalar}; use rand::CryptoRng; use rand_core::RngCore; use std::collections::BTreeMap; diff --git a/common/dkg/src/dealing.rs b/common/dkg/src/dealing.rs index 832e1260ff..def4aa0957 100644 --- a/common/dkg/src/dealing.rs +++ b/common/dkg/src/dealing.rs @@ -11,8 +11,8 @@ use crate::interpolation::{ }; use crate::utils::deserialize_g2; use crate::{NodeIndex, Share, Threshold}; -use bls12_381::{G2Projective, Scalar}; use group::GroupEncoding; +use nym_bls12_381_fork::{G2Projective, Scalar}; use rand::CryptoRng; use rand_core::RngCore; use std::collections::BTreeMap; diff --git a/common/dkg/src/interpolation/mod.rs b/common/dkg/src/interpolation/mod.rs index caedc1114e..ade36327c0 100644 --- a/common/dkg/src/interpolation/mod.rs +++ b/common/dkg/src/interpolation/mod.rs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use crate::error::DkgError; -use bls12_381::Scalar; use core::iter::Sum; use core::ops::Mul; +use nym_bls12_381_fork::Scalar; use std::collections::HashSet; pub mod polynomial; diff --git a/common/dkg/src/interpolation/polynomial.rs b/common/dkg/src/interpolation/polynomial.rs index b6a2331c13..0484400008 100644 --- a/common/dkg/src/interpolation/polynomial.rs +++ b/common/dkg/src/interpolation/polynomial.rs @@ -3,9 +3,9 @@ use crate::error::DkgError; use crate::utils::deserialize_g2; -use bls12_381::{G2Projective, Scalar}; use ff::Field; use group::GroupEncoding; +use nym_bls12_381_fork::{G2Projective, Scalar}; use rand::CryptoRng; use rand_core::RngCore; use std::ops::{Add, Index, IndexMut}; diff --git a/common/dkg/src/lib.rs b/common/dkg/src/lib.rs index d11fa6071b..14d929604a 100644 --- a/common/dkg/src/lib.rs +++ b/common/dkg/src/lib.rs @@ -13,8 +13,8 @@ pub mod dealing; pub(crate) mod share; pub(crate) mod utils; -pub use bls12_381::{G2Projective, Scalar}; pub use dealing::*; +pub use nym_bls12_381_fork::{G2Projective, Scalar}; pub use share::*; // TODO: presumably this should live in a some different, common, crate? @@ -25,7 +25,7 @@ pub type NodeIndex = u64; mod tests { use crate::interpolation::perform_lagrangian_interpolation_at_origin; use crate::interpolation::polynomial::Polynomial; - use bls12_381::Scalar; + use nym_bls12_381_fork::Scalar; use rand_chacha::rand_core::SeedableRng; #[test] diff --git a/common/dkg/src/share.rs b/common/dkg/src/share.rs index 96039048be..d448587b89 100644 --- a/common/dkg/src/share.rs +++ b/common/dkg/src/share.rs @@ -5,7 +5,7 @@ use crate::bte::{CHUNK_BYTES, NUM_CHUNKS, SCALAR_SIZE}; use crate::error::DkgError; use crate::interpolation::perform_lagrangian_interpolation_at_origin; use crate::NodeIndex; -use bls12_381::Scalar; +use nym_bls12_381_fork::Scalar; use zeroize::{Zeroize, ZeroizeOnDrop}; // if this type is changed, one must ensure all values can fit in it diff --git a/common/dkg/src/utils.rs b/common/dkg/src/utils.rs index e654a83f8c..c852379d49 100644 --- a/common/dkg/src/utils.rs +++ b/common/dkg/src/utils.rs @@ -2,10 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 use crate::bte::CHUNK_SIZE; -use bls12_381::hash_to_curve::{ExpandMsgXmd, HashToCurve, HashToField}; -use bls12_381::G1Projective; -use bls12_381::{G2Projective, Scalar}; use group::GroupEncoding; +use nym_bls12_381_fork::hash_to_curve::{ExpandMsgXmd, HashToCurve, HashToField}; +use nym_bls12_381_fork::G1Projective; +use nym_bls12_381_fork::{G2Projective, Scalar}; use sha2::{Digest, Sha256}; #[macro_export] @@ -116,7 +116,7 @@ pub(crate) fn deserialize_g2(b: &[u8]) -> Option { #[cfg(test)] mod tests { use super::*; - use bls12_381::G2Affine; + use nym_bls12_381_fork::G2Affine; #[test] fn test_hash_to_scalar() { diff --git a/common/dkg/tests/integration.rs b/common/dkg/tests/integration.rs index e34195488e..c5560e70bf 100644 --- a/common/dkg/tests/integration.rs +++ b/common/dkg/tests/integration.rs @@ -1,7 +1,7 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use bls12_381::{G2Projective, Scalar}; +use nym_bls12_381_fork::{G2Projective, Scalar}; use nym_dkg::bte::{decrypt_share, keygen, setup}; use nym_dkg::dealing::RecoveredVerificationKeys; use nym_dkg::interpolation::perform_lagrangian_interpolation_at_origin; diff --git a/common/ecash-signer-check-types/Cargo.toml b/common/ecash-signer-check-types/Cargo.toml index 0f07437c71..ac3a6f084d 100644 --- a/common/ecash-signer-check-types/Cargo.toml +++ b/common/ecash-signer-check-types/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-ecash-signer-check-types" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true @@ -9,6 +9,7 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +description = "Crate containing types for the `ecash-signer-check` crate used to check if zknym signers are up and running properly" [dependencies] semver = { workspace = true } @@ -19,8 +20,8 @@ time = { workspace = true } tracing = { workspace = true } utoipa = { workspace = true } -nym-coconut-dkg-common = { path = "../cosmwasm-smart-contracts/coconut-dkg" } -nym-crypto = { path = "../crypto", features = ["asymmetric"] } +nym-coconut-dkg-common = { workspace = true } +nym-crypto = { workspace = true, features = ["asymmetric"] } [lints] diff --git a/common/ecash-signer-check/Cargo.toml b/common/ecash-signer-check/Cargo.toml index cc8fb6ec29..8549930a03 100644 --- a/common/ecash-signer-check/Cargo.toml +++ b/common/ecash-signer-check/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-ecash-signer-check" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true @@ -9,6 +9,7 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +description = "Functions to interact with zknym signers, checking their status and health" [dependencies] futures = { workspace = true } @@ -19,10 +20,10 @@ tracing = { workspace = true } url = { workspace = true } -nym-validator-client = { path = "../client-libs/validator-client" } -nym-network-defaults = { path = "../network-defaults" } -nym-ecash-signer-check-types = { path = "../ecash-signer-check-types" } -nym-http-api-client = { path = "../http-api-client" } +nym-validator-client = { workspace = true, features = ["http-client"] } +nym-network-defaults = { workspace = true } +nym-ecash-signer-check-types = { workspace = true } +nym-http-api-client = { workspace = true } [lints] workspace = true diff --git a/common/ecash-time/Cargo.toml b/common/ecash-time/Cargo.toml index 4eaee90438..1780a96afe 100644 --- a/common/ecash-time/Cargo.toml +++ b/common/ecash-time/Cargo.toml @@ -1,19 +1,20 @@ [package] name = "nym-ecash-time" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "Time-related helper functions for Nym's zknym scheme" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] time.workspace = true -nym-compact-ecash = { path = "../nym_offline_compact_ecash", optional = true } +nym-compact-ecash = { workspace = true, optional = true } [features] -expiration = ["nym-compact-ecash"] \ No newline at end of file +expiration = ["nym-compact-ecash"] diff --git a/common/exit-policy/Cargo.toml b/common/exit-policy/Cargo.toml index a5e8f26974..3db220a920 100644 --- a/common/exit-policy/Cargo.toml +++ b/common/exit-policy/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "nym-exit-policy" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "Get and set the Nym Exit Policy, used by Exit Gateways" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/common/gateway-requests/Cargo.toml b/common/gateway-requests/Cargo.toml index 65a5704fdc..b9cdf43d01 100644 --- a/common/gateway-requests/Cargo.toml +++ b/common/gateway-requests/Cargo.toml @@ -3,10 +3,14 @@ [package] name = "nym-gateway-requests" -version = "0.1.0" +version.workspace = true authors = ["Jedrzej Stuczynski "] edition = "2021" license.workspace = true +description = "Request and response definitions for Nym Gateway <> client communication" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -24,15 +28,15 @@ 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-statistics-common = { path = "../statistics" } -nym-task = { path = "../task" } +nym-crypto = { workspace = true, features = ["aead", "hashing"] } +nym-pemstore = { workspace = true } +nym-sphinx = { workspace = true } +nym-serde-helpers = { workspace = true, features = ["base64"] } +nym-statistics-common = { workspace = true } +nym-task = { workspace = true } -nym-credentials = { path = "../credentials" } -nym-credentials-interface = { path = "../credentials-interface" } +nym-credentials = { workspace = true } +nym-credentials-interface = { workspace = true } [target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio] workspace = true @@ -48,9 +52,9 @@ default-features = false [dev-dependencies] anyhow = { workspace = true } -nym-compact-ecash = { path = "../nym_offline_compact_ecash" } # we need specific imports in tests -nym-test-utils = { path = "../test-utils" } +nym-compact-ecash = { workspace = true } # we need specific imports in tests +nym-test-utils = { workspace = true } tokio = { workspace = true, features = ["full"] } [lints] -workspace = true \ No newline at end of file +workspace = true diff --git a/common/gateway-stats-storage/Cargo.toml b/common/gateway-stats-storage/Cargo.toml index e958e25526..7c1f996059 100644 --- a/common/gateway-stats-storage/Cargo.toml +++ b/common/gateway-stats-storage/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-gateway-stats-storage" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true @@ -8,6 +8,7 @@ documentation.workspace = true edition.workspace = true license.workspace = true rust-version.workspace = true +description = "Functionality Nym Gateway statistics storage" [dependencies] sqlx = { workspace = true, features = [ @@ -22,9 +23,9 @@ time = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true } -nym-sphinx = { path = "../nymsphinx" } -nym-node-metrics = { path = "../../nym-node/nym-node-metrics" } -nym-statistics-common = { path = "../statistics" } +nym-sphinx = { workspace = true } +nym-node-metrics = { workspace = true } +nym-statistics-common = { workspace = true } [build-dependencies] diff --git a/common/gateway-storage/Cargo.toml b/common/gateway-storage/Cargo.toml index c7878511be..32424e7d4a 100644 --- a/common/gateway-storage/Cargo.toml +++ b/common/gateway-storage/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-gateway-storage" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true @@ -8,6 +8,7 @@ documentation.workspace = true edition.workspace = true license.workspace = true rust-version.workspace = true +description = "Crate handling db setup and use for Nym Gateways, used for credentials, packets, connections" [dependencies] async-trait = { workspace = true } @@ -27,9 +28,9 @@ thiserror = { workspace = true } tokio = { workspace = true, features = ["sync"], optional = true } tracing = { workspace = true } -nym-credentials-interface = { path = "../credentials-interface" } -nym-gateway-requests = { path = "../gateway-requests" } -nym-sphinx = { path = "../nymsphinx" } +nym-credentials-interface = { workspace = true } +nym-gateway-requests = { workspace = true } +nym-sphinx = { workspace = true } [build-dependencies] anyhow = { workspace = true } @@ -43,4 +44,4 @@ sqlx = { workspace = true, features = [ [features] default = [] -mock = ["tokio"] \ No newline at end of file +mock = ["tokio"] diff --git a/common/gateway-storage/migrations/20260127120000_add_wireguard_peer_psk.sql b/common/gateway-storage/migrations/20260127120000_add_wireguard_peer_psk.sql new file mode 100644 index 0000000000..783c675022 --- /dev/null +++ b/common/gateway-storage/migrations/20260127120000_add_wireguard_peer_psk.sql @@ -0,0 +1,7 @@ +/* + * Copyright 2026 - Nym Technologies SA + * SPDX-License-Identifier: Apache-2.0 + */ + +ALTER TABLE wireguard_peer + ADD COLUMN psk VARCHAR; \ No newline at end of file diff --git a/common/gateway-storage/src/lib.rs b/common/gateway-storage/src/lib.rs index 064a34daf9..55eada49e1 100644 --- a/common/gateway-storage/src/lib.rs +++ b/common/gateway-storage/src/lib.rs @@ -577,4 +577,21 @@ impl BandwidthGatewayStorage for GatewayStorage { .await?; Ok(()) } + + /// Update the stored PSK of the wireguard peer. + /// + /// # Arguments + /// + /// * `public_key`: the unique public key of the wireguard peer. + /// * `psk`: the PSK of the wireguard peer. + async fn update_peer_psk( + &self, + public_key: &str, + psk: Option<&str>, + ) -> Result<(), GatewayStorageError> { + self.wireguard_peer_manager + .update_peer_psk(public_key, psk) + .await?; + Ok(()) + } } diff --git a/common/gateway-storage/src/models.rs b/common/gateway-storage/src/models.rs index d982f2ec28..86c4457ddd 100644 --- a/common/gateway-storage/src/models.rs +++ b/common/gateway-storage/src/models.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::{error::GatewayStorageError, make_bincode_serializer}; +use defguard_wireguard_rs::key::Key; use nym_credentials_interface::{AvailableBandwidth, ClientTicket, CredentialSpendingData}; use nym_gateway_requests::shared_key::SharedSymmetricKey; use sqlx::FromRow; @@ -95,6 +96,7 @@ pub struct WireguardPeer { pub public_key: String, pub allowed_ips: Vec, pub client_id: i64, + pub psk: Option, } impl WireguardPeer { @@ -110,6 +112,7 @@ impl WireguardPeer { source, })?, client_id, + psk: value.preshared_key.map(|psk| psk.to_lower_hex()), }) } } @@ -132,6 +135,11 @@ impl TryFrom for defguard_wireguard_rs::host::Peer { field_key: "allowed_ips", source, })?, + preshared_key: value + .psk + .map(|psk| Key::decode(&psk)) + .transpose() + .map_err(|_| Self::Error::TypeConversion { field_key: "psk" })?, ..Default::default() }) } diff --git a/common/gateway-storage/src/traits.rs b/common/gateway-storage/src/traits.rs index f3a8efcdf8..f07a2b275b 100644 --- a/common/gateway-storage/src/traits.rs +++ b/common/gateway-storage/src/traits.rs @@ -170,6 +170,18 @@ pub trait BandwidthGatewayStorage: dyn_clone::DynClone { /// * `peer_public_key`: wireguard public key of the peer to be removed. async fn remove_wireguard_peer(&self, peer_public_key: &str) -> Result<(), GatewayStorageError>; + + /// Update the stored PSK of the wireguard peer. + /// + /// # Arguments + /// + /// * `public_key`: the unique public key of the wireguard peer. + /// * `psk`: the PSK of the wireguard peer. + async fn update_peer_psk( + &self, + public_key: &str, + psk: Option<&str>, + ) -> Result<(), GatewayStorageError>; } #[cfg(feature = "mock")] @@ -507,5 +519,16 @@ pub mod mock { self.write().await.wireguard_peers.remove(peer_public_key); Ok(()) } + + async fn update_peer_psk( + &self, + public_key: &str, + psk: Option<&str>, + ) -> Result<(), GatewayStorageError> { + if let Some(peer) = self.write().await.wireguard_peers.get_mut(public_key) { + peer.psk = psk.map(|psk| psk.to_owned()) + } + Ok(()) + } } } diff --git a/common/gateway-storage/src/wireguard_peers.rs b/common/gateway-storage/src/wireguard_peers.rs index 22bf178d99..c999d09358 100644 --- a/common/gateway-storage/src/wireguard_peers.rs +++ b/common/gateway-storage/src/wireguard_peers.rs @@ -98,4 +98,29 @@ impl WgPeerManager { .await?; Ok(()) } + + /// Update the stored PSK of the wireguard peer. + /// + /// # Arguments + /// + /// * `public_key`: the unique public key of the wireguard peer. + /// * `psk`: the PSK of the wireguard peer. + pub(crate) async fn update_peer_psk( + &self, + public_key: &str, + psk: Option<&str>, + ) -> Result<(), sqlx::Error> { + sqlx::query!( + r#" + UPDATE wireguard_peer + SET psk = ? + WHERE public_key = ? + "#, + psk, + public_key, + ) + .execute(&self.connection_pool) + .await?; + Ok(()) + } } diff --git a/common/http-api-client-macro/Cargo.toml b/common/http-api-client-macro/Cargo.toml index ea23bbd23a..f5e12bef4c 100644 --- a/common/http-api-client-macro/Cargo.toml +++ b/common/http-api-client-macro/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-http-api-client-macro" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true @@ -9,6 +9,7 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +description = "Proc-macros for configuring HTTP clients globally via the `inventory` crate" [lib] proc-macro = true @@ -21,7 +22,7 @@ proc-macro-crate = "3" uuid = { version = "1.0", features = ["v4"] } [dev-dependencies] -nym-http-api-client = { path = "../http-api-client" } +nym-http-api-client = { workspace = true } reqwest = { workspace = true } [features] diff --git a/common/http-api-client/Cargo.toml b/common/http-api-client/Cargo.toml index e748e37888..43daed794f 100644 --- a/common/http-api-client/Cargo.toml +++ b/common/http-api-client/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "nym-http-api-client" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "Nym's HTTP API client, examples, and tests" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -38,10 +39,10 @@ bytes = { workspace = true } encoding_rs = { workspace = true } mime = { workspace = true } -nym-http-api-common = { path = "../http-api-common", default-features = false } -nym-bin-common = { path = "../bin-common" } -nym-network-defaults = { path = "../network-defaults", optional = true } -nym-http-api-client-macro = { path = "../http-api-client-macro" } +nym-http-api-common = { workspace = true } +nym-bin-common = { workspace = true } +nym-network-defaults = { workspace = true, optional = true } +nym-http-api-client-macro = { workspace = true } [target."cfg(not(target_arch = \"wasm32\"))".dependencies] hickory-resolver = { workspace = true, features = ["https-ring", "tls-ring", "webpki-roots"] } diff --git a/common/http-api-client/src/dns/constants.rs b/common/http-api-client/src/dns/constants.rs index b25dc43a1c..b13d6f3fde 100644 --- a/common/http-api-client/src/dns/constants.rs +++ b/common/http-api-client/src/dns/constants.rs @@ -51,6 +51,9 @@ pub const VERCEL_COM_IPS: &[IpAddr] = &[ IpAddr::V4(Ipv4Addr::new(198, 169, 1, 193)), ]; +pub const NYM_API_CDN: &str = "cdn1.media-platform.net"; +pub const NYM_API_CDN_IPS: &[IpAddr] = &[IpAddr::V4(Ipv4Addr::new(172, 104, 178, 252))]; + pub const NYM_COM_DOMAIN: &str = "nym.com"; pub const NYM_COM_IPS: &[IpAddr] = &[IpAddr::V4(Ipv4Addr::new(76, 76, 21, 22))]; @@ -88,6 +91,7 @@ pub fn default_static_addrs() -> HashMap> { m.insert(YELP_FASTLY_DOMAIN.to_string(), YELP_FASTLY_IPS.to_vec()); m.insert(VERCEL_APP_DOMAIN.to_string(), VERCEL_APP_IPS.to_vec()); m.insert(VERCEL_COM_DOMAIN.to_string(), VERCEL_COM_IPS.to_vec()); + m.insert(NYM_API_CDN.to_string(), NYM_API_CDN_IPS.to_vec()); m.insert(NYM_COM_DOMAIN.to_string(), NYM_COM_IPS.to_vec()); m.insert(NYM_STATS_API_DOMAIN.to_string(), NYM_STATS_API_IPS.to_vec()); m.insert(NYM_RPC_DOMAIN.to_string(), NYM_RPC_IPS.to_vec()); diff --git a/common/http-api-client/src/lib.rs b/common/http-api-client/src/lib.rs index bc3a8d7430..06a0511564 100644 --- a/common/http-api-client/src/lib.rs +++ b/common/http-api-client/src/lib.rs @@ -329,14 +329,14 @@ pub enum HttpClientError { #[error("failed to send request for {url}: {source}")] RequestSendFailure { - url: reqwest::Url, + url: Box, #[source] source: ReqwestErrorWrapper, }, #[error("failed to read response body from {url}: {source}")] ResponseReadFailure { - url: reqwest::Url, + url: Box, headers: Box, status: StatusCode, #[source] @@ -353,7 +353,7 @@ pub enum HttpClientError { }, #[error("the requested resource could not be found at {url}")] - NotFound { url: reqwest::Url }, + NotFound { url: Box }, #[error("attempted to use domain fronting and clone a request containing stream data")] AttemptedToCloneStreamRequest, @@ -365,7 +365,7 @@ pub enum HttpClientError { "the request for {url} failed with status '{status}'. no additional error message provided. response headers: {headers:?}" )] RequestFailure { - url: reqwest::Url, + url: Box, status: StatusCode, headers: Box, }, @@ -374,7 +374,7 @@ pub enum HttpClientError { "the returned response from {url} was empty. status: '{status}'. response headers: {headers:?}" )] EmptyResponse { - url: reqwest::Url, + url: Box, status: StatusCode, headers: Box, }, @@ -383,7 +383,7 @@ pub enum HttpClientError { "failed to resolve request for {url}. status: '{status}'. response headers: {headers:?}. additional error message: {error}" )] EndpointFailure { - url: reqwest::Url, + url: Box, status: StatusCode, headers: Box, error: String, @@ -453,7 +453,7 @@ impl HttpClientError { pub fn request_send_error(url: reqwest::Url, source: reqwest::Error) -> Self { HttpClientError::RequestSendFailure { - url, + url: Box::new(url), source: ReqwestErrorWrapper(source), } } @@ -1517,7 +1517,7 @@ where if !allow_empty && let Some(0) = res.content_length() { return Err(HttpClientError::EmptyResponse { - url, + url: Box::new(url), status, headers: Box::new(headers), }); @@ -1530,25 +1530,25 @@ where .bytes() .await .map_err(|source| HttpClientError::ResponseReadFailure { - url, + url: Box::new(url), headers: Box::new(headers.clone()), status, source: ReqwestErrorWrapper(source), })?; decode_raw_response(&headers, full) } else if res.status() == StatusCode::NOT_FOUND { - Err(HttpClientError::NotFound { url }) + Err(HttpClientError::NotFound { url: Box::new(url) }) } else { let Ok(plaintext) = res.text().await else { return Err(HttpClientError::RequestFailure { - url, + url: Box::new(url), status, headers: Box::new(headers), }); }; Err(HttpClientError::EndpointFailure { - url, + url: Box::new(url), status, headers: Box::new(headers), error: plaintext, diff --git a/common/http-api-common/Cargo.toml b/common/http-api-common/Cargo.toml index 4a405c155e..17d7521788 100644 --- a/common/http-api-common/Cargo.toml +++ b/common/http-api-common/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "nym-http-api-common" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "Common crate for Nym-related HTTP API interaction" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -52,4 +53,4 @@ middleware = [ utoipa = ["dep:utoipa"] [lints] -workspace = true \ No newline at end of file +workspace = true diff --git a/common/http-api-common/src/lib.rs b/common/http-api-common/src/lib.rs index 9ed715cde2..3ce026396e 100644 --- a/common/http-api-common/src/lib.rs +++ b/common/http-api-common/src/lib.rs @@ -11,8 +11,10 @@ pub mod response; #[cfg(feature = "output")] pub use response::*; +pub use ::bincode::Options as BincodeOptions; + // be explicit about those values because bincode uses different defaults in different places -pub fn make_bincode_serializer() -> impl ::bincode::Options { +pub fn make_bincode_serializer() -> impl BincodeOptions { use ::bincode::Options; ::bincode::DefaultOptions::new() .with_little_endian() diff --git a/common/inclusion-probability/Cargo.toml b/common/inclusion-probability/Cargo.toml index b98a1ec5e8..d27d0a23fa 100644 --- a/common/inclusion-probability/Cargo.toml +++ b/common/inclusion-probability/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-inclusion-probability" -version = "0.1.0" +version.workspace = true description = "Nym active set probability simulator" edition.workspace = true authors.workspace = true diff --git a/common/ip-packet-requests/Cargo.toml b/common/ip-packet-requests/Cargo.toml index cd9663dc90..487ff4d782 100644 --- a/common/ip-packet-requests/Cargo.toml +++ b/common/ip-packet-requests/Cargo.toml @@ -1,20 +1,22 @@ [package] name = "nym-ip-packet-requests" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "Codec, signing functionality, and different version definitions for IP packet request and responses" + [dependencies] 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" } +nym-bin-common = { workspace = true } +nym-crypto = { workspace = true } +nym-service-provider-requests-common = { workspace = true } +nym-sphinx = { workspace = true } rand = { workspace = true } serde = { workspace = true, features = ["derive"] } thiserror = { workspace = true } diff --git a/common/ledger/Cargo.toml b/common/ledger/Cargo.toml index 72de9bb003..ff3cf35adf 100644 --- a/common/ledger/Cargo.toml +++ b/common/ledger/Cargo.toml @@ -1,8 +1,9 @@ [package] name = "nym-ledger" -version = "0.1.0" +version.workspace = true edition = "2021" license.workspace = true +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/common/mixnode-common/Cargo.toml b/common/mixnode-common/Cargo.toml index 8fbab39525..50d1c94cde 100644 --- a/common/mixnode-common/Cargo.toml +++ b/common/mixnode-common/Cargo.toml @@ -1,9 +1,13 @@ [package] name = "nym-mixnode-common" -version = "0.1.0" +version.workspace = true authors = ["Jędrzej Stuczyński "] edition = "2021" license.workspace = true +description = "Common crate for Nym Mix Nodes" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -26,14 +30,13 @@ url = { workspace = true } time.workspace = true thiserror = { workspace = true } -nym-crypto = { path = "../crypto", features = ["asymmetric"] } -nym-network-defaults = { path = "../network-defaults" } -nym-sphinx-acknowledgements = { path = "../nymsphinx/acknowledgements" } -nym-sphinx-addressing = { path = "../nymsphinx/addressing" } -nym-sphinx-forwarding = { path = "../nymsphinx/forwarding" } -nym-sphinx-framing = { path = "../nymsphinx/framing" } -nym-sphinx-params = { path = "../nymsphinx/params" } -nym-sphinx-types = { path = "../nymsphinx/types" } -nym-task = { path = "../task" } -nym-metrics = { path = "../nym-metrics" } - +nym-crypto = { workspace = true, features = ["asymmetric"] } +nym-network-defaults = { workspace = true } +nym-sphinx-acknowledgements = { workspace = true } +nym-sphinx-addressing = { workspace = true } +nym-sphinx-forwarding = { workspace = true } +nym-sphinx-framing = { workspace = true } +nym-sphinx-params = { workspace = true } +nym-sphinx-types = { workspace = true } +nym-task = { workspace = true } +nym-metrics = { workspace = true } diff --git a/common/network-defaults/Cargo.toml b/common/network-defaults/Cargo.toml index 1287d2a7e4..924d09151f 100644 --- a/common/network-defaults/Cargo.toml +++ b/common/network-defaults/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-network-defaults" -version = "0.1.0" +version.workspace = true description = "Nym network defaults" edition.workspace = true authors.workspace = true diff --git a/common/network-defaults/src/mainnet.rs b/common/network-defaults/src/mainnet.rs index 4465875b36..e2c33def93 100644 --- a/common/network-defaults/src/mainnet.rs +++ b/common/network-defaults/src/mainnet.rs @@ -51,6 +51,10 @@ pub const NYM_APIS: &[ApiUrlConst] = &[ url: "https://nym-frontdoor.global.ssl.fastly.net/api/", front_hosts: Some(&["yelp.global.ssl.fastly.net"]), }, + ApiUrlConst { + url: "https://cdn1.media-platform.net/api/", + front_hosts: None, + }, ]; pub const NYM_VPN_API: &str = "https://nymvpn.com/api/"; diff --git a/common/node-tester-utils/Cargo.toml b/common/node-tester-utils/Cargo.toml index 89e4043ad3..d3845fb094 100644 --- a/common/node-tester-utils/Cargo.toml +++ b/common/node-tester-utils/Cargo.toml @@ -1,8 +1,12 @@ [package] name = "nym-node-tester-utils" -version = "0.1.0" +version.workspace = true edition = "2021" license.workspace = true +description = "Utils for the Nym Node Tester" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -16,17 +20,17 @@ serde_json = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true, features = ["macros"] } -nym-crypto = { path = "../crypto", features = ["asymmetric"] } -nym-task = { path = "../task" } -nym-topology = { path = "../topology" } -nym-sphinx-params = { path = "../nymsphinx/params" } +nym-crypto = { workspace = true, features = ["asymmetric"] } +nym-task = { workspace = true } +nym-topology = { workspace = true } +nym-sphinx-params = { workspace = true } # TODO: do we need the whole nymsphinx? -nym-sphinx = { path = "../nymsphinx" } +nym-sphinx = { workspace = true } ## non-wasm-only dependencies [target."cfg(not(target_arch = \"wasm32\"))".dependencies.log] workspace = true ## wasm-only dependencies -[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-utils] -path = "../wasm/utils" +[target."cfg(target_arch = \"wasm32\")".dependencies.nym-wasm-utils] +workspace = true diff --git a/common/node-tester-utils/src/lib.rs b/common/node-tester-utils/src/lib.rs index 444d1780f8..6a164c460b 100644 --- a/common/node-tester-utils/src/lib.rs +++ b/common/node-tester-utils/src/lib.rs @@ -21,7 +21,7 @@ pub(crate) type NodeId = u32; macro_rules! log_err { ($($t:tt)*) => {{ #[cfg(target_arch = "wasm32")] - {::wasm_utils::console_error!($($t)*)} + {::nym_wasm_utils::console_error!($($t)*)} #[cfg(not(target_arch = "wasm32"))] {::log::error!($($t)*)} @@ -32,7 +32,7 @@ macro_rules! log_err { macro_rules! log_warn { ($($t:tt)*) => {{ #[cfg(target_arch = "wasm32")] - {::wasm_utils::console_warn!($($t)*)} + {::nym_wasm_utils::console_warn!($($t)*)} #[cfg(not(target_arch = "wasm32"))] {::log::warn!($($t)*)} @@ -43,7 +43,7 @@ macro_rules! log_warn { macro_rules! log_info { ($($t:tt)*) => {{ #[cfg(target_arch = "wasm32")] - {::wasm_utils::console_log!($($t)*)} + {::nym_wasm_utils::console_log!($($t)*)} #[cfg(not(target_arch = "wasm32"))] {::log::info!($($t)*)} diff --git a/common/nonexhaustive-delayqueue/Cargo.toml b/common/nonexhaustive-delayqueue/Cargo.toml index 76903a4b5f..f8d4671688 100644 --- a/common/nonexhaustive-delayqueue/Cargo.toml +++ b/common/nonexhaustive-delayqueue/Cargo.toml @@ -1,9 +1,13 @@ [package] name = "nym-nonexhaustive-delayqueue" -version = "0.1.0" +version.workspace = true authors = ["Jędrzej Stuczyński "] edition = "2021" license.workspace = true +description = "A copy of tokio-util delay_queue with `Sleep` and `Instant` being replaced with`wasm_timer` equivalents" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -20,4 +24,3 @@ features = ["time"] [target."cfg(target_arch = \"wasm32\")".dependencies.wasmtimer] workspace = true features = ["tokio", "tokio-util"] - diff --git a/common/nym-cache/Cargo.toml b/common/nym-cache/Cargo.toml index 8bcfc2e997..3d935c3e89 100644 --- a/common/nym-cache/Cargo.toml +++ b/common/nym-cache/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-cache" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true @@ -9,6 +9,7 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +description = "Helper functions around a RwLock for writing to local cache of items" [dependencies] tokio = { workspace = true, features = ["sync"] } diff --git a/common/nym-common/Cargo.toml b/common/nym-common/Cargo.toml index 5bf119c8ee..6bdbef1bce 100644 --- a/common/nym-common/Cargo.toml +++ b/common/nym-common/Cargo.toml @@ -1,10 +1,11 @@ [package] name = "nym-common" -version = "1.18.0" +version.workspace = true authors.workspace = true repository.workspace = true license.workspace = true edition.workspace = true +description = "Runtime diagnostics for high frequency logging, debugging and error handling utilities" [lints] workspace = true diff --git a/common/nym-connection-monitor/Cargo.toml b/common/nym-connection-monitor/Cargo.toml index e19991d3e4..03de5da740 100644 --- a/common/nym-connection-monitor/Cargo.toml +++ b/common/nym-connection-monitor/Cargo.toml @@ -7,6 +7,7 @@ homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +publish = false [lints] workspace = true @@ -15,10 +16,10 @@ workspace = true bincode.workspace = true bytes.workspace = true futures.workspace = true -nym-config = { path = "../config" } -nym-common = { path = "../nym-common" } -nym-ip-packet-requests = { path = "../ip-packet-requests" } -nym-sdk = { path = "../../sdk/rust/nym-sdk" } +nym-config = { workspace = true } +nym-common = { workspace = true } +nym-ip-packet-requests = { workspace = true } +nym-sdk = { workspace = true } pnet_packet.workspace = true thiserror.workspace = true tokio.workspace = true diff --git a/common/nym-id/Cargo.toml b/common/nym-id/Cargo.toml index eb80073fb7..17b4ac31ef 100644 --- a/common/nym-id/Cargo.toml +++ b/common/nym-id/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "nym-id" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "Functionality for importing and storing credentials and cryptographic keys" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -16,5 +17,5 @@ time.workspace = true tracing.workspace = true zeroize.workspace = true -nym-credential-storage = { path = "../credential-storage" } -nym-credentials = { path = "../credentials" } +nym-credential-storage = { workspace = true } +nym-credentials = { workspace = true } diff --git a/common/nym-kcp/Cargo.toml b/common/nym-kcp/Cargo.toml index 73cf7edfcb..488c9fff8a 100644 --- a/common/nym-kcp/Cargo.toml +++ b/common/nym-kcp/Cargo.toml @@ -1,8 +1,11 @@ [package] name = "nym-kcp" version = "0.1.0" +description = "KCP protocol implementation for Nym" edition = { workspace = true } +authors = { workspace = true } license = { workspace = true } +repository = { workspace = true } [lib] name = "nym_kcp" diff --git a/common/nym-kkt-ciphersuite/Cargo.toml b/common/nym-kkt-ciphersuite/Cargo.toml new file mode 100644 index 0000000000..079325f625 --- /dev/null +++ b/common/nym-kkt-ciphersuite/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "nym-kkt-ciphersuite" +authors.workspace = true +repository.workspace = true +homepage.workspace = true +documentation.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +readme.workspace = true +version.workspace = true + +[dependencies] +thiserror = { workspace = true } +num_enum = { workspace = true } +strum = { workspace = true } +strum_macros = { workspace = true } + +blake3 = { workspace = true, optional = true } +libcrux-sha3 = { git = "https://github.com/cryspen/libcrux", optional = true } + +[features] +digests = ["blake3", "libcrux-sha3"] + +[lints] +workspace = true diff --git a/common/nym-kkt-ciphersuite/src/error.rs b/common/nym-kkt-ciphersuite/src/error.rs new file mode 100644 index 0000000000..f644888d1e --- /dev/null +++ b/common/nym-kkt-ciphersuite/src/error.rs @@ -0,0 +1,21 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum KKTCiphersuiteError { + #[error( + "attempted to use an insecure encapsulation key hash length. requested: {requested}. minimum: {minimum}" + )] + InsecureHashLen { requested: u8, minimum: u8 }, + + #[error("{raw} does not correspond to any known KEM type encoding")] + UnknownKEMType { raw: u8 }, + + #[error("{raw} does not correspond to any known Hash Function type encoding")] + UnknownHashFunctionType { raw: u8 }, + + #[error("{raw} does not correspond to any known Signature Scheme type encoding")] + UnknownSignatureSchemeType { raw: u8 }, +} diff --git a/common/nym-kkt-ciphersuite/src/lib.rs b/common/nym-kkt-ciphersuite/src/lib.rs new file mode 100644 index 0000000000..e72d873a87 --- /dev/null +++ b/common/nym-kkt-ciphersuite/src/lib.rs @@ -0,0 +1,383 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::error::KKTCiphersuiteError; +use num_enum::{IntoPrimitive, TryFromPrimitive}; +use std::collections::HashMap; +use std::fmt::Display; +use strum_macros::{Display, EnumIter, EnumString}; + +pub mod error; + +pub const DEFAULT_HASH_LEN: usize = 32; +const _: () = assert!(DEFAULT_HASH_LEN <= u8::MAX as usize); + +pub const MINIMUM_SECURE_HASH_LEN: u8 = 16; +const _: () = assert!(MINIMUM_SECURE_HASH_LEN <= DEFAULT_HASH_LEN as u8); + +pub const CIPHERSUITE_ENCODING_LEN: usize = 4; + +// no point in importing curve libraries for well-defined constants +pub mod ed25519 { + pub const SECRET_KEY_LENGTH: usize = 32; + pub const PUBLIC_KEY_LENGTH: usize = 32; + pub const SIGNATURE_LENGTH: usize = 64; +} + +pub mod x25519 { + pub const PUBLIC_KEY_LENGTH: usize = 32; + pub const SECRET_KEY_LENGTH: usize = 32; +} + +pub mod ml_kem768 { + pub const PUBLIC_KEY_LENGTH: usize = 1184; +} + +pub mod mceliece { + pub const PUBLIC_KEY_LENGTH: usize = 524160; + pub const SECRET_KEY_LENGTH: usize = 13608; + pub const CIPHERTEXT_LENGTH: usize = 156; +} + +pub mod xwing { + use crate::{ml_kem768, x25519}; + + pub const PUBLIC_KEY_LENGTH: usize = x25519::PUBLIC_KEY_LENGTH + ml_kem768::PUBLIC_KEY_LENGTH; +} + +pub type KEMKeyDigests = KeyDigests; +pub type SigningKeyDigests = KeyDigests; + +pub type KeyDigests = HashMap>; + +#[derive( + Clone, + Copy, + PartialEq, + Eq, + Hash, + Debug, + IntoPrimitive, + TryFromPrimitive, + EnumIter, + EnumString, + Display, +)] +#[strum(ascii_case_insensitive)] +#[strum(serialize_all = "lowercase")] +#[repr(u8)] +pub enum HashFunction { + Blake3 = 0, + Shake256 = 1, + Shake128 = 2, + SHA256 = 3, +} + +impl HashFunction { + #[cfg(feature = "digests")] + pub fn digest>(&self, data: M, output_length: usize) -> Vec { + let mut out = vec![0u8; output_length]; + match self { + HashFunction::Blake3 => { + let mut hasher = blake3::Hasher::new(); + hasher.update(data.as_ref()); + hasher.finalize_xof().fill(&mut out); + hasher.reset(); + } + HashFunction::Shake256 => libcrux_sha3::shake256_ema(&mut out, data.as_ref()), + HashFunction::Shake128 => libcrux_sha3::shake128_ema(&mut out, data.as_ref()), + HashFunction::SHA256 => libcrux_sha3::sha256_ema(&mut out, data.as_ref()), + } + + out + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, IntoPrimitive)] +#[repr(u8)] +pub enum HashLength { + Default = 0, + #[num_enum(catch_all)] + Custom(u8), +} + +impl Display for HashLength { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + HashLength::Default => DEFAULT_HASH_LEN.fmt(f), + HashLength::Custom(custom_len) => custom_len.fmt(f), + } + } +} + +impl HashLength { + pub fn decode(raw: u8) -> Result { + // check if we're using encoding for 'default' value + if raw == u8::from(Self::Default) { + return Ok(Self::Default); + } + // otherwise, we treat it as a custom length, and we have to validate its security + let custom_len = raw; + + if custom_len < MINIMUM_SECURE_HASH_LEN { + return Err(KKTCiphersuiteError::InsecureHashLen { + requested: custom_len, + minimum: MINIMUM_SECURE_HASH_LEN, + }); + } + Ok(Self::Custom(custom_len)) + } +} + +impl TryFrom> for HashLength { + type Error = KKTCiphersuiteError; + + fn try_from(value: Option) -> Result { + match value { + None => Ok(Self::Default), + Some(custom_len) => Self::decode(custom_len), + } + } +} + +impl HashLength { + pub const fn value(&self) -> usize { + match self { + HashLength::Default => DEFAULT_HASH_LEN, + HashLength::Custom(custom_len) => *custom_len as usize, + } + } +} + +#[derive( + Clone, + Copy, + PartialEq, + Eq, + Hash, + Debug, + IntoPrimitive, + TryFromPrimitive, + EnumIter, + EnumString, + Display, +)] +#[strum(ascii_case_insensitive)] +#[strum(serialize_all = "lowercase")] +#[repr(u8)] +pub enum SignatureScheme { + Ed25519 = 0, +} + +impl SignatureScheme { + pub const fn signing_key_length(&self) -> usize { + match self { + // 32 bytes + SignatureScheme::Ed25519 => ed25519::SECRET_KEY_LENGTH, + } + } + + pub const fn verification_key_length(&self) -> usize { + match self { + // 32 bytes + SignatureScheme::Ed25519 => ed25519::PUBLIC_KEY_LENGTH, + } + } + + pub const fn signature_length(&self) -> usize { + match self { + // 64 bytes + SignatureScheme::Ed25519 => ed25519::SIGNATURE_LENGTH, + } + } +} + +#[derive( + Clone, + Copy, + PartialEq, + Eq, + Hash, + Debug, + IntoPrimitive, + TryFromPrimitive, + EnumIter, + EnumString, + Display, +)] +#[strum(ascii_case_insensitive)] +#[strum(serialize_all = "lowercase")] +#[repr(u8)] +pub enum KEM { + XWing = 0, + MlKem768 = 1, + McEliece = 2, + X25519 = 255, +} + +impl KEM { + pub fn encapsulation_key_length(&self) -> usize { + match self { + KEM::MlKem768 => ml_kem768::PUBLIC_KEY_LENGTH, + KEM::XWing => xwing::PUBLIC_KEY_LENGTH, + KEM::X25519 => x25519::PUBLIC_KEY_LENGTH, + KEM::McEliece => mceliece::PUBLIC_KEY_LENGTH, + } + } +} + +#[derive(Clone, Copy, PartialEq, Debug)] +pub struct Ciphersuite { + hash_function: HashFunction, + signature_scheme: SignatureScheme, + kem: KEM, + hash_length: HashLength, + encapsulation_key_length: usize, + signing_key_length: usize, + verification_key_length: usize, + signature_length: usize, +} + +impl Ciphersuite { + pub fn new( + kem: KEM, + hash_function: HashFunction, + signature_scheme: SignatureScheme, + hash_length: HashLength, + ) -> Self { + Self { + hash_function, + signature_scheme, + kem, + hash_length, + encapsulation_key_length: kem.encapsulation_key_length(), + signing_key_length: signature_scheme.signing_key_length(), + verification_key_length: signature_scheme.verification_key_length(), + signature_length: signature_scheme.signature_length(), + } + } + + pub fn kem_key_len(&self) -> usize { + self.encapsulation_key_length + } + + pub fn signature_len(&self) -> usize { + self.signature_length + } + + pub fn signing_key_len(&self) -> usize { + self.signing_key_length + } + + pub fn verification_key_len(&self) -> usize { + self.verification_key_length + } + + pub fn hash_function(&self) -> HashFunction { + self.hash_function + } + + pub fn kem(&self) -> KEM { + self.kem + } + + pub fn signature_scheme(&self) -> SignatureScheme { + self.signature_scheme + } + + pub fn hash_len(&self) -> usize { + self.hash_length.value() + } + + pub fn resolve_ciphersuite( + kem: KEM, + hash_function: HashFunction, + signature_scheme: SignatureScheme, + // This should be None 99.9999% of the time + custom_hash_length: Option, + ) -> Result { + let hash_length = HashLength::try_from(custom_hash_length)?; + + Ok(Ciphersuite::new( + kem, + hash_function, + signature_scheme, + hash_length, + )) + } + pub fn encode(&self) -> [u8; CIPHERSUITE_ENCODING_LEN] { + // [kem, hash, hashlen, sig] + [ + self.kem.into(), + self.hash_function.into(), + self.hash_length.into(), + self.signature_scheme.into(), + ] + } + + pub fn decode(encoding: [u8; CIPHERSUITE_ENCODING_LEN]) -> Result { + let raw_kem = encoding[0]; + let raw_hash_function = encoding[1]; + let hash_len = encoding[2]; + let raw_signature_scheme = encoding[3]; + + let kem = KEM::try_from(raw_kem) + .map_err(|_| KKTCiphersuiteError::UnknownKEMType { raw: raw_kem })?; + let hash_function = HashFunction::try_from(raw_hash_function).map_err(|_| { + KKTCiphersuiteError::UnknownHashFunctionType { + raw: raw_hash_function, + } + })?; + let hash_length = HashLength::decode(hash_len)?; + let signature_scheme = SignatureScheme::try_from(raw_signature_scheme).map_err(|_| { + KKTCiphersuiteError::UnknownSignatureSchemeType { + raw: raw_signature_scheme, + } + })?; + + Ok(Self::new(kem, hash_function, signature_scheme, hash_length)) + } +} + +impl Display for Ciphersuite { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + &format!( + "{}_{}({})_{}", + self.kem, self.hash_function, self.hash_length, self.signature_scheme + ) + .to_ascii_lowercase(), + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::str::FromStr; + use strum::IntoEnumIterator; + + #[test] + fn kem_display_consistency() { + for kem in KEM::iter() { + let display = format!("{kem}"); + assert_eq!(kem, KEM::from_str(&display).unwrap()); + } + } + + #[test] + fn hash_function_display_consistency() { + for hash_fn in HashFunction::iter() { + let display = format!("{hash_fn}"); + assert_eq!(hash_fn, HashFunction::from_str(&display).unwrap()); + } + } + + #[test] + fn signature_scheme_display_consistency() { + for scheme in SignatureScheme::iter() { + let display = format!("{scheme}"); + assert_eq!(scheme, SignatureScheme::from_str(&display).unwrap()); + } + } +} diff --git a/common/nym-kkt/Cargo.toml b/common/nym-kkt/Cargo.toml index 30d008ee52..fcc535016d 100644 --- a/common/nym-kkt/Cargo.toml +++ b/common/nym-kkt/Cargo.toml @@ -4,28 +4,34 @@ version = "0.1.0" authors = ["Georgio Nicolas "] edition = { workspace = true } license.workspace = true +publish = false [dependencies] blake3 = { workspace = true } thiserror = { workspace = true } +num_enum = { workspace = true } +strum = { workspace = true } + # internal nym-crypto = { path = "../crypto", features = ["asymmetric", "serde"] } +nym-kkt-ciphersuite = { workspace = true, features = ["digests"] } -libcrux-traits = { git = "https://github.com/cryspen/libcrux" } libcrux-kem = { git = "https://github.com/cryspen/libcrux" } -libcrux-psq = { git = "https://github.com/cryspen/libcrux", features = ["test-utils"] } -libcrux-sha3 = { git = "https://github.com/cryspen/libcrux" } -libcrux-ml-kem = { git = "https://github.com/cryspen/libcrux" } libcrux-ecdh = { git = "https://github.com/cryspen/libcrux", features = ["codec"] } +libcrux-chacha20poly1305 = { git = "https://github.com/cryspen/libcrux" } rand = "0.9.2" +zeroize = { workspace = true, features = ["zeroize_derive"] } classic-mceliece-rust = { git = "https://github.com/georgio/classic-mceliece-rust", features = ["mceliece460896f", "zeroize"] } [dev-dependencies] +rand_chacha = "0.9.0" +anyhow = { workspace = true } criterion = { workspace = true } + [[bench]] name = "benches" harness = false diff --git a/common/nym-kkt/benches/benches.rs b/common/nym-kkt/benches/benches.rs index 62038d6c36..2acb7ab7b5 100644 --- a/common/nym-kkt/benches/benches.rs +++ b/common/nym-kkt/benches/benches.rs @@ -48,13 +48,14 @@ pub fn kkt_benchmark(c: &mut Criterion) { let mut secret_responder: [u8; 32] = [0u8; 32]; rng.fill_bytes(&mut secret_responder); + let responder_ed25519_keypair = ed25519::KeyPair::from_secret(secret_responder, 1); for kem in [KEM::MlKem768, KEM::XWing, KEM::X25519, KEM::McEliece] { for hash_function in [ HashFunction::Blake3, HashFunction::SHA256, - HashFunction::SHAKE128, - HashFunction::SHAKE256, + HashFunction::Shake128, + HashFunction::Shake256, ] { let ciphersuite = Ciphersuite::resolve_ciphersuite( kem, @@ -104,10 +105,7 @@ pub fn kkt_benchmark(c: &mut Criterion) { // Anonymous Initiator, OneWay { c.bench_function( - &format!( - "{}, {} | Anonymous Initiator: Generate Request", - kem, hash_function - ), + &format!("{kem}, {hash_function} | Anonymous Initiator: Generate Request",), |b| { b.iter(|| anonymous_initiator_process(&mut rng, ciphersuite).unwrap()); }, @@ -118,8 +116,7 @@ pub fn kkt_benchmark(c: &mut Criterion) { c.bench_function( &format!( - "{}, {} | Anonymous Initiator: Encode Frame - Request", - kem, hash_function + "{kem}, {hash_function} | Anonymous Initiator: Encode Frame - Request", ), |b| b.iter(|| i_frame.to_bytes()), ); @@ -128,8 +125,7 @@ pub fn kkt_benchmark(c: &mut Criterion) { c.bench_function( &format!( - "{}, {} | Anonymous Initiator: Decode Frame - Request", - kem, hash_function + "{kem}, {hash_function} | Anonymous Initiator: Decode Frame - Request", ), |b| b.iter(|| KKTFrame::from_bytes(&i_frame_bytes).unwrap()), ); @@ -138,8 +134,7 @@ pub fn kkt_benchmark(c: &mut Criterion) { c.bench_function( &format!( - "{}, {} | Anonymous Initiator: Responder Ingest Frame", - kem, hash_function + "{kem}, {hash_function} | Anonymous Initiator: Responder Ingest Frame", ), |b| { b.iter(|| { @@ -153,14 +148,13 @@ pub fn kkt_benchmark(c: &mut Criterion) { c.bench_function( &format!( - "{}, {} | Anonymous Initiator: Responder Generate Response", - kem, hash_function + "{kem}, {hash_function} | Anonymous Initiator: Responder Generate Response", ), |b| { b.iter(|| { responder_process( &mut r_context, - i_frame_r.session_id_ref(), + i_frame_r.session_id(), responder_ed25519_keypair.private_key(), &responder_kem_public_key, ) @@ -170,7 +164,7 @@ pub fn kkt_benchmark(c: &mut Criterion) { ); let r_frame = responder_process( &mut r_context, - i_frame_r.session_id_ref(), + i_frame_r.session_id(), responder_ed25519_keypair.private_key(), &responder_kem_public_key, ) @@ -178,26 +172,23 @@ pub fn kkt_benchmark(c: &mut Criterion) { c.bench_function( &format!( - "{}, {} | Anonymous Initiator: Responder Encode Frame", - kem, hash_function + "{kem}, {hash_function} | Anonymous Initiator: Responder Encode Frame", ), |b| b.iter(|| r_frame.to_bytes()), ); - let r_bytes = r_frame.to_bytes(); - c.bench_function( &format!( - "{}, {} | Anonymous Initiator: Initiator Ingest Response", - kem, hash_function + "{kem}, {hash_function} | Anonymous Initiator: Initiator Ingest Response", ), |b| { b.iter(|| { initiator_ingest_response( &mut i_context, + &r_frame, + &r_frame.context().unwrap(), responder_ed25519_keypair.public_key(), &r_dir_hash, - &r_bytes, ) .unwrap() }); @@ -206,9 +197,10 @@ pub fn kkt_benchmark(c: &mut Criterion) { let obtained_key = initiator_ingest_response( &mut i_context, + &r_frame, + &r_frame.context().unwrap(), responder_ed25519_keypair.public_key(), &r_dir_hash, - &r_bytes, ) .unwrap(); @@ -226,10 +218,7 @@ pub fn kkt_benchmark(c: &mut Criterion) { .unwrap(); c.bench_function( - &format!( - "{}, {} | Initiator OneWay: Generate Request", - kem, hash_function - ), + &format!("{kem}, {hash_function} | Initiator OneWay: Generate Request",), |b| { b.iter(|| { initiator_process( @@ -245,30 +234,21 @@ pub fn kkt_benchmark(c: &mut Criterion) { ); c.bench_function( - &format!( - "{}, {} | Initiator OneWay: Encode Frame - Request", - kem, hash_function - ), + &format!("{kem}, {hash_function} | Initiator OneWay: Encode Frame - Request",), |b| b.iter(|| i_frame.to_bytes()), ); let i_frame_bytes = i_frame.to_bytes(); c.bench_function( - &format!( - "{}, {} | Initiator OneWay: Decode Frame - Request", - kem, hash_function - ), + &format!("{kem}, {hash_function} | Initiator OneWay: Decode Frame - Request",), |b| b.iter(|| KKTFrame::from_bytes(&i_frame_bytes).unwrap()), ); let (i_frame_r, r_context) = KKTFrame::from_bytes(&i_frame_bytes).unwrap(); c.bench_function( - &format!( - "{}, {} | Initiator OneWay: Responder Ingest Frame", - kem, hash_function - ), + &format!("{kem}, {hash_function} | Initiator OneWay: Responder Ingest Frame",), |b| { b.iter(|| { responder_ingest_message( @@ -294,14 +274,13 @@ pub fn kkt_benchmark(c: &mut Criterion) { c.bench_function( &format!( - "{}, {} | Initiator OneWay: Responder Generate Response", - kem, hash_function + "{kem}, {hash_function} | Initiator OneWay: Responder Generate Response", ), |b| { b.iter(|| { responder_process( &mut r_context, - i_frame_r.session_id_ref(), + i_frame_r.session_id(), responder_ed25519_keypair.private_key(), &responder_kem_public_key, ) @@ -312,36 +291,31 @@ pub fn kkt_benchmark(c: &mut Criterion) { let r_frame = responder_process( &mut r_context, - i_frame_r.session_id_ref(), + i_frame_r.session_id(), responder_ed25519_keypair.private_key(), &responder_kem_public_key, ) .unwrap(); c.bench_function( - &format!( - "{}, {} | Initiator OneWay: Responder Encode Frame", - kem, hash_function - ), + &format!("{kem}, {hash_function} | Initiator OneWay: Responder Encode Frame",), |b| { b.iter(|| r_frame.to_bytes()); }, ); - let r_bytes = r_frame.to_bytes(); - c.bench_function( &format!( - "{}, {} | Initiator OneWay: Initiator Ingest Response", - kem, hash_function + "{kem}, {hash_function} | Initiator OneWay: Initiator Ingest Response", ), |b| { b.iter(|| { initiator_ingest_response( &mut i_context, + &r_frame, + &r_frame.context().unwrap(), responder_ed25519_keypair.public_key(), &r_dir_hash, - &r_bytes, ) .unwrap() }); @@ -350,9 +324,10 @@ pub fn kkt_benchmark(c: &mut Criterion) { let i_obtained_key = initiator_ingest_response( &mut i_context, + &r_frame, + &r_frame.context().unwrap(), responder_ed25519_keypair.public_key(), &r_dir_hash, - &r_bytes, ) .unwrap(); @@ -362,10 +337,7 @@ pub fn kkt_benchmark(c: &mut Criterion) { // Initiator, Mutual { c.bench_function( - &format!( - "{}, {} | Initiator Mutual: Generate Request", - kem, hash_function - ), + &format!("{kem}, {hash_function} | Initiator Mutual: Generate Request",), |b| { b.iter(|| { initiator_process( @@ -390,10 +362,7 @@ pub fn kkt_benchmark(c: &mut Criterion) { .unwrap(); c.bench_function( - &format!( - "{}, {} | Initiator Mutual: Encode Frame - Request", - kem, hash_function - ), + &format!("{kem}, {hash_function} | Initiator Mutual: Encode Frame - Request",), |b| { b.iter(|| i_frame.to_bytes()); }, @@ -402,10 +371,7 @@ pub fn kkt_benchmark(c: &mut Criterion) { let i_frame_bytes = i_frame.to_bytes(); c.bench_function( - &format!( - "{}, {} | Initiator Mutual: Decode Frame - Request", - kem, hash_function - ), + &format!("{kem}, {hash_function} | Initiator Mutual: Decode Frame - Request",), |b| { b.iter(|| KKTFrame::from_bytes(&i_frame_bytes).unwrap()); }, @@ -414,10 +380,7 @@ pub fn kkt_benchmark(c: &mut Criterion) { let (i_frame_r, r_context) = KKTFrame::from_bytes(&i_frame_bytes).unwrap(); c.bench_function( - &format!( - "{}, {} | Initiator Mutual: Responder Ingest Frame", - kem, hash_function - ), + &format!("{kem}, {hash_function} | Initiator Mutual: Responder Ingest Frame",), |b| { b.iter(|| { responder_ingest_message( @@ -443,14 +406,13 @@ pub fn kkt_benchmark(c: &mut Criterion) { c.bench_function( &format!( - "{}, {} | Initiator Mutual: Responder Generate Response", - kem, hash_function + "{kem}, {hash_function} | Initiator Mutual: Responder Generate Response", ), |b| { b.iter(|| { responder_process( &mut r_context, - i_frame_r.session_id_ref(), + i_frame_r.session_id(), responder_ed25519_keypair.private_key(), &responder_kem_public_key, ) @@ -461,17 +423,14 @@ pub fn kkt_benchmark(c: &mut Criterion) { let r_frame = responder_process( &mut r_context, - i_frame_r.session_id_ref(), + i_frame_r.session_id(), responder_ed25519_keypair.private_key(), &responder_kem_public_key, ) .unwrap(); c.bench_function( - &format!( - "{}, {} | Initiator Mutual: Responder Encode Frame", - kem, hash_function - ), + &format!("{kem}, {hash_function} | Initiator Mutual: Responder Encode Frame",), |b| { b.iter(|| { r_frame.to_bytes(); @@ -479,20 +438,18 @@ pub fn kkt_benchmark(c: &mut Criterion) { }, ); - let r_bytes = r_frame.to_bytes(); - c.bench_function( &format!( - "{}, {} | Initiator Mutual: Initiator Ingest Response", - kem, hash_function + "{kem}, {hash_function} | Initiator Mutual: Initiator Ingest Response", ), |b| { b.iter(|| { initiator_ingest_response( &mut i_context, + &r_frame, + &r_frame.context().unwrap(), responder_ed25519_keypair.public_key(), &r_dir_hash, - &r_bytes, ) .unwrap() }); @@ -501,9 +458,10 @@ pub fn kkt_benchmark(c: &mut Criterion) { let obtained_key = initiator_ingest_response( &mut i_context, + &r_frame, + &r_frame.context().unwrap(), responder_ed25519_keypair.public_key(), &r_dir_hash, - &r_bytes, ) .unwrap(); diff --git a/common/nym-kkt/src/ciphersuite.rs b/common/nym-kkt/src/ciphersuite.rs index 0ada7ffb4c..c87915881c 100644 --- a/common/nym-kkt/src/ciphersuite.rs +++ b/common/nym-kkt/src/ciphersuite.rs @@ -1,35 +1,10 @@ // Copyright 2025 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use std::fmt::Display; - -use libcrux_kem::{Algorithm, MlKem768PublicKey}; -use nym_crypto::asymmetric::ed25519; - use crate::error::KKTError; +use libcrux_kem::Algorithm; -pub const HASH_LEN_256: u8 = 32; -pub const CIPHERSUITE_ENCODING_LEN: usize = 4; - -pub const CURVE25519_KEY_LEN: usize = 32; - -#[derive(Clone, Copy, Debug)] -pub enum HashFunction { - Blake3, - SHAKE128, - SHAKE256, - SHA256, -} -impl Display for HashFunction { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(match self { - HashFunction::Blake3 => "Blake3", - HashFunction::SHAKE128 => "SHAKE128", - HashFunction::SHAKE256 => "SHAKE256", - HashFunction::SHA256 => "SHA256", - }) - } -} +pub use nym_kkt_ciphersuite::*; pub enum EncapsulationKey<'a> { MlKem768(libcrux_kem::PublicKey), @@ -87,210 +62,6 @@ impl<'a> EncapsulationKey<'a> { } } -#[derive(Clone, Copy, Debug)] -pub enum SignatureScheme { - Ed25519, -} -impl Display for SignatureScheme { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(match self { - SignatureScheme::Ed25519 => "Ed25519", - }) - } -} - -#[derive(Clone, Copy, Debug)] -pub enum KEM { - MlKem768, - XWing, - X25519, - McEliece, -} - -impl Display for KEM { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(match self { - KEM::MlKem768 => "MlKem768", - KEM::XWing => "XWing", - KEM::X25519 => "x25519", - KEM::McEliece => "McEliece", - }) - } -} - -#[derive(Clone, Copy, Debug)] -pub struct Ciphersuite { - hash_function: HashFunction, - signature_scheme: SignatureScheme, - kem: KEM, - hash_length: u8, - encapsulation_key_length: usize, - signing_key_length: usize, - verification_key_length: usize, - signature_length: usize, -} - -impl Ciphersuite { - pub fn kem_key_len(&self) -> usize { - self.encapsulation_key_length - } - - pub fn signature_len(&self) -> usize { - self.signature_length - } - pub fn signing_key_len(&self) -> usize { - self.signing_key_length - } - pub fn verification_key_len(&self) -> usize { - self.verification_key_length - } - pub fn hash_function(&self) -> HashFunction { - self.hash_function - } - pub fn kem(&self) -> KEM { - self.kem - } - pub fn signature_scheme(&self) -> SignatureScheme { - self.signature_scheme - } - pub fn hash_len(&self) -> usize { - self.hash_length as usize - } - - pub fn resolve_ciphersuite( - kem: KEM, - hash_function: HashFunction, - signature_scheme: SignatureScheme, - // This should be None 99.9999% of the time - custom_hash_length: Option, - ) -> Result { - let hash_len = match custom_hash_length { - Some(l) => { - if l < 16 { - return Err(KKTError::InsecureHashLen); - } else { - l - } - } - None => HASH_LEN_256, - }; - Ok(Self { - hash_function, - signature_scheme, - kem, - hash_length: hash_len, - encapsulation_key_length: match kem { - // 1184 bytes - KEM::MlKem768 => MlKem768PublicKey::len(), - // 1216 bytes = 1184 + 32 - KEM::XWing => MlKem768PublicKey::len() + CURVE25519_KEY_LEN, - // 32 bytes - KEM::X25519 => CURVE25519_KEY_LEN, - // 524160 bytes - KEM::McEliece => classic_mceliece_rust::CRYPTO_PUBLICKEYBYTES, - }, - signing_key_length: match signature_scheme { - // 32 bytes - SignatureScheme::Ed25519 => ed25519::SECRET_KEY_LENGTH, - }, - verification_key_length: match signature_scheme { - // 32 bytes - SignatureScheme::Ed25519 => ed25519::PUBLIC_KEY_LENGTH, - }, - signature_length: match signature_scheme { - // 64 bytes - SignatureScheme::Ed25519 => ed25519::SIGNATURE_LENGTH, - }, - }) - } - pub fn encode(&self) -> [u8; 4] { - // [kem, hash, hashlen, sig] - [ - match self.kem { - KEM::XWing => 0, - KEM::MlKem768 => 1, - KEM::McEliece => 2, - KEM::X25519 => 255, - }, - match self.hash_function { - HashFunction::Blake3 => 0, - HashFunction::SHAKE256 => 1, - HashFunction::SHAKE128 => 2, - HashFunction::SHA256 => 3, - }, - match self.hash_length { - HASH_LEN_256 => 0, - _ => self.hash_length, - }, - match self.signature_scheme { - SignatureScheme::Ed25519 => 0, - }, - ] - } - pub fn decode(encoding: &[u8]) -> Result { - if encoding.len() == 4 { - let kem = match encoding[0] { - 0 => KEM::XWing, - 1 => KEM::MlKem768, - 2 => KEM::McEliece, - 255 => KEM::X25519, - _ => { - return Err(KKTError::CiphersuiteDecodingError { - info: format!("Undefined KEM: {}", encoding[0]), - }); - } - }; - let hash_function = match encoding[1] { - 0 => HashFunction::Blake3, - 1 => HashFunction::SHAKE256, - 2 => HashFunction::SHAKE128, - 3 => HashFunction::SHA256, - _ => { - return Err(KKTError::CiphersuiteDecodingError { - info: format!("Undefined Hash Function: {}", encoding[1]), - }); - } - }; - - let custom_hash_length = match encoding[2] { - 0 => None, - _ => Some(encoding[2]), - }; - - let signature_scheme = match encoding[3] { - 0 => SignatureScheme::Ed25519, - _ => { - return Err(KKTError::CiphersuiteDecodingError { - info: format!("Undefined Signature Scheme: {}", encoding[3]), - }); - } - }; - - Self::resolve_ciphersuite(kem, hash_function, signature_scheme, custom_hash_length) - } else { - Err(KKTError::CiphersuiteDecodingError { - info: format!( - "Incorrect Encoding Length: actual: {} != expected: {}", - encoding.len(), - CIPHERSUITE_ENCODING_LEN - ), - }) - } - } -} - -impl Display for Ciphersuite { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str( - &format!( - "{}_{}({})_{}", - self.kem, self.hash_function, self.hash_length, self.signature_scheme - ) - .to_ascii_lowercase(), - ) - } -} - pub const fn map_kem_to_libcrux_kem(kem: KEM) -> Result { match kem { KEM::MlKem768 => Ok(Algorithm::MlKem768), diff --git a/common/nym-kkt/src/context.rs b/common/nym-kkt/src/context.rs index da66bd3ae6..8034c52af7 100644 --- a/common/nym-kkt/src/context.rs +++ b/common/nym-kkt/src/context.rs @@ -1,22 +1,25 @@ // Copyright 2025 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::ciphersuite::CIPHERSUITE_ENCODING_LEN; +use crate::{KKT_VERSION, ciphersuite::Ciphersuite, error::KKTError, frame::KKT_SESSION_ID_LEN}; +use num_enum::{IntoPrimitive, TryFromPrimitive}; use std::fmt::Display; -use crate::{KKT_VERSION, ciphersuite::Ciphersuite, error::KKTError, frame::KKT_SESSION_ID_LEN}; +pub const KKT_CONTEXT_LEN: usize = 3 + CIPHERSUITE_ENCODING_LEN; -pub const KKT_CONTEXT_LEN: usize = 7; - -#[derive(Clone, Copy, PartialEq, Debug)] +// bitmask used: 0b1110_0000 +#[derive(Clone, Copy, PartialEq, Debug, IntoPrimitive, TryFromPrimitive)] +#[repr(u8)] pub enum KKTStatus { - Ok, - InvalidRequestFormat, - InvalidResponseFormat, - InvalidSignature, - UnsupportedCiphersuite, - UnsupportedKKTVersion, - InvalidKey, - Timeout, + Ok = 0b0000_0000, + InvalidRequestFormat = 0b0010_0000, + InvalidResponseFormat = 0b0100_0000, + InvalidSignature = 0b0110_0000, + UnsupportedCiphersuite = 0b1000_0000, + UnsupportedKKTVersion = 0b1010_0000, + InvalidKey = 0b1100_0000, + Timeout = 0b1110_0000, } impl Display for KKTStatus { @@ -33,20 +36,25 @@ impl Display for KKTStatus { }) } } -#[derive(Clone, Copy, PartialEq, Debug)] + +// bitmask used: 0b0000_0011 +#[derive(Clone, Copy, PartialEq, Debug, IntoPrimitive, TryFromPrimitive)] +#[repr(u8)] pub enum KKTRole { - Initiator, - AnonymousInitiator, - Responder, + Initiator = 0b0000_0000, + Responder = 0b0000_0001, + AnonymousInitiator = 0b0000_0010, } -#[derive(Clone, Copy, PartialEq, Debug)] +// bitmask used: 0b0001_1100 +#[derive(Clone, Copy, PartialEq, Debug, IntoPrimitive, TryFromPrimitive)] +#[repr(u8)] pub enum KKTMode { - OneWay, - Mutual, + OneWay = 0b0000_0000, + Mutual = 0b0000_0100, } -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq)] pub struct KKTContext { version: u8, message_sequence: u8, @@ -127,11 +135,14 @@ impl KKTContext { } } - pub fn header_len(&self) -> usize { + pub const fn header_len(&self) -> usize { KKT_CONTEXT_LEN } - pub fn session_id_len(&self) -> usize { + pub const fn session_id_len(&self) -> usize { + // note: if anyone decides to update this function and changes the constant value, + // you will have to adjust encoding/decoding functions + // match self.role { // KKTRole::Initiator | KKTRole::Responder => SESSION_ID_LENGTH, // It doesn't make sense to send a session_id if we send messages in the clear @@ -144,115 +155,85 @@ impl KKTContext { self.body_len() + self.signature_len() + self.header_len() + self.session_id_len() } - pub fn encode(&self) -> Result, KKTError> { - let mut header_bytes: Vec = Vec::with_capacity(KKT_CONTEXT_LEN); + pub fn encode(&self) -> Result<[u8; KKT_CONTEXT_LEN], KKTError> { + let mut header_bytes = [0u8; KKT_CONTEXT_LEN]; if self.message_sequence >= 1 << 4 { return Err(KKTError::MessageCountLimitReached); } - header_bytes.push((KKT_VERSION << 4) + self.message_sequence); + let ciphersuite_bytes = self.ciphersuite.encode(); - header_bytes.push( - match self.status { - KKTStatus::Ok => 0, - KKTStatus::InvalidRequestFormat => 0b0010_0000, - KKTStatus::InvalidResponseFormat => 0b0100_0000, - KKTStatus::InvalidSignature => 0b0110_0000, - KKTStatus::UnsupportedCiphersuite => 0b1000_0000, - KKTStatus::UnsupportedKKTVersion => 0b1010_0000, - KKTStatus::InvalidKey => 0b1100_0000, - KKTStatus::Timeout => 0b1110_0000, - } + match self.mode { - KKTMode::OneWay => 0, - KKTMode::Mutual => 0b0000_0100, - } + match self.role { - KKTRole::Initiator => 0, - KKTRole::Responder => 1, - KKTRole::AnonymousInitiator => 2, - }, - ); + header_bytes[0] = (KKT_VERSION << 4) + self.message_sequence; + header_bytes[1] = u8::from(self.status) + u8::from(self.mode) + u8::from(self.role); - header_bytes.extend_from_slice(&self.ciphersuite.encode()); - header_bytes.push(0); + let mut i = 2; + for b in ciphersuite_bytes.into_iter() { + header_bytes[i] = b; + i += 1; + } + header_bytes[i] = 0; Ok(header_bytes) } - pub fn try_decode(header_bytes: &[u8]) -> Result { - if header_bytes.len() == KKT_CONTEXT_LEN { - let kkt_version = header_bytes[0] & 0b1111_0000; + pub fn try_decode(header_bytes: [u8; KKT_CONTEXT_LEN]) -> Result { + let kkt_version = (header_bytes[0] & 0b1111_0000) >> 4; + let message_sequence_counter = header_bytes[0] & 0b0000_1111; - let message_sequence_counter = header_bytes[0] & 0b0000_1111; + // We only check if stuff is valid here, not necessarily if it's compatible - // We only check if stuff is valid here, not necessarily if it's compatible - - if (kkt_version >> 4) > KKT_VERSION { - return Err(KKTError::FrameDecodingError { - info: format!("Header - Invalid KKT Version: {}", kkt_version >> 4), - }); - } - - let status = match header_bytes[1] & 0b1110_0000 { - 0 => KKTStatus::Ok, - 0b0010_0000 => KKTStatus::InvalidRequestFormat, - 0b0100_0000 => KKTStatus::InvalidResponseFormat, - 0b0110_0000 => KKTStatus::InvalidSignature, - 0b1000_0000 => KKTStatus::UnsupportedCiphersuite, - 0b1010_0000 => KKTStatus::UnsupportedKKTVersion, - 0b1100_0000 => KKTStatus::InvalidKey, - 0b1110_0000 => KKTStatus::Timeout, - _ => { - return Err(KKTError::FrameDecodingError { - info: format!( - "Header - Invalid KKT Status: {}", - header_bytes[1] & 0b1110_0000 - ), - }); - } - }; - - let role = match header_bytes[1] & 0b0000_0011 { - 0 => KKTRole::Initiator, - 1 => KKTRole::Responder, - 2 => KKTRole::AnonymousInitiator, - _ => { - return Err(KKTError::FrameDecodingError { - info: format!( - "Header - Invalid KKT Role: {}", - header_bytes[1] & 0b0000_0011 - ), - }); - } - }; - - let mode = match (header_bytes[1] & 0b0001_1100) >> 2 { - 0 => KKTMode::OneWay, - 1 => KKTMode::Mutual, - _ => { - return Err(KKTError::FrameDecodingError { - info: format!( - "Header - Invalid KKT Mode: {}", - (header_bytes[1] & 0b0001_1100) >> 2 - ), - }); - } - }; - - Ok(KKTContext { - version: kkt_version, - status, - mode, - role, - ciphersuite: Ciphersuite::decode(&header_bytes[2..6])?, - message_sequence: message_sequence_counter, - }) - } else { - Err(KKTError::FrameDecodingError { - info: format!( - "Header - Invalid Header Length: actual: {} != expected: {}", - header_bytes.len(), - KKT_CONTEXT_LEN - ), - }) + if kkt_version > KKT_VERSION { + return Err(KKTError::FrameDecodingError { + info: format!("Header - Invalid KKT Version: {kkt_version}"), + }); } + + let raw_kkt_status = header_bytes[1] & 0b1110_0000; + let raw_kkt_role = header_bytes[1] & 0b0000_0011; + let raw_kkt_mode = header_bytes[1] & 0b0001_1100; + + let status = + KKTStatus::try_from(raw_kkt_status).map_err(|_| KKTError::FrameDecodingError { + info: format!("Header - Invalid KKT Status: {raw_kkt_status}"), + })?; + let role = KKTRole::try_from(raw_kkt_role).map_err(|_| KKTError::FrameDecodingError { + info: format!("Header - Invalid KKT Role: {raw_kkt_role}"), + })?; + let mode = KKTMode::try_from(raw_kkt_mode).map_err(|_| KKTError::FrameDecodingError { + info: format!("Header - Invalid KKT Mode: {raw_kkt_mode}"), + })?; + + // SAFETY: we're taking exactly `CIPHERSUITE_ENCODING_LEN` bytes + #[allow(clippy::unwrap_used)] + let ciphersuite_bytes = header_bytes[2..2 + CIPHERSUITE_ENCODING_LEN] + .try_into() + .unwrap(); + + Ok(KKTContext { + version: kkt_version, + status, + mode, + role, + ciphersuite: Ciphersuite::decode(ciphersuite_bytes)?, + message_sequence: message_sequence_counter, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn kkt_context_encoding() { + let valid_context = KKTContext::new( + KKTRole::Initiator, + KKTMode::Mutual, + Ciphersuite::decode([255, 1, 0, 0]).unwrap(), + ) + .unwrap(); + let encoded = valid_context.encode().unwrap(); + let decoded = KKTContext::try_decode(encoded).unwrap(); + + assert_eq!(decoded, valid_context); } } diff --git a/common/nym-kkt/src/encryption.rs b/common/nym-kkt/src/encryption.rs index 65ac46f0ac..75257fc79c 100644 --- a/common/nym-kkt/src/encryption.rs +++ b/common/nym-kkt/src/encryption.rs @@ -1,95 +1,254 @@ -use core::hash; +// Copyright 2025-2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 -use blake3::{Hash, Hasher}; -use curve25519_dalek::digest::DynDigest; -use libcrux_psq::traits::Ciphertext; -use nym_crypto::symmetric::aead::{AeadKey, Nonce}; -use nym_crypto::{ - aes::Aes256, - asymmetric::x25519::{self, PrivateKey, PublicKey}, - generic_array::GenericArray, - Aes256GcmSiv, -}; -// use rand::{CryptoRng, RngCore}; +use crate::{KKT_INITIAL_FRAME_AAD, context::KKTContext, error::KKTError, frame::KKTFrame}; +use blake3::Hasher; +use libcrux_chacha20poly1305::{NONCE_LEN, TAG_LEN}; +use nym_crypto::asymmetric::x25519; +use rand::{CryptoRng, RngCore}; use zeroize::Zeroize; -use nym_crypto::aes::cipher::crypto_common::rand_core::{CryptoRng, RngCore}; +#[derive(Clone, Copy, Zeroize)] +pub struct KKTSessionSecret([u8; 32]); -use crate::error::KKTError; +impl KKTSessionSecret { + pub fn new(rng: &mut R, remote_public_key: &x25519::PublicKey) -> (Self, x25519::PublicKey) + where + R: RngCore + CryptoRng, + { + let mut private_key_bytes = [0u8; x25519::PRIVATE_KEY_SIZE]; + rng.fill_bytes(&mut private_key_bytes); -fn generate_round_trip_symmetric_key( - rng: &mut R, - remote_public_key: &PublicKey, -) -> ([u8; 64], [u8; 32]) -where - R: CryptoRng + RngCore, -{ - let mut s = x25519::PrivateKey::new(rng); - let gs = s.public_key(); + let ephemeral_private_key = x25519::PrivateKey::from_secret(private_key_bytes); + let ephemeral_public_key = x25519::PublicKey::from(&ephemeral_private_key); - let mut gbs = s.diffie_hellman(remote_public_key); - s.zeroize(); + ( + Self::derive(&ephemeral_private_key, remote_public_key), + ephemeral_public_key, + ) + } + pub fn from_bytes(secret: [u8; 32]) -> Self { + Self(secret) + } - let mut message: [u8; 64] = [0u8; 64]; - message[0..32].clone_from_slice(gs.as_bytes()); + fn try_derive(private_key: &x25519::PrivateKey, public_key: &[u8]) -> Result { + let mut pub_key: [u8; 32] = [0u8; 32]; + pub_key.copy_from_slice(&public_key[0..x25519::PUBLIC_KEY_SIZE]); - let mut hasher = Hasher::new(); + // Todo: check validity of pk... + let pk = x25519::PublicKey::from(pub_key); + Ok(Self::derive(private_key, &pk)) + } - hasher.update(&gbs); - gbs.zeroize(); - let key: [u8; 32] = hasher.finalize().as_bytes().to_owned(); + pub fn derive(private_key: &x25519::PrivateKey, public_key: &x25519::PublicKey) -> Self { + let mut shared_secret = private_key.diffie_hellman(public_key); - hasher.update(remote_public_key.as_bytes()); - hasher.update(gs.as_bytes()); + let mut hasher = Hasher::new(); - hasher.finalize_into_reset(&mut message[32..64]); + hasher.update(&shared_secret); + shared_secret.zeroize(); - (message, key) -} - -fn extract_shared_secret(b: &PrivateKey, message: &[u8; 64]) -> Result<[u8; 32], KKTError> { - let gs = PublicKey::from_bytes(&message[0..32])?; - - let mut gsb = b.diffie_hellman(&gs); - - let mut hasher = Hasher::new(); - hasher.update(&gsb); - gsb.zeroize(); - let key: [u8; 32] = hasher.finalize().as_bytes().to_owned(); - - hasher.update(b.public_key().as_bytes()); - hasher.update(gs.as_bytes()); - - // This runs in constant time - if hasher.finalize() == message[32..64] { - Ok(key) - } else { - Err(KKTError::X25519Error { - info: format!("Symmetric Key Hash Validation Error"), - }) + Self(hasher.finalize().as_bytes().to_owned()) + } + pub fn as_bytes(&self) -> &[u8; 32] { + &self.0 } } -fn encrypt(mut key: [u8; 32], message: &[u8]) -> Result, KKTError> { - // The empty nonce is fine since we use the key once. - let nonce = Nonce::::from_slice(&[]); +pub fn encrypt_initial_kkt_frame( + rng: &mut R, + remote_public_key: &x25519::PublicKey, + kkt_frame: &KKTFrame, +) -> Result<(KKTSessionSecret, Vec), KKTError> +where + R: CryptoRng + RngCore, +{ + let (session_secret_key, ephemeral_public_key) = KKTSessionSecret::new(rng, remote_public_key); - let ciphertext = - nym_crypto::symmetric::aead::encrypt::(&key.into(), nonce, message)?; + let mut encrypted_frame = + encrypt_kkt_frame(rng, &session_secret_key, kkt_frame, KKT_INITIAL_FRAME_AAD)?; - key.zeroize(); + let mut output_buffer = Vec::with_capacity(encrypted_frame.len() + x25519::PUBLIC_KEY_SIZE); + output_buffer.extend_from_slice(ephemeral_public_key.as_bytes()); + output_buffer.append(&mut encrypted_frame); - Ok(ciphertext) + // [ 32 | 12 | ciphertext | 16]; + // [eph_pub_key | nonce | ciphertext | tag]; + Ok((session_secret_key, output_buffer)) } -fn decrypt(key: [u8; 32], ciphertext: Vec) -> Vec { - // The empty nonce is fine since we use the key once. - let nonce = Nonce::::from_slice(&[]); +pub fn decrypt_initial_kkt_frame( + responder_private_key: &x25519::PrivateKey, + encrypted_frame_bytes: &[u8], +) -> Result<(KKTSessionSecret, KKTFrame, KKTContext), KKTError> { + if encrypted_frame_bytes.len() < x25519::PUBLIC_KEY_SIZE + TAG_LEN + NONCE_LEN { + Err(KKTError::AEADError { + info: "Encrypted KKT Frame is too short.", + }) + } else { + let shared_secret = KKTSessionSecret::try_derive( + responder_private_key, + &encrypted_frame_bytes[0..x25519::PUBLIC_KEY_SIZE], + )?; - let ciphertext = - nym_crypto::symmetric::aead::encrypt::(&key.into(), nonce, message)?; - - key.zeroize(); - - Ok(ciphertext) + let (kkt_frame, kkt_context) = decrypt_kkt_frame( + &shared_secret, + &encrypted_frame_bytes[x25519::PUBLIC_KEY_SIZE..], + KKT_INITIAL_FRAME_AAD, + )?; + Ok((shared_secret, kkt_frame, kkt_context)) + } +} + +pub fn encrypt_kkt_frame( + rng: &mut R, + secret_key: &KKTSessionSecret, + kkt_frame: &KKTFrame, + aad: &[u8], +) -> Result, KKTError> +where + R: CryptoRng + RngCore, +{ + let kkt_frame_bytes = kkt_frame.to_bytes(); + + // generate nonce + let mut nonce: [u8; NONCE_LEN] = [0u8; NONCE_LEN]; + rng.fill_bytes(&mut nonce); + + let mut ciphertext = encrypt(secret_key.as_bytes(), &kkt_frame_bytes, aad, &nonce)?; + + // [ 12 | ciphertext | 16]; + // [nonce | ciphertext | tag]; + let mut output_buffer: Vec = + Vec::with_capacity(NONCE_LEN + kkt_frame_bytes.len() + TAG_LEN); + + output_buffer.extend_from_slice(&nonce); + output_buffer.append(&mut ciphertext); + + Ok(output_buffer) +} + +// kkt_frame_bytes should look like this +// [ 12 | ciphertext | 16]; +// [nonce | ciphertext | tag]; +pub fn decrypt_kkt_frame( + secret_key: &KKTSessionSecret, + kkt_frame_bytes: &[u8], + aad: &[u8], +) -> Result<(KKTFrame, KKTContext), KKTError> { + let mut nonce: [u8; NONCE_LEN] = [0u8; NONCE_LEN]; + nonce.copy_from_slice(&kkt_frame_bytes[0..NONCE_LEN]); + + let plaintext = decrypt( + secret_key.as_bytes(), + &kkt_frame_bytes[NONCE_LEN..], + aad, + &nonce, + )?; + + KKTFrame::from_bytes(&plaintext) +} + +fn encrypt( + secret_key: &[u8; 32], + plaintext: &[u8], + aad: &[u8], + nonce: &[u8; NONCE_LEN], +) -> Result, KKTError> { + let mut output_buffer = vec![0; plaintext.len() + TAG_LEN]; + libcrux_chacha20poly1305::encrypt(secret_key, plaintext, &mut output_buffer, aad, nonce)?; + Ok(output_buffer) +} + +fn decrypt( + secret_key: &[u8; 32], + ciphertext: &[u8], + aad: &[u8], + nonce: &[u8; NONCE_LEN], +) -> Result, KKTError> { + let mut output_buffer = vec![0; ciphertext.len() - TAG_LEN]; + libcrux_chacha20poly1305::decrypt(secret_key, &mut output_buffer, ciphertext, aad, nonce)?; + Ok(output_buffer) +} + +#[cfg(test)] +mod test { + use crate::ciphersuite::Ciphersuite; + use crate::context::{KKTContext, KKTMode, KKTRole}; + use crate::encryption::{decrypt_kkt_frame, encrypt_kkt_frame}; + use crate::frame::{KKT_SESSION_ID_LEN, KKTFrame}; + use crate::{ + ciphersuite::DEFAULT_HASH_LEN, + encryption::{KKTSessionSecret, decrypt, encrypt}, + key_utils::generate_keypair_x25519, + }; + use rand::{RngCore, SeedableRng, rng}; + use rand_chacha::ChaCha20Rng; + + #[test] + fn test_keygen() { + let mut rng = rng(); + let responder_x25519_keypair = generate_keypair_x25519(&mut rng); + + let (session_secret_key, ephemeral_public_key) = + KKTSessionSecret::new(&mut rng, responder_x25519_keypair.public_key()); + + let shared_secret = KKTSessionSecret::try_derive( + responder_x25519_keypair.private_key(), + ephemeral_public_key.as_bytes().as_slice(), + ) + .unwrap(); + + assert_eq!(shared_secret.as_bytes(), session_secret_key.as_bytes()) + } + + #[test] + fn test_encryption() { + let mut rng = rng(); + + let mut secret_key = [0u8; DEFAULT_HASH_LEN]; + rng.fill_bytes(&mut secret_key); + + let mut plaintext = vec![0; 100]; + rng.fill_bytes(&mut plaintext); + + let mut nonce = [0; 12]; + rng.fill_bytes(&mut nonce); + + let mut aad = vec![0; 124]; + rng.fill_bytes(&mut aad); + + let ciphertext = encrypt(&secret_key, &plaintext, &aad, &nonce).unwrap(); + + let o_plaintext = decrypt(&secret_key, &ciphertext, &aad, &nonce).unwrap(); + + assert_eq!(o_plaintext, plaintext) + } + + #[test] + fn kkt_frame_encryption() -> anyhow::Result<()> { + let mut rng = ChaCha20Rng::seed_from_u64(42); + let session_key = KKTSessionSecret::from_bytes([42u8; 32]); + let aad = b"my-amazing-aad"; + + let valid_context = KKTContext::new( + KKTRole::Initiator, + KKTMode::Mutual, + Ciphersuite::decode([255, 1, 0, 0])?, + )?; + let dummy_frame = KKTFrame::new( + valid_context.encode()?, + &[2u8; 32], + [3u8; KKT_SESSION_ID_LEN], + &[4u8; 64], + ); + + let ciphertext = encrypt_kkt_frame(&mut rng, &session_key, &dummy_frame, aad.as_slice())?; + + let (frame, context) = decrypt_kkt_frame(&session_key, &ciphertext, aad.as_slice())?; + + assert_eq!(dummy_frame, frame); + assert_eq!(context, valid_context); + Ok(()) + } } diff --git a/common/nym-kkt/src/error.rs b/common/nym-kkt/src/error.rs index 0144fef6ac..1defc1b1e0 100644 --- a/common/nym-kkt/src/error.rs +++ b/common/nym-kkt/src/error.rs @@ -1,9 +1,10 @@ // Copyright 2025 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use thiserror::Error; - use crate::context::KKTStatus; +use nym_kkt_ciphersuite::error::KKTCiphersuiteError; +use std::fmt::Debug; +use thiserror::Error; #[derive(Error, Debug)] pub enum KKTError { @@ -11,8 +12,8 @@ pub enum KKTError { SigConstructorError, #[error("Signature verification error")] SigVerifError, - #[error("Ciphersuite Decoding Error: {}", info)] - CiphersuiteDecodingError { info: String }, + #[error(transparent)] + CiphersuiteDecodingError(#[from] KKTCiphersuiteError), #[error("KEM mapping failure: {}", info)] KEMMapping { info: &'static str }, @@ -44,6 +45,9 @@ pub enum KKTError { #[error("{}", info)] X25519Error { info: &'static str }, + #[error("{}", info)] + AEADError { info: &'static str }, + #[error("Generic libcrux error")] LibcruxError, } @@ -87,3 +91,27 @@ impl From for KKTError { } } } +impl From for KKTError { + fn from(err: libcrux_chacha20poly1305::AeadError) -> Self { + KKTError::KEMError { + info: match err { + libcrux_chacha20poly1305::AeadError::PlaintextTooLarge => { + "Plaintext is longer than u32::MAX" + } + libcrux_chacha20poly1305::AeadError::CiphertextTooLarge => { + "Ciphertext is longer than u32::MAX" + } + libcrux_chacha20poly1305::AeadError::AadTooLarge => "Aad is longer than u32::MAX", + libcrux_chacha20poly1305::AeadError::CiphertextTooShort => { + "The provided destination ciphertext does not fit the ciphertext and tag" + } + libcrux_chacha20poly1305::AeadError::PlaintextTooShort => { + "The provided destination plaintext is too short to fit the decrypted plaintext" + } + libcrux_chacha20poly1305::AeadError::InvalidCiphertext => { + "The ciphertext is not a valid encryption under the given key and nonce." + } + }, + } + } +} diff --git a/common/nym-kkt/src/frame.rs b/common/nym-kkt/src/frame.rs index 1745b997f1..6502af882c 100644 --- a/common/nym-kkt/src/frame.rs +++ b/common/nym-kkt/src/frame.rs @@ -14,9 +14,12 @@ use crate::{ pub const KKT_SESSION_ID_LEN: usize = 16; +pub type KKTSessionId = [u8; KKT_SESSION_ID_LEN]; + +#[derive(Debug, PartialEq, Clone)] pub struct KKTFrame { - context: Vec, - session_id: Vec, + context: [u8; KKT_CONTEXT_LEN], + session_id: KKTSessionId, body: Vec, signature: Vec, } @@ -27,20 +30,31 @@ pub struct KKTFrame { // if coming from responder => body has the responder's kem public key and the signature is over the context + body + session_id. impl KKTFrame { - pub fn new(context: &[u8], body: &[u8], session_id: &[u8], signature: &[u8]) -> Self { + pub fn new( + context: [u8; KKT_CONTEXT_LEN], + body: &[u8], + session_id: [u8; KKT_SESSION_ID_LEN], + signature: &[u8], + ) -> Self { Self { - context: Vec::from(context), + context, body: Vec::from(body), - session_id: Vec::from(session_id), + session_id, signature: Vec::from(signature), } } pub fn context_ref(&self) -> &[u8] { &self.context } + + pub fn context(&self) -> Result { + KKTContext::try_decode(self.context) + } + pub fn signature_ref(&self) -> &[u8] { &self.signature } + pub fn body_ref(&self) -> &[u8] { &self.body } @@ -48,6 +62,10 @@ impl KKTFrame { pub fn session_id_ref(&self) -> &[u8] { &self.session_id } + pub fn session_id(&self) -> [u8; KKT_SESSION_ID_LEN] { + self.session_id + } + pub fn signature_mut(&mut self) -> &mut [u8] { &mut self.signature } @@ -73,57 +91,65 @@ impl KKTFrame { } pub fn from_bytes(bytes: &[u8]) -> Result<(Self, KKTContext), KKTError> { + let len = bytes.len(); if bytes.len() < KKT_CONTEXT_LEN { - Err(KKTError::FrameDecodingError { + return Err(KKTError::FrameDecodingError { info: format!( - "Frame is shorter than expected context length: actual {} != expected {}", - bytes.len(), - KKT_CONTEXT_LEN + "Frame is shorter than expected context length: actual {len} != expected {KKT_CONTEXT_LEN}", ), - }) - } else { - let context_bytes = Vec::from(&bytes[0..KKT_CONTEXT_LEN]); - - let context = KKTContext::try_decode(&context_bytes)?; - - let (mut session_id, mut body, mut signature): (Vec, Vec, Vec) = - (vec![], vec![], vec![]); - - if bytes.len() == context.full_message_len() { - if context.body_len() > 0 { - body.extend_from_slice( - &bytes[KKT_CONTEXT_LEN..KKT_CONTEXT_LEN + context.body_len()], - ); - } - if context.session_id_len() > 0 { - session_id.extend_from_slice( - &bytes[KKT_CONTEXT_LEN + context.body_len() - ..KKT_CONTEXT_LEN + context.body_len() + context.session_id_len()], - ); - } - if context.signature_len() > 0 { - signature.extend_from_slice( - &bytes[KKT_CONTEXT_LEN + context.body_len() + context.session_id_len() - ..KKT_CONTEXT_LEN - + context.body_len() - + context.session_id_len() - + context.signature_len()], - ); - } - - Ok(( - KKTFrame::new(&context_bytes, &body, &session_id, &signature), - context, - )) - } else { - Err(KKTError::FrameDecodingError { - info: format!( - "Frame is shorter than expected: actual {} != expected {}", - bytes.len(), - context.full_message_len() - ), - }) - } + }); } + + // SAFETY: we're using exactly KKT_CONTEXT_LEN bytes + #[allow(clippy::unwrap_used)] + let context_bytes = bytes[0..KKT_CONTEXT_LEN].try_into().unwrap(); + let context = KKTContext::try_decode(context_bytes)?; + + if bytes.len() != context.full_message_len() { + return Err(KKTError::FrameDecodingError { + info: format!( + "Frame is shorter than expected: actual {len} != expected {}", + context.full_message_len() + ), + }); + } + + let mut body = Vec::new(); + let mut signature = Vec::new(); + + // decode body + if context.body_len() > 0 { + let body_bytes = &bytes[KKT_CONTEXT_LEN..KKT_CONTEXT_LEN + context.body_len()]; + body.extend_from_slice(body_bytes); + } + + let session_bytes = &bytes[KKT_CONTEXT_LEN + context.body_len() + ..KKT_CONTEXT_LEN + context.body_len() + KKT_SESSION_ID_LEN]; + // SAFETY: we're using exactly KKT_SESSION_ID_LEN bytes and we checked for sufficient bytes + #[allow(clippy::unwrap_used)] + let session_id = session_bytes.try_into().unwrap(); + + // // old code left for reference if session id becomes variable in length: + // if context.session_id_len() > 0 { + // session_id.extend_from_slice( + // &bytes[KKT_CONTEXT_LEN + context.body_len() + // ..KKT_CONTEXT_LEN + context.body_len() + context.session_id_len()], + // ); + // } + + // decode signature + if context.signature_len() > 0 { + let signature_bytes = &bytes[KKT_CONTEXT_LEN + context.body_len() + KKT_SESSION_ID_LEN + ..KKT_CONTEXT_LEN + + context.body_len() + + KKT_SESSION_ID_LEN + + context.signature_len()]; + signature.extend_from_slice(signature_bytes); + } + + Ok(( + KKTFrame::new(context_bytes, &body, session_id, &signature), + context, + )) } } diff --git a/common/nym-kkt/src/key_utils.rs b/common/nym-kkt/src/key_utils.rs index 1ab18934e0..535ba8b000 100644 --- a/common/nym-kkt/src/key_utils.rs +++ b/common/nym-kkt/src/key_utils.rs @@ -1,27 +1,54 @@ -use crate::{ - ciphersuite::{HashFunction, KEM}, - error::KKTError, -}; +use crate::ciphersuite::HashFunction; +use std::collections::HashMap; use classic_mceliece_rust::keypair_boxed; -use libcrux_kem::{Algorithm, key_gen}; -use libcrux_sha3; +use nym_kkt_ciphersuite::{DEFAULT_HASH_LEN, KeyDigests}; use rand::{CryptoRng, RngCore}; +pub fn generate_keypair_ed25519( + rng: &mut R, + index: Option, +) -> nym_crypto::asymmetric::ed25519::KeyPair +where + R: RngCore + CryptoRng, +{ + let mut secret_initiator: [u8; 32] = [0u8; 32]; + rng.fill_bytes(&mut secret_initiator); + nym_crypto::asymmetric::ed25519::KeyPair::from_secret(secret_initiator, index.unwrap_or(0)) +} + +pub fn generate_keypair_x25519(rng: &mut R) -> nym_crypto::asymmetric::x25519::KeyPair +where + R: RngCore + CryptoRng, +{ + let mut secret_initiator: [u8; 32] = [0u8; 32]; + rng.fill_bytes(&mut secret_initiator); + + let private_key = nym_crypto::asymmetric::x25519::PrivateKey::from_secret(secret_initiator); + private_key.into() +} + // (decapsulation_key, encapsulation_key) pub fn generate_keypair_libcrux( rng: &mut R, - kem: KEM, -) -> Result<(libcrux_kem::PrivateKey, libcrux_kem::PublicKey), KKTError> + kem: crate::ciphersuite::KEM, +) -> Result<(libcrux_kem::PrivateKey, libcrux_kem::PublicKey), crate::error::KKTError> where R: RngCore + CryptoRng, { match kem { - KEM::MlKem768 => Ok(key_gen(Algorithm::MlKem768, rng)?), - KEM::XWing => Ok(key_gen(Algorithm::XWingKemDraft06, rng)?), - KEM::X25519 => Ok(key_gen(Algorithm::X25519, rng)?), - _ => Err(KKTError::KEMError { + crate::ciphersuite::KEM::MlKem768 => { + Ok(libcrux_kem::key_gen(libcrux_kem::Algorithm::MlKem768, rng)?) + } + crate::ciphersuite::KEM::XWing => Ok(libcrux_kem::key_gen( + libcrux_kem::Algorithm::XWingKemDraft06, + rng, + )?), + crate::ciphersuite::KEM::X25519 => { + Ok(libcrux_kem::key_gen(libcrux_kem::Algorithm::X25519, rng)?) + } + _ => Err(crate::error::KKTError::KEMError { info: "Key Generation Error: Unsupported Libcrux Algorithm", }), } @@ -46,26 +73,18 @@ pub fn hash_key_bytes( hash_length: usize, key_bytes: &[u8], ) -> Vec { - let mut hashed_key: Vec = vec![0u8; hash_length]; - match hash_function { - HashFunction::Blake3 => { - let mut hasher = blake3::Hasher::new(); - hasher.update(key_bytes); - hasher.finalize_xof().fill(&mut hashed_key); - hasher.reset(); - } - HashFunction::SHAKE256 => { - libcrux_sha3::shake256_ema(&mut hashed_key, key_bytes); - } - HashFunction::SHAKE128 => { - libcrux_sha3::shake128_ema(&mut hashed_key, key_bytes); - } - HashFunction::SHA256 => { - libcrux_sha3::sha256_ema(&mut hashed_key, key_bytes); - } - } + hash_function.digest(key_bytes, hash_length) +} - hashed_key +/// attempt to produce digests of the provided key using all known [HashFunction] with a default +/// hash length where variable output is available +pub fn produce_key_digests(key_bytes: &[u8]) -> KeyDigests { + use strum::IntoEnumIterator; + let mut digests = HashMap::new(); + for hash in HashFunction::iter() { + digests.insert(hash, hash.digest(key_bytes, DEFAULT_HASH_LEN)); + } + digests } /// This does NOT run in constant time. diff --git a/common/nym-kkt/src/kkt.rs b/common/nym-kkt/src/kkt.rs index 7fcef8d3e3..05b55d575d 100644 --- a/common/nym-kkt/src/kkt.rs +++ b/common/nym-kkt/src/kkt.rs @@ -8,14 +8,14 @@ //! //! The underlying KKT protocol is implemented in the `session` module. -use nym_crypto::asymmetric::ed25519; +use nym_crypto::asymmetric::{ed25519, x25519}; use rand::{CryptoRng, RngCore}; use crate::{ ciphersuite::{Ciphersuite, EncapsulationKey}, context::{KKTContext, KKTMode}, + encryption::{decrypt_initial_kkt_frame, decrypt_kkt_frame, encrypt_kkt_frame}, error::KKTError, - frame::KKTFrame, }; // Re-export core session functions for advanced use cases @@ -24,7 +24,10 @@ pub use crate::session::{ responder_ingest_message, responder_process, }; -/// Request a KEM public key from a responder (OneWay mode). +use crate::encryption::{KKTSessionSecret, encrypt_initial_kkt_frame}; +use crate::frame::KKTFrame; + +/// Perform an *Encrypted* request for a KEM public key from a responder (OneWay mode). /// /// This is the client-side operation that initiates a KKT exchange. /// The request will be signed with the provided signing key. @@ -33,17 +36,20 @@ pub use crate::session::{ /// * `rng` - Random number generator /// * `ciphersuite` - Negotiated ciphersuite (KEM, hash, signature algorithms) /// * `signing_key` - Client's Ed25519 signing key for authentication +/// * `responder_dh_public_key` - Responder's long-term x25519 Diffie-Hellman public key /// /// # Returns +/// * `KKTSessionSecret` - Session Secret Key to use when decrypting responses /// * `KKTContext` - Context to use when validating the response -/// * `KKTFrame` - Signed request frame to send to responder +/// * `Vec` - Contains the client's ephemeral public key and encrypted and signed bytes to send to responder /// /// # Example /// ```ignore -/// let (context, request_frame) = request_kem_key( +/// let (session_secret, context, request_frame) = request_kem_key( /// &mut rng, /// ciphersuite, /// client_signing_key, +/// responder_dh_public_key, /// )?; /// // Send request_frame to gateway /// ``` @@ -51,13 +57,21 @@ pub fn request_kem_key( rng: &mut R, ciphersuite: Ciphersuite, signing_key: &ed25519::PrivateKey, -) -> Result<(KKTContext, KKTFrame), KKTError> { + responder_dh_public_key: &x25519::PublicKey, +) -> Result<(KKTSessionSecret, KKTContext, Vec), KKTError> { // OneWay mode: client only wants responder's KEM key // None: client doesn't send their own KEM key - initiator_process(rng, KKTMode::OneWay, ciphersuite, signing_key, None) + let (initiator_context, initiator_frame) = + initiator_process(rng, KKTMode::OneWay, ciphersuite, signing_key, None)?; + + // Generate the session's shared secret and encrypt the Initiator's request + let (session_secret, encrypted_request_bytes) = + encrypt_initial_kkt_frame(rng, responder_dh_public_key, &initiator_frame)?; + + Ok((session_secret, initiator_context, encrypted_request_bytes)) } -/// Validate a KKT response and extract the responder's KEM public key. +/// Decrypt, validate an *Encrypted* KKT response and extract the responder's KEM public key. /// /// This is the client-side operation that processes the gateway's response. /// It verifies the signature and validates the key hash against the expected value @@ -65,6 +79,7 @@ pub fn request_kem_key( /// /// # Arguments /// * `context` - Context from the initial request +/// * `session_secret` - Session Secret Key (generated with request) /// * `responder_vk` - Responder's Ed25519 verification key (from directory) /// * `expected_key_hash` - Expected hash of responder's KEM key (from directory) /// * `response_bytes` - Serialized response frame from responder @@ -76,7 +91,8 @@ pub fn request_kem_key( /// ```ignore /// let gateway_kem_key = validate_kem_response( /// &mut context, -/// gateway_verification_key, +/// &session_secret, +/// &gateway_verification_key, /// &expected_hash_from_directory, /// &response_bytes, /// )?; @@ -84,23 +100,44 @@ pub fn request_kem_key( /// ``` pub fn validate_kem_response<'a>( context: &mut KKTContext, + session_secret: &KKTSessionSecret, responder_vk: &ed25519::PublicKey, expected_key_hash: &[u8], - response_bytes: &[u8], + encrypted_response_bytes: &[u8], ) -> Result, KKTError> { - initiator_ingest_response(context, responder_vk, expected_key_hash, response_bytes) + let (responder_frame, responder_context) = + decrypt_kkt_response_frame(session_secret, encrypted_response_bytes)?; + + initiator_ingest_response( + context, + &responder_frame, + &responder_context, + responder_vk, + expected_key_hash, + ) } -/// Handle a KKT request and generate a signed response with the responder's KEM key. +/// Decrypts and validates an *Encrypted* KKT response +/// +/// This is the client-side operation that processes the gateway's response. +pub fn decrypt_kkt_response_frame( + session_secret: &KKTSessionSecret, + frame_ciphertext: &[u8], +) -> Result<(KKTFrame, KKTContext), KKTError> { + decrypt_kkt_frame(session_secret, frame_ciphertext, KKT_RESPONSE_AAD) +} + +/// Handle an *Encrypted* KKT request and generate a signed response with the responder's KEM key. /// /// This is the gateway-side operation that processes a client's KKT request. /// It validates the request signature (if authenticated) and responds with /// the gateway's KEM public key, signed for authenticity. /// /// # Arguments -/// * `request_frame` - Request frame received from initiator +/// * `encrypted_request_bytes` - encrypted KEM request /// * `initiator_vk` - Initiator's Ed25519 verification key (None for anonymous) /// * `responder_signing_key` - Gateway's Ed25519 signing key +/// * `responder_dh_public_key` - Gateway's long-term x25519 Diffie-Hellman private key /// * `responder_kem_key` - Gateway's KEM public key to send /// /// # Returns @@ -116,31 +153,40 @@ pub fn validate_kem_response<'a>( /// )?; /// // Send response_frame back to client /// ``` -pub fn handle_kem_request<'a>( - request_frame: &KKTFrame, +pub fn handle_kem_request<'a, R>( + rng: &mut R, + encrypted_request_bytes: &[u8], initiator_vk: Option<&ed25519::PublicKey>, responder_signing_key: &ed25519::PrivateKey, + responder_dh_private_key: &x25519::PrivateKey, responder_kem_key: &EncapsulationKey<'a>, -) -> Result { - // Parse context from the request frame - let request_bytes = request_frame.to_bytes(); - let (_, request_context) = KKTFrame::from_bytes(&request_bytes)?; +) -> Result, KKTError> +where + R: RngCore + CryptoRng, +{ + // Compute the session's shared secret, decrypt and parse context from the request frame + + let (session_secret, request_frame, initiator_context) = + decrypt_initial_kkt_frame(responder_dh_private_key, encrypted_request_bytes)?; // Validate the request (verifies signature if initiator_vk provided) let (mut response_context, _) = responder_ingest_message( - &request_context, + &initiator_context, initiator_vk, None, // Not checking initiator's KEM key in OneWay mode - request_frame, + &request_frame, )?; // Generate signed response with our KEM public key - responder_process( + let responder_frame = responder_process( &mut response_context, - request_frame.session_id_ref(), + request_frame.session_id(), responder_signing_key, responder_kem_key, - ) + )?; + + // Encrypt the responder's response with the session's shared secret + encrypt_kkt_frame(rng, &session_secret, &responder_frame, KKT_RESPONSE_AAD) } #[cfg(test)] @@ -151,6 +197,13 @@ mod tests { key_utils::{generate_keypair_libcrux, hash_encapsulation_key}, }; + fn random_x25519_key() -> x25519::PrivateKey { + let mut bytes = [0u8; 32]; + let mut rng = rand::rng(); + rng.fill_bytes(&mut bytes); + x25519::PrivateKey::from_secret(bytes) + } + #[test] fn test_kkt_wrappers_oneway_authenticated() { let mut rng = rand::rng(); @@ -158,11 +211,14 @@ mod tests { // Generate Ed25519 keypairs for both parties let mut initiator_secret = [0u8; 32]; rng.fill_bytes(&mut initiator_secret); - let initiator_keypair = ed25519::KeyPair::from_secret(initiator_secret, 0); + let ed25519_init = ed25519::KeyPair::from_secret(initiator_secret, 0); let mut responder_secret = [0u8; 32]; rng.fill_bytes(&mut responder_secret); - let responder_keypair = ed25519::KeyPair::from_secret(responder_secret, 1); + let ed25519_resp = ed25519::KeyPair::from_secret(responder_secret, 1); + + let x25519_resp_priv = random_x25519_key(); + let x25519_resp_pub = x25519::PublicKey::from(&x25519_resp_priv); // Generate responder's KEM keypair (X25519 for testing) let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); @@ -185,14 +241,21 @@ mod tests { ); // Client: Request KEM key - let (mut context, request_frame) = - request_kem_key(&mut rng, ciphersuite, initiator_keypair.private_key()).unwrap(); + let (session_key, mut context, request_frame_ciphertext) = request_kem_key( + &mut rng, + ciphersuite, + ed25519_init.private_key(), + &x25519_resp_pub, + ) + .unwrap(); // Gateway: Handle request - let response_frame = handle_kem_request( - &request_frame, - Some(initiator_keypair.public_key()), // Authenticated - responder_keypair.private_key(), + let response_frame_ciphertext = handle_kem_request( + &mut rng, + &request_frame_ciphertext, + Some(ed25519_init.public_key()), // Authenticated + ed25519_resp.private_key(), + &x25519_resp_priv, &responder_kem_key, ) .unwrap(); @@ -200,9 +263,10 @@ mod tests { // Client: Validate response let obtained_key = validate_kem_response( &mut context, - responder_keypair.public_key(), + &session_key, + ed25519_resp.public_key(), &key_hash, - &response_frame.to_bytes(), + &response_frame_ciphertext, ) .unwrap(); @@ -222,6 +286,9 @@ mod tests { let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); let responder_kem_key = EncapsulationKey::X25519(responder_kem_pk); + let x25519_resp_priv = random_x25519_key(); + let x25519_resp_pub = x25519::PublicKey::from(&x25519_resp_priv); + let ciphersuite = Ciphersuite::resolve_ciphersuite( KEM::X25519, HashFunction::Blake3, @@ -240,11 +307,17 @@ mod tests { let (mut context, request_frame) = anonymous_initiator_process(&mut rng, ciphersuite).unwrap(); + // Generate the session's shared secret and encrypt the Initiator's request + let (session_secret, encrypted_request_bytes) = + encrypt_initial_kkt_frame(&mut rng, &x25519_resp_pub, &request_frame).unwrap(); + // Gateway: Handle anonymous request let response_frame = handle_kem_request( - &request_frame, + &mut rng, + &encrypted_request_bytes, None, // Anonymous - no verification key responder_keypair.private_key(), + &x25519_resp_priv, &responder_kem_key, ) .unwrap(); @@ -252,9 +325,10 @@ mod tests { // Initiator: Validate response let obtained_key = validate_kem_response( &mut context, + &session_secret, responder_keypair.public_key(), &key_hash, - &response_frame.to_bytes(), + &response_frame, ) .unwrap(); @@ -273,6 +347,9 @@ mod tests { rng.fill_bytes(&mut responder_secret); let responder_keypair = ed25519::KeyPair::from_secret(responder_secret, 1); + let x25519_resp_priv = random_x25519_key(); + let x25519_resp_pub = x25519::PublicKey::from(&x25519_resp_priv); + // Different keypair for wrong signature let mut wrong_secret = [0u8; 32]; rng.fill_bytes(&mut wrong_secret); @@ -289,14 +366,21 @@ mod tests { ) .unwrap(); - let (_context, request_frame) = - request_kem_key(&mut rng, ciphersuite, initiator_keypair.private_key()).unwrap(); + let (_session_key, _context, request_frame_ciphertext) = request_kem_key( + &mut rng, + ciphersuite, + initiator_keypair.private_key(), + &x25519_resp_pub, + ) + .unwrap(); // Gateway handles request but we provide WRONG verification key let result = handle_kem_request( - &request_frame, + &mut rng, + &request_frame_ciphertext, Some(wrong_keypair.public_key()), // Wrong key! responder_keypair.private_key(), + &x25519_resp_priv, &responder_kem_key, ); @@ -316,6 +400,9 @@ mod tests { rng.fill_bytes(&mut responder_secret); let responder_keypair = ed25519::KeyPair::from_secret(responder_secret, 1); + let x25519_resp_priv = random_x25519_key(); + let x25519_resp_pub = x25519::PublicKey::from(&x25519_resp_priv); + let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); let responder_kem_key = EncapsulationKey::X25519(responder_kem_pk); @@ -330,13 +417,20 @@ mod tests { // Use WRONG hash let wrong_hash = [0u8; 32]; - let (mut context, request_frame) = - request_kem_key(&mut rng, ciphersuite, initiator_keypair.private_key()).unwrap(); + let (session_key, mut context, request_frame) = request_kem_key( + &mut rng, + ciphersuite, + initiator_keypair.private_key(), + &x25519_resp_pub, + ) + .unwrap(); let response_frame = handle_kem_request( + &mut rng, &request_frame, Some(initiator_keypair.public_key()), responder_keypair.private_key(), + &x25519_resp_priv, &responder_kem_key, ) .unwrap(); @@ -344,9 +438,10 @@ mod tests { // Client validates with WRONG hash let result = validate_kem_response( &mut context, + &session_key, responder_keypair.public_key(), &wrong_hash, // Wrong! - &response_frame.to_bytes(), + &response_frame, ); // Should fail hash validation diff --git a/common/nym-kkt/src/lib.rs b/common/nym-kkt/src/lib.rs index 348e8fb01c..e6a91a83ec 100644 --- a/common/nym-kkt/src/lib.rs +++ b/common/nym-kkt/src/lib.rs @@ -3,28 +3,33 @@ pub mod ciphersuite; pub mod context; -// pub mod encryption; +pub mod encryption; pub mod error; pub mod frame; pub mod key_utils; -pub mod kkt; +// pub mod kkt; pub mod session; -// pub mod psq; - // This must be less than 4 bits pub const KKT_VERSION: u8 = 1; const _: () = assert!(KKT_VERSION < 1 << 4); +pub const KKT_RESPONSE_AAD: &[u8] = b"KKT_Response"; +pub(crate) const KKT_INITIAL_FRAME_AAD: &[u8] = b"KKT_INITIAL_FRAME"; #[cfg(test)] mod test { - use nym_crypto::asymmetric::ed25519; - use rand::prelude::*; - use crate::{ + KKT_RESPONSE_AAD, ciphersuite::{Ciphersuite, EncapsulationKey, HashFunction, KEM}, + encryption::{ + decrypt_initial_kkt_frame, decrypt_kkt_frame, encrypt_initial_kkt_frame, + encrypt_kkt_frame, + }, frame::KKTFrame, - key_utils::{generate_keypair_libcrux, generate_keypair_mceliece, hash_encapsulation_key}, + key_utils::{ + generate_keypair_ed25519, generate_keypair_libcrux, generate_keypair_mceliece, + generate_keypair_x25519, hash_encapsulation_key, + }, session::{ anonymous_initiator_process, initiator_ingest_response, initiator_process, responder_ingest_message, responder_process, @@ -36,19 +41,15 @@ mod test { let mut rng = rand::rng(); // generate ed25519 keys - let mut secret_initiator: [u8; 32] = [0u8; 32]; - rng.fill_bytes(&mut secret_initiator); - let initiator_ed25519_keypair = ed25519::KeyPair::from_secret(secret_initiator, 0); + let initiator_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(0)); + let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(1)); - let mut secret_responder: [u8; 32] = [0u8; 32]; - rng.fill_bytes(&mut secret_responder); - let responder_ed25519_keypair = ed25519::KeyPair::from_secret(secret_responder, 1); for kem in [KEM::MlKem768, KEM::XWing, KEM::X25519, KEM::McEliece] { for hash_function in [ HashFunction::Blake3, HashFunction::SHA256, - HashFunction::SHAKE128, - HashFunction::SHAKE256, + HashFunction::Shake128, + HashFunction::Shake256, ] { let ciphersuite = Ciphersuite::resolve_ciphersuite( kem, @@ -117,7 +118,7 @@ mod test { let r_frame = responder_process( &mut r_context, - i_frame_r.session_id_ref(), + i_frame_r.session_id(), responder_ed25519_keypair.private_key(), &responder_kem_public_key, ) @@ -125,15 +126,18 @@ mod test { let r_bytes = r_frame.to_bytes(); - let obtained_key = initiator_ingest_response( + let (i_frame_r, i_context_r) = KKTFrame::from_bytes(&r_bytes).unwrap(); + + let i_obtained_key = initiator_ingest_response( &mut i_context, + &i_frame_r, + &i_context_r, responder_ed25519_keypair.public_key(), &r_dir_hash, - &r_bytes, ) .unwrap(); - assert_eq!(obtained_key.encode(), r_kem_key_bytes) + assert_eq!(i_obtained_key.encode(), r_kem_key_bytes) } // Initiator, OneWay { @@ -162,7 +166,7 @@ mod test { let r_frame = responder_process( &mut r_context, - i_frame_r.session_id_ref(), + i_frame_r.session_id(), responder_ed25519_keypair.private_key(), &responder_kem_public_key, ) @@ -170,11 +174,14 @@ mod test { let r_bytes = r_frame.to_bytes(); + let (i_frame_r, i_context_r) = KKTFrame::from_bytes(&r_bytes).unwrap(); + let i_obtained_key = initiator_ingest_response( &mut i_context, + &i_frame_r, + &i_context_r, responder_ed25519_keypair.public_key(), &r_dir_hash, - &r_bytes, ) .unwrap(); @@ -208,7 +215,7 @@ mod test { let r_frame = responder_process( &mut r_context, - i_frame_r.session_id_ref(), + i_frame_r.session_id(), responder_ed25519_keypair.private_key(), &responder_kem_public_key, ) @@ -216,15 +223,275 @@ mod test { let r_bytes = r_frame.to_bytes(); - let obtained_key = initiator_ingest_response( + let (i_frame_r, i_context_r) = KKTFrame::from_bytes(&r_bytes).unwrap(); + + let i_obtained_key = initiator_ingest_response( &mut i_context, + &i_frame_r, + &i_context_r, responder_ed25519_keypair.public_key(), &r_dir_hash, - &r_bytes, ) .unwrap(); - assert_eq!(obtained_key.encode(), r_kem_key_bytes) + assert_eq!(i_obtained_key.encode(), r_kem_key_bytes) + } + } + } + } + #[test] + fn test_kkt_psq_e2e_encrypted() { + let mut rng = rand::rng(); + + // generate ed25519 keys + let initiator_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(0)); + let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(1)); + + // generate responder x25519 keys + let responder_x25519_keypair = generate_keypair_x25519(&mut rng); + + for kem in [KEM::MlKem768, KEM::XWing, KEM::X25519, KEM::McEliece] { + for hash_function in [ + HashFunction::Blake3, + HashFunction::SHA256, + HashFunction::Shake128, + HashFunction::Shake256, + ] { + let ciphersuite = Ciphersuite::resolve_ciphersuite( + kem, + hash_function, + crate::ciphersuite::SignatureScheme::Ed25519, + None, + ) + .unwrap(); + + // generate kem public keys + + let (responder_kem_public_key, initiator_kem_public_key) = match kem { + KEM::MlKem768 => ( + EncapsulationKey::MlKem768( + generate_keypair_libcrux(&mut rng, kem).unwrap().1, + ), + EncapsulationKey::MlKem768( + generate_keypair_libcrux(&mut rng, kem).unwrap().1, + ), + ), + KEM::XWing => ( + EncapsulationKey::XWing(generate_keypair_libcrux(&mut rng, kem).unwrap().1), + EncapsulationKey::XWing(generate_keypair_libcrux(&mut rng, kem).unwrap().1), + ), + KEM::X25519 => ( + EncapsulationKey::X25519( + generate_keypair_libcrux(&mut rng, kem).unwrap().1, + ), + EncapsulationKey::X25519( + generate_keypair_libcrux(&mut rng, kem).unwrap().1, + ), + ), + KEM::McEliece => ( + EncapsulationKey::McEliece(generate_keypair_mceliece(&mut rng).1), + EncapsulationKey::McEliece(generate_keypair_mceliece(&mut rng).1), + ), + }; + + let i_kem_key_bytes = initiator_kem_public_key.encode(); + + let r_kem_key_bytes = responder_kem_public_key.encode(); + + let i_dir_hash = hash_encapsulation_key( + &ciphersuite.hash_function(), + ciphersuite.hash_len(), + &i_kem_key_bytes, + ); + + let r_dir_hash = hash_encapsulation_key( + &ciphersuite.hash_function(), + ciphersuite.hash_len(), + &r_kem_key_bytes, + ); + + // Anonymous Initiator, OneWay + { + let (mut i_context, i_frame) = + anonymous_initiator_process(&mut rng, ciphersuite).unwrap(); + + // encryption - initiator frame + + let (i_session_secret, i_bytes) = encrypt_initial_kkt_frame( + &mut rng, + responder_x25519_keypair.public_key(), + &i_frame, + ) + .unwrap(); + + // decryption - initiator frame + + let (r_session_secret, i_frame_r, i_context_r) = + decrypt_initial_kkt_frame(responder_x25519_keypair.private_key(), &i_bytes) + .unwrap(); + + let (mut r_context, _) = + responder_ingest_message(&i_context_r, None, None, &i_frame_r).unwrap(); + + let r_frame = responder_process( + &mut r_context, + i_frame_r.session_id(), + responder_ed25519_keypair.private_key(), + &responder_kem_public_key, + ) + .unwrap(); + + // encryption - responder frame + let r_bytes = + encrypt_kkt_frame(&mut rng, &r_session_secret, &r_frame, KKT_RESPONSE_AAD) + .unwrap(); + + // decryption - responder frame + + let (i_frame_r, i_context_r) = + decrypt_kkt_frame(&i_session_secret, &r_bytes, KKT_RESPONSE_AAD).unwrap(); + + let i_obtained_key = initiator_ingest_response( + &mut i_context, + &i_frame_r, + &i_context_r, + responder_ed25519_keypair.public_key(), + &r_dir_hash, + ) + .unwrap(); + + assert_eq!(i_obtained_key.encode(), r_kem_key_bytes) + } + // Initiator, OneWay + { + let (mut i_context, i_frame) = initiator_process( + &mut rng, + crate::context::KKTMode::OneWay, + ciphersuite, + initiator_ed25519_keypair.private_key(), + None, + ) + .unwrap(); + + // encryption - initiator frame + + let (i_session_secret, i_bytes) = encrypt_initial_kkt_frame( + &mut rng, + responder_x25519_keypair.public_key(), + &i_frame, + ) + .unwrap(); + + // decryption - initiator frame + + let (r_session_secret, i_frame_r, r_context) = + decrypt_initial_kkt_frame(responder_x25519_keypair.private_key(), &i_bytes) + .unwrap(); + + let (mut r_context, r_obtained_key) = responder_ingest_message( + &r_context, + Some(initiator_ed25519_keypair.public_key()), + None, + &i_frame_r, + ) + .unwrap(); + + assert!(r_obtained_key.is_none()); + + let r_frame = responder_process( + &mut r_context, + i_frame_r.session_id(), + responder_ed25519_keypair.private_key(), + &responder_kem_public_key, + ) + .unwrap(); + + // encryption - responder frame + let r_bytes = + encrypt_kkt_frame(&mut rng, &r_session_secret, &r_frame, KKT_RESPONSE_AAD) + .unwrap(); + + // decryption - responder frame + + let (i_frame_r, i_context_r) = + decrypt_kkt_frame(&i_session_secret, &r_bytes, KKT_RESPONSE_AAD).unwrap(); + + let i_obtained_key = initiator_ingest_response( + &mut i_context, + &i_frame_r, + &i_context_r, + responder_ed25519_keypair.public_key(), + &r_dir_hash, + ) + .unwrap(); + + assert_eq!(i_obtained_key.encode(), r_kem_key_bytes) + } + + // Initiator, Mutual + { + let (mut i_context, i_frame) = initiator_process( + &mut rng, + crate::context::KKTMode::Mutual, + ciphersuite, + initiator_ed25519_keypair.private_key(), + Some(&initiator_kem_public_key), + ) + .unwrap(); + + // encryption - initiator frame + + let (i_session_secret, i_bytes) = encrypt_initial_kkt_frame( + &mut rng, + responder_x25519_keypair.public_key(), + &i_frame, + ) + .unwrap(); + + // decryption - initiator frame + + let (r_session_secret, i_frame_r, i_context_r) = + decrypt_initial_kkt_frame(responder_x25519_keypair.private_key(), &i_bytes) + .unwrap(); + + let (mut r_context, r_obtained_key) = responder_ingest_message( + &i_context_r, + Some(initiator_ed25519_keypair.public_key()), + Some(&i_dir_hash), + &i_frame_r, + ) + .unwrap(); + + assert_eq!(r_obtained_key.unwrap().encode(), i_kem_key_bytes); + + let r_frame = responder_process( + &mut r_context, + i_frame_r.session_id(), + responder_ed25519_keypair.private_key(), + &responder_kem_public_key, + ) + .unwrap(); + + // encryption - responder frame + let r_bytes = + encrypt_kkt_frame(&mut rng, &r_session_secret, &r_frame, KKT_RESPONSE_AAD) + .unwrap(); + + // decryption - responder frame + + let (i_frame_r, i_context_r) = + decrypt_kkt_frame(&i_session_secret, &r_bytes, KKT_RESPONSE_AAD).unwrap(); + + let i_obtained_key = initiator_ingest_response( + &mut i_context, + &i_frame_r, + &i_context_r, + responder_ed25519_keypair.public_key(), + &r_dir_hash, + ) + .unwrap(); + + assert_eq!(i_obtained_key.encode(), r_kem_key_bytes) } } } diff --git a/common/nym-kkt/src/session.rs b/common/nym-kkt/src/session.rs index 75492a6170..88a59500eb 100644 --- a/common/nym-kkt/src/session.rs +++ b/common/nym-kkt/src/session.rs @@ -1,6 +1,7 @@ use nym_crypto::asymmetric::ed25519::{self, Signature}; use rand::{CryptoRng, RngCore}; +use crate::frame::KKTSessionId; use crate::{ ciphersuite::{Ciphersuite, EncapsulationKey}, context::{KKTContext, KKTMode, KKTRole, KKTStatus}, @@ -51,7 +52,7 @@ where Ok(( context, - KKTFrame::new(&context_bytes, body, &session_id, &signature), + KKTFrame::new(context_bytes, body, session_id, &signature), )) } @@ -68,43 +69,38 @@ where let mut session_id = [0u8; KKT_SESSION_ID_LEN]; rng.fill_bytes(&mut session_id); - Ok(( - context, - KKTFrame::new(&context_bytes, &[], &session_id, &[]), - )) + Ok((context, KKTFrame::new(context_bytes, &[], session_id, &[]))) } pub fn initiator_ingest_response<'a>( own_context: &mut KKTContext, + remote_frame: &KKTFrame, + remote_context: &KKTContext, remote_verification_key: &ed25519::PublicKey, expected_hash: &[u8], - message_bytes: &[u8], ) -> Result, KKTError> { - // sizes have to be correct - let (frame, remote_context) = KKTFrame::from_bytes(message_bytes)?; - - check_compatibility(own_context, &remote_context)?; + check_compatibility(own_context, remote_context)?; match remote_context.status() { KKTStatus::Ok => { let mut bytes_to_verify: Vec = Vec::with_capacity( remote_context.full_message_len() - remote_context.signature_len(), ); bytes_to_verify.extend_from_slice(&remote_context.encode()?); - bytes_to_verify.extend_from_slice(frame.body_ref()); - bytes_to_verify.extend_from_slice(frame.session_id_ref()); + bytes_to_verify.extend_from_slice(remote_frame.body_ref()); + bytes_to_verify.extend_from_slice(remote_frame.session_id_ref()); - match Signature::from_bytes(frame.signature_ref()) { + match Signature::from_bytes(remote_frame.signature_ref()) { Ok(sig) => match remote_verification_key.verify(bytes_to_verify, &sig) { Ok(()) => { let received_encapsulation_key = EncapsulationKey::decode( own_context.ciphersuite().kem(), - frame.body_ref(), + remote_frame.body_ref(), )?; match validate_encapsulation_key( &own_context.ciphersuite().hash_function(), own_context.ciphersuite().hash_len(), - frame.body_ref(), + remote_frame.body_ref(), expected_hash, ) { true => Ok(received_encapsulation_key), @@ -206,7 +202,7 @@ pub fn responder_ingest_message<'a>( pub fn responder_process<'a>( own_context: &mut KKTContext, - session_id: &[u8], + session_id: KKTSessionId, signing_key: &ed25519::PrivateKey, encapsulation_key: &EncapsulationKey<'a>, ) -> Result { @@ -218,11 +214,11 @@ pub fn responder_process<'a>( Vec::with_capacity(own_context.full_message_len() - own_context.signature_len()); bytes_to_sign.extend_from_slice(&own_context.encode()?); bytes_to_sign.extend_from_slice(&body); - bytes_to_sign.extend_from_slice(session_id); + bytes_to_sign.extend_from_slice(&session_id); let signature = signing_key.sign(bytes_to_sign).to_bytes(); - Ok(KKTFrame::new(&context_bytes, &body, session_id, &signature)) + Ok(KKTFrame::new(context_bytes, &body, session_id, &signature)) } fn check_compatibility( diff --git a/common/nym-lp-common/Cargo.toml b/common/nym-lp-common/Cargo.toml index b70550c82b..b84b617154 100644 --- a/common/nym-lp-common/Cargo.toml +++ b/common/nym-lp-common/Cargo.toml @@ -3,5 +3,6 @@ name = "nym-lp-common" version = "0.1.0" edition = { workspace = true } license = { workspace = true } +publish = false [dependencies] diff --git a/common/nym-lp-transport/Cargo.toml b/common/nym-lp-transport/Cargo.toml index c2aba1690d..d4448e49c8 100644 --- a/common/nym-lp-transport/Cargo.toml +++ b/common/nym-lp-transport/Cargo.toml @@ -9,10 +9,12 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +publish = false [dependencies] -tokio = { workspace = true, features = ["net"] } +tokio = { workspace = true, features = ["net", "io-util"] } nym-test-utils = { path = "../test-utils", optional = true } +tracing = { workspace = true } [features] io-mocks = ["nym-test-utils"] diff --git a/common/nym-lp-transport/src/traits.rs b/common/nym-lp-transport/src/traits.rs index 5b3d300884..888c800f64 100644 --- a/common/nym-lp-transport/src/traits.rs +++ b/common/nym-lp-transport/src/traits.rs @@ -4,8 +4,9 @@ #[cfg(feature = "io-mocks")] use nym_test_utils::mocks::async_read_write::MockIOStream; use std::net::SocketAddr; -use tokio::io::{AsyncRead, AsyncWrite}; +use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; use tokio::net::TcpStream; +use tracing::debug; // only used in internal code (and tests) #[allow(async_fn_in_trait)] @@ -13,6 +14,78 @@ pub trait LpTransport: AsyncRead + AsyncWrite + Sized { async fn connect(endpoint: SocketAddr) -> std::io::Result; fn set_no_delay(&mut self, nodelay: bool) -> std::io::Result<()>; + + /// Sends a serialised (and optionally encrypted) LP packet over the data stream with length-prefixed framing. + /// + /// Format: 4-byte big-endian u32 length + packet bytes + /// + /// # Arguments + /// * `packet_data` - The serialised LP packet to send + /// + /// # Errors + /// Returns an error on network transmission fails. + async fn send_serialised_packet(&mut self, packet_data: &[u8]) -> std::io::Result<()> + where + Self: Unpin, + { + // Send 4-byte length prefix (u32 big-endian) + let len = packet_data.len() as u32; + self.write_all(&len.to_be_bytes()) + .await + .inspect_err(|e| debug!("Failed to send packet length: {e}"))?; + + // Send the actual packet data + self.write_all(packet_data) + .await + .inspect_err(|e| debug!("Failed to send packet data: {e}"))?; + + // Flush to ensure data is sent immediately + self.flush() + .await + .inspect_err(|e| debug!("Failed to flush stream: {e}"))?; + + tracing::trace!( + "Sent LP packet ({} bytes + 4 byte header)", + packet_data.len() + ); + Ok(()) + } + + /// Receives an LP packet from a TCP stream with length-prefixed framing. + /// + /// Format: 4-byte big-endian u32 length + packet bytes + /// + /// # Errors + /// Returns an error on network transmission fails. + async fn receive_raw_packet(&mut self) -> std::io::Result> + where + Self: Unpin, + { + // Read 4-byte length prefix (u32 big-endian) + let mut len_buf = [0u8; 4]; + self.read_exact(&mut len_buf) + .await + .inspect_err(|e| debug!("Failed to read packet length: {e}"))?; + + let packet_len = u32::from_be_bytes(len_buf) as usize; + + // Sanity check to prevent huge allocations + const MAX_PACKET_SIZE: usize = 65536; // 64KB max + if packet_len > MAX_PACKET_SIZE { + return Err(std::io::Error::other(format!( + "Packet size {packet_len} exceeds maximum {MAX_PACKET_SIZE}", + ))); + } + + // Read the actual packet data + let mut packet_buf = vec![0u8; packet_len]; + self.read_exact(&mut packet_buf) + .await + .inspect_err(|e| debug!("Failed to read packet data: {e}"))?; + + tracing::trace!("Received LP packet ({packet_len} bytes + 4 byte header)"); + Ok(packet_buf) + } } impl LpTransport for TcpStream { diff --git a/common/nym-lp/Cargo.toml b/common/nym-lp/Cargo.toml index 50fec3ac36..58f376b1e5 100644 --- a/common/nym-lp/Cargo.toml +++ b/common/nym-lp/Cargo.toml @@ -3,9 +3,9 @@ name = "nym-lp" version = "0.1.0" edition = { workspace = true } license = { workspace = true } +publish = false [dependencies] -bincode = { workspace = true } thiserror = { workspace = true } parking_lot = { workspace = true } snow = { workspace = true } @@ -14,9 +14,7 @@ serde = { workspace = true } bytes = { workspace = true } dashmap = { workspace = true } sha2 = { workspace = true } -ansi_term = { workspace = true } tracing = { workspace = true } -utoipa = { workspace = true, features = ["macros", "non_strict_integers"] } rand = { workspace = true } # rand 0.9 for KKT integration (nym-kkt uses rand 0.9) rand09 = { package = "rand", version = "0.9.2" } @@ -24,7 +22,6 @@ rand09 = { package = "rand", version = "0.9.2" } nym-crypto = { path = "../crypto", features = ["hashing", "asymmetric"] } nym-kkt = { path = "../nym-kkt" } nym-lp-common = { path = "../nym-lp-common" } -nym-sphinx = { path = "../nymsphinx" } # libcrux dependencies for PSQ (Post-Quantum PSK derivation) libcrux-psq = { git = "https://github.com/cryspen/libcrux", features = [ @@ -40,6 +37,8 @@ zeroize = { workspace = true, features = ["zeroize_derive"] } [dev-dependencies] criterion = { version = "0.5", features = ["html_reports"] } rand_chacha = "0.3" +nym-crypto = { path = "../crypto", features = ["rand"] } +nym-test-utils = { workspace = true } [[bench]] diff --git a/common/nym-lp/src/codec.rs b/common/nym-lp/src/codec.rs index 08c98b1221..d8eee41844 100644 --- a/common/nym-lp/src/codec.rs +++ b/common/nym-lp/src/codec.rs @@ -2,13 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 use crate::LpError; -use crate::message::{ - ClientHelloData, EncryptedDataPayload, ForwardPacketData, HandshakeData, KKTRequestData, - KKTResponseData, LpMessage, MessageType, SubsessionKK1Data, SubsessionKK2Data, - SubsessionReadyData, -}; +use crate::message::{LpMessage, MessageType}; use crate::packet::{LpHeader, LpPacket, OuterHeader, TRAILER_LEN}; -use crate::serialisation::{BincodeOptions, lp_bincode_serializer}; use bytes::{BufMut, BytesMut}; use chacha20poly1305::{ ChaCha20Poly1305, Key, Nonce, Tag, @@ -96,87 +91,7 @@ fn parse_message_from_type_and_content( let message_type = MessageType::from_u32(msg_type_raw) .ok_or_else(|| LpError::invalid_message_type(msg_type_raw))?; - match message_type { - MessageType::Busy => { - if !content.is_empty() { - return Err(LpError::InvalidPayloadSize { - expected: 0, - actual: content.len(), - }); - } - Ok(LpMessage::Busy) - } - MessageType::Handshake => Ok(LpMessage::Handshake(HandshakeData(content.to_vec()))), - MessageType::EncryptedData => Ok(LpMessage::EncryptedData(EncryptedDataPayload( - content.to_vec(), - ))), - MessageType::ClientHello => { - let data = ClientHelloData::decode(content)?; - Ok(LpMessage::ClientHello(data)) - } - MessageType::KKTRequest => Ok(LpMessage::KKTRequest(KKTRequestData(content.to_vec()))), - MessageType::KKTResponse => Ok(LpMessage::KKTResponse(KKTResponseData(content.to_vec()))), - MessageType::ForwardPacket => { - let data: ForwardPacketData = lp_bincode_serializer() - .deserialize(content) - .map_err(|e| LpError::DeserializationError(e.to_string()))?; - Ok(LpMessage::ForwardPacket(data)) - } - MessageType::Collision => { - if !content.is_empty() { - return Err(LpError::InvalidPayloadSize { - expected: 0, - actual: content.len(), - }); - } - Ok(LpMessage::Collision) - } - MessageType::Ack => { - if !content.is_empty() { - return Err(LpError::InvalidPayloadSize { - expected: 0, - actual: content.len(), - }); - } - Ok(LpMessage::Ack) - } - MessageType::SubsessionRequest => { - if !content.is_empty() { - return Err(LpError::InvalidPayloadSize { - expected: 0, - actual: content.len(), - }); - } - Ok(LpMessage::SubsessionRequest) - } - MessageType::SubsessionKK1 => { - let data: SubsessionKK1Data = lp_bincode_serializer() - .deserialize(content) - .map_err(|e| LpError::DeserializationError(e.to_string()))?; - Ok(LpMessage::SubsessionKK1(data)) - } - MessageType::SubsessionKK2 => { - let data: SubsessionKK2Data = lp_bincode_serializer() - .deserialize(content) - .map_err(|e| LpError::DeserializationError(e.to_string()))?; - Ok(LpMessage::SubsessionKK2(data)) - } - MessageType::SubsessionReady => { - let data: SubsessionReadyData = lp_bincode_serializer() - .deserialize(content) - .map_err(|e| LpError::DeserializationError(e.to_string()))?; - Ok(LpMessage::SubsessionReady(data)) - } - MessageType::SubsessionAbort => { - // Empty signal message - no content to deserialize - if !content.is_empty() { - return Err(LpError::DeserializationError( - "SubsessionAbort should have no payload".to_string(), - )); - } - Ok(LpMessage::SubsessionAbort) - } - } + LpMessage::decode_content(content, message_type) } /// Parse only the outer header from raw packet bytes. @@ -249,7 +164,7 @@ pub fn parse_lp_packet(src: &[u8], outer_key: Option<&OuterAeadKey>) -> Result) -> Result { - assert_eq!(decoded_data.client_lp_public_key, [version; 32]); - } - _ => panic!("Expected ClientHello message for version {}", version), - } - } - } - - #[test] - fn test_forward_packet_encode_decode_roundtrip() { + fn test_forward_packet_encode_decode_roundtrip_v4() { let mut dst = BytesMut::new(); let forward_data = crate::message::ForwardPacketData { target_gateway_identity: [77u8; 32], - target_lp_address: "1.2.3.4:41264".to_string(), + target_lp_address: "1.2.3.4:41264".parse().unwrap(), inner_packet_bytes: vec![0xa, 0xb, 0xc, 0xd], }; let packet = LpPacket { header: LpHeader { protocol_version: 1, - reserved: 0, + reserved: [0u8; 3], receiver_idx: 999, counter: 555, }, @@ -904,7 +789,50 @@ mod tests { if let LpMessage::ForwardPacket(data) = decoded.message { assert_eq!(data.target_gateway_identity, [77u8; 32]); - assert_eq!(data.target_lp_address, "1.2.3.4:41264"); + assert_eq!(data.target_lp_address, "1.2.3.4:41264".parse().unwrap()); + assert_eq!(data.inner_packet_bytes, vec![0xa, 0xb, 0xc, 0xd]); + } else { + panic!("Expected ForwardPacket message"); + } + } + + #[test] + fn test_forward_packet_encode_decode_roundtrip_v6() { + let mut dst = BytesMut::new(); + + let forward_data = crate::message::ForwardPacketData { + target_gateway_identity: [77u8; 32], + target_lp_address: "[dead:beef:4242:c0ff:ee00::1111]:41264".parse().unwrap(), + inner_packet_bytes: vec![0xa, 0xb, 0xc, 0xd], + }; + + let packet = LpPacket { + header: LpHeader { + protocol_version: 1, + reserved: [0u8; 3], + receiver_idx: 999, + counter: 555, + }, + message: LpMessage::ForwardPacket(forward_data), + trailer: [0xff; TRAILER_LEN], + }; + + // Serialize + serialize_lp_packet(&packet, &mut dst, None).unwrap(); + + // Parse back + let decoded = parse_lp_packet(&dst, None).unwrap(); + + // Verify LP protocol handling works correctly + assert_eq!(decoded.header.receiver_idx, 999); + assert!(matches!(decoded.message.typ(), MessageType::ForwardPacket)); + + if let LpMessage::ForwardPacket(data) = decoded.message { + assert_eq!(data.target_gateway_identity, [77u8; 32]); + assert_eq!( + data.target_lp_address, + "[dead:beef:4242:c0ff:ee00::1111]:41264".parse().unwrap() + ); assert_eq!(data.inner_packet_bytes, vec![0xa, 0xb, 0xc, 0xd]); } else { panic!("Expected ForwardPacket message"); @@ -922,7 +850,7 @@ mod tests { let packet = LpPacket { header: LpHeader { protocol_version: 1, - reserved: 0, + reserved: [0u8; 3], receiver_idx: 12345, counter: 999, }, @@ -951,7 +879,7 @@ mod tests { let packet = LpPacket { header: LpHeader { protocol_version: 1, - reserved: 0, + reserved: [0u8; 3], receiver_idx: 12345, counter: 999, }, @@ -998,7 +926,7 @@ mod tests { let packet = LpPacket { header: LpHeader { protocol_version: 1, - reserved: 0, + reserved: [0u8; 3], receiver_idx: 12345, counter: 999, }, @@ -1027,7 +955,7 @@ mod tests { let packet = LpPacket { header: LpHeader { protocol_version: 1, - reserved: 0, + reserved: [0u8; 3], receiver_idx: 12345, counter: 999, }, @@ -1056,7 +984,7 @@ mod tests { let packet1 = LpPacket { header: LpHeader { protocol_version: 1, - reserved: 0, + reserved: [0u8; 3], receiver_idx: 12345, counter: 1, }, @@ -1067,7 +995,7 @@ mod tests { let packet2 = LpPacket { header: LpHeader { protocol_version: 1, - reserved: 0, + reserved: [0u8; 3], receiver_idx: 12345, counter: 2, // Different counter }, @@ -1102,7 +1030,7 @@ mod tests { let packet = LpPacket { header: LpHeader { protocol_version: 1, - reserved: 0, + reserved: [0u8; 3], receiver_idx: 12345, counter: 999, }, @@ -1128,7 +1056,7 @@ mod tests { let packet = LpPacket { header: LpHeader { protocol_version: 1, - reserved: 0, + reserved: [0u8; 3], receiver_idx: 54321, counter: 12345678, }, @@ -1161,7 +1089,7 @@ mod tests { let packet = LpPacket { header: LpHeader { protocol_version: 1, - reserved: 0, + reserved: [0u8; 3], receiver_idx: 99999, counter: 2, }, @@ -1191,7 +1119,7 @@ mod tests { let packet = LpPacket { header: LpHeader { protocol_version: 1, - reserved: 0, + reserved: [0u8; 3], receiver_idx: 42, counter: 100, }, @@ -1220,7 +1148,7 @@ mod tests { let packet = LpPacket { header: LpHeader { protocol_version: 1, - reserved: 0, + reserved: [0u8; 3], receiver_idx: 123, counter: 456, }, @@ -1253,7 +1181,7 @@ mod tests { let packet = LpPacket { header: LpHeader { protocol_version: 1, - reserved: 0, + reserved: [0u8; 3], receiver_idx: 789, counter: 1000, }, @@ -1286,7 +1214,7 @@ mod tests { let packet = LpPacket { header: LpHeader { protocol_version: 1, - reserved: 0, + reserved: [0u8; 3], receiver_idx: 42, counter: 200, }, @@ -1341,7 +1269,7 @@ mod tests { let packet = LpPacket { header: LpHeader { protocol_version: 1, - reserved: 0, + reserved: [0u8; 3], receiver_idx: 54321, counter: 999, }, diff --git a/common/nym-lp/src/error.rs b/common/nym-lp/src/error.rs index 3e6592313d..caedcc84e4 100644 --- a/common/nym-lp/src/error.rs +++ b/common/nym-lp/src/error.rs @@ -3,6 +3,7 @@ use crate::{noise_protocol::NoiseError, replay::ReplayError}; use nym_crypto::asymmetric::ed25519::Ed25519RecoveryError; +use nym_kkt::ciphersuite::{HashFunction, KEM}; use thiserror::Error; #[derive(Error, Debug)] @@ -82,4 +83,23 @@ pub enum LpError { /// Outer AEAD authentication tag verification failed. #[error("AEAD authentication tag verification failed")] AeadTagMismatch, + + /// Received an LP packet with an incompatible, future, version + #[error("incompatible LP packet version. got: {got}, highest supported: {highest_supported}")] + IncompatibleFuturePacketVersion { got: u8, highest_supported: u8 }, + + /// Received an LP packet with an incompatible, legacy, version + #[error("incompatible LP packet version. got: {got}, lowest supported: {lowest_supported}")] + IncompatibleLegacyPacketVersion { got: u8, lowest_supported: u8 }, + + #[error("attempted to create an LP responder without providing a valid KEM key")] + ResponderWithMissingKEMKey, + + #[error( + "there are no known digests for remote's KEM key with {kem} KEM and {hash_function} hash function" + )] + NoKnownKEMKeyDigests { + kem: KEM, + hash_function: HashFunction, + }, } diff --git a/common/nym-lp/src/keypair.rs b/common/nym-lp/src/keypair.rs deleted file mode 100644 index 6f9546ba52..0000000000 --- a/common/nym-lp/src/keypair.rs +++ /dev/null @@ -1,200 +0,0 @@ -use std::fmt::{self, Display, Formatter}; -use std::ops::Deref; -use std::str::FromStr; - -use nym_sphinx::{PrivateKey as SphinxPrivateKey, PublicKey as SphinxPublicKey}; -use serde::Serialize; -use utoipa::ToSchema; - -use crate::LpError; - -#[derive(Clone)] -pub struct PrivateKey(SphinxPrivateKey); - -impl fmt::Debug for PrivateKey { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_tuple("PrivateKey").field(&"[REDACTED]").finish() - } -} - -impl Deref for PrivateKey { - type Target = SphinxPrivateKey; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl Default for PrivateKey { - fn default() -> Self { - Self::new() - } -} - -impl PrivateKey { - pub fn new() -> Self { - let private_key = SphinxPrivateKey::random(); - Self(private_key) - } - - pub fn to_base58_string(&self) -> String { - bs58::encode(self.0.to_bytes()).into_string() - } - - pub fn from_base58_string(s: &str) -> Result { - let bytes: [u8; 32] = bs58::decode(s).into_vec()?.try_into().unwrap(); - Ok(PrivateKey(SphinxPrivateKey::from(bytes))) - } - - pub fn from_bytes(bytes: &[u8; 32]) -> Self { - PrivateKey(SphinxPrivateKey::from(*bytes)) - } - - pub fn public_key(&self) -> PublicKey { - let public_key = SphinxPublicKey::from(&self.0); - PublicKey(public_key) - } -} - -#[derive(Clone)] -pub struct PublicKey(SphinxPublicKey); - -impl fmt::Debug for PublicKey { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_tuple("PublicKey") - .field(&self.to_base58_string()) - .finish() - } -} - -impl Deref for PublicKey { - type Target = SphinxPublicKey; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl PublicKey { - pub fn to_base58_string(&self) -> String { - bs58::encode(self.0.as_bytes()).into_string() - } - - pub fn from_base58_string(s: &str) -> Result { - let bytes: [u8; 32] = bs58::decode(s).into_vec()?.try_into().unwrap(); - Ok(PublicKey(SphinxPublicKey::from(bytes))) - } - - pub fn from_bytes(bytes: &[u8; 32]) -> Result { - Ok(PublicKey(SphinxPublicKey::from(*bytes))) - } - - pub fn as_bytes(&self) -> &[u8; 32] { - self.0.as_bytes() - } -} - -impl Default for PublicKey { - fn default() -> Self { - let private_key = PrivateKey::default(); - private_key.public_key() - } -} - -pub struct Keypair { - private_key: PrivateKey, - public_key: PublicKey, -} - -impl Default for Keypair { - fn default() -> Self { - Self::new() - } -} - -impl Keypair { - pub fn new() -> Self { - let private_key = PrivateKey::default(); - let public_key = private_key.public_key(); - Self { - private_key, - public_key, - } - } - - pub fn from_private_key(private_key: PrivateKey) -> Self { - let public_key = private_key.public_key(); - Self { - private_key, - public_key, - } - } - - pub fn from_keys(private_key: PrivateKey, public_key: PublicKey) -> Self { - Self { - private_key, - public_key, - } - } - - pub fn private_key(&self) -> &PrivateKey { - &self.private_key - } - - pub fn public_key(&self) -> &PublicKey { - &self.public_key - } -} - -impl From for Keypair { - fn from(keypair: KeypairReadable) -> Self { - Self { - private_key: PrivateKey::from_base58_string(&keypair.private).unwrap(), - public_key: PublicKey::from_base58_string(&keypair.public).unwrap(), - } - } -} - -impl From<&Keypair> for KeypairReadable { - fn from(keypair: &Keypair) -> Self { - Self { - private: keypair.private_key.to_base58_string(), - public: keypair.public_key.to_base58_string(), - } - } -} -impl FromStr for PrivateKey { - type Err = LpError; - - fn from_str(s: &str) -> Result { - PrivateKey::from_base58_string(s) - } -} - -impl Display for PrivateKey { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.to_base58_string()) - } -} - -impl Display for PublicKey { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.to_base58_string()) - } -} - -#[derive(Serialize, serde::Deserialize, Clone, ToSchema, Debug)] -pub struct KeypairReadable { - private: String, - public: String, -} - -impl KeypairReadable { - pub fn private_key(&self) -> Result { - PrivateKey::from_base58_string(&self.private) - } - - pub fn public_key(&self) -> Result { - PublicKey::from_base58_string(&self.public) - } -} diff --git a/common/nym-lp/src/kkt_orchestrator.rs b/common/nym-lp/src/kkt_orchestrator.rs index e6b20df373..98e3cdff71 100644 --- a/common/nym-lp/src/kkt_orchestrator.rs +++ b/common/nym-lp/src/kkt_orchestrator.rs @@ -37,9 +37,10 @@ //! ).unwrap(); //! //! // Client: Create request -//! let (client_context, request_data) = create_request( +//! let (session_secret, client_context, request_data) = create_request( //! ciphersuite, //! &client_signing_key, +//! &responder_dh_public_key //! ).unwrap(); //! //! // Gateway: Handle request @@ -47,12 +48,14 @@ //! &request_data, //! Some(&client_verification_key), //! &gateway_signing_key, +//! &gateway_dh_private_key, //! &gateway_kem_public_key, //! ).unwrap(); //! //! // Client: Process response //! let gateway_kem_key = process_response( //! client_context, +//! &session_secret, //! &gateway_verification_key, //! &expected_key_hash, //! &response_data, @@ -61,10 +64,10 @@ use crate::LpError; use crate::message::{KKTRequestData, KKTResponseData}; -use nym_crypto::asymmetric::ed25519; +use nym_crypto::asymmetric::{ed25519, x25519}; use nym_kkt::ciphersuite::{Ciphersuite, EncapsulationKey}; use nym_kkt::context::KKTContext; -use nym_kkt::frame::KKTFrame; +use nym_kkt::encryption::KKTSessionSecret; use nym_kkt::kkt::{handle_kem_request, request_kem_key, validate_kem_response}; /// Creates a KKT request to obtain the responder's KEM public key. @@ -75,8 +78,10 @@ use nym_kkt::kkt::{handle_kem_request, request_kem_key, validate_kem_response}; /// # Arguments /// * `ciphersuite` - Negotiated ciphersuite (KEM, hash, signature algorithms) /// * `signing_key` - Client's Ed25519 signing key for authentication +/// * `responder_dh_public_key` - Gateway's x25519 public key (from directory) /// /// # Returns +/// * `KKTSessionSecret` - Session secret key to encrypt/decrypt KKT messages for this session /// * `KKTContext` - Context to use when validating the response /// * `KKTRequestData` - Serialized KKT request frame to send to gateway /// @@ -85,14 +90,15 @@ use nym_kkt::kkt::{handle_kem_request, request_kem_key, validate_kem_response}; pub fn create_request( ciphersuite: Ciphersuite, signing_key: &ed25519::PrivateKey, -) -> Result<(KKTContext, KKTRequestData), LpError> { + responder_dh_public_key: &x25519::PublicKey, +) -> Result<(KKTSessionSecret, KKTContext, KKTRequestData), LpError> { // Note: Uses rand 0.9's thread_rng() to match nym-kkt's rand version let mut rng = rand09::rng(); - let (context, frame) = request_kem_key(&mut rng, ciphersuite, signing_key) - .map_err(|e| LpError::KKTError(e.to_string()))?; + let (session_secret, context, request_bytes) = + request_kem_key(&mut rng, ciphersuite, signing_key, responder_dh_public_key) + .map_err(|e| LpError::KKTError(e.to_string()))?; - let request_bytes = frame.to_bytes(); - Ok((context, KKTRequestData(request_bytes))) + Ok((session_secret, context, KKTRequestData(request_bytes))) } /// Processes a KKT response and extracts the responder's KEM public key. @@ -102,6 +108,7 @@ pub fn create_request( /// /// # Arguments /// * `context` - Context from the initial `create_request()` call +/// * `session_secret` - The KKT session secret key from the initial `create_request()` call /// * `responder_vk` - Responder's Ed25519 verification key (from directory) /// * `expected_key_hash` - Expected hash of responder's KEM key (from directory) /// * `response_data` - Serialized KKT response frame from responder @@ -116,12 +123,14 @@ pub fn create_request( /// - Key hash doesn't match expected value pub fn process_response<'a>( mut context: KKTContext, + session_secret: &KKTSessionSecret, responder_vk: &ed25519::PublicKey, expected_key_hash: &[u8], response_data: &KKTResponseData, ) -> Result, LpError> { validate_kem_response( &mut context, + session_secret, responder_vk, expected_key_hash, &response_data.0, @@ -139,6 +148,7 @@ pub fn process_response<'a>( /// * `request_data` - Serialized KKT request frame from initiator /// * `initiator_vk` - Initiator's Ed25519 verification key (None for anonymous) /// * `responder_signing_key` - Gateway's Ed25519 signing key +/// * `responder_dh_private_key` - Gateway's x25519 private key /// * `responder_kem_key` - Gateway's KEM public key to send /// /// # Returns @@ -153,30 +163,33 @@ pub fn handle_request<'a>( request_data: &KKTRequestData, initiator_vk: Option<&ed25519::PublicKey>, responder_signing_key: &ed25519::PrivateKey, + responder_dh_private_key: &x25519::PrivateKey, responder_kem_key: &EncapsulationKey<'a>, ) -> Result { - // Deserialize request frame - let (request_frame, _) = KKTFrame::from_bytes(&request_data.0) - .map_err(|e| LpError::KKTError(format!("Failed to parse KKT request: {}", e)))?; - + let mut rng = rand09::rng(); // Handle the request and generate response - let response_frame = handle_kem_request( - &request_frame, + let response_bytes = handle_kem_request( + &mut rng, + &request_data.0, initiator_vk, responder_signing_key, + responder_dh_private_key, responder_kem_key, ) .map_err(|e| LpError::KKTError(e.to_string()))?; - let response_bytes = response_frame.to_bytes(); Ok(KKTResponseData(response_bytes)) } #[cfg(test)] mod tests { use super::*; + use crate::peer::mock_peers; use nym_kkt::ciphersuite::{HashFunction, KEM, SignatureScheme}; - use nym_kkt::key_utils::{generate_keypair_libcrux, hash_encapsulation_key}; + use nym_kkt::key_utils::{ + generate_keypair_ed25519, generate_keypair_libcrux, generate_keypair_x25519, + hash_encapsulation_key, + }; use rand09::RngCore; #[test] @@ -184,13 +197,10 @@ mod tests { let mut rng = rand09::rng(); // Generate Ed25519 keypairs for both parties - let mut initiator_secret = [0u8; 32]; - rng.fill_bytes(&mut initiator_secret); - let initiator_keypair = ed25519::KeyPair::from_secret(initiator_secret, 0); + let initiator_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(0)); + let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(1)); - let mut responder_secret = [0u8; 32]; - rng.fill_bytes(&mut responder_secret); - let responder_keypair = ed25519::KeyPair::from_secret(responder_secret, 1); + let responder_x25519 = generate_keypair_x25519(&mut rng); // Generate responder's KEM keypair (X25519 for testing) let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); @@ -213,14 +223,19 @@ mod tests { ); // Client: Create request - let (context, request_data) = - create_request(ciphersuite, initiator_keypair.private_key()).unwrap(); + let (session_secret, context, request_data) = create_request( + ciphersuite, + initiator_ed25519_keypair.private_key(), + responder_x25519.public_key(), + ) + .unwrap(); // Gateway: Handle request let response_data = handle_request( &request_data, - Some(initiator_keypair.public_key()), - responder_keypair.private_key(), + Some(initiator_ed25519_keypair.public_key()), + responder_ed25519_keypair.private_key(), + responder_x25519.private_key(), &responder_kem_key, ) .unwrap(); @@ -228,7 +243,8 @@ mod tests { // Client: Process response let obtained_key = process_response( context, - responder_keypair.public_key(), + &session_secret, + responder_ed25519_keypair.public_key(), &key_hash, &response_data, ) @@ -238,70 +254,71 @@ mod tests { assert_eq!(obtained_key.encode(), responder_kem_key.encode()); } - #[test] - fn test_kkt_roundtrip_anonymous() { - let mut rng = rand09::rng(); + // #[test] + // fn test_kkt_roundtrip_anonymous() { + // let mut rng = rand09::rng(); - // Only responder has keys (anonymous initiator) - let mut responder_secret = [0u8; 32]; - rng.fill_bytes(&mut responder_secret); - let responder_keypair = ed25519::KeyPair::from_secret(responder_secret, 1); + // // Only responder has keys (anonymous initiator) + // // Generate Ed25519 keypairs for both parties - let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); - let responder_kem_key = EncapsulationKey::X25519(responder_kem_pk); + // let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(1)); - let ciphersuite = Ciphersuite::resolve_ciphersuite( - KEM::X25519, - HashFunction::Blake3, - SignatureScheme::Ed25519, - None, - ) - .unwrap(); + // let responder_x25519 = generate_keypair_x25519(&mut rng); - let key_hash = hash_encapsulation_key( - &ciphersuite.hash_function(), - ciphersuite.hash_len(), - &responder_kem_key.encode(), - ); + // let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); + // let responder_kem_key = EncapsulationKey::X25519(responder_kem_pk); - // Anonymous initiator - use anonymous_initiator_process directly - use nym_kkt::kkt::anonymous_initiator_process; - let (mut context, request_frame) = - anonymous_initiator_process(&mut rng, ciphersuite).unwrap(); - let request_data = KKTRequestData(request_frame.to_bytes()); + // let ciphersuite = Ciphersuite::resolve_ciphersuite( + // KEM::X25519, + // HashFunction::Blake3, + // SignatureScheme::Ed25519, + // None, + // ) + // .unwrap(); - // Gateway: Handle anonymous request - let response_data = handle_request( - &request_data, - None, // Anonymous - no verification key - responder_keypair.private_key(), - &responder_kem_key, - ) - .unwrap(); + // let key_hash = hash_encapsulation_key( + // &ciphersuite.hash_function(), + // ciphersuite.hash_len(), + // &responder_kem_key.encode(), + // ); - // Initiator: Validate response - let obtained_key = validate_kem_response( - &mut context, - responder_keypair.public_key(), - &key_hash, - &response_data.0, - ) - .unwrap(); + // // Anonymous initiator - use anonymous_initiator_process directly + // use nym_kkt::kkt::anonymous_initiator_process; + // let (mut context, request_frame) = + // anonymous_initiator_process(&mut rng, ciphersuite).unwrap(); + // let request_data = KKTRequestData(request_frame.to_bytes()); - assert_eq!(obtained_key.encode(), responder_kem_key.encode()); - } + // // Gateway: Handle anonymous request + // let response_data = handle_request( + // &request_data, + // None, + // responder_ed25519_keypair.private_key(), + // &responder_x25519_sk, + // &responder_kem_key, + // ) + // .unwrap(); + + // // Initiator: Validate response + // let obtained_key = initiator_ingest_response( + // &mut context, + // responder_ed25519_keypair.public_key(), + // &key_hash, + // &response_data.0, + // ) + // .unwrap(); + + // assert_eq!(obtained_key.encode(), responder_kem_key.encode()); + // } #[test] fn test_invalid_signature_rejected() { let mut rng = rand09::rng(); - let mut initiator_secret = [0u8; 32]; - rng.fill_bytes(&mut initiator_secret); - let initiator_keypair = ed25519::KeyPair::from_secret(initiator_secret, 0); + // Generate Ed25519 keypairs for both parties + let initiator_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(0)); + let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(1)); - let mut responder_secret = [0u8; 32]; - rng.fill_bytes(&mut responder_secret); - let responder_keypair = ed25519::KeyPair::from_secret(responder_secret, 1); + let responder_x25519 = generate_keypair_x25519(&mut rng); // Different keypair for wrong signature let mut wrong_secret = [0u8; 32]; @@ -319,14 +336,19 @@ mod tests { ) .unwrap(); - let (_context, request_data) = - create_request(ciphersuite, initiator_keypair.private_key()).unwrap(); + let (_session_secret, _context, request_data) = create_request( + ciphersuite, + initiator_ed25519_keypair.private_key(), + responder_x25519.public_key(), + ) + .unwrap(); // Gateway handles request but we provide WRONG verification key let result = handle_request( &request_data, Some(wrong_keypair.public_key()), // Wrong key! - responder_keypair.private_key(), + responder_ed25519_keypair.private_key(), + responder_x25519.private_key(), &responder_kem_key, ); @@ -341,18 +363,8 @@ mod tests { #[test] fn test_hash_mismatch_rejected() { - let mut rng = rand09::rng(); - - let mut initiator_secret = [0u8; 32]; - rng.fill_bytes(&mut initiator_secret); - let initiator_keypair = ed25519::KeyPair::from_secret(initiator_secret, 0); - - let mut responder_secret = [0u8; 32]; - rng.fill_bytes(&mut responder_secret); - let responder_keypair = ed25519::KeyPair::from_secret(responder_secret, 1); - - let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); - let responder_kem_key = EncapsulationKey::X25519(responder_kem_pk); + let (init, resp) = mock_peers(); + let responder_kem_key = resp.encapsulate_kem_key().unwrap(); let ciphersuite = Ciphersuite::resolve_ciphersuite( KEM::X25519, @@ -365,13 +377,18 @@ mod tests { // Use WRONG hash let wrong_hash = [0u8; 32]; - let (context, request_data) = - create_request(ciphersuite, initiator_keypair.private_key()).unwrap(); + let (session_secret, context, request_data) = create_request( + ciphersuite, + init.ed25519.private_key(), + resp.x25519.public_key(), + ) + .unwrap(); let response_data = handle_request( &request_data, - Some(initiator_keypair.public_key()), - responder_keypair.private_key(), + Some(init.ed25519.public_key()), + resp.ed25519.private_key(), + resp.x25519.private_key(), &responder_kem_key, ) .unwrap(); @@ -379,7 +396,8 @@ mod tests { // Client validates with WRONG hash let result = process_response( context, - responder_keypair.public_key(), + &session_secret, + resp.ed25519.public_key(), &wrong_hash, // Wrong! &response_data, ); @@ -399,7 +417,9 @@ mod tests { let mut responder_secret = [0u8; 32]; rng.fill_bytes(&mut responder_secret); - let responder_keypair = ed25519::KeyPair::from_secret(responder_secret, 1); + let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(1)); + + let responder_x25519 = generate_keypair_x25519(&mut rng); let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); let responder_kem_key = EncapsulationKey::X25519(responder_kem_pk); @@ -410,7 +430,8 @@ mod tests { let result = handle_request( &malformed_request, None, - responder_keypair.private_key(), + responder_ed25519_keypair.private_key(), + responder_x25519.private_key(), &responder_kem_key, ); @@ -427,13 +448,11 @@ mod tests { fn test_malformed_response_rejected() { let mut rng = rand09::rng(); - let mut initiator_secret = [0u8; 32]; - rng.fill_bytes(&mut initiator_secret); - let initiator_keypair = ed25519::KeyPair::from_secret(initiator_secret, 0); + // Generate Ed25519 keypairs for both parties + let initiator_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(0)); + let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(1)); - let mut responder_secret = [0u8; 32]; - rng.fill_bytes(&mut responder_secret); - let responder_keypair = ed25519::KeyPair::from_secret(responder_secret, 1); + let responder_x25519 = generate_keypair_x25519(&mut rng); let ciphersuite = Ciphersuite::resolve_ciphersuite( KEM::X25519, @@ -443,8 +462,12 @@ mod tests { ) .unwrap(); - let (context, _request_data) = - create_request(ciphersuite, initiator_keypair.private_key()).unwrap(); + let (session_secret, context, _request_data) = create_request( + ciphersuite, + initiator_ed25519_keypair.private_key(), + responder_x25519.public_key(), + ) + .unwrap(); // Create malformed response data let malformed_response = KKTResponseData(vec![0xFF; 100]); @@ -452,7 +475,8 @@ mod tests { let result = process_response( context, - responder_keypair.public_key(), + &session_secret, + responder_ed25519_keypair.public_key(), &key_hash, &malformed_response, ); diff --git a/common/nym-lp/src/lib.rs b/common/nym-lp/src/lib.rs index 9cedea941a..e1198e7fdc 100644 --- a/common/nym-lp/src/lib.rs +++ b/common/nym-lp/src/lib.rs @@ -4,16 +4,17 @@ pub mod codec; pub mod config; pub mod error; -pub mod keypair; -pub mod kkt_orchestrator; +// pub mod kkt_orchestrator; pub mod message; pub mod noise_protocol; pub mod packet; +pub mod peer; pub mod psk; pub mod replay; pub mod session; mod session_integration; pub mod session_manager; +pub mod state_machine; pub use config::LpConfig; pub use error::LpError; @@ -22,11 +23,6 @@ pub use packet::{BOOTSTRAP_RECEIVER_IDX, LpPacket, OuterHeader}; pub use replay::{ReceivingKeyCounterValidator, ReplayError}; pub use session::{LpSession, generate_fresh_salt}; pub use session_manager::SessionManager; - -// Add the new state machine module -pub mod serialisation; -pub mod state_machine; - pub use state_machine::LpStateMachine; pub const NOISE_PATTERN: &str = "Noise_XKpsk3_25519_ChaChaPoly_SHA256"; @@ -34,50 +30,31 @@ pub const NOISE_PSK_INDEX: u8 = 3; #[cfg(test)] pub fn sessions_for_tests() -> (LpSession, LpSession) { - use crate::keypair::Keypair; - use nym_crypto::asymmetric::ed25519; - - // X25519 keypairs for Noise protocol - let keypair_1 = Keypair::default(); - let keypair_2 = Keypair::default(); + let (init, resp) = crate::peer::mock_peers(); // Use a fixed receiver_index for deterministic tests let receiver_index: u32 = 12345; - // Ed25519 keypairs for PSQ authentication (placeholders for testing) - let ed25519_keypair_1 = ed25519::KeyPair::from_secret([1u8; 32], 0); - let ed25519_keypair_2 = ed25519::KeyPair::from_secret([2u8; 32], 1); - // Use consistent salt for deterministic tests let salt = [1u8; 32]; - // PSQ will always derive the PSK during handshake using X25519 as DHKEM - let initiator_session = LpSession::new( receiver_index, true, - ( - ed25519_keypair_1.private_key(), - ed25519_keypair_1.public_key(), - ), - keypair_1.private_key(), - ed25519_keypair_2.public_key(), - keypair_2.public_key(), + init.clone(), + resp.as_remote(), &salt, + packet::version::CURRENT, ) .expect("Test session creation failed"); let responder_session = LpSession::new( receiver_index, false, - ( - ed25519_keypair_2.private_key(), - ed25519_keypair_2.public_key(), - ), - keypair_2.private_key(), - ed25519_keypair_1.public_key(), - keypair_1.public_key(), + resp, + init.as_remote(), &salt, + packet::version::CURRENT, ) .expect("Test session creation failed"); @@ -87,13 +64,14 @@ pub fn sessions_for_tests() -> (LpSession, LpSession) { #[cfg(test)] mod tests { use crate::message::LpMessage; - use crate::packet::{LpHeader, LpPacket, TRAILER_LEN}; + use crate::packet::{LpHeader, LpPacket, TRAILER_LEN, version}; use crate::session_manager::SessionManager; use crate::{LpError, sessions_for_tests}; use bytes::BytesMut; // Import the new standalone functions use crate::codec::{parse_lp_packet, serialize_lp_packet}; + use crate::peer::mock_peers; #[test] fn test_replay_protection_integration() { @@ -104,7 +82,7 @@ mod tests { let packet1 = LpPacket { header: LpHeader { protocol_version: 1, - reserved: 0, + reserved: [0u8; 3], receiver_idx: 42, // Matches session's sending_index assumption for this test counter: 0, }, @@ -133,7 +111,7 @@ mod tests { let packet2 = LpPacket { header: LpHeader { protocol_version: 1, - reserved: 0, + reserved: [0u8; 3], receiver_idx: 42, counter: 0, // Same counter as before (replay) }, @@ -163,7 +141,7 @@ mod tests { let packet3 = LpPacket { header: LpHeader { protocol_version: 1, - reserved: 0, + reserved: [0u8; 3], receiver_idx: 42, counter: 1, // Incremented counter }, @@ -196,15 +174,12 @@ mod tests { #[test] fn test_session_manager_integration() { - use nym_crypto::asymmetric::ed25519; - // Create session manager let local_manager = SessionManager::new(); let remote_manager = SessionManager::new(); // Generate Ed25519 keypairs for PSQ authentication - let ed25519_keypair_local = ed25519::KeyPair::from_secret([8u8; 32], 0); - let ed25519_keypair_remote = ed25519::KeyPair::from_secret([9u8; 32], 1); + let (init, resp) = mock_peers(); // Use fixed receiver_index for deterministic test let receiver_index: u32 = 54321; @@ -216,33 +191,29 @@ mod tests { let _ = local_manager .create_session_state_machine( receiver_index, - ( - ed25519_keypair_local.private_key(), - ed25519_keypair_local.public_key(), - ), - ed25519_keypair_remote.public_key(), true, + init.clone(), + resp.as_remote(), &salt, + version::CURRENT, ) .unwrap(); let _ = remote_manager .create_session_state_machine( receiver_index, - ( - ed25519_keypair_remote.private_key(), - ed25519_keypair_remote.public_key(), - ), - ed25519_keypair_local.public_key(), false, + resp, + init.as_remote(), &salt, + version::CURRENT, ) .unwrap(); // === Packet 1 (Counter 0 - Should succeed) === let packet1 = LpPacket { header: LpHeader { protocol_version: 1, - reserved: 0, + reserved: [0u8; 3], receiver_idx: receiver_index, counter: 0, }, @@ -275,7 +246,7 @@ mod tests { let packet2 = LpPacket { header: LpHeader { protocol_version: 1, - reserved: 0, + reserved: [0u8; 3], receiver_idx: receiver_index, counter: 1, }, @@ -303,7 +274,7 @@ mod tests { let packet3 = LpPacket { header: LpHeader { protocol_version: 1, - reserved: 0, + reserved: [0u8; 3], receiver_idx: receiver_index, counter: 0, // Replay of first packet }, diff --git a/common/nym-lp/src/message.rs b/common/nym-lp/src/message.rs index 09e31c99e0..0796d0ffcc 100644 --- a/common/nym-lp/src/message.rs +++ b/common/nym-lp/src/message.rs @@ -1,12 +1,15 @@ // Copyright 2025 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::serialisation::{BincodeOptions, lp_bincode_serializer}; +use crate::peer::LpRemotePeer; use crate::{BOOTSTRAP_RECEIVER_IDX, LpError}; use bytes::{BufMut, BytesMut}; use num_enum::{IntoPrimitive, TryFromPrimitive}; +use nym_crypto::asymmetric::{ed25519, x25519}; +use rand::RngCore; use serde::{Deserialize, Serialize}; use std::fmt::{self, Display}; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; /// Data structure for the ClientHello message #[derive(Debug, Clone, Serialize, Deserialize)] @@ -15,16 +18,21 @@ pub struct ClientHelloData { /// Auto-generated randomly by the client pub receiver_index: u32, /// Client's LP x25519 public key (32 bytes) - derived from Ed25519 key - pub client_lp_public_key: [u8; 32], + pub client_lp_public_key: x25519::PublicKey, /// Client's Ed25519 public key (32 bytes) - for PSQ authentication - pub client_ed25519_public_key: [u8; 32], + pub client_ed25519_public_key: ed25519::PublicKey, /// Salt for PSK derivation (32 bytes: 8-byte timestamp + 24-byte nonce) pub salt: [u8; 32], } impl ClientHelloData { + // 4 bytes for receiver index + 32 bytes for client lp key, 32 bytes for client ed25519 key + 32 bytes for salt pub const LEN: usize = 100; + fn len(&self) -> usize { + Self::LEN + } + fn generate_receiver_index() -> u32 { loop { let candidate = rand::random(); @@ -42,8 +50,8 @@ impl ClientHelloData { /// * `client_lp_public_key` - Client's x25519 public key (derived from Ed25519) /// * `client_ed25519_public_key` - Client's Ed25519 public key (for PSQ authentication) pub fn new_with_fresh_salt( - client_lp_public_key: [u8; 32], - client_ed25519_public_key: [u8; 32], + client_lp_public_key: x25519::PublicKey, + client_ed25519_public_key: ed25519::PublicKey, timestamp: u64, ) -> Self { // Generate salt: timestamp + nonce @@ -53,7 +61,6 @@ impl ClientHelloData { salt[..8].copy_from_slice(×tamp.to_le_bytes()); // Last 24 bytes: random nonce - use rand::RngCore; rand::thread_rng().fill_bytes(&mut salt[8..]); Self { @@ -74,14 +81,13 @@ impl ClientHelloData { u64::from_le_bytes(timestamp_bytes) } - pub fn encode(&self) -> Vec { - let mut out = Vec::with_capacity(Self::LEN); - out.put_u32_le(self.receiver_index); - out.put_slice(&self.client_lp_public_key); - out.put_slice(&self.client_ed25519_public_key); - out.put_slice(&self.salt); - out + pub fn encode(&self, dst: &mut BytesMut) { + dst.put_u32_le(self.receiver_index); + dst.put_slice(self.client_lp_public_key.as_bytes()); + dst.put_slice(self.client_ed25519_public_key.as_bytes()); + dst.put_slice(&self.salt); } + pub fn decode(b: &[u8]) -> Result { if b.len() != Self::LEN { return Err(LpError::DeserializationError(format!( @@ -93,13 +99,23 @@ impl ClientHelloData { // SAFETY: we checked for valid byte lengths #[allow(clippy::unwrap_used)] + let client_lp_public_key_bytes = b[4..36].try_into().unwrap(); + let client_ed25519_public_key_bytes = b[36..68].try_into().unwrap(); + Ok(ClientHelloData { receiver_index: u32::from_le_bytes([b[0], b[1], b[2], b[3]]), - client_lp_public_key: b[4..36].try_into().unwrap(), - client_ed25519_public_key: b[36..68].try_into().unwrap(), + client_lp_public_key: x25519::PublicKey::from_byte_array(client_lp_public_key_bytes), + client_ed25519_public_key: ed25519::PublicKey::from_byte_array( + client_ed25519_public_key_bytes, + )?, salt: b[68..].try_into().unwrap(), }) } + + /// Attempt to construct remote peer information based on the data provided in this packet. + pub fn to_remote_peer(&self) -> LpRemotePeer { + LpRemotePeer::new(self.client_ed25519_public_key, self.client_lp_public_key) + } } #[derive(Debug, Copy, Clone, PartialEq, Eq, IntoPrimitive, TryFromPrimitive)] @@ -141,52 +157,275 @@ impl MessageType { #[derive(Debug, Clone, PartialEq, Eq)] pub struct HandshakeData(pub Vec); +impl HandshakeData { + fn len(&self) -> usize { + self.0.len() + } + + fn encode(&self, dst: &mut BytesMut) { + dst.put_slice(&self.0); + } + + fn decode(bytes: &[u8]) -> Result { + Ok(HandshakeData(bytes.to_vec())) + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct EncryptedDataPayload(pub Vec); +impl EncryptedDataPayload { + fn len(&self) -> usize { + self.0.len() + } + + fn encode(&self, dst: &mut BytesMut) { + dst.put_slice(&self.0); + } + + fn decode(bytes: &[u8]) -> Result { + Ok(EncryptedDataPayload(bytes.to_vec())) + } +} + /// KKT request frame data (serialized KKTFrame bytes) #[derive(Debug, Clone, PartialEq, Eq)] pub struct KKTRequestData(pub Vec); +impl KKTRequestData { + fn len(&self) -> usize { + self.0.len() + } + + fn encode(&self, dst: &mut BytesMut) { + dst.put_slice(&self.0); + } + + fn decode(bytes: &[u8]) -> Result { + Ok(KKTRequestData(bytes.to_vec())) + } +} + /// KKT response frame data (serialized KKTFrame bytes) #[derive(Debug, Clone, PartialEq, Eq)] pub struct KKTResponseData(pub Vec); +impl KKTResponseData { + fn len(&self) -> usize { + self.0.len() + } + + fn encode(&self, dst: &mut BytesMut) { + dst.put_slice(&self.0); + } + + fn decode(bytes: &[u8]) -> Result { + Ok(KKTResponseData(bytes.to_vec())) + } +} + /// Packet forwarding request with embedded inner LP packet -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone)] pub struct ForwardPacketData { /// Target gateway's Ed25519 identity (32 bytes) pub target_gateway_identity: [u8; 32], /// Target gateway's LP address (IP:port string) - pub target_lp_address: String, + pub target_lp_address: SocketAddr, /// Complete inner LP packet bytes (serialized LpPacket) /// This is the CLIENT→EXIT gateway packet, encrypted for exit pub inner_packet_bytes: Vec, } +impl ForwardPacketData { + pub fn new( + target_gateway_identity: ed25519::PublicKey, + target_lp_address: SocketAddr, + inner_packet_bytes: Vec, + ) -> Self { + ForwardPacketData { + target_gateway_identity: target_gateway_identity.to_bytes(), + target_lp_address, + inner_packet_bytes, + } + } + + fn len(&self) -> usize { + // 32 bytes target gateway identity + // + + // 1 byte length of target lp address type + // + + // {4,16} target_lp_address IPv{4,6} + // + + // 2 bytes target_lp_address port + // + + // 4 bytes of length of inner packet bytes + // + + // inner_packet_bytes.len() + match self.target_lp_address { + SocketAddr::V4(_) => 32 + 1 + 4 + 2 + 4 + self.inner_packet_bytes.len(), + SocketAddr::V6(_) => 32 + 1 + 16 + 2 + 4 + self.inner_packet_bytes.len(), + } + } + + fn encode(&self, dst: &mut BytesMut) { + let (is_ipv6, ip_bytes) = match &self.target_lp_address { + SocketAddr::V4(address) => (false, address.ip().octets().to_vec()), + SocketAddr::V6(address) => (true, address.ip().octets().to_vec()), + }; + + dst.put_slice(&self.target_gateway_identity); + dst.put_u8(is_ipv6 as u8); // IP type , 0 for ipv4 + dst.put_slice(&ip_bytes); // IP bytes + dst.put_u16_le(self.target_lp_address.port()); // Port + dst.put_u32_le(self.inner_packet_bytes.len() as u32); + dst.put_slice(&self.inner_packet_bytes); + } + + pub fn to_bytes(&self) -> Vec { + let mut buf = BytesMut::new(); + self.encode(&mut buf); + buf.into() + } + + pub fn decode(bytes: &[u8]) -> Result { + // smallest possible packet with ipv4 and empty data + if bytes.len() < 43 { + // 32 + 1 + 4 + 2 + 4 + 0 + return Err(LpError::DeserializationError(format!( + "Too few bytes to deserialise ForwardPacketData. got {}", + bytes.len() + ))); + } + // SAFETY: we ensured we have sufficient data + #[allow(clippy::unwrap_used)] + let target_gateway_identity = bytes[0..32].try_into().unwrap(); + let target_lp_address_is_ipv6 = bytes[32] != 0; + + let (target_lp_address, next_index) = if target_lp_address_is_ipv6 { + // IPv6, first check we have actually enough bytes + // smallest possible packet with ipv6 and empty data + if bytes.len() < 55 { + // 32 + 1 + 16 + 2 + 4 + 0 + return Err(LpError::DeserializationError(format!( + "Too few bytes to deserialise ipv6 ForwardPacketData. got {}", + bytes.len() + ))); + } + // SAFETY: we ensured we have sufficient data, and the length is correct for casting + #[allow(clippy::unwrap_used)] + let ipv6 = IpAddr::V6(Ipv6Addr::from_octets(bytes[33..49].try_into().unwrap())); + let port = u16::from_le_bytes([bytes[49], bytes[50]]); + (SocketAddr::new(ipv6, port), 51) + } else { + // IPv4. Length check done at the start + // SAFETY: we ensured we have sufficient data, and the length is correct for casting + #[allow(clippy::unwrap_used)] + let ipv4 = IpAddr::V4(Ipv4Addr::from_octets(bytes[33..37].try_into().unwrap())); + let port = u16::from_le_bytes([bytes[37], bytes[38]]); + (SocketAddr::new(ipv4, port), 39) + }; + + let inner_packet_bytes_len = u32::from_le_bytes([ + bytes[next_index], + bytes[next_index + 1], + bytes[next_index + 2], + bytes[next_index + 3], + ]); + if bytes[next_index + 4..].len() != inner_packet_bytes_len as usize { + return Err(LpError::DeserializationError(format!( + "Expected {inner_packet_bytes_len} bytes to deserialise inner packet bytes of ForwardPacketData. got {}", + bytes[next_index + 4..].len() + ))); + } + let inner_packet_bytes = bytes[next_index + 4..].to_vec(); + + Ok(ForwardPacketData { + target_gateway_identity, + target_lp_address, + inner_packet_bytes, + }) + } +} + /// Subsession KK1 message - first message of Noise KK handshake -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct SubsessionKK1Data { /// Noise KK first message payload (ephemeral key + encrypted static) pub payload: Vec, } +impl SubsessionKK1Data { + fn len(&self) -> usize { + self.payload.len() + } + + fn encode(&self, dst: &mut BytesMut) { + dst.put_slice(&self.payload); + } + + fn decode(bytes: &[u8]) -> Result { + Ok(SubsessionKK1Data { + payload: bytes.to_vec(), + }) + } +} + /// Subsession KK2 message - second message of Noise KK handshake -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct SubsessionKK2Data { /// Noise KK second message payload (ephemeral key + encrypted response) pub payload: Vec, } +impl SubsessionKK2Data { + fn len(&self) -> usize { + self.payload.len() + } + + fn encode(&self, dst: &mut BytesMut) { + dst.put_slice(&self.payload); + } + + fn decode(bytes: &[u8]) -> Result { + Ok(SubsessionKK2Data { + payload: bytes.to_vec(), + }) + } +} + /// Subsession ready confirmation with new session index -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct SubsessionReadyData { /// New subsession's receiver index for routing pub receiver_index: u32, } +impl SubsessionReadyData { + pub const LEN: usize = 4; + + fn len(&self) -> usize { + Self::LEN + } + + fn encode(&self, dst: &mut BytesMut) { + dst.put_u32_le(self.receiver_index); + } + + fn decode(bytes: &[u8]) -> Result { + if bytes.len() != 4 { + return Err(LpError::DeserializationError(format!( + "Expected 4 bytes to deserialise SubsessionReadyData. got {}", + bytes.len() + ))); + } + Ok(SubsessionReadyData { + receiver_index: u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]), + }) + } +} + #[derive(Debug, Clone)] pub enum LpMessage { Busy, @@ -275,23 +514,18 @@ impl LpMessage { pub fn len(&self) -> usize { match self { LpMessage::Busy => 0, - LpMessage::Handshake(payload) => payload.0.len(), - LpMessage::EncryptedData(payload) => payload.0.len(), - // 4 bytes receiver_index + 32 bytes x25519 key + 32 bytes ed25519 key + 32 bytes salt - LpMessage::ClientHello(_) => ClientHelloData::LEN, - LpMessage::KKTRequest(payload) => payload.0.len(), - LpMessage::KKTResponse(payload) => payload.0.len(), - LpMessage::ForwardPacket(data) => { - 32 + data.target_lp_address.len() + data.inner_packet_bytes.len() + 10 - } + LpMessage::Handshake(payload) => payload.len(), + LpMessage::EncryptedData(payload) => payload.len(), + LpMessage::ClientHello(payload) => payload.len(), + LpMessage::KKTRequest(payload) => payload.len(), + LpMessage::KKTResponse(payload) => payload.len(), + LpMessage::ForwardPacket(payload) => payload.len(), LpMessage::Collision => 0, LpMessage::Ack => 0, LpMessage::SubsessionRequest => 0, - // Variable length: bincode overhead (~8 bytes for Vec length) + payload - LpMessage::SubsessionKK1(data) => 8 + data.payload.len(), - LpMessage::SubsessionKK2(data) => 8 + data.payload.len(), - // 4 bytes u32 + bincode overhead (~4 bytes) - LpMessage::SubsessionReady(_) => 8, + LpMessage::SubsessionKK1(payload) => payload.len(), + LpMessage::SubsessionKK2(payload) => payload.len(), + LpMessage::SubsessionReady(payload) => payload.len(), LpMessage::SubsessionAbort => 0, } } @@ -318,51 +552,90 @@ impl LpMessage { pub fn encode_content(&self, dst: &mut BytesMut) { match self { LpMessage::Busy => { /* No content */ } - LpMessage::Handshake(payload) => { - dst.put_slice(&payload.0); - } - LpMessage::EncryptedData(payload) => { - dst.put_slice(&payload.0); - } - LpMessage::ClientHello(data) => { - dst.put_slice(&data.encode()); - } - LpMessage::KKTRequest(payload) => { - dst.put_slice(&payload.0); - } - LpMessage::KKTResponse(payload) => { - dst.put_slice(&payload.0); - } - LpMessage::ForwardPacket(data) => { - let serialized = lp_bincode_serializer() - .serialize(data) - .expect("Failed to serialize ForwardPacketData"); - dst.put_slice(&serialized); - } + LpMessage::Handshake(payload) => payload.encode(dst), + LpMessage::EncryptedData(payload) => payload.encode(dst), + LpMessage::ClientHello(data) => data.encode(dst), + LpMessage::KKTRequest(payload) => payload.encode(dst), + LpMessage::KKTResponse(payload) => payload.encode(dst), + LpMessage::ForwardPacket(data) => data.encode(dst), LpMessage::Collision => { /* No content */ } LpMessage::Ack => { /* No content */ } LpMessage::SubsessionRequest => { /* No content - signal only */ } - LpMessage::SubsessionKK1(data) => { - let serialized = lp_bincode_serializer() - .serialize(data) - .expect("Failed to serialize SubsessionKK1Data"); - dst.put_slice(&serialized); - } - LpMessage::SubsessionKK2(data) => { - let serialized = lp_bincode_serializer() - .serialize(data) - .expect("Failed to serialize SubsessionKK2Data"); - dst.put_slice(&serialized); - } - LpMessage::SubsessionReady(data) => { - let serialized = lp_bincode_serializer() - .serialize(data) - .expect("Failed to serialize SubsessionReadyData"); - dst.put_slice(&serialized); - } + LpMessage::SubsessionKK1(data) => data.encode(dst), + LpMessage::SubsessionKK2(data) => data.encode(dst), + LpMessage::SubsessionReady(data) => data.encode(dst), LpMessage::SubsessionAbort => { /* No content - signal only */ } } } + + /// Parse message from its type and content bytes. + /// + /// Used when decrypting outer-encrypted packets where the message type + /// was encrypted along with the content. + pub fn decode_content(content: &[u8], message_type: MessageType) -> Result { + match message_type { + MessageType::Busy => { + content.ensure_empty()?; + Ok(LpMessage::Busy) + } + MessageType::Handshake => Ok(LpMessage::Handshake(HandshakeData::decode(content)?)), + MessageType::EncryptedData => Ok(LpMessage::EncryptedData( + EncryptedDataPayload::decode(content)?, + )), + MessageType::ClientHello => { + Ok(LpMessage::ClientHello(ClientHelloData::decode(content)?)) + } + MessageType::KKTRequest => Ok(LpMessage::KKTRequest(KKTRequestData::decode(content)?)), + MessageType::KKTResponse => { + Ok(LpMessage::KKTResponse(KKTResponseData::decode(content)?)) + } + MessageType::ForwardPacket => Ok(LpMessage::ForwardPacket(ForwardPacketData::decode( + content, + )?)), + MessageType::Collision => { + content.ensure_empty()?; + Ok(LpMessage::Collision) + } + MessageType::Ack => { + content.ensure_empty()?; + Ok(LpMessage::Ack) + } + MessageType::SubsessionRequest => { + content.ensure_empty()?; + Ok(LpMessage::SubsessionRequest) + } + MessageType::SubsessionKK1 => Ok(LpMessage::SubsessionKK1(SubsessionKK1Data::decode( + content, + )?)), + MessageType::SubsessionKK2 => Ok(LpMessage::SubsessionKK2(SubsessionKK2Data::decode( + content, + )?)), + MessageType::SubsessionReady => Ok(LpMessage::SubsessionReady( + SubsessionReadyData::decode(content)?, + )), + MessageType::SubsessionAbort => { + content.ensure_empty()?; + Ok(LpMessage::SubsessionAbort) + } + } + } +} + +/// Helper trait for improving readability to return error if bytes content is not empty +trait EnsureEmptyContent { + fn ensure_empty(&self) -> Result<(), LpError>; +} + +impl EnsureEmptyContent for &[u8] { + fn ensure_empty(&self) -> Result<(), LpError> { + if !self.is_empty() { + return Err(LpError::InvalidPayloadSize { + expected: 0, + actual: self.len(), + }); + } + Ok(()) + } } #[cfg(test)] @@ -379,7 +652,7 @@ mod tests { let resp_header = LpHeader { protocol_version: 1, - reserved: 0, + reserved: [0u8; 3], receiver_idx: 0, counter: 0, }; @@ -411,8 +684,13 @@ mod tests { .duration_since(UNIX_EPOCH) .expect("System time before UNIX epoch") .as_secs(); - let client_key = [1u8; 32]; - let client_ed25519_key = [2u8; 32]; + + let mut rng = rand::thread_rng(); + let ed25519 = ed25519::KeyPair::new(&mut rng); + let x25519 = ed25519.to_x25519(); + + let client_key = *x25519.public_key(); + let client_ed25519_key = *ed25519.public_key(); let hello1 = ClientHelloData::new_with_fresh_salt(client_key, client_ed25519_key, timestamp); let hello2 = @@ -433,8 +711,12 @@ mod tests { .duration_since(UNIX_EPOCH) .expect("System time before UNIX epoch") .as_secs(); - let client_key = [2u8; 32]; - let client_ed25519_key = [3u8; 32]; + let mut rng = rand::thread_rng(); + let ed25519 = ed25519::KeyPair::new(&mut rng); + let x25519 = ed25519.to_x25519(); + + let client_key = *x25519.public_key(); + let client_ed25519_key = *ed25519.public_key(); let hello = ClientHelloData::new_with_fresh_salt(client_key, client_ed25519_key, timestamp); let timestamp = hello.extract_timestamp(); @@ -453,8 +735,12 @@ mod tests { .duration_since(UNIX_EPOCH) .expect("System time before UNIX epoch") .as_secs(); - let client_key = [3u8; 32]; - let client_ed25519_key = [4u8; 32]; + let mut rng = rand::thread_rng(); + let ed25519 = ed25519::KeyPair::new(&mut rng); + let x25519 = ed25519.to_x25519(); + + let client_key = *x25519.public_key(); + let client_ed25519_key = *ed25519.public_key(); let hello = ClientHelloData::new_with_fresh_salt(client_key, client_ed25519_key, timestamp); // First 8 bytes should be non-zero timestamp diff --git a/common/nym-lp/src/noise_protocol.rs b/common/nym-lp/src/noise_protocol.rs index 41e601494b..293bd9ccd4 100644 --- a/common/nym-lp/src/noise_protocol.rs +++ b/common/nym-lp/src/noise_protocol.rs @@ -294,8 +294,8 @@ pub fn create_noise_state( remote_public_key: &[u8], psk: &[u8], ) -> Result { - let pattern_name = "Noise_XKpsk3_25519_ChaChaPoly_SHA256"; - let psk_index = 3; + let pattern_name = crate::NOISE_PATTERN; + let psk_index = crate::NOISE_PSK_INDEX; let noise_params: NoiseParams = pattern_name.parse().unwrap(); let builder = snow::Builder::new(noise_params.clone()); @@ -314,8 +314,8 @@ pub fn create_noise_state_responder( remote_public_key: &[u8], psk: &[u8], ) -> Result { - let pattern_name = "Noise_XKpsk3_25519_ChaChaPoly_SHA256"; - let psk_index = 3; + let pattern_name = crate::NOISE_PATTERN; + let psk_index = crate::NOISE_PSK_INDEX; let noise_params: NoiseParams = pattern_name.parse().unwrap(); let builder = snow::Builder::new(noise_params.clone()); diff --git a/common/nym-lp/src/packet.rs b/common/nym-lp/src/packet.rs index 0bcc5fe7e0..e93edfaad4 100644 --- a/common/nym-lp/src/packet.rs +++ b/common/nym-lp/src/packet.rs @@ -10,6 +10,7 @@ use parking_lot::Mutex; use std::fmt::Write; use std::fmt::{Debug, Formatter}; use std::sync::Arc; +use tracing::warn; #[allow(dead_code)] pub(crate) const UDP_HEADER_LEN: usize = 8; @@ -25,6 +26,11 @@ pub const TRAILER_LEN: usize = 16; #[allow(dead_code)] pub(crate) const UDP_PAYLOAD_SIZE: usize = MTU - UDP_OVERHEAD - TRAILER_LEN; +pub mod version { + /// The current version of the Lewes Protocol that is put into each new constructed header. + pub const CURRENT: u8 = 1; +} + #[derive(Clone)] pub struct LpPacket { pub(crate) header: LpHeader, @@ -187,7 +193,7 @@ impl OuterHeader { #[derive(Debug, Clone)] pub struct LpHeader { pub protocol_version: u8, - pub reserved: u16, + pub reserved: [u8; 3], pub receiver_idx: u32, pub counter: u64, } @@ -197,10 +203,10 @@ impl LpHeader { } impl LpHeader { - pub fn new(receiver_idx: u32, counter: u64) -> Self { + pub fn new(receiver_idx: u32, counter: u64, protocol_version: u8) -> Self { Self { - protocol_version: 1, - reserved: 0, + protocol_version, + reserved: [0u8; 3], receiver_idx, counter, } @@ -211,7 +217,7 @@ impl LpHeader { dst.put_u8(self.protocol_version); // reserved - dst.put_slice(&[0, 0, 0]); + dst.put_slice(&self.reserved); // sender index dst.put_slice(&self.receiver_idx.to_le_bytes()); @@ -226,7 +232,28 @@ impl LpHeader { } let protocol_version = src[0]; - // Skip reserved bytes [1..4] + + // Ensure we are using compatible protocol + // right now only support a single version + if protocol_version > version::CURRENT { + return Err(LpError::IncompatibleFuturePacketVersion { + got: protocol_version, + highest_supported: version::CURRENT, + }); + } + + if protocol_version < version::CURRENT { + return Err(LpError::IncompatibleLegacyPacketVersion { + got: protocol_version, + lowest_supported: version::CURRENT, + }); + } + + // skip reserved bytes, but log if they're different from the expected zeroes + let reserved = [src[1], src[2], src[3]]; + if reserved != [0u8; 3] { + warn!("received non-zero reserved bytes. got: {reserved:?}"); + } let mut receiver_idx_bytes = [0u8; 4]; receiver_idx_bytes.copy_from_slice(&src[4..8]); @@ -238,7 +265,7 @@ impl LpHeader { Ok(LpHeader { protocol_version, - reserved: 0, + reserved, receiver_idx, counter, }) diff --git a/common/nym-lp/src/peer.rs b/common/nym-lp/src/peer.rs new file mode 100644 index 0000000000..fd6e5ab7f3 --- /dev/null +++ b/common/nym-lp/src/peer.rs @@ -0,0 +1,168 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::ClientHelloData; +use nym_crypto::asymmetric::{ed25519, x25519}; +use nym_kkt::ciphersuite::{KEM, KEMKeyDigests, SignatureScheme, SigningKeyDigests}; +use std::collections::HashMap; +use std::sync::Arc; + +/// Representation of a local Lewes Protocol peer +/// encapsulating all the known information and keys. +#[derive(Debug, Clone)] +pub struct LpLocalPeer { + /// Local Ed25519 keys for PSQ authentication + pub(crate) ed25519: Arc, + + /// Local x25519 keys (Noise static key) + pub(crate) x25519: Arc, + + /// Local KEM key used for PSQ + pub(crate) kem_psq: Option>, +} + +impl LpLocalPeer { + pub fn new(ed25519: Arc, x25519: Arc) -> Self { + LpLocalPeer { + ed25519, + x25519, + kem_psq: None, + } + } + + pub fn build_client_hello_data(&self, timestamp: u64) -> ClientHelloData { + ClientHelloData::new_with_fresh_salt( + *self.x25519().public_key(), + *self.ed25519().public_key(), + timestamp, + ) + } + + #[must_use] + pub fn with_kem_psq_key(mut self, key: Arc) -> Self { + self.kem_psq = Some(key); + self + } + + pub fn ed25519(&self) -> &Arc { + &self.ed25519 + } + + pub fn x25519(&self) -> &Arc { + &self.x25519 + } + + /// Convert this `LpLocalPeer` into a valid `LpRemotePeer` that can be used within tests + #[doc(hidden)] + pub fn as_remote(&self) -> LpRemotePeer { + let expected_kem_key_digests = match &self.kem_psq { + None => HashMap::new(), + Some(kem_keys) => { + let mut digests = HashMap::new(); + digests.insert( + KEM::X25519, + nym_kkt::key_utils::produce_key_digests(kem_keys.public_key().as_bytes()), + ); + digests + } + }; + + let mut expected_signing_key_digests = HashMap::new(); + expected_signing_key_digests.insert( + SignatureScheme::Ed25519, + nym_kkt::key_utils::produce_key_digests(self.ed25519.public_key().as_bytes()), + ); + + LpRemotePeer { + ed25519_public: *self.ed25519.public_key(), + x25519_public: *self.x25519.public_key(), + expected_kem_key_digests, + expected_signing_key_digests, + } + } + + // this is only exposed in tests as ideally we should be storing the proper types to begin with + #[cfg(test)] + pub fn encapsulate_kem_key(&self) -> Option> { + let pk_bytes = self.kem_psq.as_ref()?.public_key().to_bytes(); + let libcrux_pk = + libcrux_kem::PublicKey::decode(libcrux_kem::Algorithm::X25519, &pk_bytes).ok()?; + + Some(nym_kkt::ciphersuite::EncapsulationKey::X25519(libcrux_pk)) + } +} + +/// Representation of a remote Lewes Protocol peer +/// encapsulating all the known information and keys. +#[derive(Debug, Clone)] +pub struct LpRemotePeer { + /// Remote Ed25519 public key for PSQ authentication + pub(crate) ed25519_public: ed25519::PublicKey, + + /// Remote X25519 public key (Noise static key) + pub(crate) x25519_public: x25519::PublicKey, + + /// Expected digests of the remote's KEM key + pub(crate) expected_kem_key_digests: HashMap, + + /// Expected digests of the remote's signing key + pub(crate) expected_signing_key_digests: HashMap, +} + +impl LpRemotePeer { + pub fn new(ed25519_public: ed25519::PublicKey, x25519_public: x25519::PublicKey) -> Self { + LpRemotePeer { + ed25519_public, + x25519_public, + expected_kem_key_digests: Default::default(), + expected_signing_key_digests: Default::default(), + } + } + + pub fn ed25519(&self) -> ed25519::PublicKey { + self.ed25519_public + } + + pub fn x25519(&self) -> x25519::PublicKey { + self.x25519_public + } + + #[must_use] + pub fn with_key_digests( + mut self, + expected_kem_key_digests: HashMap, + expected_signing_key_digests: HashMap, + ) -> Self { + self.expected_kem_key_digests = expected_kem_key_digests; + self.expected_signing_key_digests = expected_signing_key_digests; + self + } +} + +#[cfg(test)] +pub fn mock_peer() -> LpLocalPeer { + // use deterministic rng + let mut rng = nym_test_utils::helpers::deterministic_rng(); + random_peer(&mut rng) +} + +#[cfg(test)] +pub fn random_peer(rng: &mut R) -> LpLocalPeer { + let ed25519 = Arc::new(ed25519::KeyPair::new(rng)); + let x25519 = Arc::new(ed25519.to_x25519()); + let kem_psq = Some(x25519.clone()); + + LpLocalPeer { + ed25519, + x25519, + kem_psq, + } +} + +#[cfg(test)] +pub fn mock_peers() -> (LpLocalPeer, LpLocalPeer) { + // use deterministic rng + let mut rng = nym_test_utils::helpers::deterministic_rng(); + + (random_peer(&mut rng), random_peer(&mut rng)) +} diff --git a/common/nym-lp/src/psk.rs b/common/nym-lp/src/psk.rs index ea48db9415..45c352f490 100644 --- a/common/nym-lp/src/psk.rs +++ b/common/nym-lp/src/psk.rs @@ -47,12 +47,11 @@ //! - **No cleanup needed**: No state was mutated use crate::LpError; -use crate::keypair::{PrivateKey, PublicKey}; use libcrux_psq::v1::cred::{Authenticator, Ed25519}; use libcrux_psq::v1::impls::X25519 as PsqX25519; use libcrux_psq::v1::psk_registration::{Initiator, InitiatorMsg, Responder}; use libcrux_psq::v1::traits::{Ciphertext as PsqCiphertext, PSQ}; -use nym_crypto::asymmetric::ed25519; +use nym_crypto::asymmetric::{ed25519, x25519}; use nym_kkt::ciphersuite::{DecapsulationKey, EncapsulationKey}; use std::time::Duration; use tls_codec::{Deserialize as TlsDeserializeTrait, Serialize as TlsSerializeTrait}; @@ -137,8 +136,8 @@ pub struct PsqResponderResult { /// // Send ciphertext to gateway /// ``` pub fn derive_psk_with_psq_initiator( - local_x25519_private: &PrivateKey, - remote_x25519_public: &PublicKey, + local_x25519_private: &x25519::PrivateKey, + remote_x25519_public: &x25519::PublicKey, remote_kem_public: &EncapsulationKey, salt: &[u8; 32], ) -> Result<([u8; 32], Vec), LpError> { @@ -168,7 +167,7 @@ pub fn derive_psk_with_psq_initiator( // Step 3: Combine ECDH + PSQ via Blake3 KDF let mut combined = Vec::with_capacity(64 + psq_psk.len()); - combined.extend_from_slice(ecdh_secret.as_bytes()); + combined.extend_from_slice(&ecdh_secret); combined.extend_from_slice(&psq_psk); // psq_psk is [u8; 32], need & combined.extend_from_slice(salt); @@ -220,8 +219,8 @@ pub fn derive_psk_with_psq_initiator( /// )?; /// ``` pub fn derive_psk_with_psq_responder( - local_x25519_private: &PrivateKey, - remote_x25519_public: &PublicKey, + local_x25519_private: &x25519::PrivateKey, + remote_x25519_public: &x25519::PublicKey, local_kem_keypair: (&DecapsulationKey, &EncapsulationKey), ciphertext: &[u8], salt: &[u8; 32], @@ -249,7 +248,7 @@ pub fn derive_psk_with_psq_responder( // Step 5: Combine ECDH + PSQ via Blake3 KDF (same formula as initiator) let mut combined = Vec::with_capacity(64 + psq_psk.len()); - combined.extend_from_slice(ecdh_secret.as_bytes()); + combined.extend_from_slice(&ecdh_secret); combined.extend_from_slice(&psq_psk); // psq_psk is [u8; 32], need & combined.extend_from_slice(salt); @@ -280,8 +279,8 @@ pub fn derive_psk_with_psq_responder( /// # Returns /// `PsqInitiatorResult` containing PSK, payload, and raw PQ shared secret pub fn psq_initiator_create_message( - local_x25519_private: &PrivateKey, - remote_x25519_public: &PublicKey, + local_x25519_private: &x25519::PrivateKey, + remote_x25519_public: &x25519::PublicKey, remote_kem_public: &EncapsulationKey, client_ed25519_sk: &ed25519::PrivateKey, client_ed25519_pk: &ed25519::PublicKey, @@ -335,7 +334,7 @@ pub fn psq_initiator_create_message( // Step 3: Combine ECDH + PSQ via Blake3 KDF let mut combined = Vec::with_capacity(64 + psq_psk.len()); - combined.extend_from_slice(ecdh_secret.as_bytes()); + combined.extend_from_slice(&ecdh_secret); combined.extend_from_slice(psq_psk); // psq_psk is already a &[u8; 32] combined.extend_from_slice(salt); @@ -375,8 +374,8 @@ pub fn psq_initiator_create_message( /// # Returns /// `PsqResponderResult` containing PSK, PSK handle, and raw PQ shared secret pub fn psq_responder_process_message( - local_x25519_private: &PrivateKey, - remote_x25519_public: &PublicKey, + local_x25519_private: &x25519::PrivateKey, + remote_x25519_public: &x25519::PublicKey, local_kem_keypair: (&DecapsulationKey, &EncapsulationKey), initiator_ed25519_pk: &ed25519::PublicKey, psq_payload: &[u8], @@ -444,7 +443,7 @@ pub fn psq_responder_process_message( // Step 6: Combine ECDH + PSQ via Blake3 KDF (same formula as initiator) let mut combined = Vec::with_capacity(64 + psq_psk.len()); - combined.extend_from_slice(ecdh_secret.as_bytes()); + combined.extend_from_slice(&ecdh_secret); combined.extend_from_slice(&psq_psk); // psq_psk is [u8; 32], need & combined.extend_from_slice(salt); @@ -493,12 +492,16 @@ pub fn derive_subsession_psk(pq_shared_secret: &[u8; 32], subsession_index: u64) #[cfg(test)] mod tests { use super::*; - use crate::keypair::Keypair; + use rand::thread_rng; + + fn generate_x25519_keypair() -> x25519::KeyPair { + x25519::KeyPair::new(&mut thread_rng()) + } #[test] fn test_psk_derivation_is_symmetric() { - let keypair_1 = Keypair::default(); - let keypair_2 = Keypair::default(); + let keypair_1 = generate_x25519_keypair(); + let keypair_2 = generate_x25519_keypair(); let salt = [2u8; 32]; let mut rng = &mut rand09::rng(); @@ -533,8 +536,8 @@ mod tests { #[test] fn test_different_salts_produce_different_psks() { - let keypair_1 = Keypair::default(); - let keypair_2 = Keypair::default(); + let keypair_1 = generate_x25519_keypair(); + let keypair_2 = generate_x25519_keypair(); let salt1 = [1u8; 32]; let salt2 = [2u8; 32]; @@ -562,9 +565,9 @@ mod tests { #[test] fn test_different_keys_produce_different_psks() { - let keypair_1 = Keypair::default(); - let keypair_2 = Keypair::default(); - let keypair_3 = Keypair::default(); + let keypair_1 = generate_x25519_keypair(); + let keypair_2 = generate_x25519_keypair(); + let keypair_3 = generate_x25519_keypair(); let salt = [3u8; 32]; let mut rng = &mut rand09::rng(); @@ -601,8 +604,8 @@ mod tests { let mut rng = rand09::rng(); // Generate X25519 keypairs for Noise - let client_keypair = Keypair::default(); - let gateway_keypair = Keypair::default(); + let client_keypair = generate_x25519_keypair(); + let gateway_keypair = generate_x25519_keypair(); // Generate KEM keypair for PSQ let (kem_sk, kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); @@ -659,8 +662,8 @@ mod tests { let mut rng = rand09::rng(); // Generate X25519 keypairs for Noise - let client_keypair = Keypair::default(); - let gateway_keypair = Keypair::default(); + let client_keypair = generate_x25519_keypair(); + let gateway_keypair = generate_x25519_keypair(); // Generate KEM keypair for PSQ let (kem_sk, kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); @@ -698,8 +701,8 @@ mod tests { fn test_different_kem_keys_different_psk() { let mut rng = rand09::rng(); - let client_keypair = Keypair::default(); - let gateway_keypair = Keypair::default(); + let client_keypair = generate_x25519_keypair(); + let gateway_keypair = generate_x25519_keypair(); // Two different KEM keypairs let (_, kem_pk1) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); @@ -736,8 +739,8 @@ mod tests { fn test_psq_psk_output_length() { let mut rng = rand09::rng(); - let client_keypair = Keypair::default(); - let gateway_keypair = Keypair::default(); + let client_keypair = generate_x25519_keypair(); + let gateway_keypair = generate_x25519_keypair(); let (_, kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); let enc_key = EncapsulationKey::X25519(kem_pk); @@ -759,8 +762,8 @@ mod tests { fn test_psq_different_salts_different_psks() { let mut rng = rand09::rng(); - let client_keypair = Keypair::default(); - let gateway_keypair = Keypair::default(); + let client_keypair = generate_x25519_keypair(); + let gateway_keypair = generate_x25519_keypair(); let (_, kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); let enc_key = EncapsulationKey::X25519(kem_pk); diff --git a/common/nym-lp/src/session.rs b/common/nym-lp/src/session.rs index 9275e9be95..0b8cee57a3 100644 --- a/common/nym-lp/src/session.rs +++ b/common/nym-lp/src/session.rs @@ -7,20 +7,33 @@ //! and Noise protocol state handling. use crate::codec::OuterAeadKey; -use crate::keypair::{PrivateKey, PublicKey}; use crate::message::{EncryptedDataPayload, HandshakeData}; +// noiserm use crate::noise_protocol::{NoiseError, NoiseProtocol, ReadResult}; use crate::packet::LpHeader; +use crate::peer::{LpLocalPeer, LpRemotePeer}; use crate::psk::{ derive_subsession_psk, psq_initiator_create_message, psq_responder_process_message, }; use crate::replay::ReceivingKeyCounterValidator; use crate::{LpError, LpMessage, LpPacket}; -use nym_crypto::asymmetric::ed25519; +use nym_crypto::asymmetric::{ed25519, x25519}; +use nym_kkt::KKT_RESPONSE_AAD; use nym_kkt::ciphersuite::{DecapsulationKey, EncapsulationKey}; +use nym_kkt::context::KKTContext; +use nym_kkt::encryption::{ + KKTSessionSecret, decrypt_initial_kkt_frame, decrypt_kkt_frame, encrypt_initial_kkt_frame, + encrypt_kkt_frame, +}; +use nym_kkt::session::{ + anonymous_initiator_process, initiator_ingest_response, responder_ingest_message, + responder_process, +}; use parking_lot::Mutex; +use rand::RngCore; use snow::Builder; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; +use std::time::{SystemTime, UNIX_EPOCH}; use zeroize::{Zeroize, ZeroizeOnDrop}; /// PQ shared secret wrapper with automatic memory zeroization. @@ -71,6 +84,7 @@ pub enum KKTState { InitiatorWaiting { /// KKT context for verifying the response context: nym_kkt::context::KKTContext, + session_secret: KKTSessionSecret, }, /// KKT exchange completed (initiator received and validated KEM key). @@ -88,7 +102,7 @@ impl std::fmt::Debug for KKTState { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::NotStarted => write!(f, "KKTState::NotStarted"), - Self::InitiatorWaiting { context } => f + Self::InitiatorWaiting { context, .. } => f .debug_struct("KKTState::InitiatorWaiting") .field("context", context) .finish(), @@ -119,7 +133,7 @@ pub enum PSQState { NotStarted, /// Initiator has sent PSQ ciphertext and is waiting for confirmation. - /// PSK is already derived but we don't encrypt outgoing packets yet + /// PSK is already derived, but we don't encrypt outgoing packets yet /// because the responder may not have processed our message yet. InitiatorWaiting { /// The derived PSK, stored until we transition to Completed @@ -156,6 +170,7 @@ pub struct LpSession { /// Flag indicating if this session acts as the Noise protocol initiator. is_initiator: bool, + // noiserm /// Noise protocol state machine noise_state: Mutex, @@ -174,25 +189,18 @@ pub struct LpSession { /// Validator for incoming packet counters to prevent replay attacks receiving_counter: Mutex, + // noiserm /// Safety flag: `true` if real PSK was injected via PSQ, `false` if still using dummy PSK. /// This prevents transport mode operations from running with the insecure dummy `[0u8; 32]` PSK. psk_injected: AtomicBool, - // PSQ-related keys stored for handshake - /// Local Ed25519 private key for PSQ authentication - local_ed25519_private: ed25519::PrivateKey, + /// Representation of a local Lewes Protocol peer + /// encapsulating all the known information and keys. + local_peer: LpLocalPeer, - /// Local Ed25519 public key for PSQ authentication - local_ed25519_public: ed25519::PublicKey, - - /// Remote Ed25519 public key for PSQ authentication - remote_ed25519_public: ed25519::PublicKey, - - /// Local X25519 private key (Noise static key) - local_x25519_private: PrivateKey, - - /// Remote X25519 public key (Noise static key) - remote_x25519_public: PublicKey, + /// Representation of a remote Lewes Protocol peer + /// encapsulating all the known information and keys. + remote_peer: LpRemotePeer, /// Salt for PSK derivation salt: [u8; 32], @@ -223,9 +231,10 @@ pub struct LpSession { /// Negotiated protocol version from handshake. /// Set during handshake completion from the ClientHello/ServerHello packet header. /// Used for future version negotiation and compatibility checks. - negotiated_version: std::sync::atomic::AtomicU8, + negotiated_version: u8, } +// noiserm /// Generates a fresh salt for PSK derivation. /// /// Salt format: 8 bytes timestamp (u64 LE) + 24 bytes random nonce @@ -236,9 +245,6 @@ pub struct LpSession { /// # Returns /// A 32-byte array containing fresh salt material pub fn generate_fresh_salt() -> [u8; 32] { - use rand::RngCore; - use std::time::{SystemTime, UNIX_EPOCH}; - let mut salt = [0u8; 32]; // First 8 bytes: current timestamp as u64 little-endian @@ -270,36 +276,30 @@ impl LpSession { /// Returns the negotiated protocol version from the handshake. /// - /// Defaults to 1 (current LP version). Set during handshake via - /// `set_negotiated_version()` when ClientHello/ServerHello is processed. + /// Set during `LpSession` creation after sending / receiving `ClientHelloData` pub fn negotiated_version(&self) -> u8 { self.negotiated_version - .load(std::sync::atomic::Ordering::Acquire) } - /// Sets the negotiated protocol version from handshake packet header. - /// - /// Should be called during handshake when processing ClientHello (responder) - /// or ServerHello (initiator) to record the agreed protocol version. - pub fn set_negotiated_version(&self, version: u8) { - self.negotiated_version - .store(version, std::sync::atomic::Ordering::Release); - } - - /// Returns the local X25519 public key derived from the private key. + /// Returns the local X25519 public key. /// /// This is used for KKT protocol when the responder needs to send their /// KEM public key in the KKT response. - pub fn local_x25519_public(&self) -> PublicKey { - self.local_x25519_private.public_key() + pub fn local_x25519_public(&self) -> x25519::PublicKey { + *self.local_peer.x25519.public_key() + } + + /// Returns the remote ed25519 public key. + pub fn remote_ed25519_public(&self) -> ed25519::PublicKey { + self.remote_peer.ed25519_public } /// Returns the remote X25519 public key. /// /// Used for tie-breaking in simultaneous subsession initiation. /// Lower key loses and becomes responder. - pub fn remote_x25519_public(&self) -> &PublicKey { - &self.remote_x25519_public + pub fn remote_x25519_public(&self) -> &x25519::PublicKey { + &self.remote_peer.x25519_public } /// Returns the outer AEAD key for packet encryption/decryption. @@ -352,51 +352,65 @@ impl LpSession { /// /// * `id` - Session identifier /// * `is_initiator` - True if this side initiates the Noise handshake. - /// * `local_ed25519_keypair` - This side's Ed25519 keypair for PSQ authentication - /// * `local_x25519_key` - This side's X25519 private key for Noise protocol and DHKEM - /// * `remote_ed25519_key` - Peer's Ed25519 public key for PSQ authentication - /// * `remote_x25519_key` - Peer's X25519 public key for Noise protocol and DHKEM + /// * `local_peer` - This side's LP peer's keys + /// * `remote_peer` - The remote's LP peer's keys /// * `salt` - Salt for PSK derivation + /// * `protocol_version` - Protocol version to attach in all `LpPacket`s pub fn new( id: u32, is_initiator: bool, - local_ed25519_keypair: (&ed25519::PrivateKey, &ed25519::PublicKey), - local_x25519_key: &PrivateKey, - remote_ed25519_key: &ed25519::PublicKey, - remote_x25519_key: &PublicKey, + local_peer: LpLocalPeer, + remote_peer: LpRemotePeer, salt: &[u8; 32], + protocol_version: u8, ) -> Result { + // noiserm + // if we're LP responder, we **must** set our kem key + // georgio: if the initiator is a client, this is ok. but if it's a node then this will block mutual kkt. + if !is_initiator && local_peer.kem_psq.is_none() { + return Err(LpError::ResponderWithMissingKEMKey); + } + // XKpsk3 pattern requires remote static key known upfront (XK) // and PSK mixed at position 3. This provides forward secrecy with PSK authentication. - let pattern_name = "Noise_XKpsk3_25519_ChaChaPoly_SHA256"; - let psk_index = 3; + let pattern_name = crate::NOISE_PATTERN; + let psk_index = crate::NOISE_PSK_INDEX; + // noiserm let params = pattern_name.parse()?; let builder = Builder::new(params); - let local_key_bytes = local_x25519_key.to_bytes(); - let builder = builder.local_private_key(&local_key_bytes); + let local_key_bytes = local_peer.x25519.private_key().as_bytes(); + // noiserm + let builder = builder.local_private_key(local_key_bytes); - let remote_key_bytes = remote_x25519_key.to_bytes(); + let remote_key_bytes = remote_peer.x25519_public.to_bytes(); + // noiserm let builder = builder.remote_public_key(&remote_key_bytes); + // noiserm // Initialize with dummy PSK - real PSK will be injected via set_psk() during handshake // when PSQ runs using X25519 as DHKEM let dummy_psk = [0u8; 32]; let builder = builder.psk(psk_index, &dummy_psk); + // noiserm let initial_state = if is_initiator { builder.build_initiator().map_err(LpError::SnowKeyError)? } else { builder.build_responder().map_err(LpError::SnowKeyError)? }; + // noiserm let noise_protocol = NoiseProtocol::new(initial_state); // Initialize KKT state - both roles start at NotStarted let kkt_state = KKTState::NotStarted; // Initialize PSQ state based on role + // georgio: why PSQState::ResponderWaiting if responder? + // georgio: maybe because we can start straight with PSQ? + // georgio: either way, doesn't matter so much because a bad PSQ request will be rejected let psq_state = if is_initiator { PSQState::NotStarted } else { @@ -406,39 +420,30 @@ impl LpSession { Ok(Self { id, is_initiator, + // noiserm noise_state: Mutex::new(noise_protocol), kkt_state: Mutex::new(kkt_state), psq_state: Mutex::new(psq_state), psk_handle: Mutex::new(None), sending_counter: AtomicU64::new(0), receiving_counter: Mutex::new(ReceivingKeyCounterValidator::default()), + // noiserm psk_injected: AtomicBool::new(false), - // Ed25519 keys don't impl Clone, so convert to bytes and reconstruct - local_ed25519_private: ed25519::PrivateKey::from_bytes( - &local_ed25519_keypair.0.to_bytes(), - ) - .expect("Valid ed25519 private key"), - local_ed25519_public: ed25519::PublicKey::from_bytes( - &local_ed25519_keypair.1.to_bytes(), - ) - .expect("Valid ed25519 public key"), - remote_ed25519_public: ed25519::PublicKey::from_bytes(&remote_ed25519_key.to_bytes()) - .expect("Valid ed25519 public key"), - local_x25519_private: local_x25519_key.clone(), - remote_x25519_public: remote_x25519_key.clone(), + local_peer, + remote_peer, salt: *salt, outer_aead_key: Mutex::new(None), pq_shared_secret: Mutex::new(None), subsession_counter: AtomicU64::new(0), read_only: AtomicBool::new(false), successor_session_id: Mutex::new(None), - negotiated_version: std::sync::atomic::AtomicU8::new(1), // Default to version 1 + negotiated_version: protocol_version, }) } pub fn next_packet(&self, message: LpMessage) -> Result { let counter = self.next_counter(); - let header = LpHeader::new(self.id(), counter); + let header = LpHeader::new(self.id(), counter, self.negotiated_version); let packet = LpPacket::new(header, message); Ok(packet) } @@ -515,16 +520,60 @@ impl LpSession { self.psk_handle.lock().clone() } + /// Returns the reference to the KEM Public key of the peer (if available). + pub fn get_kem_key_handle(&self) -> Result<&x25519::PublicKey, LpError> { + self.local_peer + .kem_psq + .as_ref() + .map(|kp| kp.public_key()) + .ok_or(LpError::ResponderWithMissingKEMKey) + } + + // TODO: missing Zeroize + /// Convert local KEM PSQ keys to typed EncapsulationKey / EncapsulationKey + pub fn encapsulated_kem_keys( + &self, + ) -> Result<(DecapsulationKey<'_>, EncapsulationKey<'_>), LpError> { + let kem_keys = self + .local_peer + .kem_psq + .as_ref() + .ok_or(LpError::ResponderWithMissingKEMKey)?; + + let libcrux_private_key = libcrux_kem::PrivateKey::decode( + libcrux_kem::Algorithm::X25519, + kem_keys.private_key().as_bytes(), + ) + .map_err(|e| { + LpError::KKTError(format!( + "Failed to convert X25519 private key to libcrux PrivateKey: {e:?}", + )) + })?; + let dec_key = DecapsulationKey::X25519(libcrux_private_key); + + let libcrux_public_key = libcrux_kem::PublicKey::decode( + libcrux_kem::Algorithm::X25519, + kem_keys.public_key().as_bytes(), + ) + .map_err(|e| { + LpError::KKTError(format!( + "Failed to convert X25519 public key to libcrux PublicKey: {e:?}", + )) + })?; + let enc_key = EncapsulationKey::X25519(libcrux_public_key); + Ok((dec_key, enc_key)) + } + /// Prepares a KKT (KEM Key Transfer) request message. + /// Config: Encryption Enabled, One-Way, Anonymous /// - /// This should be called by the initiator before starting the Noise handshake - /// to obtain the responder's KEM public key. The KKT protocol authenticates - /// the exchange using Ed25519 signatures. + /// This should be called by the initiator before starting PSQ + /// to obtain the responder's KEM public key. /// /// **Protocol Flow:** - /// 1. Initiator creates KKT request with Ed25519 signature - /// 2. Responder validates signature and responds with KEM public key + signature - /// 3. Initiator validates response and stores KEM key for PSQ + /// 1. Initiator creates an Encrypted, Anonymous, One-Way, KKT request + /// 2. Responder responds with encrypted KEM public key + signature + /// 3. Initiator validates response and stores KEM key for PSQ use /// /// # Returns /// @@ -532,10 +581,7 @@ impl LpSession { /// * `Some(Err(LpError))` - Error creating KKT request /// * `None` - KKT not applicable (responder, or already completed) pub fn prepare_kkt_request(&self) -> Option> { - use nym_kkt::{ - ciphersuite::{Ciphersuite, HashFunction, KEM, SignatureScheme}, - kkt::request_kem_key, - }; + use nym_kkt::ciphersuite::{Ciphersuite, HashFunction, KEM, SignatureScheme}; let mut kkt_state = self.kkt_state.lock(); @@ -544,7 +590,7 @@ impl LpSession { return None; } - // Use X25519 as KEM for now (can extend to ML-KEM-768 later) + // georgio this needs to be moved one level up (maybe chosen in LPSession) let ciphersuite = match Ciphersuite::resolve_ciphersuite( KEM::X25519, HashFunction::Blake3, @@ -561,16 +607,30 @@ impl LpSession { }; let mut rng = rand09::rng(); - match request_kem_key(&mut rng, ciphersuite, &self.local_ed25519_private) { - Ok((context, request_frame)) => { - // Store context for response validation - *kkt_state = KKTState::InitiatorWaiting { context }; + match anonymous_initiator_process(&mut rng, ciphersuite) { + Ok((context, kkt_frame)) => { + match encrypt_initial_kkt_frame( + &mut rng, + &self.remote_peer.x25519_public, + &kkt_frame, + ) { + Ok((session_secret, encrypted_request_bytes)) => { + // Store context for response validation + *kkt_state = KKTState::InitiatorWaiting { + context, + session_secret, + }; - // Serialize KKT frame to bytes - let request_bytes = request_frame.to_bytes(); - Some(Ok(LpMessage::KKTRequest(crate::message::KKTRequestData( - request_bytes, - )))) + // Serialize KKT frame to bytes + Some(Ok(LpMessage::KKTRequest(crate::message::KKTRequestData( + encrypted_request_bytes, + )))) + } + Err(e) => Some(Err(LpError::Internal(format!( + "KKT request encryption failed: {:?}", + e + )))), + } } Err(e) => Some(Err(LpError::Internal(format!( "KKT request creation failed: {:?}", @@ -579,42 +639,47 @@ impl LpSession { } } + /// Attempt to retrieve expected KEM key hash of the remote + /// for [KEM] key type and [HashFunction] specified by own [KKTContext] + fn expected_kem_key_hash(&self, context: KKTContext) -> Result<&Vec, LpError> { + let kem = context.ciphersuite().kem(); + let hash_function = context.ciphersuite().hash_function(); + + let digests = self + .remote_peer + .expected_kem_key_digests + .get(&kem) + .ok_or(LpError::NoKnownKEMKeyDigests { kem, hash_function })?; + + digests + .get(&hash_function) + .ok_or(LpError::NoKnownKEMKeyDigests { kem, hash_function }) + } + /// Processes a KKT response from the responder. /// - /// Validates the responder's signature and stores the authenticated KEM public key + /// Decrypts and validates the response and stores the authenticated KEM public key /// for use in PSQ encapsulation. /// /// # Arguments /// /// * `response_bytes` - Raw KKT response message from responder - /// * `expected_key_hash` - Optional expected hash of responder's KEM key. - /// - `Some(hash)`: Full KKT validation (signature + hash) - use when directory service available - /// - `None`: Signature-only validation (hash computed from received key) - temporary mode + /// * `expected_key_hash` - Expected hash of responder's KEM key (obtained from directory). /// /// # Returns /// /// * `Ok(())` - KKT exchange completed, KEM key stored /// * `Err(LpError)` - Signature verification failed, hash mismatch, or invalid state /// - /// # Note - /// - /// When None is passed, the function computes the hash from the received key and validates against - /// that (effectively signature-only mode). This allows easy upgrade: just pass Some(directory_hash) - /// when directory service becomes available. The full KKT protocol with hash pinning provides - /// protection against key substitution attacks. - pub fn process_kkt_response( - &self, - response_bytes: &[u8], - expected_key_hash: Option<&[u8]>, - ) -> Result<(), LpError> { - use nym_kkt::key_utils::hash_encapsulation_key; - use nym_kkt::kkt::validate_kem_response; - + pub fn process_kkt_response(&self, encrypted_response_bytes: &[u8]) -> Result<(), LpError> { let mut kkt_state = self.kkt_state.lock(); // Extract context from waiting state - let mut context = match &*kkt_state { - KKTState::InitiatorWaiting { context } => *context, + let (mut context, session_secret) = match &*kkt_state { + KKTState::InitiatorWaiting { + context, + session_secret, + } => (*context, *session_secret), _ => { return Err(LpError::Internal( "KKT response received in invalid state".to_string(), @@ -622,42 +687,38 @@ impl LpSession { } }; - // Determine hash to validate against - let hash_for_validation: Vec; - let hash_ref = match expected_key_hash { - Some(hash) => hash, - None => { - // Signature-only mode: extract key from response and compute its hash - // This effectively bypasses hash validation while keeping signature validation - use nym_kkt::frame::KKTFrame; - - let (frame, _) = KKTFrame::from_bytes(response_bytes).map_err(|e| { - LpError::Internal(format!("Failed to parse KKT response: {:?}", e)) - })?; - - hash_for_validation = hash_encapsulation_key( - &context.ciphersuite().hash_function(), - context.ciphersuite().hash_len(), - frame.body_ref(), - ); - &hash_for_validation - } - }; - - // Validate response and extract KEM key - let kem_pk = validate_kem_response( - &mut context, - &self.remote_ed25519_public, - hash_ref, - response_bytes, - ) - .map_err(|e| LpError::Internal(format!("KKT response validation failed: {:?}", e)))?; + let remote_encapsulation_key = + match decrypt_kkt_frame(&session_secret, encrypted_response_bytes, KKT_RESPONSE_AAD) { + Ok((response_frame, remote_context)) => { + let expected_kem_key_digest = self.expected_kem_key_hash(context)?; + match initiator_ingest_response( + &mut context, + &response_frame, + &remote_context, + &self.remote_peer.ed25519_public, + expected_kem_key_digest, + ) { + Ok(remote_encapsulation_key) => remote_encapsulation_key, + Err(e) => { + return Err(LpError::Internal(format!( + "KKT response handling failure: {:?}", + e + ))); + } + } + } + Err(e) => { + return Err(LpError::Internal(format!( + "KKT response decryption failure: {:?}", + e + ))); + } + }; // Store the authenticated KEM key *kkt_state = KKTState::Completed { - kem_pk: Box::new(kem_pk), + kem_pk: Box::new(remote_encapsulation_key), }; - Ok(()) } @@ -680,31 +741,63 @@ impl LpSession { request_bytes: &[u8], responder_kem_pk: &EncapsulationKey, ) -> Result { - use nym_kkt::{frame::KKTFrame, kkt::handle_kem_request}; + let mut rng = rand09::rng(); let mut kkt_state = self.kkt_state.lock(); - // Deserialize request frame - let (request_frame, _) = KKTFrame::from_bytes(request_bytes).map_err(|e| { - LpError::Internal(format!("KKT request deserialization failed: {:?}", e)) - })?; - - // Handle request and create signed response - let response_frame = handle_kem_request( - &request_frame, - Some(&self.remote_ed25519_public), // Verify initiator signature - &self.local_ed25519_private, // Sign response - responder_kem_pk, - ) - .map_err(|e| LpError::Internal(format!("KKT request handling failed: {:?}", e)))?; + let response_bytes = match decrypt_initial_kkt_frame( + self.local_peer.x25519().private_key(), + request_bytes, + ) { + Ok((session_secret, request_frame, remote_context)) => { + match responder_ingest_message(&remote_context, None, None, &request_frame) { + Ok((mut context, _)) => match responder_process( + &mut context, + request_frame.session_id(), + self.local_peer.ed25519().private_key(), + responder_kem_pk, + ) { + Ok(response_frame) => match encrypt_kkt_frame( + &mut rng, + &session_secret, + &response_frame, + KKT_RESPONSE_AAD, + ) { + Ok(response_bytes) => response_bytes, + Err(e) => { + return Err(LpError::Internal(format!( + "KKT response encryption failure : {:?}", + e + ))); + } + }, + Err(e) => { + return Err(LpError::Internal(format!( + "KKT response generation failure : {:?}", + e + ))); + } + }, + Err(e) => { + return Err(LpError::Internal(format!( + "KKT request handling failure: {:?}", + e + ))); + } + } + } + Err(e) => { + return Err(LpError::Internal(format!( + "KKT request decryption failure: {:?}", + e + ))); + } + }; // Mark KKT as processed // Responder doesn't store the kem_pk since they already have their own KEM keypair *kkt_state = KKTState::ResponderProcessed; - // Serialize response frame - let response_bytes = response_frame.to_bytes(); - Ok(LpMessage::KKTResponse(crate::message::KKTResponseData( response_bytes, ))) @@ -748,11 +841,11 @@ impl LpSession { let session_context = self.id.to_le_bytes(); let psq_result = match psq_initiator_create_message( - &self.local_x25519_private, - &self.remote_x25519_public, + self.local_peer.x25519.private_key(), + &self.remote_peer.x25519_public, remote_kem, - &self.local_ed25519_private, - &self.local_ed25519_public, + self.local_peer.ed25519.private_key(), + self.local_peer.ed25519.public_key(), &self.salt, &session_context, ) { @@ -768,10 +861,13 @@ impl LpSession { // Store PQ shared secret for subsession PSK derivation *self.pq_shared_secret.lock() = Some(PqSharedSecret::new(psq_result.pq_shared_secret)); + // georgio start psq v2 here + // Inject PSK into Noise HandshakeState if let Err(e) = noise_state.set_psk(3, &psk) { return Some(Err(LpError::NoiseError(e))); } + // noiserm // Mark PSK as injected for safety checks in transport mode self.psk_injected.store(true, Ordering::Release); @@ -885,42 +981,16 @@ impl LpSession { let psq_payload = &payload[2..2 + psq_len]; let noise_payload = &payload[2 + psq_len..]; - // Convert X25519 local keys to DecapsulationKey/EncapsulationKey (DHKEM) - let local_private_bytes = &self.local_x25519_private.to_bytes(); - let libcrux_private_key = libcrux_kem::PrivateKey::decode( - libcrux_kem::Algorithm::X25519, - local_private_bytes, - ) - .map_err(|e| { - LpError::KKTError(format!( - "Failed to convert X25519 private key to libcrux PrivateKey: {:?}", - e - )) - })?; - let dec_key = DecapsulationKey::X25519(libcrux_private_key); - - let local_public_key = self.local_x25519_private.public_key(); - let local_public_bytes = local_public_key.as_bytes(); - let libcrux_public_key = libcrux_kem::PublicKey::decode( - libcrux_kem::Algorithm::X25519, - local_public_bytes, - ) - .map_err(|e| { - LpError::KKTError(format!( - "Failed to convert X25519 public key to libcrux PublicKey: {:?}", - e - )) - })?; - let enc_key = EncapsulationKey::X25519(libcrux_public_key); + let (dec_key, enc_key) = self.encapsulated_kem_keys()?; // Decapsulate PSK from PSQ payload using X25519 as DHKEM let session_context = self.id.to_le_bytes(); let psq_result = match psq_responder_process_message( - &self.local_x25519_private, - &self.remote_x25519_public, + self.local_peer.x25519.private_key(), + &self.remote_peer.x25519_public, (&dec_key, &enc_key), - &self.remote_ed25519_public, + &self.remote_peer.ed25519_public, psq_payload, &self.salt, &session_context, @@ -1123,7 +1193,7 @@ impl LpSession { /// Test-only method to set KKT state to Completed with a mock KEM key. /// This allows tests to bypass KKT exchange and directly test PSQ handshake. #[cfg(test)] - pub(crate) fn set_kkt_completed_for_test(&self, remote_x25519_pub: &PublicKey) { + pub(crate) fn set_kkt_completed_for_test(&self, remote_x25519_pub: &x25519::PublicKey) { // Convert remote X25519 public key to EncapsulationKey for testing let remote_kem_bytes = remote_x25519_pub.as_bytes(); let libcrux_public_key = @@ -1176,8 +1246,8 @@ impl LpSession { let pattern_name = "Noise_KKpsk0_25519_ChaChaPoly_SHA256"; let params = pattern_name.parse()?; - let local_key_bytes = self.local_x25519_private.to_bytes(); - let remote_key_bytes = self.remote_x25519_public.to_bytes(); + let local_key_bytes = self.local_peer.x25519.private_key().to_bytes(); + let remote_key_bytes = self.remote_x25519_public().to_bytes(); let builder = Builder::new(params) .local_private_key(&local_key_bytes) @@ -1194,23 +1264,11 @@ impl LpSession { index: subsession_index, noise_state: Mutex::new(NoiseProtocol::new(handshake_state)), is_initiator, - // Copy key material from parent for into_session() conversion - local_ed25519_private: ed25519::PrivateKey::from_bytes( - &self.local_ed25519_private.to_bytes(), - ) - .expect("Valid Ed25519 private key from parent"), - local_ed25519_public: ed25519::PublicKey::from_bytes( - &self.local_ed25519_public.to_bytes(), - ) - .expect("Valid Ed25519 public key from parent"), - remote_ed25519_public: ed25519::PublicKey::from_bytes( - &self.remote_ed25519_public.to_bytes(), - ) - .expect("Valid Ed25519 public key from parent"), - local_x25519_private: self.local_x25519_private.clone(), - remote_x25519_public: self.remote_x25519_public.clone(), + local_peer: self.local_peer.clone(), + remote_peer: self.remote_peer.clone(), pq_shared_secret: PqSharedSecret::new(pq_secret), subsession_psk, + negotiated_version: self.negotiated_version, }) } } @@ -1240,20 +1298,22 @@ pub struct SubsessionHandshake { is_initiator: bool, // Key material inherited from parent session for into_session() conversion - /// Local Ed25519 private key (for PSQ auth if needed) - local_ed25519_private: ed25519::PrivateKey, - /// Local Ed25519 public key - local_ed25519_public: ed25519::PublicKey, - /// Remote Ed25519 public key - remote_ed25519_public: ed25519::PublicKey, - /// Local X25519 private key (Noise static key) - local_x25519_private: PrivateKey, - /// Remote X25519 public key (Noise static key) - remote_x25519_public: PublicKey, + /// Representation of a local Lewes Protocol peer + /// encapsulating all the known information and keys. + local_peer: LpLocalPeer, + + /// Representation of a remote Lewes Protocol peer + /// encapsulating all the known information and keys. + remote_peer: LpRemotePeer, + /// PQ shared secret inherited from parent (for creating further subsessions) pq_shared_secret: PqSharedSecret, + /// Subsession PSK (for deriving outer AEAD key) subsession_psk: [u8; 32], + + /// Negotiated protocol version from handshake. + negotiated_version: u8, } impl SubsessionHandshake { @@ -1331,6 +1391,7 @@ impl SubsessionHandshake { Ok(LpSession { id: receiver_index, is_initiator: self.is_initiator, + // noiserm noise_state: Mutex::new(noise_state), // KKT: subsession inherits from parent, mark as processed kkt_state: Mutex::new(KKTState::ResponderProcessed), @@ -1341,12 +1402,10 @@ impl SubsessionHandshake { psk_handle: Mutex::new(None), // Subsession doesn't have its own handle sending_counter: AtomicU64::new(0), receiving_counter: Mutex::new(ReceivingKeyCounterValidator::new(0)), + // noiserm psk_injected: AtomicBool::new(true), // PSK was in KKpsk0 - local_ed25519_private: self.local_ed25519_private, - local_ed25519_public: self.local_ed25519_public, - remote_ed25519_public: self.remote_ed25519_public, - local_x25519_private: self.local_x25519_private, - remote_x25519_public: self.remote_x25519_public, + local_peer: self.local_peer, + remote_peer: self.remote_peer, salt, outer_aead_key: Mutex::new(Some(outer_key)), pq_shared_secret: Mutex::new(Some(self.pq_shared_secret)), @@ -1354,7 +1413,7 @@ impl SubsessionHandshake { read_only: AtomicBool::new(false), successor_session_id: Mutex::new(None), // Inherit parent's protocol version - negotiated_version: std::sync::atomic::AtomicU8::new(1), + negotiated_version: self.negotiated_version, }) } } @@ -1362,50 +1421,45 @@ impl SubsessionHandshake { #[cfg(test)] mod tests { use super::*; + use crate::packet::version; + use crate::peer::mock_peers; use crate::{replay::ReplayError, sessions_for_tests}; + use nym_crypto::asymmetric::ed25519; + use rand::thread_rng; // Helper function to generate keypairs for tests - fn generate_keypair() -> crate::keypair::Keypair { - crate::keypair::Keypair::default() + fn generate_x25519_keypair() -> x25519::KeyPair { + x25519::KeyPair::new(&mut thread_rng()) } // Helper function to create a session with real keys for handshake tests - fn create_handshake_test_session( - receiver_index: u32, - is_initiator: bool, - local_keys: &crate::keypair::Keypair, - remote_pub_key: &crate::keypair::PublicKey, - ) -> LpSession { - use nym_crypto::asymmetric::ed25519; + fn create_handshake_test_session(receiver_index: u32, is_initiator: bool) -> LpSession { + let (keys_1, keys_2) = mock_peers(); // Create Ed25519 keypairs that correspond to initiator/responder roles // Initiator uses [1u8], Responder uses [2u8] - let (local_ed25519_seed, remote_ed25519_seed) = if is_initiator { - ([1u8; 32], [2u8; 32]) + let (local, remote) = if is_initiator { + (keys_1, keys_2.as_remote()) } else { - ([2u8; 32], [1u8; 32]) + (keys_2, keys_1.as_remote()) }; - let local_ed25519 = ed25519::KeyPair::from_secret(local_ed25519_seed, 0); - let remote_ed25519 = ed25519::KeyPair::from_secret(remote_ed25519_seed, 1); - let salt = [0u8; 32]; // Test salt // PSQ will derive the PSK during handshake using X25519 as DHKEM let session = LpSession::new( receiver_index, is_initiator, - (local_ed25519.private_key(), local_ed25519.public_key()), - local_keys.private_key(), - remote_ed25519.public_key(), - remote_pub_key, + local, + remote.clone(), &salt, + version::CURRENT, ) .expect("Test session creation failed"); // Initialize KKT state to Completed for tests (bypasses KKT exchange) // This simulates having already received the remote party's KEM key via KKT - session.set_kkt_completed_for_test(remote_pub_key); + session.set_kkt_completed_for_test(&remote.x25519_public); session } @@ -1501,21 +1555,13 @@ mod tests { #[test] fn test_prepare_handshake_message_initial_state() { - let initiator_keys = generate_keypair(); - let responder_keys = generate_keypair(); let receiver_index = 12345u32; - let initiator_session = create_handshake_test_session( - receiver_index, - true, - &initiator_keys, - responder_keys.public_key(), - ); + let initiator_session = create_handshake_test_session(receiver_index, true); let responder_session = create_handshake_test_session( receiver_index, false, - &responder_keys, - initiator_keys.public_key(), // Responder also needs initiator's key for XK + // Responder also needs initiator's key for XK ); // Initiator should have a message to send immediately (-> e) @@ -1533,22 +1579,10 @@ mod tests { #[test] fn test_process_handshake_message_first_step() { - let initiator_keys = generate_keypair(); - let responder_keys = generate_keypair(); let receiver_index = 12345u32; - let initiator_session = create_handshake_test_session( - receiver_index, - true, - &initiator_keys, - responder_keys.public_key(), - ); - let responder_session = create_handshake_test_session( - receiver_index, - false, - &responder_keys, - initiator_keys.public_key(), - ); + let initiator_session = create_handshake_test_session(receiver_index, true); + let responder_session = create_handshake_test_session(receiver_index, false); // 1. Initiator prepares the first message (-> e) let initiator_msg_result = initiator_session.prepare_handshake_message(); @@ -1579,21 +1613,8 @@ mod tests { #[test] fn test_handshake_driver_simulation() { - let initiator_keys = generate_keypair(); - let responder_keys = generate_keypair(); - - let initiator_session = create_handshake_test_session( - 12345u32, - true, - &initiator_keys, - responder_keys.public_key(), - ); - let responder_session = create_handshake_test_session( - 12345u32, - false, - &responder_keys, - initiator_keys.public_key(), - ); + let initiator_session = create_handshake_test_session(12345u32, true); + let responder_session = create_handshake_test_session(12345u32, false); let mut responder_to_initiator_msg = None; let mut rounds = 0; @@ -1674,21 +1695,9 @@ mod tests { #[test] fn test_encrypt_decrypt_after_handshake() { // --- Setup Handshake --- - let initiator_keys = generate_keypair(); - let responder_keys = generate_keypair(); - let initiator_session = create_handshake_test_session( - 12345u32, - true, - &initiator_keys, - responder_keys.public_key(), - ); - let responder_session = create_handshake_test_session( - 12345u32, - false, - &responder_keys, - initiator_keys.public_key(), - ); + let initiator_session = create_handshake_test_session(12345u32, true); + let responder_session = create_handshake_test_session(12345u32, false); // Drive handshake to completion (simplified loop from previous test) let mut i_msg = initiator_session @@ -1743,15 +1752,7 @@ mod tests { #[test] fn test_encrypt_decrypt_before_handshake() { - let initiator_keys = generate_keypair(); - let responder_keys = generate_keypair(); - - let initiator_session = create_handshake_test_session( - 12345u32, - true, - &initiator_keys, - responder_keys.public_key(), - ); + let initiator_session = create_handshake_test_session(12345u32, true); assert!(!initiator_session.is_handshake_complete()); @@ -1819,21 +1820,8 @@ mod tests { /// Test that PSQ runs during handshake and derives a PSK #[test] fn test_psq_handshake_runs_with_psk_injection() { - let initiator_keys = generate_keypair(); - let responder_keys = generate_keypair(); - - let initiator_session = create_handshake_test_session( - 12345u32, - true, - &initiator_keys, - responder_keys.public_key(), - ); - let responder_session = create_handshake_test_session( - 12345u32, - false, - &responder_keys, - initiator_keys.public_key(), - ); + let initiator_session = create_handshake_test_session(12345u32, true); + let responder_session = create_handshake_test_session(12345u32, false); // Drive the handshake let mut i_msg = initiator_session @@ -1896,8 +1884,8 @@ mod tests { fn test_x25519_to_kem_conversion() { use nym_kkt::ciphersuite::EncapsulationKey; - let initiator_keys = generate_keypair(); - let responder_keys = generate_keypair(); + let initiator_keys = generate_x25519_keypair(); + let responder_keys = generate_x25519_keypair(); // Verify we can convert X25519 public key to KEM format (as done in session.rs) let x25519_public_bytes = responder_keys.public_key().as_bytes(); @@ -1920,22 +1908,9 @@ mod tests { /// Test that PSQ actually derives a different PSK (not using dummy) #[test] fn test_psq_derived_psk_differs_from_dummy() { - let initiator_keys = generate_keypair(); - let responder_keys = generate_keypair(); - // Create sessions - they start with dummy PSK [0u8; 32] - let initiator_session = create_handshake_test_session( - 12345u32, - true, - &initiator_keys, - responder_keys.public_key(), - ); - let responder_session = create_handshake_test_session( - 12345u32, - false, - &responder_keys, - initiator_keys.public_key(), - ); + let initiator_session = create_handshake_test_session(12345u32, true); + let responder_session = create_handshake_test_session(12345u32, false); // Prepare first message (initiator runs PSQ and injects PSK) let i_msg = initiator_session @@ -1994,21 +1969,8 @@ mod tests { /// Test full end-to-end handshake with PSQ integration #[test] fn test_handshake_with_psq_end_to_end() { - let initiator_keys = generate_keypair(); - let responder_keys = generate_keypair(); - - let initiator_session = create_handshake_test_session( - 12345u32, - true, - &initiator_keys, - responder_keys.public_key(), - ); - let responder_session = create_handshake_test_session( - 12345u32, - false, - &responder_keys, - initiator_keys.public_key(), - ); + let initiator_session = create_handshake_test_session(12345u32, true); + let responder_session = create_handshake_test_session(12345u32, false); // Verify initial state assert!(!initiator_session.is_handshake_complete()); @@ -2080,22 +2042,9 @@ mod tests { /// Test that Ed25519 keys are used in PSQ authentication #[test] fn test_psq_handshake_uses_ed25519_authentication() { - let initiator_keys = generate_keypair(); - let responder_keys = generate_keypair(); - // Create sessions with explicit Ed25519 keys - let initiator_session = create_handshake_test_session( - 12345u32, - true, - &initiator_keys, - responder_keys.public_key(), - ); - let responder_session = create_handshake_test_session( - 12345u32, - false, - &responder_keys, - initiator_keys.public_key(), - ); + let initiator_session = create_handshake_test_session(12345u32, true); + let responder_session = create_handshake_test_session(12345u32, false); // Verify sessions store Ed25519 keys // (Internal verification - keys are used in PSQ calls) @@ -2134,15 +2083,8 @@ mod tests { #[test] fn test_psq_deserialization_failure() { // Test that corrupted PSQ payload causes clean abort - let responder_keys = generate_keypair(); - let initiator_keys = generate_keypair(); - let responder_session = create_handshake_test_session( - 12345u32, - false, - &responder_keys, - initiator_keys.public_key(), - ); + let responder_session = create_handshake_test_session(12345u32, false); // Create a handshake message with corrupted PSQ payload let corrupted_psq_data = vec![0xFF; 128]; // Random garbage @@ -2162,51 +2104,44 @@ mod tests { #[test] fn test_handshake_abort_on_psq_failure() { - // Test that Ed25519 auth failure causes handshake abort - let initiator_keys = generate_keypair(); - let responder_keys = generate_keypair(); + let (init, resp) = mock_peers(); + + let mut bad_resp = resp.as_remote(); + let wrong_ed25519 = ed25519::KeyPair::from_secret([99u8; 32], 99); // Different key! + bad_resp.ed25519_public = *wrong_ed25519.public_key(); + + let mut bad_init = init.as_remote(); + bad_init.ed25519_public = *wrong_ed25519.public_key(); // Create sessions with MISMATCHED Ed25519 keys // This simulates authentication failure - let initiator_ed25519 = ed25519::KeyPair::from_secret([1u8; 32], 0); - let wrong_ed25519 = ed25519::KeyPair::from_secret([99u8; 32], 99); // Different key! - let receiver_index: u32 = 55555; let salt = [0u8; 32]; let initiator_session = LpSession::new( receiver_index, true, - ( - initiator_ed25519.private_key(), - initiator_ed25519.public_key(), - ), - initiator_keys.private_key(), - wrong_ed25519.public_key(), // Responder expects THIS key - responder_keys.public_key(), + init.clone(), + bad_resp, &salt, + version::CURRENT, ) .unwrap(); - // Initialize KKT state for test - initiator_session.set_kkt_completed_for_test(responder_keys.public_key()); - let responder_ed25519 = ed25519::KeyPair::from_secret([2u8; 32], 1); + // Initialize KKT state for test + initiator_session.set_kkt_completed_for_test(resp.x25519.public_key()); let responder_session = LpSession::new( receiver_index, false, - ( - responder_ed25519.private_key(), - responder_ed25519.public_key(), - ), - responder_keys.private_key(), - wrong_ed25519.public_key(), // Expects WRONG key (not initiator's) - initiator_keys.public_key(), + resp, + bad_init, &salt, + version::CURRENT, ) .unwrap(); // Initialize KKT state for test - responder_session.set_kkt_completed_for_test(initiator_keys.public_key()); + responder_session.set_kkt_completed_for_test(init.x25519.public_key()); // Initiator prepares message (should succeed - signing works) let msg1 = initiator_session @@ -2230,17 +2165,11 @@ mod tests { #[test] fn test_psq_invalid_signature() { - // Test Ed25519 signature validation specifically - // Setup with matching X25519 keys but mismatched Ed25519 keys - let initiator_keys = generate_keypair(); - let responder_keys = generate_keypair(); + let (init, resp) = mock_peers(); - // Initiator uses Ed25519 key [1u8] - let initiator_ed25519 = ed25519::KeyPair::from_secret([1u8; 32], 0); - - // Responder expects Ed25519 key [99u8] (wrong!) - let wrong_ed25519_keypair = ed25519::KeyPair::from_secret([99u8; 32], 99); - let wrong_ed25519_public = wrong_ed25519_keypair.public_key(); + let mut bad_init = init.as_remote(); + let wrong_ed25519 = ed25519::KeyPair::from_secret([99u8; 32], 99); // Different key! + bad_init.ed25519_public = *wrong_ed25519.public_key(); let receiver_index: u32 = 66666; let salt = [0u8; 32]; @@ -2248,36 +2177,26 @@ mod tests { let initiator_session = LpSession::new( receiver_index, true, - ( - initiator_ed25519.private_key(), - initiator_ed25519.public_key(), - ), - initiator_keys.private_key(), - wrong_ed25519_public, // This doesn't matter for initiator - responder_keys.public_key(), + init.clone(), + resp.as_remote(), &salt, + version::CURRENT, ) .unwrap(); // Initialize KKT state for test - initiator_session.set_kkt_completed_for_test(responder_keys.public_key()); - - let responder_ed25519 = ed25519::KeyPair::from_secret([2u8; 32], 1); + initiator_session.set_kkt_completed_for_test(resp.x25519.public_key()); let responder_session = LpSession::new( receiver_index, false, - ( - responder_ed25519.private_key(), - responder_ed25519.public_key(), - ), - responder_keys.private_key(), - wrong_ed25519_public, // Responder expects WRONG key - initiator_keys.public_key(), + resp, + bad_init, &salt, + version::CURRENT, ) .unwrap(); // Initialize KKT state for test - responder_session.set_kkt_completed_for_test(initiator_keys.public_key()); + responder_session.set_kkt_completed_for_test(init.x25519.public_key()); // Initiator creates message with valid signature (signed with [1u8]) let msg = initiator_session @@ -2303,15 +2222,7 @@ mod tests { #[test] fn test_psq_state_unchanged_on_error() { // Verify that PSQ errors leave session in clean state - let responder_keys = generate_keypair(); - let initiator_keys = generate_keypair(); - - let responder_session = create_handshake_test_session( - 12345u32, - false, - &responder_keys, - initiator_keys.public_key(), - ); + let responder_session = create_handshake_test_session(12345u32, false); // Capture initial PSQ state (should be ResponderWaiting) // (We can't directly access psq_state, but we can verify behavior) @@ -2328,12 +2239,7 @@ mod tests { // Session should still be functional - can process valid messages // Create a proper initiator to send valid message - let initiator_session = create_handshake_test_session( - 12345u32, - true, - &initiator_keys, - responder_keys.public_key(), - ); + let initiator_session = create_handshake_test_session(12345u32, true); let valid_msg = initiator_session .prepare_handshake_message() @@ -2355,16 +2261,8 @@ mod tests { // This test verifies the safety mechanism that prevents transport mode operations // from running with the dummy PSK if PSQ injection fails or is skipped. - let initiator_keys = generate_keypair(); - let responder_keys = generate_keypair(); - // Create session but don't complete handshake (no PSK injection will occur) - let session = create_handshake_test_session( - 12345u32, - true, - &initiator_keys, - responder_keys.public_key(), - ); + let session = create_handshake_test_session(12345u32, true); // Verify session was created successfully assert!(!session.is_handshake_complete()); @@ -2404,15 +2302,7 @@ mod tests { #[test] fn test_demote_sets_read_only() { - let initiator_keys = generate_keypair(); - let responder_keys = generate_keypair(); - - let session = create_handshake_test_session( - 12345u32, - true, - &initiator_keys, - responder_keys.public_key(), - ); + let session = create_handshake_test_session(12345u32, true); // Initially not read-only assert!(!session.is_read_only()); @@ -2429,21 +2319,9 @@ mod tests { #[test] fn test_encrypt_fails_after_demotion() { // --- Setup Handshake --- - let initiator_keys = generate_keypair(); - let responder_keys = generate_keypair(); - let initiator_session = create_handshake_test_session( - 12345u32, - true, - &initiator_keys, - responder_keys.public_key(), - ); - let responder_session = create_handshake_test_session( - 12345u32, - false, - &responder_keys, - initiator_keys.public_key(), - ); + let initiator_session = create_handshake_test_session(12345u32, true); + let responder_session = create_handshake_test_session(12345u32, false); // Drive handshake to completion let i_msg = initiator_session @@ -2485,21 +2363,9 @@ mod tests { #[test] fn test_decrypt_works_after_demotion() { // --- Setup Handshake --- - let initiator_keys = generate_keypair(); - let responder_keys = generate_keypair(); - let initiator_session = create_handshake_test_session( - 12345u32, - true, - &initiator_keys, - responder_keys.public_key(), - ); - let responder_session = create_handshake_test_session( - 12345u32, - false, - &responder_keys, - initiator_keys.public_key(), - ); + let initiator_session = create_handshake_test_session(12345u32, true); + let responder_session = create_handshake_test_session(12345u32, false); // Drive handshake to completion let i_msg = initiator_session diff --git a/common/nym-lp/src/session_integration/mod.rs b/common/nym-lp/src/session_integration/mod.rs index ceafc3f81f..7e52d88b9c 100644 --- a/common/nym-lp/src/session_integration/mod.rs +++ b/common/nym-lp/src/session_integration/mod.rs @@ -1,7 +1,6 @@ #[cfg(test)] mod tests { use crate::codec::{parse_lp_packet, serialize_lp_packet}; - use crate::keypair::PublicKey; use crate::{ LpError, message::LpMessage, @@ -9,7 +8,6 @@ mod tests { session_manager::SessionManager, }; use bytes::BytesMut; - use nym_crypto::asymmetric::ed25519; // Function to create a test packet - similar to how it's done in codec.rs tests fn create_test_packet( @@ -21,7 +19,7 @@ mod tests { // Create the header let header = LpHeader { protocol_version, - reserved: 0u16, // reserved + reserved: [0u8; 3], // reserved receiver_idx, counter, }; @@ -50,24 +48,7 @@ mod tests { let session_manager_2 = SessionManager::new(); // 2. Generate Ed25519 keypairs for PSQ authentication - let ed25519_keypair_a = ed25519::KeyPair::from_secret([1u8; 32], 0); - let ed25519_keypair_b = ed25519::KeyPair::from_secret([2u8; 32], 1); - - // Derive X25519 keys from Ed25519 (needed for KKT init test) - let x25519_pub_a = ed25519_keypair_a - .public_key() - .to_x25519() - .expect("Failed to derive X25519 from Ed25519"); - let x25519_pub_b = ed25519_keypair_b - .public_key() - .to_x25519() - .expect("Failed to derive X25519 from Ed25519"); - - // Convert to LP keypair types - let lp_pub_a = PublicKey::from_bytes(x25519_pub_a.as_bytes()) - .expect("Failed to create PublicKey from bytes"); - let lp_pub_b = PublicKey::from_bytes(x25519_pub_b.as_bytes()) - .expect("Failed to create PublicKey from bytes"); + let (a, b) = mock_peers(); // Use fixed receiver_index for deterministic test let receiver_index: u32 = 100001; @@ -79,26 +60,22 @@ mod tests { let peer_a_sm = session_manager_1 .create_session_state_machine( receiver_index, - ( - ed25519_keypair_a.private_key(), - ed25519_keypair_a.public_key(), - ), - ed25519_keypair_b.public_key(), true, + a.clone(), + b.as_remote(), &salt, + version::CURRENT, ) .expect("Failed to create session A"); let peer_b_sm = session_manager_2 .create_session_state_machine( receiver_index, - ( - ed25519_keypair_b.private_key(), - ed25519_keypair_b.public_key(), - ), - ed25519_keypair_a.public_key(), false, + b.clone(), + a.as_remote(), &salt, + version::CURRENT, ) .expect("Failed to create session B"); @@ -108,10 +85,10 @@ mod tests { // Initialize KKT state for both sessions (test bypass) session_manager_1 - .init_kkt_for_test(peer_a_sm, &lp_pub_b) + .init_kkt_for_test(peer_a_sm, b.x25519.public_key()) .expect("Failed to init KKT for peer A"); session_manager_2 - .init_kkt_for_test(peer_b_sm, &lp_pub_a) + .init_kkt_for_test(peer_b_sm, a.x25519.public_key()) .expect("Failed to init KKT for peer B"); // 5. Simulate Noise Handshake (Sans-IO) @@ -509,24 +486,7 @@ mod tests { let session_manager_2 = SessionManager::new(); // 2. Generate Ed25519 keypairs for PSQ authentication - let ed25519_keypair_a = ed25519::KeyPair::from_secret([3u8; 32], 0); - let ed25519_keypair_b = ed25519::KeyPair::from_secret([4u8; 32], 1); - - // Derive X25519 keys from Ed25519 (same as state machine does internally) - let x25519_pub_a = ed25519_keypair_a - .public_key() - .to_x25519() - .expect("Failed to derive X25519 from Ed25519"); - let x25519_pub_b = ed25519_keypair_b - .public_key() - .to_x25519() - .expect("Failed to derive X25519 from Ed25519"); - - // Convert to LP keypair types - let lp_pub_a = PublicKey::from_bytes(x25519_pub_a.as_bytes()) - .expect("Failed to create PublicKey from bytes"); - let lp_pub_b = PublicKey::from_bytes(x25519_pub_b.as_bytes()) - .expect("Failed to create PublicKey from bytes"); + let (a, b) = mock_peers(); // Use fixed receiver_index for test let receiver_index: u32 = 100002; @@ -537,34 +497,31 @@ mod tests { let peer_a_sm = session_manager_1 .create_session_state_machine( receiver_index, - ( - ed25519_keypair_a.private_key(), - ed25519_keypair_a.public_key(), - ), - ed25519_keypair_b.public_key(), true, + a.clone(), + b.as_remote(), &salt, + version::CURRENT, ) - .unwrap(); + .expect("Failed to create session A"); + let peer_b_sm = session_manager_2 .create_session_state_machine( receiver_index, - ( - ed25519_keypair_b.private_key(), - ed25519_keypair_b.public_key(), - ), - ed25519_keypair_a.public_key(), false, + b.clone(), + a.as_remote(), &salt, + version::CURRENT, ) - .unwrap(); + .expect("Failed to create session B"); // Initialize KKT state for both sessions (test bypass) session_manager_1 - .init_kkt_for_test(peer_a_sm, &lp_pub_b) + .init_kkt_for_test(peer_a_sm, b.x25519.public_key()) .expect("Failed to init KKT for peer A"); session_manager_2 - .init_kkt_for_test(peer_b_sm, &lp_pub_a) + .init_kkt_for_test(peer_b_sm, a.x25519.public_key()) .expect("Failed to init KKT for peer B"); // Drive handshake to completion (simplified) @@ -720,17 +677,7 @@ mod tests { let session_manager = SessionManager::new(); // Generate Ed25519 keypair for PSQ authentication - let ed25519_keypair = ed25519::KeyPair::from_secret([5u8; 32], 0); - - // Derive X25519 key from Ed25519 (same as state machine does internally) - let x25519_pub = ed25519_keypair - .public_key() - .to_x25519() - .expect("Failed to derive X25519 from Ed25519"); - - // Convert to LP keypair type (still needed for init_kkt_for_test below if used) - let _lp_pub = PublicKey::from_bytes(x25519_pub.as_bytes()) - .expect("Failed to create PublicKey from bytes"); + let (a, b) = mock_peers(); // Use fixed receiver_index for test let receiver_index: u32 = 100003; @@ -742,10 +689,11 @@ mod tests { let _session = session_manager .create_session_state_machine( receiver_index, - (ed25519_keypair.private_key(), ed25519_keypair.public_key()), - ed25519_keypair.public_key(), true, + a.clone(), + b.as_remote(), &salt, + version::CURRENT, ) .expect("Failed to create session"); @@ -765,10 +713,11 @@ mod tests { let _temp_session = session_manager .create_session_state_machine( receiver_index_temp, - (ed25519_keypair.private_key(), ed25519_keypair.public_key()), - ed25519_keypair.public_key(), true, + a.clone(), + b.as_remote(), &salt, + version::CURRENT, ) .expect("Failed to create temp session"); @@ -820,13 +769,16 @@ mod tests { } // Remove unused imports if SessionManager methods are no longer direct dependencies // use crate::noise_protocol::{create_noise_state, create_noise_state_responder}; + use crate::packet::version; + use crate::peer::mock_peers; + use crate::state_machine::LpData; use crate::{ // Bring in state machine types state_machine::{LpAction, LpInput, LpStateBare}, // message::LpMessage, // LpMessage likely still needed for LpInput/LpAction // packet::{LpHeader, LpPacket, TRAILER_LEN}, // LpPacket needed for LpAction/LpInput }; - use bytes::Bytes; // Use Bytes for SendData input + // Use Bytes for SendData input // Keep helper function for creating test packets if needed, // but LpAction::SendPacket should provide the packets now. @@ -846,8 +798,7 @@ mod tests { let session_manager_2 = SessionManager::new(); // 2. Generate Ed25519 keypairs for PSQ authentication - let ed25519_keypair_a = ed25519::KeyPair::from_secret([6u8; 32], 0); - let ed25519_keypair_b = ed25519::KeyPair::from_secret([7u8; 32], 1); + let (a, b) = mock_peers(); // Use fixed receiver_index for test let receiver_index: u32 = 100005; @@ -860,13 +811,11 @@ mod tests { session_manager_1 .create_session_state_machine( receiver_index, - ( - ed25519_keypair_a.private_key(), - ed25519_keypair_a.public_key() - ), - ed25519_keypair_b.public_key(), true, + a.clone(), + b.as_remote(), &salt, + version::CURRENT ) // Initiator .is_ok() ); @@ -874,13 +823,11 @@ mod tests { session_manager_2 .create_session_state_machine( receiver_index, - ( - ed25519_keypair_b.private_key(), - ed25519_keypair_b.public_key() - ), - ed25519_keypair_a.public_key(), false, + b, + a.as_remote(), &salt, + version::CURRENT ) // Responder .is_ok() ); @@ -1162,13 +1109,13 @@ mod tests { // --- 5. Simulate Data Transfer via process_input --- println!("Starting data transfer simulation via process_input..."); - let plaintext_a_to_b = b"Hello from A via process_input!"; - let plaintext_b_to_a = b"Hello from B via process_input!"; + let plaintext_a_to_b = LpData::new_opaque(b"Hello from A via process_input!".to_vec()); + let plaintext_b_to_a = LpData::new_opaque(b"Hello from B via process_input!".to_vec()); // --- A sends to B --- println!(" A sends to B"); let action_a_send = session_manager_1 - .process_input(receiver_index, LpInput::SendData(plaintext_a_to_b.to_vec())) + .process_input(receiver_index, LpInput::SendData(plaintext_a_to_b.clone())) .expect("A SendData should produce action") .expect("A SendData failed"); @@ -1191,14 +1138,10 @@ mod tests { .expect("B ReceivePacket (data) failed"); if let LpAction::DeliverData(data) = action_b_recv { - assert_eq!( - data, - Bytes::copy_from_slice(plaintext_a_to_b), - "Decrypted data mismatch A->B" - ); + assert_eq!(data, plaintext_a_to_b, "Decrypted data mismatch A->B"); println!( " B successfully decrypted: {:?}", - String::from_utf8_lossy(&data) + String::from_utf8_lossy(&data.content) ); } else { panic!("B ReceivePacket did not produce DeliverData"); @@ -1207,7 +1150,7 @@ mod tests { // --- B sends to A --- println!(" B sends to A"); let action_b_send = session_manager_2 - .process_input(receiver_index, LpInput::SendData(plaintext_b_to_a.to_vec())) + .process_input(receiver_index, LpInput::SendData(plaintext_b_to_a.clone())) .expect("B SendData should produce action") .expect("B SendData failed"); @@ -1232,14 +1175,10 @@ mod tests { .expect("A ReceivePacket (data) failed"); if let LpAction::DeliverData(data) = action_a_recv { - assert_eq!( - data, - Bytes::copy_from_slice(plaintext_b_to_a), - "Decrypted data mismatch B->A" - ); + assert_eq!(data, plaintext_b_to_a, "Decrypted data mismatch B->A"); println!( " A successfully decrypted: {:?}", - String::from_utf8_lossy(&data) + String::from_utf8_lossy(&data.content) ); } else { panic!("A ReceivePacket did not produce DeliverData"); @@ -1264,11 +1203,11 @@ mod tests { println!("Testing out-of-order reception via process_input..."); // A prepares N+1 then N - let data_n_plus_1 = Bytes::from_static(b"Message N+1"); - let data_n = Bytes::from_static(b"Message N"); + let data_n_plus_1 = LpData::new_opaque(b"Message N+1".to_vec()); + let data_n = LpData::new_opaque(b"Message N".to_vec()); let action_send_n1 = session_manager_1 - .process_input(receiver_index, LpInput::SendData(data_n_plus_1.to_vec())) + .process_input(receiver_index, LpInput::SendData(data_n_plus_1.clone())) .unwrap() .unwrap(); let packet_n1 = match action_send_n1 { @@ -1277,7 +1216,7 @@ mod tests { }; let action_send_n = session_manager_1 - .process_input(receiver_index, LpInput::SendData(data_n.to_vec())) + .process_input(receiver_index, LpInput::SendData(data_n.clone())) .unwrap() .unwrap(); let packet_n = match action_send_n { @@ -1334,8 +1273,10 @@ mod tests { ); // Further actions on A fail - let send_after_close_a = - session_manager_1.process_input(receiver_index, LpInput::SendData(b"fail".to_vec())); + let send_after_close_a = session_manager_1.process_input( + receiver_index, + LpInput::SendData(LpData::new_opaque(b"fail".to_vec())), + ); assert!(send_after_close_a.is_err()); assert!(matches!( send_after_close_a.err().unwrap(), @@ -1354,8 +1295,10 @@ mod tests { ); // Further actions on B fail - let send_after_close_b = - session_manager_2.process_input(receiver_index, LpInput::SendData(b"fail".to_vec())); + let send_after_close_b = session_manager_2.process_input( + receiver_index, + LpInput::SendData(LpData::new_opaque(b"fail".to_vec())), + ); assert!(send_after_close_b.is_err()); assert!(matches!( send_after_close_b.err().unwrap(), diff --git a/common/nym-lp/src/session_manager.rs b/common/nym-lp/src/session_manager.rs index 8eb1bbcb3d..9988167164 100644 --- a/common/nym-lp/src/session_manager.rs +++ b/common/nym-lp/src/session_manager.rs @@ -6,12 +6,11 @@ //! This module implements session lifecycle management functionality, handling //! creation, retrieval, and storage of sessions. -use dashmap::DashMap; -use nym_crypto::asymmetric::ed25519; - use crate::noise_protocol::ReadResult; +use crate::peer::{LpLocalPeer, LpRemotePeer}; use crate::state_machine::{LpAction, LpInput, LpState, LpStateBare}; use crate::{LpError, LpMessage, LpSession, LpStateMachine}; +use dashmap::DashMap; /// Manages the lifecycle of Lewes Protocol sessions. /// @@ -169,17 +168,19 @@ impl SessionManager { pub fn create_session_state_machine( &self, receiver_index: u32, - local_ed25519_keypair: (&ed25519::PrivateKey, &ed25519::PublicKey), - remote_ed25519_key: &ed25519::PublicKey, is_initiator: bool, + local_peer: LpLocalPeer, + remote_peer: LpRemotePeer, salt: &[u8; 32], + protocol_version: u8, ) -> Result { let sm = LpStateMachine::new( receiver_index, is_initiator, - local_ed25519_keypair, - remote_ed25519_key, + local_peer, + remote_peer, salt, + protocol_version, )?; self.state_machines.insert(receiver_index, sm); @@ -199,7 +200,7 @@ impl SessionManager { pub fn init_kkt_for_test( &self, lp_id: u32, - remote_x25519_pub: &crate::keypair::PublicKey, + remote_x25519_pub: &nym_crypto::asymmetric::x25519::PublicKey, ) -> Result<(), LpError> { self.with_state_machine(lp_id, |sm| { sm.session()?.set_kkt_completed_for_test(remote_x25519_pub); @@ -211,22 +212,28 @@ impl SessionManager { #[cfg(test)] mod tests { use super::*; - use nym_crypto::asymmetric::ed25519; + use crate::packet::version; + use crate::peer::{mock_peers, random_peer}; + use nym_test_utils::helpers::deterministic_rng; #[test] fn test_session_manager_get() { let manager = SessionManager::new(); - let ed25519_keypair = ed25519::KeyPair::from_secret([10u8; 32], 0); + let mut rng = deterministic_rng(); + let local = random_peer(&mut rng); + let peer1 = random_peer(&mut rng); + let salt = [47u8; 32]; let receiver_index: u32 = 1001; let sm_1_id = manager .create_session_state_machine( receiver_index, - (ed25519_keypair.private_key(), ed25519_keypair.public_key()), - ed25519_keypair.public_key(), true, + local, + peer1.as_remote(), &salt, + version::CURRENT, ) .unwrap(); @@ -240,17 +247,21 @@ mod tests { #[test] fn test_session_manager_remove() { let manager = SessionManager::new(); - let ed25519_keypair = ed25519::KeyPair::from_secret([11u8; 32], 0); + let mut rng = deterministic_rng(); + let local = random_peer(&mut rng); + let peer1 = random_peer(&mut rng); + let salt = [48u8; 32]; let receiver_index: u32 = 2002; let sm_1_id = manager .create_session_state_machine( receiver_index, - (ed25519_keypair.private_key(), ed25519_keypair.public_key()), - ed25519_keypair.public_key(), true, + local, + peer1.as_remote(), &salt, + version::CURRENT, ) .unwrap(); @@ -265,47 +276,44 @@ mod tests { #[test] fn test_multiple_sessions() { let manager = SessionManager::new(); - let ed25519_keypair_1 = ed25519::KeyPair::from_secret([12u8; 32], 0); - let ed25519_keypair_2 = ed25519::KeyPair::from_secret([13u8; 32], 1); - let ed25519_keypair_3 = ed25519::KeyPair::from_secret([14u8; 32], 2); + let mut rng = deterministic_rng(); + let local = random_peer(&mut rng); + let peer1 = random_peer(&mut rng); + let peer2 = random_peer(&mut rng); + let peer3 = random_peer(&mut rng); + let salt = [49u8; 32]; let sm_1 = manager .create_session_state_machine( 3001, - ( - ed25519_keypair_1.private_key(), - ed25519_keypair_1.public_key(), - ), - ed25519_keypair_1.public_key(), true, + local.clone(), + peer1.as_remote(), &salt, + version::CURRENT, ) .unwrap(); let sm_2 = manager .create_session_state_machine( 3002, - ( - ed25519_keypair_2.private_key(), - ed25519_keypair_2.public_key(), - ), - ed25519_keypair_2.public_key(), true, + local.clone(), + peer2.as_remote(), &salt, + version::CURRENT, ) .unwrap(); let sm_3 = manager .create_session_state_machine( 3003, - ( - ed25519_keypair_3.private_key(), - ed25519_keypair_3.public_key(), - ), - ed25519_keypair_3.public_key(), true, + local.clone(), + peer3.as_remote(), &salt, + version::CURRENT, ) .unwrap(); @@ -323,16 +331,18 @@ mod tests { #[test] fn test_session_manager_create_session() { let manager = SessionManager::new(); - let ed25519_keypair = ed25519::KeyPair::from_secret([15u8; 32], 0); + let (init, resp) = mock_peers(); + let salt = [50u8; 32]; let receiver_index: u32 = 4004; let sm = manager.create_session_state_machine( receiver_index, - (ed25519_keypair.private_key(), ed25519_keypair.public_key()), - ed25519_keypair.public_key(), true, + init, + resp.as_remote(), &salt, + version::CURRENT, ); assert!(sm.is_ok()); diff --git a/common/nym-lp/src/state_machine.rs b/common/nym-lp/src/state_machine.rs index ddd63e8d13..91fa48cbe7 100644 --- a/common/nym-lp/src/state_machine.rs +++ b/common/nym-lp/src/state_machine.rs @@ -13,16 +13,17 @@ //! State machine ensures protocol steps execute in correct order. Invalid transitions //! return LpError, preventing protocol violations. +use crate::peer::{LpLocalPeer, LpRemotePeer}; use crate::{ LpError, - keypair::{Keypair, PrivateKey as LpPrivateKey, PublicKey as LpPublicKey}, message::{LpMessage, SubsessionKK1Data, SubsessionKK2Data, SubsessionReadyData}, noise_protocol::NoiseError, packet::LpPacket, session::{LpSession, SubsessionHandshake}, }; -use bytes::BytesMut; -use nym_crypto::asymmetric::ed25519; +use bytes::{Buf, Bytes}; +use num_enum::{IntoPrimitive, TryFromPrimitive}; +use nym_kkt::ciphersuite::EncapsulationKey; use std::mem; use tracing::debug; @@ -90,6 +91,7 @@ impl From<&LpState> for LpStateBare { } /// Represents inputs that drive the state machine transitions. +#[allow(clippy::large_enum_variant)] #[derive(Debug)] pub enum LpInput { /// Explicitly trigger the start of the handshake (optional, could be implicit on creation) @@ -97,7 +99,7 @@ pub enum LpInput { /// Received an LP Packet from the network. ReceivePacket(LpPacket), /// Application wants to send data (only valid in Transport state). - SendData(Vec), // Using Bytes for efficiency + SendData(LpData), /// Close the connection. Close, /// Initiate a subsession handshake (only valid in Transport state). @@ -111,7 +113,7 @@ pub enum LpAction { /// Send an LP Packet over the network. SendPacket(LpPacket), /// Deliver decrypted application data received from the peer. - DeliverData(BytesMut), + DeliverData(LpData), /// Inform the environment that KKT exchange completed successfully. KKTComplete, /// Inform the environment that the handshake is complete. @@ -134,6 +136,75 @@ pub enum LpAction { }, } +/// Represent application data being sent in Transport mode +#[derive(Debug, Clone, PartialEq)] +pub struct LpData { + pub kind: LpDataKind, + pub content: Bytes, +} + +impl AsRef<[u8]> for LpData { + fn as_ref(&self) -> &[u8] { + &self.content + } +} + +impl LpData { + pub fn new(kind: LpDataKind, content: impl Into) -> Self { + Self { + kind, + content: content.into(), + } + } + pub fn new_opaque(content: impl Into) -> Self { + Self::new(LpDataKind::Opaque, content) + } + + pub fn new_registration(data: impl Into) -> Self { + Self::new(LpDataKind::Registration, data) + } + + pub fn new_forward(data: impl Into) -> Self { + Self::new(LpDataKind::Forward, data) + } + + pub fn to_vec(self) -> Vec { + self.into() + } +} + +impl From for Vec { + fn from(data: LpData) -> Self { + let mut out = Vec::with_capacity(data.content.len() + 1); + out.push(data.kind as u8); + out.extend_from_slice(data.content.as_ref()); + out + } +} + +impl TryFrom> for LpData { + type Error = LpError; + + fn try_from(value: Vec) -> Result { + let kind = LpDataKind::try_from(value[0]).map_err(|_| { + LpError::DeserializationError(format!("unknown data type: {}", value[0])) + })?; + let mut content = Bytes::from(value); + content.advance(1); + + Ok(LpData::new(kind, content)) + } +} + +/// Represent kind of application data being sent in Transport mode +#[derive(Clone, Copy, PartialEq, Eq, Debug, IntoPrimitive, TryFromPrimitive)] +#[repr(u8)] +pub enum LpDataKind { + Opaque = 0, + Registration = 1, + Forward = 2, +} + /// The Lewes Protocol State Machine. pub struct LpStateMachine { pub state: LpState, @@ -177,70 +248,33 @@ impl LpStateMachine { Ok(self.session()?.id()) } - /// Creates a new state machine from Ed25519 keys, internally deriving X25519 keys. - /// - /// This is the primary constructor that accepts only Ed25519 keys (identity/signing keys) - /// and internally derives the X25519 keys needed for Noise protocol and DHKEM. - /// This simplifies the API by hiding the X25519 derivation as an implementation detail. + /// Creates a new state machine from Ed25519 keys. /// /// # Arguments /// /// * `receiver_index` - Client-proposed session identifier (random 4 bytes) /// * `is_initiator` - Whether this side initiates the handshake - /// * `local_ed25519_keypair` - Ed25519 keypair for PSQ authentication and X25519 derivation - /// (from client identity key or gateway signing key) - /// * `remote_ed25519_key` - Peer's Ed25519 public key for PSQ authentication and X25519 derivation + /// * `local_peer` - This side's LP peer's keys + /// * `remote_peer` - The remote's LP peer's keys /// * `salt` - Fresh salt for PSK derivation (must be unique per session) - /// - /// # Errors - /// - /// Returns `LpError::Ed25519RecoveryError` if Ed25519→X25519 conversion fails for the remote key. - /// Local private key conversion cannot fail. + /// * `protocol_version` - Protocol version to attach in all `LpPacket`s pub fn new( receiver_index: u32, is_initiator: bool, - local_ed25519_keypair: (&ed25519::PrivateKey, &ed25519::PublicKey), - remote_ed25519_key: &ed25519::PublicKey, + local_peer: LpLocalPeer, + remote_peer: LpRemotePeer, salt: &[u8; 32], + protocol_version: u8, ) -> Result { - // We use standard RFC 7748 conversion to derive X25519 keys from Ed25519 identity keys. - // This allows callers to provide only Ed25519 keys (which they already have for signing/identity) - // without needing to manage separate X25519 keypairs. - // - // Security: Ed25519→X25519 conversion is cryptographically sound (RFC 7748). - // The derived X25519 keys are used for: - // - Noise protocol ephemeral DH - // - PSQ ECDH baseline security (pre-quantum) - - // Convert Ed25519 keys to X25519 for Noise protocol - let local_x25519_private = local_ed25519_keypair.0.to_x25519(); - let local_x25519_public = local_ed25519_keypair - .1 - .to_x25519() - .map_err(LpError::Ed25519RecoveryError)?; - - let remote_x25519_public = remote_ed25519_key - .to_x25519() - .map_err(LpError::Ed25519RecoveryError)?; - - // Convert nym_crypto X25519 types to nym_lp keypair types - let lp_private = LpPrivateKey::from_bytes(local_x25519_private.as_bytes()); - let lp_public = LpPublicKey::from_bytes(local_x25519_public.as_bytes())?; - let lp_remote_public = LpPublicKey::from_bytes(remote_x25519_public.as_bytes())?; - - // Create X25519 keypair for Noise - let local_x25519_keypair = Keypair::from_keys(lp_private, lp_public); - // Create the session with both Ed25519 (for PSQ auth) and derived X25519 keys (for Noise) // receiver_index is client-proposed, passed through directly let session = LpSession::new( receiver_index, is_initiator, - local_ed25519_keypair, - local_x25519_keypair.private_key(), - remote_ed25519_key, - &lp_remote_public, + local_peer, + remote_peer, salt, + protocol_version, )?; Ok(LpStateMachine { @@ -331,33 +365,44 @@ impl LpStateMachine { result_action = Some(Err(LpError::UnknownSessionId(packet.header.receiver_idx()))); LpState::KKTExchange { session } } else { - use crate::message::LpMessage; // Packet message is already parsed, match on it directly match &packet.message { LpMessage::KKTRequest(kkt_request) if !session.is_initiator() => { // Responder processes KKT request // Convert X25519 public key to KEM format for KKT response - use nym_kkt::ciphersuite::EncapsulationKey; - // Get local X25519 public key by deriving from private key - let local_x25519_public = session.local_x25519_public(); + // Get local KEM key + match session.get_kem_key_handle() { + Err(err) => { + let reason = err.to_string(); + result_action = Some(Err(err)); + LpState::Closed { reason } + } + Ok(local_kem_psq_public) => { + // Convert to libcrux KEM public key + // V1 is X255519 + match libcrux_kem::PublicKey::decode( + libcrux_kem::Algorithm::X25519, + local_kem_psq_public.as_bytes(), + ) { + Ok(libcrux_public_key) => { + let responder_kem_pk = EncapsulationKey::X25519(libcrux_public_key); - // Convert to libcrux KEM public key - match libcrux_kem::PublicKey::decode( - libcrux_kem::Algorithm::X25519, - local_x25519_public.as_bytes(), - ) { - Ok(libcrux_public_key) => { - let responder_kem_pk = EncapsulationKey::X25519(libcrux_public_key); - - match session.process_kkt_request(&kkt_request.0, &responder_kem_pk) { - Ok(kkt_response_message) => { - match session.next_packet(kkt_response_message) { - Ok(response_packet) => { - result_action = Some(Ok(LpAction::SendPacket(response_packet))); - // After KKT exchange, move to Handshaking - LpState::Handshaking { session } + match session.process_kkt_request(&kkt_request.0, &responder_kem_pk) { + Ok(kkt_response_message) => { + match session.next_packet(kkt_response_message) { + Ok(response_packet) => { + result_action = Some(Ok(LpAction::SendPacket(response_packet))); + // After KKT exchange, move to Handshaking + LpState::Handshaking { session } + } + Err(e) => { + let reason = e.to_string(); + result_action = Some(Err(e)); + LpState::Closed { reason } + } + } } Err(e) => { let reason = e.to_string(); @@ -367,23 +412,17 @@ impl LpStateMachine { } } Err(e) => { - let reason = e.to_string(); - result_action = Some(Err(e)); + let reason = format!("Failed to convert X25519 to KEM: {:?}", e); + let err = LpError::Internal(reason.clone()); + result_action = Some(Err(err)); LpState::Closed { reason } } } - } - Err(e) => { - let reason = format!("Failed to convert X25519 to KEM: {:?}", e); - let err = LpError::Internal(reason.clone()); - result_action = Some(Err(err)); - LpState::Closed { reason } - } + }, } } LpMessage::KKTResponse(kkt_response) if session.is_initiator() => { - // Initiator processes KKT response (signature-only mode with None) - match session.process_kkt_response(&kkt_response.0, None) { + match session.process_kkt_response(&kkt_response.0) { Ok(()) => { result_action = Some(Ok(LpAction::KKTComplete)); // After successful KKT, move to Handshaking @@ -439,75 +478,75 @@ impl LpStateMachine { // --- Inline handle_handshake_packet logic --- // 1. Check replay protection *before* processing if let Err(e) = session.receiving_counter_quick_check(packet.header.counter) { - let _reason = e.to_string(); - result_action = Some(Err(e)); - LpState::Handshaking { session } + let _reason = e.to_string(); + result_action = Some(Err(e)); + LpState::Handshaking { session } // LpState::Closed { reason } } else { - // 2. Process the handshake message - match session.process_handshake_message(&packet.message) { - Ok(_) => { - // 3. Mark counter as received *after* successful processing - if let Err(e) = session.receiving_counter_mark(packet.header.counter) { - let _reason = e.to_string(); - result_action = Some(Err(e)); + // 2. Process the handshake message + match session.process_handshake_message(&packet.message) { + Ok(_) => { + // 3. Mark counter as received *after* successful processing + if let Err(e) = session.receiving_counter_mark(packet.header.counter) { + let _reason = e.to_string(); + result_action = Some(Err(e)); // LpState::Closed { reason } LpState::Handshaking { session } - } else { - // 4. First check if we need to send a handshake message (before checking completion) - match session.prepare_handshake_message() { - Some(Ok(message)) => { - match session.next_packet(message) { - Ok(response_packet) => { - result_action = Some(Ok(LpAction::SendPacket(response_packet))); - // Check if handshake became complete after preparing message - if session.is_handshake_complete() { - LpState::Transport { session } // Transition to Transport - } else { - LpState::Handshaking { session } // Remain Handshaking - } - } - Err(e) => { - let reason = e.to_string(); - result_action = Some(Err(e)); - LpState::Closed { reason } - } - } - } - Some(Err(e)) => { - let reason = e.to_string(); - result_action = Some(Err(e)); - LpState::Closed { reason } - } - None => { - // 5. No message to send - check if handshake is complete - if session.is_handshake_complete() { - result_action = Some(Ok(LpAction::HandshakeComplete)); - LpState::Transport { session } // Transition to Transport - } else { - // Handshake stalled unexpectedly - let err = LpError::NoiseError(NoiseError::Other( - "Handshake stalled unexpectedly".to_string(), - )); - let reason = err.to_string(); - result_action = Some(Err(err)); - LpState::Closed { reason } - } - } - } - } - } - Err(e) => { // Error from process_handshake_message - let reason = e.to_string(); - result_action = Some(Err(e)); - LpState::Closed { reason } - } - } + } else { + // 4. First check if we need to send a handshake message (before checking completion) + match session.prepare_handshake_message() { + Some(Ok(message)) => { + match session.next_packet(message) { + Ok(response_packet) => { + result_action = Some(Ok(LpAction::SendPacket(response_packet))); + // Check if handshake became complete after preparing message + if session.is_handshake_complete() { + LpState::Transport { session } // Transition to Transport + } else { + LpState::Handshaking { session } // Remain Handshaking + } + } + Err(e) => { + let reason = e.to_string(); + result_action = Some(Err(e)); + LpState::Closed { reason } + } + } + } + Some(Err(e)) => { + let reason = e.to_string(); + result_action = Some(Err(e)); + LpState::Closed { reason } + } + None => { + // 5. No message to send - check if handshake is complete + if session.is_handshake_complete() { + result_action = Some(Ok(LpAction::HandshakeComplete)); + LpState::Transport { session } // Transition to Transport + } else { + // Handshake stalled unexpectedly + let err = LpError::NoiseError(NoiseError::Other( + "Handshake stalled unexpectedly".to_string(), + )); + let reason = err.to_string(); + result_action = Some(Err(err)); + LpState::Closed { reason } + } + } + } + } + } + Err(e) => { // Error from process_handshake_message + let reason = e.to_string(); + result_action = Some(Err(e)); + LpState::Closed { reason } + } + } } // --- End inline handle_handshake_packet logic --- } } - // Reject SendData during handshake + // Reject SendData during handshake (LpState::Handshaking { session }, LpInput::SendData(_)) => { // Keep session if returning to this state result_action = Some(Err(LpError::InvalidStateTransition { state: "Handshaking".to_string(), @@ -522,118 +561,127 @@ impl LpStateMachine { state: "Handshaking".to_string(), input: "StartHandshake".to_string(), })); - // Invalid input, remain in Handshaking state - LpState::Handshaking { session } + // Invalid input, remain in Handshaking state + LpState::Handshaking { session } } // --- Transport State --- (LpState::Transport { session }, LpInput::ReceivePacket(packet)) => { - // Check if packet lp_id matches our session - if packet.header.receiver_idx() != session.id() { + // Check if packet lp_id matches our session + if packet.header.receiver_idx() != session.id() { result_action = Some(Err(LpError::UnknownSessionId(packet.header.receiver_idx()))); LpState::Transport { session } - } else { - // Check message type - handle subsession initiation from peer - match &packet.message { - // Peer initiated subsession - we become responder - LpMessage::SubsessionKK1(kk1_data) => { - // Create subsession as responder - let subsession_index = session.next_subsession_index(); - match session.create_subsession(subsession_index, false) { - Ok(subsession) => { - // Process KK1 - match subsession.process_message(&kk1_data.payload) { - Ok(_) => { - // Prepare KK2 response - match subsession.prepare_message() { - Ok(kk2_payload) => { - let kk2_msg = LpMessage::SubsessionKK2(SubsessionKK2Data { payload: kk2_payload }); - match session.next_packet(kk2_msg) { - Ok(response_packet) => { - result_action = Some(Ok(LpAction::SendPacket(response_packet))); - // Stay in SubsessionHandshaking, wait for SubsessionReady - LpState::SubsessionHandshaking { session, subsession: Box::new(subsession) } - } - Err(e) => { - let reason = e.to_string(); - result_action = Some(Err(e)); - LpState::Closed { reason } - } - } - } - Err(e) => { - let reason = e.to_string(); - result_action = Some(Err(e)); - LpState::Closed { reason } - } - } - } - Err(e) => { - let reason = e.to_string(); - result_action = Some(Err(e)); - LpState::Closed { reason } - } - } - } - Err(e) => { - let reason = e.to_string(); - result_action = Some(Err(e)); - LpState::Closed { reason } - } - } - } - // Normal encrypted data - LpMessage::EncryptedData(_) => { - // 1. Check replay protection - if let Err(e) = session.receiving_counter_quick_check(packet.header.counter) { - result_action = Some(Err(e)); - LpState::Transport { session } - } else { - // 2. Decrypt data - match session.decrypt_data(&packet.message) { - Ok(plaintext) => { - // 3. Mark counter as received - if let Err(e) = session.receiving_counter_mark(packet.header.counter) { - result_action = Some(Err(e)); - LpState::Transport { session } - } else { - // 4. Deliver data - result_action = Some(Ok(LpAction::DeliverData(BytesMut::from(plaintext.as_slice())))); - LpState::Transport { session } - } - } - Err(e) => { - let reason = e.to_string(); - result_action = Some(Err(e.into())); - LpState::Closed { reason } - } - } - } - } - // Stale abort in Transport state - race already resolved. - // This can happen if abort arrives after loser already returned to Transport - // via KK1 processing (loser detected local < remote and became responder). - // The winner's abort message arrived late. Silently ignore. - LpMessage::SubsessionAbort => { - debug!("Ignoring stale SubsessionAbort in Transport state"); - result_action = None; - LpState::Transport { session } - } - _ => { - // Unexpected message type in Transport state - let err = LpError::InvalidStateTransition { - state: "Transport".to_string(), - input: format!("Unexpected message type: {}", packet.message), - }; - result_action = Some(Err(err)); - LpState::Transport { session } - } - } - } + } else { + // Check message type - handle subsession initiation from peer + match &packet.message { + // Peer initiated subsession - we become responder + LpMessage::SubsessionKK1(kk1_data) => { + // Create subsession as responder + let subsession_index = session.next_subsession_index(); + match session.create_subsession(subsession_index, false) { + Ok(subsession) => { + // Process KK1 + match subsession.process_message(&kk1_data.payload) { + Ok(_) => { + // Prepare KK2 response + match subsession.prepare_message() { + Ok(kk2_payload) => { + let kk2_msg = LpMessage::SubsessionKK2(SubsessionKK2Data { payload: kk2_payload }); + match session.next_packet(kk2_msg) { + Ok(response_packet) => { + result_action = Some(Ok(LpAction::SendPacket(response_packet))); + // Stay in SubsessionHandshaking, wait for SubsessionReady + LpState::SubsessionHandshaking { session, subsession: Box::new(subsession) } + } + Err(e) => { + let reason = e.to_string(); + result_action = Some(Err(e)); + LpState::Closed { reason } + } + } + } + Err(e) => { + let reason = e.to_string(); + result_action = Some(Err(e)); + LpState::Closed { reason } + } + } + } + Err(e) => { + let reason = e.to_string(); + result_action = Some(Err(e)); + LpState::Closed { reason } + } + } + } + Err(e) => { + let reason = e.to_string(); + result_action = Some(Err(e)); + LpState::Closed { reason } + } + } + } + // Normal encrypted data + LpMessage::EncryptedData(_) => { + // 1. Check replay protection + if let Err(e) = session.receiving_counter_quick_check(packet.header.counter) { + result_action = Some(Err(e)); + LpState::Transport { session } + } else { + // 2. Decrypt data + match session.decrypt_data(&packet.message) { + Ok(plaintext) => { + // 3. Mark counter as received + if let Err(e) = session.receiving_counter_mark(packet.header.counter) { + result_action = Some(Err(e)); + LpState::Transport { session } + } else { + // 4. Deliver data + match plaintext.try_into() { + Ok(data) => { + result_action = Some(Ok(LpAction::DeliverData(data))); + LpState::Transport { session } + }, + Err(e) => { + let reason = e.to_string(); + result_action = Some(Err(e)); + LpState::Closed { reason } + } + } + } + } + Err(e) => { + let reason = e.to_string(); + result_action = Some(Err(e.into())); + LpState::Closed { reason } + } + } + } + } + // Stale abort in Transport state - race already resolved. + // This can happen if abort arrives after loser already returned to Transport + // via KK1 processing (loser detected local < remote and became responder). + // The winner's abort message arrived late. Silently ignore. + LpMessage::SubsessionAbort => { + debug!("Ignoring stale SubsessionAbort in Transport state"); + result_action = None; + LpState::Transport { session } + } + _ => { + // Unexpected message type in Transport state + let err = LpError::InvalidStateTransition { + state: "Transport".to_string(), + input: format!("Unexpected message type: {}", packet.message), + }; + result_action = Some(Err(err)); + LpState::Transport { session } + } + } + } } (LpState::Transport { session }, LpInput::SendData(data)) => { // Encrypt and send application data - match self.prepare_data_packet(&session, &data) { + match self.prepare_data_packet(&session, data) { Ok(packet) => result_action = Some(Ok(LpAction::SendPacket(packet))), Err(e) => { // If prepare fails, should we close? Let's report error and stay Transport for now. @@ -641,17 +689,17 @@ impl LpStateMachine { result_action = Some(Err(e.into())); } } - // Remain in transport state - LpState::Transport { session } + // Remain in transport state + LpState::Transport { session } } - // Reject StartHandshake if already in transport + // Reject StartHandshake if already in transport (LpState::Transport { session }, LpInput::StartHandshake) => { // Keep session result_action = Some(Err(LpError::InvalidStateTransition { state: "Transport".to_string(), input: "StartHandshake".to_string(), })); - // Invalid input, remain in Transport state - LpState::Transport { session } + // Invalid input, remain in Transport state + LpState::Transport { session } } // --- Transport + InitiateSubsession → SubsessionHandshaking --- @@ -870,8 +918,16 @@ impl LpStateMachine { result_action = Some(Err(e)); LpState::SubsessionHandshaking { session, subsession } } else { - result_action = Some(Ok(LpAction::DeliverData(BytesMut::from(plaintext.as_slice())))); - LpState::SubsessionHandshaking { session, subsession } + match plaintext.try_into() { + Ok(data) => { + result_action = Some(Ok(LpAction::DeliverData(data))); + LpState::SubsessionHandshaking { session, subsession } + } + Err(err) => { + result_action = Some(Err(err)); + LpState::SubsessionHandshaking { session, subsession } + } + } } } Err(e) => { @@ -937,7 +993,7 @@ impl LpStateMachine { // Parent can still send data during subsession handshake (LpState::SubsessionHandshaking { session, subsession }, LpInput::SendData(data)) => { - match self.prepare_data_packet(&session, &data) { + match self.prepare_data_packet(&session, data) { Ok(packet) => result_action = Some(Ok(LpAction::SendPacket(packet))), Err(e) => { result_action = Some(Err(e.into())); @@ -970,25 +1026,33 @@ impl LpStateMachine { result_action = Some(Err(LpError::UnknownSessionId(packet.header.receiver_idx()))); LpState::ReadOnlyTransport { session } } else if let Err(e) = session.receiving_counter_quick_check(packet.header.counter) { - result_action = Some(Err(e)); - LpState::ReadOnlyTransport { session } - } else { - match session.decrypt_data(&packet.message) { - Ok(plaintext) => { - if let Err(e) = session.receiving_counter_mark(packet.header.counter) { - result_action = Some(Err(e)); - LpState::ReadOnlyTransport { session } - } else { - result_action = Some(Ok(LpAction::DeliverData(BytesMut::from(plaintext.as_slice())))); - LpState::ReadOnlyTransport { session } + result_action = Some(Err(e)); + LpState::ReadOnlyTransport { session } + } else { + match session.decrypt_data(&packet.message) { + Ok(plaintext) => { + if let Err(e) = session.receiving_counter_mark(packet.header.counter) { + result_action = Some(Err(e)); + LpState::ReadOnlyTransport { session } + } else { + match plaintext.try_into() { + Ok(data) => { + result_action = Some(Ok(LpAction::DeliverData(data))); + LpState::ReadOnlyTransport { session } + } + Err(err) => { + result_action = Some(Err(err)); + LpState::ReadOnlyTransport { session } + } } } - Err(e) => { - let reason = e.to_string(); - result_action = Some(Err(e.into())); - LpState::Closed { reason } - } } + Err(e) => { + let reason = e.to_string(); + result_action = Some(Err(e.into())); + LpState::Closed { reason } + } + } } } @@ -1026,8 +1090,8 @@ impl LpStateMachine { LpInput::Close, ) => { result_action = Some(Ok(LpAction::ConnectionClosed)); - // Transition to Closed state - LpState::Closed { reason: "Closed by user".to_string() } + // Transition to Closed state + LpState::Closed { reason: "Closed by user".to_string() } } // Ignore Close if already Closed (closed_state @ LpState::Closed { .. }, LpInput::Close) => { @@ -1040,36 +1104,36 @@ impl LpStateMachine { // result_action = Some(Err(LpError::LpSessionClosed)); // closed_state // } - // Ignore ReceivePacket if Closed + // Ignore ReceivePacket if Closed (closed_state @ LpState::Closed { .. }, LpInput::ReceivePacket(_)) => { - result_action = Some(Err(LpError::LpSessionClosed)); - closed_state + result_action = Some(Err(LpError::LpSessionClosed)); + closed_state } - // Ignore SendData if Closed + // Ignore SendData if Closed (closed_state @ LpState::Closed { .. }, LpInput::SendData(_)) => { - result_action = Some(Err(LpError::LpSessionClosed)); - closed_state + result_action = Some(Err(LpError::LpSessionClosed)); + closed_state } // Processing state should not be matched directly if using replace (LpState::Processing, _) => { - // This case should ideally be unreachable if placeholder logic is correct - let err = LpError::Internal("Reached Processing state unexpectedly".to_string()); - let reason = err.to_string(); - result_action = Some(Err(err)); - LpState::Closed { reason } + // This case should ideally be unreachable if placeholder logic is correct + let err = LpError::Internal("Reached Processing state unexpectedly".to_string()); + let reason = err.to_string(); + result_action = Some(Err(err)); + LpState::Closed { reason } } // --- Default: Invalid input for current state (if any combinations missed) --- // Consider if this should transition to Closed state. For now, just report error // and transition to Closed as a safety measure. (invalid_state, input) => { - let err = LpError::InvalidStateTransition { - state: format!("{:?}", invalid_state), // Use owned state for debug info - input: format!("{:?}", input), - }; - let reason = err.to_string(); - result_action = Some(Err(err)); - LpState::Closed { reason } + let err = LpError::InvalidStateTransition { + state: format!("{:?}", invalid_state), // Use owned state for debug info + input: format!("{:?}", input), + }; + let reason = err.to_string(); + result_action = Some(Err(err)); + LpState::Closed { reason } } }; @@ -1084,9 +1148,9 @@ impl LpStateMachine { fn prepare_data_packet( &self, session: &LpSession, - data: &[u8], + data: LpData, ) -> Result { - let encrypted_message = session.encrypt_data(data)?; + let encrypted_message = session.encrypt_data(Vec::::from(data).as_ref())?; session .next_packet(encrypted_message) .map_err(|e| NoiseError::Other(e.to_string())) // Improve error conversion? @@ -1096,14 +1160,12 @@ impl LpStateMachine { #[cfg(test)] mod tests { use super::*; - use bytes::Bytes; - use nym_crypto::asymmetric::ed25519; + use crate::packet::version; + use crate::peer::mock_peers; #[test] fn test_state_machine_init() { - // Ed25519 keypairs for PSQ authentication and X25519 derivation - let ed25519_keypair_init = ed25519::KeyPair::from_secret([16u8; 32], 0); - let ed25519_keypair_resp = ed25519::KeyPair::from_secret([17u8; 32], 1); + let (init, resp) = mock_peers(); // Test salt let salt = [51u8; 32]; @@ -1113,12 +1175,10 @@ mod tests { let initiator_sm = LpStateMachine::new( receiver_index, true, - ( - ed25519_keypair_init.private_key(), - ed25519_keypair_init.public_key(), - ), - ed25519_keypair_resp.public_key(), + init.clone(), + resp.as_remote(), &salt, + version::CURRENT, ); assert!(initiator_sm.is_ok()); let initiator_sm = initiator_sm.unwrap(); @@ -1132,12 +1192,10 @@ mod tests { let responder_sm = LpStateMachine::new( receiver_index, false, - ( - ed25519_keypair_resp.private_key(), - ed25519_keypair_resp.public_key(), - ), - ed25519_keypair_init.public_key(), + resp, + init.as_remote(), &salt, + version::CURRENT, ); assert!(responder_sm.is_ok()); let responder_sm = responder_sm.unwrap(); @@ -1154,9 +1212,7 @@ mod tests { #[test] fn test_state_machine_simplified_flow() { - // Ed25519 keypairs for PSQ authentication and X25519 derivation - let ed25519_keypair_init = ed25519::KeyPair::from_secret([18u8; 32], 0); - let ed25519_keypair_resp = ed25519::KeyPair::from_secret([19u8; 32], 1); + let (init, resp) = mock_peers(); // Test salt let salt = [52u8; 32]; @@ -1166,24 +1222,20 @@ mod tests { let mut initiator = LpStateMachine::new( receiver_index, true, // is_initiator - ( - ed25519_keypair_init.private_key(), - ed25519_keypair_init.public_key(), - ), - ed25519_keypair_resp.public_key(), + init.clone(), + resp.as_remote(), &salt, + version::CURRENT, ) .unwrap(); let mut responder = LpStateMachine::new( receiver_index, false, // is_initiator - ( - ed25519_keypair_resp.private_key(), - ed25519_keypair_resp.public_key(), - ), - ed25519_keypair_init.public_key(), + resp, + init.as_remote(), &salt, + version::CURRENT, ) .unwrap(); @@ -1306,8 +1358,8 @@ mod tests { // --- Transport Phase --- println!("--- Step 8: Initiator sends data ---"); - let data_to_send_1 = b"hello responder"; - let init_actions_4 = initiator.process_input(LpInput::SendData(data_to_send_1.to_vec())); + let data_to_send_1 = LpData::new_opaque(b"hello responder".to_vec()); + let init_actions_4 = initiator.process_input(LpInput::SendData(data_to_send_1.clone())); let data_packet_1 = if let Some(Ok(LpAction::SendPacket(packet))) = init_actions_4 { packet.clone() } else { @@ -1322,11 +1374,11 @@ mod tests { } else { panic!("Responder should deliver data"); }; - assert_eq!(resp_data_1, Bytes::copy_from_slice(data_to_send_1)); + assert_eq!(resp_data_1, data_to_send_1); println!("--- Step 10: Responder sends data ---"); - let data_to_send_2 = b"hello initiator"; - let resp_actions_6 = responder.process_input(LpInput::SendData(data_to_send_2.to_vec())); + let data_to_send_2 = LpData::new_opaque(b"hello initiator".to_vec()); + let resp_actions_6 = responder.process_input(LpInput::SendData(data_to_send_2.clone())); let data_packet_2 = if let Some(Ok(LpAction::SendPacket(packet))) = resp_actions_6 { packet.clone() } else { @@ -1337,7 +1389,7 @@ mod tests { println!("--- Step 11: Initiator receives data ---"); let init_actions_5 = initiator.process_input(LpInput::ReceivePacket(data_packet_2)); if let Some(Ok(LpAction::DeliverData(data))) = init_actions_5 { - assert_eq!(data, Bytes::copy_from_slice(data_to_send_2)); + assert_eq!(data, data_to_send_2); } else { panic!("Initiator should deliver data"); } @@ -1362,9 +1414,7 @@ mod tests { #[test] fn test_kkt_exchange_initiator_flow() { - // Ed25519 keypairs for PSQ authentication and X25519 derivation - let ed25519_keypair_init = ed25519::KeyPair::from_secret([20u8; 32], 0); - let ed25519_keypair_resp = ed25519::KeyPair::from_secret([21u8; 32], 1); + let (init, resp) = mock_peers(); let salt = [53u8; 32]; let receiver_index: u32 = 99901; @@ -1373,12 +1423,10 @@ mod tests { let mut initiator = LpStateMachine::new( receiver_index, true, - ( - ed25519_keypair_init.private_key(), - ed25519_keypair_init.public_key(), - ), - ed25519_keypair_resp.public_key(), + init, + resp.as_remote(), &salt, + version::CURRENT, ) .unwrap(); @@ -1393,9 +1441,7 @@ mod tests { #[test] fn test_kkt_exchange_responder_flow() { - // Ed25519 keypairs for PSQ authentication and X25519 derivation - let ed25519_keypair_init = ed25519::KeyPair::from_secret([22u8; 32], 0); - let ed25519_keypair_resp = ed25519::KeyPair::from_secret([23u8; 32], 1); + let (init, resp) = mock_peers(); let salt = [54u8; 32]; let receiver_index: u32 = 99902; @@ -1404,12 +1450,10 @@ mod tests { let mut responder = LpStateMachine::new( receiver_index, false, - ( - ed25519_keypair_resp.private_key(), - ed25519_keypair_resp.public_key(), - ), - ed25519_keypair_init.public_key(), + resp, + init.as_remote(), &salt, + version::CURRENT, ) .unwrap(); @@ -1425,8 +1469,7 @@ mod tests { #[test] fn test_kkt_exchange_full_roundtrip() { // Ed25519 keypairs for PSQ authentication and X25519 derivation - let ed25519_keypair_init = ed25519::KeyPair::from_secret([24u8; 32], 0); - let ed25519_keypair_resp = ed25519::KeyPair::from_secret([25u8; 32], 1); + let (init, resp) = mock_peers(); let salt = [55u8; 32]; let receiver_index: u32 = 99903; @@ -1435,24 +1478,20 @@ mod tests { let mut initiator = LpStateMachine::new( receiver_index, true, - ( - ed25519_keypair_init.private_key(), - ed25519_keypair_init.public_key(), - ), - ed25519_keypair_resp.public_key(), + init.clone(), + resp.as_remote(), &salt, + version::CURRENT, ) .unwrap(); let mut responder = LpStateMachine::new( receiver_index, false, - ( - ed25519_keypair_resp.private_key(), - ed25519_keypair_resp.public_key(), - ), - ed25519_keypair_init.public_key(), + resp, + init.as_remote(), &salt, + version::CURRENT, ) .unwrap(); @@ -1482,6 +1521,7 @@ mod tests { // Step 4: Initiator receives KKT response, completes KKT let init_action = initiator.process_input(LpInput::ReceivePacket(kkt_response_packet)); + assert!(matches!(init_action, Some(Ok(LpAction::KKTComplete)))); // After KKT complete, initiator moves to Handshaking assert!(matches!(initiator.state, LpState::Handshaking { .. })); @@ -1489,9 +1529,7 @@ mod tests { #[test] fn test_kkt_exchange_close() { - // Ed25519 keypairs for KKT authentication - let ed25519_keypair_init = ed25519::KeyPair::from_secret([26u8; 32], 0); - let ed25519_keypair_resp = ed25519::KeyPair::from_secret([27u8; 32], 1); + let (init, resp) = mock_peers(); let salt = [56u8; 32]; let receiver_index: u32 = 99904; @@ -1500,12 +1538,10 @@ mod tests { let mut initiator = LpStateMachine::new( receiver_index, true, - ( - ed25519_keypair_init.private_key(), - ed25519_keypair_init.public_key(), - ), - ed25519_keypair_resp.public_key(), + init.clone(), + resp.as_remote(), &salt, + version::CURRENT, ) .unwrap(); @@ -1521,9 +1557,7 @@ mod tests { #[test] fn test_kkt_exchange_rejects_invalid_inputs() { - // Ed25519 keypairs for KKT authentication - let ed25519_keypair_init = ed25519::KeyPair::from_secret([28u8; 32], 0); - let ed25519_keypair_resp = ed25519::KeyPair::from_secret([29u8; 32], 1); + let (init, resp) = mock_peers(); let salt = [57u8; 32]; let receiver_index: u32 = 99905; @@ -1532,12 +1566,10 @@ mod tests { let mut initiator = LpStateMachine::new( receiver_index, true, - ( - ed25519_keypair_init.private_key(), - ed25519_keypair_init.public_key(), - ), - ed25519_keypair_resp.public_key(), + init.clone(), + resp.as_remote(), &salt, + version::CURRENT, ) .unwrap(); @@ -1546,7 +1578,8 @@ mod tests { assert!(matches!(initiator.state, LpState::KKTExchange { .. })); // Try SendData during KKT exchange (should be rejected) - let send_action = initiator.process_input(LpInput::SendData(vec![1, 2, 3])); + let send_data = LpData::new_opaque(vec![1, 2, 3]); + let send_action = initiator.process_input(LpInput::SendData(send_data)); assert!(matches!( send_action, Some(Err(LpError::InvalidStateTransition { .. })) @@ -1565,10 +1598,7 @@ mod tests { /// Helper function to complete a full handshake between initiator and responder, /// returning both in Transport state ready for subsession testing. fn setup_transport_sessions() -> (LpStateMachine, LpStateMachine) { - // Use different seeds to get different X25519 keys. - // The tie-breaker compares X25519 public keys. - let ed25519_keypair_a = ed25519::KeyPair::from_secret([30u8; 32], 0); - let ed25519_keypair_b = ed25519::KeyPair::from_secret([31u8; 32], 1); + let (a, b) = mock_peers(); let salt = [60u8; 32]; let receiver_index: u32 = 111111; @@ -1577,24 +1607,20 @@ mod tests { let mut alice = LpStateMachine::new( receiver_index, true, - ( - ed25519_keypair_a.private_key(), - ed25519_keypair_a.public_key(), - ), - ed25519_keypair_b.public_key(), + a.clone(), + b.as_remote(), &salt, + version::CURRENT, ) .unwrap(); let mut bob = LpStateMachine::new( receiver_index, false, - ( - ed25519_keypair_b.private_key(), - ed25519_keypair_b.public_key(), - ), - ed25519_keypair_a.public_key(), + b, + a.as_remote(), &salt, + version::CURRENT, ) .unwrap(); diff --git a/common/nym-metrics/Cargo.toml b/common/nym-metrics/Cargo.toml index 1ac3bce16a..491b72f07d 100644 --- a/common/nym-metrics/Cargo.toml +++ b/common/nym-metrics/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "nym-metrics" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "Metrics macros" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/common/nym_offline_compact_ecash/Cargo.toml b/common/nym_offline_compact_ecash/Cargo.toml index 718787b457..e70063bd17 100644 --- a/common/nym_offline_compact_ecash/Cargo.toml +++ b/common/nym_offline_compact_ecash/Cargo.toml @@ -3,15 +3,19 @@ [package] name = "nym-compact-ecash" -version = "0.1.0" +version.workspace = true authors = ["Ania Piotrowska "] edition = "2021" license = { workspace = true } +description = "Nym's ecash implementation" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bls12_381 = { workspace = true, features = ["alloc", "pairings", "experimental", "zeroize", "experimental_serde"] } +nym-bls12_381-fork = { workspace = true, features = ["alloc", "pairings", "experimental", "zeroize", "experimental_serde"] } bincode.workspace = true cfg-if.workspace = true itertools = { workspace = true } @@ -27,8 +31,8 @@ ff = { workspace = true } group = { workspace = true } subtle = { workspace = true } -nym-pemstore = { path = "../pemstore" } -nym-network-defaults = { path = "../network-defaults", default-features = false } +nym-pemstore = { workspace = true } +nym-network-defaults = { workspace = true } [dev-dependencies] criterion = { workspace = true, features = ["html_reports"] } diff --git a/common/nym_offline_compact_ecash/benches/benchmarks_group_operations.rs b/common/nym_offline_compact_ecash/benches/benchmarks_group_operations.rs index 73c74472e7..a6769200b0 100644 --- a/common/nym_offline_compact_ecash/benches/benchmarks_group_operations.rs +++ b/common/nym_offline_compact_ecash/benches/benchmarks_group_operations.rs @@ -4,24 +4,24 @@ use std::ops::Neg; use std::time::Duration; -use bls12_381::{ - multi_miller_loop, G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, Gt, Scalar, -}; use criterion::{criterion_group, criterion_main, Criterion}; use ff::Field; use group::{Curve, Group}; +use nym_bls12_381_fork::{ + multi_miller_loop, G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, Gt, Scalar, +}; use nym_compact_ecash::utils::check_bilinear_pairing; #[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); + let gt1 = nym_bls12_381_fork::pairing(g11, g21); + let gt2 = nym_bls12_381_fork::pairing(g12, g22); assert_eq!(gt1, gt2) } #[allow(unused)] fn single_pairing(g11: &G1Affine, g21: &G2Affine) { - let gt1 = bls12_381::pairing(g11, g21); + let gt1 = nym_bls12_381_fork::pairing(g11, g21); } #[allow(unused)] @@ -98,7 +98,7 @@ fn bench_group_operations(c: &mut Criterion) { let g22 = (g2 * r).to_affine(); let g22_prep = G2Prepared::from(g22); - let gt = bls12_381::pairing(&g11, &g21); + let gt = nym_bls12_381_fork::pairing(&g11, &g21); let gen1 = G1Projective::generator(); let gen2 = G2Projective::generator(); diff --git a/common/nym_offline_compact_ecash/src/common_types.rs b/common/nym_offline_compact_ecash/src/common_types.rs index bc450f97a4..90ce4ac0f7 100644 --- a/common/nym_offline_compact_ecash/src/common_types.rs +++ b/common/nym_offline_compact_ecash/src/common_types.rs @@ -7,7 +7,7 @@ use crate::helpers::{g1_tuple_to_bytes, recover_g1_tuple}; use serde::{Deserialize, Serialize}; use subtle::Choice; -pub use bls12_381::{G1Projective, G2Projective, Scalar}; +pub use nym_bls12_381_fork::{G1Projective, G2Projective, Scalar}; pub type SignerIndex = u64; #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] diff --git a/common/nym_offline_compact_ecash/src/constants.rs b/common/nym_offline_compact_ecash/src/constants.rs index 0902c2fa0f..b58762d9a8 100644 --- a/common/nym_offline_compact_ecash/src/constants.rs +++ b/common/nym_offline_compact_ecash/src/constants.rs @@ -1,7 +1,7 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use bls12_381::Scalar; +use nym_bls12_381_fork::Scalar; use nym_network_defaults::ecash::TICKETBOOK_VALIDITY_DAYS; use nym_network_defaults::TICKETBOOK_SIZE; diff --git a/common/nym_offline_compact_ecash/src/helpers.rs b/common/nym_offline_compact_ecash/src/helpers.rs index af741d5f34..856bb397e4 100644 --- a/common/nym_offline_compact_ecash/src/helpers.rs +++ b/common/nym_offline_compact_ecash/src/helpers.rs @@ -3,8 +3,8 @@ use crate::utils::try_deserialize_g1_projective; use crate::{CompactEcashError, EncodedDate, EncodedTicketType}; -use bls12_381::{G1Projective, Scalar}; use group::Curve; +use nym_bls12_381_fork::{G1Projective, Scalar}; use std::any::{type_name, Any}; pub(crate) fn g1_tuple_to_bytes(el: (G1Projective, G1Projective)) -> [u8; 96] { diff --git a/common/nym_offline_compact_ecash/src/lib.rs b/common/nym_offline_compact_ecash/src/lib.rs index 77e1ce5199..46bb93ec9b 100644 --- a/common/nym_offline_compact_ecash/src/lib.rs +++ b/common/nym_offline_compact_ecash/src/lib.rs @@ -6,14 +6,14 @@ #![warn(clippy::todo)] #![warn(clippy::dbg_macro)] -use bls12_381::Scalar; +use nym_bls12_381_fork::Scalar; use std::sync::OnceLock; pub use crate::error::CompactEcashError; pub use crate::traits::Bytable; -pub use bls12_381::G1Projective; pub use common_types::{BlindedSignature, Signature}; pub use helpers::{date_scalar, type_scalar}; +pub use nym_bls12_381_fork::G1Projective; pub use scheme::aggregation::aggregate_verification_keys; pub use scheme::aggregation::aggregate_wallets; pub use scheme::identify; diff --git a/common/nym_offline_compact_ecash/src/proofs/mod.rs b/common/nym_offline_compact_ecash/src/proofs/mod.rs index 5133c2bedd..c1a19acc8c 100644 --- a/common/nym_offline_compact_ecash/src/proofs/mod.rs +++ b/common/nym_offline_compact_ecash/src/proofs/mod.rs @@ -3,9 +3,9 @@ use std::borrow::Borrow; -use bls12_381::Scalar; use digest::generic_array::typenum::Unsigned; use digest::Digest; +use nym_bls12_381_fork::Scalar; use sha2::Sha256; pub mod proof_spend; diff --git a/common/nym_offline_compact_ecash/src/proofs/proof_spend.rs b/common/nym_offline_compact_ecash/src/proofs/proof_spend.rs index 966dd2211c..6b81e50cb1 100644 --- a/common/nym_offline_compact_ecash/src/proofs/proof_spend.rs +++ b/common/nym_offline_compact_ecash/src/proofs/proof_spend.rs @@ -5,8 +5,8 @@ use crate::ecash_group_parameters; use crate::proofs::{compute_challenge, produce_response, produce_responses, ChallengeDigest}; use crate::scheme::keygen::VerificationKeyAuth; use crate::scheme::PayInfo; -use bls12_381::{G1Projective, G2Projective, Scalar}; use group::GroupEncoding; +use nym_bls12_381_fork::{G1Projective, G2Projective, Scalar}; use serde::{Deserialize, Serialize}; use zeroize::{Zeroize, ZeroizeOnDrop}; diff --git a/common/nym_offline_compact_ecash/src/proofs/proof_withdrawal.rs b/common/nym_offline_compact_ecash/src/proofs/proof_withdrawal.rs index b16b66d7ff..0d8576e5d1 100644 --- a/common/nym_offline_compact_ecash/src/proofs/proof_withdrawal.rs +++ b/common/nym_offline_compact_ecash/src/proofs/proof_withdrawal.rs @@ -4,9 +4,9 @@ use crate::ecash_group_parameters; use crate::proofs::{compute_challenge, produce_response, produce_responses, ChallengeDigest}; use crate::scheme::keygen::PublicKeyUser; -use bls12_381::{G1Projective, Scalar}; use group::GroupEncoding; use itertools::izip; +use nym_bls12_381_fork::{G1Projective, Scalar}; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize)] diff --git a/common/nym_offline_compact_ecash/src/scheme/aggregation.rs b/common/nym_offline_compact_ecash/src/scheme/aggregation.rs index 148a619a41..87131dcd33 100644 --- a/common/nym_offline_compact_ecash/src/scheme/aggregation.rs +++ b/common/nym_offline_compact_ecash/src/scheme/aggregation.rs @@ -9,11 +9,11 @@ use crate::scheme::withdrawal::RequestInfo; use crate::scheme::{PartialWallet, Wallet, WalletSignatures}; use crate::utils::{check_bilinear_pairing, perform_lagrangian_interpolation_at_origin}; use crate::{ecash_group_parameters, Attribute}; -use bls12_381::{G2Prepared, G2Projective, Scalar}; use core::iter::Sum; use core::ops::Mul; use group::Curve; use itertools::Itertools; +use nym_bls12_381_fork::{G2Prepared, G2Projective, Scalar}; use zeroize::Zeroizing; pub(crate) trait Aggregatable: Sized { diff --git a/common/nym_offline_compact_ecash/src/scheme/coin_indices_signatures.rs b/common/nym_offline_compact_ecash/src/scheme/coin_indices_signatures.rs index c98047570e..f140cc5f6e 100644 --- a/common/nym_offline_compact_ecash/src/scheme/coin_indices_signatures.rs +++ b/common/nym_offline_compact_ecash/src/scheme/coin_indices_signatures.rs @@ -8,8 +8,8 @@ use crate::scheme::keygen::{SecretKeyAuth, VerificationKeyAuth}; use crate::scheme::setup::Parameters; use crate::utils::generate_lagrangian_coefficients_at_origin; use crate::utils::{batch_verify_signatures, hash_g1}; -use bls12_381::{G1Projective, Scalar}; use itertools::Itertools; +use nym_bls12_381_fork::{G1Projective, Scalar}; use serde::{Deserialize, Serialize}; use std::borrow::Borrow; diff --git a/common/nym_offline_compact_ecash/src/scheme/expiration_date_signatures.rs b/common/nym_offline_compact_ecash/src/scheme/expiration_date_signatures.rs index c475943b7b..06a2395271 100644 --- a/common/nym_offline_compact_ecash/src/scheme/expiration_date_signatures.rs +++ b/common/nym_offline_compact_ecash/src/scheme/expiration_date_signatures.rs @@ -8,8 +8,8 @@ use crate::scheme::keygen::{SecretKeyAuth, VerificationKeyAuth}; use crate::utils::generate_lagrangian_coefficients_at_origin; use crate::utils::{batch_verify_signatures, hash_g1}; use crate::{constants, EncodedDate}; -use bls12_381::{G1Projective, Scalar}; use itertools::Itertools; +use nym_bls12_381_fork::{G1Projective, Scalar}; use serde::{Deserialize, Serialize}; use std::borrow::Borrow; diff --git a/common/nym_offline_compact_ecash/src/scheme/keygen.rs b/common/nym_offline_compact_ecash/src/scheme/keygen.rs index 151de7c17a..c771b6658f 100644 --- a/common/nym_offline_compact_ecash/src/scheme/keygen.rs +++ b/common/nym_offline_compact_ecash/src/scheme/keygen.rs @@ -11,11 +11,11 @@ use crate::utils::{ try_deserialize_scalar_vec, }; use crate::{ecash_group_parameters, Base58}; -use bls12_381::{G1Projective, G2Projective, Scalar}; use core::borrow::Borrow; use core::iter::Sum; use core::ops::{Add, Mul}; use group::{Curve, GroupEncoding}; +use nym_bls12_381_fork::{G1Projective, G2Projective, Scalar}; use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair}; use serde::{Deserialize, Serialize}; use zeroize::{Zeroize, ZeroizeOnDrop}; diff --git a/common/nym_offline_compact_ecash/src/scheme/mod.rs b/common/nym_offline_compact_ecash/src/scheme/mod.rs index 7f9687a380..8d830c1aaf 100644 --- a/common/nym_offline_compact_ecash/src/scheme/mod.rs +++ b/common/nym_offline_compact_ecash/src/scheme/mod.rs @@ -15,8 +15,8 @@ use crate::utils::{ }; use crate::{constants, ecash_group_parameters}; use crate::{Base58, EncodedDate, EncodedTicketType}; -use bls12_381::{G1Projective, G2Prepared, G2Projective, Scalar}; use group::Curve; +use nym_bls12_381_fork::{G1Projective, G2Prepared, G2Projective, Scalar}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::borrow::Borrow; use zeroize::{Zeroize, ZeroizeOnDrop}; diff --git a/common/nym_offline_compact_ecash/src/scheme/setup.rs b/common/nym_offline_compact_ecash/src/scheme/setup.rs index 349706fe47..abbd7e1fbb 100644 --- a/common/nym_offline_compact_ecash/src/scheme/setup.rs +++ b/common/nym_offline_compact_ecash/src/scheme/setup.rs @@ -3,9 +3,9 @@ use crate::ecash_group_parameters; use crate::utils::hash_g1; -use bls12_381::{G1Affine, G1Projective, G2Affine, G2Prepared, Scalar}; use ff::Field; use group::GroupEncoding; +use nym_bls12_381_fork::{G1Affine, G1Projective, G2Affine, G2Prepared, Scalar}; use rand::thread_rng; #[derive(Debug)] diff --git a/common/nym_offline_compact_ecash/src/scheme/withdrawal.rs b/common/nym_offline_compact_ecash/src/scheme/withdrawal.rs index af620f3159..454ad6b613 100644 --- a/common/nym_offline_compact_ecash/src/scheme/withdrawal.rs +++ b/common/nym_offline_compact_ecash/src/scheme/withdrawal.rs @@ -12,8 +12,8 @@ use crate::scheme::setup::GroupParameters; use crate::scheme::PartialWallet; use crate::utils::{check_bilinear_pairing, hash_g1}; use crate::{constants, ecash_group_parameters, Attribute, EncodedDate, EncodedTicketType}; -use bls12_381::{multi_miller_loop, G1Projective, G2Prepared, G2Projective, Scalar}; use group::{Curve, Group, GroupEncoding}; +use nym_bls12_381_fork::{multi_miller_loop, G1Projective, G2Prepared, G2Projective, Scalar}; use serde::{Deserialize, Serialize}; use std::ops::Neg; use zeroize::{Zeroize, ZeroizeOnDrop}; @@ -569,7 +569,7 @@ mod tests { use crate::common_types::BlindedSignature; use crate::ecash_group_parameters; use crate::scheme::keygen::{SecretKeyUser, VerificationKeyAuth}; - use bls12_381::G1Projective; + use nym_bls12_381_fork::G1Projective; #[test] fn test_generate_non_identity_h() { diff --git a/common/nym_offline_compact_ecash/src/traits.rs b/common/nym_offline_compact_ecash/src/traits.rs index a9a22144b9..5e3ff82bf2 100644 --- a/common/nym_offline_compact_ecash/src/traits.rs +++ b/common/nym_offline_compact_ecash/src/traits.rs @@ -7,8 +7,8 @@ use crate::proofs::proof_withdrawal::{WithdrawalReqInstance, WithdrawalReqProof} use crate::scheme::withdrawal::RequestInfo; use crate::scheme::{Payment, WalletSignatures}; use crate::{Attribute, CompactEcashError, PartialWallet, WithdrawalRequest}; -use bls12_381::{G1Affine, G1Projective}; use group::GroupEncoding; +use nym_bls12_381_fork::{G1Affine, G1Projective}; #[macro_export] macro_rules! impl_byteable_bs58 { diff --git a/common/nym_offline_compact_ecash/src/utils.rs b/common/nym_offline_compact_ecash/src/utils.rs index 9042975dec..1c262541a3 100644 --- a/common/nym_offline_compact_ecash/src/utils.rs +++ b/common/nym_offline_compact_ecash/src/utils.rs @@ -5,15 +5,15 @@ use crate::common_types::SignerIndex; use crate::error::{CompactEcashError, Result}; use crate::scheme::setup::GroupParameters; use crate::{ecash_group_parameters, Signature, VerificationKeyAuth}; -use bls12_381::hash_to_curve::{ExpandMsgXmd, HashToCurve, HashToField}; -use bls12_381::{ - multi_miller_loop, G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, Scalar, -}; use core::iter::Sum; use core::ops::Mul; use ff::Field; use group::{Curve, Group}; use itertools::Itertools; +use nym_bls12_381_fork::hash_to_curve::{ExpandMsgXmd, HashToCurve, HashToField}; +use nym_bls12_381_fork::{ + multi_miller_loop, G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, Scalar, +}; use std::borrow::Borrow; use std::ops::Neg; diff --git a/common/nymnoise/Cargo.toml b/common/nymnoise/Cargo.toml index 63050814ef..64dd0470f5 100644 --- a/common/nymnoise/Cargo.toml +++ b/common/nymnoise/Cargo.toml @@ -1,9 +1,13 @@ [package] name = "nym-noise" -version = "0.1.0" +version.workspace = true authors = ["Simon Wicky "] edition = "2021" license.workspace = true +description = "Nym's Noise protocol implementation" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true [dependencies] arc-swap = { workspace = true } @@ -20,15 +24,15 @@ tokio = { workspace = true, features = ["net", "io-util", "time"] } tokio-util = { workspace = true, features = ["codec"] } # internal -nym-crypto = { path = "../crypto" } -nym-noise-keys = { path = "keys" } +nym-crypto = { workspace = true } +nym-noise-keys = { workspace = true } [dev-dependencies] anyhow = { workspace = true } tokio = { workspace = true, features = ["full"] } rand_chacha = { workspace = true } -nym-crypto = { path = "../crypto", features = ["rand"] } -nym-test-utils = { path = "../test-utils" } +nym-crypto = { workspace = true, features = ["rand"] } +nym-test-utils = { workspace = true } [lints] diff --git a/common/nymnoise/keys/Cargo.toml b/common/nymnoise/keys/Cargo.toml index 94080a004b..3dfc1c64d2 100644 --- a/common/nymnoise/keys/Cargo.toml +++ b/common/nymnoise/keys/Cargo.toml @@ -1,9 +1,13 @@ [package] name = "nym-noise-keys" -version = "0.1.0" +version.workspace = true authors = ["Simon Wicky "] edition = "2021" license.workspace = true +description = "Helpers and type definition of Nym's Noise protocol keys" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true [dependencies] schemars = { workspace = true, features = ["preserve_order"] } @@ -11,7 +15,7 @@ serde = { workspace = true, features = ["derive"] } utoipa = { workspace = true } # internal -nym-crypto = { path = "../../crypto", features = ["asymmetric", "serde"] } +nym-crypto = { workspace = true, features = ["asymmetric", "serde"] } [lints] -workspace = true \ No newline at end of file +workspace = true diff --git a/common/nymnoise/keys/src/lib.rs b/common/nymnoise/keys/src/lib.rs index b4cd70148b..59fd6cfb12 100644 --- a/common/nymnoise/keys/src/lib.rs +++ b/common/nymnoise/keys/src/lib.rs @@ -30,8 +30,14 @@ impl From for u8 { } } -#[derive(Copy, Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, utoipa::ToSchema)] -pub struct VersionedNoiseKey { +// to whoever is thinking of modifying this struct. +// you MUST NOT change its structure in any way - adding, removing or changing fields +// otherwise, it will break old clients as bincode serialisation is not backwards compatible +// even if you put `#[serde(default)]` all over the place +#[derive( + Copy, Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, utoipa::ToSchema, PartialEq, +)] +pub struct VersionedNoiseKeyV1 { #[schemars(with = "u8")] #[schema(value_type = u8)] pub supported_version: NoiseVersion, diff --git a/common/nymnoise/src/config.rs b/common/nymnoise/src/config.rs index 6f5e087d6c..1ef2235928 100644 --- a/common/nymnoise/src/config.rs +++ b/common/nymnoise/src/config.rs @@ -10,7 +10,7 @@ use std::{ use arc_swap::ArcSwap; use nym_crypto::asymmetric::x25519; -use nym_noise_keys::{NoiseVersion, VersionedNoiseKey}; +use nym_noise_keys::{NoiseVersion, VersionedNoiseKeyV1}; use snow::params::NoiseParams; use strum_macros::{EnumIter, FromRepr}; @@ -55,7 +55,7 @@ impl NoisePattern { #[derive(Debug, Default)] struct SocketAddrToKey { - inner: ArcSwap>, + inner: ArcSwap>, } // SW NOTE : Only for phased upgrade. To remove once we decide all nodes have to support Noise @@ -78,7 +78,7 @@ impl NoiseNetworkView { } } - pub fn swap_view(&self, new: HashMap) { + pub fn swap_view(&self, new: HashMap) { let noise_support = new .iter() .map(|(s_addr, key)| (s_addr.ip(), key.supported_version)) @@ -126,7 +126,7 @@ impl NoiseConfig { self } - pub(crate) fn get_noise_key(&self, s_address: &SocketAddr) -> Option { + pub(crate) fn get_noise_key(&self, s_address: &SocketAddr) -> Option { self.network.keys.inner.load().get(s_address).copied() } diff --git a/common/nymsphinx/Cargo.toml b/common/nymsphinx/Cargo.toml index d6b77f6969..1fba34a0c2 100644 --- a/common/nymsphinx/Cargo.toml +++ b/common/nymsphinx/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-sphinx" -version = "0.1.0" +version.workspace = true description = "Top-level crate for sphinx packets as used by the Nym mixnet" edition = { workspace = true } authors = { workspace = true } @@ -14,32 +14,32 @@ rand_distr = { workspace = true } rand_chacha = { workspace = true } thiserror = { workspace = true } -nym-sphinx-acknowledgements = { path = "acknowledgements" } -nym-sphinx-addressing = { path = "addressing" } -nym-sphinx-anonymous-replies = { path = "anonymous-replies" } -nym-sphinx-chunking = { path = "chunking" } -nym-sphinx-cover = { path = "cover" } -nym-sphinx-forwarding = { path = "forwarding" } -nym-sphinx-params = { path = "params" } -nym-sphinx-routing = { path = "routing" } -nym-sphinx-types = { path = "types" } +nym-sphinx-acknowledgements = { workspace = true } +nym-sphinx-addressing = { workspace = true } +nym-sphinx-anonymous-replies = { workspace = true } +nym-sphinx-chunking = { workspace = true } +nym-sphinx-cover = { workspace = true } +nym-sphinx-forwarding = { workspace = true } +nym-sphinx-params = { workspace = true } +nym-sphinx-routing = { workspace = true } +nym-sphinx-types = { workspace = true } # those dependencies are due to intriducing preparer and receiver. Perpaphs that indicates they should be moved # to separate crate? -nym-crypto = { path = "../crypto", version = "0.4.0" } -nym-topology = { path = "../topology" } -nym-metrics = { path = "../nym-metrics" } +nym-crypto = { workspace = true } +nym-topology = { workspace = true } +nym-metrics = { workspace = true } [dev-dependencies] -nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" } -nym-crypto = { path = "../crypto", version = "0.4.0", features = [ +nym-mixnet-contract-common = { workspace = true } +nym-crypto = { workspace = true, features = [ "asymmetric", ] } # do not include this when compiling into wasm as it somehow when combined together with reqwest, it will require # net2 via tokio-util -> tokio -> mio -> net2 [target."cfg(not(target_arch = \"wasm32\"))".dependencies.nym-sphinx-framing] -path = "framing" +workspace = true [target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio] workspace = true diff --git a/common/nymsphinx/acknowledgements/Cargo.toml b/common/nymsphinx/acknowledgements/Cargo.toml index 117bc6b7d5..6d0fe4fcff 100644 --- a/common/nymsphinx/acknowledgements/Cargo.toml +++ b/common/nymsphinx/acknowledgements/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-sphinx-acknowledgements" -version = "0.1.0" +version.workspace = true description = "Sphinx packet ack messages" edition = { workspace = true } authors = { workspace = true } @@ -14,13 +14,13 @@ generic-array = { workspace = true, optional = true, features = ["serde"] } thiserror = { workspace = true } zeroize = { workspace = true } -nym-crypto = { path = "../../crypto", features = ["stream_cipher", "rand"] } -nym-pemstore = { path = "../../pemstore" } -nym-sphinx-addressing = { path = "../addressing" } -nym-sphinx-params = { path = "../params" } -nym-sphinx-routing = { path = "../routing" } -nym-sphinx-types = { path = "../types", features = ["sphinx"] } -nym-topology = { path = "../../topology" } +nym-crypto = { workspace = true, features = ["stream_cipher", "rand"] } +nym-pemstore = { workspace = true } +nym-sphinx-addressing = { workspace = true } +nym-sphinx-params = { workspace = true } +nym-sphinx-routing = { workspace = true } +nym-sphinx-types = { workspace = true, features = ["sphinx"] } +nym-topology = { workspace = true } [features] serde = ["dep:serde", "generic-array"] diff --git a/common/nymsphinx/addressing/Cargo.toml b/common/nymsphinx/addressing/Cargo.toml index c7f756cb5c..5aec9af3ed 100644 --- a/common/nymsphinx/addressing/Cargo.toml +++ b/common/nymsphinx/addressing/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-sphinx-addressing" -version = "0.1.0" +version.workspace = true description = "Nym mixnet addressing" edition = { workspace = true } authors = { workspace = true } @@ -8,14 +8,14 @@ license = { workspace = true } repository = { workspace = true } [dependencies] -nym-crypto = { path = "../../crypto", features = ["asymmetric", "sphinx"] } # all addresses are expressed in terms on their crypto keys -nym-sphinx-types = { path = "../types", features = ["sphinx"] } # we need to be able to refer to some types defined inside sphinx crate +nym-crypto = { workspace = true, features = ["asymmetric", "sphinx"] } # all addresses are expressed in terms on their crypto keys +nym-sphinx-types = { workspace = true, features = ["sphinx"] } # we need to be able to refer to some types defined inside sphinx crate serde = { workspace = true } # implementing serialization/deserialization for some types, like `Recipient` thiserror = { workspace = true } [dev-dependencies] rand = { workspace = true } -nym-crypto = { path = "../../crypto", features = ["rand"] } +nym-crypto = { workspace = true, features = ["rand"] } bincode = { workspace = true } serde_json = { workspace = true } -serde = { workspace = true, features = ["derive"] } \ No newline at end of file +serde = { workspace = true, features = ["derive"] } diff --git a/common/nymsphinx/anonymous-replies/Cargo.toml b/common/nymsphinx/anonymous-replies/Cargo.toml index ef9c74b73d..fe9f272146 100644 --- a/common/nymsphinx/anonymous-replies/Cargo.toml +++ b/common/nymsphinx/anonymous-replies/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-sphinx-anonymous-replies" -version = "0.1.0" +version.workspace = true description = "Anonymous sphinx packet replies using singly-use-reply-blocks (SURB)" edition = { workspace = true } authors = { workspace = true } @@ -13,12 +13,12 @@ bs58 = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true } -nym-crypto = { path = "../../crypto", features = ["stream_cipher", "rand"] } -nym-sphinx-addressing = { path = "../addressing" } -nym-sphinx-params = { path = "../params" } -nym-sphinx-routing = { path = "../routing" } -nym-sphinx-types = { path = "../types" } -nym-topology = { path = "../../topology" } +nym-crypto = { workspace = true, features = ["stream_cipher", "rand"] } +nym-sphinx-addressing = { workspace = true } +nym-sphinx-params = { workspace = true } +nym-sphinx-routing = { workspace = true } +nym-sphinx-types = { workspace = true } +nym-topology = { workspace = true } [target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen] workspace = true diff --git a/common/nymsphinx/chunking/Cargo.toml b/common/nymsphinx/chunking/Cargo.toml index f5ea240859..708ecb5c10 100644 --- a/common/nymsphinx/chunking/Cargo.toml +++ b/common/nymsphinx/chunking/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-sphinx-chunking" -version = "0.1.0" +version.workspace = true description = "Sphinx packet chunking of underlying data packets" edition = { workspace = true } authors = { workspace = true } @@ -17,11 +17,11 @@ dashmap = { workspace = true, features = ["serde"] } serde = { workspace = true, features = ["derive"] } utoipa = { workspace = true } -nym-sphinx-addressing = { path = "../addressing" } -nym-sphinx-params = { path = "../params" } -nym-sphinx-types = { path = "../types" } -nym-metrics = { path = "../../nym-metrics" } -nym-crypto = { path = "../../crypto", version = "0.4.0", features = [ +nym-sphinx-addressing = { workspace = true } +nym-sphinx-params = { workspace = true } +nym-sphinx-types = { workspace = true } +nym-metrics = { workspace = true } +nym-crypto = { workspace = true, features = [ "asymmetric", ] } diff --git a/common/nymsphinx/cover/Cargo.toml b/common/nymsphinx/cover/Cargo.toml index 59710923d1..69dcf6fe65 100644 --- a/common/nymsphinx/cover/Cargo.toml +++ b/common/nymsphinx/cover/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-sphinx-cover" -version = "0.1.0" +version.workspace = true description = "Sphinx packets used as cover traffic" edition = { workspace = true } authors = { workspace = true } @@ -11,12 +11,12 @@ repository = { workspace = true } rand = { workspace = true } thiserror = { workspace = true } -nym-crypto = { path = "../../crypto" } -nym-sphinx-acknowledgements = { path = "../acknowledgements" } -nym-sphinx-addressing = { path = "../addressing" } -nym-sphinx-chunking = { path = "../chunking" } -nym-sphinx-forwarding = { path = "../forwarding" } -nym-sphinx-params = { path = "../params" } -nym-sphinx-routing = { path = "../routing" } -nym-sphinx-types = { path = "../types" } -nym-topology = { path = "../../topology" } +nym-crypto = { workspace = true } +nym-sphinx-acknowledgements = { workspace = true } +nym-sphinx-addressing = { workspace = true } +nym-sphinx-chunking = { workspace = true } +nym-sphinx-forwarding = { workspace = true } +nym-sphinx-params = { workspace = true } +nym-sphinx-routing = { workspace = true } +nym-sphinx-types = { workspace = true } +nym-topology = { workspace = true } diff --git a/common/nymsphinx/forwarding/Cargo.toml b/common/nymsphinx/forwarding/Cargo.toml index c8beb33b12..e9a299c626 100644 --- a/common/nymsphinx/forwarding/Cargo.toml +++ b/common/nymsphinx/forwarding/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-sphinx-forwarding" -version = "0.1.0" +version.workspace = true description = "Sphinx packet forwarding as Nym mix packets" edition = { workspace = true } authors = { workspace = true } @@ -8,8 +8,8 @@ license = { workspace = true } repository = { workspace = true } [dependencies] -nym-sphinx-addressing = { path = "../addressing" } -nym-sphinx-params = { path = "../params" } -nym-sphinx-types = { path = "../types", features = ["sphinx", "outfox"] } -nym-sphinx-anonymous-replies = { path = "../anonymous-replies" } +nym-sphinx-addressing = { workspace = true } +nym-sphinx-params = { workspace = true } +nym-sphinx-types = { workspace = true, features = ["sphinx", "outfox"] } +nym-sphinx-anonymous-replies = { workspace = true } thiserror = { workspace = true } diff --git a/common/nymsphinx/framing/Cargo.toml b/common/nymsphinx/framing/Cargo.toml index 01c5e6e4ae..a2f42c7e16 100644 --- a/common/nymsphinx/framing/Cargo.toml +++ b/common/nymsphinx/framing/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-sphinx-framing" -version = "0.1.0" +version.workspace = true description = "Sphinx packet framing for the Nym mixnet" edition = { workspace = true } authors = { workspace = true } @@ -14,11 +14,11 @@ tokio-util = { workspace = true, features = ["codec"] } thiserror = { workspace = true } tracing = { workspace = true } -nym-sphinx-types = { path = "../types", features = ["sphinx", "outfox"] } -nym-sphinx-params = { path = "../params", features = ["sphinx", "outfox"] } -nym-sphinx-forwarding = { path = "../forwarding" } -nym-sphinx-addressing = { path = "../addressing" } -nym-sphinx-acknowledgements = { path = "../acknowledgements" } +nym-sphinx-types = { workspace = true, features = ["sphinx", "outfox"] } +nym-sphinx-params = { workspace = true, features = ["sphinx", "outfox"] } +nym-sphinx-forwarding = { workspace = true } +nym-sphinx-addressing = { workspace = true } +nym-sphinx-acknowledgements = { workspace = true } [dev-dependencies] tokio = { workspace = true, features = ["full"] } diff --git a/common/nymsphinx/params/Cargo.toml b/common/nymsphinx/params/Cargo.toml index 8db26e7b1d..c5fd617d63 100644 --- a/common/nymsphinx/params/Cargo.toml +++ b/common/nymsphinx/params/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-sphinx-params" -version = "0.1.0" +version.workspace = true description = "Sphinx packet parameters for the Nym mixnet" edition = { workspace = true } authors = { workspace = true } @@ -11,8 +11,8 @@ repository = { workspace = true } thiserror = { workspace = true } serde = { workspace = true, features = ["derive"] } -nym-crypto = { path = "../../crypto", features = ["hashing", "stream_cipher", "aead"] } -nym-sphinx-types = { path = "../types" } +nym-crypto = { workspace = true, features = ["hashing", "stream_cipher", "aead"] } +nym-sphinx-types = { workspace = true } [features] default = ["sphinx"] diff --git a/common/nymsphinx/routing/Cargo.toml b/common/nymsphinx/routing/Cargo.toml index 37b8d5a4dc..209c9baefb 100644 --- a/common/nymsphinx/routing/Cargo.toml +++ b/common/nymsphinx/routing/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-sphinx-routing" -version = "0.1.0" +version.workspace = true description = "Sphinx packet routing as Nym mix packets" edition = { workspace = true } authors = { workspace = true } @@ -10,7 +10,7 @@ repository = { workspace = true } [dependencies] thiserror = { workspace = true } -nym-sphinx-addressing = { path = "../addressing" } -nym-sphinx-types = { path = "../types", features = ["sphinx"] } +nym-sphinx-addressing = { workspace = true } +nym-sphinx-types = { workspace = true, features = ["sphinx"] } -[features] \ No newline at end of file +[features] diff --git a/common/nymsphinx/types/Cargo.toml b/common/nymsphinx/types/Cargo.toml index b12d22d7f6..776f88a41a 100644 --- a/common/nymsphinx/types/Cargo.toml +++ b/common/nymsphinx/types/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-sphinx-types" -version = "0.2.0" +version.workspace = true description = "Re-export sphinx packet types" edition = { workspace = true } authors = { workspace = true } @@ -9,7 +9,7 @@ repository = { workspace = true } [dependencies] sphinx-packet = { workspace = true, optional = true } -nym-outfox = { path = "../../../nym-outfox", optional = true } +nym-outfox = { workspace = true, optional = true } thiserror = { workspace = true } [features] diff --git a/common/nyxd-scraper-psql/Cargo.toml b/common/nyxd-scraper-psql/Cargo.toml index 0c2253cf9b..c99249f0cd 100644 --- a/common/nyxd-scraper-psql/Cargo.toml +++ b/common/nyxd-scraper-psql/Cargo.toml @@ -9,6 +9,7 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +publish = false [dependencies] async-trait = { workspace = true } @@ -21,7 +22,7 @@ thiserror = { workspace = true } tokio = { workspace = true, features = ["full"] } tracing.workspace = true -nyxd-scraper-shared = { path = "../nyxd-scraper-shared" } +nyxd-scraper-shared = { workspace = true } # temp due to cosmrs redefinitions for serde cosmrs = { workspace = true } diff --git a/common/nyxd-scraper-shared/Cargo.toml b/common/nyxd-scraper-shared/Cargo.toml index 3f7a343ec2..299d2a6186 100644 --- a/common/nyxd-scraper-shared/Cargo.toml +++ b/common/nyxd-scraper-shared/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nyxd-scraper-shared" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true @@ -9,6 +9,7 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +description = "Common crate for the sqlite and psql Nyxd blockchain scrapers" [dependencies] async-trait.workspace = true diff --git a/common/nyxd-scraper-sqlite/Cargo.toml b/common/nyxd-scraper-sqlite/Cargo.toml index eb10336f0b..bcf59ce2cf 100644 --- a/common/nyxd-scraper-sqlite/Cargo.toml +++ b/common/nyxd-scraper-sqlite/Cargo.toml @@ -7,6 +7,7 @@ homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -17,7 +18,7 @@ thiserror = { workspace = true } tokio = { workspace = true, features = ["full"] } tracing.workspace = true -nyxd-scraper-shared = { path = "../nyxd-scraper-shared" } +nyxd-scraper-shared = { workspace = true } [build-dependencies] @@ -26,4 +27,4 @@ tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } anyhow.workspace = true [lints] -workspace = true \ No newline at end of file +workspace = true diff --git a/common/pemstore/Cargo.toml b/common/pemstore/Cargo.toml index 9c93dc122d..5534b81dd0 100644 --- a/common/pemstore/Cargo.toml +++ b/common/pemstore/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "nym-pemstore" description = "Store private-public keypairs in PEM format" -version = "0.3.0" +version.workspace = true edition = { workspace = true } authors = { workspace = true } license = { workspace = true } @@ -10,4 +10,4 @@ repository = { workspace = true } [dependencies] pem = { workspace = true } tracing = { workspace = true } -zeroize = { workspace = true } \ No newline at end of file +zeroize = { workspace = true } diff --git a/common/published-crates.txt b/common/published-crates.txt deleted file mode 100644 index 5aee0f41d8..0000000000 --- a/common/published-crates.txt +++ /dev/null @@ -1,8 +0,0 @@ -# List of published common crates -nym-bin-common -nym-contracts-common -nym-crypto -nym-mixnet-contract-common -nym-pemstore -nym-sphinx-types -nym-vesting-contract-common diff --git a/common/registration/Cargo.toml b/common/registration/Cargo.toml index 6d4c56e022..c361b21461 100644 --- a/common/registration/Cargo.toml +++ b/common/registration/Cargo.toml @@ -1,27 +1,30 @@ [package] name = "nym-registration-common" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "Struct definitions for NymNode, GatewayData, and AssignedAddresses" [lints] workspace = true [dependencies] +bincode = { workspace = true } serde = { workspace = true, features = ["derive"] } -tokio-util.workspace = true +tracing = { workspace = true } -nym-authenticator-requests = { path = "../authenticator-requests" } -nym-credentials-interface = { path = "../credentials-interface" } -nym-crypto = { path = "../crypto" } -nym-ip-packet-requests = { path = "../ip-packet-requests" } -nym-sphinx = { path = "../nymsphinx" } -nym-wireguard-types = { path = "../wireguard-types" } +nym-authenticator-requests = { workspace = true } +nym-credentials-interface = { workspace = true } +nym-crypto = { workspace = true } +nym-ip-packet-requests = { workspace = true } +nym-sphinx = { workspace = true } +nym-wireguard-types = { workspace = true } +nym-kkt-ciphersuite = { workspace = true } [dev-dependencies] bincode.workspace = true -time.workspace = true +nym-test-utils = { workspace = true } diff --git a/common/registration/src/lib.rs b/common/registration/src/lib.rs index 7ac2e20e9d..f297de143d 100644 --- a/common/registration/src/lib.rs +++ b/common/registration/src/lib.rs @@ -1,38 +1,70 @@ // Copyright 2025 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -mod lp_messages; - -pub use lp_messages::{ - LpGatewayData, LpRegistrationRequest, LpRegistrationResponse, RegistrationMode, -}; - +use nym_authenticator_requests::AuthenticatorVersion; +use nym_crypto::asymmetric::x25519::serde_helpers::bs58_x25519_pubkey; +use nym_crypto::asymmetric::{ed25519, x25519}; +use nym_ip_packet_requests::IpPair; +use nym_kkt_ciphersuite::{KEM, KEMKeyDigests, SignatureScheme}; +use nym_sphinx::addressing::Recipient; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; -use nym_authenticator_requests::AuthenticatorVersion; -use nym_crypto::asymmetric::x25519::{PublicKey, serde_helpers::bs58_x25519_pubkey}; -use nym_ip_packet_requests::IpPair; -use nym_sphinx::addressing::{NodeIdentity, Recipient}; -use serde::{Deserialize, Serialize}; +pub use lp_messages::*; +pub use serialisation::BincodeError; -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct NymNode { - pub identity: NodeIdentity, +mod lp_messages; +mod serialisation; + +#[derive(Debug, Clone)] +pub struct NymNodeInformation { + pub identity: ed25519::PublicKey, pub ip_address: IpAddr, pub ipr_address: Option, pub authenticator_address: Option, - pub lp_address: Option, + pub lp_data: Option, pub version: AuthenticatorVersion, } -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct GatewayData { + +#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] +pub struct WireguardRegistrationData { + /// Public x25519 key of this gateway #[serde(with = "bs58_x25519_pubkey")] - pub public_key: PublicKey, + pub public_key: x25519::PublicKey, + + /// Port at which this gateway is accessible for wireguard + pub port: u16, + + /// Ipv4 address assigned to this peer + pub private_ipv4: Ipv4Addr, + + /// Ipv6 address assigned to this peer + pub private_ipv6: Ipv6Addr, +} + +#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] +pub struct WireguardConfiguration { + #[serde(with = "bs58_x25519_pubkey")] + pub public_key: x25519::PublicKey, + pub psk: Option<[u8; 32]>, pub endpoint: SocketAddr, pub private_ipv4: Ipv4Addr, pub private_ipv6: Ipv6Addr, } +#[derive(Clone, Debug)] +pub struct NymNodeLPInformation { + pub address: SocketAddr, + pub expected_kem_key_hashes: HashMap, + pub expected_signing_key_hashes: HashMap, + pub x25519: x25519::PublicKey, + + /// Supported protocol version of the remote gateway. + /// Included in case we have to downgrade our version. + pub lp_protocol_version: u8, +} + #[derive(Clone, Copy, Debug)] pub struct AssignedAddresses { pub entry_mixnet_gateway_ip: IpAddr, diff --git a/common/registration/src/lp_messages.rs b/common/registration/src/lp_messages.rs index 94c3889f5f..c294b87abb 100644 --- a/common/registration/src/lp_messages.rs +++ b/common/registration/src/lp_messages.rs @@ -3,34 +3,21 @@ //! LP (Lewes Protocol) registration message types shared between client and gateway. -use nym_credentials_interface::{CredentialSpendingData, TicketType}; +use crate::WireguardRegistrationData; +use crate::dvpn::{ + LpDvpnRegistrationFinalisation, LpDvpnRegistrationInitialRequest, + LpDvpnRegistrationRequestMessage, LpDvpnRegistrationRequestMessageContent, + LpDvpnRegistrationResponseMessage, LpDvpnRegistrationResponseMessageContent, + RequiresCredentialResponse, +}; +use crate::mixnet::{ + LpMixnetGatewayData, LpMixnetRegistrationRequestMessage, LpMixnetRegistrationResponseMessage, + LpMixnetRegistrationResponseMessageContent, +}; +use crate::serialisation::{BincodeError, BincodeOptions, lp_bincode_serializer}; +use nym_authenticator_requests::models::BandwidthClaim; use serde::{Deserialize, Serialize}; -use std::net::IpAddr; - -use crate::GatewayData; - -/// Registration request sent by client after LP handshake -/// Aligned with existing authenticator registration flow -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct LpRegistrationRequest { - /// Client's WireGuard public key (for dVPN mode) - pub wg_public_key: nym_wireguard_types::PeerPublicKey, - - /// Bandwidth credential for payment - pub credential: CredentialSpendingData, - - /// Ticket type for bandwidth allocation - pub ticket_type: TicketType, - - /// Registration mode - pub mode: RegistrationMode, - - /// Client's IP address (for tracking/metrics) - pub client_ip: IpAddr, - - /// Unix timestamp for replay protection - pub timestamp: u64, -} +use tracing::error; #[derive(Debug, Clone, Serialize, Deserialize)] pub enum RegistrationMode { @@ -38,150 +25,420 @@ pub enum RegistrationMode { Dvpn, /// Mixnet mode - register for mixnet routing via IPR - /// - /// Client provides identity and encryption keys for nym address derivation. - /// Gateway stores client in ActiveClientsStore for SURB reply delivery. - Mixnet { - /// Client's ed25519 public key (identity) - /// - /// Used to derive DestinationAddressBytes for ActiveClientsStore lookup. - /// Must match the key used in LP handshake for authentication. - client_ed25519_pubkey: [u8; 32], - - /// Client's x25519 public key (encryption) - /// - /// Used for SURB reply encryption. Combined with ed25519 identity - /// and gateway identity to form the full nym Recipient address. - client_x25519_pubkey: [u8; 32], - }, + Mixnet, } -/// Gateway data for mixnet mode registration -/// -/// Contains the gateway's identity and sphinx key needed for the client -/// to construct its full nym Recipient address. +/// Registration request sent by client after LP handshake +/// Aligned with existing authenticator registration flow #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct LpGatewayData { - /// Gateway's ed25519 identity public key - /// - /// Forms part of the client's nym Recipient address. - pub gateway_identity: [u8; 32], +pub struct LpRegistrationRequest { + /// Mode specific registration data + pub registration_data: LpRegistrationRequestData, - /// Gateway's x25519 sphinx public key - /// - /// Used by the client for Sphinx packet construction. - pub gateway_sphinx_key: [u8; 32], + /// Unix timestamp for replay protection + pub timestamp: u64, +} + +impl LpRegistrationRequest { + pub fn mode(&self) -> RegistrationMode { + match self.registration_data { + LpRegistrationRequestData::Dvpn { .. } => RegistrationMode::Dvpn, + LpRegistrationRequestData::Mixnet { .. } => RegistrationMode::Mixnet, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum LpRegistrationRequestData { + /// dVPN mode - register as WireGuard peer (most common) + Dvpn { + data: Box, + }, + + /// Mixnet mode - register for mixnet routing via IPR + Mixnet { + data: LpMixnetRegistrationRequestMessage, + }, } /// Registration response from gateway /// Contains GatewayData for compatibility with existing client code #[derive(Debug, Clone, Serialize, Deserialize)] pub struct LpRegistrationResponse { - /// Whether registration succeeded - pub success: bool, + /// The status of this registration after the last received client message + pub status: RegistrationStatus, - /// Error message if registration failed - pub error: Option, + /// Mode specific registration response + pub response_data: LpRegistrationResponseData, +} - /// Gateway configuration data for dVPN mode (WireGuard) - /// This matches what WireguardRegistrationResult expects - pub gateway_data: Option, +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum LpRegistrationResponseData { + /// dVPN mode - register as WireGuard peer (most common) + Dvpn { + data: LpDvpnRegistrationResponseMessage, + }, - /// Gateway data for mixnet mode - /// - /// Contains gateway identity and sphinx key needed for nym address construction. - /// Only populated for Mixnet mode registrations. - pub lp_gateway_data: Option, + /// Mixnet mode - register for mixnet routing via IPR + Mixnet { + data: LpMixnetRegistrationResponseMessage, + }, +} - /// Allocated bandwidth in bytes - pub allocated_bandwidth: i64, +/// Represents the registration status after the last received client message. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum RegistrationStatus { + /// The registration has been completed successfully + Completed, + + /// The registration has failed + Failed, + + /// To complete registration the client needs to send additional data, + /// e.g. a credential. it is context dependent. + PendingMoreData, +} + +impl RegistrationStatus { + pub fn is_successful(&self) -> bool { + matches!(self, RegistrationStatus::Completed) + } + + pub fn is_failed(&self) -> bool { + matches!(self, RegistrationStatus::Failed) + } + + pub fn is_pending(&self) -> bool { + matches!(self, RegistrationStatus::PendingMoreData) + } +} + +fn current_timestamp() -> u64 { + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .inspect_err(|_| error!("the current timestamp predates unix epoch!")) + .unwrap_or_default() + .as_secs() } impl LpRegistrationRequest { - /// Create a new dVPN registration request - pub fn new_dvpn( - wg_public_key: nym_wireguard_types::PeerPublicKey, - credential: CredentialSpendingData, - ticket_type: TicketType, - client_ip: IpAddr, - ) -> Self { + /// Helper wrapping timestamp extraction + fn new(registration_data: LpRegistrationRequestData) -> LpRegistrationRequest { Self { - wg_public_key, - credential, - ticket_type, - mode: RegistrationMode::Dvpn, - client_ip, - #[allow(clippy::expect_used)] - timestamp: std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .expect("System time before UNIX epoch") - .as_secs(), + registration_data, + timestamp: current_timestamp(), } } + /// Create new dVPN registration initialisation request + pub fn new_initial_dvpn( + wg_public_key: nym_wireguard_types::PeerPublicKey, + psk: [u8; 32], + ) -> Self { + Self::new(LpRegistrationRequestData::Dvpn { + data: Box::new(LpDvpnRegistrationRequestMessage { + content: LpDvpnRegistrationRequestMessageContent::InitialRequest( + LpDvpnRegistrationInitialRequest { wg_public_key, psk }, + ), + }), + }) + } + + pub fn new_finalise_dvpn(credential: BandwidthClaim) -> Self { + Self::new(LpRegistrationRequestData::Dvpn { + data: Box::new(LpDvpnRegistrationRequestMessage { + content: LpDvpnRegistrationRequestMessageContent::Finalisation( + LpDvpnRegistrationFinalisation { credential }, + ), + }), + }) + } + /// Validate the request timestamp is within acceptable bounds pub fn validate_timestamp(&self, max_skew_secs: u64) -> bool { - let now = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap_or_default() - .as_secs(); + let now = current_timestamp(); (now as i64 - self.timestamp as i64).abs() <= max_skew_secs as i64 } + + /// Attempt to serialise this `LpRegistrationRequest` into bytes. + pub fn serialise(&self) -> Result, BincodeError> { + lp_bincode_serializer().serialize(self) + } + + /// Attempt to deserialise a `LpRegistrationRequest` from bytes. + pub fn try_deserialise(b: &[u8]) -> Result { + lp_bincode_serializer().deserialize(b) + } } impl LpRegistrationResponse { /// Create a success response with GatewayData (for dVPN mode) - pub fn success(allocated_bandwidth: i64, gateway_data: GatewayData) -> Self { + pub fn success_dvpn(config: WireguardRegistrationData, upgrade_mode: bool) -> Self { Self { - success: true, - error: None, - gateway_data: Some(gateway_data), - lp_gateway_data: None, - allocated_bandwidth, + status: RegistrationStatus::Completed, + response_data: LpRegistrationResponseData::Dvpn { + data: LpDvpnRegistrationResponseMessage { + content: LpDvpnRegistrationResponseMessageContent::CompletedRegistration( + dvpn::CompletedRegistrationResponse { + config, + upgrade_mode, + }, + ), + }, + }, } } - /// Create a success response for mixnet mode with LpGatewayData - pub fn success_mixnet(allocated_bandwidth: i64, lp_gateway_data: LpGatewayData) -> Self { + pub fn success_mixnet(config: LpMixnetGatewayData) -> Self { Self { - success: true, - error: None, - gateway_data: None, - lp_gateway_data: Some(lp_gateway_data), - allocated_bandwidth, + status: RegistrationStatus::Completed, + response_data: LpRegistrationResponseData::Mixnet { + data: LpMixnetRegistrationResponseMessage { + content: LpMixnetRegistrationResponseMessageContent::CompletedRegistration( + mixnet::CompletedRegistrationResponse { config }, + ), + }, + }, } } /// Create an error response - pub fn error(error: String) -> Self { - Self { - success: false, - error: Some(error), - gateway_data: None, - lp_gateway_data: None, - allocated_bandwidth: 0, + pub fn error(error: impl Into, mode: RegistrationMode) -> Self { + let response_data = match mode { + RegistrationMode::Dvpn => LpRegistrationResponseData::Dvpn { + data: LpDvpnRegistrationResponseMessage::error(error), + }, + RegistrationMode::Mixnet => LpRegistrationResponseData::Mixnet { + data: LpMixnetRegistrationResponseMessage::error(error), + }, + }; + LpRegistrationResponse { + status: RegistrationStatus::Failed, + response_data, } } + + pub fn request_dvpn_credential() -> Self { + LpRegistrationResponse { + status: RegistrationStatus::PendingMoreData, + response_data: LpRegistrationResponseData::Dvpn { + data: LpDvpnRegistrationResponseMessage { + content: LpDvpnRegistrationResponseMessageContent::RequiresCredential( + RequiresCredentialResponse, + ), + }, + }, + } + } + + pub fn into_dvpn_response(self) -> Option { + match self.response_data { + LpRegistrationResponseData::Dvpn { data } => Some(data), + LpRegistrationResponseData::Mixnet { .. } => None, + } + } + + pub fn into_mixnet_response(self) -> Option { + match self.response_data { + LpRegistrationResponseData::Mixnet { data } => Some(data), + LpRegistrationResponseData::Dvpn { .. } => None, + } + } + + pub fn error_message(&self) -> Option<&str> { + match &self.response_data { + LpRegistrationResponseData::Dvpn { data } => match &data.content { + LpDvpnRegistrationResponseMessageContent::RegistrationFailure(response) => { + Some(&response.error) + } + _ => None, + }, + LpRegistrationResponseData::Mixnet { data } => match &data.content { + LpMixnetRegistrationResponseMessageContent::RegistrationFailure(response) => { + Some(&response.error) + } + _ => None, + }, + } + } + + /// Attempt to serialise this `LpRegistrationResponse` into bytes. + pub fn serialise(&self) -> Result, BincodeError> { + lp_bincode_serializer().serialize(self) + } + + /// Attempt to deserialise a `LpRegistrationResponse` from bytes. + pub fn try_deserialise(b: &[u8]) -> Result { + lp_bincode_serializer().deserialize(b) + } +} + +pub mod dvpn { + use crate::WireguardRegistrationData; + use nym_authenticator_requests::models::BandwidthClaim; + use serde::{Deserialize, Serialize}; + + // client + #[derive(Debug, Clone, Serialize, Deserialize)] + pub struct LpDvpnRegistrationRequestMessage { + pub content: LpDvpnRegistrationRequestMessageContent, + } + + #[derive(Debug, Clone, Serialize, Deserialize)] + pub enum LpDvpnRegistrationRequestMessageContent { + InitialRequest(LpDvpnRegistrationInitialRequest), + Finalisation(LpDvpnRegistrationFinalisation), + // in theory, we could also extend it with Bandwidth-related messages, + // but that shouldn't really be the responsibility of a Registration client. + } + + #[derive(Debug, Clone, Serialize, Deserialize)] + pub struct LpDvpnRegistrationInitialRequest { + /// Client's WireGuard public key (for dVPN mode) + pub wg_public_key: nym_wireguard_types::PeerPublicKey, + + /// Preshared key to be used for the connection + pub psk: [u8; 32], + } + + #[derive(Debug, Clone, Serialize, Deserialize)] + pub struct LpDvpnRegistrationFinalisation { + /// Ecash credential + pub credential: BandwidthClaim, + } + + // gateway + #[derive(Debug, Clone, Serialize, Deserialize)] + pub struct LpDvpnRegistrationResponseMessage { + pub content: LpDvpnRegistrationResponseMessageContent, + } + + impl LpDvpnRegistrationResponseMessage { + pub fn error(error: impl Into) -> Self { + LpDvpnRegistrationResponseMessage { + content: LpDvpnRegistrationResponseMessageContent::RegistrationFailure( + RegistrationFailureResponse { + error: error.into(), + }, + ), + } + } + } + + #[derive(Debug, Clone, Serialize, Deserialize)] + pub enum LpDvpnRegistrationResponseMessageContent { + RequiresCredential(RequiresCredentialResponse), + CompletedRegistration(CompletedRegistrationResponse), + RegistrationFailure(RegistrationFailureResponse), + } + + #[derive(Debug, Clone, Copy, Serialize, Deserialize)] + pub struct CompletedRegistrationResponse { + /// Gateway configuration data for dVPN mode (WireGuard) + /// This matches what WireguardRegistrationResult expects + pub config: WireguardRegistrationData, + + /// Flag indicating whether the gateway has detected the system is undergoing the upgrade + /// (thus it will not meter bandwidth) + pub upgrade_mode: bool, + } + + #[derive(Debug, Clone, Copy, Serialize, Deserialize)] + pub struct RequiresCredentialResponse; + + #[derive(Debug, Clone, Serialize, Deserialize)] + pub struct RegistrationFailureResponse { + pub error: String, + } +} + +pub mod mixnet { + use nym_crypto::asymmetric::ed25519; + use serde::{Deserialize, Serialize}; + + // client + #[derive(Debug, Clone, Serialize, Deserialize)] + pub struct LpMixnetRegistrationRequestMessage { + pub content: LpMixnetRegistrationRequestContent, + } + + #[derive(Debug, Clone, Serialize, Deserialize)] + pub struct LpMixnetRegistrationRequestContent { + /// Client's ed25519 public key (identity) + /// + /// Used to derive DestinationAddressBytes for ActiveClientsStore lookup. + pub client_ed25519_pubkey: ed25519::PublicKey, + } + + // gateway + + #[derive(Debug, Clone, Serialize, Deserialize)] + pub struct LpMixnetRegistrationResponseMessage { + pub content: LpMixnetRegistrationResponseMessageContent, + } + + impl LpMixnetRegistrationResponseMessage { + pub fn error(error: impl Into) -> Self { + LpMixnetRegistrationResponseMessage { + content: LpMixnetRegistrationResponseMessageContent::RegistrationFailure( + RegistrationFailureResponse { + error: error.into(), + }, + ), + } + } + } + + #[derive(Debug, Clone, Serialize, Deserialize)] + pub enum LpMixnetRegistrationResponseMessageContent { + CompletedRegistration(CompletedRegistrationResponse), + RegistrationFailure(RegistrationFailureResponse), + } + + /// Gateway data for mixnet mode registration + /// + /// Contains the gateway's identity and sphinx key needed for the client + /// to construct its full nym Recipient address. + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] + pub struct LpMixnetGatewayData { + /// Gateway's ed25519 identity public key + /// + /// Forms part of the client's nym Recipient address. + pub gateway_identity: ed25519::PublicKey, + // TODO: what we really need in here is the address of internal IPR + } + + #[derive(Debug, Clone, Serialize, Deserialize)] + pub struct CompletedRegistrationResponse { + /// Gateway data for mixnet mode + /// + /// Contains gateway identity and sphinx key needed for nym address construction. + pub config: LpMixnetGatewayData, + } + + #[derive(Debug, Clone, Serialize, Deserialize)] + pub struct RegistrationFailureResponse { + pub error: String, + } } #[cfg(test)] mod tests { use super::*; - use std::net::Ipv4Addr; - + use nym_crypto::asymmetric::ed25519; + use nym_test_utils::helpers::deterministic_rng; + use std::net::{Ipv4Addr, Ipv6Addr}; // ==================== Helper Functions ==================== - fn create_test_gateway_data() -> GatewayData { - use std::net::Ipv6Addr; - - GatewayData { + fn create_test_wg_config() -> WireguardRegistrationData { + WireguardRegistrationData { public_key: nym_crypto::asymmetric::x25519::PublicKey::from( nym_sphinx::PublicKey::from([1u8; 32]), ), + port: 1234, private_ipv4: Ipv4Addr::new(10, 0, 0, 1), private_ipv6: Ipv6Addr::new(0xfc00, 0, 0, 0, 0, 0, 0, 1), - endpoint: "192.168.1.1:8080".parse().expect("Valid test endpoint"), } } @@ -189,96 +446,81 @@ mod tests { // ==================== LpRegistrationResponse Tests ==================== - #[test] - fn test_lp_registration_response_success() { - let gateway_data = create_test_gateway_data(); - let allocated_bandwidth = 1_000_000_000; - - let response = LpRegistrationResponse::success(allocated_bandwidth, gateway_data.clone()); - - assert!(response.success); - assert!(response.error.is_none()); - assert!(response.gateway_data.is_some()); - assert_eq!(response.allocated_bandwidth, allocated_bandwidth); - - let returned_gw_data = response - .gateway_data - .expect("Gateway data should be present in success response"); - assert_eq!(returned_gw_data.public_key, gateway_data.public_key); - assert_eq!(returned_gw_data.private_ipv4, gateway_data.private_ipv4); - assert_eq!(returned_gw_data.private_ipv6, gateway_data.private_ipv6); - assert_eq!(returned_gw_data.endpoint, gateway_data.endpoint); - } - #[test] fn test_lp_registration_response_error() { let error_msg = String::from("Insufficient bandwidth"); - let response = LpRegistrationResponse::error(error_msg.clone()); + let response_mixnet = + LpRegistrationResponse::error(error_msg.clone(), RegistrationMode::Mixnet); + let response_dvpn = + LpRegistrationResponse::error(error_msg.clone(), RegistrationMode::Dvpn); - assert!(!response.success); - assert_eq!(response.error, Some(error_msg)); - assert!(response.gateway_data.is_none()); - assert_eq!(response.allocated_bandwidth, 0); - } - // ==================== RegistrationMode Tests ==================== + assert!(response_mixnet.status.is_failed()); + assert!(response_dvpn.status.is_failed()); - #[test] - fn test_registration_mode_serialize_dvpn() { - let mode = RegistrationMode::Dvpn; - - let serialized = bincode::serialize(&mode).expect("Failed to serialize mode"); - let deserialized: RegistrationMode = - bincode::deserialize(&serialized).expect("Failed to deserialize mode"); - - assert!(matches!(deserialized, RegistrationMode::Dvpn)); - } - - #[test] - fn test_registration_mode_serialize_mixnet() { - let client_ed25519_pubkey = [99u8; 32]; - let client_x25519_pubkey = [88u8; 32]; - let mode = RegistrationMode::Mixnet { - client_ed25519_pubkey, - client_x25519_pubkey, + // check mixnet + let LpRegistrationResponseData::Mixnet { data } = response_mixnet.response_data else { + panic!("unexpected response") }; - let serialized = bincode::serialize(&mode).expect("Failed to serialize mode"); - let deserialized: RegistrationMode = - bincode::deserialize(&serialized).expect("Failed to deserialize mode"); + let LpMixnetRegistrationResponseMessageContent::RegistrationFailure(failure) = data.content + else { + panic!("unexpected response") + }; + assert_eq!(failure.error, error_msg); - match deserialized { - RegistrationMode::Mixnet { - client_ed25519_pubkey: ed25519, - client_x25519_pubkey: x25519, - } => { - assert_eq!(ed25519, client_ed25519_pubkey); - assert_eq!(x25519, client_x25519_pubkey); - } - _ => panic!("Expected Mixnet mode"), - } + // check dvpn + let LpRegistrationResponseData::Dvpn { data } = response_dvpn.response_data else { + panic!("unexpected response") + }; + + let LpDvpnRegistrationResponseMessageContent::RegistrationFailure(failure) = data.content + else { + panic!("unexpected response") + }; + assert_eq!(failure.error, error_msg); + } + + #[test] + fn test_lp_registration_response_success_dvpn() { + let cfg = create_test_wg_config(); + + let response = LpRegistrationResponse::success_dvpn(cfg, false); + assert!(response.status.is_successful()); + + let LpRegistrationResponseData::Dvpn { data } = response.response_data else { + panic!("unexpected response") + }; + + let LpDvpnRegistrationResponseMessageContent::CompletedRegistration(complete) = + data.content + else { + panic!("unexpected response") + }; + assert_eq!(complete.config, cfg); + assert!(!complete.upgrade_mode); } #[test] fn test_lp_registration_response_success_mixnet() { - let lp_gateway_data = LpGatewayData { - gateway_identity: [1u8; 32], - gateway_sphinx_key: [2u8; 32], + let mut rng = deterministic_rng(); + let valid_key = ed25519::KeyPair::new(&mut rng); + + let lp_gateway_data = LpMixnetGatewayData { + gateway_identity: *valid_key.public_key(), }; - let allocated_bandwidth = 500_000_000; + let response = LpRegistrationResponse::success_mixnet(lp_gateway_data.clone()); + assert!(response.status.is_successful()); - let response = LpRegistrationResponse::success_mixnet(allocated_bandwidth, lp_gateway_data); + let LpRegistrationResponseData::Mixnet { data } = response.response_data else { + panic!("unexpected response") + }; - assert!(response.success); - assert!(response.error.is_none()); - assert!(response.gateway_data.is_none()); - assert!(response.lp_gateway_data.is_some()); - assert_eq!(response.allocated_bandwidth, allocated_bandwidth); - - let gw_data = response - .lp_gateway_data - .expect("LpGatewayData should be present"); - assert_eq!(gw_data.gateway_identity, [1u8; 32]); - assert_eq!(gw_data.gateway_sphinx_key, [2u8; 32]); + let LpMixnetRegistrationResponseMessageContent::CompletedRegistration(complete) = + data.content + else { + panic!("unexpected response") + }; + assert_eq!(complete.config, lp_gateway_data); } } diff --git a/common/nym-lp/src/serialisation.rs b/common/registration/src/serialisation.rs similarity index 100% rename from common/nym-lp/src/serialisation.rs rename to common/registration/src/serialisation.rs diff --git a/common/serde-helpers/Cargo.toml b/common/serde-helpers/Cargo.toml index bc9de1862e..9b219e23cb 100644 --- a/common/serde-helpers/Cargo.toml +++ b/common/serde-helpers/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "nym-serde-helpers" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "Serde helpers for hex/base64/base58/datetime" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -22,4 +23,4 @@ time = { workspace = true, features = ["formatting", "parsing"], optional = true hex = ["dep:hex"] bs58 = ["dep:bs58"] base64 = ["dep:base64"] -date = ["time"] \ No newline at end of file +date = ["time"] diff --git a/common/service-provider-requests-common/Cargo.toml b/common/service-provider-requests-common/Cargo.toml index 63aef2eb76..b289a776b7 100644 --- a/common/service-provider-requests-common/Cargo.toml +++ b/common/service-provider-requests-common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-service-provider-requests-common" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true @@ -9,6 +9,7 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +description = "Common crate for requests and responses for Nym Service Providers" [dependencies] serde = { workspace = true, features = ["derive"] } diff --git a/common/service-provider-requests-common/src/lib.rs b/common/service-provider-requests-common/src/lib.rs index 44e54dc2af..c43ea89074 100644 --- a/common/service-provider-requests-common/src/lib.rs +++ b/common/service-provider-requests-common/src/lib.rs @@ -11,7 +11,7 @@ pub enum ProtocolError { InvalidServiceProviderType(u8), } -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] #[repr(u8)] pub enum ServiceProviderType { NetworkRequester = 0, @@ -76,7 +76,7 @@ impl ServiceProviderTypeExt for u8 { } } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)] pub struct Protocol { pub version: u8, pub service_provider_type: ServiceProviderType, diff --git a/common/socks5-client-core/Cargo.toml b/common/socks5-client-core/Cargo.toml index ce8421f3d7..f86c6cf686 100644 --- a/common/socks5-client-core/Cargo.toml +++ b/common/socks5-client-core/Cargo.toml @@ -1,8 +1,12 @@ [package] name = "nym-socks5-client-core" -version = "0.1.0" +version.workspace = true edition = "2021" license.workspace = true +description = "Core functionality of the Nym SOCKS client" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -21,19 +25,19 @@ thiserror = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread", "net", "signal"] } url = { workspace = true } -nym-bandwidth-controller = { path = "../../common/bandwidth-controller" } -nym-client-core = { path = "../client-core", features = ["fs-credentials-storage", "fs-surb-storage", "fs-gateways-storage"] } -nym-config = { path = "../config" } -nym-contracts-common = { path = "../cosmwasm-smart-contracts/contracts-common" } -nym-credential-storage = { path = "../credential-storage" } -nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" } -nym-network-defaults = { path = "../network-defaults" } -nym-service-providers-common = { path = "../../service-providers/common" } -nym-socks5-proxy-helpers = { path = "../socks5/proxy-helpers" } -nym-socks5-requests = { path = "../socks5/requests" } -nym-sphinx = { path = "../nymsphinx" } -nym-task = { path = "../task" } -nym-validator-client = { path = "../client-libs/validator-client" } +nym-bandwidth-controller = { workspace = true } +nym-client-core = { workspace = true, features = ["fs-credentials-storage", "fs-surb-storage", "fs-gateways-storage"] } +nym-config = { workspace = true } +nym-contracts-common = { workspace = true } +nym-credential-storage = { workspace = true } +nym-mixnet-contract-common = { workspace = true } +nym-network-defaults = { workspace = true } +nym-service-providers-common = { workspace = true } +nym-socks5-proxy-helpers = { workspace = true } +nym-socks5-requests = { workspace = true } +nym-sphinx = { workspace = true } +nym-task = { workspace = true } +nym-validator-client = { workspace = true } [features] default = [] diff --git a/common/socks5/ordered-buffer/Cargo.toml b/common/socks5/ordered-buffer/Cargo.toml index f8d684cd1f..572f2c0d59 100644 --- a/common/socks5/ordered-buffer/Cargo.toml +++ b/common/socks5/ordered-buffer/Cargo.toml @@ -1,9 +1,13 @@ [package] name = "nym-ordered-buffer" -version = "0.1.0" +version.workspace = true authors = ["Dave Hrycyszyn "] edition = "2021" license.workspace = true +description = "This crate takes care of reliably and speedily turning bytes into a series of ordered message fragments on one side, and of reliably reassembling the fragments into the original message on the other" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/common/socks5/proxy-helpers/Cargo.toml b/common/socks5/proxy-helpers/Cargo.toml index da1d98e254..9b74fd005d 100644 --- a/common/socks5/proxy-helpers/Cargo.toml +++ b/common/socks5/proxy-helpers/Cargo.toml @@ -1,9 +1,13 @@ [package] name = "nym-socks5-proxy-helpers" -version = "0.1.0" +version.workspace = true authors = ["Jedrzej Stuczynski "] edition = "2021" license.workspace = true +description = "Helpers for the Nym SOCKS client" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -17,9 +21,9 @@ futures = { workspace = true } log = { workspace = true } # internal -nym-ordered-buffer = { path = "../ordered-buffer" } -nym-socks5-requests = { path = "../requests" } -nym-task = { path = "../../task" } +nym-ordered-buffer = { workspace = true } +nym-socks5-requests = { workspace = true } +nym-task = { workspace = true } [dev-dependencies] tokio-test = { workspace = true } diff --git a/common/socks5/requests/Cargo.toml b/common/socks5/requests/Cargo.toml index be8ec4f8ca..1197c4beb3 100644 --- a/common/socks5/requests/Cargo.toml +++ b/common/socks5/requests/Cargo.toml @@ -1,18 +1,22 @@ [package] name = "nym-socks5-requests" -version = "0.1.0" +version.workspace = true authors = ["Dave Hrycyszyn "] edition = "2021" license.workspace = true +description = "Request and response definitions for the Nym SOCKS client" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] bincode = { workspace = true } log = { workspace = true } -nym-exit-policy = { path = "../../../common/exit-policy"} -nym-service-providers-common = { path = "../../../service-providers/common" } -nym-sphinx-addressing = { path = "../../../common/nymsphinx/addressing" } +nym-exit-policy = { workspace = true} +nym-service-providers-common = { workspace = true } +nym-sphinx-addressing = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } tap = { workspace = true } diff --git a/common/statistics/Cargo.toml b/common/statistics/Cargo.toml index b0f122646c..c086bd8374 100644 --- a/common/statistics/Cargo.toml +++ b/common/statistics/Cargo.toml @@ -3,9 +3,13 @@ [package] name = "nym-statistics-common" -version = "0.1.0" +version.workspace = true edition.workspace = true license.workspace = true +description = "This crate contains basic statistics utilities and abstractions to be re-used and applied throughout both the client and gateway implementations" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -23,11 +27,11 @@ si-scale = { workspace = true } strum = { workspace = true } strum_macros = { workspace = true } -nym-crypto = { path = "../crypto" } -nym-sphinx = { path = "../nymsphinx" } -nym-credentials-interface = { path = "../credentials-interface" } -nym-metrics = { path = "../nym-metrics" } -nym-task = { path = "../task" } +nym-crypto = { workspace = true } +nym-sphinx = { workspace = true } +nym-credentials-interface = { workspace = true } +nym-metrics = { workspace = true } +nym-task = { workspace = true } utoipa = { workspace = true, optional = true } diff --git a/common/store-cipher/Cargo.toml b/common/store-cipher/Cargo.toml index ca0a628635..124d5b1869 100644 --- a/common/store-cipher/Cargo.toml +++ b/common/store-cipher/Cargo.toml @@ -1,8 +1,12 @@ [package] name = "nym-store-cipher" -version = "0.1.0" +version.workspace = true edition = "2021" license.workspace = true +description = "Helpers for various ciphers used throughout the Nym network" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/common/task/Cargo.toml b/common/task/Cargo.toml index d96fcbe068..0daef2c763 100644 --- a/common/task/Cargo.toml +++ b/common/task/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-task" -version = "0.1.0" +version.workspace = true description = "Task handling" edition.workspace = true authors.workspace = true @@ -36,7 +36,7 @@ tokio-tracing = ["tokio/tracing"] [dev-dependencies] anyhow = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread", "net", "signal", "test-util", "macros"] } -nym-test-utils = { path = "../test-utils" } +nym-test-utils = { workspace = true } [lints] -workspace = true \ No newline at end of file +workspace = true diff --git a/common/test-utils/Cargo.toml b/common/test-utils/Cargo.toml index 5993d47a30..f89073afc7 100644 --- a/common/test-utils/Cargo.toml +++ b/common/test-utils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-test-utils" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true @@ -9,6 +9,7 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +description = "Helpers, traits, and mock definitions for tests" [dependencies] anyhow = { workspace = true } @@ -17,7 +18,7 @@ rand_chacha = { workspace = true } tokio = { workspace = true, features = ["sync", "time", "rt"] } tracing = { workspace = true } -nym-bin-common = { path = "../bin-common", features = ["tracing"] } +nym-bin-common = { workspace = true, features = ["tracing"] } [dev-dependencies] tokio = { workspace = true, features = ["full"] } diff --git a/common/test-utils/src/helpers.rs b/common/test-utils/src/helpers.rs index 29d6c184d3..e96494dfc0 100644 --- a/common/test-utils/src/helpers.rs +++ b/common/test-utils/src/helpers.rs @@ -2,16 +2,16 @@ // SPDX-License-Identifier: Apache-2.0 use crate::traits::Timeboxed; -use rand_chacha::ChaCha20Rng; +use nym_bin_common::logging::tracing_subscriber::EnvFilter; +use nym_bin_common::logging::tracing_subscriber::layer::SubscriberExt; +use nym_bin_common::logging::tracing_subscriber::util::SubscriberInitExt; +use nym_bin_common::logging::{default_tracing_fmt_layer, tracing_subscriber}; use rand_chacha::rand_core::SeedableRng; use std::future::Future; use tokio::task::JoinHandle; use tokio::time::error::Elapsed; -use nym_bin_common::logging::tracing_subscriber::EnvFilter; -use nym_bin_common::logging::tracing_subscriber::layer::SubscriberExt; -use nym_bin_common::logging::tracing_subscriber::util::SubscriberInitExt; -use nym_bin_common::logging::{default_tracing_fmt_layer, tracing_subscriber}; +pub use rand_chacha::ChaCha20Rng as DeterministicRng; pub use rand_chacha::rand_core::{CryptoRng, RngCore}; pub fn leak(val: T) -> &'static mut T { @@ -26,16 +26,16 @@ where tokio::spawn(async move { fut.timeboxed().await }) } -pub fn deterministic_rng() -> ChaCha20Rng { +pub fn deterministic_rng() -> DeterministicRng { seeded_rng([42u8; 32]) } -pub fn seeded_rng(seed: [u8; 32]) -> ChaCha20Rng { - ChaCha20Rng::from_seed(seed) +pub fn seeded_rng(seed: [u8; 32]) -> DeterministicRng { + DeterministicRng::from_seed(seed) } -pub fn u64_seeded_rng(seed: u64) -> ChaCha20Rng { - ChaCha20Rng::seed_from_u64(seed) +pub fn u64_seeded_rng(seed: u64) -> DeterministicRng { + DeterministicRng::seed_from_u64(seed) } // test logger to use during debugging diff --git a/common/ticketbooks-merkle/Cargo.toml b/common/ticketbooks-merkle/Cargo.toml index 3c3f278c6b..037852a91d 100644 --- a/common/ticketbooks-merkle/Cargo.toml +++ b/common/ticketbooks-merkle/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-ticketbooks-merkle" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true @@ -9,6 +9,7 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +description = "Generate and verify merkleproofs of zknym ticketbooks" [dependencies] sha2 = { workspace = true } @@ -18,8 +19,8 @@ utoipa = { workspace = true } serde = { workspace = true, features = ["derive"] } time = { workspace = true } -nym-credentials-interface = { path = "../credentials-interface" } -nym-serde-helpers = { path = "../serde-helpers", features = ["date", "base64", "hex"] } +nym-credentials-interface = { workspace = true } +nym-serde-helpers = { workspace = true, features = ["date", "base64", "hex"] } [dev-dependencies] rand_chacha = { workspace = true } diff --git a/common/topology/Cargo.toml b/common/topology/Cargo.toml index 64d60171f4..da51f63bbd 100644 --- a/common/topology/Cargo.toml +++ b/common/topology/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "nym-topology" -version = "0.1.0" +version.workspace = true edition = { workspace = true } authors = { workspace = true } license = { workspace = true } repository = { workspace = true } homepage = { workspace = true } documentation = { workspace = true } +description = "Nym's topology crate" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -27,25 +28,25 @@ tsify = { workspace = true, features = ["js"], optional = true } wasm-bindgen = { workspace = true, optional = true } ## internal -nym-crypto = { path = "../crypto" } -nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" } -nym-sphinx-addressing = { path = "../nymsphinx/addressing" } -nym-sphinx-types = { path = "../nymsphinx/types", features = [ +nym-crypto = { workspace = true } +nym-mixnet-contract-common = { workspace = true } +nym-sphinx-addressing = { workspace = true } +nym-sphinx-types = { workspace = true, features = [ "sphinx", "outfox", ] } # I'm not sure how to feel about pulling in this dependency here... -nym-api-requests = { path = "../../nym-api/nym-api-requests" } +nym-api-requests = { workspace = true } # 'wasm-serde-types' feature -wasm-utils = { path = "../wasm/utils", default-features = false, optional = true } +nym-wasm-utils = { workspace = true, optional = true } [features] default = ["provider-trait"] provider-trait = ["async-trait"] -wasm-serde-types = ["tsify", "wasm-bindgen", "wasm-utils"] +wasm-serde-types = ["tsify", "wasm-bindgen", "nym-wasm-utils"] persistence = ["serde_json"] outfox = [] diff --git a/common/topology/src/node.rs b/common/topology/src/node.rs index 85cb81b51f..8100977bee 100644 --- a/common/topology/src/node.rs +++ b/common/topology/src/node.rs @@ -1,7 +1,7 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use nym_api_requests::models::DeclaredRoles; +use nym_api_requests::models::DeclaredRolesV1; use nym_api_requests::nym_nodes::SkimmedNode; use nym_crypto::asymmetric::{ed25519, x25519}; use nym_mixnet_contract_common::NodeId; @@ -35,8 +35,8 @@ pub struct SupportedRoles { pub mixnet_exit: bool, } -impl From for SupportedRoles { - fn from(value: DeclaredRoles) -> Self { +impl From for SupportedRoles { + fn from(value: DeclaredRolesV1) -> Self { SupportedRoles { mixnode: value.mixnode, mixnet_entry: value.entry, diff --git a/common/topology/src/wasm_helpers.rs b/common/topology/src/wasm_helpers.rs index 1abf88272b..8d07cbcce0 100644 --- a/common/topology/src/wasm_helpers.rs +++ b/common/topology/src/wasm_helpers.rs @@ -6,13 +6,13 @@ use crate::node::{EntryDetails, RoutingNode, RoutingNodeError, SupportedRoles}; use crate::{CachedEpochRewardedSet, NymTopology, NymTopologyMetadata}; +use nym_wasm_utils::error::simple_js_error; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::net::SocketAddr; use thiserror::Error; use tsify::Tsify; use wasm_bindgen::{JsValue, prelude::wasm_bindgen}; -use wasm_utils::error::simple_js_error; #[derive(Debug, Error)] pub enum SerializableTopologyError { diff --git a/common/tun/Cargo.toml b/common/tun/Cargo.toml index 3915e481e3..e5451f0f81 100644 --- a/common/tun/Cargo.toml +++ b/common/tun/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "nym-tun" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "Nym's tun implementation" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -15,7 +16,7 @@ thiserror.workspace = true tokio = { workspace = true, features = ["rt-multi-thread", "net", "io-util", "time", "sync", "macros"] } etherparse = { workspace = true } log.workspace = true -nym-wireguard-types = { path = "../wireguard-types", optional = true } +nym-wireguard-types = { workspace = true, optional = true } [target.'cfg(target_os = "linux")'.dependencies] tokio-tun.workspace = true diff --git a/common/types/Cargo.toml b/common/types/Cargo.toml index 620ec264d1..2f6ae032b4 100644 --- a/common/types/Cargo.toml +++ b/common/types/Cargo.toml @@ -1,11 +1,14 @@ [package] name = "nym-types" -version = "1.0.0" +version.workspace = true description = "Nym common types" authors.workspace = true edition = "2021" rust-version.workspace = true license.workspace = true +repository.workspace = true +homepage.workspace = true +documentation.workspace = true [dependencies] base64 = { workspace = true } @@ -29,11 +32,11 @@ x25519-dalek = { workspace = true, features = ["static_secrets"] } cosmwasm-std = { workspace = true } cosmrs = { workspace = true } -nym-validator-client = { path = "../../common/client-libs/validator-client" } -nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract" } -nym-vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract" } -nym-config = { path = "../../common/config" } -nym-crypto = { path = "../../common/crypto", features = ["asymmetric"] } +nym-validator-client = { workspace = true } +nym-mixnet-contract-common = { workspace = true } +nym-vesting-contract-common = { workspace = true } +nym-config = { workspace = true } +nym-crypto = { workspace = true, features = ["asymmetric"] } [dev-dependencies] tempfile = { workspace = true } diff --git a/common/upgrade-mode-check/Cargo.toml b/common/upgrade-mode-check/Cargo.toml index 17f292a566..8078ede9a4 100644 --- a/common/upgrade-mode-check/Cargo.toml +++ b/common/upgrade-mode-check/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-upgrade-mode-check" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true @@ -9,6 +9,7 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +description = "Functions and tests for checking Nym's Credential Proxy is being properly upgraded" [dependencies] jwt-simple = { workspace = true } @@ -20,14 +21,14 @@ thiserror = { workspace = true } tracing = { workspace = true } utoipa = { workspace = true, optional = true } -nym-http-api-client = { path = "../http-api-client", default-features = false } -nym-crypto = { path = "../crypto", features = ["asymmetric", "serde", "naive_jwt"] } +nym-http-api-client = { workspace = true } +nym-crypto = { workspace = true, features = ["asymmetric", "serde", "naive_jwt"] } [dev-dependencies] anyhow = { workspace = true } time = { workspace = true, features = ["macros"] } -nym-test-utils = { path = "../test-utils" } -nym-crypto = { path = "../crypto", features = ["rand"] } +nym-test-utils = { workspace = true } +nym-crypto = { workspace = true, features = ["rand"] } [features] diff --git a/common/verloc/Cargo.toml b/common/verloc/Cargo.toml index 32b81ed1d6..fa218f9aee 100644 --- a/common/verloc/Cargo.toml +++ b/common/verloc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-verloc" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true @@ -9,6 +9,7 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +description = "Nym's verloc (Verifiable Location) implementation" [dependencies] bytes = { workspace = true } @@ -22,8 +23,8 @@ thiserror = { workspace = true } rand = { workspace = true } url = { workspace = true } -nym-crypto = { path = "../crypto", features = ["asymmetric"] } -nym-task = { path = "../task" } -nym-validator-client = { path = "../client-libs/validator-client" } -nym-http-api-client = { path = "../http-api-client" } -nym-api-requests = { path = "../../nym-api/nym-api-requests" } +nym-crypto = { workspace = true, features = ["asymmetric"] } +nym-task = { workspace = true } +nym-validator-client = { workspace = true } +nym-http-api-client = { workspace = true } +nym-api-requests = { workspace = true } diff --git a/common/verloc/src/measurements/measurer.rs b/common/verloc/src/measurements/measurer.rs index cc83426a64..edbbfab765 100644 --- a/common/verloc/src/measurements/measurer.rs +++ b/common/verloc/src/measurements/measurer.rs @@ -9,7 +9,7 @@ use futures::StreamExt; use futures::stream::FuturesUnordered; use nym_crypto::asymmetric::ed25519; use nym_task::ShutdownToken; -use nym_validator_client::models::NymNodeDescription; +use nym_validator_client::models::NymNodeDescriptionV1; use nym_validator_client::nym_api::NymApiClientExt; use rand::prelude::SliceRandom; use rand::thread_rng; @@ -132,7 +132,7 @@ impl VerlocMeasurer { MeasurementOutcome::Done } - async fn get_list_of_nodes(&self) -> Option> { + async fn get_list_of_nodes(&self) -> Option> { let mut api_endpoints = self.config.nym_api_urls.clone(); api_endpoints.shuffle(&mut thread_rng()); for api_endpoint in api_endpoints { @@ -145,6 +145,10 @@ impl VerlocMeasurer { continue; } }; + + // for now allow usage of old endpoint for we don't need LP related data + // and the new endpoint might not be immediately deployed + #[allow(deprecated)] match client.get_all_described_nodes().await { Ok(res) => return Some(res), Err(err) => { diff --git a/common/wasm/client-core/Cargo.toml b/common/wasm/client-core/Cargo.toml index 85668972f9..f7811e6f84 100644 --- a/common/wasm/client-core/Cargo.toml +++ b/common/wasm/client-core/Cargo.toml @@ -1,10 +1,11 @@ [package] -name = "wasm-client-core" +name = "nym-wasm-client-core" authors = ["Jedrzej Stuczynski "] -version = "0.1.0" +version.workspace = true edition = "2021" license = "Apache-2.0" repository = "https://github.com/nymtech/nym" +description = "Crate containing core WASM client functionality and configs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -22,21 +23,21 @@ wasm-bindgen = { workspace = true } wasm-bindgen-futures = { workspace = true } zeroize = { workspace = true } -nym-bandwidth-controller = { path = "../../bandwidth-controller" } -nym-client-core = { path = "../../client-core", default-features = false, features = ["wasm"] } -nym-config = { path = "../../config" } -nym-credential-storage = { path = "../../credential-storage" } -nym-crypto = { path = "../../crypto", features = ["asymmetric", "serde"] } -nym-gateway-client = { path = "../../client-libs/gateway-client", default-features = false, features = ["wasm"] } -nym-sphinx = { path = "../../nymsphinx" } -nym-sphinx-acknowledgements = { path = "../../nymsphinx/acknowledgements", features = ["serde"] } -nym-statistics-common = { path = "../../statistics" } -nym-task = { path = "../../task" } -nym-topology = { path = "../../topology", features = ["wasm-serde-types"] } -nym-validator-client = { path = "../../client-libs/validator-client", default-features = false } -nym-http-api-client = { path = "../../http-api-client" } -wasm-utils = { path = "../utils" } -wasm-storage = { path = "../storage" } +nym-bandwidth-controller = { workspace = true } +nym-client-core = { workspace = true, default-features = false, features = ["wasm"] } +nym-config = { workspace = true } +nym-credential-storage = { workspace = true } +nym-crypto = { workspace = true, features = ["asymmetric", "serde"] } +nym-gateway-client = { workspace = true, default-features = false, features = ["wasm"] } +nym-sphinx = { workspace = true } +nym-sphinx-acknowledgements = { workspace = true, features = ["serde"] } +nym-statistics-common = { workspace = true } +nym-task = { workspace = true } +nym-topology = { workspace = true, features = ["wasm-serde-types"] } +nym-validator-client = { workspace = true, default-features = false } +nym-http-api-client = { workspace = true } +nym-wasm-utils = { workspace = true } +nym-wasm-storage = { workspace = true } # The `console_error_panic_hook` crate provides better debugging of panics by diff --git a/common/wasm/client-core/src/error.rs b/common/wasm/client-core/src/error.rs index ab41b190dd..6c93b1846a 100644 --- a/common/wasm/client-core/src/error.rs +++ b/common/wasm/client-core/src/error.rs @@ -11,8 +11,8 @@ use nym_sphinx::addressing::clients::RecipientFormattingError; use nym_sphinx::anonymous_replies::requests::InvalidAnonymousSenderTagRepresentation; use nym_topology::NymTopologyError; use nym_validator_client::ValidatorClientError; +use nym_wasm_utils::wasm_error; use thiserror::Error; -use wasm_utils::wasm_error; #[derive(Debug, Error)] pub enum WasmCoreError { @@ -85,7 +85,7 @@ pub enum WasmCoreError { #[error(transparent)] BaseStorageError { #[from] - source: wasm_storage::error::StorageError, + source: nym_wasm_storage::error::StorageError, }, #[error(transparent)] diff --git a/common/wasm/client-core/src/helpers.rs b/common/wasm/client-core/src/helpers.rs index 8d7c4dc6e4..2b69630cf7 100644 --- a/common/wasm/client-core/src/helpers.rs +++ b/common/wasm/client-core/src/helpers.rs @@ -22,15 +22,15 @@ use nym_topology::wasm_helpers::WasmFriendlyNymTopology; use nym_topology::{EpochRewardedSet, NymTopology, RoutingNode}; use nym_validator_client::client::IdentityKey; use nym_validator_client::{nym_api::NymApiClientExt, UserAgent}; +use nym_wasm_utils::error::PromisableResult; use rand::thread_rng; use url::Url; use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen_futures::future_to_promise; -use wasm_utils::error::PromisableResult; pub use nym_credential_storage::ephemeral_storage::EphemeralStorage as EphemeralCredentialStorage; use nym_topology::provider_trait::ToTopologyMetadata; -use wasm_utils::{console_log, console_warn}; +use nym_wasm_utils::{console_log, console_warn}; // don't get too excited about the name, under the hood it's just a big fat placeholder // with no disk_persistence diff --git a/common/wasm/client-core/src/lib.rs b/common/wasm/client-core/src/lib.rs index 761df16ae4..90c70febfd 100644 --- a/common/wasm/client-core/src/lib.rs +++ b/common/wasm/client-core/src/lib.rs @@ -35,4 +35,4 @@ pub use nym_validator_client::{DirectSigningReqwestRpcNyxdClient, QueryReqwestRp pub use nym_validator_client::client::IdentityKey; #[cfg(target_arch = "wasm32")] -pub use wasm_utils::set_panic_hook; +pub use nym_wasm_utils::set_panic_hook; diff --git a/common/wasm/client-core/src/storage/core_client_traits.rs b/common/wasm/client-core/src/storage/core_client_traits.rs index 9e1e409891..7526b28eab 100644 --- a/common/wasm/client-core/src/storage/core_client_traits.rs +++ b/common/wasm/client-core/src/storage/core_client_traits.rs @@ -18,7 +18,7 @@ use nym_client_core::client::key_manager::ClientKeys; use nym_client_core::client::replies::reply_storage::browser_backend; use nym_credential_storage::ephemeral_storage::EphemeralStorage as EphemeralCredentialStorage; use nym_crypto::asymmetric::ed25519; -use wasm_utils::console_log; +use nym_wasm_utils::console_log; // temporary until other variants are properly implemented (probably it should get changed into `ClientStorage` // implementing all traits and everything getting combined diff --git a/common/wasm/client-core/src/storage/mod.rs b/common/wasm/client-core/src/storage/mod.rs index b129ab331b..08c988adef 100644 --- a/common/wasm/client-core/src/storage/mod.rs +++ b/common/wasm/client-core/src/storage/mod.rs @@ -5,15 +5,15 @@ use crate::error::WasmCoreError; use crate::storage::wasm_client_traits::{v1, v2, WasmClientStorage}; use async_trait::async_trait; use js_sys::Promise; +use nym_wasm_storage::traits::BaseWasmStorage; +use nym_wasm_storage::{ + Build, Database, RawDbResult, TryFromJs, TryToJs, VersionChangeEvent, WasmStorage, +}; +use nym_wasm_utils::error::{simple_js_error, PromisableResult}; use serde::de::DeserializeOwned; use serde::Serialize; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::future_to_promise; -use wasm_storage::traits::BaseWasmStorage; -use wasm_storage::{ - Build, Database, RawDbResult, TryFromJs, TryToJs, VersionChangeEvent, WasmStorage, -}; -use wasm_utils::error::{simple_js_error, PromisableResult}; use zeroize::Zeroizing; pub mod core_client_traits; diff --git a/common/wasm/client-core/src/storage/wasm_client_traits.rs b/common/wasm/client-core/src/storage/wasm_client_traits.rs index 916d22cd1d..eb6d791b39 100644 --- a/common/wasm/client-core/src/storage/wasm_client_traits.rs +++ b/common/wasm/client-core/src/storage/wasm_client_traits.rs @@ -6,10 +6,10 @@ use async_trait::async_trait; use nym_client_core::client::base_client::storage::gateways_storage::RawActiveGateway; use nym_crypto::asymmetric::{ed25519, x25519}; use nym_sphinx_acknowledgements::AckKey; +use nym_wasm_storage::traits::BaseWasmStorage; use std::error::Error; use thiserror::Error; use wasm_bindgen::JsValue; -use wasm_storage::traits::BaseWasmStorage; // v1 tables pub(crate) mod v1 { diff --git a/common/wasm/storage/Cargo.toml b/common/wasm/storage/Cargo.toml index 71364c0c57..884a7b89b6 100644 --- a/common/wasm/storage/Cargo.toml +++ b/common/wasm/storage/Cargo.toml @@ -1,9 +1,13 @@ [package] -name = "wasm-storage" -version = "0.1.0" +name = "nym-wasm-storage" +version.workspace = true authors = ["Jedrzej Stuczynski "] edition = "2021" license.workspace = true +description = "indexeddb-backed in-browser storage with optional encryption implentation and helpers" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -17,5 +21,5 @@ serde-wasm-bindgen = { workspace = true } indexed_db_futures = { workspace = true } thiserror = { workspace = true } -nym-store-cipher = { path = "../../store-cipher", features = ["json"] } -wasm-utils = { path = "../utils", default-features = false } +nym-store-cipher = { workspace = true, features = ["json"] } +nym-wasm-utils = { workspace = true, default-features = false } diff --git a/common/wasm/storage/src/error.rs b/common/wasm/storage/src/error.rs index 3ca74adee4..ee28306c8b 100644 --- a/common/wasm/storage/src/error.rs +++ b/common/wasm/storage/src/error.rs @@ -1,10 +1,10 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use nym_wasm_utils::error::simple_js_error; use serde_wasm_bindgen::Error; use thiserror::Error; use wasm_bindgen::JsValue; -use wasm_utils::error::simple_js_error; #[derive(Debug, Error)] pub enum StorageError { diff --git a/common/wasm/storage/src/lib.rs b/common/wasm/storage/src/lib.rs index 8d9e795270..c20015ab53 100644 --- a/common/wasm/storage/src/lib.rs +++ b/common/wasm/storage/src/lib.rs @@ -8,11 +8,11 @@ use nym_store_cipher::{ Aes256Gcm, Algorithm, EncryptedData, KdfInfo, KeySizeUser, Params, StoreCipher, Unsigned, Version, }; +use nym_wasm_utils::console_log; use serde::de::DeserializeOwned; use serde::Serialize; use std::future::IntoFuture; use wasm_bindgen::JsValue; -use wasm_utils::console_log; pub use indexed_db_futures::database::{Database, VersionChangeEvent}; pub use indexed_db_futures::prelude::*; diff --git a/common/wasm/utils/Cargo.toml b/common/wasm/utils/Cargo.toml index 7305908344..49af79aa41 100644 --- a/common/wasm/utils/Cargo.toml +++ b/common/wasm/utils/Cargo.toml @@ -1,9 +1,13 @@ [package] -name = "wasm-utils" -version = "0.1.0" +name = "nym-wasm-utils" +version.workspace = true authors = ["Jedrzej Stuczynski "] edition = "2021" license.workspace = true +description = "Helpers and macros for the Nym WASM client" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -15,7 +19,7 @@ wasm-bindgen-futures = { workspace = true } getrandom = { workspace = true, features = ["js"], optional = true } gloo-utils = { workspace = true } gloo-net = { workspace = true, features = ["websocket"], optional = true } -#gloo-net = { path = "../../../../gloo/crates/net", features = ["websocket"], optional = true } +#gloo-net = { workspace = truepath = "../../../../gloo/crates/net", features = ["websocket"], optional = true } console_error_panic_hook = { workspace = true, optional = true } diff --git a/common/wireguard-private-metadata/client/Cargo.toml b/common/wireguard-private-metadata/client/Cargo.toml index d7d23ce445..d039810387 100644 --- a/common/wireguard-private-metadata/client/Cargo.toml +++ b/common/wireguard-private-metadata/client/Cargo.toml @@ -1,18 +1,19 @@ [package] name = "nym-wireguard-private-metadata-client" -version = "1.0.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "nym-wireguard client implementation" [dependencies] async-trait = { workspace = true } tracing = { workspace = true } -nym-http-api-client = { path = "../../http-api-client" } -nym-wireguard-private-metadata-shared = { path = "../shared" } +nym-http-api-client = { workspace = true } +nym-wireguard-private-metadata-shared = { workspace = true } [lints] workspace = true diff --git a/common/wireguard-private-metadata/server/Cargo.toml b/common/wireguard-private-metadata/server/Cargo.toml index fa850181f1..b16725b149 100644 --- a/common/wireguard-private-metadata/server/Cargo.toml +++ b/common/wireguard-private-metadata/server/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "nym-wireguard-private-metadata-server" -version = "1.0.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "nym-wireguard server implementation" [dependencies] anyhow = { workspace = true } @@ -25,15 +26,15 @@ tower-http = { workspace = true, features = [ utoipa = { workspace = true, features = ["axum_extras", "time"] } utoipa-swagger-ui = { workspace = true, features = ["axum"] } -nym-credentials-interface = { path = "../../credentials-interface" } -nym-credential-verification = { path = "../../credential-verification" } -nym-http-api-common = { path = "../../http-api-common", features = [ +nym-credentials-interface = { workspace = true } +nym-credential-verification = { workspace = true } +nym-http-api-common = { workspace = true, features = [ "middleware", "utoipa", "output", ] } -nym-wireguard = { path = "../../wireguard" } -nym-wireguard-private-metadata-shared = { path = "../shared" } +nym-wireguard = { workspace = true } +nym-wireguard-private-metadata-shared = { workspace = true } [dev-dependencies] async-trait = { workspace = true } diff --git a/common/wireguard-private-metadata/shared/Cargo.toml b/common/wireguard-private-metadata/shared/Cargo.toml index 26b3cc6cd6..4c4ffacca8 100644 --- a/common/wireguard-private-metadata/shared/Cargo.toml +++ b/common/wireguard-private-metadata/shared/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "nym-wireguard-private-metadata-shared" -version = "1.0.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "Common crate for nym-wireguard server, client, and tests" [dependencies] axum = { workspace = true } @@ -16,7 +17,7 @@ serde = { workspace = true } thiserror = { workspace = true } utoipa = { workspace = true } -nym-credentials-interface = { path = "../../credentials-interface" } +nym-credentials-interface = { workspace = true } [features] testing = [] diff --git a/common/wireguard-private-metadata/tests/Cargo.toml b/common/wireguard-private-metadata/tests/Cargo.toml index 074d088f15..de8a2ab501 100644 --- a/common/wireguard-private-metadata/tests/Cargo.toml +++ b/common/wireguard-private-metadata/tests/Cargo.toml @@ -1,28 +1,29 @@ [package] name = "nym-wireguard-private-metadata-tests" -version = "1.0.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "Tests for nym-wireguard implementation" [dependencies] async-trait = { workspace = true } axum = { workspace = true, features = ["tokio", "macros"] } futures = { workspace = true } -nym-credential-verification = { path = "../../credential-verification" } -nym-credentials-interface = { path = "../../credentials-interface" } -nym-crypto = { path = "../../crypto", features = ["asymmetric"] } -nym-http-api-client = { path = "../../http-api-client" } -nym-http-api-common = { path = "../../http-api-common", features = [ +nym-credential-verification = { workspace = true } +nym-credentials-interface = { workspace = true } +nym-crypto = { workspace = true, features = ["asymmetric"] } +nym-http-api-client = { workspace = true } +nym-http-api-common = { workspace = true, features = [ "middleware", "utoipa", "output", ] } -nym-upgrade-mode-check = { path = "../../upgrade-mode-check" } -nym-wireguard = { path = "../../wireguard" } +nym-upgrade-mode-check = { workspace = true } +nym-wireguard = { workspace = true } time = { workspace = true, features = ["macros"] } tokio = { workspace = true, features = ["rt-multi-thread", "net", "io-util"] } tower = { workspace = true } @@ -36,9 +37,8 @@ tower-http = { workspace = true, features = [ ] } utoipa = { workspace = true, features = ["axum_extras", "time"] } -nym-wireguard-private-metadata-client = { path = "../client" } -nym-wireguard-private-metadata-shared = { path = "../shared", features = [ +nym-wireguard-private-metadata-client = { workspace = true } +nym-wireguard-private-metadata-shared = { workspace = true, features = [ "testing", ] } -nym-wireguard-private-metadata-server = { path = "../server" } - +nym-wireguard-private-metadata-server = { workspace = true } diff --git a/common/wireguard-private-metadata/tests/src/v2/peer_controller.rs b/common/wireguard-private-metadata/tests/src/v2/peer_controller.rs index 68fd5cdeed..2b29de63c6 100644 --- a/common/wireguard-private-metadata/tests/src/v2/peer_controller.rs +++ b/common/wireguard-private-metadata/tests/src/v2/peer_controller.rs @@ -26,7 +26,7 @@ impl From<&PeerControlRequest> for PeerControlRequestTypeV2 { fn from(req: &PeerControlRequest) -> Self { match req { PeerControlRequest::AddPeer { .. } => PeerControlRequestTypeV2::AddPeer, - PeerControlRequest::RegisterPeer { .. } => PeerControlRequestTypeV2::AddPeer, + PeerControlRequest::PreAllocateIpPair { .. } => PeerControlRequestTypeV2::AddPeer, PeerControlRequest::RemovePeer { .. } => PeerControlRequestTypeV2::RemovePeer, PeerControlRequest::QueryPeer { .. } => PeerControlRequestTypeV2::QueryPeer, PeerControlRequest::GetClientBandwidthByKey { .. } => { @@ -41,6 +41,8 @@ impl From<&PeerControlRequest> for PeerControlRequestTypeV2 { PeerControlRequest::GetVerifierByIp { ip, .. } => { PeerControlRequestTypeV2::GetVerifierByIp { ip: *ip } } + PeerControlRequest::CheckActivePeer { .. } => unreachable!(), + PeerControlRequest::ReleaseIpPair { .. } => unreachable!(), } } } @@ -113,7 +115,7 @@ impl MockPeerControllerV2 { ) .unwrap(); } - PeerControlRequest::RegisterPeer { response_tx, .. } => { + PeerControlRequest::PreAllocateIpPair { response_tx, .. } => { response_tx .send( *response @@ -176,6 +178,24 @@ impl MockPeerControllerV2 { ) .ok(); } + PeerControlRequest::ReleaseIpPair { response_tx, .. } => { + response_tx + .send( + *response + .downcast() + .expect("registered response has mismatched type"), + ) + .ok(); + } + PeerControlRequest::CheckActivePeer { response_tx, .. } => { + response_tx + .send( + *response + .downcast() + .expect("registered response has mismatched type"), + ) + .ok(); + } } } diff --git a/common/wireguard-types/Cargo.toml b/common/wireguard-types/Cargo.toml index 9a8a783db0..35af9c2341 100644 --- a/common/wireguard-types/Cargo.toml +++ b/common/wireguard-types/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "nym-wireguard-types" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "Wireguard public key and config definitions" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -16,8 +17,8 @@ serde = { workspace = true, features = ["derive"] } thiserror = { workspace = true } x25519-dalek = { workspace = true, features = ["static_secrets"] } -nym-crypto = { path = "../crypto", features = ["asymmetric"] } +nym-crypto = { workspace = true, features = ["asymmetric"] } [dev-dependencies] rand = { workspace = true } -nym-crypto = { path = "../crypto", features = ["rand"] } +nym-crypto = { workspace = true, features = ["rand"] } diff --git a/common/wireguard/Cargo.toml b/common/wireguard/Cargo.toml index 8e1d63fd89..f6b3c0e6fb 100644 --- a/common/wireguard/Cargo.toml +++ b/common/wireguard/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "nym-wireguard" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "Interface and peer handling functionality for Wireguard protocol" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -22,20 +23,23 @@ tokio = { workspace = true, features = ["rt-multi-thread", "net", "io-util"] } tokio-stream = { workspace = true } tracing = { workspace = true } -nym-credentials-interface = { path = "../credentials-interface" } -nym-credential-verification = { path = "../credential-verification" } -nym-crypto = { path = "../crypto", features = ["asymmetric"] } -nym-gateway-storage = { path = "../gateway-storage" } -nym-gateway-requests = { path = "../gateway-requests" } -nym-ip-packet-requests = { path = "../ip-packet-requests" } -nym-metrics = { path = "../nym-metrics" } -nym-network-defaults = { path = "../network-defaults" } -nym-task = { path = "../task" } -nym-wireguard-types = { path = "../wireguard-types" } -nym-node-metrics = { path = "../../nym-node/nym-node-metrics" } +nym-credentials-interface = { workspace = true } +nym-credential-verification = { workspace = true } +nym-crypto = { workspace = true, features = ["asymmetric"] } +nym-gateway-storage = { workspace = true } +nym-gateway-requests = { workspace = true } +nym-authenticator-requests = { workspace = true } +nym-metrics = { workspace = true } +nym-network-defaults = { workspace = true } +nym-task = { workspace = true } +nym-wireguard-types = { workspace = true } +nym-node-metrics = { workspace = true } [dev-dependencies] -nym-gateway-storage = { path = "../gateway-storage", features = ["mock"] } +anyhow = { workspace = true } +nym-gateway-storage = { workspace = true, features = ["mock"] } +mock_instant = { workspace = true } +nym-test-utils = { workspace = true } [features] default = [] diff --git a/common/wireguard/src/error.rs b/common/wireguard/src/error.rs index 7f5437d630..68d465a534 100644 --- a/common/wireguard/src/error.rs +++ b/common/wireguard/src/error.rs @@ -1,6 +1,8 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::IpPoolError; + #[derive(Debug, thiserror::Error)] pub enum Error { #[error("{0}")] @@ -22,7 +24,7 @@ pub enum Error { SystemTime(#[from] std::time::SystemTimeError), #[error("IP pool error: {0}")] - IpPool(String), + IpPool(#[from] IpPoolError), } pub type Result = std::result::Result; diff --git a/common/wireguard/src/ip_pool.rs b/common/wireguard/src/ip_pool.rs deleted file mode 100644 index e1c2b0453f..0000000000 --- a/common/wireguard/src/ip_pool.rs +++ /dev/null @@ -1,202 +0,0 @@ -// Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use ipnetwork::IpNetwork; -use nym_ip_packet_requests::IpPair; -use rand::seq::IteratorRandom; -use std::collections::HashMap; -use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; -use std::sync::Arc; -use std::time::SystemTime; -use tokio::sync::RwLock; - -/// Represents the state of an IP allocation -#[derive(Debug, Clone, Copy)] -pub enum AllocationState { - /// IP is available for allocation - Free, - /// IP is allocated and in use, with timestamp of allocation - Allocated(SystemTime), -} - -/// Thread-safe IP address pool manager -/// -/// Manages allocation of IPv4/IPv6 address pairs from configured CIDR ranges. -/// Ensures collision-free allocation and supports stale cleanup. -#[derive(Clone)] -pub struct IpPool { - allocations: Arc>>, -} - -impl IpPool { - /// Create a new IP pool from IPv4 and IPv6 CIDR ranges - /// - /// # Arguments - /// * `ipv4_network` - Base IPv4 address for the pool - /// * `ipv4_prefix` - CIDR prefix length for IPv4 (e.g., 16 for /16) - /// * `ipv6_network` - Base IPv6 address for the pool - /// * `ipv6_prefix` - CIDR prefix length for IPv6 (e.g., 112 for /112) - /// - /// # Errors - /// Returns error if CIDR ranges are invalid - pub fn new( - ipv4_network: Ipv4Addr, - ipv4_prefix: u8, - ipv6_network: Ipv6Addr, - ipv6_prefix: u8, - ) -> Result { - let ipv4_net = IpNetwork::new(ipv4_network.into(), ipv4_prefix)?; - let ipv6_net = IpNetwork::new(ipv6_network.into(), ipv6_prefix)?; - - // Build initial pool with all IPs marked as free - let mut allocations = HashMap::new(); - - // Collect IPv4 and IPv6 addresses into vectors for pairing - let ipv4_addrs: Vec = ipv4_net - .iter() - .filter_map(|ip| { - if let IpAddr::V4(v4) = ip { - Some(v4) - } else { - None - } - }) - .collect(); - - let ipv6_addrs: Vec = ipv6_net - .iter() - .filter_map(|ip| { - if let IpAddr::V6(v6) = ip { - Some(v6) - } else { - None - } - }) - .collect(); - - // Create IpPairs by matching IPv4 and IPv6 addresses - // Use the minimum length to avoid index out of bounds - let pair_count = ipv4_addrs.len().min(ipv6_addrs.len()); - for i in 0..pair_count { - let pair = IpPair::new(ipv4_addrs[i], ipv6_addrs[i]); - allocations.insert(pair, AllocationState::Free); - } - - tracing::info!( - "Initialized IP pool with {} address pairs from {}/{} and {}/{}", - allocations.len(), - ipv4_network, - ipv4_prefix, - ipv6_network, - ipv6_prefix - ); - - Ok(IpPool { - allocations: Arc::new(RwLock::new(allocations)), - }) - } - - /// Allocate a free IP pair from the pool - /// - /// Randomly selects an available IP pair and marks it as allocated. - /// - /// # Errors - /// Returns `IpPoolError::NoFreeIp` if no IPs are available - pub async fn allocate(&self) -> Result { - let mut pool = self.allocations.write().await; - - // Find a free IP and allocate it - let free_ip = pool - .iter_mut() - .filter(|(_, state)| matches!(state, AllocationState::Free)) - .choose(&mut rand::thread_rng()) - .ok_or(IpPoolError::NoFreeIp)?; - - let ip_pair = *free_ip.0; - *free_ip.1 = AllocationState::Allocated(SystemTime::now()); - - tracing::debug!("Allocated IP pair: {}", ip_pair); - Ok(ip_pair) - } - - /// Release an IP pair back to the pool - /// - /// Marks the IP as free for future allocations. - pub async fn release(&self, ip_pair: IpPair) { - let mut pool = self.allocations.write().await; - if let Some(state) = pool.get_mut(&ip_pair) { - *state = AllocationState::Free; - tracing::debug!("Released IP pair: {}", ip_pair); - } - } - - /// Mark an IP pair as allocated (used during initialization from database) - /// - /// This is used when restoring state from the database on gateway startup. - pub async fn mark_used(&self, ip_pair: IpPair) { - let mut pool = self.allocations.write().await; - if let Some(state) = pool.get_mut(&ip_pair) { - *state = AllocationState::Allocated(SystemTime::now()); - tracing::debug!("Marked IP pair as used: {}", ip_pair); - } else { - tracing::warn!("Attempted to mark unknown IP pair as used: {}", ip_pair); - } - } - - /// Get the number of free IPs in the pool - pub async fn free_count(&self) -> usize { - let pool = self.allocations.read().await; - pool.iter() - .filter(|(_, state)| matches!(state, AllocationState::Free)) - .count() - } - - /// Get the number of allocated IPs in the pool - pub async fn allocated_count(&self) -> usize { - let pool = self.allocations.read().await; - pool.iter() - .filter(|(_, state)| matches!(state, AllocationState::Allocated(_))) - .count() - } - - /// Get the total pool size - pub async fn total_count(&self) -> usize { - let pool = self.allocations.read().await; - pool.len() - } - - /// Clean up stale allocations older than the specified duration - /// - /// Returns the number of IPs that were freed - pub async fn cleanup_stale(&self, max_age: std::time::Duration) -> usize { - let mut pool = self.allocations.write().await; - let now = SystemTime::now(); - let mut freed = 0; - - for (_ip, state) in pool.iter_mut() { - if let AllocationState::Allocated(allocated_at) = state - && let Ok(age) = now.duration_since(*allocated_at) - && age > max_age - { - *state = AllocationState::Free; - freed += 1; - } - } - - if freed > 0 { - tracing::info!("Cleaned up {} stale IP allocations", freed); - } - - freed - } -} - -/// Errors that can occur during IP pool operations -#[derive(Debug, thiserror::Error)] -pub enum IpPoolError { - #[error("No free IP addresses available in pool")] - NoFreeIp, - - #[error("Invalid IP network configuration: {0}")] - InvalidNetwork(#[from] ipnetwork::IpNetworkError), -} diff --git a/common/wireguard/src/ip_pool/compat.rs b/common/wireguard/src/ip_pool/compat.rs new file mode 100644 index 0000000000..95ae7da686 --- /dev/null +++ b/common/wireguard/src/ip_pool/compat.rs @@ -0,0 +1,60 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::ip_pool::IpPair; + +impl From for nym_authenticator_requests::v6::registration::IpPair { + fn from(ip_pair: IpPair) -> Self { + nym_authenticator_requests::v6::registration::IpPair { + ipv4: ip_pair.ipv4, + ipv6: ip_pair.ipv6, + } + } +} + +impl From for nym_authenticator_requests::v5::registration::IpPair { + fn from(ip_pair: IpPair) -> Self { + nym_authenticator_requests::v5::registration::IpPair { + ipv4: ip_pair.ipv4, + ipv6: ip_pair.ipv6, + } + } +} + +impl From for nym_authenticator_requests::v4::registration::IpPair { + fn from(ip_pair: IpPair) -> Self { + nym_authenticator_requests::v4::registration::IpPair { + ipv4: ip_pair.ipv4, + ipv6: ip_pair.ipv6, + } + } +} + +// + +impl From for IpPair { + fn from(ip_pair: nym_authenticator_requests::v6::registration::IpPair) -> Self { + IpPair { + ipv4: ip_pair.ipv4, + ipv6: ip_pair.ipv6, + } + } +} + +impl From for IpPair { + fn from(ip_pair: nym_authenticator_requests::v5::registration::IpPair) -> Self { + IpPair { + ipv4: ip_pair.ipv4, + ipv6: ip_pair.ipv6, + } + } +} + +impl From for IpPair { + fn from(ip_pair: nym_authenticator_requests::v4::registration::IpPair) -> Self { + IpPair { + ipv4: ip_pair.ipv4, + ipv6: ip_pair.ipv6, + } + } +} diff --git a/common/wireguard/src/ip_pool/mod.rs b/common/wireguard/src/ip_pool/mod.rs new file mode 100644 index 0000000000..7044c8a806 --- /dev/null +++ b/common/wireguard/src/ip_pool/mod.rs @@ -0,0 +1,529 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use defguard_wireguard_rs::host::Peer; +use ipnetwork::IpNetwork; +use rand::seq::IteratorRandom; +use std::collections::HashMap; +use std::fmt::{Display, Formatter}; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; +use std::time::Duration; +use tracing::{trace, warn}; + +mod compat; + +#[cfg(test)] +use mock_instant::thread_local::Instant; +#[cfg(not(test))] +use std::time::Instant; + +// helper to convert peer's allocation into an `IpPair` +pub fn allocated_ip_pair(peer: &Peer) -> Option { + for allowed_ip in &peer.allowed_ips { + // Extract IPv4 and IPv6 from peer's allowed_ips + if let IpAddr::V4(ipv4) = allowed_ip.address { + // Find corresponding IPv6 + if let Some(ipv6_mask) = peer + .allowed_ips + .iter() + .find(|ip| matches!(ip.address, IpAddr::V6(_))) + && let IpAddr::V6(ipv6) = ipv6_mask.address + { + return Some(IpPair::new(ipv4, ipv6)); + } + } + } + None +} + +#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Hash)] +pub struct IpPair { + pub ipv4: Ipv4Addr, + pub ipv6: Ipv6Addr, +} + +impl IpPair { + pub fn new(ipv4: Ipv4Addr, ipv6: Ipv6Addr) -> Self { + IpPair { ipv4, ipv6 } + } +} + +impl Display for IpPair { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "IPv4: {}, IPv6: {}", self.ipv4, self.ipv6) + } +} + +/// Represents the state of an IP allocation +#[derive(Debug, Clone, Copy)] +pub enum AllocationState { + /// IP is available for allocation + Free, + + /// The IP has been pre-allocated for a peer, but the corresponding registration has not yet been finalised + PreAllocated { allocated_at: Instant }, + + /// IP is allocated and in use, with timestamp + Allocated { allocated_at: Instant }, +} + +impl AllocationState { + pub fn is_free(&self) -> bool { + matches!(self, AllocationState::Free) + } + + pub fn new_pre_allocated() -> Self { + AllocationState::PreAllocated { + allocated_at: Instant::now(), + } + } + + pub fn new_allocated() -> Self { + AllocationState::Allocated { + allocated_at: Instant::now(), + } + } +} + +/// Thread-safe IP address pool manager +/// +/// Manages allocation of IPv4/IPv6 address pairs from configured CIDR ranges. +/// Ensures collision-free allocation and supports stale cleanup. +pub struct IpPool { + allocations: HashMap, +} + +impl IpPool { + /// Create a new IP pool from IPv4 and IPv6 CIDR ranges + /// + /// # Arguments + /// * `ipv4_network` - Base IPv4 address for the pool + /// * `ipv4_prefix` - CIDR prefix length for IPv4 (e.g., 16 for /16) + /// * `ipv6_network` - Base IPv6 address for the pool + /// * `ipv6_prefix` - CIDR prefix length for IPv6 (e.g., 112 for /112) + /// + /// # Errors + /// Returns error if CIDR ranges are invalid + pub fn new( + ipv4_network: Ipv4Addr, + ipv4_prefix: u8, + ipv6_network: Ipv6Addr, + ipv6_prefix: u8, + ) -> Result { + let ipv4_net = IpNetwork::new(ipv4_network.into(), ipv4_prefix)?; + let ipv6_net = IpNetwork::new(ipv6_network.into(), ipv6_prefix)?; + + // Build initial pool with all IPs marked as free + let mut allocations = HashMap::new(); + + // Collect IPv4 and IPv6 addresses into vectors for pairing + let ipv4_addrs: Vec = ipv4_net + .iter() + .filter_map(|ip| { + if let IpAddr::V4(v4) = ip { + if v4 != ipv4_network { Some(v4) } else { None } + } else { + None + } + }) + .collect(); + + let ipv6_addrs: Vec = ipv6_net + .iter() + .filter_map(|ip| { + if let IpAddr::V6(v6) = ip { + if v6 != ipv6_network { Some(v6) } else { None } + } else { + None + } + }) + .collect(); + + // Create IpPairs by matching IPv4 and IPv6 addresses + // Use the minimum length to avoid index out of bounds + let pair_count = ipv4_addrs.len().min(ipv6_addrs.len()); + for i in 0..pair_count { + let pair = IpPair::new(ipv4_addrs[i], ipv6_addrs[i]); + allocations.insert(pair, AllocationState::Free); + } + + tracing::info!( + "Initialized IP pool with {} address pairs from {ipv4_network}/{ipv4_prefix} and {ipv6_network}/{ipv6_prefix}", + allocations.len(), + ); + + Ok(IpPool { allocations }) + } + + /// Preallocate a free IP pair from the pool + /// + /// # Errors + /// Returns `IpPoolError::NoFreeIp` if no IPs are available + pub fn pre_allocate(&mut self) -> Result { + // Find a free IP and allocate it + let assignment_start = Instant::now(); + let free_ip = self + .allocations + .iter_mut() + .filter(|(_, state)| matches!(state, AllocationState::Free)) + .choose(&mut rand::thread_rng()) + .ok_or(IpPoolError::NoFreeIp)?; + let taken = assignment_start.elapsed(); + trace!("assigning free ip pair took {taken:?}"); + if taken > Duration::from_millis(500) { + warn!("assigning free ip pair took {taken:?}"); + } + + let ip_pair = *free_ip.0; + *free_ip.1 = AllocationState::new_pre_allocated(); + + tracing::debug!("Allocated IP pair: {ip_pair}"); + Ok(ip_pair) + } + + pub fn confirm_allocation(&mut self, ip_pair: IpPair) -> Result<(), IpPoolError> { + let Some(allocation) = self.allocations.get_mut(&ip_pair) else { + return Err(IpPoolError::UnknownIpPair { ip_pair }); + }; + match allocation { + AllocationState::Free => { + // seems the IpPair has been released before the confirmation, but it has not yet been re-allocated + warn!( + "{ip_pair} seems to have already been released, but has not been allocated to a new peer yet" + ); + *allocation = AllocationState::Allocated { + allocated_at: Instant::now(), + }; + Ok(()) + } + AllocationState::PreAllocated { allocated_at } => { + *allocation = AllocationState::Allocated { + allocated_at: *allocated_at, + }; + Ok(()) + } + AllocationState::Allocated { .. } => Err(IpPoolError::AlreadyUsed { ip_pair }), + } + } + + /// Release an IP pair back to the pool + /// + /// Marks the IP as free for future allocations. + pub fn release(&mut self, ip_pair: IpPair) { + if let Some(state) = self.allocations.get_mut(&ip_pair) { + *state = AllocationState::Free; + tracing::debug!("Released IP pair: {ip_pair}"); + } + } + + /// Mark an IP pair as allocated (used during initialization from database) + /// + /// This is used when restoring state from the database on gateway startup. + pub fn mark_used(&mut self, ip_pair: IpPair) -> Result<(), IpPoolError> { + let Some(state) = self.allocations.get_mut(&ip_pair) else { + return Err(IpPoolError::UnknownIpPair { ip_pair }); + }; + + if !state.is_free() { + return Err(IpPoolError::AlreadyUsed { ip_pair }); + } + tracing::debug!("Marked IP pair as used: {ip_pair}"); + *state = AllocationState::new_allocated(); + Ok(()) + } + + /// Get the number of free IPs in the pool + pub fn free_count(&self) -> usize { + self.allocations + .iter() + .filter(|(_, state)| matches!(state, AllocationState::Free)) + .count() + } + + /// Get the number of allocated IPs in the pool + pub fn pre_allocated_count(&self) -> usize { + self.allocations + .iter() + .filter(|(_, state)| matches!(state, AllocationState::PreAllocated { .. })) + .count() + } + + /// Get the number of allocated IPs in the pool + pub fn allocated_count(&self) -> usize { + self.allocations + .iter() + .filter(|(_, state)| matches!(state, AllocationState::Allocated { .. })) + .count() + } + + /// Get the total pool size + pub fn total_count(&self) -> usize { + self.allocations.len() + } + + /// Clean up stale allocations older than the specified duration + /// + /// Returns the number of IPs that were freed + pub fn cleanup_stale(&mut self, max_age: Duration) -> usize { + let now = Instant::now(); + let mut freed = 0; + + for state in self.allocations.values_mut() { + if let AllocationState::PreAllocated { allocated_at, .. } = state { + let age = now.duration_since(*allocated_at); + if age > max_age { + *state = AllocationState::Free; + freed += 1; + } + } + } + + if freed > 0 { + tracing::info!("Cleaned up {freed} stale IP allocations"); + } + + freed + } +} + +/// Errors that can occur during IP pool operations +#[derive(Debug, thiserror::Error, PartialEq, Eq)] +pub enum IpPoolError { + #[error("No free IP addresses available in pool")] + NoFreeIp, + + #[error("Attempted to mark an IpPair that is already in used: {ip_pair}")] + AlreadyUsed { ip_pair: IpPair }, + + #[error("Attempted to mark an unknown ip pair: {ip_pair}")] + UnknownIpPair { ip_pair: IpPair }, + + #[error("Invalid IP network configuration: {0}")] + InvalidNetwork(#[from] ipnetwork::IpNetworkError), +} + +#[cfg(test)] +mod tests { + use super::*; + use anyhow::bail; + use mock_instant::thread_local::MockClock; + + // 3 addresses in each pool + fn small_ip_pool() -> IpPool { + IpPool::new( + Ipv4Addr::new(10, 0, 0, 0), + 30, + Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 0), + 126, + ) + .unwrap() + } + + #[test] + fn ip_pool_initial_allocation() -> anyhow::Result<()> { + let base_ipv4_network = Ipv4Addr::new(10, 0, 0, 0); + let base_ipv4_prefix = 24; // 255 addresses + let base_ipv6_network = Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 0); + let base_ipv6_prefix = 112; // 65535 addresses + + // ipv4 pool size < ipv6 pool size + let base_ip_pool = IpPool::new( + base_ipv4_network, + base_ipv4_prefix, + base_ipv6_network, + base_ipv6_prefix, + )?; + let inner = base_ip_pool.allocations; + // minimum of ipv4 and ipv6 allocations + assert_eq!(inner.len(), 255); + + // no ipv4 addresses + let base_ip_pool = IpPool::new(base_ipv4_network, 32, base_ipv6_network, base_ipv6_prefix)?; + let inner = base_ip_pool.allocations; + assert_eq!(inner.len(), 0); + + // no ipv6 addresses + let base_ip_pool = + IpPool::new(base_ipv4_network, base_ipv4_prefix, base_ipv6_network, 128)?; + let inner = base_ip_pool.allocations; + assert_eq!(inner.len(), 0); + + // ipv4 pool size == ipv6 pool size + let base_ip_pool = IpPool::new(base_ipv4_network, 16, base_ipv6_network, base_ipv6_prefix)?; + let inner = base_ip_pool.allocations; + assert_eq!(inner.len(), 65535); + + // ipv4 pool size > ipv6 pool size + let base_ip_pool = IpPool::new(base_ipv4_network, 12, base_ipv6_network, base_ipv6_prefix)?; + let inner = base_ip_pool.allocations; + assert_eq!(inner.len(), 65535); + + Ok(()) + } + + fn ensure_different_allocation(left: IpPair, right: IpPair) -> anyhow::Result<()> { + if left.ipv4 == right.ipv4 || left.ipv6 == right.ipv6 { + bail!("ip allocation overlap") + } + + Ok(()) + } + + #[test] + fn ip_pool_allocation() -> anyhow::Result<()> { + let mut pool = small_ip_pool(); + assert_eq!(pool.allocations.len(), 3); + + let gateway_pair = IpPair { + ipv4: Ipv4Addr::new(10, 0, 0, 0), + ipv6: Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 0), + }; + + assert_eq!(pool.free_count(), 3); + assert_eq!(pool.pre_allocated_count(), 0); + + let allocation1 = pool.pre_allocate()?; + assert_eq!(pool.free_count(), 2); + assert_eq!(pool.pre_allocated_count(), 1); + + let allocation2 = pool.pre_allocate()?; + assert_eq!(pool.free_count(), 1); + assert_eq!(pool.pre_allocated_count(), 2); + + pool.confirm_allocation(allocation1)?; + assert_eq!(pool.free_count(), 1); + assert_eq!(pool.pre_allocated_count(), 1); + assert_eq!(pool.allocated_count(), 1); + + let allocation3 = pool.pre_allocate()?; + assert_eq!(pool.free_count(), 0); + assert_eq!(pool.pre_allocated_count(), 2); + assert_eq!(pool.allocated_count(), 1); + + // make sure each was unique and different from the gateway + ensure_different_allocation(allocation1, allocation2)?; + ensure_different_allocation(allocation1, allocation3)?; + ensure_different_allocation(allocation2, allocation3)?; + + ensure_different_allocation(allocation1, gateway_pair)?; + ensure_different_allocation(allocation2, gateway_pair)?; + ensure_different_allocation(allocation3, gateway_pair)?; + + // allocation 4 will fail as we have run out of addresses + assert_eq!(pool.pre_allocate().unwrap_err(), IpPoolError::NoFreeIp); + + // if pair gets released, it's eligible for allocation again + pool.release(allocation2); + assert_eq!(pool.free_count(), 1); + assert_eq!(pool.pre_allocated_count(), 1); + assert_eq!(pool.allocated_count(), 1); + + let reallocation = pool.pre_allocate()?; + assert_eq!(reallocation, allocation2); + + assert_eq!(pool.free_count(), 0); + assert_eq!(pool.pre_allocated_count(), 2); + assert_eq!(pool.allocated_count(), 1); + + Ok(()) + } + + #[test] + fn ip_pool_mark_used() -> anyhow::Result<()> { + let mut pool = small_ip_pool(); + + let pair1 = IpPair::new( + Ipv4Addr::new(10, 0, 0, 1), + Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 1), + ); + let pair2 = IpPair::new( + Ipv4Addr::new(10, 0, 0, 2), + Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 2), + ); + let pair3 = IpPair::new( + Ipv4Addr::new(10, 0, 0, 3), + Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 3), + ); + + let bad_pair1 = IpPair::new( + Ipv4Addr::new(10, 0, 0, 1), + Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 4), + ); + let bad_pair2 = IpPair::new( + Ipv4Addr::new(10, 0, 0, 4), + Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 1), + ); + + assert!(pool.mark_used(pair1,).is_ok()); + assert_eq!( + pool.mark_used(pair1).unwrap_err(), + IpPoolError::AlreadyUsed { ip_pair: pair1 } + ); + + assert!(pool.mark_used(pair2).is_ok()); + assert!(pool.mark_used(pair3).is_ok()); + + assert_eq!( + pool.mark_used(bad_pair1).unwrap_err(), + IpPoolError::UnknownIpPair { ip_pair: bad_pair1 } + ); + + assert_eq!( + pool.mark_used(bad_pair2,).unwrap_err(), + IpPoolError::UnknownIpPair { ip_pair: bad_pair2 } + ); + + Ok(()) + } + + #[test] + fn ip_pool_cleanup() -> anyhow::Result<()> { + MockClock::set_time(Duration::ZERO); + + let mut pool = small_ip_pool(); + + let age_threshold = Duration::from_secs(1); + + // nothing to cleanup + assert_eq!(pool.cleanup_stale(age_threshold), 0); + + // just allocated + let pair1 = pool.pre_allocate()?; + let pair2 = pool.pre_allocate()?; + assert_eq!(pool.cleanup_stale(age_threshold), 0); + + // advance time to go beyond the allocation threshold + MockClock::advance(Duration::from_millis(1001)); + assert_eq!(pool.cleanup_stale(age_threshold), 2); + + // ensure those pairs are now marked as free + assert!(pool.allocations.get(&pair1).unwrap().is_free()); + assert!(pool.allocations.get(&pair2).unwrap().is_free()); + + pool.pre_allocate()?; + MockClock::advance(Duration::from_millis(500)); + pool.pre_allocate()?; + + assert_eq!(pool.cleanup_stale(age_threshold), 0); + MockClock::advance(Duration::from_millis(501)); + assert_eq!(pool.cleanup_stale(age_threshold), 1); + + MockClock::advance(Duration::from_millis(500)); + assert_eq!(pool.cleanup_stale(age_threshold), 1); + + let mut new_pool = small_ip_pool(); + let pair1 = new_pool.pre_allocate()?; + let pair2 = new_pool.pre_allocate()?; + + // complete allocation for pair2 + new_pool.confirm_allocation(pair2)?; + MockClock::advance(Duration::from_millis(2000)); + + // only pair1 should have got cleaned up + assert_eq!(new_pool.cleanup_stale(age_threshold), 1); + assert!(new_pool.allocations.get(&pair1).unwrap().is_free()); + assert!(!new_pool.allocations.get(&pair2).unwrap().is_free()); + + Ok(()) + } +} diff --git a/common/wireguard/src/lib.rs b/common/wireguard/src/lib.rs index 978443cefd..e64ab3b9e2 100644 --- a/common/wireguard/src/lib.rs +++ b/common/wireguard/src/lib.rs @@ -2,21 +2,17 @@ // SPDX-License-Identifier: Apache-2.0 #![cfg_attr(not(target_os = "linux"), allow(dead_code))] -// #![warn(clippy::pedantic)] -// #![warn(clippy::expect_used)] -// #![warn(clippy::unwrap_used)] -use defguard_wireguard_rs::{WGApi, WireguardInterfaceApi, host::Peer, key::Key, net::IpAddrMask}; +use defguard_wireguard_rs::{ + WGApi, WireguardInterfaceApi, error::WireguardInterfaceError, host::Peer, key::Key, + net::IpAddrMask, +}; use nym_crypto::asymmetric::x25519::KeyPair; +use std::net::IpAddr; use std::sync::Arc; use tokio::sync::mpsc::{self, Receiver, Sender}; use tracing::error; -#[cfg(target_os = "linux")] -use nym_ip_packet_requests::IpPair; -#[cfg(target_os = "linux")] -use std::net::IpAddr; - #[cfg(target_os = "linux")] use nym_network_defaults::constants::WG_TUN_BASE_NAME; @@ -26,6 +22,7 @@ pub mod peer_controller; pub mod peer_handle; pub mod peer_storage_manager; +pub use defguard_wireguard_rs::host::Peer as DefguardPeer; pub use error::Error; pub use ip_pool::{IpPool, IpPoolError}; pub use nym_wireguard_types::Config as WireguardConfig; @@ -33,13 +30,104 @@ pub use peer_controller::{PeerControlRequest, PeerRegistrationData}; pub const CONTROL_CHANNEL_SIZE: usize = 256; +#[derive(Debug, thiserror::Error)] +pub enum WgApiWrapperError { + #[error("WireGuard kernel implementation is not available on this platform")] + KernelUnavailable, + + #[error("WireGuard userspace implementation is not available on this platform")] + UserspaceUnavailable, + + #[error("WireGuard interface error: {0}")] + Interface(#[from] WireguardInterfaceError), +} + pub struct WgApiWrapper { - inner: WGApi, + inner: Box, +} + +impl WgApiWrapper { + /// Create new instance of `WgApiWrapper` choosing internal implementation based on `use_userspace` flag and platform availability. + /// + /// Falls back to userspace implementation when kernel implementation is requested but not available. + pub fn new(ifname: &str, use_userspace: bool) -> Result { + if use_userspace { + Self::userspace(ifname) + } else { + Self::kernel(ifname).or_else(|err| { + if matches!(err, WgApiWrapperError::KernelUnavailable) { + Self::userspace(ifname) + } else { + Err(err) + } + }) + } + } + + /// Create userspace implementation + fn userspace(_ifname: &str) -> Result { + #[cfg(any( + target_os = "linux", + target_os = "macos", + target_os = "freebsd", + target_os = "netbsd" + ))] + { + let api = WGApi::::new(_ifname)?; + Ok(Self { + inner: Box::new(api), + }) + } + + #[cfg(not(any( + target_os = "linux", + target_os = "macos", + target_os = "freebsd", + target_os = "netbsd" + )))] + { + Err(WgApiWrapperError::UserspaceUnavailable) + } + } + + /// Create kernel implementation if available. + fn kernel(_ifname: &str) -> Result { + #[cfg(any( + target_os = "linux", + target_os = "windows", + target_os = "freebsd", + target_os = "netbsd" + ))] + { + let api = WGApi::::new(_ifname)?; + Ok(Self { + inner: Box::new(api), + }) + } + + #[cfg(not(any( + target_os = "linux", + target_os = "windows", + target_os = "freebsd", + target_os = "netbsd" + )))] + { + Err(WgApiWrapperError::KernelUnavailable) + } + } +} + +impl Drop for WgApiWrapper { + fn drop(&mut self) { + if let Err(e) = self.inner.remove_interface() { + error!("Could not remove the wireguard interface: {e:?}"); + } + } } impl WireguardInterfaceApi for WgApiWrapper { fn create_interface( - &self, + &mut self, ) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { self.inner.create_interface() } @@ -58,7 +146,6 @@ impl WireguardInterfaceApi for WgApiWrapper { self.inner.configure_peer_routing(peers) } - #[cfg(not(target_os = "windows"))] fn configure_interface( &self, config: &defguard_wireguard_rs::InterfaceConfiguration, @@ -66,21 +153,20 @@ impl WireguardInterfaceApi for WgApiWrapper { self.inner.configure_interface(config) } - #[cfg(target_os = "windows")] - fn configure_interface( - &self, - config: &defguard_wireguard_rs::InterfaceConfiguration, - dns: &[std::net::IpAddr], - ) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { - self.inner.configure_interface(config, dns) - } - + #[cfg(not(windows))] fn remove_interface( &self, ) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { self.inner.remove_interface() } + #[cfg(windows)] + fn remove_interface( + &mut self, + ) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { + self.inner.remove_interface() + } + fn configure_peer( &self, peer: &Peer, @@ -106,24 +192,10 @@ impl WireguardInterfaceApi for WgApiWrapper { fn configure_dns( &self, - dns: &[std::net::IpAddr], + dns: &[IpAddr], + search_domains: &[&str], ) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { - self.inner.configure_dns(dns) - } -} - -impl WgApiWrapper { - pub fn new(wg_api: WGApi) -> Self { - WgApiWrapper { inner: wg_api } - } -} - -impl Drop for WgApiWrapper { - fn drop(&mut self) { - if let Err(e) = defguard_wireguard_rs::WireguardInterfaceApi::remove_interface(&self.inner) - { - error!("Could not remove the wireguard interface: {e:?}"); - } + self.inner.configure_dns(dns, search_domains) } } @@ -181,7 +253,7 @@ pub async fn start_wireguard( use_userspace: bool, ) -> Result, Box> { use base64::{Engine, prelude::BASE64_STANDARD}; - use defguard_wireguard_rs::{InterfaceConfiguration, WireguardInterfaceApi}; + use defguard_wireguard_rs::InterfaceConfiguration; use ip_network::IpNetwork; use peer_controller::PeerController; use std::collections::HashMap; @@ -193,7 +265,7 @@ pub async fn start_wireguard( "Initializing WireGuard interface '{}' with use_userspace={}", ifname, use_userspace ); - let wg_api = defguard_wireguard_rs::WGApi::new(ifname.clone(), use_userspace)?; + let mut wg_api = WgApiWrapper::new(&ifname, use_userspace)?; let mut peer_bandwidth_managers = HashMap::with_capacity(peers.len()); for peer in peers.iter() { @@ -210,18 +282,42 @@ pub async fn start_wireguard( peer_bandwidth_managers.insert(peer.public_key.clone(), (bandwidth_manager, peer.clone())); } + // Initialize IP pool from configuration + info!("Initializing IP pool for WireGuard peer allocation"); + let mut ip_pool = IpPool::new( + wireguard_data.inner.config().private_ipv4, + wireguard_data.inner.config().private_network_prefix_v4, + wireguard_data.inner.config().private_ipv6, + wireguard_data.inner.config().private_network_prefix_v6, + )?; + + // Mark existing peer IPs as used in the pool + for peer in &peers { + if let Some(ip_pair) = crate::ip_pool::allocated_ip_pair(peer) { + ip_pool.mark_used(ip_pair)?; + } + } + wg_api.create_interface()?; let interface_config = InterfaceConfiguration { name: ifname.clone(), prvkey: BASE64_STANDARD.encode(wireguard_data.inner.keypair().private_key().to_bytes()), - address: wireguard_data.inner.config().private_ipv4.to_string(), - port: wireguard_data.inner.config().announced_tunnel_port as u32, - peers: peers.clone(), // Clone since we need to use peers later to mark IPs as used + addresses: vec![IpAddrMask::host(IpAddr::from( + wireguard_data.inner.config().private_ipv4, + ))], + port: wireguard_data.inner.config().announced_tunnel_port, + peers, mtu: None, }; info!( - "attempting to configure wireguard interface '{ifname}': address={}, port={}", - interface_config.address, interface_config.port + "attempting to configure wireguard interface '{ifname}': addresses=[{}], port={}", + interface_config + .addresses + .iter() + .map(|s| s.to_string()) + .collect::>() + .join(", "), + interface_config.port ); info!("Configuring WireGuard interface..."); @@ -263,35 +359,8 @@ pub async fn start_wireguard( wg_api.configure_peer_routing(&[catch_all_peer])?; let host = wg_api.read_interface_data()?; - let wg_api = std::sync::Arc::new(WgApiWrapper::new(wg_api)); - - // Initialize IP pool from configuration - info!("Initializing IP pool for WireGuard peer allocation"); - let ip_pool = IpPool::new( - wireguard_data.inner.config().private_ipv4, - wireguard_data.inner.config().private_network_prefix_v4, - wireguard_data.inner.config().private_ipv6, - wireguard_data.inner.config().private_network_prefix_v6, - )?; - - // Mark existing peer IPs as used in the pool - for peer in &peers { - for allowed_ip in &peer.allowed_ips { - // Extract IPv4 and IPv6 from peer's allowed_ips - if let IpAddr::V4(ipv4) = allowed_ip.ip { - // Find corresponding IPv6 - if let Some(ipv6_mask) = peer - .allowed_ips - .iter() - .find(|ip| matches!(ip.ip, IpAddr::V6(_))) - && let IpAddr::V6(ipv6) = ipv6_mask.ip - { - ip_pool.mark_used(IpPair::new(ipv4, ipv6)).await; - } - } - } - } + let wg_api = std::sync::Arc::new(wg_api); let mut controller = PeerController::new( ecash_manager, metrics, diff --git a/common/wireguard/src/peer_controller/mock.rs b/common/wireguard/src/peer_controller/mock.rs index 5398183127..37f9ba1f0a 100644 --- a/common/wireguard/src/peer_controller/mock.rs +++ b/common/wireguard/src/peer_controller/mock.rs @@ -19,6 +19,7 @@ use std::sync::Arc; use tokio::sync::RwLock; use tokio::sync::mpsc::Receiver; +use crate::ip_pool::IpPair; pub use defguard_wireguard_rs::key::Key; pub fn mock_peer_controller( @@ -70,9 +71,11 @@ impl From<&Key> for KeyWrapper { #[derive(Hash, PartialOrd, PartialEq, Clone, Debug, Eq)] pub enum PeerControlRequestType { AddPeer { public_key: KeyWrapper }, - RegisterPeer { public_key: KeyWrapper }, + AllocatePeerIpPair {}, + ReleaseIpPair { ip_pair: IpPair }, RemovePeer { key: KeyWrapper }, QueryPeer { key: KeyWrapper }, + CheckActivePeer { key: KeyWrapper }, GetClientBandwidthByKey { key: KeyWrapper }, GetClientBandwidthByIp { ip: IpAddr }, GetVerifierByKey { key: KeyWrapper }, @@ -83,13 +86,15 @@ impl PeerControlRequestType { pub fn peer_key(&self) -> Option { match self { PeerControlRequestType::AddPeer { public_key } => Some(public_key.clone()), - PeerControlRequestType::RegisterPeer { public_key } => Some(public_key.clone()), + PeerControlRequestType::AllocatePeerIpPair {} => None, + PeerControlRequestType::ReleaseIpPair { .. } => None, PeerControlRequestType::RemovePeer { key } => Some(key.clone()), PeerControlRequestType::QueryPeer { key } => Some(key.clone()), PeerControlRequestType::GetClientBandwidthByKey { key } => Some(key.clone()), PeerControlRequestType::GetClientBandwidthByIp { .. } => None, PeerControlRequestType::GetVerifierByKey { key } => Some(key.clone()), PeerControlRequestType::GetVerifierByIp { .. } => None, + PeerControlRequestType::CheckActivePeer { key } => Some(key.clone()), } } @@ -104,11 +109,12 @@ impl From<&PeerControlRequest> for PeerControlRequestType { PeerControlRequest::AddPeer { peer, .. } => PeerControlRequestType::AddPeer { public_key: (&peer.public_key).into(), }, - PeerControlRequest::RegisterPeer { - registration_data, .. - } => PeerControlRequestType::RegisterPeer { - public_key: (®istration_data.public_key).into(), - }, + PeerControlRequest::PreAllocateIpPair { .. } => { + PeerControlRequestType::AllocatePeerIpPair {} + } + PeerControlRequest::ReleaseIpPair { ip_pair, .. } => { + PeerControlRequestType::ReleaseIpPair { ip_pair: *ip_pair } + } PeerControlRequest::RemovePeer { key, .. } => { PeerControlRequestType::RemovePeer { key: key.into() } } @@ -127,6 +133,9 @@ impl From<&PeerControlRequest> for PeerControlRequestType { PeerControlRequest::GetVerifierByIp { ip, .. } => { PeerControlRequestType::GetVerifierByIp { ip: *ip } } + PeerControlRequest::CheckActivePeer { key, .. } => { + PeerControlRequestType::CheckActivePeer { key: key.into() } + } } } } @@ -166,9 +175,6 @@ pub struct MockPeerControllerState { #[derive(Clone, Default)] pub struct PeerState { - /// Has IpPair been allocated to the peer? - pub register_success: bool, - // in the future maybe we could extend it with `ClientBandwidth` information /// Has the client handle been spawned pub add_success: bool, @@ -265,14 +271,13 @@ impl MockPeerController { } response_tx.send_downcasted(response.content) } - PeerControlRequest::RegisterPeer { response_tx, .. } => { - let key = typ.peer_key_unchecked(); - let peer = peers_guard.peers.entry(key).or_default(); - if response.success { - peer.register_success = true; - } + PeerControlRequest::PreAllocateIpPair { response_tx, .. } => { response_tx.send_downcasted(response.content) } + PeerControlRequest::ReleaseIpPair { + response_tx, + ip_pair: _, + } => response_tx.send_downcasted(response.content), PeerControlRequest::RemovePeer { response_tx, .. } => { let key = typ.peer_key_unchecked(); if response.success { @@ -295,6 +300,9 @@ impl MockPeerController { PeerControlRequest::GetVerifierByIp { response_tx, .. } => { response_tx.send_downcasted(response.content) } + PeerControlRequest::CheckActivePeer { response_tx, .. } => { + response_tx.send_downcasted(response.content) + } } } diff --git a/common/wireguard/src/peer_controller/mod.rs b/common/wireguard/src/peer_controller/mod.rs index 50db3677a3..9aae772a8f 100644 --- a/common/wireguard/src/peer_controller/mod.rs +++ b/common/wireguard/src/peer_controller/mod.rs @@ -1,6 +1,7 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::ip_pool::allocated_ip_pair; use crate::{ IpPool, error::{Error, Result}, @@ -27,14 +28,14 @@ use nym_wireguard_types::{ }; use std::{collections::HashMap, sync::Arc}; use std::{ - net::{IpAddr, SocketAddr}, + net::IpAddr, time::{Duration, SystemTime}, }; use tokio::sync::{RwLock, mpsc}; use tokio_stream::{StreamExt, wrappers::IntervalStream}; use tracing::{debug, error, info, trace}; -pub use nym_ip_packet_requests::IpPair; +pub use crate::ip_pool::IpPair; #[cfg(feature = "mock")] pub mod mock; @@ -43,35 +44,30 @@ pub mod mock; #[derive(Debug, Clone)] pub struct PeerRegistrationData { pub public_key: Key, - pub preshared_key: Option, - pub endpoint: Option, - pub persistent_keepalive_interval: Option, + pub preshared_key: Key, + // pub endpoint: Option, + // pub persistent_keepalive_interval: Option, } impl PeerRegistrationData { - pub fn new(public_key: Key) -> Self { + pub fn new(public_key: Key, psk: Key) -> Self { Self { public_key, - preshared_key: None, - endpoint: None, - persistent_keepalive_interval: None, + preshared_key: psk, + // endpoint: None, + // persistent_keepalive_interval: None, } } - - pub fn with_preshared_key(mut self, key: Key) -> Self { - self.preshared_key = Some(key); - self - } - - pub fn with_endpoint(mut self, endpoint: SocketAddr) -> Self { - self.endpoint = Some(endpoint); - self - } - - pub fn with_keepalive(mut self, interval: u16) -> Self { - self.persistent_keepalive_interval = Some(interval); - self - } + // + // pub fn with_endpoint(mut self, endpoint: SocketAddr) -> Self { + // self.endpoint = Some(endpoint); + // self + // } + // + // pub fn with_keepalive(mut self, interval: u16) -> Self { + // self.persistent_keepalive_interval = Some(interval); + // self + // } } pub enum PeerControlRequest { @@ -80,10 +76,14 @@ pub enum PeerControlRequest { peer: Peer, response_tx: oneshot::Sender, }, - /// Register a new peer and allocate IPs from the pool - RegisterPeer { - registration_data: PeerRegistrationData, - response_tx: oneshot::Sender, + /// Attempt to allocate an IP pair from the pool + PreAllocateIpPair { + response_tx: oneshot::Sender, + }, + /// Attempt to return an IP pair back to the pool + ReleaseIpPair { + response_tx: oneshot::Sender, + ip_pair: IpPair, }, RemovePeer { key: Key, @@ -93,6 +93,10 @@ pub enum PeerControlRequest { key: Key, response_tx: oneshot::Sender, }, + CheckActivePeer { + key: Key, + response_tx: oneshot::Sender, + }, GetClientBandwidthByKey { key: Key, response_tx: oneshot::Sender, @@ -114,9 +118,11 @@ pub enum PeerControlRequest { } pub type AddPeerControlResponse = Result<()>; -pub type RegisterPeerControlResponse = Result; +pub type AllocatePeerControlResponse = Result; +pub type ReleaseIpPairControlResponse = Result<()>; pub type RemovePeerControlResponse = Result<()>; pub type QueryPeerControlResponse = Result>; +pub type CheckActivePeerResponse = Result; pub type GetClientBandwidthControlResponse = Result; pub type QueryVerifierControlResponse = Result>; @@ -211,6 +217,13 @@ impl PeerController { .remove_wireguard_peer(&key.to_string()) .await?; self.bw_storage_managers.remove(key); + + if let Ok(Some(peer)) = self.handle_query_peer_by_key(key).await + && let Some(ip_pair) = allocated_ip_pair(&peer) + { + self.ip_pool.release(ip_pair) + } + let ret = self.wg_api.remove_peer(key); if ret.is_err() { nym_metrics::inc!("wg_peer_removal_failed"); @@ -250,6 +263,13 @@ impl PeerController { async fn handle_add_request(&mut self, peer: &Peer) -> Result<()> { nym_metrics::inc!("wg_peer_addition_attempts"); + // confirm ip allocation so that it wouldn't be released for as long as the peer exists + let Some(ip_pair) = allocated_ip_pair(peer) else { + return Err(Error::Internal( + "could not determine ip pair allocated to the peer".to_string(), + )); + }; + // Try to configure WireGuard peer if let Err(e) = self.wg_api.configure_peer(peer) { nym_metrics::inc!("wg_peer_addition_failed"); @@ -281,6 +301,9 @@ impl PeerController { *self.host_information.write().await = host_information; } let public_key = peer.public_key.clone(); + + self.ip_pool.confirm_allocation(ip_pair)?; + tokio::spawn(async move { handle.run().await; debug!("Peer handle shut down for {public_key}"); @@ -294,25 +317,23 @@ impl PeerController { /// /// This only allocates IPs - the caller must handle database storage and /// then call AddPeer with a complete Peer struct. - async fn handle_register_request( - &mut self, - _registration_data: PeerRegistrationData, - ) -> Result { + fn handle_ip_allocation_request(&mut self) -> Result { nym_metrics::inc!("wg_ip_allocation_attempts"); // Allocate IP pair from pool - let ip_pair = self - .ip_pool - .allocate() - .await - .map_err(|e| Error::IpPool(e.to_string()))?; + let ip_pair = self.ip_pool.pre_allocate()?; nym_metrics::inc!("wg_ip_allocation_success"); - tracing::debug!("Allocated IP pair: {}", ip_pair); + tracing::debug!("Allocated IP pair: {ip_pair}"); Ok(ip_pair) } + /// Return IP pair back to the pool + fn handle_ip_release(&mut self, ip_pair: IpPair) { + self.ip_pool.release(ip_pair) + } + async fn ip_to_key(&self, ip: IpAddr) -> Result> { Ok(self .bw_storage_managers @@ -321,7 +342,7 @@ impl PeerController { bw_manager .allowed_ips() .iter() - .find(|ip_mask| ip_mask.ip == ip) + .find(|ip_mask| ip_mask.address == ip) .and(Some(key.clone())) })) } @@ -349,6 +370,12 @@ impl PeerController { .client_bandwidth()) } + fn check_active_peer(&self, key: Key) -> Result { + // peer is active as long as we still have an entry inside the bandwidth storage manager, + // as it is removed upon peer removal + Ok(self.bw_storage_managers.contains_key(&key)) + } + async fn handle_get_client_bandwidth_by_ip(&self, ip: IpAddr) -> Result { let Some(key) = self.ip_to_key(ip).await? else { return Err(Error::MissingClientKernelEntry(ip.to_string())); @@ -477,6 +504,63 @@ impl PeerController { ); } + async fn handle_peer_control_request(&mut self, msg: PeerControlRequest) { + match msg { + PeerControlRequest::AddPeer { peer, response_tx } => { + response_tx.send(self.handle_add_request(&peer).await).ok(); + } + PeerControlRequest::PreAllocateIpPair { response_tx } => { + response_tx.send(self.handle_ip_allocation_request()).ok(); + } + PeerControlRequest::ReleaseIpPair { + response_tx, + ip_pair, + } => { + self.handle_ip_release(ip_pair); + response_tx.send(Ok(())).ok(); + } + PeerControlRequest::RemovePeer { key, response_tx } => { + response_tx.send(self.remove_peer(&key).await).ok(); + } + PeerControlRequest::QueryPeer { key, response_tx } => { + response_tx + .send(self.handle_query_peer_by_key(&key).await) + .ok(); + } + PeerControlRequest::GetClientBandwidthByKey { key, response_tx } => { + response_tx + .send(self.handle_get_client_bandwidth_by_key(&key).await) + .ok(); + } + PeerControlRequest::GetClientBandwidthByIp { ip, response_tx } => { + response_tx + .send(self.handle_get_client_bandwidth_by_ip(ip).await) + .ok(); + } + PeerControlRequest::GetVerifierByKey { + key, + credential, + response_tx, + } => { + response_tx + .send(self.handle_query_verifier_by_key(&key, *credential).await) + .ok(); + } + PeerControlRequest::GetVerifierByIp { + ip, + credential, + response_tx, + } => { + response_tx + .send(self.handle_query_verifier_by_ip(ip, *credential).await) + .ok(); + } + PeerControlRequest::CheckActivePeer { key, response_tx } => { + response_tx.send(self.check_active_peer(key)).ok(); + } + } + } + pub async fn run(&mut self) { info!("started wireguard peer controller"); loop { @@ -492,7 +576,7 @@ impl PeerController { } _ = self.ip_cleanup_interval.next() => { // Periodically cleanup stale IP allocations - let freed = self.ip_pool.cleanup_stale(DEFAULT_IP_STALE_AGE).await; + let freed = self.ip_pool.cleanup_stale(DEFAULT_IP_STALE_AGE); if freed > 0 { nym_metrics::inc_by!("wg_stale_ips_cleaned", freed as u64); info!("Cleaned up {} stale IP allocations", freed); @@ -504,30 +588,7 @@ impl PeerController { } msg = self.request_rx.recv() => { match msg { - Some(PeerControlRequest::AddPeer { peer, response_tx }) => { - response_tx.send(self.handle_add_request(&peer).await).ok(); - } - Some(PeerControlRequest::RegisterPeer { registration_data, response_tx }) => { - response_tx.send(self.handle_register_request(registration_data).await).ok(); - } - Some(PeerControlRequest::RemovePeer { key, response_tx }) => { - response_tx.send(self.remove_peer(&key).await).ok(); - } - Some(PeerControlRequest::QueryPeer { key, response_tx }) => { - response_tx.send(self.handle_query_peer_by_key(&key).await).ok(); - } - Some(PeerControlRequest::GetClientBandwidthByKey { key, response_tx }) => { - response_tx.send(self.handle_get_client_bandwidth_by_key(&key).await).ok(); - } - Some(PeerControlRequest::GetClientBandwidthByIp { ip, response_tx }) => { - response_tx.send(self.handle_get_client_bandwidth_by_ip(ip).await).ok(); - } - Some(PeerControlRequest::GetVerifierByKey { key, credential, response_tx }) => { - response_tx.send(self.handle_query_verifier_by_key(&key, *credential).await).ok(); - } - Some(PeerControlRequest::GetVerifierByIp { ip, credential, response_tx }) => { - response_tx.send(self.handle_query_verifier_by_ip(ip, *credential).await).ok(); - } + Some(request) => self.handle_peer_control_request(request).await, None => { trace!("PeerController [main loop]: stopping since channel closed"); break; @@ -551,7 +612,7 @@ struct MockWgApi { #[allow(clippy::todo)] impl WireguardInterfaceApi for MockWgApi { fn create_interface( - &self, + &mut self, ) -> std::result::Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { todo!() } @@ -570,7 +631,6 @@ impl WireguardInterfaceApi for MockWgApi { todo!() } - #[cfg(not(target_os = "windows"))] fn configure_interface( &self, _config: &defguard_wireguard_rs::InterfaceConfiguration, @@ -578,21 +638,20 @@ impl WireguardInterfaceApi for MockWgApi { todo!() } - #[cfg(target_os = "windows")] - fn configure_interface( - &self, - _config: &defguard_wireguard_rs::InterfaceConfiguration, - _dns: &[std::net::IpAddr], - ) -> std::result::Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { - todo!() - } - + #[cfg(not(windows))] fn remove_interface( &self, ) -> std::result::Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { todo!() } + #[cfg(windows)] + fn remove_interface( + &mut self, + ) -> std::result::Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { + todo!() + } + fn configure_peer( &self, peer: &Peer, @@ -623,6 +682,7 @@ impl WireguardInterfaceApi for MockWgApi { fn configure_dns( &self, _dns: &[std::net::IpAddr], + _search_domains: &[&str], ) -> std::result::Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { todo!() } diff --git a/common/zulip-client/Cargo.toml b/common/zulip-client/Cargo.toml index acfc84a540..cc065e917c 100644 --- a/common/zulip-client/Cargo.toml +++ b/common/zulip-client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "zulip-client" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true @@ -9,6 +9,7 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +publish = false [dependencies] thiserror = { workspace = true } @@ -18,8 +19,8 @@ url = { workspace = true, features = ["serde"] } serde = { workspace = true, features = ["derive"] } zeroize = { workspace = true } -nym-bin-common = { path = "../bin-common" } -nym-http-api-client = { path = "../http-api-client" } +nym-bin-common = { workspace = true } +nym-http-api-client = { workspace = true } reqwest = { workspace = true } tracing = { workspace = true } diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock index 36907864f0..92d73ca89e 100644 --- a/contracts/Cargo.lock +++ b/contracts/Cargo.lock @@ -2,6 +2,17 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + [[package]] name = "ahash" version = "0.8.11" @@ -76,7 +87,7 @@ dependencies = [ "ark-serialize", "ark-std", "derivative", - "digest", + "digest 0.10.7", "itertools 0.10.5", "num-bigint", "num-traits", @@ -130,7 +141,7 @@ checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" dependencies = [ "ark-serialize-derive", "ark-std", - "digest", + "digest 0.10.7", "num-bigint", ] @@ -156,6 +167,12 @@ dependencies = [ "rayon", ] +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + [[package]] name = "autocfg" version = "1.2.0" @@ -198,13 +215,25 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" +[[package]] +name = "blake2" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94cb07b0da6a73955f8fb85d24c466778e70cda767a568229b104f0264089330" +dependencies = [ + "byte-tools", + "crypto-mac", + "digest 0.8.1", + "opaque-debug", +] + [[package]] name = "block-buffer" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -222,6 +251,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + [[package]] name = "byteorder" version = "1.5.0" @@ -286,6 +321,26 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chacha" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddf3c081b5fba1e5615640aae998e0fbd10c24cbd897ee39ed754a77601a4862" +dependencies = [ + "byteorder", + "keystream", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -325,7 +380,7 @@ dependencies = [ "ark-serialize", "cosmwasm-core", "curve25519-dalek", - "digest", + "digest 0.10.7", "ecdsa", "ed25519-zebra", "k256", @@ -436,9 +491,9 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ - "generic-array", + "generic-array 0.14.7", "rand_core", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -448,10 +503,29 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array", + "generic-array 0.14.7", "typenum", ] +[[package]] +name = "crypto-mac" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" +dependencies = [ + "generic-array 0.12.4", + "subtle 1.0.0", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + [[package]] name = "curve25519-dalek" version = "4.1.3" @@ -461,10 +535,10 @@ dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", - "digest", + "digest 0.10.7", "fiat-crypto", "rustc_version", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -722,6 +796,15 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array 0.12.4", +] + [[package]] name = "digest" version = "0.10.7" @@ -731,7 +814,7 @@ dependencies = [ "block-buffer", "const-oid", "crypto-common", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -742,7 +825,7 @@ checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" [[package]] name = "easy-addr" -version = "0.1.0" +version = "1.20.1" dependencies = [ "cosmwasm-std", "quote", @@ -756,7 +839,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ "der", - "digest", + "digest 0.10.7", "elliptic-curve", "rfc6979", "signature", @@ -783,7 +866,7 @@ dependencies = [ "rand_core", "serde", "sha2", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -816,13 +899,13 @@ checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", "crypto-bigint", - "digest", + "digest 0.10.7", "ff", - "generic-array", + "generic-array 0.14.7", "group", "rand_core", "sec1", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -839,7 +922,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ "rand_core", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -848,6 +931,15 @@ version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c007b1ae3abe1cb6f85a16305acd418b7ca6343b953633fee2b76d8f108b830f" +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -878,7 +970,7 @@ checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", "rand_core", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -912,13 +1004,22 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + [[package]] name = "hmac" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -947,6 +1048,15 @@ dependencies = [ "hashbrown 0.15.2", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array 0.14.7", +] + [[package]] name = "itertools" version = "0.10.5" @@ -992,6 +1102,12 @@ dependencies = [ "sha2", ] +[[package]] +name = "keystream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33070833c9ee02266356de0c43f723152bd38bd96ddf52c82b3af10c9138b28" + [[package]] name = "konst" version = "0.3.16" @@ -1025,6 +1141,24 @@ version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "lioness" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae926706ba42c425c9457121178330d75e273df2e82e28b758faf3de3a9acb9" +dependencies = [ + "arrayref", + "blake2", + "chacha", + "keystream", +] + [[package]] name = "memchr" version = "2.7.2" @@ -1078,6 +1212,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -1113,7 +1248,7 @@ dependencies = [ [[package]] name = "nym-coconut-dkg-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1126,7 +1261,7 @@ dependencies = [ [[package]] name = "nym-contracts-common" -version = "0.5.0" +version = "1.20.1" dependencies = [ "bs58", "cosmwasm-schema", @@ -1140,7 +1275,7 @@ dependencies = [ [[package]] name = "nym-contracts-common-testing" -version = "0.1.0" +version = "1.20.1" dependencies = [ "anyhow", "cosmwasm-std", @@ -1154,7 +1289,7 @@ dependencies = [ [[package]] name = "nym-crypto" -version = "0.4.0" +version = "1.20.1" dependencies = [ "base64 0.22.1", "bs58", @@ -1200,7 +1335,7 @@ dependencies = [ [[package]] name = "nym-ecash-contract-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bs58", "cosmwasm-schema", @@ -1214,7 +1349,7 @@ dependencies = [ [[package]] name = "nym-group-contract-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "cosmwasm-schema", "cw-controllers", @@ -1249,7 +1384,7 @@ dependencies = [ [[package]] name = "nym-mixnet-contract-common" -version = "0.6.0" +version = "1.20.1" dependencies = [ "bs58", "cosmwasm-schema", @@ -1269,7 +1404,7 @@ dependencies = [ [[package]] name = "nym-multisig-contract-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1284,7 +1419,7 @@ dependencies = [ [[package]] name = "nym-network-defaults" -version = "0.1.0" +version = "1.20.1" dependencies = [ "cargo_metadata 0.19.2", "regex", @@ -1292,7 +1427,7 @@ dependencies = [ [[package]] name = "nym-pemstore" -version = "0.3.0" +version = "1.20.1" dependencies = [ "pem", "tracing", @@ -1320,7 +1455,7 @@ dependencies = [ [[package]] name = "nym-performance-contract-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1348,7 +1483,7 @@ dependencies = [ [[package]] name = "nym-pool-contract-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1360,8 +1495,9 @@ dependencies = [ [[package]] name = "nym-sphinx-types" -version = "0.2.0" +version = "1.20.1" dependencies = [ + "sphinx-packet", "thiserror 2.0.12", ] @@ -1388,7 +1524,7 @@ dependencies = [ [[package]] name = "nym-vesting-contract-common" -version = "0.7.0" +version = "1.20.1" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1405,6 +1541,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + [[package]] name = "p256" version = "0.13.2" @@ -1575,6 +1717,16 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rand_distr" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" +dependencies = [ + "num-traits", + "rand", +] + [[package]] name = "rayon" version = "1.10.0" @@ -1631,7 +1783,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ "hmac", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -1709,8 +1861,8 @@ checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ "base16ct", "der", - "generic-array", - "subtle", + "generic-array 0.14.7", + "subtle 2.4.1", "zeroize", ] @@ -1803,7 +1955,7 @@ checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.7", ] [[package]] @@ -1812,10 +1964,36 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "digest", + "digest 0.10.7", "rand_core", ] +[[package]] +name = "sphinx-packet" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c26f0c20d909fdda1c5d0ece3973127ca421984d55b000215df365e93722fc6e" +dependencies = [ + "aes", + "arrayref", + "blake2", + "bs58", + "byteorder", + "chacha", + "ctr", + "curve25519-dalek", + "digest 0.10.7", + "hkdf", + "hmac", + "lioness", + "rand", + "rand_distr", + "sha2", + "subtle 2.4.1", + "x25519-dalek", + "zeroize", +] + [[package]] name = "spki" version = "0.7.3" @@ -1832,6 +2010,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "subtle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" + [[package]] name = "subtle" version = "2.4.1" diff --git a/contracts/Cargo.toml b/contracts/Cargo.toml index ec3239772c..edcaf4b335 100644 --- a/contracts/Cargo.toml +++ b/contracts/Cargo.toml @@ -57,6 +57,41 @@ schemars = "0.8.16" thiserror = "2.0.11" +# Common crates from parent workspace (paths relative to contracts/) +# +# TODO: Once these crates are published to crates.io, switch from path dependencies +# to crates.io versions. +# +# TODO add a [patch.crates-io] section at the bottom for local development if you need to use a modded version of common import instead e.g.: +# +# [patch.crates-io] +# nym-contracts-common = { path = "../common/cosmwasm-smart-contracts/contracts-common" } +# +easy-addr = { version = "1.20.1", path = "../common/cosmwasm-smart-contracts/easy_addr" } +nym-coconut-dkg-common = { version = "1.20.1", path = "../common/cosmwasm-smart-contracts/coconut-dkg" } +nym-contracts-common = { version = "1.20.1", path = "../common/cosmwasm-smart-contracts/contracts-common" } +nym-contracts-common-testing = { version = "1.20.1", path = "../common/cosmwasm-smart-contracts/contracts-common-testing" } +nym-crypto = { version = "1.20.1", path = "../common/crypto", default-features = false } +nym-ecash-contract-common = { version = "1.20.1", path = "../common/cosmwasm-smart-contracts/ecash-contract" } +nym-group-contract-common = { version = "1.20.1", path = "../common/cosmwasm-smart-contracts/group-contract" } +nym-mixnet-contract-common = { version = "1.20.1", path = "../common/cosmwasm-smart-contracts/mixnet-contract" } +nym-multisig-contract-common = { version = "1.20.1", path = "../common/cosmwasm-smart-contracts/multisig-contract" } +nym-network-defaults = { version = "1.20.1", path = "../common/network-defaults", default-features = false } +nym-performance-contract-common = { version = "1.20.1", path = "../common/cosmwasm-smart-contracts/nym-performance-contract" } +nym-pool-contract-common = { version = "1.20.1", path = "../common/cosmwasm-smart-contracts/nym-pool-contract" } +nym-vesting-contract-common = { version = "1.20.1", path = "../common/cosmwasm-smart-contracts/vesting-contract" } + +# Aliases for crates that some contracts import under different names +contracts-common = { version = "1.20.1", path = "../common/cosmwasm-smart-contracts/contracts-common", package = "nym-contracts-common" } +mixnet-contract-common = { version = "1.20.1", path = "../common/cosmwasm-smart-contracts/mixnet-contract", package = "nym-mixnet-contract-common" } +vesting-contract-common = { version = "1.20.1", path = "../common/cosmwasm-smart-contracts/vesting-contract", package = "nym-vesting-contract-common" } + +# Internal contract workspace members (for cross-contract testing) +cw3-flex-multisig = { version = "2.0.0", path = "multisig/cw3-flex-multisig" } +cw4-group = { version = "2.0.0", path = "multisig/cw4-group" } +nym-mixnet-contract = { version = "1.5.1", path = "mixnet" } +nym-vesting-contract = { version = "1.4.1", path = "vesting" } + [workspace.lints.clippy] unwrap_used = "deny" expect_used = "deny" diff --git a/contracts/coconut-dkg/Cargo.toml b/contracts/coconut-dkg/Cargo.toml index 977448b9e7..66cb2acbe0 100644 --- a/contracts/coconut-dkg/Cargo.toml +++ b/contracts/coconut-dkg/Cargo.toml @@ -16,10 +16,10 @@ required-features = ["schema-gen"] crate-type = ["cdylib", "rlib"] [dependencies] -nym-coconut-dkg-common = { path = "../../common/cosmwasm-smart-contracts/coconut-dkg" } -nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common" } -nym-contracts-common-testing = { path = "../../common/cosmwasm-smart-contracts/contracts-common-testing", optional = true } -nym-group-contract-common = { path = "../../common/cosmwasm-smart-contracts/group-contract", optional = true } +nym-coconut-dkg-common = { workspace = true } +nym-contracts-common = { workspace = true } +nym-contracts-common-testing = { workspace = true, optional = true } +nym-group-contract-common = { workspace = true, optional = true } cosmwasm-schema = { workspace = true, optional = true } cosmwasm-std = { workspace = true } @@ -30,15 +30,15 @@ cw2 = { workspace = true } cw4 = { workspace = true } thiserror = { workspace = true } -cw3-flex-multisig = { path = "../multisig/cw3-flex-multisig", features = ["testable-cw3-contract"], optional = true } -cw4-group = { path = "../multisig/cw4-group", features = ["testable-cw4-contract"], optional = true } +cw3-flex-multisig = { workspace = true, features = ["testable-cw3-contract"], optional = true } +cw4-group = { workspace = true, features = ["testable-cw4-contract"], optional = true } [dev-dependencies] anyhow = { workspace = true } -easy-addr = { path = "../../common/cosmwasm-smart-contracts/easy_addr" } -nym-group-contract-common = { path = "../../common/cosmwasm-smart-contracts/group-contract" } +easy-addr = { workspace = true } +nym-group-contract-common = { workspace = true } cw-multi-test = { workspace = true } -cw4-group = { path = "../multisig/cw4-group" } +cw4-group = { workspace = true } [features] schema-gen = ["nym-coconut-dkg-common/schema", "cosmwasm-schema"] diff --git a/contracts/ecash/Cargo.toml b/contracts/ecash/Cargo.toml index a80b0fb241..4badeb4460 100644 --- a/contracts/ecash/Cargo.toml +++ b/contracts/ecash/Cargo.toml @@ -30,15 +30,15 @@ cw4 = { workspace = true } cw-utils = { workspace = true } semver = { workspace = true, default-features = false } -nym-ecash-contract-common = { path = "../../common/cosmwasm-smart-contracts/ecash-contract" } -nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common" } -nym-multisig-contract-common = { path = "../../common/cosmwasm-smart-contracts/multisig-contract" } -nym-network-defaults = { path = "../../common/network-defaults", default-features = false } +nym-ecash-contract-common = { workspace = true } +nym-contracts-common = { workspace = true } +nym-multisig-contract-common = { workspace = true } +nym-network-defaults = { workspace = true, default-features = false } [dev-dependencies] anyhow = { workspace = true } sylvia = { workspace = true, features = ["mt"] } -nym-crypto = { path = "../../common/crypto", features = ["rand", "asymmetric"] } +nym-crypto = { workspace = true, features = ["rand", "asymmetric"] } rand_chacha = "0.3" cw-multi-test = { workspace = true } diff --git a/contracts/mixnet-vesting-integration-tests/Cargo.toml b/contracts/mixnet-vesting-integration-tests/Cargo.toml index c3fc07974f..a1f6aa3104 100644 --- a/contracts/mixnet-vesting-integration-tests/Cargo.toml +++ b/contracts/mixnet-vesting-integration-tests/Cargo.toml @@ -16,15 +16,15 @@ cosmwasm-std = { workspace = true } cw-multi-test = { workspace = true } # contracts dependencies -nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract" } -nym-vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract" } -nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common" } +nym-mixnet-contract-common = { workspace = true } +nym-vesting-contract-common = { workspace = true } +nym-contracts-common = { workspace = true } -nym-mixnet-contract = { path = "../mixnet" } -nym-vesting-contract = { path = "../vesting" } +nym-mixnet-contract = { workspace = true } +nym-vesting-contract = { workspace = true } # other local dependencies -nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] } +nym-crypto = { workspace = true, features = ["asymmetric", "rand"] } # external dependencies rand_chacha = "0.3" diff --git a/contracts/mixnet/Cargo.toml b/contracts/mixnet/Cargo.toml index ae2ad99d36..e5c7b47719 100644 --- a/contracts/mixnet/Cargo.toml +++ b/contracts/mixnet/Cargo.toml @@ -26,10 +26,10 @@ name = "mixnet_contract" crate-type = ["cdylib", "rlib"] [dependencies] -mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract", package = "nym-mixnet-contract-common" } -vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract", package = "nym-vesting-contract-common" } -nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common" } -nym-contracts-common-testing = { path = "../../common/cosmwasm-smart-contracts/contracts-common-testing", optional = true } +mixnet-contract-common = { workspace = true } +vesting-contract-common = { workspace = true } +nym-contracts-common = { workspace = true } +nym-contracts-common-testing = { workspace = true, optional = true } cosmwasm-schema = { workspace = true, optional = true } cosmwasm-std = { workspace = true } @@ -46,12 +46,12 @@ semver = { workspace = true } anyhow.workspace = true rand_chacha = { workspace = true } rand = { workspace = true } -nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] } -easy-addr = { path = "../../common/cosmwasm-smart-contracts/easy_addr" } +nym-crypto = { workspace = true, features = ["asymmetric", "rand"] } +easy-addr = { workspace = true } # activate the `testable-mixnet-contract` in tests (weird workaround, but it does the trick) -nym-mixnet-contract = { path = ".", features = ["testable-mixnet-contract"] } -nym-contracts-common-testing = { path = "../../common/cosmwasm-smart-contracts/contracts-common-testing" } +nym-mixnet-contract = { workspace = true, features = ["testable-mixnet-contract"] } +nym-contracts-common-testing = { workspace = true } [features] default = [] @@ -60,4 +60,4 @@ testable-mixnet-contract = ["nym-contracts-common-testing"] schema-gen = ["mixnet-contract-common/schema", "cosmwasm-schema"] [lints] -workspace = true \ No newline at end of file +workspace = true diff --git a/contracts/multisig/cw3-flex-multisig/Cargo.toml b/contracts/multisig/cw3-flex-multisig/Cargo.toml index 695ec7bcac..e016a5f4c8 100644 --- a/contracts/multisig/cw3-flex-multisig/Cargo.toml +++ b/contracts/multisig/cw3-flex-multisig/Cargo.toml @@ -27,18 +27,18 @@ cw-storage-plus = { workspace = true } cosmwasm-schema = { workspace = true, optional = true } cosmwasm-std = { workspace = true } -nym-group-contract-common = { path = "../../../common/cosmwasm-smart-contracts/group-contract" } -nym-multisig-contract-common = { path = "../../../common/cosmwasm-smart-contracts/multisig-contract" } -nym-contracts-common = { path = "../../../common/cosmwasm-smart-contracts/contracts-common" } -nym-contracts-common-testing = { path = "../../../common/cosmwasm-smart-contracts/contracts-common-testing", optional = true } +nym-group-contract-common = { workspace = true } +nym-multisig-contract-common = { workspace = true } +nym-contracts-common = { workspace = true } +nym-contracts-common-testing = { workspace = true, optional = true } [dev-dependencies] -easy-addr = { path = "../../../common/cosmwasm-smart-contracts/easy_addr" } -cw4-group = { path = "../cw4-group" } +easy-addr = { workspace = true } +cw4-group = { workspace = true } cw-multi-test = { workspace = true } cw20-base = { workspace = true } [features] # use library feature to disable all instantiate/execute/query exports library = [] -testable-cw3-contract = ["nym-contracts-common-testing"] \ No newline at end of file +testable-cw3-contract = ["nym-contracts-common-testing"] diff --git a/contracts/multisig/cw4-group/Cargo.toml b/contracts/multisig/cw4-group/Cargo.toml index f0f3711631..b8334b756e 100644 --- a/contracts/multisig/cw4-group/Cargo.toml +++ b/contracts/multisig/cw4-group/Cargo.toml @@ -23,9 +23,9 @@ name = "schema" crate-type = ["cdylib", "rlib"] [dependencies] -nym-group-contract-common = { path = "../../../common/cosmwasm-smart-contracts/group-contract" } -nym-contracts-common = { path = "../../../common/cosmwasm-smart-contracts/contracts-common" } -nym-contracts-common-testing = { path = "../../../common/cosmwasm-smart-contracts/contracts-common-testing", optional = true } +nym-group-contract-common = { workspace = true } +nym-contracts-common = { workspace = true } +nym-contracts-common-testing = { workspace = true, optional = true } cw-utils = { workspace = true } cw2 = { workspace = true } @@ -39,9 +39,9 @@ serde = { workspace = true, default-features = false, features = ["derive"] } thiserror = { workspace = true } [dev-dependencies] -easy-addr = { path = "../../../common/cosmwasm-smart-contracts/easy_addr" } +easy-addr = { workspace = true } [features] # use library feature to disable all instantiate/execute/query exports library = [] -testable-cw4-contract = ["nym-contracts-common-testing"] \ No newline at end of file +testable-cw4-contract = ["nym-contracts-common-testing"] diff --git a/contracts/nym-pool/Cargo.toml b/contracts/nym-pool/Cargo.toml index 5bd54bb3bc..f3fc80740e 100644 --- a/contracts/nym-pool/Cargo.toml +++ b/contracts/nym-pool/Cargo.toml @@ -22,12 +22,12 @@ cw-controllers = { workspace = true } cosmwasm-schema = { workspace = true, optional = true } -nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common" } -nym-pool-contract-common = { path = "../../common/cosmwasm-smart-contracts/nym-pool-contract" } +nym-contracts-common = { workspace = true } +nym-pool-contract-common = { workspace = true } [dev-dependencies] anyhow = { workspace = true } -nym-contracts-common-testing = { path = "../../common/cosmwasm-smart-contracts/contracts-common-testing" } +nym-contracts-common-testing = { workspace = true } [features] schema-gen = ["nym-pool-contract-common/schema", "cosmwasm-schema"] diff --git a/contracts/performance/Cargo.toml b/contracts/performance/Cargo.toml index abeef17f40..72086ad419 100644 --- a/contracts/performance/Cargo.toml +++ b/contracts/performance/Cargo.toml @@ -25,18 +25,18 @@ serde = { workspace = true } cosmwasm-schema = { workspace = true, optional = true } -nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common" } -nym-performance-contract-common = { path = "../../common/cosmwasm-smart-contracts/nym-performance-contract" } -nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract" } +nym-contracts-common = { workspace = true } +nym-performance-contract-common = { workspace = true } +nym-mixnet-contract-common = { workspace = true } [dev-dependencies] anyhow = { workspace = true } -nym-contracts-common-testing = { path = "../../common/cosmwasm-smart-contracts/contracts-common-testing" } -nym-mixnet-contract = { path = "../mixnet", features = ["testable-mixnet-contract"] } -nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] } +nym-contracts-common-testing = { workspace = true } +nym-mixnet-contract = { workspace = true, features = ["testable-mixnet-contract"] } +nym-crypto = { workspace = true, features = ["asymmetric", "rand"] } [features] schema-gen = ["nym-performance-contract-common/schema", "cosmwasm-schema"] [lints] -workspace = true \ No newline at end of file +workspace = true diff --git a/contracts/vesting/Cargo.toml b/contracts/vesting/Cargo.toml index 2845ee15a1..bc227c5806 100644 --- a/contracts/vesting/Cargo.toml +++ b/contracts/vesting/Cargo.toml @@ -24,9 +24,9 @@ name = "vesting_contract" crate-type = ["cdylib", "rlib"] [dependencies] -mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract", package = "nym-mixnet-contract-common", version = "0.6.0" } -contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", package = "nym-contracts-common", version = "0.5.0" } -vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract", package = "nym-vesting-contract-common", version = "0.7.0" } +mixnet-contract-common = { workspace = true } +contracts-common = { workspace = true } +vesting-contract-common = { workspace = true } cosmwasm-schema = { workspace = true, optional = true } cosmwasm-std = { workspace = true } diff --git a/crate-publishing.md b/crate-publishing.md new file mode 100644 index 0000000000..780194e8b7 --- /dev/null +++ b/crate-publishing.md @@ -0,0 +1,22 @@ +# Publishing workspace dependencies +## Rationale re: versioning +We publish the majority of our workspace dependencies (essentially everything in the repo aside from binaries, smart contracts, and some internal tooling) to [crates.io](https://crates.io). + +In order to make this easy to maintain, the versions of these workspace dependencies and the `nym-sdk` crate are kept in sync. + +This version is defined in the `[workspace.package]` section of the root monorepo `Cargo.toml` file. Each of the workspace dependencies have their paths and versions (this has to be individually defined at the moment per-dependency, **this version needs to stay the same as the `workspace.package` version**) defined in the `[workspace.dependencies]` section of the root monorepo `Cargo.toml` file. + +## When Developing +If you add a workspace dependency to the SDK when developing, make sure to add this to the workspace dependencies in the root monorepo `Cargo.toml`. + +## Publishing +``` +# List crates to publish +cargo workspaces list + +# Dry run - check for compilation or other problems +cargo workspaces publish --no-git-commit --dry-run + +# Publish +TODO +``` diff --git a/documentation/autodoc/Cargo.toml b/documentation/autodoc/Cargo.toml index 9fae8c5434..1905285f99 100644 --- a/documentation/autodoc/Cargo.toml +++ b/documentation/autodoc/Cargo.toml @@ -7,6 +7,7 @@ homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +publish = false [dependencies] env_logger = { workspace = true } diff --git a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-percent-stake.md b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-percent-stake.md index 3381dfa5c0..00cd13c087 100644 --- a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-percent-stake.md +++ b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-percent-stake.md @@ -1 +1 @@ -0.83% +0.87% diff --git a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-total-stake.md b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-total-stake.md index 3926dee04e..6b54162743 100644 --- a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-total-stake.md +++ b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-total-stake.md @@ -1 +1 @@ -34.648 +33.087 diff --git a/documentation/docs/components/outputs/api-scraping-outputs/time-now.md b/documentation/docs/components/outputs/api-scraping-outputs/time-now.md index edf8f1ed5d..8e121587e5 100644 --- a/documentation/docs/components/outputs/api-scraping-outputs/time-now.md +++ b/documentation/docs/components/outputs/api-scraping-outputs/time-now.md @@ -1 +1 @@ -Thursday, January 15th 2026, 09:44:55 UTC +Wednesday, January 28th 2026, 09:28:07 UTC diff --git a/documentation/docs/components/outputs/command-outputs/nym-node-run-help.md b/documentation/docs/components/outputs/command-outputs/nym-node-run-help.md index 29cf2e2826..ab11bb896e 100644 --- a/documentation/docs/components/outputs/command-outputs/nym-node-run-help.md +++ b/documentation/docs/components/outputs/command-outputs/nym-node-run-help.md @@ -76,6 +76,10 @@ Options: Indicates whether this gateway is accepting only coconut credentials for accessing the mixnet or if it also accepts non-paying clients [env: NYMNODE_ENFORCE_ZK_NYMS=] [possible values: true, false] --mnemonic Custom cosmos wallet mnemonic used for zk-nym redemption. If no value is provided, a fresh mnemonic is going to be generated [env: NYMNODE_MNEMONIC=] + --upgrade-mode-attestation-url + Endpoint to query to retrieve current upgrade mode attestation. This argument should never be set outside testnets and local networks [env: NYMNODE_UPGRADE_MODE_ATTESTATION_URL=] + --upgrade-mode-attester-public-key + Expected public key of the entity signing the published attestation. This argument should never be set outside testnets and local networks [env: NYMNODE_UPGRADE_MODE_ATTESTER_PUBKEY=] --upstream-exit-policy-url Specifies the url for an upstream source of the exit policy used by this node [env: NYMNODE_UPSTREAM_EXIT_POLICY=] --open-proxy diff --git a/documentation/docs/components/outputs/csv2md-outputs/isp-sheet.md b/documentation/docs/components/outputs/csv2md-outputs/isp-sheet.md index 6c0d218cff..168049880d 100644 --- a/documentation/docs/components/outputs/csv2md-outputs/isp-sheet.md +++ b/documentation/docs/components/outputs/csv2md-outputs/isp-sheet.md @@ -25,12 +25,12 @@ | [LiteServer](https://liteserver.nl) | Netherlands | Yes, on by default | Yes | Very reliable Dutch provider. They do allow Relay nodes but for Exit nodes you need to contact them. Always check T&C https://liteserver.nl/legal | 07/2024 | | [Lowendbox](https://lowendbox.com/category/dedicated-servers) | | | | Just an aggregator with good offers | 07/2025 | | [M247](https://m247.com/eu/services/host/dedicated-servers/) | UK, Austria, Br, Sw, Jp, Poland, Fr, USA, Netherlands | Yes | No | nan | 07/2025 | -| [Mebilcom](https://www.melbicom.net/dedicatedserver/) | NL, USA, DE, UAE, NG, ESP, IN, IT, FR, LT, SG, BG, LV, PL | nan | No | nan | 07/2025 | +| [Mebilcom](https://www.melbicom.net/dedicatedserver/) | NL, US, DE, UAE, NG, ESP, IN, IT, FR, LT, SG, BG, LV, PL | nan | No | nan | 07/2025 | | [Mevspace](https://mevspace.com) | Poland | Yes, on by default | Yes | Flexible Polish providers with 3 DCs in Poland. They do allow Tor Exit nodes but you may need a dedicated server for this. Make sure you open a ticket to check. As of today's date, they have 48h for 1 EUR tariff | 07/2024 | | [Misaka](https://www.misaka.io/) | South Africa | Yes, native support | No | Very Expensive | 05/2024 | | [NiceVPS](https://nicevps.net/) | Netherlands | Yes | nan | nan | 07/2025 | | [Njalla](https://nja.la) | Sweden | Yes | Yes | Privacy vandguards! The biggest VPS 45 is 3 cores only, but it works better than many “larger” servers on the market. | 05/2024 | -| [OVH](https://us.ovhcloud.com/bare-metal/rise/rise-3/) | USA, DE, FR, UK, PL, CA | | No | Not all locations always available | 07/2025 | +| [OVH](https://us.ovhcloud.com/bare-metal/rise/rise-3/) | USA, DE, FR, UK, PL, CA | | No | Exit nodes not allowed on VPS offering, see their [Service Specific Terms](https://us.ovhcloud.com/legal/service-specific-terms/). Not all locations always available | 09/2025 | | [Oneprovider](https://oneprovider.com/en/dedicated-servers/ipv6) | PL, FR, NL, UA, USA, BG, RO, DK, ESP, NO, CZ, RS, IE, IT, UK, HU, CH, SK, AT, BE, BA, HK, JP, SG, LU, AU, SWE, UAE, BR, CR, MX, GR, CL, MA, AR | Yes | No | nan | 07/2025 | | [PrivateLayer](https://privatelayer.com) | Swiss | Yes | Yes | Slow customer response | 07/2025 | | [Privex](https://www.privex.io/tor-exit-policy/) | USA, Germany, Sweden | Yes | Yes | nan | 07/2025 | diff --git a/documentation/docs/components/snippets-general/varible-info-snippet.mdx b/documentation/docs/components/snippets-general/varible-info-snippet.mdx index 4ab6ed8f16..9414218ddb 100644 --- a/documentation/docs/components/snippets-general/varible-info-snippet.mdx +++ b/documentation/docs/components/snippets-general/varible-info-snippet.mdx @@ -1,6 +1,6 @@ import { Callout } from 'nextra/components' -Our documentation often refer to syntax annotated in `<>` brackets. We use this expression for variables that are unique to each user (like path, local moniker, versions etcetra). +Our documentation often refers to syntax annotated in `<>` brackets. We use this expression for variables that are unique to each user (like path, local moniker, versions et cetera). Any syntax in `<>` brackets needs to be substituted with your correct name or version, without the `<>` brackets. If you are unsure, please check our table of essential [parameters and variables](https://nym.com/docs/operators/variables). diff --git a/documentation/docs/pages/operators/changelog.mdx b/documentation/docs/pages/operators/changelog.mdx index 1f7b4b690b..9ea5885dd6 100644 --- a/documentation/docs/pages/operators/changelog.mdx +++ b/documentation/docs/pages/operators/changelog.mdx @@ -49,6 +49,136 @@ This page displays a full list of all the changes during our release cycle from +## `v2026.2-oscypek` + +- [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2026.2-oscypek) +- [`nym-node`](nodes/nym-node.mdx) version `1.24.0` + +```sh +nym-node +Binary Name: nym-node +Build Timestamp: 2026-01-27T14:54:15.579821601Z +Build Version: 1.24.0 +Commit SHA: 83bf9dc7cc2b01f65cab671733f2bf6c3abd471d +Commit Date: 2026-01-27T15:46:52.000000000+01:00 +Commit Branch: HEAD +rustc Version: 1.91.1 +rustc Channel: stable +cargo Profile: release +``` + +### Operators Updates & Tools + + +**This release comes with breaking changes - please follow the [steps below](#oscypek-special-update) before upgrading!** + +Secondly, the outcome of [NIP-7: Nym Exit Policy Update - Opening Ports for Steam, Discord & SSH](https://governator.nym.com/proposal/prop-281e9ec1-8e10-4e97-848c-311823e83f61), is added to the [Network tunnel manager (NTM)](https://github.com/nymtech/nym/blob/develop/scripts/nym-node-setup/network-tunnel-manager.sh) and operators are required to rerun the tool on their servers as [documented here](update-nym-exit-policy). + + +#### `oscypek` special update + +This release brings changes which would lead into a *foreign constraint bug* if operators just switched binaries and restarted the node. To prevent it we need to do a little `sqlite` tweak on the node database. + +To simplify this, we made **a build in command, which operators must run after getting the new binary, but beofre restarting the node.** + +These are the steps to follow: + + +###### 1. Get `oscypek` binary +- SSH to your machine as root +- Navigate to the destination where you have `nym-node` binary +- Get the latest binary and provide it with permissions to run +```sh +curl -L "https://github.com/nymtech/nym/releases/download/nym-binaries-v2026.2-oscypek/nym-node" -o nym-node && \ +chmod +x nym-node +``` + +###### 2. Run `debug` command +```sh +./nym-node debug reset-providers-gateway-dbs --id +``` + +###### 3. Restart your node +- Restart the `nym-node.service` +```sh +systemctl restart nym-node +``` +- Additionaly look for starus or serivice journal +```sh +service nym-node status +# or +journalctl -u nym-node -f --all +``` + + +#### Update Nym exit policy + +As a result of [NIP-7: Nym Exit Policy Update - Opening Ports for Steam, Discord & SSH](https://governator.nym.com/proposal/prop-281e9ec1-8e10-4e97-848c-311823e83f61), we updated [`network-tunnel-manager.sh` (NTM)](https://github.com/nymtech/nym/blob/develop/scripts/nym-node-setup/network-tunnel-manager.sh). Every operator is required to download and re-run the current version of NTM on the servers hosting Nym nodes. + +These are the steps for the exit policy update, using NTM. + + +###### 1. Get the new NTM +- Download the updated NTM and make executable +```sh +curl -L https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/nym-node-setup/network-tunnel-manager.sh -o ./network-tunnel-manager.sh && \ +chmod +x network-tunnel-manager.sh +``` + +###### 2. Update exit policy +- To be sure that your routing is clean, run this command: +```sh +./network-tunnel-manager.sh complete_networking_configuration +``` + + +### Features + +- [Deriving `Serialize` for `GatewayData`](https://github.com/nymtech/nym/pull/6314): Deriving `Serialize` for gateway data, that will be used by the diagnostic tool in the `vpn-client` repo + +- [DNS static table pre-resolve](https://github.com/nymtech/nym/pull/6297): This PR adds pre-resolve stage that returns addres if we have used static table previously. This ensures that we don't continually suffer the penalty of a lookup timeout, while also allowing for the possibility of going back to the default internal secure resolver if one or more nameservers becomes usable again at a future time. + +- [Add `Copy+Clone` to `nym_api_provider::Config`](https://github.com/nymtech/nym/pull/6296): Add `Copy+Clone` to `nym_client_core::client::topology_control::nym_api_provider::Config` + +- [LP Registration + Telescoping + Gateway Probe Localnet Mode](https://github.com/nymtech/nym/pull/6286): Combines LP registration protocol implementation, adds telescoping/nested sessions support, adds localnet mode for `gateway-probe` testing, integrates KKT & PSQ cryptographic primitives + +- [Minor DNS improvements](https://github.com/nymtech/nym/pull/6283): Increase timeouts back to 10 seconds for overall lookup and 5 seconds per query, gnore unreliable test, remove JIT resolution in http client as it is at best not useful, and at worst increasing timeout + +- [HTTP client without default features](https://github.com/nymtech/nym/pull/6281): Fix compile issue caused when using the http client using `default-features=false` + +- [DNS: reduce number of attempts](https://github.com/nymtech/nym/pull/6278): Reduce number of retry attempts performed by hickory to `0`, define `new_resolver` as infallible, use `ResolverOpts` to build builder + +- [Fallback gateway listener and remove legacy key support](https://github.com/nymtech/nym/pull/6249) + +- [Fix assertion](https://github.com/nymtech/nym/pull/6238) + +- [Initial changes to support extra configurable parameters and to print…](https://github.com/nymtech/nym/pull/6237): This branch adds support for the additional configurable parameters introduced in `nicolas/sdk-param-support-debug` in the nym vpn client branch and also debugging messages to verify that it works + +- [Data Observatory](https://github.com/nymtech/nym/pull/6172): This PR adds the Data Observatory that is: + - chain scraper + - parses Cosmos SDK messages + - parses Cosmwasm messages + - stores data in pgsql + +### Bugfix + +- [Downgrade gateway protocol to clients proposed version](https://github.com/nymtech/nym/pull/6377) + +- [Ack fix](https://github.com/nymtech/nym/pull/6364) + +- [Sqlite transaction escalation was causing errors ](https://github.com/nymtech/nym/pull/6299): Getting tickets from credential storage requires a transaction doing a read and then a write. Running registration in parallel was causing sqlite to return errors, because it can't escalate two transactions, only one. + +- [Use proper mixing delay instead of poisson delay in cover traffic](https://github.com/nymtech/nym/pull/6269): Currently the secondary cover traffic loop uses its Poisson process delays instead of a proper mixing delay, this PR fixes that + +### Refactors & Maintenance + +- [Update nix to `v0.30.1`](https://github.com/nymtech/nym/pull/6316) + +- [Rremove repetitive words in comment](https://github.com/nymtech/nym/pull/6313) + +- [Clippy fixes and use fixed rust version from `REQUIRED_RUSTC_VERSION`](https://github.com/nymtech/nym/pull/6295) + + ## `v2026.1-niolo` - [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2026.1-niolo) @@ -120,7 +250,7 @@ Please, let us know how that worked for you. - [`gateway-probe` fixes for run-local](https://github.com/nymtech/nym/pull/6212) -- [Upgrade mode: VPN adjustments](https://github.com/nymtech/nym/pull/6189): This PR further builds up on [\#6174](https://github.com/nymtech/nym/pull/6174) to include changes required by the VPN-client to fully support the upgrade mode, what is relevant here is that this PR modifies the credential storage to allow it to storage an opaque `emergency credential` that lets it be shared between sessions (if it is still valid) ] +- [Upgrade mode: VPN adjustments](https://github.com/nymtech/nym/pull/6189): This PR further builds up on [\#6174](https://github.com/nymtech/nym/pull/6174) to include changes required by the VPN-client to fully support the upgrade mode, what is relevant here is that this PR modifies the credential storage to allow it to storage an opaque `emergency credential` that lets it be shared between sessions (if it is still valid) - [Add weighted scoring to NS API](https://github.com/nymtech/nym/pull/6144) diff --git a/documentation/docs/pages/operators/nodes/nym-node/setup.mdx b/documentation/docs/pages/operators/nodes/nym-node/setup.mdx index 59d401496f..971a629c0f 100644 --- a/documentation/docs/pages/operators/nodes/nym-node/setup.mdx +++ b/documentation/docs/pages/operators/nodes/nym-node/setup.mdx @@ -21,11 +21,11 @@ This documentation page provides a guide on how to set up and run a [NYM NODE](. ```sh nym-node Binary Name: nym-node -Build Timestamp: 2025-12-02T16:21:03.251191389Z -Build Version: 1.23.0 -Commit SHA: 46fe1bc8191f42aa27f34743c96e9e9f26453d87 -Commit Date: 2025-12-02T15:29:30.000000000Z -Commit Branch: release/2025.22-niolo +Build Timestamp: 2026-01-27T14:54:15.579821601Z +Build Version: 1.24.0 +Commit SHA: 83bf9dc7cc2b01f65cab671733f2bf6c3abd471d +Commit Date: 2026-01-27T15:46:52.000000000+01:00 +Commit Branch: HEAD rustc Version: 1.91.1 rustc Channel: stable cargo Profile: release diff --git a/envs/canary.env b/envs/canary.env index fbf97b2643..c42ce00a20 100644 --- a/envs/canary.env +++ b/envs/canary.env @@ -23,4 +23,8 @@ NYM_API=https://canary-api.performance.nymte.ch/api/ NYXD_WS=wss://rpc.canary-validator.performance.nymte.ch/websocket NYM_VPN_API=https://nym-vpn-api-git-deploy-canary-nyx-network-staging.vercel.app/api/ -UPGRADE_MODE_ATTESTER_ED25519_PUBKEY=U1NXToPYUTsh7pYPLcwXCXwcL6pGoLUou7fyAJrNz8b \ No newline at end of file +UPGRADE_MODE_ATTESTER_ED25519_PUBKEY=U1NXToPYUTsh7pYPLcwXCXwcL6pGoLUou7fyAJrNz8b +UPGRADE_MODE_ATTESTATION_URL=http://upgrade-mode.performance.nymte.ch/.wellknown/canary/attestation.json + +NYM_APIS=[{"url":"https://canary-api.performance.nymte.ch/api/","front_hosts":null},{"url":"https://nym-frontdoor.vercel.app/canary/nym-api/","front_hosts":["vercel.app","vercel.com"]}] +NYM_VPN_APIS=[{"url":"https://nym-vpn-api-git-deploy-canary-nyx-network-staging.vercel.app/api/","front_hosts":["vercel.app","vercel.com"]},{"url":"https://nym-frontdoor.vercel.app/canary/nym-vpn-api/","front_hosts":["vercel.app","vercel.com"]}] diff --git a/envs/sandbox.env b/envs/sandbox.env index 96a46881dd..8495e73e18 100644 --- a/envs/sandbox.env +++ b/envs/sandbox.env @@ -24,4 +24,8 @@ NYXD_WS=wss://rpc.sandbox.nymtech.net/websocket NYM_API=https://sandbox-nym-api1.nymtech.net/api/ NYM_VPN_API=https://nym-vpn-api-git-deploy-sandbox-nyx-network-staging.vercel.app/api/ -UPGRADE_MODE_ATTESTER_ED25519_PUBKEY=EGwzKXPrqStv8cHF68VT2LbQuEBGDPzhCAixScvybfem \ No newline at end of file +UPGRADE_MODE_ATTESTER_ED25519_PUBKEY=EGwzKXPrqStv8cHF68VT2LbQuEBGDPzhCAixScvybfem +UPGRADE_MODE_ATTESTATION_URL=http://upgrade-mode.performance.nymte.ch/.wellknown/sandbox/attestation.json + +NYM_APIS=[{"url":"https://sandbox-nym-api1.nymtech.net/api/","front_hosts":null},{"url":"https://nym-frontdoor.vercel.app/sandbox/nym-api/","front_hosts":["vercel.app","vercel.com"]}] +NYM_VPN_APIS=[{"url":"https://nym-vpn-api-git-deploy-sandbox-nyx-network-staging.vercel.app/api/","front_hosts":["vercel.app","vercel.com"]},{"url":"https://nym-frontdoor.vercel.app/sandbox/nym-vpn-api/","front_hosts":["vercel.app","vercel.com"]}] diff --git a/gateway/Cargo.toml b/gateway/Cargo.toml index c802e5c1b8..e912053e34 100644 --- a/gateway/Cargo.toml +++ b/gateway/Cargo.toml @@ -12,6 +12,7 @@ authors = [ description = "Implementation of the Nym Mixnet Gateway" edition = "2021" rust-version = "1.85" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -47,36 +48,36 @@ zeroize = { workspace = true } # internal -nym-credentials = { path = "../common/credentials" } -nym-credentials-interface = { path = "../common/credentials-interface" } -nym-credential-verification = { path = "../common/credential-verification" } -nym-crypto = { path = "../common/crypto", features = ["sphinx"] } -nym-config = { path = "../common/config" } -nym-gateway-storage = { path = "../common/gateway-storage" } -nym-gateway-stats-storage = { path = "../common/gateway-stats-storage" } -nym-gateway-requests = { path = "../common/gateway-requests" } -nym-mixnet-client = { path = "../common/client-libs/mixnet-client" } -nym-network-defaults = { path = "../common/network-defaults" } +nym-credentials = { workspace = true } +nym-credentials-interface = { workspace = true } +nym-credential-verification = { workspace = true } +nym-crypto = { workspace = true, features = ["sphinx"] } +nym-config = { workspace = true } +nym-gateway-storage = { workspace = true } +nym-gateway-stats-storage = { workspace = true } +nym-gateway-requests = { workspace = true } +nym-mixnet-client = { workspace = true } +nym-network-defaults = { workspace = true } nym-network-requester = { path = "../service-providers/network-requester" } -nym-sdk = { path = "../sdk/rust/nym-sdk" } -nym-sphinx = { path = "../common/nymsphinx" } -nym-statistics-common = { path = "../common/statistics" } -nym-task = { path = "../common/task" } -nym-topology = { path = "../common/topology" } -nym-validator-client = { path = "../common/client-libs/validator-client" } +nym-sdk = { workspace = true } +nym-sphinx = { workspace = true } +nym-statistics-common = { workspace = true } +nym-task = { workspace = true } +nym-topology = { workspace = true } +nym-validator-client = { workspace = true } nym-ip-packet-router = { path = "../service-providers/ip-packet-router" } -nym-node-metrics = { path = "../nym-node/nym-node-metrics" } -nym-upgrade-mode-check = { path = "../common/upgrade-mode-check" } -nym-metrics = { path = "../common/nym-metrics" } +nym-node-metrics = { workspace = true } +nym-upgrade-mode-check = { workspace = true } +nym-metrics = { workspace = true } -nym-wireguard = { path = "../common/wireguard" } -nym-wireguard-private-metadata-server = { path = "../common/wireguard-private-metadata/server" } -nym-wireguard-types = { path = "../common/wireguard-types", default-features = false } +nym-wireguard = { workspace = true } +nym-wireguard-private-metadata-server = { workspace = true } +nym-wireguard-types = { workspace = true } -nym-authenticator-requests = { path = "../common/authenticator-requests" } -nym-client-core = { path = "../common/client-core", features = ["cli"] } -nym-id = { path = "../common/nym-id" } -nym-service-provider-requests-common = { path = "../common/service-provider-requests-common" } +nym-authenticator-requests = { workspace = true } +nym-client-core = { workspace = true, features = ["cli"] } +nym-id = { workspace = true } +nym-service-provider-requests-common = { workspace = true } # LP dependencies nym-lp = { path = "../common/nym-lp" } @@ -88,10 +89,12 @@ bytes = { workspace = true } defguard_wireguard_rs = { workspace = true } [dev-dependencies] -nym-gateway-storage = { path = "../common/gateway-storage", features = ["mock"] } -nym-wireguard = { path = "../common/wireguard", features = ["mock"] } -mock_instant = "0.6.0" +anyhow = { workspace = true } +nym-test-utils = { workspace = true } +nym-gateway-storage = { workspace = true, features = ["mock"] } +nym-wireguard = { workspace = true, features = ["mock"] } +mock_instant = { workspace = true } time = { workspace = true } [lints] -workspace = true \ No newline at end of file +workspace = true diff --git a/gateway/src/error.rs b/gateway/src/error.rs index 7801ef01d2..1b9a39b6ae 100644 --- a/gateway/src/error.rs +++ b/gateway/src/error.rs @@ -1,7 +1,10 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only +pub use crate::node::client_handling::websocket::connection_handler::authenticated::RequestHandlingError; use crate::node::internal_service_providers::authenticator::error::AuthenticatorError; +use crate::node::wireguard::GatewayWireguardError; +use nym_credential_verification::upgrade_mode::UpgradeModeEnableError; use nym_gateway_stats_storage::error::StatsStorageError; use nym_gateway_storage::error::GatewayStorageError; use nym_ip_packet_router::error::IpPacketRouterError; @@ -12,8 +15,6 @@ use nym_validator_client::ValidatorClientError; use std::net::IpAddr; use thiserror::Error; -pub use crate::node::client_handling::websocket::connection_handler::authenticated::RequestHandlingError; - #[derive(Debug, Error)] pub enum GatewayError { #[error("the configured version of the gateway ({config_version}) is incompatible with the binary version ({binary_version})")] @@ -155,6 +156,12 @@ pub enum GatewayError { #[error("Invalid SystemTime: {0}")] InvalidSystemTime(#[from] std::time::SystemTimeError), + + #[error(transparent)] + UpgradeModeEnable(#[from] UpgradeModeEnableError), + + #[error(transparent)] + WireguardFailure(#[from] GatewayWireguardError), } impl From for GatewayError { diff --git a/gateway/src/node/internal_service_providers/authenticator/error.rs b/gateway/src/node/internal_service_providers/authenticator/error.rs index 5bdde15919..15a7edf1e0 100644 --- a/gateway/src/node/internal_service_providers/authenticator/error.rs +++ b/gateway/src/node/internal_service_providers/authenticator/error.rs @@ -1,6 +1,7 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::node::wireguard::GatewayWireguardError; use ipnetwork::IpNetworkError; use nym_client_core::error::ClientCoreError; use nym_credential_verification::upgrade_mode::UpgradeModeEnableError; @@ -82,9 +83,6 @@ pub enum AuthenticatorError { #[error("internal data corruption: {0}")] InternalDataCorruption(String), - #[error("peers can't be interacted with anymore")] - PeerInteractionStopped, - #[error("unknown version number")] UnknownVersion, @@ -108,6 +106,9 @@ pub enum AuthenticatorError { #[error(transparent)] UpgradeModeEnable(#[from] UpgradeModeEnableError), + + #[error(transparent)] + WireguardFailure(#[from] GatewayWireguardError), } impl AuthenticatorError { diff --git a/gateway/src/node/internal_service_providers/authenticator/mixnet_listener.rs b/gateway/src/node/internal_service_providers/authenticator/mixnet_listener.rs index c05d9d8cd6..d9591e0090 100644 --- a/gateway/src/node/internal_service_providers/authenticator/mixnet_listener.rs +++ b/gateway/src/node/internal_service_providers/authenticator/mixnet_listener.rs @@ -2,31 +2,17 @@ // SPDX-License-Identifier: Apache-2.0 use crate::node::internal_service_providers::authenticator::{ - config::Config, error::AuthenticatorError, peer_manager::PeerManager, - seen_credential_cache::SeenCredentialCache, + config::Config, error::AuthenticatorError, seen_credential_cache::SeenCredentialCache, }; -use defguard_wireguard_rs::net::IpAddrMask; -use defguard_wireguard_rs::{host::Peer, key::Key}; +use crate::node::wireguard::{PeerManager, PeerRegistrator}; use futures::StreamExt; -use nym_authenticator_requests::models::BandwidthClaim; use nym_authenticator_requests::traits::UpgradeModeMessage; -use nym_authenticator_requests::{latest, v4::registration::IpPair}; use nym_authenticator_requests::{ - latest::registration::{GatewayClient, PendingRegistrations, PrivateIPs}, request::AuthenticatorRequest, traits::{FinalMessage, InitMessage, QueryBandwidthMessage, TopUpMessage}, v1, v2, v3, v4, v5, v6, AuthenticatorVersion, CURRENT_VERSION, }; -use nym_credential_verification::ecash::traits::EcashManager; use nym_credential_verification::upgrade_mode::UpgradeModeDetails; -use nym_credential_verification::{ - bandwidth_storage_manager::BandwidthStorageManager, BandwidthFlushingBehaviourConfig, - ClientBandwidth, CredentialVerifier, -}; -use nym_credentials_interface::{BandwidthCredential, CredentialSpendingData}; -use nym_crypto::asymmetric::x25519::KeyPair; -use nym_gateway_requests::models::CredentialSpendingRequest; -use nym_gateway_storage::models::PersistedBandwidth; use nym_sdk::mixnet::{ AnonymousSenderTag, InputMessage, MixnetMessageSender, Recipient, TransmissionLane, }; @@ -35,52 +21,29 @@ use nym_sphinx::receiver::ReconstructedMessage; use nym_task::ShutdownToken; use nym_wireguard::WireguardGatewayData; use nym_wireguard_types::PeerPublicKey; -use rand::{prelude::IteratorRandom, thread_rng}; use std::cmp::max; -use std::{ - net::IpAddr, - sync::Arc, - time::{Duration, SystemTime}, -}; -use tokio::sync::RwLock; +use std::time::Duration; use tokio_stream::wrappers::IntervalStream; type AuthenticatorHandleResult = Result<(Vec, Option), AuthenticatorError>; -const DEFAULT_REGISTRATION_TIMEOUT_CHECK: Duration = Duration::from_secs(60); // 1 minute +const DEFAULT_CREDENTIAL_TIMEOUT_CHECK: Duration = Duration::from_secs(60); // 1 minute // we need to be above MINIMUM_REMAINING_BANDWIDTH (500MB) plus we also have to trick the client // its depletion is low enough to not require sending new tickets const DEFAULT_WG_CLIENT_BANDWIDTH_THRESHOLD: i64 = 1024 * 1024 * 1024; -pub(crate) struct RegisteredAndFree { - registration_in_progres: PendingRegistrations, - free_private_network_ips: PrivateIPs, -} - -impl RegisteredAndFree { - pub(crate) fn new(free_private_network_ips: PrivateIPs) -> Self { - RegisteredAndFree { - registration_in_progres: Default::default(), - free_private_network_ips, - } - } -} - pub(crate) struct MixnetListener { // The configuration for the mixnet listener - pub(crate) config: Config, + pub(crate) _config: Config, // The mixnet client that we use to send and receive packets from the mixnet pub(crate) mixnet_client: nym_sdk::mixnet::MixnetClient, - // Registrations awaiting confirmation - pub(crate) registered_and_free: RwLock, - pub(crate) peer_manager: PeerManager, pub(crate) upgrade_mode: UpgradeModeDetails, - pub(crate) ecash_verifier: Arc, + pub(crate) peer_registrator: PeerRegistrator, pub(crate) timeout_check_interval: IntervalStream, @@ -90,21 +53,19 @@ pub(crate) struct MixnetListener { impl MixnetListener { pub fn new( config: Config, - free_private_network_ips: PrivateIPs, wireguard_gateway_data: WireguardGatewayData, mixnet_client: nym_sdk::mixnet::MixnetClient, + peer_registrator: PeerRegistrator, upgrade_mode: UpgradeModeDetails, - ecash_verifier: Arc, ) -> Self { let timeout_check_interval = - IntervalStream::new(tokio::time::interval(DEFAULT_REGISTRATION_TIMEOUT_CHECK)); + IntervalStream::new(tokio::time::interval(DEFAULT_CREDENTIAL_TIMEOUT_CHECK)); MixnetListener { - config, + _config: config, mixnet_client, - registered_and_free: RwLock::new(RegisteredAndFree::new(free_private_network_ips)), peer_manager: PeerManager::new(wireguard_gateway_data), upgrade_mode, - ecash_verifier, + peer_registrator, timeout_check_interval, seen_credential_cache: SeenCredentialCache::new(), } @@ -114,10 +75,6 @@ impl MixnetListener { self.upgrade_mode.enabled() } - fn keypair(&self) -> &Arc { - self.peer_manager.wireguard_gateway_data.keypair() - } - async fn upgrade_mode_bandwidth(&self, peer: PeerPublicKey) -> Result { // if we're undergoing upgrade mode, we don't meter bandwidth, // we simply return MAX of clients current bandwidth and minimum bandwidth before default @@ -131,51 +88,6 @@ impl MixnetListener { )) } - async fn remove_stale_registrations(&self) -> Result<(), AuthenticatorError> { - let mut registered_and_free = self.registered_and_free.write().await; - let registered_values: Vec<_> = registered_and_free - .registration_in_progres - .values() - .cloned() - .collect(); - for reg in registered_values { - let ip = registered_and_free - .free_private_network_ips - .get_mut(®.gateway_data.private_ips) - .ok_or(AuthenticatorError::InternalDataCorruption(format!( - "IPs {} should be present", - reg.gateway_data.private_ips - )))?; - - let Some(timestamp) = ip else { - registered_and_free - .registration_in_progres - .remove(®.gateway_data.pub_key()); - tracing::debug!( - "Removed stale registration of {}", - reg.gateway_data.pub_key() - ); - continue; - }; - let duration = SystemTime::now().duration_since(*timestamp).map_err(|_| { - AuthenticatorError::InternalDataCorruption( - "set timestamp shouldn't have been set in the future".to_string(), - ) - })?; - if duration > DEFAULT_REGISTRATION_TIMEOUT_CHECK { - *ip = None; - registered_and_free - .registration_in_progres - .remove(®.gateway_data.pub_key()); - tracing::debug!( - "Removed stale registration of {}", - reg.gateway_data.pub_key() - ); - } - } - Ok(()) - } - async fn on_initial_request( &mut self, init_message: Box, @@ -183,353 +95,12 @@ impl MixnetListener { request_id: u64, reply_to: Option, ) -> AuthenticatorHandleResult { - let remote_public = init_message.pub_key(); - let nonce: u64 = fastrand::u64(..); - let mut registered_and_free = self.registered_and_free.write().await; - if let Some(registration_data) = registered_and_free - .registration_in_progres - .get(&remote_public) - { - let gateway_data = registration_data.gateway_data.clone(); - let bytes = match AuthenticatorVersion::from(protocol) { - AuthenticatorVersion::V1 => { - v1::response::AuthenticatorResponse::new_pending_registration_success( - v1::registration::RegistrationData { - nonce: registration_data.nonce, - gateway_data: v1::GatewayClient { - pub_key: gateway_data.pub_key, - private_ip: gateway_data.private_ips.ipv4.into(), - mac: v1::ClientMac::new(gateway_data.mac.to_vec()), - }, - wg_port: registration_data.wg_port, - }, - request_id, - reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?, - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)? - } - AuthenticatorVersion::V2 => { - v2::response::AuthenticatorResponse::new_pending_registration_success( - v2::registration::RegistrationData { - nonce: registration_data.nonce, - gateway_data: v2::registration::GatewayClient::new( - self.keypair().private_key(), - remote_public.inner(), - registration_data.gateway_data.private_ips.ipv4.into(), - registration_data.nonce, - ), - wg_port: registration_data.wg_port, - }, - request_id, - reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?, - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)? - } - AuthenticatorVersion::V3 => { - v3::response::AuthenticatorResponse::new_pending_registration_success( - v3::registration::RegistrationData { - nonce: registration_data.nonce, - gateway_data: v3::registration::GatewayClient::new( - self.keypair().private_key(), - remote_public.inner(), - registration_data.gateway_data.private_ips.ipv4.into(), - registration_data.nonce, - ), - wg_port: registration_data.wg_port, - }, - request_id, - reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?, - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)? - } - AuthenticatorVersion::V4 => { - v4::response::AuthenticatorResponse::new_pending_registration_success( - v4::registration::RegistrationData { - nonce: registration_data.nonce, - // convert current to v5 and then v5 to v4 (current as of 28.08.25) - gateway_data: v5::registration::GatewayClient::from( - registration_data.gateway_data.clone(), - ) - .into(), - wg_port: registration_data.wg_port, - }, - request_id, - reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?, - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)? - } - AuthenticatorVersion::V5 => { - v5::response::AuthenticatorResponse::new_pending_registration_success( - v5::registration::RegistrationData { - nonce: registration_data.nonce, - gateway_data: registration_data.gateway_data.clone().into(), - wg_port: registration_data.wg_port, - }, - request_id, - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)? - } - AuthenticatorVersion::V6 => { - v6::response::AuthenticatorResponse::new_pending_registration_success( - v6::registration::RegistrationData { - nonce: registration_data.nonce, - gateway_data: registration_data.gateway_data.clone(), - wg_port: registration_data.wg_port, - }, - request_id, - self.upgrade_mode_enabled(), - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)? - } - AuthenticatorVersion::UNKNOWN => return Err(AuthenticatorError::UnknownVersion), - }; - return Ok((bytes, reply_to)); - } + let response = self + .peer_registrator + .on_initial_authenticator_request(init_message, protocol, request_id, reply_to) + .await?; - let peer = self.peer_manager.query_peer(remote_public).await?; - if let Some(peer) = peer { - let allowed_ipv4 = peer - .allowed_ips - .iter() - .find_map(|ip_mask| match ip_mask.ip { - IpAddr::V4(ipv4_addr) => Some(ipv4_addr), - _ => None, - }) - .ok_or(AuthenticatorError::InternalError( - "there should be one private IPv4 in the list".to_string(), - ))?; - let allowed_ipv6 = peer - .allowed_ips - .iter() - .find_map(|ip_mask| match ip_mask.ip { - IpAddr::V6(ipv6_addr) => Some(ipv6_addr), - _ => None, - }) - .unwrap_or(IpPair::from(IpAddr::from(allowed_ipv4)).ipv6); - let bytes = match AuthenticatorVersion::from(protocol) { - AuthenticatorVersion::V1 => v1::response::AuthenticatorResponse::new_registered( - v1::registration::RegisteredData { - pub_key: self.keypair().public_key().into(), - private_ip: allowed_ipv4.into(), - wg_port: self.config.authenticator.tunnel_announced_port, - }, - reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?, - request_id, - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)?, - AuthenticatorVersion::V2 => v2::response::AuthenticatorResponse::new_registered( - v2::registration::RegisteredData { - pub_key: self.keypair().public_key().into(), - private_ip: allowed_ipv4.into(), - wg_port: self.config.authenticator.tunnel_announced_port, - }, - reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?, - request_id, - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)?, - AuthenticatorVersion::V3 => v3::response::AuthenticatorResponse::new_registered( - v3::registration::RegisteredData { - pub_key: self.keypair().public_key().into(), - private_ip: allowed_ipv4.into(), - wg_port: self.config.authenticator.tunnel_announced_port, - }, - reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?, - request_id, - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)?, - AuthenticatorVersion::V4 => v4::response::AuthenticatorResponse::new_registered( - v4::registration::RegisteredData { - pub_key: self.keypair().public_key().into(), - private_ips: (allowed_ipv4, allowed_ipv6).into(), - wg_port: self.config.authenticator.tunnel_announced_port, - }, - reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?, - request_id, - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)?, - AuthenticatorVersion::V5 => v5::response::AuthenticatorResponse::new_registered( - v5::registration::RegisteredData { - pub_key: self.keypair().public_key().into(), - private_ips: (allowed_ipv4, allowed_ipv6).into(), - wg_port: self.config.authenticator.tunnel_announced_port, - }, - request_id, - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)?, - AuthenticatorVersion::V6 => v6::response::AuthenticatorResponse::new_registered( - v6::registration::RegisteredData { - pub_key: self.keypair().public_key().into(), - private_ips: (allowed_ipv4, allowed_ipv6).into(), - wg_port: self.config.authenticator.tunnel_announced_port, - }, - request_id, - self.upgrade_mode_enabled(), - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)?, - AuthenticatorVersion::UNKNOWN => return Err(AuthenticatorError::UnknownVersion), - }; - return Ok((bytes, reply_to)); - } - - let private_ip_ref = registered_and_free - .free_private_network_ips - .iter_mut() - .filter(|r| r.1.is_none()) - .choose(&mut thread_rng()) - .ok_or(AuthenticatorError::NoFreeIp)?; - let private_ips = *private_ip_ref.0; - // mark it as used, even though it's not final - *private_ip_ref.1 = Some(SystemTime::now()); - let gateway_data = GatewayClient::new( - self.keypair().private_key(), - remote_public.inner(), - *private_ip_ref.0, - nonce, - ); - let registration_data = latest::registration::RegistrationData { - nonce, - gateway_data: gateway_data.clone(), - wg_port: self.config.authenticator.tunnel_announced_port, - }; - registered_and_free - .registration_in_progres - .insert(remote_public, registration_data.clone()); - let bytes = match AuthenticatorVersion::from(protocol) { - AuthenticatorVersion::V1 => { - v1::response::AuthenticatorResponse::new_pending_registration_success( - v1::registration::RegistrationData { - nonce: registration_data.nonce, - gateway_data: v1::registration::GatewayClient::new( - self.keypair().private_key(), - remote_public.inner(), - private_ips.ipv4.into(), - nonce, - ), - wg_port: registration_data.wg_port, - }, - request_id, - reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?, - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)? - } - AuthenticatorVersion::V2 => { - v2::response::AuthenticatorResponse::new_pending_registration_success( - v2::registration::RegistrationData { - nonce: registration_data.nonce, - gateway_data: v2::registration::GatewayClient::new( - self.keypair().private_key(), - remote_public.inner(), - private_ips.ipv4.into(), - nonce, - ), - wg_port: registration_data.wg_port, - }, - request_id, - reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?, - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)? - } - AuthenticatorVersion::V3 => { - v3::response::AuthenticatorResponse::new_pending_registration_success( - v3::registration::RegistrationData { - nonce: registration_data.nonce, - gateway_data: v3::registration::GatewayClient::new( - self.keypair().private_key(), - remote_public.inner(), - private_ips.ipv4.into(), - nonce, - ), - wg_port: registration_data.wg_port, - }, - request_id, - reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?, - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)? - } - AuthenticatorVersion::V4 => { - v4::response::AuthenticatorResponse::new_pending_registration_success( - v4::registration::RegistrationData { - nonce: registration_data.nonce, - // convert current to v5 and then v5 to v4 (current as of 28.08.25) - gateway_data: v5::registration::GatewayClient::from( - registration_data.gateway_data.clone(), - ) - .into(), - wg_port: registration_data.wg_port, - }, - request_id, - reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?, - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)? - } - AuthenticatorVersion::V5 => { - v5::response::AuthenticatorResponse::new_pending_registration_success( - v5::registration::RegistrationData { - nonce: registration_data.nonce, - gateway_data: registration_data.gateway_data.into(), - wg_port: registration_data.wg_port, - }, - request_id, - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)? - } - AuthenticatorVersion::V6 => { - v6::response::AuthenticatorResponse::new_pending_registration_success( - v6::registration::RegistrationData { - nonce: registration_data.nonce, - gateway_data: registration_data.gateway_data, - wg_port: registration_data.wg_port, - }, - request_id, - self.upgrade_mode_enabled(), - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)? - } - AuthenticatorVersion::UNKNOWN => return Err(AuthenticatorError::UnknownVersion), - }; - - Ok((bytes, reply_to)) - } - - async fn handle_final_credential_claim( - &self, - claim: BandwidthClaim, - client_id: i64, - ) -> Result<(), AuthenticatorError> { - match claim.credential { - BandwidthCredential::ZkNym(zk_nym) => { - // if we got zk-nym, we just try to verify it - credential_verification(self.ecash_verifier.clone(), *zk_nym, client_id).await?; - Ok(()) - } - BandwidthCredential::UpgradeModeJWT { token } => { - // if we're already in the upgrade mode, don't bother validating the token - if self.upgrade_mode_enabled() { - return Ok(()); - } - - self.upgrade_mode.try_enable_via_received_jwt(token).await?; - Ok(()) - } - } + Ok((response.bytes, response.reply_to)) } async fn on_final_request( @@ -539,139 +110,12 @@ impl MixnetListener { request_id: u64, reply_to: Option, ) -> AuthenticatorHandleResult { - let mut registered_and_free = self.registered_and_free.write().await; - let registration_data = registered_and_free - .registration_in_progres - .get(&final_message.gateway_client_pub_key()) - .ok_or(AuthenticatorError::RegistrationNotInProgress)? - .clone(); - - if final_message - .verify(self.keypair().private_key(), registration_data.nonce) - .is_err() - { - return Err(AuthenticatorError::MacVerificationFailure); - } - - let mut peer = Peer::new(Key::new(final_message.gateway_client_pub_key().to_bytes())); - peer.allowed_ips - .push(IpAddrMask::new(final_message.private_ips().ipv4.into(), 32)); - peer.allowed_ips.push(IpAddrMask::new( - final_message.private_ips().ipv6.into(), - 128, - )); - - // ideally credential wouldn't have been required in upgrade mode, - // however, we need some basic information to insert valid wg peer - let Some(credential) = final_message.credential() else { - return Err(AuthenticatorError::NoCredentialReceived); - }; - - let typ = credential.kind; - - let client_id = self - .ecash_verifier - .storage() - .insert_wireguard_peer(&peer, typ.into()) + let response = self + .peer_registrator + .on_final_authenticator_request(final_message, protocol, request_id, reply_to) .await?; - if let Err(err) = self - .handle_final_credential_claim(credential, client_id) - .await - { - self.ecash_verifier - .storage() - .remove_wireguard_peer(&peer.public_key.to_string()) - .await?; - return Err(err); - } - - let public_key = peer.public_key.to_string(); - if let Err(e) = self.peer_manager.add_peer(peer).await { - self.ecash_verifier - .storage() - .remove_wireguard_peer(&public_key) - .await?; - return Err(e); - } - - registered_and_free - .registration_in_progres - .remove(&final_message.gateway_client_pub_key()); - - let bytes = match AuthenticatorVersion::from(protocol) { - AuthenticatorVersion::V1 => v1::response::AuthenticatorResponse::new_registered( - v1::registration::RegisteredData { - pub_key: registration_data.gateway_data.pub_key, - private_ip: registration_data.gateway_data.private_ips.ipv4.into(), - wg_port: registration_data.wg_port, - }, - reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?, - request_id, - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)?, - AuthenticatorVersion::V2 => v2::response::AuthenticatorResponse::new_registered( - v2::registration::RegisteredData { - pub_key: registration_data.gateway_data.pub_key, - private_ip: registration_data.gateway_data.private_ips.ipv4.into(), - wg_port: registration_data.wg_port, - }, - reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?, - request_id, - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)?, - AuthenticatorVersion::V3 => v3::response::AuthenticatorResponse::new_registered( - v3::registration::RegisteredData { - pub_key: registration_data.gateway_data.pub_key, - private_ip: registration_data.gateway_data.private_ips.ipv4.into(), - wg_port: registration_data.wg_port, - }, - reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?, - request_id, - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)?, - AuthenticatorVersion::V4 => v4::response::AuthenticatorResponse::new_registered( - v4::registration::RegisteredData { - pub_key: registration_data.gateway_data.pub_key, - // convert current to v5 and then v5 to v4 (current as of 28.08.25) - private_ips: v5::registration::IpPair::from( - registration_data.gateway_data.private_ips, - ) - .into(), - wg_port: registration_data.wg_port, - }, - reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?, - request_id, - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)?, - AuthenticatorVersion::V5 => v5::response::AuthenticatorResponse::new_registered( - v5::registration::RegisteredData { - pub_key: registration_data.gateway_data.pub_key, - private_ips: registration_data.gateway_data.private_ips.into(), - wg_port: registration_data.wg_port, - }, - request_id, - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)?, - AuthenticatorVersion::V6 => v6::response::AuthenticatorResponse::new_registered( - v6::registration::RegisteredData { - pub_key: registration_data.gateway_data.pub_key, - private_ips: registration_data.gateway_data.private_ips, - wg_port: registration_data.wg_port, - }, - request_id, - self.upgrade_mode_enabled(), - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)?, - AuthenticatorVersion::UNKNOWN => return Err(AuthenticatorError::UnknownVersion), - }; - Ok((bytes, reply_to)) + Ok((response.bytes, response.reply_to)) } async fn on_query_bandwidth_request( @@ -962,9 +406,6 @@ impl MixnetListener { break; }, _ = self.timeout_check_interval.next() => { - if let Err(e) = self.remove_stale_registrations().await { - tracing::error!("Could not clear stale registrations. The registration process might get jammed soon - {e:?}"); - } self.seen_credential_cache.remove_stale(); } msg = self.mixnet_client.next() => { @@ -994,45 +435,6 @@ impl MixnetListener { } } -pub async fn credential_storage_preparation( - ecash_verifier: Arc, - client_id: i64, -) -> Result { - ecash_verifier - .storage() - .create_bandwidth_entry(client_id) - .await?; - let bandwidth = ecash_verifier - .storage() - .get_available_bandwidth(client_id) - .await? - .ok_or(AuthenticatorError::InternalError( - "bandwidth entry should have just been created".to_string(), - ))?; - Ok(bandwidth) -} - -async fn credential_verification( - ecash_verifier: Arc, - credential: CredentialSpendingData, - client_id: i64, -) -> Result { - let bandwidth = credential_storage_preparation(ecash_verifier.clone(), client_id).await?; - let client_bandwidth = ClientBandwidth::new(bandwidth.into()); - let mut verifier = CredentialVerifier::new( - CredentialSpendingRequest::new(credential), - ecash_verifier.clone(), - BandwidthStorageManager::new( - ecash_verifier.storage(), - client_bandwidth, - client_id, - BandwidthFlushingBehaviourConfig::default(), - true, - ), - ); - Ok(verifier.verify().await?) -} - fn deserialize_request( reconstructed: &ReconstructedMessage, ) -> Result { diff --git a/gateway/src/node/internal_service_providers/authenticator/mod.rs b/gateway/src/node/internal_service_providers/authenticator/mod.rs index a98c31868e..fb50f6ddb0 100644 --- a/gateway/src/node/internal_service_providers/authenticator/mod.rs +++ b/gateway/src/node/internal_service_providers/authenticator/mod.rs @@ -2,22 +2,21 @@ // SPDX-License-Identifier: Apache-2.0 use crate::node::internal_service_providers::authenticator::error::AuthenticatorError; +use crate::node::wireguard::PeerRegistrator; use futures::channel::oneshot; -use ipnetwork::IpNetwork; use nym_client_core::{HardcodedTopologyProvider, TopologyProvider}; +use nym_credential_verification::upgrade_mode::UpgradeModeDetails; use nym_sdk::{mixnet::Recipient, GatewayTransceiver}; use nym_task::ShutdownTracker; use nym_wireguard::WireguardGatewayData; -use std::{net::IpAddr, path::Path, sync::Arc, time::SystemTime}; +use std::path::Path; pub use config::Config; -use nym_credential_verification::upgrade_mode::UpgradeModeDetails; pub mod config; pub mod error; pub mod mixnet_client; pub mod mixnet_listener; -mod peer_manager; mod seen_credential_cache; pub struct OnStartData { @@ -34,13 +33,12 @@ impl OnStartData { pub struct Authenticator { #[allow(unused)] config: Config, + peer_registrator: PeerRegistrator, upgrade_mode_state: UpgradeModeDetails, wait_for_gateway: bool, custom_topology_provider: Option>, custom_gateway_transceiver: Option>, wireguard_gateway_data: WireguardGatewayData, - ecash_verifier: Arc, - used_private_network_ips: Vec, shutdown: ShutdownTracker, on_start: Option>, } @@ -48,23 +46,19 @@ pub struct Authenticator { impl Authenticator { pub fn new( config: Config, + peer_registrator: PeerRegistrator, upgrade_mode_state: UpgradeModeDetails, wireguard_gateway_data: WireguardGatewayData, - used_private_network_ips: Vec, - ecash_verifier: Arc< - dyn nym_credential_verification::ecash::traits::EcashManager + Send + Sync, - >, shutdown: ShutdownTracker, ) -> Self { Self { config, + peer_registrator, upgrade_mode_state, wait_for_gateway: false, custom_topology_provider: None, custom_gateway_transceiver: None, - ecash_verifier, wireguard_gateway_data, - used_private_network_ips, shutdown, on_start: None, } @@ -135,30 +129,12 @@ impl Authenticator { let self_address = *mixnet_client.nym_address(); - let used_private_network_ips = - std::collections::BTreeSet::from_iter(self.used_private_network_ips.iter()); - let private_ip_network = IpNetwork::new( - self.config.authenticator.private_ipv4.into(), - self.config.authenticator.private_network_prefix_v4, - )?; - let now = SystemTime::now(); - let free_private_network_ips = private_ip_network - .iter() - .map(|ip: IpAddr| { - if used_private_network_ips.contains(&ip) { - (ip.into(), Some(now)) - } else { - (ip.into(), None) - } - }) - .collect(); - let mixnet_listener = crate::node::internal_service_providers::authenticator::mixnet_listener::MixnetListener::new( + let mixnet_listener = mixnet_listener::MixnetListener::new( self.config, - free_private_network_ips, self.wireguard_gateway_data, mixnet_client, + self.peer_registrator, self.upgrade_mode_state, - self.ecash_verifier, ); tracing::info!("The address of this client is: {self_address}"); diff --git a/gateway/src/node/internal_service_providers/authenticator/peer_manager.rs b/gateway/src/node/internal_service_providers/authenticator/peer_manager.rs deleted file mode 100644 index c057dfa57c..0000000000 --- a/gateway/src/node/internal_service_providers/authenticator/peer_manager.rs +++ /dev/null @@ -1,469 +0,0 @@ -// Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::node::internal_service_providers::authenticator::error::AuthenticatorError; -use defguard_wireguard_rs::{host::Peer, key::Key}; -use futures::channel::oneshot; -use nym_credential_verification::{ClientBandwidth, TicketVerifier}; -use nym_credentials_interface::CredentialSpendingData; -use nym_wireguard::{peer_controller::PeerControlRequest, WireguardGatewayData}; -use nym_wireguard_types::PeerPublicKey; - -pub struct PeerManager { - pub(crate) wireguard_gateway_data: WireguardGatewayData, -} - -impl PeerManager { - pub fn new(wireguard_gateway_data: WireguardGatewayData) -> Self { - PeerManager { - wireguard_gateway_data, - } - } - pub async fn add_peer(&self, peer: Peer) -> Result<(), AuthenticatorError> { - let (response_tx, response_rx) = oneshot::channel(); - let msg = PeerControlRequest::AddPeer { peer, response_tx }; - self.wireguard_gateway_data - .peer_tx() - .send(msg) - .await - .map_err(|_| AuthenticatorError::PeerInteractionStopped)?; - - response_rx - .await - .map_err(|_| AuthenticatorError::InternalError("no response for add peer".to_string()))? - .map_err(|err| { - AuthenticatorError::InternalError(format!( - "adding peer could not be performed: {err:?}" - )) - }) - } - - pub async fn _remove_peer(&self, pub_key: PeerPublicKey) -> Result<(), AuthenticatorError> { - let key = Key::new(pub_key.to_bytes()); - let (response_tx, response_rx) = oneshot::channel(); - let msg = PeerControlRequest::RemovePeer { key, response_tx }; - self.wireguard_gateway_data - .peer_tx() - .send(msg) - .await - .map_err(|_| AuthenticatorError::PeerInteractionStopped)?; - - response_rx - .await - .map_err(|_| { - AuthenticatorError::InternalError("no response for remove peer".to_string()) - })? - .map_err(|err| { - AuthenticatorError::InternalError(format!( - "removing peer could not be performed: {err:?}" - )) - }) - } - - pub async fn query_peer( - &self, - public_key: PeerPublicKey, - ) -> Result, AuthenticatorError> { - let key = Key::new(public_key.to_bytes()); - let (response_tx, response_rx) = oneshot::channel(); - let msg = PeerControlRequest::QueryPeer { key, response_tx }; - self.wireguard_gateway_data - .peer_tx() - .send(msg) - .await - .map_err(|_| AuthenticatorError::PeerInteractionStopped)?; - - response_rx - .await - .map_err(|_| { - AuthenticatorError::InternalError("no response for query peer".to_string()) - })? - .map_err(|err| { - AuthenticatorError::InternalError(format!( - "querying peer could not be performed: {err:?}" - )) - }) - } - - pub async fn query_bandwidth( - &self, - public_key: PeerPublicKey, - ) -> Result { - let client_bandwidth = self.query_client_bandwidth(public_key).await?; - Ok(client_bandwidth.available().await) - } - - pub async fn query_client_bandwidth( - &self, - key: PeerPublicKey, - ) -> Result { - let key = Key::new(key.to_bytes()); - let (response_tx, response_rx) = oneshot::channel(); - let msg = PeerControlRequest::GetClientBandwidthByKey { key, response_tx }; - self.wireguard_gateway_data - .peer_tx() - .send(msg) - .await - .map_err(|_| AuthenticatorError::PeerInteractionStopped)?; - - response_rx - .await - .map_err(|_| { - AuthenticatorError::InternalError( - "no response for query client bandwidth".to_string(), - ) - })? - .map_err(|err| { - AuthenticatorError::InternalError(format!( - "querying client bandwidth could not be performed: {err:?}" - )) - }) - } - - pub async fn query_verifier_by_key( - &self, - key: PeerPublicKey, - credential: CredentialSpendingData, - ) -> Result, AuthenticatorError> { - let key = Key::new(key.to_bytes()); - let (response_tx, response_rx) = oneshot::channel(); - let msg = PeerControlRequest::GetVerifierByKey { - key, - credential: Box::new(credential), - response_tx, - }; - self.wireguard_gateway_data - .peer_tx() - .send(msg) - .await - .map_err(|_| AuthenticatorError::PeerInteractionStopped)?; - - response_rx - .await - .map_err(|_| { - AuthenticatorError::InternalError("no response for query verifier".to_string()) - })? - .map_err(|err| { - AuthenticatorError::InternalError(format!( - "querying verifier could not be performed: {err:?}" - )) - }) - } -} - -#[cfg(test)] -mod tests { - use std::{str::FromStr, sync::Arc}; - - use nym_credential_verification::{ - bandwidth_storage_manager::BandwidthStorageManager, ecash::MockEcashManager, - }; - use nym_credentials_interface::Bandwidth; - use nym_crypto::asymmetric::x25519::KeyPair; - use nym_gateway_storage::traits::{mock::MockGatewayStorage, BandwidthGatewayStorage}; - use nym_wireguard::peer_controller::{start_controller, stop_controller}; - use rand::rngs::OsRng; - use time::{Duration, OffsetDateTime}; - use tokio::sync::RwLock; - - use crate::nym_authenticator::{ - config::Authenticator, mixnet_listener::credential_storage_preparation, - }; - - use super::*; - - const CREDENTIAL_BYTES: [u8; 1245] = [ - 0, 0, 4, 133, 96, 179, 223, 185, 136, 23, 213, 166, 59, 203, 66, 69, 209, 181, 227, 254, - 16, 102, 98, 237, 59, 119, 170, 111, 31, 194, 51, 59, 120, 17, 115, 229, 79, 91, 11, 139, - 154, 2, 212, 23, 68, 70, 167, 3, 240, 54, 224, 171, 221, 1, 69, 48, 60, 118, 119, 249, 123, - 35, 172, 227, 131, 96, 232, 209, 187, 123, 4, 197, 102, 90, 96, 45, 125, 135, 140, 99, 1, - 151, 17, 131, 143, 157, 97, 107, 139, 232, 212, 87, 14, 115, 253, 255, 166, 167, 186, 43, - 90, 96, 173, 105, 120, 40, 10, 163, 250, 224, 214, 200, 178, 4, 160, 16, 130, 59, 76, 193, - 39, 240, 3, 101, 141, 209, 183, 226, 186, 207, 56, 210, 187, 7, 164, 240, 164, 205, 37, 81, - 184, 214, 193, 195, 90, 205, 238, 225, 195, 104, 12, 123, 203, 57, 233, 243, 215, 145, 195, - 196, 57, 38, 125, 172, 18, 47, 63, 165, 110, 219, 180, 40, 58, 116, 92, 254, 160, 98, 48, - 92, 254, 232, 107, 184, 80, 234, 60, 160, 235, 249, 76, 41, 38, 165, 28, 40, 136, 74, 48, - 166, 50, 245, 23, 201, 140, 101, 79, 93, 235, 128, 186, 146, 126, 180, 134, 43, 13, 186, - 19, 195, 48, 168, 201, 29, 216, 95, 176, 198, 132, 188, 64, 39, 212, 150, 32, 52, 53, 38, - 228, 199, 122, 226, 217, 75, 40, 191, 151, 48, 164, 242, 177, 79, 14, 122, 105, 151, 85, - 88, 199, 162, 17, 96, 103, 83, 178, 128, 9, 24, 30, 74, 108, 241, 85, 240, 166, 97, 241, - 85, 199, 11, 198, 226, 234, 70, 107, 145, 28, 208, 114, 51, 12, 234, 108, 101, 202, 112, - 48, 185, 22, 159, 67, 109, 49, 27, 149, 90, 109, 32, 226, 112, 7, 201, 208, 209, 104, 31, - 97, 134, 204, 145, 27, 181, 206, 181, 106, 32, 110, 136, 115, 249, 201, 111, 5, 245, 203, - 71, 121, 169, 126, 151, 178, 236, 59, 221, 195, 48, 135, 115, 6, 50, 227, 74, 97, 107, 107, - 213, 90, 2, 203, 154, 138, 47, 128, 52, 134, 128, 224, 51, 65, 240, 90, 8, 55, 175, 180, - 178, 204, 206, 168, 110, 51, 57, 189, 169, 48, 169, 136, 121, 99, 51, 170, 178, 214, 74, 1, - 96, 151, 167, 25, 173, 180, 171, 155, 10, 55, 142, 234, 190, 113, 90, 79, 80, 244, 71, 166, - 30, 235, 113, 150, 133, 1, 218, 17, 109, 111, 223, 24, 216, 177, 41, 2, 204, 65, 221, 212, - 207, 236, 144, 6, 65, 224, 55, 42, 1, 1, 161, 134, 118, 127, 111, 220, 110, 127, 240, 71, - 223, 129, 12, 93, 20, 220, 60, 56, 71, 146, 184, 95, 132, 69, 28, 56, 53, 192, 213, 22, - 119, 230, 152, 225, 182, 188, 163, 219, 37, 175, 247, 73, 14, 247, 38, 72, 243, 1, 48, 131, - 59, 8, 13, 96, 143, 185, 127, 241, 161, 217, 24, 149, 193, 40, 16, 30, 202, 151, 28, 119, - 240, 153, 101, 156, 61, 193, 72, 245, 199, 181, 12, 231, 65, 166, 67, 142, 121, 207, 202, - 58, 197, 113, 188, 248, 42, 124, 105, 48, 161, 241, 55, 209, 36, 194, 27, 63, 233, 144, - 189, 85, 117, 234, 9, 139, 46, 31, 206, 114, 95, 131, 29, 240, 13, 81, 142, 140, 133, 33, - 30, 41, 141, 37, 80, 217, 95, 221, 76, 115, 86, 201, 165, 51, 252, 9, 28, 209, 1, 48, 150, - 74, 248, 212, 187, 222, 66, 210, 3, 200, 19, 217, 171, 184, 42, 148, 53, 150, 57, 50, 6, - 227, 227, 62, 49, 42, 148, 148, 157, 82, 191, 58, 24, 34, 56, 98, 120, 89, 105, 176, 85, - 15, 253, 241, 41, 153, 195, 136, 1, 48, 142, 126, 213, 101, 223, 79, 133, 230, 105, 38, - 161, 149, 2, 21, 136, 150, 42, 72, 218, 85, 146, 63, 223, 58, 108, 186, 183, 248, 62, 20, - 47, 34, 113, 160, 177, 204, 181, 16, 24, 212, 224, 35, 84, 51, 168, 56, 136, 11, 1, 48, - 135, 242, 62, 149, 230, 178, 32, 224, 119, 26, 234, 163, 237, 224, 114, 95, 112, 140, 170, - 150, 96, 125, 136, 221, 180, 78, 18, 11, 12, 184, 2, 198, 217, 119, 43, 69, 4, 172, 109, - 55, 183, 40, 131, 172, 161, 88, 183, 101, 1, 48, 173, 216, 22, 73, 42, 255, 211, 93, 249, - 87, 159, 115, 61, 91, 55, 130, 17, 216, 60, 34, 122, 55, 8, 244, 244, 153, 151, 57, 5, 144, - 178, 55, 249, 64, 211, 168, 34, 148, 56, 89, 92, 203, 70, 124, 219, 152, 253, 165, 0, 32, - 203, 116, 63, 7, 240, 222, 82, 86, 11, 149, 167, 72, 224, 55, 190, 66, 201, 65, 168, 184, - 96, 47, 194, 241, 168, 124, 7, 74, 214, 250, 37, 76, 32, 218, 69, 122, 103, 215, 145, 169, - 24, 212, 229, 168, 106, 10, 144, 31, 13, 25, 178, 242, 250, 106, 159, 40, 48, 163, 165, 61, - 130, 57, 146, 4, 73, 32, 254, 233, 125, 135, 212, 29, 111, 4, 177, 114, 15, 210, 170, 82, - 108, 110, 62, 166, 81, 209, 106, 176, 156, 14, 133, 242, 60, 127, 120, 242, 28, 97, 0, 1, - 32, 103, 93, 109, 89, 240, 91, 1, 84, 150, 50, 206, 157, 203, 49, 220, 120, 234, 175, 234, - 150, 126, 225, 94, 163, 164, 199, 138, 114, 62, 99, 106, 112, 1, 32, 171, 40, 220, 82, 241, - 203, 76, 146, 111, 139, 182, 179, 237, 182, 115, 75, 128, 201, 107, 43, 214, 0, 135, 217, - 160, 68, 150, 232, 144, 114, 237, 98, 32, 30, 134, 232, 59, 93, 163, 253, 244, 13, 202, 52, - 147, 168, 83, 121, 123, 95, 21, 210, 209, 225, 223, 143, 49, 10, 205, 238, 1, 22, 83, 81, - 70, 1, 32, 26, 76, 6, 234, 160, 50, 139, 102, 161, 232, 155, 106, 130, 171, 226, 210, 233, - 178, 85, 247, 71, 123, 55, 53, 46, 67, 148, 137, 156, 207, 208, 107, 1, 32, 102, 31, 4, 98, - 110, 156, 144, 61, 229, 140, 198, 84, 196, 238, 128, 35, 131, 182, 137, 125, 241, 95, 69, - 131, 170, 27, 2, 144, 75, 72, 242, 102, 3, 32, 121, 80, 45, 173, 56, 65, 218, 27, 40, 251, - 197, 32, 169, 104, 123, 110, 90, 78, 153, 166, 38, 9, 129, 228, 99, 8, 1, 116, 142, 233, - 162, 69, 32, 216, 169, 159, 116, 95, 12, 63, 176, 195, 6, 183, 123, 135, 75, 61, 112, 106, - 83, 235, 176, 41, 27, 248, 48, 71, 165, 170, 12, 92, 103, 103, 81, 32, 58, 74, 75, 145, - 192, 94, 153, 69, 80, 128, 241, 3, 16, 117, 192, 86, 161, 103, 44, 174, 211, 196, 182, 124, - 55, 11, 107, 142, 49, 88, 6, 41, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 37, 139, 240, 0, 0, - 0, 0, 0, 0, 0, 1, - ]; - - #[tokio::test] - async fn add_peer() { - let (wireguard_data, request_rx) = WireguardGatewayData::new( - Authenticator::default().into(), - Arc::new(KeyPair::new(&mut OsRng)), - ); - let peer_manager = PeerManager::new(wireguard_data); - let (storage, task_manager) = start_controller( - peer_manager.wireguard_gateway_data.peer_tx().clone(), - request_rx, - ); - let peer = Peer::default(); - let ecash_manager = MockEcashManager::new(Box::new(storage.clone())); - - assert!(peer_manager.add_peer(peer.clone()).await.is_err()); - - let client_id = storage - .insert_wireguard_peer(&peer, FromStr::from_str("entry_wireguard").unwrap()) - .await - .unwrap(); - assert!(peer_manager.add_peer(peer.clone()).await.is_err()); - - credential_storage_preparation(Arc::new(ecash_manager), client_id) - .await - .unwrap(); - peer_manager.add_peer(peer.clone()).await.unwrap(); - - stop_controller(task_manager).await; - } - - async fn helper_add_peer( - storage: &Arc>, - peer_manager: &mut PeerManager, - ) -> i64 { - let peer = Peer::default(); - let ecash_manager = MockEcashManager::new(Box::new(storage.clone())); - let client_id = storage - .insert_wireguard_peer(&peer, FromStr::from_str("entry_wireguard").unwrap()) - .await - .unwrap(); - credential_storage_preparation(Arc::new(ecash_manager), client_id) - .await - .unwrap(); - peer_manager.add_peer(peer.clone()).await.unwrap(); - - client_id - } - - #[tokio::test] - async fn remove_peer() { - let (wireguard_data, request_rx) = WireguardGatewayData::new( - Authenticator::default().into(), - Arc::new(KeyPair::new(&mut OsRng)), - ); - let mut peer_manager = PeerManager::new(wireguard_data); - let key = Key::default(); - let public_key = PeerPublicKey::from_str(&key.to_string()).unwrap(); - let (storage, task_manager) = start_controller( - peer_manager.wireguard_gateway_data.peer_tx().clone(), - request_rx, - ); - - helper_add_peer(&storage, &mut peer_manager).await; - peer_manager._remove_peer(public_key).await.unwrap(); - - stop_controller(task_manager).await; - } - - #[tokio::test] - async fn query_peer() { - let (wireguard_data, request_rx) = WireguardGatewayData::new( - Authenticator::default().into(), - Arc::new(KeyPair::new(&mut OsRng)), - ); - let mut peer_manager = PeerManager::new(wireguard_data); - let key = Key::default(); - let public_key = PeerPublicKey::from_str(&key.to_string()).unwrap(); - let (storage, task_manager) = start_controller( - peer_manager.wireguard_gateway_data.peer_tx().clone(), - request_rx, - ); - - assert!(peer_manager.query_peer(public_key).await.unwrap().is_none()); - - helper_add_peer(&storage, &mut peer_manager).await; - let peer = peer_manager.query_peer(public_key).await.unwrap().unwrap(); - assert_eq!(peer.public_key, key); - - stop_controller(task_manager).await; - } - - #[tokio::test] - async fn query_bandwidth() { - let (wireguard_data, request_rx) = WireguardGatewayData::new( - Authenticator::default().into(), - Arc::new(KeyPair::new(&mut OsRng)), - ); - let mut peer_manager = PeerManager::new(wireguard_data); - let key = Key::default(); - let public_key = PeerPublicKey::from_str(&key.to_string()).unwrap(); - let (storage, task_manager) = start_controller( - peer_manager.wireguard_gateway_data.peer_tx().clone(), - request_rx, - ); - - assert!(peer_manager.query_bandwidth(public_key).await.is_err()); - - helper_add_peer(&storage, &mut peer_manager).await; - let available_bandwidth = peer_manager.query_bandwidth(public_key).await.unwrap(); - assert_eq!(available_bandwidth, 0); - - stop_controller(task_manager).await; - } - - #[tokio::test] - async fn query_client_bandwidth() { - let (wireguard_data, request_rx) = WireguardGatewayData::new( - Authenticator::default().into(), - Arc::new(KeyPair::new(&mut OsRng)), - ); - let mut peer_manager = PeerManager::new(wireguard_data); - let key = Key::default(); - let public_key = PeerPublicKey::from_str(&key.to_string()).unwrap(); - let (storage, task_manager) = start_controller( - peer_manager.wireguard_gateway_data.peer_tx().clone(), - request_rx, - ); - - assert!(peer_manager - .query_client_bandwidth(public_key) - .await - .is_err()); - - helper_add_peer(&storage, &mut peer_manager).await; - let available_bandwidth = peer_manager - .query_client_bandwidth(public_key) - .await - .unwrap() - .available() - .await; - assert_eq!(available_bandwidth, 0); - - stop_controller(task_manager).await; - } - - #[tokio::test] - async fn query_verifier() { - let (wireguard_data, request_rx) = WireguardGatewayData::new( - Authenticator::default().into(), - Arc::new(KeyPair::new(&mut OsRng)), - ); - let mut peer_manager = PeerManager::new(wireguard_data); - let key = Key::default(); - let public_key = PeerPublicKey::from_str(&key.to_string()).unwrap(); - let (storage, task_manager) = start_controller( - peer_manager.wireguard_gateway_data.peer_tx().clone(), - request_rx, - ); - let credential = CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap(); - - assert!(peer_manager - .query_verifier_by_key(public_key, credential.clone()) - .await - .is_err()); - - helper_add_peer(&storage, &mut peer_manager).await; - peer_manager - .query_verifier_by_key(public_key, credential) - .await - .unwrap(); - - stop_controller(task_manager).await; - } - - #[tokio::test] - async fn increase_decrease_bandwidth() { - let (wireguard_data, request_rx) = WireguardGatewayData::new( - Authenticator::default().into(), - Arc::new(KeyPair::new(&mut OsRng)), - ); - let mut peer_manager = PeerManager::new(wireguard_data); - let key = Key::default(); - let public_key = PeerPublicKey::from_str(&key.to_string()).unwrap(); - let top_up = 42; - let consume = 4; - let (storage, task_manager) = start_controller( - peer_manager.wireguard_gateway_data.peer_tx().clone(), - request_rx, - ); - - let client_id = helper_add_peer(&storage, &mut peer_manager).await; - let client_bandwidth = peer_manager - .query_client_bandwidth(public_key) - .await - .unwrap(); - - let mut bw_manager = BandwidthStorageManager::new( - Box::new(storage), - client_bandwidth.clone(), - client_id, - Default::default(), - true, - ); - bw_manager - .increase_bandwidth( - Bandwidth::new_unchecked(top_up as u64), - OffsetDateTime::now_utc() - .checked_add(Duration::minutes(1)) - .unwrap(), - ) - .await - .unwrap(); - - assert_eq!(client_bandwidth.available().await, top_up); - assert_eq!( - peer_manager.query_bandwidth(public_key).await.unwrap(), - top_up - ); - - bw_manager.try_use_bandwidth(consume).await.unwrap(); - let remaining = top_up - consume; - assert_eq!(client_bandwidth.available().await, remaining); - assert_eq!( - peer_manager.query_bandwidth(public_key).await.unwrap(), - remaining - ); - - stop_controller(task_manager).await; - } -} diff --git a/gateway/src/node/internal_service_providers/authenticator/seen_credential_cache.rs b/gateway/src/node/internal_service_providers/authenticator/seen_credential_cache.rs index 0c2253f7c5..e06b66760e 100644 --- a/gateway/src/node/internal_service_providers/authenticator/seen_credential_cache.rs +++ b/gateway/src/node/internal_service_providers/authenticator/seen_credential_cache.rs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 #[cfg(test)] -use mock_instant::thread_local::SystemTime; +use mock_instant::thread_local::Instant; #[cfg(not(test))] -use std::time::SystemTime; +use std::time::Instant; use std::{collections::HashMap, time::Duration}; use nym_credentials_interface::CredentialSpendingData; @@ -15,7 +15,7 @@ const SEEN_CREDENTIAL_CACHE_TIME: Duration = Duration::from_secs(60 * 60); // 1 #[derive(Eq, Hash, PartialEq)] struct TimestampedPeerPubKey { peer_pub_key: PeerPublicKey, - timestamp: SystemTime, + timestamp: Instant, } pub(crate) struct SeenCredentialCache { @@ -36,7 +36,7 @@ impl SeenCredentialCache { ) { let value = TimestampedPeerPubKey { peer_pub_key, - timestamp: SystemTime::now(), + timestamp: Instant::now(), }; self.cached_credentials .insert(credential.serial_number_b58(), value); @@ -52,12 +52,9 @@ impl SeenCredentialCache { } pub(crate) fn remove_stale(&mut self) { - let now = SystemTime::now(); + let now = Instant::now(); self.cached_credentials.retain(|_, value| { - let Ok(cache_time) = now.duration_since(value.timestamp) else { - tracing::warn!("Got decreasing consecutive system timestamps"); - return false; - }; + let cache_time = now.duration_since(value.timestamp); cache_time < SEEN_CREDENTIAL_CACHE_TIME }); } @@ -159,30 +156,9 @@ mod test { cache.remove_stale(); assert!(cache.get_peer_pub_key(&credential).is_some()); - MockClock::advance_system_time(SEEN_CREDENTIAL_CACHE_TIME * 2); + MockClock::advance(SEEN_CREDENTIAL_CACHE_TIME * 2); cache.remove_stale(); assert!(cache.get_peer_pub_key(&credential).is_none()); } - - #[test] - fn invalid_time() { - assert!(MockClock::is_thread_local()); - assert!(SystemTime::now().is_thread_local()); - - let mut cache = SeenCredentialCache::new(); - let credential = CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap(); - let peer_pub_key = PeerPublicKey::from_str(PUB_KEY).unwrap(); - - // set some value for time - MockClock::set_system_time(Duration::from_secs(10)); - cache.insert_credential(credential.clone(), peer_pub_key); - - // then set the time in the past - MockClock::set_system_time(Duration::ZERO); - cache.remove_stale(); - - // invalid time should remove the credential, just in case - assert!(cache.get_peer_pub_key(&credential).is_none()); - } } diff --git a/gateway/src/node/lp_listener/data_handler.rs b/gateway/src/node/lp_listener/data_handler.rs index eda6367aa2..2b2ce11c64 100644 --- a/gateway/src/node/lp_listener/data_handler.rs +++ b/gateway/src/node/lp_listener/data_handler.rs @@ -191,7 +191,7 @@ impl LpDataHandler { match action { LpAction::DeliverData(data) => { // Decrypted application data - forward as Sphinx packet - self.forward_sphinx_packet(&data).await?; + self.forward_sphinx_packet(&data.content).await?; inc!("lp_data_packets_forwarded"); Ok(()) } diff --git a/gateway/src/node/lp_listener/handler.rs b/gateway/src/node/lp_listener/handler.rs index 4fa5daad76..c29f9298f1 100644 --- a/gateway/src/node/lp_listener/handler.rs +++ b/gateway/src/node/lp_listener/handler.rs @@ -1,17 +1,17 @@ // Copyright 2025 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use super::messages::LpRegistrationRequest; -use super::registration::process_registration; -use super::LpHandlerState; +use super::{LpHandlerState, ReceiverIndex}; use crate::error::GatewayError; -use nym_lp::serialisation::{lp_bincode_serializer, BincodeOptions}; +use nym_crypto::asymmetric::{ed25519, x25519}; +use nym_lp::state_machine::{LpAction, LpData, LpDataKind, LpInput}; use nym_lp::{ - codec::OuterAeadKey, keypair::PublicKey, message::ForwardPacketData, packet::LpHeader, - LpMessage, LpPacket, OuterHeader, + codec::OuterAeadKey, message::ForwardPacketData, packet::LpHeader, LpMessage, LpPacket, + OuterHeader, }; use nym_lp_transport::traits::LpTransport; use nym_metrics::{add_histogram_obs, inc}; +use nym_registration_common::{LpRegistrationRequest, RegistrationStatus}; use std::net::SocketAddr; use std::time::Duration; use tokio::io::{AsyncReadExt, AsyncWriteExt}; @@ -290,31 +290,15 @@ where async fn handle_client_hello(&mut self, packet: LpPacket) -> Result<(), GatewayError> { use nym_lp::packet::LpHeader; use nym_lp::state_machine::{LpInput, LpStateMachine}; + let remote = self.remote_addr; + + // if we managed to parse out the header, i.e. we have code supporting whatever + // protocol version has been sent - use that one instead + let protocol_version = packet.header().protocol_version; // Extract ClientHello data - let (receiver_index, client_ed25519_pubkey, salt) = match packet.message() { - LpMessage::ClientHello(hello_data) => { - // Validate timestamp - let timestamp = hello_data.extract_timestamp(); - Self::validate_timestamp( - timestamp, - self.state.lp_config.debug.timestamp_tolerance, - )?; - - // Extract client-proposed receiver_index - let receiver_index = hello_data.receiver_index; - - let client_ed25519_pubkey = nym_crypto::asymmetric::ed25519::PublicKey::from_bytes( - &hello_data.client_ed25519_public_key, - ) - .map_err(|e| { - GatewayError::LpProtocolError( - format!("Invalid client Ed25519 public key: {e}",), - ) - })?; - - (receiver_index, client_ed25519_pubkey, hello_data.salt) - } + let hello_data = match packet.message() { + LpMessage::ClientHello(hello_data) => hello_data, other => { inc!("lp_client_hello_failed"); return Err(GatewayError::LpProtocolError(format!( @@ -323,28 +307,33 @@ where } }; - debug!( - "Processing ClientHello from {} (proposed receiver_index={})", - self.remote_addr, receiver_index - ); + // Validate timestamp + let timestamp = hello_data.extract_timestamp(); + Self::validate_timestamp(timestamp, self.state.lp_config.debug.timestamp_tolerance)?; + + // Extract client-proposed receiver_index + let receiver_index = hello_data.receiver_index; + + // let client_ed25519_pubkey = hello_data.client_ed25519_public_key; + + debug!("Processing ClientHello from {remote} (proposed receiver_index={receiver_index})",); // Collision check for client-proposed receiver_index // Check both handshake_states (in-progress) and session_states (established) if self.state.handshake_states.contains_key(&receiver_index) || self.state.session_states.contains_key(&receiver_index) { - warn!( - "Receiver index collision: {} from {}", - receiver_index, self.remote_addr - ); + warn!("Receiver index collision: {receiver_index} from {remote}",); inc!("lp_receiver_index_collision"); // Send Collision response to tell client to retry with new receiver_index // No outer key - this is before PSK derivation // Note: Do NOT set binding on collision - client may retry with new receiver_index - let collision_packet = - LpPacket::new(LpHeader::new(receiver_index, 0), LpMessage::Collision); - self.send_lp_packet(&collision_packet, None).await?; + let collision_packet = LpPacket::new( + LpHeader::new(receiver_index, 0, protocol_version), + LpMessage::Collision, + ); + self.send_lp_packet(collision_packet, None).await?; return Ok(()); } @@ -352,32 +341,23 @@ where // Collision check passed - bind this connection to the receiver_index // All subsequent packets on this connection must use this receiver_index self.bound_receiver_idx = Some(receiver_index); - trace!( - "Bound connection from {} to receiver_idx={} (via ClientHello)", - self.remote_addr, - receiver_index - ); + trace!("Bound connection from {remote} to receiver_idx={receiver_index} (via ClientHello)",); // Create state machine for this handshake using client-proposed receiver_index let mut state_machine = LpStateMachine::new( receiver_index, false, // responder - ( - self.state.local_identity.private_key(), - self.state.local_identity.public_key(), - ), - &client_ed25519_pubkey, - &salt, + self.state.local_lp_peer.clone(), + hello_data.to_remote_peer(), + &hello_data.salt, + protocol_version, ) .map_err(|e| { inc!("lp_client_hello_failed"); GatewayError::LpHandshakeError(format!("Failed to create state machine: {}", e)) })?; - debug!( - "Created handshake state for {} (receiver_index={})", - self.remote_addr, receiver_index - ); + debug!("Created handshake state for {remote} (receiver_index={receiver_index})",); // Transition state machine to KKTExchange (responder waits for client's KKT request) // For responder, StartHandshake returns None (just transitions state) @@ -385,8 +365,7 @@ where if let Some(Err(e)) = state_machine.process_input(LpInput::StartHandshake) { inc!("lp_client_hello_failed"); return Err(GatewayError::LpHandshakeError(format!( - "StartHandshake failed: {}", - e + "StartHandshake failed: {e}", ))); // Responder (gateway) gets Ok but no packet to send - we just wait for client's next packet } @@ -397,14 +376,16 @@ where .insert(receiver_index, super::TimestampedState::new(state_machine)); debug!( - "Stored handshake state for {} (receiver_index={}) - waiting for KKT request", - self.remote_addr, receiver_index + "Stored handshake state for {remote} (receiver_index={receiver_index}) - waiting for KKT request", ); // Send Ack to confirm ClientHello received // No outer key - this is before PSK derivation - let ack_packet = LpPacket::new(LpHeader::new(receiver_index, 0), LpMessage::Ack); - self.send_lp_packet(&ack_packet, None).await?; + let ack_packet = LpPacket::new( + LpHeader::new(receiver_index, 0, protocol_version), + LpMessage::Ack, + ); + self.send_lp_packet(ack_packet, None).await?; Ok(()) } @@ -415,8 +396,6 @@ where receiver_idx: u32, packet: LpPacket, ) -> Result<(), GatewayError> { - use nym_lp::state_machine::{LpAction, LpInput}; - debug!( "Processing handshake packet from {} (receiver_idx={})", self.remote_addr, receiver_idx @@ -462,13 +441,15 @@ where self.remote_addr, receiver_idx ); + let session = state_entry.value().state.session().map_err(|err| { + GatewayError::LpHandshakeError(format!("no session available: {err}")) + })?; + // Get outer key for Ack encryption before releasing borrow - let outer_key = state_entry - .value() - .state - .session() - .ok() - .and_then(|s| s.outer_aead_key()); + let outer_key = session.outer_aead_key(); + + // Get previously negotiated protocol version for header creation + let negotiated_version = session.negotiated_version(); // Move state machine to session_states (already in Transport state) // We keep the state machine (not just session) to enable @@ -492,7 +473,10 @@ where inc!("lp_handshakes_success"); // Send Ack to confirm handshake completion to the client - let ack_packet = LpPacket::new(LpHeader::new(receiver_idx, 0), LpMessage::Ack); + let ack_packet = LpPacket::new( + LpHeader::new(receiver_idx, 0, negotiated_version), + LpMessage::Ack, + ); trace!( "Moved session {} to transport mode, sending Ack", receiver_idx @@ -508,7 +492,7 @@ where // Send response packet if needed if let Some((packet, outer_key)) = should_send { - self.send_lp_packet(&packet, outer_key.as_ref()).await?; + self.send_lp_packet(packet, outer_key.as_ref()).await?; trace!( "Sent handshake response to {} (encrypted={})", self.remote_addr, @@ -565,16 +549,12 @@ where })? .map_err(|e| GatewayError::LpProtocolError(format!("State machine error: {}", e)))?; + let lp_session = state_machine.session().map_err(|e| { + GatewayError::LpProtocolError(format!("Session unavailable after processing: {}", e)) + })?; + // Get outer key before releasing borrow - let outer_key = state_machine - .session() - .map_err(|e| { - GatewayError::LpProtocolError(format!( - "Session unavailable after processing: {}", - e - )) - })? - .outer_aead_key(); + let outer_key = lp_session.outer_aead_key(); drop(state_entry); match action { @@ -586,14 +566,13 @@ where self.remote_addr, receiver_idx ); inc!("lp_subsession_kk2_sent"); - self.send_lp_packet(&response_packet, outer_key.as_ref()) + self.send_lp_packet(response_packet, outer_key.as_ref()) .await?; Ok(()) } LpAction::DeliverData(data) => { // Decrypted application data - process as registration/forwarding - self.handle_decrypted_payload(receiver_idx, data.to_vec()) - .await + self.handle_decrypted_payload(receiver_idx, data).await } LpAction::SubsessionComplete { packet: ready_packet, @@ -627,42 +606,41 @@ where async fn handle_decrypted_payload( &mut self, receiver_idx: u32, - decrypted_bytes: Vec, + decrypted_data: LpData, ) -> Result<(), GatewayError> { let remote = self.remote_addr; - // Try to deserialize as LpRegistrationRequest first (most common case after handshake) - if let Ok(request) = - lp_bincode_serializer().deserialize::(&decrypted_bytes) - { - debug!( - "LP registration request from {remote} (receiver_idx={receiver_idx}): mode={:?}", - request.mode - ); - return self - .handle_registration_request(receiver_idx, request) - .await; - } + let bytes = decrypted_data.content; + match decrypted_data.kind { + LpDataKind::Registration => { + let request = LpRegistrationRequest::try_deserialise(&bytes).map_err(|err| { + GatewayError::LpProtocolError(format!("malformed LpRegistrationRequest: {err}")) + })?; - // Try to deserialize as ForwardPacketData (entry gateway forwarding to exit) - if let Ok(forward_data) = - lp_bincode_serializer().deserialize::(&decrypted_bytes) - { - debug!( - "LP forward request from {remote} (receiver_idx={receiver_idx}) to {}", - forward_data.target_lp_address - ); - return self - .handle_forwarding_request(receiver_idx, forward_data) - .await; - } + debug!( + "LP registration request from {remote} (receiver_idx={receiver_idx}): mode={:?}", + request.mode()); - // Neither registration nor forwarding - unknown payload type - warn!("Unknown transport payload type from {remote} (receiver_idx={receiver_idx})"); - inc!("lp_errors_unknown_payload_type"); - Err(GatewayError::LpProtocolError( - "Unknown transport payload type (not registration or forwarding)".to_string(), - )) + self.handle_registration_request(receiver_idx, request) + .await + } + LpDataKind::Forward => { + let forward_data = ForwardPacketData::decode(&bytes).map_err(|err| { + GatewayError::LpProtocolError(format!("malformed ForwardPacketData: {err}")) + })?; + + self.handle_forwarding_request(receiver_idx, forward_data) + .await + } + LpDataKind::Opaque => { + // Neither registration nor forwarding - unknown payload type + warn!("Unknown transport payload type from {remote} (receiver_idx={receiver_idx}). dropping {} bytes", bytes.len()); + inc!("lp_errors_unknown_payload_type"); + Err(GatewayError::LpProtocolError( + "Unknown transport payload type (not registration or forwarding)".to_string(), + )) + } + } } /// Handle subsession completion - promote subsession to new session @@ -689,7 +667,7 @@ where // Send SubsessionReady packet if present (for initiator - gateway is responder, so typically None) if let Some(packet) = ready_packet { - self.send_lp_packet(&packet, outer_key.as_ref()).await?; + self.send_lp_packet(packet, outer_key.as_ref()).await?; } // Create new state machine from completed subsession @@ -733,60 +711,80 @@ where Ok(()) } + /// Attempt to wrap and send specified response back to the client + async fn send_response_packet( + &mut self, + receiver_idx: u32, + serialised_response: Vec, + response_kind: LpDataKind, + ) -> Result<(), GatewayError> { + let session_entry = self + .state + .session_states + .get(&receiver_idx) + .ok_or_else(|| { + GatewayError::LpProtocolError(format!("Session not found: {receiver_idx}")) + })?; + + // Access session via state machine for subsession support + let session = session_entry + .value() + .state + .session() + .map_err(|e| GatewayError::LpProtocolError(format!("Session error: {e}")))?; + + let wrapped_lp_data = LpData::new(response_kind, serialised_response); + let data_bytes = wrapped_lp_data.to_vec(); + + let encrypted_message = session.encrypt_data(&data_bytes).map_err(|e| { + GatewayError::LpProtocolError(format!("Failed to encrypt response: {e}")) + })?; + + let response_packet = session.next_packet(encrypted_message).map_err(|e| { + GatewayError::LpProtocolError(format!("Failed to create response packet: {e}")) + })?; + + let outer_key = session.outer_aead_key(); + drop(session_entry); + + // Send response (encrypted with outer AEAD) + self.send_lp_packet(response_packet, outer_key.as_ref()) + .await?; + Ok(()) + } + /// Handle registration request on an established session async fn handle_registration_request( &mut self, - receiver_idx: u32, + receiver_idx: ReceiverIndex, request: LpRegistrationRequest, ) -> Result<(), GatewayError> { // Process registration (might modify state) - let response = process_registration(request, &self.state).await; + let response = self.state.process_registration(receiver_idx, request).await; + let response_bytes = response.serialise().map_err(|e| { + GatewayError::LpProtocolError(format!("Failed to serialize response: {e}")) + })?; - // Acquire session lock for encryption and get outer AEAD key - let (response_packet, outer_key) = { - let session_entry = self - .state - .session_states - .get(&receiver_idx) - .ok_or_else(|| { - GatewayError::LpProtocolError(format!("Session not found: {}", receiver_idx)) - })?; - // Access session via state machine for subsession support - let session = session_entry - .value() - .state - .session() - .map_err(|e| GatewayError::LpProtocolError(format!("Session error: {}", e)))?; - - // Serialize and encrypt response - let response_bytes = lp_bincode_serializer().serialize(&response).map_err(|e| { - GatewayError::LpProtocolError(format!("Failed to serialize response: {}", e)) - })?; - - let encrypted_message = session.encrypt_data(&response_bytes).map_err(|e| { - GatewayError::LpProtocolError(format!("Failed to encrypt response: {}", e)) - })?; - - let packet = session.next_packet(encrypted_message).map_err(|e| { - GatewayError::LpProtocolError(format!("Failed to create response packet: {}", e)) - })?; - - // Get outer AEAD key for packet encryption - let outer_key = session.outer_aead_key(); - (packet, outer_key) - }; - - // Send response (encrypted with outer AEAD) - self.send_lp_packet(&response_packet, outer_key.as_ref()) + self.send_response_packet(receiver_idx, response_bytes, LpDataKind::Registration) .await?; - if response.success { - info!("LP registration successful for {})", self.remote_addr); - } else { - warn!( - "LP registration failed for {}: {:?}", - self.remote_addr, response.error - ); + match response.status { + RegistrationStatus::Completed => { + info!("LP registration successful for {}", self.remote_addr); + } + RegistrationStatus::Failed => { + warn!( + "LP registration failed for {}: {:?}", + self.remote_addr, + response.error_message() + ); + } + RegistrationStatus::PendingMoreData => { + info!( + "we required more deta from {} to complete registration", + self.remote_addr + ); + } } Ok(()) @@ -801,40 +799,10 @@ where receiver_idx: u32, forward_data: ForwardPacketData, ) -> Result<(), GatewayError> { - // Forward the packet to the target gateway + // Forward the packet to the target gateway and retrieve its response let response_bytes = self.handle_forward_packet(forward_data).await?; - // Encrypt response for client and get outer AEAD key - let (response_packet, outer_key) = { - let session_entry = self - .state - .session_states - .get(&receiver_idx) - .ok_or_else(|| { - GatewayError::LpProtocolError(format!("Session not found: {}", receiver_idx)) - })?; - // Access session via state machine for subsession support - let session = session_entry - .value() - .state - .session() - .map_err(|e| GatewayError::LpProtocolError(format!("Session error: {}", e)))?; - - let encrypted_message = session.encrypt_data(&response_bytes).map_err(|e| { - GatewayError::LpProtocolError(format!("Failed to encrypt forward response: {}", e)) - })?; - - let packet = session.next_packet(encrypted_message).map_err(|e| { - GatewayError::LpProtocolError(format!("Failed to create response packet: {}", e)) - })?; - - // Get outer AEAD key for packet encryption - let outer_key = session.outer_aead_key(); - (packet, outer_key) - }; - - // Send encrypted response to client (encrypted with outer AEAD) - self.send_lp_packet(&response_packet, outer_key.as_ref()) + self.send_response_packet(receiver_idx, response_bytes, LpDataKind::Forward) .await?; debug!( @@ -902,14 +870,7 @@ where #[allow(dead_code)] async fn receive_client_hello( &mut self, - ) -> Result< - ( - PublicKey, - nym_crypto::asymmetric::ed25519::PublicKey, - [u8; 32], - ), - GatewayError, - > { + ) -> Result<(x25519::PublicKey, ed25519::PublicKey, [u8; 32]), GatewayError> { // Receive first packet which should be ClientHello (no outer encryption) let (raw_bytes, _header) = self.receive_raw_packet().await?; let packet = nym_lp::codec::parse_lp_packet(&raw_bytes, None) @@ -936,22 +897,11 @@ where self.state.lp_config.debug.timestamp_tolerance.as_secs() ); - // Convert bytes to X25519 PublicKey (for Noise protocol) - let client_pubkey = PublicKey::from_bytes(&hello_data.client_lp_public_key) - .map_err(|e| { - GatewayError::LpProtocolError(format!("Invalid client public key: {}", e)) - })?; + // Retrieve X25519 PublicKey (for Noise protocol) + let client_pubkey = hello_data.client_lp_public_key; - // Convert bytes to Ed25519 PublicKey (for PSQ authentication) - let client_ed25519_pubkey = nym_crypto::asymmetric::ed25519::PublicKey::from_bytes( - &hello_data.client_ed25519_public_key, - ) - .map_err(|e| { - GatewayError::LpProtocolError(format!( - "Invalid client Ed25519 public key: {}", - e - )) - })?; + // Retrieve Ed25519 PublicKey (for PSQ authentication) + let client_ed25519_pubkey = hello_data.client_ed25519_public_key; // Extract salt for PSK derivation let salt = hello_data.salt; @@ -1052,10 +1002,7 @@ where let start = std::time::Instant::now(); // Parse target gateway address - let target_addr: SocketAddr = forward_data.target_lp_address.parse().map_err(|e| { - inc!("lp_forward_failed"); - GatewayError::LpProtocolError(format!("Invalid target address: {}", e)) - })?; + let target_addr = forward_data.target_lp_address; // Check if we need to open a new connection let need_new_connection = match &self.exit_stream { @@ -1223,7 +1170,7 @@ where /// * `outer_key` - Optional outer AEAD key for encryption (None for cleartext, Some for encrypted) async fn send_lp_packet( &mut self, - packet: &LpPacket, + packet: LpPacket, outer_key: Option<&OuterAeadKey>, ) -> Result<(), GatewayError> { use bytes::BytesMut; @@ -1231,7 +1178,7 @@ where // Serialize the packet (encrypted if outer_key provided) let mut packet_buf = BytesMut::new(); - serialize_lp_packet(packet, &mut packet_buf, outer_key).map_err(|e| { + serialize_lp_packet(&packet, &mut packet_buf, outer_key).map_err(|e| { GatewayError::LpProtocolError(format!("Failed to serialize packet: {}", e)) })?; @@ -1299,10 +1246,10 @@ mod tests { use nym_lp::codec::{parse_lp_packet, serialize_lp_packet}; use nym_lp::message::{ClientHelloData, EncryptedDataPayload, HandshakeData, LpMessage}; use nym_lp::packet::{LpHeader, LpPacket}; + use nym_lp::peer::LpLocalPeer; use std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; use tokio::io::{AsyncRead, AsyncReadExt, AsyncWriteExt}; - // ==================== Test Helpers ==================== /// Create a minimal test state for handler tests @@ -1333,20 +1280,24 @@ mod tests { // Create mix forwarding channel (unused in tests but required by struct) let (mix_sender, _mix_receiver) = nym_mixnet_client::forwarder::mix_forwarding_channels(); + let id_keys = Arc::new(ed25519::KeyPair::new(&mut OsRng)); + let x_keys = Arc::new(id_keys.to_x25519()); + + let lp_peer = LpLocalPeer::new(id_keys, x_keys.clone()).with_kem_psq_key(x_keys); + LpHandlerState { lp_config, ecash_verifier: Arc::new(ecash_verifier) as Arc, storage, - local_identity: Arc::new(ed25519::KeyPair::new(&mut OsRng)), + local_lp_peer: lp_peer, metrics: nym_node_metrics::NymNodeMetrics::default(), active_clients_store: ActiveClientsStore::new(), - wg_peer_controller: None, - wireguard_data: None, outbound_mix_sender: mix_sender, handshake_states: Arc::new(dashmap::DashMap::new()), session_states: Arc::new(dashmap::DashMap::new()), forward_semaphore, + peer_registrator: None, } } @@ -1525,7 +1476,7 @@ mod tests { let packet = LpPacket::new( LpHeader { protocol_version: 1, - reserved: 0, + reserved: [0u8; 3], receiver_idx: 42, counter: 0, }, @@ -1591,13 +1542,13 @@ mod tests { let packet = LpPacket::new( LpHeader { protocol_version: 1, - reserved: 0, + reserved: [0u8; 3], receiver_idx: 99, counter: 5, }, LpMessage::Busy, ); - handler.send_lp_packet(&packet, None).await + handler.send_lp_packet(packet, None).await }); let mut client_stream = TcpStream::connect(addr).await.unwrap(); @@ -1631,13 +1582,13 @@ mod tests { let packet = LpPacket::new( LpHeader { protocol_version: 1, - reserved: 0, + reserved: [0u8; 3], receiver_idx: 100, counter: 10, }, LpMessage::Handshake(HandshakeData(handshake_data)), ); - handler.send_lp_packet(&packet, None).await + handler.send_lp_packet(packet, None).await }); let mut client_stream = TcpStream::connect(addr).await.unwrap(); @@ -1672,13 +1623,13 @@ mod tests { let packet = LpPacket::new( LpHeader { protocol_version: 1, - reserved: 0, + reserved: [0u8; 3], receiver_idx: 200, counter: 20, }, LpMessage::EncryptedData(EncryptedDataPayload(encrypted_payload)), ); - handler.send_lp_packet(&packet, None).await + handler.send_lp_packet(packet, None).await }); let mut client_stream = TcpStream::connect(addr).await.unwrap(); @@ -1710,8 +1661,13 @@ mod tests { let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); let addr = listener.local_addr().unwrap(); - let client_key = [7u8; 32]; - let client_ed25519_key = [8u8; 32]; + let mut rng = rand::thread_rng(); + let ed25519 = ed25519::KeyPair::new(&mut rng); + let x25519 = ed25519.to_x25519(); + + let client_key = *x25519.public_key(); + let client_ed25519_key = *ed25519.public_key(); + let hello_data = ClientHelloData::new_with_fresh_salt(client_key, client_ed25519_key, timestamp); let expected_salt = hello_data.salt; // Clone salt before moving hello_data @@ -1724,13 +1680,13 @@ mod tests { let packet = LpPacket::new( LpHeader { protocol_version: 1, - reserved: 0, + reserved: [0u8; 3], receiver_idx: 300, counter: 30, }, LpMessage::ClientHello(hello_data), ); - handler.send_lp_packet(&packet, None).await + handler.send_lp_packet(packet, None).await }); let mut client_stream = TcpStream::connect(addr).await.unwrap(); @@ -1782,14 +1738,14 @@ mod tests { let client_x25519_public = client_ed25519_keypair.public_key().to_x25519().unwrap(); let hello_data = ClientHelloData::new_with_fresh_salt( - client_x25519_public.to_bytes(), - client_ed25519_keypair.public_key().to_bytes(), + client_x25519_public, + *client_ed25519_keypair.public_key(), timestamp, ); let packet = LpPacket::new( LpHeader { protocol_version: 1, - reserved: 0, + reserved: [0u8; 3], receiver_idx: 0, counter: 0, }, @@ -1843,8 +1799,8 @@ mod tests { let client_x25519_public = client_ed25519_keypair.public_key().to_x25519().unwrap(); let mut hello_data = ClientHelloData::new_with_fresh_salt( - client_x25519_public.to_bytes(), - client_ed25519_keypair.public_key().to_bytes(), + client_x25519_public, + *client_ed25519_keypair.public_key(), timestamp, ); @@ -1859,7 +1815,7 @@ mod tests { let packet = LpPacket::new( LpHeader { protocol_version: 1, - reserved: 0, + reserved: [0u8; 3], receiver_idx: 0, counter: 0, }, diff --git a/gateway/src/node/lp_listener/messages.rs b/gateway/src/node/lp_listener/messages.rs deleted file mode 100644 index 4dc60af0d2..0000000000 --- a/gateway/src/node/lp_listener/messages.rs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2025 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only - -//! LP registration message types. -//! -//! Re-exports shared message types from nym-registration-common. - -pub use nym_registration_common::{ - LpGatewayData, LpRegistrationRequest, LpRegistrationResponse, RegistrationMode, -}; diff --git a/gateway/src/node/lp_listener/mod.rs b/gateway/src/node/lp_listener/mod.rs index 0e1a948e7a..367e16e9bf 100644 --- a/gateway/src/node/lp_listener/mod.rs +++ b/gateway/src/node/lp_listener/mod.rs @@ -68,10 +68,11 @@ // They can be exported via Prometheus format using the metrics endpoint. use crate::error::GatewayError; +use crate::node::wireguard::PeerRegistrator; use crate::node::ActiveClientsStore; use dashmap::DashMap; use nym_config::serde_helpers::de_maybe_port; -use nym_crypto::asymmetric::ed25519; +use nym_credential_verification::ecash::traits::EcashManager; use nym_gateway_storage::GatewayStorage; use nym_lp::state_machine::LpStateMachine; use nym_node_metrics::NymNodeMetrics; @@ -80,9 +81,10 @@ use std::net::{IpAddr, Ipv6Addr, SocketAddr}; use std::sync::Arc; use std::time::Duration; use tokio::net::TcpListener; -use tokio::sync::{mpsc, Semaphore}; +use tokio::sync::Semaphore; use tracing::*; +pub use nym_lp::peer::LpLocalPeer; pub use nym_mixnet_client::forwarder::{ mix_forwarding_channels, MixForwardingReceiver, MixForwardingSender, }; @@ -90,9 +92,10 @@ pub use nym_wireguard::{PeerControlRequest, WireguardGatewayData}; mod data_handler; pub mod handler; -mod messages; mod registration; +pub type ReceiverIndex = u32; + /// Configuration for LP listener #[derive(Debug, Clone, Copy, serde::Deserialize, serde::Serialize)] #[serde(default)] @@ -112,7 +115,7 @@ pub struct LpConfig { pub announce_control_port: Option, /// Custom announced port for listening for the UDP LP data traffic. - /// If unspecified, the value from the `data_bind_address` will be used instead + /// If unspecified, the value from the `data_bind_address` will be used instead /// (default: None) #[serde(deserialize_with = "de_maybe_port")] pub announce_data_port: Option, @@ -312,12 +315,12 @@ impl TimestampedState { } /// Get age since creation - pub fn age(&self) -> std::time::Duration { + pub fn age(&self) -> Duration { self.created_at.elapsed() } /// Get time since last activity - pub fn since_activity(&self) -> std::time::Duration { + pub fn since_activity(&self) -> Duration { let now = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap_or_default() @@ -333,14 +336,13 @@ impl TimestampedState { #[derive(Clone)] pub struct LpHandlerState { /// Ecash verifier for bandwidth credentials - pub ecash_verifier: - Arc, + pub ecash_verifier: Arc, /// Storage backend for persistence pub storage: GatewayStorage, - /// Gateway's identity keypair - pub local_identity: Arc, + /// Encapsulates all required key information of a local Lewes Protocol Peer. + pub local_lp_peer: LpLocalPeer, /// Metrics collection pub metrics: NymNodeMetrics, @@ -348,11 +350,8 @@ pub struct LpHandlerState { /// Active clients tracking pub active_clients_store: ActiveClientsStore, - /// WireGuard peer controller channel (for dVPN registrations) - pub wg_peer_controller: Option>, - - /// WireGuard gateway data (contains keypair and config) - pub wireguard_data: Option, + /// Handle registering new wireguard peers + pub peer_registrator: Option, /// LP configuration (for timestamp validation, etc.) pub lp_config: LpConfig, @@ -370,7 +369,7 @@ pub struct LpHandlerState { /// state moves to session_states map. /// /// Wrapped in TimestampedState for TTL-based cleanup of stale handshakes. - pub handshake_states: Arc>>, + pub handshake_states: Arc>>, /// Established sessions keyed by session_id /// @@ -384,7 +383,7 @@ pub struct LpHandlerState { /// subsession/rekeying support. The state machine handles subsession initiation /// (SubsessionKK1/KK2/Ready) during transport phase, allowing long-lived connections /// to rekey without re-authentication. - pub session_states: Arc>>, + pub session_states: Arc>>, /// Semaphore limiting concurrent forward connections /// @@ -577,13 +576,10 @@ impl LpListener { ); self.shutdown.try_spawn_named( - Self::cleanup_loop( + cleanup_task::cleanup_loop( handshake_states, session_states, - handshake_ttl, - session_ttl, - demoted_session_ttl, - interval, + dbg_cfg, shutdown, metrics, ), @@ -591,104 +587,6 @@ impl LpListener { ) } - /// Background loop for cleaning up stale state entries - /// - /// Runs periodically to scan handshake_states and session_states maps, - /// removing entries that have exceeded their TTL. - /// - /// Demoted sessions (ReadOnlyTransport) use shorter TTL since they - /// only need to drain in-flight packets after subsession promotion. - #[allow(clippy::too_many_arguments)] - async fn cleanup_loop( - handshake_states: Arc>>, - session_states: Arc>>, - handshake_ttl: Duration, - session_ttl: Duration, - demoted_session_ttl: Duration, - interval: Duration, - shutdown: nym_task::ShutdownToken, - _metrics: NymNodeMetrics, - ) { - use nym_lp::state_machine::LpStateBare; - use nym_metrics::inc_by; - - let mut cleanup_interval = tokio::time::interval(interval); - - loop { - tokio::select! { - biased; - - _ = shutdown.cancelled() => { - debug!("LP state cleanup task: received shutdown signal"); - break; - } - - _ = cleanup_interval.tick() => { - let start = std::time::Instant::now(); - let mut hs_removed = 0u64; - let mut ss_removed = 0u64; - let mut demoted_removed = 0u64; - - // Remove stale handshakes (based on age since creation) - handshake_states.retain(|_, timestamped| { - if timestamped.age() > handshake_ttl { - hs_removed += 1; - false - } else { - true - } - }); - - // Remove stale sessions (based on time since last activity) - // Use shorter TTL for demoted (ReadOnlyTransport) sessions - session_states.retain(|_, timestamped| { - let is_demoted = timestamped.state.bare_state() == LpStateBare::ReadOnlyTransport; - let ttl = if is_demoted { - demoted_session_ttl - } else { - session_ttl - }; - - if timestamped.since_activity() > ttl { - if is_demoted { - demoted_removed += 1; - } else { - ss_removed += 1; - } - false - } else { - true - } - }); - - if hs_removed > 0 || ss_removed > 0 || demoted_removed > 0 { - let duration = start.elapsed(); - info!( - "LP state cleanup: removed {} handshakes, {} sessions, {} demoted (took {:.3}s)", - hs_removed, - ss_removed, - demoted_removed, - duration.as_secs_f64() - ); - - // Track metrics - if hs_removed > 0 { - inc_by!("lp_states_cleanup_handshake_removed", hs_removed as i64); - } - if ss_removed > 0 { - inc_by!("lp_states_cleanup_session_removed", ss_removed as i64); - } - if demoted_removed > 0 { - inc_by!("lp_states_cleanup_demoted_removed", demoted_removed as i64); - } - } - } - } - } - - info!("LP state cleanup task shutdown complete"); - } - fn active_lp_connections(&self) -> usize { self.handler_state .metrics @@ -696,3 +594,114 @@ impl LpListener { .active_lp_connections_count() } } + +pub(crate) mod cleanup_task { + use crate::node::lp_listener::{LpDebug, TimestampedState}; + use dashmap::DashMap; + use nym_lp::state_machine::LpStateBare; + use nym_lp::LpStateMachine; + use nym_metrics::inc_by; + use nym_node_metrics::NymNodeMetrics; + use std::sync::Arc; + use tracing::{debug, info}; + + async fn perform_cleanup( + handshake_states: &Arc>>, + session_states: &Arc>>, + cfg: LpDebug, + ) { + let handshake_ttl = cfg.handshake_ttl; + let session_ttl = cfg.session_ttl; + let demoted_session_ttl = cfg.demoted_session_ttl; + + let start = std::time::Instant::now(); + let mut hs_removed = 0u64; + let mut ss_removed = 0u64; + let mut demoted_removed = 0u64; + + // Remove stale handshakes (based on age since creation) + handshake_states.retain(|_, timestamped| { + if timestamped.age() > handshake_ttl { + hs_removed += 1; + false + } else { + true + } + }); + + // Remove stale sessions (based on time since last activity) + // Use shorter TTL for demoted (ReadOnlyTransport) sessions + session_states.retain(|_, timestamped| { + let is_demoted = timestamped.state.bare_state() == LpStateBare::ReadOnlyTransport; + let ttl = if is_demoted { + demoted_session_ttl + } else { + session_ttl + }; + + if timestamped.since_activity() > ttl { + if is_demoted { + demoted_removed += 1; + } else { + ss_removed += 1; + } + false + } else { + true + } + }); + + if hs_removed > 0 || ss_removed > 0 || demoted_removed > 0 { + let duration = start.elapsed(); + info!( + "LP state cleanup: removed {hs_removed} handshakes, {ss_removed} sessions, {demoted_removed} demoted (took {:.3}s)", + duration.as_secs_f64() + ); + + // Track metrics + if hs_removed > 0 { + inc_by!("lp_states_cleanup_handshake_removed", hs_removed as i64); + } + if ss_removed > 0 { + inc_by!("lp_states_cleanup_session_removed", ss_removed as i64); + } + if demoted_removed > 0 { + inc_by!("lp_states_cleanup_demoted_removed", demoted_removed as i64); + } + } + } + + /// Background loop for cleaning up stale state entries + /// + /// Runs periodically to scan handshake_states and session_states maps, + /// removing entries that have exceeded their TTL. + /// + /// Demoted sessions (ReadOnlyTransport) use shorter TTL since they + /// only need to drain in-flight packets after subsession promotion. + pub(crate) async fn cleanup_loop( + handshake_states: Arc>>, + session_states: Arc>>, + cfg: LpDebug, + shutdown: nym_task::ShutdownToken, + _metrics: NymNodeMetrics, + ) { + let interval = cfg.state_cleanup_interval; + + let mut cleanup_interval = tokio::time::interval(interval); + + loop { + tokio::select! { + biased; + _ = shutdown.cancelled() => { + debug!("LP state cleanup task: received shutdown signal"); + break; + } + _ = cleanup_interval.tick() => { + perform_cleanup(&handshake_states, &session_states, cfg).await; + } + } + } + + info!("LP state cleanup task shutdown complete"); + } +} diff --git a/gateway/src/node/lp_listener/registration.rs b/gateway/src/node/lp_listener/registration.rs index 92ae86516e..7f2e7b875c 100644 --- a/gateway/src/node/lp_listener/registration.rs +++ b/gateway/src/node/lp_listener/registration.rs @@ -1,30 +1,17 @@ // Copyright 2025 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use super::messages::{ - LpGatewayData, LpRegistrationRequest, LpRegistrationResponse, RegistrationMode, +use crate::node::lp_listener::{LpHandlerState, ReceiverIndex}; +use nym_metrics::{add_histogram_obs, inc}; +use nym_registration_common::dvpn::{ + LpDvpnRegistrationFinalisation, LpDvpnRegistrationInitialRequest, + LpDvpnRegistrationRequestMessage, LpDvpnRegistrationRequestMessageContent, }; -use super::LpHandlerState; -use crate::error::GatewayError; -use crate::node::client_handling::websocket::message_receiver::IsActive; -use defguard_wireguard_rs::host::Peer; -use defguard_wireguard_rs::key::Key; -use futures::channel::{mpsc, oneshot}; -use nym_credential_verification::ecash::traits::EcashManager; -use nym_credential_verification::{ - bandwidth_storage_manager::BandwidthStorageManager, BandwidthFlushingBehaviourConfig, - ClientBandwidth, CredentialVerifier, +use nym_registration_common::mixnet::LpMixnetRegistrationRequestMessage; +use nym_registration_common::{ + LpRegistrationRequest, LpRegistrationRequestData, LpRegistrationResponse, RegistrationMode, + RegistrationStatus, }; -use nym_credentials_interface::CredentialSpendingData; -use nym_crypto::asymmetric::ed25519; -use nym_gateway_requests::models::CredentialSpendingRequest; -use nym_gateway_storage::models::PersistedBandwidth; -use nym_gateway_storage::traits::BandwidthGatewayStorage; -use nym_metrics::{add_histogram_obs, inc, inc_by}; -use nym_registration_common::GatewayData; -use nym_wireguard::PeerControlRequest; -use std::sync::Arc; -use time::OffsetDateTime; use tracing::*; // Histogram buckets for LP registration duration tracking @@ -41,481 +28,132 @@ const LP_REGISTRATION_DURATION_BUCKETS: &[f64] = &[ 30.0, // 30s ]; -// Histogram buckets for WireGuard peer controller channel latency -// Measures time to send request and receive response from peer controller -// Expected: 1ms-100ms for normal operations, up to 2s for slow conditions -const WG_CONTROLLER_LATENCY_BUCKETS: &[f64] = &[ - 0.001, // 1ms - 0.005, // 5ms - 0.01, // 10ms - 0.05, // 50ms - 0.1, // 100ms - 0.25, // 250ms - 0.5, // 500ms - 1.0, // 1s - 2.0, // 2s -]; - -/// Prepare bandwidth storage for a client -async fn credential_storage_preparation( - ecash_verifier: Arc, - client_id: i64, -) -> Result { - // Check if bandwidth entry already exists (idempotent) - let existing_bandwidth = ecash_verifier - .storage() - .get_available_bandwidth(client_id) - .await?; - - // Only create if it doesn't exist - if existing_bandwidth.is_none() { - ecash_verifier - .storage() - .create_bandwidth_entry(client_id) - .await?; - } - - let bandwidth = ecash_verifier - .storage() - .get_available_bandwidth(client_id) - .await? - .ok_or_else(|| GatewayError::InternalError("bandwidth entry should exist".to_string()))?; - Ok(bandwidth) -} - -/// Verify credential and allocate bandwidth using CredentialVerifier -async fn credential_verification( - ecash_verifier: Arc, - credential: CredentialSpendingData, - client_id: i64, -) -> Result { - let bandwidth = credential_storage_preparation(ecash_verifier.clone(), client_id).await?; - let client_bandwidth = ClientBandwidth::new(bandwidth.into()); - let mut verifier = CredentialVerifier::new( - CredentialSpendingRequest::new(credential), - ecash_verifier.clone(), - BandwidthStorageManager::new( - ecash_verifier.storage(), - client_bandwidth, - client_id, - BandwidthFlushingBehaviourConfig::default(), - true, - ), - ); - - // Track credential verification attempts - inc!("lp_credential_verification_attempts"); - - // For mock ecash mode (local testing), skip cryptographic verification - // and just return a dummy bandwidth value since we don't have blockchain access - let allocated = if ecash_verifier.is_mock() { - // Return a reasonable test bandwidth value (e.g., 1GB in bytes) - const MOCK_BANDWIDTH: i64 = 1024 * 1024 * 1024; - inc!("lp_credential_verification_success"); - inc_by!("lp_bandwidth_allocated_bytes_total", MOCK_BANDWIDTH); - Ok::(MOCK_BANDWIDTH) - } else { - match verifier.verify().await { - Ok(allocated) => { - inc!("lp_credential_verification_success"); - // Track allocated bandwidth - inc_by!("lp_bandwidth_allocated_bytes_total", allocated); - Ok(allocated) - } - Err(e) => { - inc!("lp_credential_verification_failed"); - Err(e.into()) - } - } - }?; - - Ok(allocated) -} - -/// Check if WG peer already registered, return cached response if so. -/// -/// This enables idempotent registration: if a client retries registration -/// with the same WG public key (e.g., after network failure), we return -/// the existing registration data instead of re-processing. This prevents -/// wasting credentials on network issues. -async fn check_existing_registration( - wg_key_str: &str, - state: &LpHandlerState, -) -> Option { - // Need WG data to build GatewayData - let wg_data = state.wireguard_data.as_ref()?; - - // Look up existing peer - let peer = state.storage.get_wireguard_peer(wg_key_str).await.ok()??; - - // Convert to defguard Peer to access allowed_ips - let defguard_peer: Peer = peer.clone().try_into().ok()?; - - // Extract IPv4 and IPv6 from allowed_ips - let mut ipv4 = None; - let mut ipv6 = None; - for ip_mask in &defguard_peer.allowed_ips { - match ip_mask.ip { - std::net::IpAddr::V4(v4) => ipv4 = Some(v4), - std::net::IpAddr::V6(v6) => ipv6 = Some(v6), - } - } - - let (private_ipv4, private_ipv6) = match (ipv4, ipv6) { - (Some(v4), Some(v6)) => (v4, v6), - _ => return None, // Incomplete data, treat as new registration - }; - - // Get current bandwidth - let bandwidth = state - .ecash_verifier - .storage() - .get_available_bandwidth(peer.client_id) - .await - .ok()? - .map(|b| b.available) - .unwrap_or(0); - - // Only return cached response if bandwidth was actually allocated. - // If bandwidth is 0, registration was incomplete (peer exists but - // credential verification failed or never completed). Let the caller - // proceed with normal registration flow which will handle cleanup. - if bandwidth == 0 { - return None; - } - - Some(LpRegistrationResponse::success( - bandwidth, - GatewayData { - public_key: *wg_data.keypair().public_key(), - endpoint: wg_data.config().bind_address, - private_ipv4, - private_ipv6, - }, - )) -} - -/// Process an LP registration request -pub async fn process_registration( - request: LpRegistrationRequest, - state: &LpHandlerState, -) -> LpRegistrationResponse { - let session_id = rand::random::(); - let registration_start = std::time::Instant::now(); - - // Track total registration attempts - inc!("lp_registration_attempts_total"); - - // 1. Validate timestamp for replay protection - if !request.validate_timestamp(30) { - warn!("LP registration failed: timestamp too old or too far in future"); - inc!("lp_registration_failed_timestamp"); - return LpRegistrationResponse::error("Invalid timestamp".to_string()); - } - - // 2. Process based on mode - let result = match request.mode { - RegistrationMode::Dvpn => { - // Track dVPN registration attempts - inc!("lp_registration_dvpn_attempts"); - - // Check for idempotent re-registration (same WG key already registered) - // This allows clients to retry registration after network failures - // without wasting credentials - let wg_key_str = request.wg_public_key.to_string(); - if let Some(existing_response) = check_existing_registration(&wg_key_str, state).await { - info!("LP dVPN re-registration for existing peer {wg_key_str} (idempotent)",); - inc!("lp_registration_dvpn_idempotent"); - return existing_response; - } - - // Register as WireGuard peer first to get client_id - let (gateway_data, client_id) = match register_wg_peer( - request.wg_public_key.inner().as_ref(), - request.ticket_type, - state, - ) - .await - { - Ok(result) => result, - Err(e) => { - error!("LP WireGuard peer registration failed: {e}"); - inc!("lp_registration_dvpn_failed"); - inc!("lp_errors_wg_peer_registration"); - return LpRegistrationResponse::error(format!( - "WireGuard peer registration failed: {e}", - )); - } - }; - - // Verify credential with CredentialVerifier (handles double-spend, storage, etc.) - let allocated_bandwidth = match credential_verification( - state.ecash_verifier.clone(), - request.credential, - client_id, - ) - .await - { - Ok(bandwidth) => bandwidth, - Err(e) => { - // Credential verification failed, remove the peer - warn!("LP credential verification failed for client {client_id}: {e}",); - inc!("lp_registration_dvpn_failed"); - if let Err(remove_err) = state - .storage - .remove_wireguard_peer(&request.wg_public_key.to_string()) - .await - { - error!("Failed to remove peer after credential verification failure: {remove_err}"); - } - return LpRegistrationResponse::error(format!( - "Credential verification failed: {e}", - )); - } - }; - - info!("LP dVPN registration successful (client_id: {})", client_id); - inc!("lp_registration_dvpn_success"); - LpRegistrationResponse::success(allocated_bandwidth, gateway_data) - } - RegistrationMode::Mixnet { - client_ed25519_pubkey, - client_x25519_pubkey: _, - } => { - // Track mixnet registration attempts - inc!("lp_registration_mixnet_attempts"); - - // Parse client's ed25519 public key - let client_identity = match ed25519::PublicKey::from_bytes(&client_ed25519_pubkey) { - Ok(key) => key, - Err(e) => { - warn!("LP Mixnet registration failed: invalid ed25519 key: {e}"); - inc!("lp_registration_mixnet_failed"); - return LpRegistrationResponse::error(format!( - "Invalid client ed25519 key: {e}", - )); - } - }; - - // Derive destination address for ActiveClientsStore lookup - let client_address = client_identity.derive_destination_address(); - - // Generate client_id for credential verification (first 8 bytes of ed25519 key) - #[allow(clippy::expect_used)] - let client_id = i64::from_be_bytes( - client_ed25519_pubkey[0..8] - .try_into() - .expect("This cannot fail, since the key is 32 bytes long"), +impl LpHandlerState { + async fn process_dvpn_initial_registration( + &self, + sender: ReceiverIndex, + request: LpDvpnRegistrationInitialRequest, + ) -> LpRegistrationResponse { + let Some(registrator) = self.peer_registrator.as_ref() else { + return LpRegistrationResponse::error( + "dVPN via LP is not enabled on this node", + RegistrationMode::Dvpn, ); + }; - info!("LP Mixnet registration for client {client_identity}, session {session_id}",); - - // Verify credential with CredentialVerifier - let allocated_bandwidth = match credential_verification( - state.ecash_verifier.clone(), - request.credential, - client_id, - ) + registrator + .on_initial_lp_request(request, sender) .await - { - Ok(bandwidth) => bandwidth, - Err(e) => { - warn!("LP Mixnet credential verification failed for client {client_identity}: {e}"); - inc!("lp_registration_mixnet_failed"); - return LpRegistrationResponse::error(format!( - "Credential verification failed: {e}" - )); - } - }; + .unwrap_or_else(|err| { + LpRegistrationResponse::error( + format!("LP registration has failed: {err}"), + RegistrationMode::Dvpn, + ) + }) + } - // Create channels for client message delivery - let (mix_sender, _mix_receiver) = mpsc::unbounded(); - let (is_active_request_sender, _is_active_request_receiver) = - mpsc::unbounded::>(); + async fn process_dvpn_registration_finalisation( + &self, + sender: ReceiverIndex, + request: LpDvpnRegistrationFinalisation, + ) -> LpRegistrationResponse { + let Some(registrator) = self.peer_registrator.as_ref() else { + return LpRegistrationResponse::error( + "dVPN via LP is not enabled on this node", + RegistrationMode::Dvpn, + ); + }; - // Insert client into ActiveClientsStore for SURB reply delivery - if !state.active_clients_store.insert_remote( - client_address, - mix_sender, - is_active_request_sender, - OffsetDateTime::now_utc(), - ) { - warn!("LP Mixnet registration failed: client {client_identity} already registered",); - inc!("lp_registration_mixnet_failed"); - return LpRegistrationResponse::error("Client already registered".to_string()); + registrator + .on_final_lp_request(request, sender) + .await + .unwrap_or_else(|err| { + LpRegistrationResponse::error( + format!("LP registration has failed: {err}"), + RegistrationMode::Dvpn, + ) + }) + } + + async fn process_dvpn_registration( + &self, + sender: ReceiverIndex, + request: Box, + ) -> LpRegistrationResponse { + // Track dVPN registration attempts + inc!("lp_registration_dvpn_attempts"); + + match request.content { + LpDvpnRegistrationRequestMessageContent::InitialRequest(req) => { + self.process_dvpn_initial_registration(sender, req).await + } + LpDvpnRegistrationRequestMessageContent::Finalisation(req) => { + self.process_dvpn_registration_finalisation(sender, req) + .await } - - // Get gateway identity and derive sphinx key - let gateway_identity = state.local_identity.public_key().to_bytes(); - - warn!("TEMPORARY ed25519 -> x25519 conversion"); - #[allow(clippy::expect_used)] - let gateway_sphinx_key = state - .local_identity - .public_key() - .to_x25519() - .expect("valid ed25519 key should convert to x25519") - .to_bytes(); - - info!("LP Mixnet registration successful (client: {client_identity})",); - inc!("lp_registration_mixnet_success"); - - LpRegistrationResponse::success_mixnet( - allocated_bandwidth, - LpGatewayData { - gateway_identity, - gateway_sphinx_key, - }, - ) } - }; - - // Track registration duration - let duration = registration_start.elapsed().as_secs_f64(); - add_histogram_obs!( - "lp_registration_duration_seconds", - duration, - LP_REGISTRATION_DURATION_BUCKETS - ); - - // Track overall success/failure - if result.success { - inc!("lp_registration_success_total"); - } else { - inc!("lp_registration_failed_total"); } - result -} - -/// Register a WireGuard peer and return gateway data along with the client_id -async fn register_wg_peer( - public_key_bytes: &[u8], - ticket_type: nym_credentials_interface::TicketType, - state: &LpHandlerState, -) -> Result<(GatewayData, i64), GatewayError> { - let Some(wg_controller) = &state.wg_peer_controller else { - return Err(GatewayError::ServiceProviderNotRunning { - service: "WireGuard".to_string(), - }); - }; - - let Some(wg_data) = &state.wireguard_data else { - return Err(GatewayError::ServiceProviderNotRunning { - service: "WireGuard".to_string(), - }); - }; - - // Convert public key bytes to WireGuard Key - let mut key_bytes = [0u8; 32]; - if public_key_bytes.len() != 32 { - return Err(GatewayError::LpProtocolError( - "Invalid WireGuard public key length".to_string(), - )); + async fn process_mixnet_registration( + &self, + request: LpMixnetRegistrationRequestMessage, + ) -> LpRegistrationResponse { + let _ = request; + LpRegistrationResponse::error( + "mixnet registration is not yet supported", + RegistrationMode::Mixnet, + ) } - key_bytes.copy_from_slice(public_key_bytes); - let peer_key = Key::new(key_bytes); - // Allocate IPs from centralized pool managed by PeerController - let registration_data = nym_wireguard::PeerRegistrationData::new(peer_key.clone()); + /// Process an LP registration request + pub async fn process_registration( + &self, + sender: ReceiverIndex, + request: LpRegistrationRequest, + ) -> LpRegistrationResponse { + let registration_start = std::time::Instant::now(); - // Request IP allocation from PeerController - let (tx, rx) = oneshot::channel(); - wg_controller - .send(PeerControlRequest::RegisterPeer { - registration_data, - response_tx: tx, - }) - .await - .map_err(|e| { - GatewayError::InternalError(format!("Failed to send IP allocation request: {}", e)) - })?; + // Track total registration attempts + inc!("lp_registration_attempts_total"); - // Wait for IP allocation from pool - let ip_pair = rx - .await - .map_err(|e| { - GatewayError::InternalError(format!("Failed to receive IP allocation: {}", e)) - })? - .map_err(|e| { - error!("Failed to allocate IPs from pool: {}", e); - GatewayError::InternalError(format!("Failed to allocate IPs: {:?}", e)) - })?; + // 1. Validate timestamp for replay protection + if !request.validate_timestamp(30) { + warn!("LP registration failed: timestamp too old or too far in future"); + inc!("lp_registration_failed_timestamp"); + return LpRegistrationResponse::error("invalid timestamp", request.mode()); + } - let client_ipv4 = ip_pair.ipv4; - let client_ipv6 = ip_pair.ipv6; + // 2. Process based on mode + let result = match request.registration_data { + LpRegistrationRequestData::Dvpn { data } => { + self.process_dvpn_registration(sender, data).await + } + LpRegistrationRequestData::Mixnet { data } => { + self.process_mixnet_registration(data).await + } + }; - info!( - "Allocated IPs for peer {}: {} / {}", - peer_key, client_ipv4, client_ipv6 - ); + // Track registration duration + let duration = registration_start.elapsed().as_secs_f64(); + add_histogram_obs!( + "lp_registration_duration_seconds", + duration, + LP_REGISTRATION_DURATION_BUCKETS + ); - // Create WireGuard peer with allocated IPs - let mut peer = Peer::new(peer_key.clone()); - peer.endpoint = None; - peer.allowed_ips = vec![ - format!("{client_ipv4}/32").parse()?, - format!("{client_ipv6}/128").parse()?, - ]; - peer.persistent_keepalive_interval = Some(25); + // Track overall success/failure + match result.status { + RegistrationStatus::Completed => { + inc!("lp_registration_success_total"); + } + RegistrationStatus::Failed => { + inc!("lp_registration_failed_total"); + } + RegistrationStatus::PendingMoreData => { + inc!("lp_registration_pending_more_data"); + } + } - // Store peer in database FIRST (before adding to controller) - // This ensures bandwidth storage exists when controller's generate_bandwidth_manager() is called - let client_id = state - .storage - .insert_wireguard_peer(&peer, ticket_type.into()) - .await - .map_err(|e| { - error!("Failed to store WireGuard peer in database: {}", e); - GatewayError::InternalError(format!("Failed to store peer: {}", e)) - })?; - - // Create bandwidth entry for the client - // This must happen BEFORE AddPeer because generate_bandwidth_manager() expects it to exist - credential_storage_preparation(state.ecash_verifier.clone(), client_id).await?; - - // Now send peer to WireGuard controller and track latency - let controller_start = std::time::Instant::now(); - let (tx, rx) = oneshot::channel(); - wg_controller - .send(PeerControlRequest::AddPeer { - peer: peer.clone(), - response_tx: tx, - }) - .await - .map_err(|e| GatewayError::InternalError(format!("Failed to send peer request: {}", e)))?; - - let result = rx - .await - .map_err(|e| { - GatewayError::InternalError(format!("Failed to receive peer response: {}", e)) - })? - .map_err(|e| GatewayError::InternalError(format!("Failed to add peer: {:?}", e))); - - // Record peer controller channel latency - let latency = controller_start.elapsed().as_secs_f64(); - add_histogram_obs!( - "wg_peer_controller_channel_latency_seconds", - latency, - WG_CONTROLLER_LATENCY_BUCKETS - ); - - result?; - - // Get gateway's actual WireGuard public key - let gateway_pubkey = *wg_data.keypair().public_key(); - - // Get gateway's WireGuard endpoint from config - let gateway_endpoint = wg_data.config().bind_address; - - // Create GatewayData response (matching authenticator response format) - Ok(( - GatewayData { - public_key: gateway_pubkey, - endpoint: gateway_endpoint, - private_ipv4: client_ipv4, - private_ipv6: client_ipv6, - }, - client_id, - )) + result + } } diff --git a/gateway/src/node/mod.rs b/gateway/src/node/mod.rs index 6b376133f7..d8376289a3 100644 --- a/gateway/src/node/mod.rs +++ b/gateway/src/node/mod.rs @@ -16,8 +16,9 @@ use nym_credential_verification::ecash::{ use nym_credential_verification::upgrade_mode::{ UpgradeModeCheckConfig, UpgradeModeDetails, UpgradeModeState, }; -use nym_crypto::asymmetric::ed25519; +use nym_crypto::asymmetric::{ed25519, x25519}; use nym_ip_packet_router::IpPacketRouter; +use nym_lp::peer::LpLocalPeer; use nym_mixnet_client::forwarder::MixForwardingSender; use nym_network_defaults::NymNetworkDetails; use nym_network_requester::NRServiceProviderBuilder; @@ -37,6 +38,7 @@ use tracing::*; use zeroize::Zeroizing; pub use crate::node::upgrade_mode::watcher::UpgradeModeWatcher; +use crate::node::wireguard::{PeerManager, PeerRegistrator}; pub use client_handling::active_clients::ActiveClientsStore; pub use lp_listener::LpConfig; pub use nym_credential_verification::upgrade_mode::UpgradeModeCheckRequestSender; @@ -53,6 +55,7 @@ pub(crate) mod internal_service_providers; pub mod lp_listener; mod stale_data_cleaner; pub mod upgrade_mode; +pub mod wireguard; #[derive(Debug, Clone)] pub struct LocalNetworkRequesterOpts { @@ -92,6 +95,12 @@ pub struct GatewayTasksBuilder { /// ed25519 keypair used to assert one's identity. identity_keypair: Arc, + /// x25519 keypair used within KTT exchange + x25519_keypair: Arc, + + /// x25519 (for now, to be changed into MlKem) keypair used for the PSQ derivation + kem_psq_keys: Arc, + storage: GatewayStorage, mix_packet_sender: MixForwardingSender, @@ -120,6 +129,8 @@ impl GatewayTasksBuilder { pub fn new( config: Config, identity: Arc, + x25519: Arc, + kem_psq_keys: Arc, storage: GatewayStorage, mix_packet_sender: MixForwardingSender, metrics_sender: MetricEventsSender, @@ -137,6 +148,8 @@ impl GatewayTasksBuilder { wireguard_data: None, user_agent, identity_keypair: identity, + x25519_keypair: x25519, + kem_psq_keys, storage, mix_packet_sender, metrics_sender, @@ -286,6 +299,22 @@ impl GatewayTasksBuilder { } } + pub async fn build_peer_registrator( + &mut self, + upgrade_mode_details: UpgradeModeDetails, + ) -> Result, GatewayError> { + let Some(wireguard_data) = &self.wireguard_data else { + return Ok(None); + }; + + let peer_manager = PeerManager::new(wireguard_data.inner.clone()); + Ok(Some(PeerRegistrator::new( + self.ecash_manager().await?, + peer_manager, + upgrade_mode_details, + ))) + } + pub async fn build_websocket_listener( &mut self, active_clients_store: ActiveClientsStore, @@ -317,22 +346,20 @@ impl GatewayTasksBuilder { pub async fn build_lp_listener( &mut self, + peer_registrator: Option, active_clients_store: ActiveClientsStore, ) -> Result { - // Get WireGuard peer controller if available - let wg_peer_controller = self - .wireguard_data - .as_ref() - .map(|wg_data| wg_data.inner.peer_tx().clone()); - let handler_state = lp_listener::LpHandlerState { ecash_verifier: self.ecash_manager().await?, storage: self.storage.clone(), - local_identity: Arc::clone(&self.identity_keypair), + local_lp_peer: LpLocalPeer::new( + self.identity_keypair.clone(), + self.x25519_keypair.clone(), + ) + .with_kem_psq_key(self.kem_psq_keys.clone()), metrics: self.metrics.clone(), active_clients_store, - wg_peer_controller, - wireguard_data: self.wireguard_data.as_ref().map(|wd| wd.inner.clone()), + peer_registrator, lp_config: self.config.lp, outbound_mix_sender: self.mix_packet_sender.clone(), handshake_states: Arc::new(dashmap::DashMap::new()), @@ -447,7 +474,7 @@ impl GatewayTasksBuilder { .await?; continue; }; - used_private_network_ips.push(allowed_ip.ip); + used_private_network_ips.push(allowed_ip.address); all_peers.push(peer); } @@ -470,26 +497,12 @@ impl GatewayTasksBuilder { Ok(peers) } - async fn get_wireguard_networks(&mut self) -> Result, GatewayError> { - if let Some(cached) = self.wireguard_networks.take() { - return Ok(cached); - } - - let (peers, used_private_network_ips) = self.build_wireguard_peers_and_networks().await?; - // cache peers for the other task - - self.wireguard_peers = Some(peers); - Ok(used_private_network_ips) - } - pub async fn build_wireguard_authenticator( &mut self, + peer_registrator: PeerRegistrator, upgrade_mode_common: UpgradeModeDetails, topology_provider: Box, ) -> Result, GatewayError> { - let ecash_manager = self.ecash_manager().await?; - let used_private_network_ips = self.get_wireguard_networks().await?; - let Some(opts) = &self.authenticator_opts else { return Err(GatewayError::UnspecifiedAuthenticatorConfig); }; @@ -509,10 +522,9 @@ impl GatewayTasksBuilder { let mut authenticator_server = Authenticator::new( opts.config.clone(), + peer_registrator, upgrade_mode_common, wireguard_data.inner.clone(), - used_private_network_ips, - ecash_manager, self.shutdown_tracker.clone(), ) .with_custom_gateway_transceiver(transceiver) diff --git a/gateway/src/node/wireguard/error.rs b/gateway/src/node/wireguard/error.rs new file mode 100644 index 0000000000..3b32f12257 --- /dev/null +++ b/gateway/src/node/wireguard/error.rs @@ -0,0 +1,58 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_credential_verification::upgrade_mode::UpgradeModeEnableError; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum GatewayWireguardError { + #[error("internal error: {0}")] + InternalError(String), + + #[error("peers can't be interacted with anymore")] + PeerInteractionStopped, + + #[error("registration is not in progress for the provided peer key")] + RegistrationNotInProgress, + + #[error("missing reply_to for old client")] + MissingReplyToForOldClient, + + #[error("unknown version number")] + UnknownAuthenticatorVersion, + + #[error("unsupported authenticator version")] + UnsupportedAuthenticatorVersion, + + #[error("mac does not verify")] + AuthenticatorMacVerificationFailure, + + #[error("no credential received")] + MissingAuthenticatorCredential, + + #[error(transparent)] + UpgradeModeEnable(#[from] UpgradeModeEnableError), + + #[error("credential verification failed: {0}")] + CredentialVerificationError(#[from] nym_credential_verification::Error), + + #[error(transparent)] + GatewayStorageError(#[from] nym_gateway_storage::error::GatewayStorageError), + + #[error("failed to serialise authenticator response packet: {source}")] + AuthenticatorResponseSerialisationFailure { source: Box }, +} + +impl GatewayWireguardError { + pub fn internal(message: impl Into) -> Self { + GatewayWireguardError::InternalError(message.into()) + } + + pub fn authenticator_response_serialisation( + source: impl Into>, + ) -> Self { + GatewayWireguardError::AuthenticatorResponseSerialisationFailure { + source: source.into(), + } + } +} diff --git a/gateway/src/node/wireguard/mod.rs b/gateway/src/node/wireguard/mod.rs new file mode 100644 index 0000000000..b65654a9d1 --- /dev/null +++ b/gateway/src/node/wireguard/mod.rs @@ -0,0 +1,10 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub mod error; +pub mod new_peer_registration; +pub mod peer_manager; + +pub use error::GatewayWireguardError; +pub use new_peer_registration::PeerRegistrator; +pub use peer_manager::PeerManager; diff --git a/gateway/src/node/wireguard/new_peer_registration/authenticator.rs b/gateway/src/node/wireguard/new_peer_registration/authenticator.rs new file mode 100644 index 0000000000..45da0e7ae4 --- /dev/null +++ b/gateway/src/node/wireguard/new_peer_registration/authenticator.rs @@ -0,0 +1,157 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::node::wireguard::new_peer_registration::helpers::build_final_authenticator_response; +use crate::node::wireguard::new_peer_registration::pending::{ + PendingRegistration, PendingRegistrationData, +}; +use crate::node::wireguard::{GatewayWireguardError, PeerRegistrator}; +use defguard_wireguard_rs::host::Peer; +use nym_authenticator_requests::authenticator_ipv4_to_ipv6; +use nym_authenticator_requests::response::SerialisedResponse; +use nym_registration_common::WireguardRegistrationData; +use nym_sdk::mixnet::Recipient; +use nym_service_provider_requests_common::Protocol; +use nym_wireguard::peer_controller::IpPair; +use nym_wireguard_types::PeerPublicKey; +use std::net::IpAddr; +use std::time::Instant; + +impl PeerRegistrator { + fn authenticator_peer_to_final_response( + &self, + peer: Peer, + protocol: Protocol, + request_id: u64, + reply_to: Option, + ) -> Result { + let allowed_ipv4 = peer + .allowed_ips + .iter() + .find_map(|ip_mask| match ip_mask.address { + IpAddr::V4(ipv4_addr) => Some(ipv4_addr), + _ => None, + }) + .ok_or(GatewayWireguardError::internal( + "there should be one private IPv4 in the list", + ))?; + let allowed_ipv6 = peer + .allowed_ips + .iter() + .find_map(|ip_mask| match ip_mask.address { + IpAddr::V6(ipv6_addr) => Some(ipv6_addr), + _ => None, + }) + .unwrap_or(authenticator_ipv4_to_ipv6(allowed_ipv4)); + + let ip_allocation = IpPair::new(allowed_ipv4, allowed_ipv6); + let wg_port = self.wg_port(); + let local_pub_key = (*self.keypair().public_key()).into(); + let upgrade_mode_enabled = self.upgrade_mode_enabled(); + + build_final_authenticator_response( + ip_allocation, + wg_port, + local_pub_key, + upgrade_mode_enabled, + request_id, + protocol.into(), + reply_to, + ) + } + + pub(super) async fn check_pending_authenticator_registration( + &self, + protocol: Protocol, + request_id: u64, + remote_public: PeerPublicKey, + reply_to: Option, + ) -> Result, GatewayWireguardError> { + let Some(pending_registration) = self + .pending_registrations + .check_authenticator(&remote_public) + .await + else { + return Ok(None); + }; + + Ok(Some( + pending_registration.to_pending_authenticator_response( + self.keypair().private_key(), + self.upgrade_mode_enabled(), + request_id, + protocol.into(), + reply_to, + )?, + )) + } + + pub(super) async fn check_existing_authenticator_peer( + &self, + protocol: Protocol, + request_id: u64, + remote_public: PeerPublicKey, + reply_to: Option, + ) -> Result, GatewayWireguardError> { + let Some(peer) = self.peer_manager.query_peer(remote_public).await? else { + return Ok(None); + }; + Ok(Some(self.authenticator_peer_to_final_response( + peer, protocol, request_id, reply_to, + )?)) + } + + pub(super) fn new_pending_authenticator( + &self, + peer: PeerPublicKey, + ip_allocation: IpPair, + ) -> PendingRegistration { + let nonce: u64 = fastrand::u64(..); + + PendingRegistration { + requested_on: Instant::now(), + data: PendingRegistrationData { + nonce, + peer_key: peer, + psk: None, + wireguard_config: WireguardRegistrationData { + public_key: *self.keypair().public_key(), + port: self.wg_port(), + private_ipv4: ip_allocation.ipv4, + private_ipv6: ip_allocation.ipv6, + }, + }, + } + } + + pub(super) async fn process_fresh_initial_authenticator_registration( + &self, + protocol: Protocol, + request_id: u64, + remote_public: PeerPublicKey, + reply_to: Option, + ) -> Result { + // 1. allocate ip pair + let ip_allocation = self.peer_manager.preallocate_peer_ip_pair().await?; + + let pending = self.new_pending_authenticator(remote_public, ip_allocation); + + // 2. construct response + let response = pending.to_pending_authenticator_response( + self.keypair().private_key(), + self.upgrade_mode_enabled(), + request_id, + protocol.into(), + reply_to, + )?; + + // 3. insert pending data into cache + self.pending_registrations + .authenticator + .write() + .await + .insert(remote_public, pending); + + Ok(response) + } +} diff --git a/gateway/src/node/wireguard/new_peer_registration/helpers.rs b/gateway/src/node/wireguard/new_peer_registration/helpers.rs new file mode 100644 index 0000000000..c301d25c76 --- /dev/null +++ b/gateway/src/node/wireguard/new_peer_registration/helpers.rs @@ -0,0 +1,209 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::node::wireguard::GatewayWireguardError; +use nym_authenticator_requests::response::SerialisedResponse; +use nym_authenticator_requests::{v1, v2, v3, v4, v5, v6, AuthenticatorVersion}; +use nym_crypto::asymmetric::x25519; +use nym_sdk::mixnet::Recipient; +use nym_wireguard::ip_pool::IpPair; +use nym_wireguard_types::PeerPublicKey; + +#[allow(clippy::too_many_arguments)] +pub(crate) fn build_pending_authenticator_response( + ip_allocation: IpPair, + wg_port: u16, + local_key: &x25519::PrivateKey, + peer_key: PeerPublicKey, + upgrade_mode_enabled: bool, + nonce: u64, + request_id: u64, + version: AuthenticatorVersion, + reply_to: Option, +) -> Result { + let private_ipv4 = ip_allocation.ipv4; + let private_ipv6 = ip_allocation.ipv6; + + let bytes = match version { + AuthenticatorVersion::V1 => Err(GatewayWireguardError::UnsupportedAuthenticatorVersion), + AuthenticatorVersion::V2 => { + v2::response::AuthenticatorResponse::new_pending_registration_success( + v2::registration::RegistrationData { + nonce, + gateway_data: v2::registration::GatewayClient::new( + local_key, + peer_key.inner(), + private_ipv4.into(), + nonce, + ), + wg_port, + }, + request_id, + reply_to.ok_or(GatewayWireguardError::MissingReplyToForOldClient)?, + ) + .to_bytes() + .map_err(GatewayWireguardError::authenticator_response_serialisation) + } + AuthenticatorVersion::V3 => { + v3::response::AuthenticatorResponse::new_pending_registration_success( + v3::registration::RegistrationData { + nonce, + gateway_data: v3::registration::GatewayClient::new( + local_key, + peer_key.inner(), + private_ipv4.into(), + nonce, + ), + wg_port, + }, + request_id, + reply_to.ok_or(GatewayWireguardError::MissingReplyToForOldClient)?, + ) + .to_bytes() + .map_err(GatewayWireguardError::authenticator_response_serialisation) + } + AuthenticatorVersion::V4 => { + v4::response::AuthenticatorResponse::new_pending_registration_success( + v4::registration::RegistrationData { + nonce, + gateway_data: v4::registration::GatewayClient::new( + local_key, + peer_key.inner(), + v4::registration::IpPair::new(private_ipv4, private_ipv6), + nonce, + ), + wg_port, + }, + request_id, + reply_to.ok_or(GatewayWireguardError::MissingReplyToForOldClient)?, + ) + .to_bytes() + .map_err(GatewayWireguardError::authenticator_response_serialisation) + } + AuthenticatorVersion::V5 => { + v5::response::AuthenticatorResponse::new_pending_registration_success( + v5::registration::RegistrationData { + nonce, + gateway_data: v5::registration::GatewayClient::new( + local_key, + peer_key.inner(), + v5::registration::IpPair::new(private_ipv4, private_ipv6), + nonce, + ), + wg_port, + }, + request_id, + ) + .to_bytes() + .map_err(GatewayWireguardError::authenticator_response_serialisation) + } + AuthenticatorVersion::V6 => { + v6::response::AuthenticatorResponse::new_pending_registration_success( + v6::registration::RegistrationData { + nonce, + gateway_data: v6::registration::GatewayClient::new( + local_key, + peer_key.inner(), + v6::registration::IpPair::new(private_ipv4, private_ipv6), + nonce, + ), + wg_port, + }, + request_id, + upgrade_mode_enabled, + ) + .to_bytes() + .map_err(GatewayWireguardError::authenticator_response_serialisation) + } + AuthenticatorVersion::UNKNOWN => { + return Err(GatewayWireguardError::UnknownAuthenticatorVersion) + } + }?; + + Ok(nym_authenticator_requests::response::SerialisedResponse::new(bytes, reply_to)) +} + +pub(crate) fn build_final_authenticator_response( + ip_allocation: IpPair, + wg_port: u16, + pub_key: PeerPublicKey, + upgrade_mode_enabled: bool, + request_id: u64, + version: AuthenticatorVersion, + reply_to: Option, +) -> Result { + let private_ipv4 = ip_allocation.ipv4; + let private_ipv6 = ip_allocation.ipv6; + + let bytes = match version { + AuthenticatorVersion::V1 => v1::response::AuthenticatorResponse::new_registered( + v1::registration::RegisteredData { + pub_key, + private_ip: private_ipv4.into(), + wg_port, + }, + reply_to.ok_or(GatewayWireguardError::MissingReplyToForOldClient)?, + request_id, + ) + .to_bytes() + .map_err(GatewayWireguardError::authenticator_response_serialisation)?, + AuthenticatorVersion::V2 => v2::response::AuthenticatorResponse::new_registered( + v2::registration::RegisteredData { + pub_key, + private_ip: private_ipv4.into(), + wg_port, + }, + reply_to.ok_or(GatewayWireguardError::MissingReplyToForOldClient)?, + request_id, + ) + .to_bytes() + .map_err(GatewayWireguardError::authenticator_response_serialisation)?, + AuthenticatorVersion::V3 => v3::response::AuthenticatorResponse::new_registered( + v3::registration::RegisteredData { + pub_key, + private_ip: private_ipv4.into(), + wg_port, + }, + reply_to.ok_or(GatewayWireguardError::MissingReplyToForOldClient)?, + request_id, + ) + .to_bytes() + .map_err(GatewayWireguardError::authenticator_response_serialisation)?, + AuthenticatorVersion::V4 => v4::response::AuthenticatorResponse::new_registered( + v4::registration::RegisteredData { + pub_key, + private_ips: v4::registration::IpPair::new(private_ipv4, private_ipv6), + wg_port, + }, + reply_to.ok_or(GatewayWireguardError::MissingReplyToForOldClient)?, + request_id, + ) + .to_bytes() + .map_err(GatewayWireguardError::authenticator_response_serialisation)?, + AuthenticatorVersion::V5 => v5::response::AuthenticatorResponse::new_registered( + v5::registration::RegisteredData { + pub_key, + private_ips: v5::registration::IpPair::new(private_ipv4, private_ipv6), + wg_port, + }, + request_id, + ) + .to_bytes() + .map_err(GatewayWireguardError::authenticator_response_serialisation)?, + AuthenticatorVersion::V6 => v6::response::AuthenticatorResponse::new_registered( + v6::registration::RegisteredData { + pub_key, + private_ips: v6::registration::IpPair::new(private_ipv4, private_ipv6), + wg_port, + }, + request_id, + upgrade_mode_enabled, + ) + .to_bytes() + .map_err(GatewayWireguardError::authenticator_response_serialisation)?, + AuthenticatorVersion::UNKNOWN => { + return Err(GatewayWireguardError::UnknownAuthenticatorVersion) + } + }; + Ok(SerialisedResponse::new(bytes, reply_to)) +} diff --git a/gateway/src/node/wireguard/new_peer_registration/lp.rs b/gateway/src/node/wireguard/new_peer_registration/lp.rs new file mode 100644 index 0000000000..dcc505ab94 --- /dev/null +++ b/gateway/src/node/wireguard/new_peer_registration/lp.rs @@ -0,0 +1,128 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::node::lp_listener::ReceiverIndex; +use crate::node::wireguard::new_peer_registration::pending::{ + PendingRegistration, PendingRegistrationData, +}; +use crate::node::wireguard::{GatewayWireguardError, PeerRegistrator}; +use defguard_wireguard_rs::host::Peer; +use defguard_wireguard_rs::key::Key; +use nym_registration_common::{LpRegistrationResponse, WireguardRegistrationData}; +use nym_wireguard::ip_pool::{allocated_ip_pair, IpPair}; +use nym_wireguard_types::PeerPublicKey; +use std::time::Instant; + +impl PeerRegistrator { + /// In the case of an already registered WG peer, update its PSK. + pub(super) async fn update_peer_psk( + &self, + peer: PeerPublicKey, + psk: Key, + ) -> Result<(), GatewayWireguardError> { + // 1. check if the peer is currently being handled + if self.peer_manager.check_active_peer(peer).await? { + // 2. if so, force disconnect it (as we're handling new request from the same peer) + self.peer_manager.remove_peer(peer).await?; + } + + // 3. update the on-disk PSK + let encoded_psk = psk.to_lower_hex(); + self.ecash_verifier + .storage() + .update_peer_psk(&peer.to_string(), Some(&encoded_psk)) + .await?; + + Ok(()) + } + + fn lp_peer_to_final_response( + &self, + peer: Peer, + ) -> Result, GatewayWireguardError> { + // Incomplete data, treat as new registration + let Some(allocated_ips) = allocated_ip_pair(&peer) else { + return Ok(None); + }; + + Ok(Some(LpRegistrationResponse::success_dvpn( + WireguardRegistrationData { + public_key: *self.keypair().public_key(), + port: self.wg_port(), + private_ipv4: allocated_ips.ipv4, + private_ipv6: allocated_ips.ipv6, + }, + self.upgrade_mode_enabled(), + ))) + } + + pub(super) async fn check_pending_lp_registration( + &self, + sender: ReceiverIndex, + ) -> Result, GatewayWireguardError> { + let Some(pending_registration) = self.pending_registrations.check_lp(sender).await else { + return Ok(None); + }; + + Ok(Some(pending_registration.to_pending_lp_response())) + } + + pub(super) async fn check_existing_lp_peer( + &self, + remote_public: PeerPublicKey, + ) -> Result, GatewayWireguardError> { + let Some(peer) = self.peer_manager.query_peer(remote_public).await? else { + return Ok(None); + }; + + self.lp_peer_to_final_response(peer) + } + + pub(super) fn new_pending_lp( + &self, + peer: PeerPublicKey, + psk: Key, + ip_allocation: IpPair, + ) -> PendingRegistration { + let nonce: u64 = fastrand::u64(..); + + PendingRegistration { + requested_on: Instant::now(), + data: PendingRegistrationData { + nonce, + peer_key: peer, + psk: Some(psk), + wireguard_config: WireguardRegistrationData { + public_key: *self.keypair().public_key(), + port: self.wg_port(), + private_ipv4: ip_allocation.ipv4, + private_ipv6: ip_allocation.ipv6, + }, + }, + } + } + + pub(super) async fn process_fresh_initial_lp_registration( + &self, + sender: ReceiverIndex, + remote_public: PeerPublicKey, + psk: Key, + ) -> Result { + // 1. allocate ip pair + let ip_allocation = self.peer_manager.preallocate_peer_ip_pair().await?; + + let pending = self.new_pending_lp(remote_public, psk, ip_allocation); + + // 2. construct response + let response = pending.to_pending_lp_response(); + + // 3. insert pending data into cache + self.pending_registrations + .lp + .write() + .await + .insert(sender, pending); + + Ok(response) + } +} diff --git a/gateway/src/node/wireguard/new_peer_registration/mod.rs b/gateway/src/node/wireguard/new_peer_registration/mod.rs new file mode 100644 index 0000000000..43dcce7bb0 --- /dev/null +++ b/gateway/src/node/wireguard/new_peer_registration/mod.rs @@ -0,0 +1,391 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +//! Unification of Nym registration flow +//! In general the registration has the following structure: +//! 1. Initial request message is received +//! 1.1. We check if the peer has already registered before -> if so, we returned the past information +//! 1.2. We check if the peer already has a pending registration -> if so, we return the past information +//! 1.3. We pre-allocated [`nym_wireguard::ip_pool::IpPair`] and save time-sensitive pending registration. +//! If it does not complete within specified time interval, the information is going to get removed. +//! 2. Finalisation request message is received, where credential has to be attached is verified. +//! Upon successful completion, pending registration is transformed into a properly inserted peer. + +use crate::node::lp_listener::ReceiverIndex; +use crate::node::wireguard::new_peer_registration::pending::{ + PendingRegistration, PendingRegistrations, +}; +use crate::node::wireguard::{GatewayWireguardError, PeerManager}; +use defguard_wireguard_rs::host::Peer; +use defguard_wireguard_rs::key::Key; +use defguard_wireguard_rs::net::IpAddrMask; +use nym_authenticator_requests::models::BandwidthClaim; +use nym_authenticator_requests::response::SerialisedResponse; +use nym_authenticator_requests::traits::{FinalMessage, InitMessage}; +use nym_credential_verification::bandwidth_storage_manager::BandwidthStorageManager; +use nym_credential_verification::ecash::traits::EcashManager; +use nym_credential_verification::upgrade_mode::UpgradeModeDetails; +use nym_credential_verification::{ + BandwidthFlushingBehaviourConfig, ClientBandwidth, CredentialVerifier, +}; +use nym_credentials_interface::{BandwidthCredential, CredentialSpendingData}; +use nym_crypto::asymmetric::x25519; +use nym_gateway_requests::models::CredentialSpendingRequest; +use nym_gateway_storage::models::PersistedBandwidth; +use nym_registration_common::dvpn::{ + LpDvpnRegistrationFinalisation, LpDvpnRegistrationInitialRequest, +}; +use nym_registration_common::LpRegistrationResponse; +use nym_sdk::mixnet::Recipient; +use nym_service_provider_requests_common::Protocol; +use nym_task::ShutdownToken; +use nym_wireguard::WireguardConfig; +use std::sync::Arc; +use std::time::Duration; +use tokio::time::{interval_at, Instant}; +use tracing::trace; + +mod authenticator; +mod helpers; +mod lp; +mod pending; + +#[derive(Clone)] +pub struct PeerRegistrator { + /// Handle for the structure managing verification of the ecash credentials for the bandwidth control + pub(crate) ecash_verifier: Arc, + + /// Handle for communication with the [`nym_wireguard::peer_controller::PeerController`] + pub(crate) peer_manager: PeerManager, + + /// Information about the current state of the upgrade mode as well as a handle + /// to remotely trigger the recheck + pub(crate) upgrade_mode: UpgradeModeDetails, + + /// Registrations in progress + pub(crate) pending_registrations: PendingRegistrations, +} + +impl PeerRegistrator { + pub fn new( + ecash_verifier: Arc, + peer_manager: PeerManager, + upgrade_mode: UpgradeModeDetails, + ) -> Self { + PeerRegistrator { + ecash_verifier, + peer_manager, + upgrade_mode, + pending_registrations: Default::default(), + } + } + + pub fn cleanup_task(&self, shutdown_token: ShutdownToken) -> StaleRegistrationRemover { + StaleRegistrationRemover { + pending_registrations: self.pending_registrations.clone(), + shutdown_token, + } + } + + fn upgrade_mode_enabled(&self) -> bool { + self.upgrade_mode.enabled() + } + + fn keypair(&self) -> &Arc { + self.peer_manager.wireguard_gateway_data.keypair() + } + + fn wireguard_config(&self) -> WireguardConfig { + self.peer_manager.wireguard_gateway_data.config() + } + + fn wg_port(&self) -> u16 { + self.wireguard_config().announced_tunnel_port + } + + pub async fn credential_storage_preparation( + &self, + client_id: i64, + ) -> Result { + self.ecash_verifier + .storage() + .create_bandwidth_entry(client_id) + .await?; + + self.ecash_verifier + .storage() + .get_available_bandwidth(client_id) + .await? + .ok_or(GatewayWireguardError::internal( + "missing bandwidth entry after it has just been created", + )) + } + + async fn credential_verification( + &self, + credential: CredentialSpendingData, + client_id: i64, + ) -> Result { + let bandwidth = self.credential_storage_preparation(client_id).await?; + let client_bandwidth = ClientBandwidth::new(bandwidth.into()); + let mut verifier = CredentialVerifier::new( + CredentialSpendingRequest::new(credential), + self.ecash_verifier.clone(), + BandwidthStorageManager::new( + self.ecash_verifier.storage(), + client_bandwidth, + client_id, + BandwidthFlushingBehaviourConfig::default(), + true, + ), + ); + + Ok(verifier.verify().await?) + } + + async fn handle_final_credential_claim( + &self, + claim: BandwidthClaim, + client_id: i64, + ) -> Result<(), GatewayWireguardError> { + match claim.credential { + BandwidthCredential::ZkNym(zk_nym) => { + // if we got zk-nym, we just try to verify it + self.credential_verification(*zk_nym, client_id).await?; + Ok(()) + } + BandwidthCredential::UpgradeModeJWT { token } => { + // if we're already in the upgrade mode, don't bother validating the token + if self.upgrade_mode_enabled() { + return Ok(()); + } + + self.upgrade_mode.try_enable_via_received_jwt(token).await?; + Ok(()) + } + } + } + + /// Attempt to process new peer by: + /// 1. retrieving previous IP allocation + /// 2. inserting it into the storage + /// 3. verifying bandwidth claim and increasing the allowance + /// 4. spawning the peer handler + async fn process_new_peer( + &self, + pending: PendingRegistration, + credential: BandwidthClaim, + ) -> Result<(), GatewayWireguardError> { + // 1. create peer based on the cached registration information + let defguard_key = Key::new(pending.data.peer_key.to_bytes()); + let mut peer = Peer::new(defguard_key); + if let Some(psk) = pending.data.psk { + peer.preshared_key = Some(psk); + } + let private_ipv4 = pending.data.wireguard_config.private_ipv4; + let private_ipv6 = pending.data.wireguard_config.private_ipv6; + peer.allowed_ips = vec![ + IpAddrMask::new(private_ipv4.into(), 32), + IpAddrMask::new(private_ipv6.into(), 128), + ]; + + let typ = credential.kind; + + // 2. attempt to pre-insert peer into the storage + let client_id = self + .ecash_verifier + .storage() + .insert_wireguard_peer(&peer, typ.into()) + .await?; + + // 3. verify the credential + if let Err(err) = self + .handle_final_credential_claim(credential, client_id) + .await + { + // 3.1. on failure -> remove the inserted peer + self.ecash_verifier + .storage() + .remove_wireguard_peer(&peer.public_key.to_string()) + .await?; + return Err(err); + } + + // 4. attempt to start the actual handle for the peer + let public_key = peer.public_key.to_string(); + if let Err(err) = self.peer_manager.add_peer(peer).await { + // 4.1. on failure -> remove the inserted peer (from the storage) + self.ecash_verifier + .storage() + .remove_wireguard_peer(&public_key) + .await?; + return Err(err); + } + + Ok(()) + } + + pub(crate) async fn on_initial_authenticator_request( + &mut self, + init_message: Box, + protocol: Protocol, + request_id: u64, + reply_to: Option, + ) -> Result { + let remote_public = init_message.pub_key(); + + // 1. check if there's any pending registration already in progress, + // if so, return the same data again without additional processing + if let Some(pending_registration) = self + .check_pending_authenticator_registration(protocol, request_id, remote_public, reply_to) + .await? + { + return Ok(pending_registration); + } + + // 2. check if there is already a peer associated with this sender, + // if so, retrieve the "final" data without additional processing + if let Some(existing_registration) = self + .check_existing_authenticator_peer(protocol, request_id, remote_public, reply_to) + .await? + { + return Ok(existing_registration); + } + + // 3. process fresh registration request + self.process_fresh_initial_authenticator_registration( + protocol, + request_id, + remote_public, + reply_to, + ) + .await + } + + pub(crate) async fn on_final_authenticator_request( + &mut self, + final_message: Box, + protocol: Protocol, + request_id: u64, + reply_to: Option, + ) -> Result { + let peer = final_message.gateway_client_pub_key(); + // 1. check if there's any pending registration associated with this peer + let pending_data = self + .pending_registrations + .check_authenticator(&peer) + .await + .ok_or(GatewayWireguardError::RegistrationNotInProgress)? + .clone(); + + // 2. verify the correctness of the received request based on the prior nonce + if final_message + .verify(self.keypair().private_key(), pending_data.data.nonce) + .is_err() + { + return Err(GatewayWireguardError::AuthenticatorMacVerificationFailure); + } + + // 3. ensure we have received a credential + let Some(credential) = final_message.credential() else { + return Err(GatewayWireguardError::MissingAuthenticatorCredential); + }; + + // 4. prepare new peer information and verify the credential + self.process_new_peer(pending_data.clone(), credential) + .await?; + + // 5. remove pending registration + self.pending_registrations.remove_authenticator(&peer).await; + + // 6. construct and return the response + pending_data.to_registered_authenticator_response( + self.upgrade_mode_enabled(), + request_id, + protocol.into(), + reply_to, + ) + } + + pub(crate) async fn on_initial_lp_request( + &self, + init_msg: LpDvpnRegistrationInitialRequest, + sender: ReceiverIndex, + ) -> Result { + let remote_public = init_msg.wg_public_key; + let psk = Key::new(init_msg.psk); + + // 1. check if there's any pending registration already in progress, + // if so, return the same data again without additional processing, + // but update stored PSK + if let Some(pending_registration) = self.check_pending_lp_registration(sender).await? { + self.update_peer_psk(remote_public, psk).await?; + return Ok(pending_registration); + } + + // 2. check if there is already a peer associated with this sender, + // if so, retrieve the "final" data without additional processing, + // but do update stored PSK + if let Some(existing_registration) = self.check_existing_lp_peer(remote_public).await? { + self.update_peer_psk(remote_public, psk).await?; + return Ok(existing_registration); + } + + // 3. process fresh registration request + self.process_fresh_initial_lp_registration(sender, remote_public, psk) + .await + } + + pub(crate) async fn on_final_lp_request( + &self, + final_msg: LpDvpnRegistrationFinalisation, + sender: ReceiverIndex, + ) -> Result { + // 1. check if there's any pending registration associated with this peer + let pending_data = self + .pending_registrations + .check_lp(sender) + .await + .ok_or(GatewayWireguardError::RegistrationNotInProgress)? + .clone(); + + let credential = final_msg.credential; + + // 2. prepare new peer information and verify the credential + self.process_new_peer(pending_data.clone(), credential) + .await?; + + // 3 remove pending registration + self.pending_registrations.remove_lp(sender).await; + + // 4. construct and return the response + Ok(pending_data.to_registered_lp_response(self.upgrade_mode_enabled())) + } +} + +pub struct StaleRegistrationRemover { + pending_registrations: PendingRegistrations, + shutdown_token: ShutdownToken, +} + +impl StaleRegistrationRemover { + // TODO: make it configurable + const STALE_REG_CHECK_INTERVAL: Duration = Duration::from_secs(60); + + pub async fn run(&self) { + let start = Instant::now() + Self::STALE_REG_CHECK_INTERVAL; + let mut interval = interval_at(start, Self::STALE_REG_CHECK_INTERVAL); + loop { + tokio::select! { + biased; + _ = self.shutdown_token.cancelled() => { + trace!("StaleRegistrationRemover: received shutdown"); + break + } + _ = interval.tick() => { + self.pending_registrations.remove_stale_registrations().await + } + } + } + } +} diff --git a/gateway/src/node/wireguard/new_peer_registration/pending.rs b/gateway/src/node/wireguard/new_peer_registration/pending.rs new file mode 100644 index 0000000000..cb933514db --- /dev/null +++ b/gateway/src/node/wireguard/new_peer_registration/pending.rs @@ -0,0 +1,158 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::node::lp_listener::ReceiverIndex; +use crate::node::wireguard::new_peer_registration::helpers::{ + build_final_authenticator_response, build_pending_authenticator_response, +}; +use crate::node::wireguard::GatewayWireguardError; +use defguard_wireguard_rs::key::Key; +use nym_authenticator_requests::AuthenticatorVersion; +use nym_crypto::asymmetric::x25519; +use nym_registration_common::{LpRegistrationResponse, WireguardRegistrationData}; +use nym_sdk::mixnet::Recipient; +use nym_wireguard::ip_pool::IpPair; +use nym_wireguard_types::PeerPublicKey; +use std::collections::HashMap; +use std::sync::Arc; +use std::time::{Duration, Instant}; +use tokio::sync::RwLock; + +const DEFAULT_PENDING_REGISTRATION_TTL: Duration = Duration::from_secs(120); // 2 minutes + +#[derive(Clone)] +pub struct PendingRegistration { + pub(super) requested_on: Instant, + pub(super) data: PendingRegistrationData, +} + +#[derive(Clone)] +pub struct PendingRegistrationData { + pub(super) nonce: u64, + + pub(super) peer_key: PeerPublicKey, + + // will not be set if registering via the Authenticator + pub(super) psk: Option, + + pub(super) wireguard_config: WireguardRegistrationData, +} + +impl PendingRegistration { + pub(crate) fn to_pending_authenticator_response( + &self, + local_key: &x25519::PrivateKey, + upgrade_mode_enabled: bool, + request_id: u64, + version: AuthenticatorVersion, + reply_to: Option, + ) -> Result + { + let nonce = self.data.nonce; + let remote_public = self.data.peer_key; + let wg_port = self.data.wireguard_config.port; + let ip_allocation = IpPair::new( + self.data.wireguard_config.private_ipv4, + self.data.wireguard_config.private_ipv6, + ); + + build_pending_authenticator_response( + ip_allocation, + wg_port, + local_key, + remote_public, + upgrade_mode_enabled, + nonce, + request_id, + version, + reply_to, + ) + } + + pub(crate) fn to_registered_authenticator_response( + &self, + upgrade_mode_enabled: bool, + request_id: u64, + version: AuthenticatorVersion, + reply_to: Option, + ) -> Result + { + let wg_port = self.data.wireguard_config.port; + let local_pub_key = self.data.wireguard_config.public_key.into(); + + let ip_allocation = IpPair::new( + self.data.wireguard_config.private_ipv4, + self.data.wireguard_config.private_ipv6, + ); + + build_final_authenticator_response( + ip_allocation, + wg_port, + local_pub_key, + upgrade_mode_enabled, + request_id, + version, + reply_to, + ) + } + + pub(crate) fn to_pending_lp_response(&self) -> LpRegistrationResponse { + LpRegistrationResponse::request_dvpn_credential() + } + + pub(crate) fn to_registered_lp_response( + &self, + upgrade_mode_enabled: bool, + ) -> LpRegistrationResponse { + LpRegistrationResponse::success_dvpn(self.data.wireguard_config, upgrade_mode_enabled) + } +} + +#[derive(Clone, Default)] +pub(crate) struct PendingRegistrations { + // TODO: unify those, somehow, later + /// Registrations in progress received from the Authenticator service provider via the + /// [`crate::node::internal_service_providers::authenticator::mixnet_listener::MixnetListener`] + pub(crate) authenticator: Arc>>, + + /// Registrations in progress received from the LP Listener via the + /// [`crate::node::lp_listener::handler::LpConnectionHandler`] and handle through + /// [`crate::node::lp_listener::registration::LpHandlerState`] + pub(crate) lp: Arc>>, +} + +impl PendingRegistrations { + pub(crate) async fn check_authenticator( + &self, + peer: &PeerPublicKey, + ) -> Option { + self.authenticator.read().await.get(peer).cloned() + } + + pub(crate) async fn remove_authenticator(&self, peer: &PeerPublicKey) { + self.authenticator.write().await.remove(peer); + } + + pub(crate) async fn remove_lp(&self, receiver_index: ReceiverIndex) { + self.lp.write().await.remove(&receiver_index); + } + + pub(crate) async fn check_lp( + &self, + receiver_index: ReceiverIndex, + ) -> Option { + self.lp.read().await.get(&receiver_index).cloned() + } + + pub(crate) async fn remove_stale_registrations(&self) { + // note: `IpPool` will release stale pre-allocated addresses by itself during the cleanup, + // so there's no need to send explicit messages over + let now = Instant::now(); + self.authenticator.write().await.retain(|_, pending| { + now.duration_since(pending.requested_on) < DEFAULT_PENDING_REGISTRATION_TTL + }); + self.lp.write().await.retain(|_, pending| { + now.duration_since(pending.requested_on) < DEFAULT_PENDING_REGISTRATION_TTL + }); + } +} diff --git a/gateway/src/node/wireguard/peer_manager.rs b/gateway/src/node/wireguard/peer_manager.rs new file mode 100644 index 0000000000..ffb25840e9 --- /dev/null +++ b/gateway/src/node/wireguard/peer_manager.rs @@ -0,0 +1,668 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::node::wireguard::GatewayWireguardError; +use defguard_wireguard_rs::{host::Peer, key::Key}; +use futures::channel::oneshot; +use nym_credential_verification::{ClientBandwidth, TicketVerifier}; +use nym_credentials_interface::CredentialSpendingData; +use nym_metrics::add_histogram_obs; +use nym_wireguard::peer_controller::IpPair; +use nym_wireguard::{peer_controller::PeerControlRequest, WireguardGatewayData}; +use nym_wireguard_types::PeerPublicKey; +use std::time::Instant; +use tracing::error; + +// Histogram buckets for WireGuard peer controller channel latency +// Measures time to send request and receive response from peer controller +// Expected: 1ms-100ms for normal operations, up to 2s for slow conditions +const WG_CONTROLLER_LATENCY_BUCKETS: &[f64] = &[ + 0.001, // 1ms + 0.005, // 5ms + 0.01, // 10ms + 0.05, // 50ms + 0.1, // 100ms + 0.25, // 250ms + 0.5, // 500ms + 1.0, // 1s + 2.0, // 2s +]; + +#[derive(Clone)] +pub struct PeerManager { + pub(crate) wireguard_gateway_data: WireguardGatewayData, +} + +impl PeerManager { + pub fn new(wireguard_gateway_data: WireguardGatewayData) -> Self { + PeerManager { + wireguard_gateway_data, + } + } + + pub async fn preallocate_peer_ip_pair(&self) -> Result { + let controller_start = Instant::now(); + let (response_tx, response_rx) = oneshot::channel(); + let msg = PeerControlRequest::PreAllocateIpPair { response_tx }; + self.wireguard_gateway_data + .peer_tx() + .send(msg) + .await + .map_err(|_| GatewayWireguardError::PeerInteractionStopped)?; + + let res = response_rx + .await + .map_err(|e| { + GatewayWireguardError::InternalError(format!( + "Failed to receive IP allocation: {e}" + )) + })? + .map_err(|e| { + error!("Failed to allocate IPs from pool: {e}"); + GatewayWireguardError::InternalError(format!("Failed to allocate IPs: {e}")) + }); + + let latency = controller_start.elapsed().as_secs_f64(); + add_histogram_obs!( + "wg_peer_controller_channel_latency_seconds", + latency, + WG_CONTROLLER_LATENCY_BUCKETS + ); + + res + } + + pub async fn release_ip_pair(&self, ip_pair: IpPair) -> Result<(), GatewayWireguardError> { + let (response_tx, response_rx) = oneshot::channel(); + let msg = PeerControlRequest::ReleaseIpPair { + response_tx, + ip_pair, + }; + self.wireguard_gateway_data + .peer_tx() + .send(msg) + .await + .map_err(|_| GatewayWireguardError::PeerInteractionStopped)?; + + response_rx + .await + .map_err(|_| GatewayWireguardError::internal("no response for release ip allocation"))? + .map_err(|err| { + GatewayWireguardError::InternalError(format!( + "releasing ip pair not be performed: {err:?}" + )) + })?; + + Ok(()) + } + + pub async fn add_peer(&self, peer: Peer) -> Result<(), GatewayWireguardError> { + let controller_start = Instant::now(); + let (response_tx, response_rx) = oneshot::channel(); + let msg = PeerControlRequest::AddPeer { peer, response_tx }; + self.wireguard_gateway_data + .peer_tx() + .send(msg) + .await + .map_err(|_| GatewayWireguardError::PeerInteractionStopped)?; + + let res = response_rx + .await + .map_err(|_| GatewayWireguardError::internal("no response for add peer".to_string()))? + .map_err(|err| { + GatewayWireguardError::internal(format!( + "adding peer could not be performed: {err:?}" + )) + }); + + let latency = controller_start.elapsed().as_secs_f64(); + add_histogram_obs!( + "wg_peer_controller_channel_latency_seconds", + latency, + WG_CONTROLLER_LATENCY_BUCKETS + ); + + res + } + + pub async fn remove_peer(&self, pub_key: PeerPublicKey) -> Result<(), GatewayWireguardError> { + let controller_start = Instant::now(); + let key = Key::new(pub_key.to_bytes()); + let (response_tx, response_rx) = oneshot::channel(); + let msg = PeerControlRequest::RemovePeer { key, response_tx }; + self.wireguard_gateway_data + .peer_tx() + .send(msg) + .await + .map_err(|_| GatewayWireguardError::PeerInteractionStopped)?; + + let res = response_rx + .await + .map_err(|_| GatewayWireguardError::internal("no response for remove peer"))? + .map_err(|err| { + GatewayWireguardError::InternalError(format!( + "removing peer could not be performed: {err:?}" + )) + }); + + let latency = controller_start.elapsed().as_secs_f64(); + add_histogram_obs!( + "wg_peer_controller_channel_latency_seconds", + latency, + WG_CONTROLLER_LATENCY_BUCKETS + ); + + res + } + + pub async fn check_active_peer( + &self, + pub_key: PeerPublicKey, + ) -> Result { + let controller_start = Instant::now(); + let key = Key::new(pub_key.to_bytes()); + let (response_tx, response_rx) = oneshot::channel(); + let msg = PeerControlRequest::CheckActivePeer { key, response_tx }; + self.wireguard_gateway_data + .peer_tx() + .send(msg) + .await + .map_err(|_| GatewayWireguardError::PeerInteractionStopped)?; + + let res = response_rx + .await + .map_err(|_| GatewayWireguardError::internal("no response for check active peer"))? + .map_err(|err| { + GatewayWireguardError::InternalError(format!( + "check active peer could not be performed: {err:?}" + )) + }); + + let latency = controller_start.elapsed().as_secs_f64(); + add_histogram_obs!( + "wg_peer_controller_channel_latency_seconds", + latency, + WG_CONTROLLER_LATENCY_BUCKETS + ); + + res + } + + pub async fn query_peer( + &self, + public_key: PeerPublicKey, + ) -> Result, GatewayWireguardError> { + let controller_start = Instant::now(); + let key = Key::new(public_key.to_bytes()); + let (response_tx, response_rx) = oneshot::channel(); + let msg = PeerControlRequest::QueryPeer { key, response_tx }; + self.wireguard_gateway_data + .peer_tx() + .send(msg) + .await + .map_err(|_| GatewayWireguardError::PeerInteractionStopped)?; + + let res = response_rx + .await + .map_err(|_| GatewayWireguardError::internal("no response for query peer".to_string()))? + .map_err(|err| { + GatewayWireguardError::internal(format!( + "querying peer could not be performed: {err:?}" + )) + }); + + let latency = controller_start.elapsed().as_secs_f64(); + add_histogram_obs!( + "wg_peer_controller_channel_latency_seconds", + latency, + WG_CONTROLLER_LATENCY_BUCKETS + ); + + res + } + + pub async fn query_bandwidth( + &self, + public_key: PeerPublicKey, + ) -> Result { + let client_bandwidth = self.query_client_bandwidth(public_key).await?; + Ok(client_bandwidth.available().await) + } + + pub async fn query_client_bandwidth( + &self, + key: PeerPublicKey, + ) -> Result { + let controller_start = Instant::now(); + let key = Key::new(key.to_bytes()); + let (response_tx, response_rx) = oneshot::channel(); + let msg = PeerControlRequest::GetClientBandwidthByKey { key, response_tx }; + self.wireguard_gateway_data + .peer_tx() + .send(msg) + .await + .map_err(|_| GatewayWireguardError::PeerInteractionStopped)?; + + let res = response_rx + .await + .map_err(|_| GatewayWireguardError::internal("no response for query client bandwidth"))? + .map_err(|err| { + GatewayWireguardError::internal(format!( + "querying client bandwidth could not be performed: {err:?}" + )) + }); + + let latency = controller_start.elapsed().as_secs_f64(); + add_histogram_obs!( + "wg_peer_controller_channel_latency_seconds", + latency, + WG_CONTROLLER_LATENCY_BUCKETS + ); + + res + } + + pub async fn query_verifier_by_key( + &self, + key: PeerPublicKey, + credential: CredentialSpendingData, + ) -> Result, GatewayWireguardError> { + let controller_start = Instant::now(); + let key = Key::new(key.to_bytes()); + let (response_tx, response_rx) = oneshot::channel(); + let msg = PeerControlRequest::GetVerifierByKey { + key, + credential: Box::new(credential), + response_tx, + }; + self.wireguard_gateway_data + .peer_tx() + .send(msg) + .await + .map_err(|_| GatewayWireguardError::PeerInteractionStopped)?; + + let res = response_rx + .await + .map_err(|_| { + GatewayWireguardError::internal("no response for query verifier".to_string()) + })? + .map_err(|err| { + GatewayWireguardError::internal(format!( + "querying verifier could not be performed: {err:?}" + )) + }); + + let latency = controller_start.elapsed().as_secs_f64(); + add_histogram_obs!( + "wg_peer_controller_channel_latency_seconds", + latency, + WG_CONTROLLER_LATENCY_BUCKETS + ); + + res + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::node::wireguard::PeerRegistrator; + use crate::nym_authenticator::config::Authenticator; + use defguard_wireguard_rs::net::IpAddrMask; + use nym_credential_verification::upgrade_mode::testing::mock_dummy_upgrade_mode_details; + use nym_credential_verification::{ + bandwidth_storage_manager::BandwidthStorageManager, ecash::MockEcashManager, + }; + use nym_credentials_interface::Bandwidth; + use nym_crypto::asymmetric::x25519::KeyPair; + use nym_gateway_storage::traits::{mock::MockGatewayStorage, BandwidthGatewayStorage}; + use nym_task::ShutdownManager; + use nym_test_utils::helpers::{deterministic_rng, DeterministicRng, RngCore}; + use nym_wireguard::peer_controller::{start_controller, stop_controller}; + use std::{str::FromStr, sync::Arc}; + use time::{Duration, OffsetDateTime}; + use tokio::sync::RwLock; + + const CREDENTIAL_BYTES: [u8; 1245] = [ + 0, 0, 4, 133, 96, 179, 223, 185, 136, 23, 213, 166, 59, 203, 66, 69, 209, 181, 227, 254, + 16, 102, 98, 237, 59, 119, 170, 111, 31, 194, 51, 59, 120, 17, 115, 229, 79, 91, 11, 139, + 154, 2, 212, 23, 68, 70, 167, 3, 240, 54, 224, 171, 221, 1, 69, 48, 60, 118, 119, 249, 123, + 35, 172, 227, 131, 96, 232, 209, 187, 123, 4, 197, 102, 90, 96, 45, 125, 135, 140, 99, 1, + 151, 17, 131, 143, 157, 97, 107, 139, 232, 212, 87, 14, 115, 253, 255, 166, 167, 186, 43, + 90, 96, 173, 105, 120, 40, 10, 163, 250, 224, 214, 200, 178, 4, 160, 16, 130, 59, 76, 193, + 39, 240, 3, 101, 141, 209, 183, 226, 186, 207, 56, 210, 187, 7, 164, 240, 164, 205, 37, 81, + 184, 214, 193, 195, 90, 205, 238, 225, 195, 104, 12, 123, 203, 57, 233, 243, 215, 145, 195, + 196, 57, 38, 125, 172, 18, 47, 63, 165, 110, 219, 180, 40, 58, 116, 92, 254, 160, 98, 48, + 92, 254, 232, 107, 184, 80, 234, 60, 160, 235, 249, 76, 41, 38, 165, 28, 40, 136, 74, 48, + 166, 50, 245, 23, 201, 140, 101, 79, 93, 235, 128, 186, 146, 126, 180, 134, 43, 13, 186, + 19, 195, 48, 168, 201, 29, 216, 95, 176, 198, 132, 188, 64, 39, 212, 150, 32, 52, 53, 38, + 228, 199, 122, 226, 217, 75, 40, 191, 151, 48, 164, 242, 177, 79, 14, 122, 105, 151, 85, + 88, 199, 162, 17, 96, 103, 83, 178, 128, 9, 24, 30, 74, 108, 241, 85, 240, 166, 97, 241, + 85, 199, 11, 198, 226, 234, 70, 107, 145, 28, 208, 114, 51, 12, 234, 108, 101, 202, 112, + 48, 185, 22, 159, 67, 109, 49, 27, 149, 90, 109, 32, 226, 112, 7, 201, 208, 209, 104, 31, + 97, 134, 204, 145, 27, 181, 206, 181, 106, 32, 110, 136, 115, 249, 201, 111, 5, 245, 203, + 71, 121, 169, 126, 151, 178, 236, 59, 221, 195, 48, 135, 115, 6, 50, 227, 74, 97, 107, 107, + 213, 90, 2, 203, 154, 138, 47, 128, 52, 134, 128, 224, 51, 65, 240, 90, 8, 55, 175, 180, + 178, 204, 206, 168, 110, 51, 57, 189, 169, 48, 169, 136, 121, 99, 51, 170, 178, 214, 74, 1, + 96, 151, 167, 25, 173, 180, 171, 155, 10, 55, 142, 234, 190, 113, 90, 79, 80, 244, 71, 166, + 30, 235, 113, 150, 133, 1, 218, 17, 109, 111, 223, 24, 216, 177, 41, 2, 204, 65, 221, 212, + 207, 236, 144, 6, 65, 224, 55, 42, 1, 1, 161, 134, 118, 127, 111, 220, 110, 127, 240, 71, + 223, 129, 12, 93, 20, 220, 60, 56, 71, 146, 184, 95, 132, 69, 28, 56, 53, 192, 213, 22, + 119, 230, 152, 225, 182, 188, 163, 219, 37, 175, 247, 73, 14, 247, 38, 72, 243, 1, 48, 131, + 59, 8, 13, 96, 143, 185, 127, 241, 161, 217, 24, 149, 193, 40, 16, 30, 202, 151, 28, 119, + 240, 153, 101, 156, 61, 193, 72, 245, 199, 181, 12, 231, 65, 166, 67, 142, 121, 207, 202, + 58, 197, 113, 188, 248, 42, 124, 105, 48, 161, 241, 55, 209, 36, 194, 27, 63, 233, 144, + 189, 85, 117, 234, 9, 139, 46, 31, 206, 114, 95, 131, 29, 240, 13, 81, 142, 140, 133, 33, + 30, 41, 141, 37, 80, 217, 95, 221, 76, 115, 86, 201, 165, 51, 252, 9, 28, 209, 1, 48, 150, + 74, 248, 212, 187, 222, 66, 210, 3, 200, 19, 217, 171, 184, 42, 148, 53, 150, 57, 50, 6, + 227, 227, 62, 49, 42, 148, 148, 157, 82, 191, 58, 24, 34, 56, 98, 120, 89, 105, 176, 85, + 15, 253, 241, 41, 153, 195, 136, 1, 48, 142, 126, 213, 101, 223, 79, 133, 230, 105, 38, + 161, 149, 2, 21, 136, 150, 42, 72, 218, 85, 146, 63, 223, 58, 108, 186, 183, 248, 62, 20, + 47, 34, 113, 160, 177, 204, 181, 16, 24, 212, 224, 35, 84, 51, 168, 56, 136, 11, 1, 48, + 135, 242, 62, 149, 230, 178, 32, 224, 119, 26, 234, 163, 237, 224, 114, 95, 112, 140, 170, + 150, 96, 125, 136, 221, 180, 78, 18, 11, 12, 184, 2, 198, 217, 119, 43, 69, 4, 172, 109, + 55, 183, 40, 131, 172, 161, 88, 183, 101, 1, 48, 173, 216, 22, 73, 42, 255, 211, 93, 249, + 87, 159, 115, 61, 91, 55, 130, 17, 216, 60, 34, 122, 55, 8, 244, 244, 153, 151, 57, 5, 144, + 178, 55, 249, 64, 211, 168, 34, 148, 56, 89, 92, 203, 70, 124, 219, 152, 253, 165, 0, 32, + 203, 116, 63, 7, 240, 222, 82, 86, 11, 149, 167, 72, 224, 55, 190, 66, 201, 65, 168, 184, + 96, 47, 194, 241, 168, 124, 7, 74, 214, 250, 37, 76, 32, 218, 69, 122, 103, 215, 145, 169, + 24, 212, 229, 168, 106, 10, 144, 31, 13, 25, 178, 242, 250, 106, 159, 40, 48, 163, 165, 61, + 130, 57, 146, 4, 73, 32, 254, 233, 125, 135, 212, 29, 111, 4, 177, 114, 15, 210, 170, 82, + 108, 110, 62, 166, 81, 209, 106, 176, 156, 14, 133, 242, 60, 127, 120, 242, 28, 97, 0, 1, + 32, 103, 93, 109, 89, 240, 91, 1, 84, 150, 50, 206, 157, 203, 49, 220, 120, 234, 175, 234, + 150, 126, 225, 94, 163, 164, 199, 138, 114, 62, 99, 106, 112, 1, 32, 171, 40, 220, 82, 241, + 203, 76, 146, 111, 139, 182, 179, 237, 182, 115, 75, 128, 201, 107, 43, 214, 0, 135, 217, + 160, 68, 150, 232, 144, 114, 237, 98, 32, 30, 134, 232, 59, 93, 163, 253, 244, 13, 202, 52, + 147, 168, 83, 121, 123, 95, 21, 210, 209, 225, 223, 143, 49, 10, 205, 238, 1, 22, 83, 81, + 70, 1, 32, 26, 76, 6, 234, 160, 50, 139, 102, 161, 232, 155, 106, 130, 171, 226, 210, 233, + 178, 85, 247, 71, 123, 55, 53, 46, 67, 148, 137, 156, 207, 208, 107, 1, 32, 102, 31, 4, 98, + 110, 156, 144, 61, 229, 140, 198, 84, 196, 238, 128, 35, 131, 182, 137, 125, 241, 95, 69, + 131, 170, 27, 2, 144, 75, 72, 242, 102, 3, 32, 121, 80, 45, 173, 56, 65, 218, 27, 40, 251, + 197, 32, 169, 104, 123, 110, 90, 78, 153, 166, 38, 9, 129, 228, 99, 8, 1, 116, 142, 233, + 162, 69, 32, 216, 169, 159, 116, 95, 12, 63, 176, 195, 6, 183, 123, 135, 75, 61, 112, 106, + 83, 235, 176, 41, 27, 248, 48, 71, 165, 170, 12, 92, 103, 103, 81, 32, 58, 74, 75, 145, + 192, 94, 153, 69, 80, 128, 241, 3, 16, 117, 192, 86, 161, 103, 44, 174, 211, 196, 182, 124, + 55, 11, 107, 142, 49, 88, 6, 41, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 37, 139, 240, 0, 0, + 0, 0, 0, 0, 0, 1, + ]; + + struct TestSetup { + rng: DeterministicRng, + _ecash_manager: Arc, + storage: Arc>, + peer_registrator: PeerRegistrator, + peer_manager: PeerManager, + task_manager: ShutdownManager, + } + + struct GeneratedPeer { + peer: Peer, + client_id: i64, + } + + impl GeneratedPeer { + fn key(&self) -> PeerPublicKey { + PeerPublicKey::from_str(self.peer.public_key.to_string().as_str()).unwrap() + } + } + + impl TestSetup { + fn new() -> TestSetup { + let mut rng = deterministic_rng(); + let (wireguard_data, request_rx) = WireguardGatewayData::new( + Authenticator::default().into(), + Arc::new(KeyPair::new(&mut rng)), + ); + + let (upgrade_mode_details, _) = mock_dummy_upgrade_mode_details(); + let peer_manager = PeerManager::new(wireguard_data); + + let (storage, task_manager) = start_controller( + peer_manager.wireguard_gateway_data.peer_tx().clone(), + request_rx, + ); + + let ecash_manager = Arc::new(MockEcashManager::new(Box::new(storage.clone()))); + let peer_registrator = PeerRegistrator::new( + ecash_manager.clone(), + peer_manager.clone(), + upgrade_mode_details, + ); + + TestSetup { + rng, + _ecash_manager: ecash_manager, + storage, + peer_registrator, + peer_manager, + task_manager, + } + } + + async fn peer_with_pre_allocated_ip(&mut self) -> Peer { + let mut peer = Peer::default(); + let mut key = [0u8; 32]; + self.rng.fill_bytes(&mut key); + peer.public_key = Key::new(key); + + let allocation = self.peer_manager.preallocate_peer_ip_pair().await.unwrap(); + peer.allowed_ips = vec![ + IpAddrMask::new(allocation.ipv4.into(), 32), + IpAddrMask::new(allocation.ipv6.into(), 128), + ]; + + peer + } + + async fn _add_peer(&self, peer: &Peer) -> i64 { + let client_id = self + .storage + .insert_wireguard_peer(peer, FromStr::from_str("entry_wireguard").unwrap()) + .await + .unwrap(); + self.peer_registrator + .credential_storage_preparation(client_id) + .await + .unwrap(); + self.peer_manager.add_peer(peer.clone()).await.unwrap(); + client_id + } + + async fn add_peer(&mut self) -> GeneratedPeer { + let peer = self.peer_with_pre_allocated_ip().await; + let client_id = self._add_peer(&peer).await; + + GeneratedPeer { peer, client_id } + } + + async fn finish(self) { + stop_controller(self.task_manager).await + } + } + + #[tokio::test] + async fn assign_peer_ip() -> anyhow::Result<()> { + let test = TestSetup::new(); + + let ip_pair1 = test.peer_manager.preallocate_peer_ip_pair().await?; + let ip_pair2 = test.peer_manager.preallocate_peer_ip_pair().await?; + assert_ne!(ip_pair1, ip_pair2); + + test.finish().await; + + Ok(()) + } + + #[tokio::test] + async fn add_peer() { + let mut test = TestSetup::new(); + let peer = test.peer_with_pre_allocated_ip().await; + + assert!(test.peer_manager.add_peer(peer.clone()).await.is_err()); + + let client_id = test + .storage + .insert_wireguard_peer(&peer, FromStr::from_str("entry_wireguard").unwrap()) + .await + .unwrap(); + assert!(test.peer_manager.add_peer(peer.clone()).await.is_err()); + + test.peer_registrator + .credential_storage_preparation(client_id) + .await + .unwrap(); + test.peer_manager.add_peer(peer.clone()).await.unwrap(); + + test.finish().await + } + + #[tokio::test] + async fn remove_peer() { + let mut test = TestSetup::new(); + let peer = test.add_peer().await; + let public_key = peer.key(); + + test.peer_manager.remove_peer(public_key).await.unwrap(); + + test.finish().await + } + + #[tokio::test] + async fn query_peer() { + let mut test = TestSetup::new(); + let peer = test.peer_with_pre_allocated_ip().await; + let public_key = PeerPublicKey::from_str(peer.public_key.to_string().as_str()).unwrap(); + + assert!(test + .peer_manager + .query_peer(public_key) + .await + .unwrap() + .is_none()); + + test._add_peer(&peer).await; + let peer_query = test + .peer_manager + .query_peer(public_key) + .await + .unwrap() + .unwrap(); + assert_eq!(peer.public_key, peer_query.public_key); + + test.finish().await + } + + #[tokio::test] + async fn query_bandwidth() { + let mut test = TestSetup::new(); + let peer = test.peer_with_pre_allocated_ip().await; + let public_key = PeerPublicKey::from_str(peer.public_key.to_string().as_str()).unwrap(); + + assert!(test.peer_manager.query_bandwidth(public_key).await.is_err()); + + test._add_peer(&peer).await; + let available_bandwidth = test.peer_manager.query_bandwidth(public_key).await.unwrap(); + assert_eq!(available_bandwidth, 0); + + test.finish().await + } + + #[tokio::test] + async fn query_client_bandwidth() { + let mut test = TestSetup::new(); + let peer = test.peer_with_pre_allocated_ip().await; + let public_key = PeerPublicKey::from_str(peer.public_key.to_string().as_str()).unwrap(); + + assert!(test + .peer_manager + .query_client_bandwidth(public_key) + .await + .is_err()); + + test._add_peer(&peer).await; + let available_bandwidth = test + .peer_manager + .query_client_bandwidth(public_key) + .await + .unwrap() + .available() + .await; + assert_eq!(available_bandwidth, 0); + + test.finish().await + } + + #[tokio::test] + async fn query_verifier() { + let mut test = TestSetup::new(); + let peer = test.peer_with_pre_allocated_ip().await; + let public_key = PeerPublicKey::from_str(peer.public_key.to_string().as_str()).unwrap(); + + let credential = CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap(); + + assert!(test + .peer_manager + .query_verifier_by_key(public_key, credential.clone()) + .await + .is_err()); + + test._add_peer(&peer).await; + test.peer_manager + .query_verifier_by_key(public_key, credential) + .await + .unwrap(); + + test.finish().await + } + + #[tokio::test] + async fn increase_decrease_bandwidth() { + let mut test = TestSetup::new(); + let peer = test.add_peer().await; + let public_key = peer.key(); + + let top_up = 42; + let consume = 4; + + let client_bandwidth = test + .peer_manager + .query_client_bandwidth(peer.key()) + .await + .unwrap(); + + let mut bw_manager = BandwidthStorageManager::new( + Box::new(test.storage.clone()), + client_bandwidth.clone(), + peer.client_id, + Default::default(), + true, + ); + bw_manager + .increase_bandwidth( + Bandwidth::new_unchecked(top_up as u64), + OffsetDateTime::now_utc() + .checked_add(Duration::minutes(1)) + .unwrap(), + ) + .await + .unwrap(); + + assert_eq!(client_bandwidth.available().await, top_up); + assert_eq!( + test.peer_manager.query_bandwidth(public_key).await.unwrap(), + top_up + ); + + bw_manager.try_use_bandwidth(consume).await.unwrap(); + let remaining = top_up - consume; + assert_eq!(client_bandwidth.available().await, remaining); + assert_eq!( + test.peer_manager.query_bandwidth(public_key).await.unwrap(), + remaining + ); + + test.finish().await + } +} diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index 2023980518..3d825c7e73 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -9,6 +9,7 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +publish = false [dependencies] anyhow = { workspace = true } @@ -25,7 +26,7 @@ nym-lp-transport = { path = "../common/nym-lp-transport", features = ["io-mocks" nym-gateway = { path = "../gateway" } sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite"] } tracing = { workspace = true } - +futures = { workspace = true } [lints] workspace = true diff --git a/integration-tests/src/lp_registration.rs b/integration-tests/src/lp_registration.rs index 28d209adc2..15074efc0e 100644 --- a/integration-tests/src/lp_registration.rs +++ b/integration-tests/src/lp_registration.rs @@ -6,14 +6,16 @@ mod tests { use anyhow::Context; use nym_bandwidth_controller::mock::MockBandwidthController; use nym_credential_verification::ecash::MockEcashManager; + use nym_credential_verification::upgrade_mode::testing::mock_dummy_upgrade_mode_details; use nym_credentials_interface::TicketType; use nym_crypto::asymmetric::{ed25519, x25519}; use nym_gateway::GatewayError; use nym_gateway::node::lp_listener::handler::LpConnectionHandler; use nym_gateway::node::lp_listener::{ - LpDebug, LpHandlerState, MixForwardingReceiver, PeerControlRequest, WireguardGatewayData, - mix_forwarding_channels, + LpDebug, LpHandlerState, LpLocalPeer, MixForwardingReceiver, PeerControlRequest, + WireguardGatewayData, mix_forwarding_channels, }; + use nym_gateway::node::wireguard::{PeerManager, PeerRegistrator}; use nym_gateway::node::{ActiveClientsStore, GatewayStorage, LpConfig}; use nym_registration_client::{LpClientError, LpRegistrationClient}; use nym_test_utils::helpers::{CryptoRng, RngCore, u64_seeded_rng}; @@ -46,9 +48,10 @@ mod tests { } struct Party { - ed25519_keys: Arc, + peer: LpLocalPeer, x25519_wg_keys: Arc, socket_addr: SocketAddr, + lp_version: u8, } impl Party { @@ -58,11 +61,17 @@ mod tests { rng.fill_bytes(&mut ip); rng.fill_bytes(&mut port); + let ed25519_keys = Arc::new(ed25519::KeyPair::new(rng)); + let x25519_wg_keys = Arc::new(x25519::KeyPair::new(rng)); + + let lp_x25519_keys = Arc::new(ed25519_keys.to_x25519()); Party { - ed25519_keys: Arc::new(ed25519::KeyPair::new(rng)), - x25519_wg_keys: Arc::new(x25519::KeyPair::new(rng)), + peer: LpLocalPeer::new(ed25519_keys, lp_x25519_keys.clone()) + .with_kem_psq_key(lp_x25519_keys), + x25519_wg_keys, socket_addr: SocketAddr::from((ip, u16::from_le_bytes(port))), + lp_version: 1, } } } @@ -154,10 +163,9 @@ mod tests { .unwrap() } - async fn allocate_ip_pair(&mut self) -> IpPair { + fn pre_allocate_ip_pair(&mut self) -> IpPair { self.ip_pool - .allocate() - .await + .pre_allocate() .expect("unexpected ip allocation failure!") } @@ -194,33 +202,35 @@ mod tests { // create wireguard data let (wireguard_data, peer_request_rx) = Self::wireguard_data(&base); + let (upgrade_mode_details, _) = mock_dummy_upgrade_mode_details(); + // mock the wg peer controller let (mock_peer_controller, peer_controller_state) = mock_peer_controller(peer_request_rx); // registering particular responses for peer controller is up to given test + let ecash_verifier = Arc::new(ecash_verifier); + + let peer_registrator = PeerRegistrator::new( + ecash_verifier.clone(), + PeerManager::new(wireguard_data), + upgrade_mode_details, + ); let lp_state = LpHandlerState { // use mock instance of ecash verifier - ecash_verifier: Arc::new(ecash_verifier), + ecash_verifier, // use in-memory database (no need for persistency) storage, - // reuse the same identity we just generated - local_identity: base.ed25519_keys.clone(), + local_lp_peer: base.peer.clone(), - // we don't care about metrics - all zeroes are perfectly fine metrics: Default::default(), // no clients at the beginning active_clients_store: ActiveClientsStore::new(), - // handles required for wg registration - wg_peer_controller: Some(wireguard_data.peer_tx().clone()), - - wireguard_data: Some(wireguard_data), - // use default lp config (with enabled flag) lp_config, @@ -233,8 +243,10 @@ mod tests { // we start with empty state session_states: Arc::new(Default::default()), - // sensible default value for tests forward_semaphore, + + // handles for dealing with new peers + peer_registrator: Some(peer_registrator), }; Ok(Gateway { @@ -373,6 +385,7 @@ mod tests { mod using_lp_registration_client { use super::*; use nym_registration_client::NestedLpSession; + use nym_wireguard::DefguardPeer; #[tokio::test] async fn test_basic_lp_entry_registration() -> anyhow::Result<()> { @@ -385,11 +398,11 @@ mod tests { let client_key = *client_data.base.x25519_wg_keys.public_key(); let mut entry = Gateway::mock(&mut gateway_rng).await?; - let mut client = LpRegistrationClient::::new_with_default_psk( - client_data.base.ed25519_keys, - *entry.base.ed25519_keys.public_key(), + let mut client = LpRegistrationClient::::new_with_default_config( + client_data.base.peer.ed25519().clone(), + entry.base.peer.as_remote(), entry.base.socket_addr, - client_data.base.socket_addr.ip(), + entry.base.lp_version, ); // 1. establish mock connection between client and gateway and retrieve gateway's handle @@ -406,13 +419,12 @@ mod tests { // 3. register all needed responses for the dvpn registration that will reach the peer controller // 1) peer registration - ip pair allocation - let ip_pair = entry.allocate_ip_pair().await; + let ip_pair = entry.pre_allocate_ip_pair(); let reg_res = Ok::<_, nym_wireguard::Error>(ip_pair); - let public_key = client_key.to_wg_key(); entry .register_peer_controller_response( - PeerControlRequestType::RegisterPeer { public_key }, + PeerControlRequestType::AllocatePeerIpPair {}, reg_res, ) .await; @@ -428,6 +440,16 @@ mod tests { ) .await; + // 3) peer query - check for prior registrations + let query_res = Ok::<_, nym_wireguard::Error>(Option::::None); + let key = client_key.to_wg_key(); + entry + .register_peer_controller_response( + PeerControlRequestType::QueryPeer { key }, + query_res, + ) + .await; + // 4. spawn peer controller to be able to handle dvpn registration requests entry.spawn_peer_controller(); @@ -436,9 +458,10 @@ mod tests { // 6. perform registration with entry only let wg_keypair = client_data.base.x25519_wg_keys; - let gateway_identity = entry.base.ed25519_keys.public_key(); + let gateway_identity = entry.base.peer.ed25519().public_key(); let registration_result = client - .register( + .register_dvpn( + &mut client_rng, &wg_keypair, gateway_identity, &client_data.ticket_provider, @@ -451,7 +474,6 @@ mod tests { let peers_guard = entry.mock_peer_controller_state.peers.read().await; let peer = peers_guard.get_by_x25519_key(&client_key).unwrap().clone(); drop(peers_guard); - assert!(peer.register_success); assert!(peer.add_success); assert_eq!(registration_result.private_ipv4, ip_pair.ipv4); @@ -476,11 +498,11 @@ mod tests { let client_data = Client::mock(&mut client_rng); let mut entry = Gateway::mock(&mut gateway_rng).await?; - let mut client = LpRegistrationClient::::new_with_default_psk( - client_data.base.ed25519_keys, - *entry.base.ed25519_keys.public_key(), + let mut client = LpRegistrationClient::::new_with_default_config( + client_data.base.peer.ed25519().clone(), + entry.base.peer.as_remote(), entry.base.socket_addr, - client_data.base.socket_addr.ip(), + entry.base.lp_version, ); // 1. establish mock connection between client and gateway and retrieve gateway's handle @@ -502,9 +524,10 @@ mod tests { // 4. perform registration with entry only // but WITHOUT performing the handshake let wg_keypair = client_data.base.x25519_wg_keys; - let gateway_identity = entry.base.ed25519_keys.public_key(); + let gateway_identity = entry.base.peer.ed25519().public_key(); let registration_result = client - .register( + .register_dvpn( + &mut client_rng, &wg_keypair, gateway_identity, &client_data.ticket_provider, @@ -517,7 +540,10 @@ mod tests { let LpClientError::Transport(err) = registration_result else { panic!("unexpected error"); }; - assert_eq!(err, "Cannot register: handshake not completed"); + assert_eq!( + err, + "State machine not available - has the handshake been completed?" + ); // 5. stop the gateway task and finish the test entry.stop_tasks().await?; @@ -526,6 +552,7 @@ mod tests { #[tokio::test] async fn test_basic_lp_exit_registration() -> anyhow::Result<()> { + // nym_test_utils::helpers::setup_test_logger(); // initialise random, but deterministic, keys, addresses, etc. for the parties let mut client_rng = u64_seeded_rng(0); let mut entry_rng = u64_seeded_rng(1); @@ -536,11 +563,11 @@ mod tests { let mut entry = Gateway::mock(&mut entry_rng).await?; let mut exit = Gateway::mock(&mut exit_rng).await?; - let mut entry_client = LpRegistrationClient::::new_with_default_psk( - client_data.base.ed25519_keys.clone(), - *entry.base.ed25519_keys.public_key(), + let mut entry_client = LpRegistrationClient::::new_with_default_config( + client_data.base.peer.ed25519().clone(), + entry.base.peer.as_remote(), entry.base.socket_addr, - client_data.base.socket_addr.ip(), + entry.base.lp_version, ); // START: ENTRY SETUP @@ -565,13 +592,12 @@ mod tests { // 4. register all needed responses for the dvpn registration that will reach the peer controller // 1) peer registration - ip pair allocation - let entry_ip_pair = entry.allocate_ip_pair().await; + let entry_ip_pair = entry.pre_allocate_ip_pair(); let reg_res = Ok::<_, nym_wireguard::Error>(entry_ip_pair); - let public_key = client_key.to_wg_key(); entry .register_peer_controller_response( - PeerControlRequestType::RegisterPeer { public_key }, + PeerControlRequestType::AllocatePeerIpPair {}, reg_res, ) .await; @@ -587,6 +613,16 @@ mod tests { ) .await; + // 3) peer query - check for prior registrations + let query_res = Ok::<_, nym_wireguard::Error>(Option::::None); + let key = client_key.to_wg_key(); + entry + .register_peer_controller_response( + PeerControlRequestType::QueryPeer { key }, + query_res, + ) + .await; + // 5. spawn peer controller to be able to handle dvpn registration requests entry.spawn_peer_controller(); @@ -607,12 +643,11 @@ mod tests { // 10. register all needed responses for the dvpn registration that will reach the peer controller // 1) peer registration - ip pair allocation - let exit_ip_pair = exit.allocate_ip_pair().await; + let exit_ip_pair = exit.pre_allocate_ip_pair(); let reg_res = Ok::<_, nym_wireguard::Error>(exit_ip_pair); - let public_key = client_key.to_wg_key(); exit.register_peer_controller_response( - PeerControlRequestType::RegisterPeer { public_key }, + PeerControlRequestType::AllocatePeerIpPair {}, reg_res, ) .await; @@ -627,6 +662,15 @@ mod tests { ) .await; + // 3) peer query - check for prior registrations + let query_res = Ok::<_, nym_wireguard::Error>(Option::::None); + let key = client_key.to_wg_key(); + exit.register_peer_controller_response( + PeerControlRequestType::QueryPeer { key }, + query_res, + ) + .await; + // 11. spawn peer controller to be able to handle dvpn registration requests exit.spawn_peer_controller(); @@ -636,30 +680,31 @@ mod tests { // technically we should use different ephemeral keys than we had for the entry // but crypto is going to work the same let mut nested_session = NestedLpSession::new( - exit.base.ed25519_keys.public_key().to_bytes(), - exit.base.socket_addr.to_string(), - client_data.base.ed25519_keys, - *exit.base.ed25519_keys.public_key(), + exit.base.socket_addr, + client_data.base.peer.ed25519().clone(), + exit.base.peer.as_remote(), + exit.base.lp_version, ); // 13. Perform handshake and registration with exit gateway (all via entry forwarding) let exit_registration_result = nested_session - .handshake_and_register( + .handshake_and_register_dvpn( &mut entry_client, + &mut client_rng, &client_data.base.x25519_wg_keys, - exit.base.ed25519_keys.public_key(), + exit.base.peer.ed25519().public_key(), &client_data.ticket_provider, TicketType::V1WireguardExit, - client_data.base.socket_addr.ip(), ) .timeboxed() .await??; // 14. complete registration with the entry let entry_registration_result = entry_client - .register( + .register_dvpn( + &mut client_rng, &client_data.base.x25519_wg_keys, - entry.base.ed25519_keys.public_key(), + entry.base.peer.ed25519().public_key(), &client_data.ticket_provider, TicketType::V1WireguardEntry, ) @@ -670,13 +715,11 @@ mod tests { let peers_guard = entry.mock_peer_controller_state.peers.read().await; let entry_peer = peers_guard.get_by_x25519_key(&client_key).unwrap().clone(); drop(peers_guard); - assert!(entry_peer.register_success); assert!(entry_peer.add_success); let peers_guard = exit.mock_peer_controller_state.peers.read().await; let exit_peer = peers_guard.get_by_x25519_key(&client_key).unwrap().clone(); drop(peers_guard); - assert!(exit_peer.register_success); assert!(exit_peer.add_success); assert_eq!(entry_registration_result.private_ipv4, entry_ip_pair.ipv4); diff --git a/nym-api/.sqlx/query-00d857b624e7edab1198114b17cbad1e16988a3f9989d135840500e1143ce5e5.json b/nym-api/.sqlx/query-00d857b624e7edab1198114b17cbad1e16988a3f9989d135840500e1143ce5e5.json deleted file mode 100644 index 03474ab35e..0000000000 --- a/nym-api/.sqlx/query-00d857b624e7edab1198114b17cbad1e16988a3f9989d135840500e1143ce5e5.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n SELECT epoch_id as \"epoch_id: u32\", serialised_signatures, serialization_revision as \"serialization_revision: u8\"\n FROM expiration_date_signatures\n WHERE expiration_date = ?\n ", - "describe": { - "columns": [ - { - "name": "epoch_id: u32", - "ordinal": 0, - "type_info": "Integer" - }, - { - "name": "serialised_signatures", - "ordinal": 1, - "type_info": "Blob" - }, - { - "name": "serialization_revision: u8", - "ordinal": 2, - "type_info": "Integer" - } - ], - "parameters": { - "Right": 1 - }, - "nullable": [ - false, - false, - false - ] - }, - "hash": "00d857b624e7edab1198114b17cbad1e16988a3f9989d135840500e1143ce5e5" -} diff --git a/nym-api/.sqlx/query-1d4535b58abdefaaca96bc7312fe14f63ccb56fa62976f7ce3d3b4f6eca8b711.json b/nym-api/.sqlx/query-1d4535b58abdefaaca96bc7312fe14f63ccb56fa62976f7ce3d3b4f6eca8b711.json deleted file mode 100644 index 131935337d..0000000000 --- a/nym-api/.sqlx/query-1d4535b58abdefaaca96bc7312fe14f63ccb56fa62976f7ce3d3b4f6eca8b711.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n SELECT timestamp, reliability as \"reliability: u8\"\n FROM gateway_status\n JOIN gateway_details\n ON gateway_status.gateway_details_id = gateway_details.id\n WHERE gateway_details.node_id=? AND gateway_status.timestamp > ?;\n ", - "describe": { - "columns": [ - { - "name": "timestamp", - "ordinal": 0, - "type_info": "Integer" - }, - { - "name": "reliability: u8", - "ordinal": 1, - "type_info": "Integer" - } - ], - "parameters": { - "Right": 2 - }, - "nullable": [ - true, - true - ] - }, - "hash": "1d4535b58abdefaaca96bc7312fe14f63ccb56fa62976f7ce3d3b4f6eca8b711" -} diff --git a/nym-api/.sqlx/query-2f3c12cb0c48084b569e12ecb0315212a6f526f78258e173c96ec177988696ef.json b/nym-api/.sqlx/query-2f3c12cb0c48084b569e12ecb0315212a6f526f78258e173c96ec177988696ef.json new file mode 100644 index 0000000000..405049043d --- /dev/null +++ b/nym-api/.sqlx/query-2f3c12cb0c48084b569e12ecb0315212a6f526f78258e173c96ec177988696ef.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n DELETE FROM emergency_credential\n WHERE id = ?\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 1 + }, + "nullable": [] + }, + "hash": "2f3c12cb0c48084b569e12ecb0315212a6f526f78258e173c96ec177988696ef" +} diff --git a/nym-api/.sqlx/query-4a4f3b32b313f7fbc6eb579659e7cec1442967e53764b83ba0a66cd9a72494f9.json b/nym-api/.sqlx/query-4a4f3b32b313f7fbc6eb579659e7cec1442967e53764b83ba0a66cd9a72494f9.json new file mode 100644 index 0000000000..e71f300174 --- /dev/null +++ b/nym-api/.sqlx/query-4a4f3b32b313f7fbc6eb579659e7cec1442967e53764b83ba0a66cd9a72494f9.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n DELETE FROM emergency_credential\n WHERE type = ?\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 1 + }, + "nullable": [] + }, + "hash": "4a4f3b32b313f7fbc6eb579659e7cec1442967e53764b83ba0a66cd9a72494f9" +} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-3cc446220668fb3e02f0578104291d2a2af57656b405212af414d765b2263347.json b/nym-api/.sqlx/query-5d3b8ad051ab6f46c702308c2fc751a5ca340ac9c6dd86da1a5e9a3e65ea589f.json similarity index 72% rename from nym-node-status-api/nym-node-status-api/.sqlx/query-3cc446220668fb3e02f0578104291d2a2af57656b405212af414d765b2263347.json rename to nym-api/.sqlx/query-5d3b8ad051ab6f46c702308c2fc751a5ca340ac9c6dd86da1a5e9a3e65ea589f.json index 9d8cff1326..eb6a62ba22 100644 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-3cc446220668fb3e02f0578104291d2a2af57656b405212af414d765b2263347.json +++ b/nym-api/.sqlx/query-5d3b8ad051ab6f46c702308c2fc751a5ca340ac9c6dd86da1a5e9a3e65ea589f.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "\n SELECT serialised_signatures, serialization_revision as \"serialization_revision: u8\"\n FROM global_expiration_date_signatures\n WHERE expiration_date = ? AND epoch_id = ?\n ", + "query": "\n SELECT serialised_signatures, serialization_revision as \"serialization_revision: u8\"\n FROM expiration_date_signatures\n WHERE expiration_date = ? AND epoch_id = ?\n ", "describe": { "columns": [ { @@ -22,5 +22,5 @@ false ] }, - "hash": "3cc446220668fb3e02f0578104291d2a2af57656b405212af414d765b2263347" + "hash": "5d3b8ad051ab6f46c702308c2fc751a5ca340ac9c6dd86da1a5e9a3e65ea589f" } diff --git a/nym-api/.sqlx/query-676299beb2004ab89f7b38cf21ffb84ab5e7d7435297573523e2532560c2e302.json b/nym-api/.sqlx/query-676299beb2004ab89f7b38cf21ffb84ab5e7d7435297573523e2532560c2e302.json deleted file mode 100644 index 903ee12ebf..0000000000 --- a/nym-api/.sqlx/query-676299beb2004ab89f7b38cf21ffb84ab5e7d7435297573523e2532560c2e302.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n SELECT\n d.node_id as \"node_id: NodeId\",\n CASE WHEN count(*) > 3 THEN AVG(reliability) ELSE 100 END as \"value: f32\"\n FROM\n gateway_details d\n JOIN\n gateway_status s on d.id = s.gateway_details_id\n WHERE\n timestamp >= ? AND\n timestamp <= ?\n GROUP BY 1\n ", - "describe": { - "columns": [ - { - "name": "node_id: NodeId", - "ordinal": 0, - "type_info": "Integer" - }, - { - "name": "value: f32", - "ordinal": 1, - "type_info": "Null" - } - ], - "parameters": { - "Right": 2 - }, - "nullable": [ - false, - null - ] - }, - "hash": "676299beb2004ab89f7b38cf21ffb84ab5e7d7435297573523e2532560c2e302" -} diff --git a/nym-api/.sqlx/query-5eb13bfbee53b50641f69d4d6b62383c7f43864bffe98642bb8d1cf7c259d7be.json b/nym-api/.sqlx/query-697e6d738aecf115a3608139579b2d1d937dd4cc4778dfb42709adf08c1497f2.json similarity index 72% rename from nym-api/.sqlx/query-5eb13bfbee53b50641f69d4d6b62383c7f43864bffe98642bb8d1cf7c259d7be.json rename to nym-api/.sqlx/query-697e6d738aecf115a3608139579b2d1d937dd4cc4778dfb42709adf08c1497f2.json index 8d03e6b94b..1fe08369b7 100644 --- a/nym-api/.sqlx/query-5eb13bfbee53b50641f69d4d6b62383c7f43864bffe98642bb8d1cf7c259d7be.json +++ b/nym-api/.sqlx/query-697e6d738aecf115a3608139579b2d1d937dd4cc4778dfb42709adf08c1497f2.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "\n SELECT epoch_id as \"epoch_id: u32\", serialised_signatures\n FROM global_expiration_date_signatures\n WHERE expiration_date = ?\n ", + "query": "\n SELECT epoch_id as \"epoch_id: u32\", serialised_signatures\n FROM global_expiration_date_signatures\n WHERE expiration_date = ? AND epoch_id = ?\n ", "describe": { "columns": [ { @@ -15,12 +15,12 @@ } ], "parameters": { - "Right": 1 + "Right": 2 }, "nullable": [ false, false ] }, - "hash": "5eb13bfbee53b50641f69d4d6b62383c7f43864bffe98642bb8d1cf7c259d7be" + "hash": "697e6d738aecf115a3608139579b2d1d937dd4cc4778dfb42709adf08c1497f2" } diff --git a/nym-api/.sqlx/query-73ca856950a0157acfd3e2ed07b11aca3d875f67c77e2e7c75653c3f337d594e.json b/nym-api/.sqlx/query-73ca856950a0157acfd3e2ed07b11aca3d875f67c77e2e7c75653c3f337d594e.json deleted file mode 100644 index 6c62e5e972..0000000000 --- a/nym-api/.sqlx/query-73ca856950a0157acfd3e2ed07b11aca3d875f67c77e2e7c75653c3f337d594e.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n SELECT timestamp, reliability as \"reliability: u8\"\n FROM mixnode_status\n JOIN mixnode_details\n ON mixnode_status.mixnode_details_id = mixnode_details.id\n WHERE mixnode_details.mix_id=? AND mixnode_status.timestamp > ?;\n ", - "describe": { - "columns": [ - { - "name": "timestamp", - "ordinal": 0, - "type_info": "Integer" - }, - { - "name": "reliability: u8", - "ordinal": 1, - "type_info": "Integer" - } - ], - "parameters": { - "Right": 2 - }, - "nullable": [ - true, - true - ] - }, - "hash": "73ca856950a0157acfd3e2ed07b11aca3d875f67c77e2e7c75653c3f337d594e" -} diff --git a/nym-api/.sqlx/query-1f72d6f538a3655a031a3a8706794559c4c0df6defdfd179c84d02d3b8a6c055.json b/nym-api/.sqlx/query-7e1829a17b81d7ee3dd8134b7a2be38ffdcaa672afbae44993365ca6910f68f6.json similarity index 72% rename from nym-api/.sqlx/query-1f72d6f538a3655a031a3a8706794559c4c0df6defdfd179c84d02d3b8a6c055.json rename to nym-api/.sqlx/query-7e1829a17b81d7ee3dd8134b7a2be38ffdcaa672afbae44993365ca6910f68f6.json index 21a1f70f90..57a7eee0f8 100644 --- a/nym-api/.sqlx/query-1f72d6f538a3655a031a3a8706794559c4c0df6defdfd179c84d02d3b8a6c055.json +++ b/nym-api/.sqlx/query-7e1829a17b81d7ee3dd8134b7a2be38ffdcaa672afbae44993365ca6910f68f6.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "\n SELECT epoch_id as \"epoch_id: u32\", serialised_signatures\n FROM partial_expiration_date_signatures\n WHERE expiration_date = ?\n ", + "query": "\n SELECT epoch_id as \"epoch_id: u32\", serialised_signatures\n FROM partial_expiration_date_signatures\n WHERE expiration_date = ? AND epoch_id = ?\n ", "describe": { "columns": [ { @@ -15,12 +15,12 @@ } ], "parameters": { - "Right": 1 + "Right": 2 }, "nullable": [ false, false ] }, - "hash": "1f72d6f538a3655a031a3a8706794559c4c0df6defdfd179c84d02d3b8a6c055" + "hash": "7e1829a17b81d7ee3dd8134b7a2be38ffdcaa672afbae44993365ca6910f68f6" } diff --git a/nym-api/.sqlx/query-805ad4f26e0234d7f482a263e186156311713d2e9f69d39c868cd16296b56326.json b/nym-api/.sqlx/query-805ad4f26e0234d7f482a263e186156311713d2e9f69d39c868cd16296b56326.json new file mode 100644 index 0000000000..e1eb54bb9c --- /dev/null +++ b/nym-api/.sqlx/query-805ad4f26e0234d7f482a263e186156311713d2e9f69d39c868cd16296b56326.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO emergency_credential\n (type, content, expiration)\n VALUES (?, ?, ?)\n ON CONFLICT(type, content) DO NOTHING;\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 3 + }, + "nullable": [] + }, + "hash": "805ad4f26e0234d7f482a263e186156311713d2e9f69d39c868cd16296b56326" +} diff --git a/nym-api/.sqlx/query-924f8eb10c6cbb7f35da6c1bb77e1025442a594dcb5c6401b3dfac7df9c25073.json b/nym-api/.sqlx/query-924f8eb10c6cbb7f35da6c1bb77e1025442a594dcb5c6401b3dfac7df9c25073.json deleted file mode 100644 index c9cf3e5e09..0000000000 --- a/nym-api/.sqlx/query-924f8eb10c6cbb7f35da6c1bb77e1025442a594dcb5c6401b3dfac7df9c25073.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "SQLite", - "query": "SELECT identity FROM gateway_details WHERE node_id = ?", - "describe": { - "columns": [ - { - "name": "identity", - "ordinal": 0, - "type_info": "Text" - } - ], - "parameters": { - "Right": 1 - }, - "nullable": [ - false - ] - }, - "hash": "924f8eb10c6cbb7f35da6c1bb77e1025442a594dcb5c6401b3dfac7df9c25073" -} diff --git a/nym-api/.sqlx/query-c19e1b3768bf2929407599e6e8783ead09f4d7319b7997fa2a9bb628f9404166.json b/nym-api/.sqlx/query-c19e1b3768bf2929407599e6e8783ead09f4d7319b7997fa2a9bb628f9404166.json deleted file mode 100644 index c4b25591f3..0000000000 --- a/nym-api/.sqlx/query-c19e1b3768bf2929407599e6e8783ead09f4d7319b7997fa2a9bb628f9404166.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n SELECT\n d.mix_id as \"mix_id: NodeId\",\n AVG(s.reliability) as \"value: f32\"\n FROM\n mixnode_details d\n JOIN\n mixnode_status s on d.id = s.mixnode_details_id\n WHERE\n timestamp >= ? AND\n timestamp <= ?\n GROUP BY 1\n ", - "describe": { - "columns": [ - { - "name": "mix_id: NodeId", - "ordinal": 0, - "type_info": "Integer" - }, - { - "name": "value: f32", - "ordinal": 1, - "type_info": "Null" - } - ], - "parameters": { - "Right": 2 - }, - "nullable": [ - false, - null - ] - }, - "hash": "c19e1b3768bf2929407599e6e8783ead09f4d7319b7997fa2a9bb628f9404166" -} diff --git a/nym-api/Cargo.toml b/nym-api/Cargo.toml index e4e094a39b..635cebdb66 100644 --- a/nym-api/Cargo.toml +++ b/nym-api/Cargo.toml @@ -4,10 +4,11 @@ [package] name = "nym-api" license = "GPL-3.0" -version = "1.1.72" +version = "1.1.73" authors.workspace = true edition = "2021" rust-version.workspace = true +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -67,43 +68,43 @@ utoipa-swagger-ui = { workspace = true, features = ["axum"] } tracing = { workspace = true } ## internal -nym-cache = { path = "../common/nym-cache" } -nym-bandwidth-controller = { path = "../common/bandwidth-controller" } -nym-ecash-contract-common = { path = "../common/cosmwasm-smart-contracts/ecash-contract" } -nym-ecash-time = { path = "../common/ecash-time", features = ["expiration"] } -nym-coconut-dkg-common = { path = "../common/cosmwasm-smart-contracts/coconut-dkg" } -nym-compact-ecash = { path = "../common/nym_offline_compact_ecash" } -nym-credentials-interface = { path = "../common/credentials-interface" } -nym-config = { path = "../common/config" } +nym-cache = { workspace = true } +nym-bandwidth-controller = { workspace = true } +nym-ecash-contract-common = { workspace = true } +nym-ecash-time = { workspace = true, features = ["expiration"] } +nym-coconut-dkg-common = { workspace = true } +nym-compact-ecash = { workspace = true } +nym-credentials-interface = { workspace = true } +nym-config = { workspace = true } cosmwasm-std = { workspace = true } -nym-credential-storage = { path = "../common/credential-storage", features = [ +nym-credential-storage = { workspace = true, features = [ "persistent-storage", ] } -nym-credentials = { path = "../common/credentials" } -nym-crypto = { path = "../common/crypto" } +nym-credentials = { workspace = true } +nym-crypto = { workspace = true } cw2 = { workspace = true } cw3 = { workspace = true } cw4 = { workspace = true } -nym-dkg = { path = "../common/dkg", features = ["cw-types"] } -nym-gateway-client = { path = "../common/client-libs/gateway-client" } -nym-mixnet-contract-common = { path = "../common/cosmwasm-smart-contracts/mixnet-contract", features = ["utoipa"] } -nym-contracts-common = { path = "../common/cosmwasm-smart-contracts/contracts-common", features = ["naive_float", "utoipa"] } -nym-sphinx = { path = "../common/nymsphinx" } -nym-pemstore = { path = "../common/pemstore" } -nym-task = { path = "../common/task" } -nym-topology = { path = "../common/topology" } -nym-api-requests = { path = "nym-api-requests" } -nym-validator-client = { path = "../common/client-libs/validator-client" } -nym-http-api-client = { path = "../common/http-api-client" } -nym-bin-common = { path = "../common/bin-common", features = ["output_format", "openapi", "basic_tracing"] } -nym-node-tester-utils = { path = "../common/node-tester-utils" } -nym-node-requests = { path = "../nym-node/nym-node-requests" } -nym-types = { path = "../common/types" } -nym-http-api-common = { path = "../common/http-api-common", features = ["utoipa", "output", "middleware"] } -nym-serde-helpers = { path = "../common/serde-helpers", features = ["date"] } -nym-ticketbooks-merkle = { path = "../common/ticketbooks-merkle" } -nym-statistics-common = { path = "../common/statistics" } -nym-ecash-signer-check = { path = "../common/ecash-signer-check" } +nym-dkg = { workspace = true, features = ["cw-types"] } +nym-gateway-client = { workspace = true } +nym-mixnet-contract-common = { workspace = true, features = ["utoipa"] } +nym-contracts-common = { workspace = true, features = ["naive_float", "utoipa"] } +nym-sphinx = { workspace = true } +nym-pemstore = { workspace = true } +nym-task = { workspace = true } +nym-topology = { workspace = true } +nym-api-requests = { workspace = true } +nym-validator-client = { workspace = true } +nym-http-api-client = { workspace = true } +nym-bin-common = { workspace = true, features = ["output_format", "openapi", "basic_tracing"] } +nym-node-tester-utils = { workspace = true } +nym-node-requests = { workspace = true, features = ["client"] } +nym-types = { workspace = true } +nym-http-api-common = { workspace = true, features = ["utoipa", "output", "middleware"] } +nym-serde-helpers = { workspace = true, features = ["date"] } +nym-ticketbooks-merkle = { workspace = true } +nym-statistics-common = { workspace = true } +nym-ecash-signer-check = { workspace = true } [features] diff --git a/nym-api/nym-api-requests/Cargo.toml b/nym-api/nym-api-requests/Cargo.toml index 267cb731f7..edb25c2972 100644 --- a/nym-api/nym-api-requests/Cargo.toml +++ b/nym-api/nym-api-requests/Cargo.toml @@ -1,17 +1,24 @@ [package] name = "nym-api-requests" -version = "0.1.0" +version.workspace = true edition = "2021" license.workspace = true +description = "Nym API request types and functions" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] bs58 = { workspace = true } +celes = { workspace = true } # country codes cosmrs = { workspace = true } cosmwasm-std = { workspace = true } schemars = { workspace = true, features = ["preserve_order"] } serde = { workspace = true, features = ["derive"] } +strum = { workspace = true, features = ["derive"] } +strum_macros = { workspace = true } humantime-serde = { workspace = true } hex = { workspace = true } serde_json = { workspace = true } @@ -27,27 +34,27 @@ tracing = { workspace = true } # for serde on secp256k1 signatures ecdsa = { workspace = true, features = ["serde"] } -nym-serde-helpers = { path = "../../common/serde-helpers", features = ["bs58", "base64", "date"] } -nym-credentials-interface = { path = "../../common/credentials-interface" } -nym-crypto = { path = "../../common/crypto", features = ["serde", "asymmetric"] } - -nym-config = { path = "../../common/config" } -nym-ecash-time = { path = "../../common/ecash-time" } -nym-compact-ecash = { path = "../../common/nym_offline_compact_ecash" } -nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", features = ["naive_float"] } -nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract", features = ["utoipa"] } -nym-coconut-dkg-common = { path = "../../common/cosmwasm-smart-contracts/coconut-dkg" } -nym-node-requests = { path = "../../nym-node/nym-node-requests", default-features = false, features = ["openapi"] } -nym-noise-keys = { path = "../../common/nymnoise/keys" } -nym-network-defaults = { path = "../../common/network-defaults" } -nym-ticketbooks-merkle = { path = "../../common/ticketbooks-merkle" } -nym-ecash-signer-check-types = { path = "../../common/ecash-signer-check-types" } +nym-serde-helpers = { workspace = true, features = ["bs58", "base64", "date"] } +nym-credentials-interface = { workspace = true } +nym-crypto = { workspace = true, features = ["serde", "asymmetric"] } +nym-config = { workspace = true } +nym-ecash-time = { workspace = true } +nym-compact-ecash = { workspace = true } +nym-contracts-common = { workspace = true, features = ["naive_float"] } +nym-mixnet-contract-common = { workspace = true, features = ["utoipa"] } +nym-coconut-dkg-common = { workspace = true } +nym-node-requests = { workspace = true, default-features = false, features = ["openapi"] } +nym-noise-keys = { workspace = true } +nym-network-defaults = { workspace = true } +nym-ticketbooks-merkle = { workspace = true } +nym-ecash-signer-check-types = { workspace = true } +nym-kkt-ciphersuite = { workspace = true } [dev-dependencies] rand_chacha = { workspace = true } -nym-crypto = { path = "../../common/crypto", features = ["rand"] } - +nym-crypto = { workspace = true, features = ["rand"] } +nym-test-utils = { workspace = true } [features] default = [] diff --git a/nym-api/nym-api-requests/src/models/described.rs b/nym-api/nym-api-requests/src/models/described.rs deleted file mode 100644 index 73612d7f38..0000000000 --- a/nym-api/nym-api-requests/src/models/described.rs +++ /dev/null @@ -1,418 +0,0 @@ -// Copyright 2025 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::models::{BinaryBuildInformationOwned, OffsetDateTimeJsonSchemaWrapper}; -use crate::nym_nodes::{BasicEntryInformation, NodeRole, SemiSkimmedNode, SkimmedNode}; -use nym_crypto::asymmetric::ed25519::serde_helpers::bs58_ed25519_pubkey; -use nym_crypto::asymmetric::x25519::serde_helpers::bs58_x25519_pubkey; -use nym_crypto::asymmetric::{ed25519, x25519}; -use nym_mixnet_contract_common::reward_params::Performance; -use nym_mixnet_contract_common::NodeId; -use nym_network_defaults::{ - DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT, WG_METADATA_PORT, WG_TUNNEL_PORT, -}; -use nym_node_requests::api::v1::authenticator::models::Authenticator; -use nym_node_requests::api::v1::gateway::models::Wireguard; -use nym_node_requests::api::v1::ip_packet_router::models::IpPacketRouter; -use nym_node_requests::api::v1::lewes_protocol::models::LewesProtocol; -use nym_node_requests::api::v1::node::models::{AuxiliaryDetails, NodeRoles}; -use nym_noise_keys::VersionedNoiseKey; -use serde::{Deserialize, Serialize}; -use std::net::IpAddr; -use tracing::warn; -use utoipa::ToSchema; - -#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct HostInformation { - #[schema(value_type = Vec)] - pub ip_address: Vec, - pub hostname: Option, - pub keys: HostKeys, -} - -impl From for HostInformation { - fn from(value: nym_node_requests::api::v1::node::models::HostInformation) -> Self { - HostInformation { - ip_address: value.ip_address, - hostname: value.hostname, - keys: value.keys.into(), - } - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct HostKeys { - #[serde(with = "bs58_ed25519_pubkey")] - #[schemars(with = "String")] - #[schema(value_type = String)] - pub ed25519: ed25519::PublicKey, - - #[deprecated(note = "use the current_x25519_sphinx_key with explicit rotation information")] - #[serde(with = "bs58_x25519_pubkey")] - #[schemars(with = "String")] - #[schema(value_type = String)] - pub x25519: x25519::PublicKey, - - pub current_x25519_sphinx_key: SphinxKey, - - #[serde(default)] - pub pre_announced_x25519_sphinx_key: Option, - - #[serde(default)] - pub x25519_versioned_noise: Option, -} - -#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct SphinxKey { - pub rotation_id: u32, - - #[serde(with = "bs58_x25519_pubkey")] - #[schemars(with = "String")] - #[schema(value_type = String)] - pub public_key: x25519::PublicKey, -} - -impl From for SphinxKey { - fn from(value: nym_node_requests::api::v1::node::models::SphinxKey) -> Self { - SphinxKey { - rotation_id: value.rotation_id, - public_key: value.public_key, - } - } -} - -impl From for HostKeys { - fn from(value: nym_node_requests::api::v1::node::models::HostKeys) -> Self { - HostKeys { - ed25519: value.ed25519_identity, - x25519: value.x25519_sphinx, - current_x25519_sphinx_key: value.primary_x25519_sphinx_key.into(), - pre_announced_x25519_sphinx_key: value.pre_announced_x25519_sphinx_key.map(Into::into), - x25519_versioned_noise: value.x25519_versioned_noise, - } - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct WebSockets { - pub ws_port: u16, - - pub wss_port: Option, -} - -impl From for WebSockets { - fn from(value: nym_node_requests::api::v1::gateway::models::WebSockets) -> Self { - WebSockets { - ws_port: value.ws_port, - wss_port: value.wss_port, - } - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct NoiseDetails { - pub key: VersionedNoiseKey, - - pub mixnet_port: u16, - - #[schema(value_type = Vec)] - pub ip_addresses: Vec, -} - -#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct NymNodeDescription { - #[schema(value_type = u32)] - pub node_id: NodeId, - pub contract_node_type: DescribedNodeType, - pub description: NymNodeData, -} - -impl NymNodeDescription { - pub fn version(&self) -> &str { - &self.description.build_information.build_version - } - - pub fn entry_information(&self) -> BasicEntryInformation { - BasicEntryInformation { - hostname: self.description.host_information.hostname.clone(), - ws_port: self.description.mixnet_websockets.ws_port, - wss_port: self.description.mixnet_websockets.wss_port, - } - } - - pub fn ed25519_identity_key(&self) -> ed25519::PublicKey { - self.description.host_information.keys.ed25519 - } - - pub fn current_sphinx_key(&self, current_rotation_id: u32) -> x25519::PublicKey { - let keys = &self.description.host_information.keys; - - if keys.current_x25519_sphinx_key.rotation_id == u32::MAX { - // legacy case (i.e. node doesn't support rotation) - return keys.current_x25519_sphinx_key.public_key; - } - - if current_rotation_id == keys.current_x25519_sphinx_key.rotation_id { - // it's the 'current' key - return keys.current_x25519_sphinx_key.public_key; - } - - if let Some(pre_announced) = &keys.pre_announced_x25519_sphinx_key { - if pre_announced.rotation_id == current_rotation_id { - return pre_announced.public_key; - } - } - - warn!( - "unexpected key rotation {current_rotation_id} for node {}", - self.node_id - ); - // this should never be reached, but just in case, return the fallback option - keys.current_x25519_sphinx_key.public_key - } - - pub fn to_skimmed_node( - &self, - current_rotation_id: u32, - role: NodeRole, - performance: Performance, - ) -> SkimmedNode { - let keys = &self.description.host_information.keys; - let entry = if self.description.declared_role.entry { - Some(self.entry_information()) - } else { - None - }; - - SkimmedNode { - node_id: self.node_id, - ed25519_identity_pubkey: keys.ed25519, - ip_addresses: self.description.host_information.ip_address.clone(), - mix_port: self.description.mix_port(), - x25519_sphinx_pubkey: self.current_sphinx_key(current_rotation_id), - // we can't use the declared roles, we have to take whatever was provided in the contract. - // why? say this node COULD operate as an exit, but it might be the case the contract decided - // to assign it an ENTRY role only. we have to use that one instead. - role, - supported_roles: self.description.declared_role, - entry, - performance, - } - } - - pub fn to_semi_skimmed_node( - &self, - current_rotation_id: u32, - role: NodeRole, - performance: Performance, - ) -> SemiSkimmedNode { - let skimmed_node = self.to_skimmed_node(current_rotation_id, role, performance); - - SemiSkimmedNode { - basic: skimmed_node, - x25519_noise_versioned_key: self - .description - .host_information - .keys - .x25519_versioned_noise, - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -#[serde(rename_all = "snake_case")] -#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] -#[cfg_attr( - feature = "generate-ts", - ts( - export, - export_to = "ts-packages/types/src/types/rust/DescribedNodeType.ts" - ) -)] -pub enum DescribedNodeType { - LegacyMixnode, - LegacyGateway, - NymNode, -} - -impl DescribedNodeType { - pub fn is_nym_node(&self) -> bool { - matches!(self, DescribedNodeType::NymNode) - } -} - -#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] -#[cfg_attr( - feature = "generate-ts", - ts( - export, - export_to = "ts-packages/types/src/types/rust/DeclaredRoles.ts" - ) -)] -pub struct DeclaredRoles { - pub mixnode: bool, - pub entry: bool, - pub exit_nr: bool, - pub exit_ipr: bool, -} - -impl DeclaredRoles { - pub fn can_operate_exit_gateway(&self) -> bool { - self.exit_ipr && self.exit_nr - } -} - -impl From for DeclaredRoles { - fn from(value: NodeRoles) -> Self { - DeclaredRoles { - mixnode: value.mixnode_enabled, - entry: value.gateway_enabled, - exit_nr: value.gateway_enabled && value.network_requester_enabled, - exit_ipr: value.gateway_enabled && value.ip_packet_router_enabled, - } - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct NetworkRequesterDetails { - /// address of the embedded network requester - pub address: String, - - /// flag indicating whether this network requester uses the exit policy rather than the deprecated allow list - pub uses_exit_policy: bool, -} - -#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct IpPacketRouterDetails { - /// address of the embedded ip packet router - pub address: String, -} - -// works for current simple case. -impl From for IpPacketRouterDetails { - fn from(value: IpPacketRouter) -> Self { - IpPacketRouterDetails { - address: value.address, - } - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct AuthenticatorDetails { - /// address of the embedded authenticator - pub address: String, -} - -// works for current simple case. -impl From for AuthenticatorDetails { - fn from(value: Authenticator) -> Self { - AuthenticatorDetails { - address: value.address, - } - } -} -#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct WireguardDetails { - // NOTE: the port field is deprecated in favour of tunnel_port - pub port: u16, - #[serde(default = "default_tunnel_port")] - pub tunnel_port: u16, - #[serde(default = "default_metadata_port")] - pub metadata_port: u16, - pub public_key: String, -} - -fn default_tunnel_port() -> u16 { - WG_TUNNEL_PORT -} -fn default_metadata_port() -> u16 { - WG_METADATA_PORT -} - -// works for current simple case. -impl From for WireguardDetails { - fn from(value: Wireguard) -> Self { - WireguardDetails { - port: value.port, - tunnel_port: value.tunnel_port, - metadata_port: value.metadata_port, - public_key: value.public_key, - } - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct LewesProtocolDetails { - /// Helper field that specifies whether the LP listener(s) is enabled on this node. - /// It is directly controlled by the node's role (i.e. it is enabled if it supports 'entry' mode) - pub enabled: bool, - - /// LP TCP control address (default: 41264) for establishing LP sessions - pub control_port: u16, - - /// LP UDP data address (default: 51264) for Sphinx packets wrapped in LP - pub data_port: u16, -} - -impl From for LewesProtocolDetails { - fn from(value: LewesProtocol) -> Self { - LewesProtocolDetails { - enabled: value.enabled, - control_port: value.control_port, - data_port: value.data_port, - } - } -} - -// this struct is getting quite bloated... -#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct NymNodeData { - #[serde(default)] - pub last_polled: OffsetDateTimeJsonSchemaWrapper, - - pub host_information: HostInformation, - - #[serde(default)] - pub declared_role: DeclaredRoles, - - #[serde(default)] - pub auxiliary_details: AuxiliaryDetails, - - // TODO: do we really care about ALL build info or just the version? - pub build_information: BinaryBuildInformationOwned, - - #[serde(default)] - pub network_requester: Option, - - #[serde(default)] - pub ip_packet_router: Option, - - #[serde(default)] - pub authenticator: Option, - - #[serde(default)] - pub wireguard: Option, - - #[serde(default)] - pub lewes_protocol: Option, - - // for now we only care about their ws/wss situation, nothing more - pub mixnet_websockets: WebSockets, -} - -impl NymNodeData { - pub fn mix_port(&self) -> u16 { - self.auxiliary_details - .announce_ports - .mix_port - .unwrap_or(DEFAULT_MIX_LISTENING_PORT) - } - - pub fn verloc_port(&self) -> u16 { - self.auxiliary_details - .announce_ports - .verloc_port - .unwrap_or(DEFAULT_VERLOC_LISTENING_PORT) - } -} diff --git a/nym-api/nym-api-requests/src/models/described/mod.rs b/nym-api/nym-api-requests/src/models/described/mod.rs new file mode 100644 index 0000000000..f3b38925be --- /dev/null +++ b/nym-api/nym-api-requests/src/models/described/mod.rs @@ -0,0 +1,26 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_noise_keys::VersionedNoiseKeyV1; +use serde::{Deserialize, Serialize}; +use std::net::IpAddr; +use utoipa::ToSchema; + +pub mod type_translation; +pub mod v1; +pub mod v2; + +// don't break existing imports +pub use type_translation::*; +pub use v1::*; +pub use v2::*; + +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +pub struct NoiseDetails { + pub key: VersionedNoiseKeyV1, + + pub mixnet_port: u16, + + #[schema(value_type = Vec)] + pub ip_addresses: Vec, +} diff --git a/nym-api/nym-api-requests/src/models/described/type_translation.rs b/nym-api/nym-api-requests/src/models/described/type_translation.rs new file mode 100644 index 0000000000..415184c980 --- /dev/null +++ b/nym-api/nym-api-requests/src/models/described/type_translation.rs @@ -0,0 +1,578 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +//! This redefines relevant types present within nym-node-requests for the purposes of this crate +//! and defines required conversion methods + +use celes::Country; +use nym_crypto::asymmetric::ed25519::serde_helpers::bs58_ed25519_pubkey; +use nym_crypto::asymmetric::x25519::serde_helpers::bs58_x25519_pubkey; +use nym_crypto::asymmetric::{ed25519, x25519}; +use nym_kkt_ciphersuite::{HashFunction, SignatureScheme, KEM}; +use nym_network_defaults::{WG_METADATA_PORT, WG_TUNNEL_PORT}; +use nym_noise_keys::VersionedNoiseKeyV1; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::net::IpAddr; +use strum_macros::{Display, EnumString}; +use thiserror::Error; +use utoipa::ToSchema; + +// to whoever is thinking of modifying this struct. +// you MUST NOT change its structure in any way - adding, removing or changing fields +// otherwise, it will break old clients as bincode serialisation is not backwards compatible +// even if you put `#[serde(default)]` all over the place +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema, PartialEq)] +pub struct HostInformationV1 { + #[schema(value_type = Vec)] + pub ip_address: Vec, + pub hostname: Option, + pub keys: HostKeysV1, +} + +// to whoever is thinking of modifying this struct. +// you MUST NOT change its structure in any way - adding, removing or changing fields +// otherwise, it will break old clients as bincode serialisation is not backwards compatible +// even if you put `#[serde(default)]` all over the place +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema, PartialEq)] +pub struct HostKeysV1 { + #[serde(with = "bs58_ed25519_pubkey")] + #[schemars(with = "String")] + #[schema(value_type = String)] + pub ed25519: ed25519::PublicKey, + + #[deprecated(note = "use the current_x25519_sphinx_key with explicit rotation information")] + #[serde(with = "bs58_x25519_pubkey")] + #[schemars(with = "String")] + #[schema(value_type = String)] + pub x25519: x25519::PublicKey, + + pub current_x25519_sphinx_key: SphinxKeyV1, + + #[serde(default)] + pub pre_announced_x25519_sphinx_key: Option, + + #[serde(default)] + pub x25519_versioned_noise: Option, +} + +// to whoever is thinking of modifying this struct. +// you MUST NOT change its structure in any way - adding, removing or changing fields +// otherwise, it will break old clients as bincode serialisation is not backwards compatible +// even if you put `#[serde(default)]` all over the place +#[derive( + Clone, Copy, Debug, Default, Serialize, Deserialize, schemars::JsonSchema, ToSchema, PartialEq, +)] +pub struct AnnouncePortsV1 { + pub verloc_port: Option, + pub mix_port: Option, +} + +// to whoever is thinking of modifying this struct. +// you MUST NOT change its structure in any way - adding, removing or changing fields +// otherwise, it will break old clients as bincode serialisation is not backwards compatible +// even if you put `#[serde(default)]` all over the place +#[derive( + Clone, Copy, Debug, Default, Serialize, Deserialize, schemars::JsonSchema, ToSchema, PartialEq, +)] +pub struct AuxiliaryDetailsV1 { + /// Optional ISO 3166 alpha-2 two-letter country code of the node's **physical** location + #[schema(example = "PL", value_type = Option)] + #[schemars(with = "Option")] + #[schemars(length(equal = 2))] + pub location: Option, + + #[serde(default)] + pub announce_ports: AnnouncePortsV1, + + /// Specifies whether this node operator has agreed to the terms and conditions + /// as defined at + // make sure to include the default deserialisation as this field hasn't existed when the struct was first created + #[serde(default)] + pub accepted_operator_terms_and_conditions: bool, +} + +// to whoever is thinking of modifying this struct. +// you MUST NOT change its structure in any way - adding, removing or changing fields +// otherwise, it will break old clients as bincode serialisation is not backwards compatible +// even if you put `#[serde(default)]` all over the place +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema, PartialEq)] +pub struct SphinxKeyV1 { + pub rotation_id: u32, + + #[serde(with = "bs58_x25519_pubkey")] + #[schemars(with = "String")] + #[schema(value_type = String)] + pub public_key: x25519::PublicKey, +} + +// to whoever is thinking of modifying this struct. +// you MUST NOT change its structure in any way - adding, removing or changing fields +// otherwise, it will break old clients as bincode serialisation is not backwards compatible +// even if you put `#[serde(default)]` all over the place +#[derive( + Clone, Copy, Debug, Default, Serialize, Deserialize, schemars::JsonSchema, ToSchema, PartialEq, +)] +#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] +#[cfg_attr( + feature = "generate-ts", + ts( + export, + export_to = "ts-packages/types/src/types/rust/DeclaredRoles.ts" + ) +)] +pub struct DeclaredRolesV1 { + pub mixnode: bool, + pub entry: bool, + pub exit_nr: bool, + pub exit_ipr: bool, +} + +impl DeclaredRolesV1 { + pub fn can_operate_exit_gateway(&self) -> bool { + self.exit_ipr && self.exit_nr + } +} + +// to whoever is thinking of modifying this struct. +// you MUST NOT change its structure in any way - adding, removing or changing fields +// otherwise, it will break old clients as bincode serialisation is not backwards compatible +// even if you put `#[serde(default)]` all over the place +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema, PartialEq)] +pub struct WebSocketsV1 { + pub ws_port: u16, + + pub wss_port: Option, +} + +// to whoever is thinking of modifying this struct. +// you MUST NOT change its structure in any way - adding, removing or changing fields +// otherwise, it will break old clients as bincode serialisation is not backwards compatible +// even if you put `#[serde(default)]` all over the place +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema, PartialEq)] +pub struct NetworkRequesterDetailsV1 { + /// address of the embedded network requester + pub address: String, + + /// flag indicating whether this network requester uses the exit policy rather than the deprecated allow list + pub uses_exit_policy: bool, +} + +// to whoever is thinking of modifying this struct. +// you MUST NOT change its structure in any way - adding, removing or changing fields +// otherwise, it will break old clients as bincode serialisation is not backwards compatible +// even if you put `#[serde(default)]` all over the place +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema, PartialEq)] +pub struct IpPacketRouterDetailsV1 { + /// address of the embedded ip packet router + pub address: String, +} + +// to whoever is thinking of modifying this struct. +// you MUST NOT change its structure in any way - adding, removing or changing fields +// otherwise, it will break old clients as bincode serialisation is not backwards compatible +// even if you put `#[serde(default)]` all over the place +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema, PartialEq)] +pub struct AuthenticatorDetailsV1 { + /// address of the embedded authenticator + pub address: String, +} + +// to whoever is thinking of modifying this struct. +// you MUST NOT change its structure in any way - adding, removing or changing fields +// otherwise, it will break old clients as bincode serialisation is not backwards compatible +// even if you put `#[serde(default)]` all over the place +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema, PartialEq)] +pub struct WireguardDetailsV1 { + // NOTE: the port field is deprecated in favour of tunnel_port + pub port: u16, + #[serde(default = "default_tunnel_port")] + pub tunnel_port: u16, + #[serde(default = "default_metadata_port")] + pub metadata_port: u16, + pub public_key: String, +} + +// to whoever is thinking of modifying this struct. +// you MUST NOT change its structure in any way - adding, removing or changing fields +// otherwise, it will break old clients as bincode serialisation is not backwards compatible +// even if you put `#[serde(default)]` all over the place +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema, PartialEq)] +pub struct LewesProtocolDetailsV1 { + /// Helper field that specifies whether the LP listener(s) is enabled on this node. + /// It is directly controlled by the node's role (i.e. it is enabled if it supports 'entry' mode) + pub enabled: bool, + + /// LP TCP control address (default: 41264) for establishing LP sessions + pub control_port: u16, + + /// LP UDP data address (default: 51264) for Sphinx packets wrapped in LP + pub data_port: u16, + + #[serde(with = "bs58_x25519_pubkey")] + #[schemars(with = "String")] + #[schema(value_type = String)] + /// LP public key + pub x25519: x25519::PublicKey, + + /// Digests of the KEM keys available to this node alongside hashing algorithms used + /// for their computation. + /// note: digests are hex encoded + pub kem_keys: HashMap>, + + /// Digests of the signing keys available to this node alongside hashing algorithms used + /// for their computation. + /// note: digests are hex encoded + pub signing_keys: HashMap>, +} + +impl LewesProtocolDetailsV1 { + fn decode_digests( + digests: &HashMap, + ) -> Result>, MalformedLPData> { + let mut kem_digests = HashMap::new(); + for (hash_function, digest) in digests { + let digest = hex::decode(digest).map_err(|source| MalformedLPData::MalformedHash { + value: digest.to_string(), + source, + })?; + kem_digests.insert((*hash_function).try_into()?, digest); + } + Ok(kem_digests) + } + + pub fn kem_keys( + &self, + ) -> Result>>, MalformedLPData> { + let mut kem_keys = HashMap::new(); + for (kem, digests) in &self.kem_keys { + let kem_digests = Self::decode_digests(digests)?; + kem_keys.insert((*kem).try_into()?, kem_digests); + } + Ok(kem_keys) + } + + pub fn signing_keys( + &self, + ) -> Result>>, MalformedLPData> { + let mut signing_keys = HashMap::new(); + for (signature_scheme, digests) in &self.signing_keys { + let kem_digests = Self::decode_digests(digests)?; + signing_keys.insert((*signature_scheme).try_into()?, kem_digests); + } + Ok(signing_keys) + } +} + +/// Convert map of digests from `nym_node_requests` types into `nym-api-requests` types +fn translate_digests( + digests: HashMap, +) -> HashMap { + digests + .into_iter() + .map(|(hash_fn, digest)| (hash_fn.into(), digest)) + .collect() +} + +#[derive( + Serialize, + Deserialize, + Debug, + Clone, + Copy, + JsonSchema, + Hash, + PartialEq, + Eq, + PartialOrd, + Display, + EnumString, + ToSchema, +)] +#[strum(serialize_all = "lowercase")] +#[non_exhaustive] +pub enum LPKEM { + MlKem768, + XWing, + X25519, + McEliece, +} + +#[derive( + Serialize, + Deserialize, + Debug, + Clone, + Copy, + JsonSchema, + Hash, + PartialEq, + Eq, + PartialOrd, + Display, + EnumString, + ToSchema, +)] +#[strum(serialize_all = "lowercase")] +#[non_exhaustive] +pub enum LPHashFunction { + Blake3, + Shake128, + Shake256, + Sha256, +} + +#[derive( + Serialize, + Deserialize, + Debug, + Clone, + Copy, + JsonSchema, + Hash, + PartialEq, + Eq, + PartialOrd, + Display, + EnumString, + ToSchema, +)] +#[strum(serialize_all = "lowercase")] +#[non_exhaustive] +pub enum LPSignatureScheme { + Ed25519, +} + +impl From for HostInformationV1 { + fn from(value: nym_node_requests::api::v1::node::models::HostInformation) -> Self { + HostInformationV1 { + ip_address: value.ip_address, + hostname: value.hostname, + keys: value.keys.into(), + } + } +} + +impl From for SphinxKeyV1 { + fn from(value: nym_node_requests::api::v1::node::models::SphinxKey) -> Self { + SphinxKeyV1 { + rotation_id: value.rotation_id, + public_key: value.public_key, + } + } +} + +impl From for HostKeysV1 { + fn from(value: nym_node_requests::api::v1::node::models::HostKeys) -> Self { + HostKeysV1 { + ed25519: value.ed25519_identity, + x25519: value.x25519_sphinx, + current_x25519_sphinx_key: value.primary_x25519_sphinx_key.into(), + pre_announced_x25519_sphinx_key: value.pre_announced_x25519_sphinx_key.map(Into::into), + x25519_versioned_noise: value.x25519_versioned_noise, + } + } +} + +impl From for AnnouncePortsV1 { + fn from(value: nym_node_requests::api::v1::node::models::AnnouncePorts) -> Self { + AnnouncePortsV1 { + verloc_port: value.verloc_port, + mix_port: value.mix_port, + } + } +} + +impl From for AuxiliaryDetailsV1 { + fn from(value: nym_node_requests::api::v1::node::models::AuxiliaryDetails) -> Self { + AuxiliaryDetailsV1 { + location: value.location, + announce_ports: value.announce_ports.into(), + accepted_operator_terms_and_conditions: value.accepted_operator_terms_and_conditions, + } + } +} + +impl From for DeclaredRolesV1 { + fn from(value: nym_node_requests::api::v1::node::models::NodeRoles) -> Self { + DeclaredRolesV1 { + mixnode: value.mixnode_enabled, + entry: value.gateway_enabled, + exit_nr: value.gateway_enabled && value.network_requester_enabled, + exit_ipr: value.gateway_enabled && value.ip_packet_router_enabled, + } + } +} + +impl From for WebSocketsV1 { + fn from(value: nym_node_requests::api::v1::gateway::models::WebSockets) -> Self { + WebSocketsV1 { + ws_port: value.ws_port, + wss_port: value.wss_port, + } + } +} + +// works for current simple case. +impl From + for IpPacketRouterDetailsV1 +{ + fn from(value: nym_node_requests::api::v1::ip_packet_router::models::IpPacketRouter) -> Self { + IpPacketRouterDetailsV1 { + address: value.address, + } + } +} + +// works for current simple case. +impl From + for AuthenticatorDetailsV1 +{ + fn from(value: nym_node_requests::api::v1::authenticator::models::Authenticator) -> Self { + AuthenticatorDetailsV1 { + address: value.address, + } + } +} + +fn default_tunnel_port() -> u16 { + WG_TUNNEL_PORT +} +fn default_metadata_port() -> u16 { + WG_METADATA_PORT +} + +// works for current simple case. +impl From for WireguardDetailsV1 { + fn from(value: nym_node_requests::api::v1::gateway::models::Wireguard) -> Self { + WireguardDetailsV1 { + port: value.port, + tunnel_port: value.tunnel_port, + metadata_port: value.metadata_port, + public_key: value.public_key, + } + } +} + +impl From + for LewesProtocolDetailsV1 +{ + fn from(value: nym_node_requests::api::v1::lewes_protocol::models::LewesProtocol) -> Self { + LewesProtocolDetailsV1 { + enabled: value.enabled, + control_port: value.control_port, + data_port: value.data_port, + x25519: value.x25519, + kem_keys: value + .kem_keys + .into_iter() + .map(|(kem, digests)| (kem.into(), translate_digests(digests))) + .collect(), + signing_keys: value + .signing_keys + .into_iter() + .map(|(scheme, digests)| (scheme.into(), translate_digests(digests))) + .collect(), + } + } +} + +impl From for LPKEM { + fn from(value: nym_node_requests::api::v1::lewes_protocol::models::LPKEM) -> Self { + match value { + nym_node_requests::api::v1::lewes_protocol::models::LPKEM::MlKem768 => LPKEM::MlKem768, + nym_node_requests::api::v1::lewes_protocol::models::LPKEM::XWing => LPKEM::XWing, + nym_node_requests::api::v1::lewes_protocol::models::LPKEM::X25519 => LPKEM::X25519, + nym_node_requests::api::v1::lewes_protocol::models::LPKEM::McEliece => LPKEM::McEliece, + } + } +} + +impl From for LPHashFunction { + fn from(value: nym_node_requests::api::v1::lewes_protocol::models::LPHashFunction) -> Self { + match value { + nym_node_requests::api::v1::lewes_protocol::models::LPHashFunction::Blake3 => { + LPHashFunction::Blake3 + } + nym_node_requests::api::v1::lewes_protocol::models::LPHashFunction::Shake128 => { + LPHashFunction::Shake128 + } + nym_node_requests::api::v1::lewes_protocol::models::LPHashFunction::Shake256 => { + LPHashFunction::Shake256 + } + nym_node_requests::api::v1::lewes_protocol::models::LPHashFunction::Sha256 => { + LPHashFunction::Sha256 + } + } + } +} + +impl From + for LPSignatureScheme +{ + fn from(value: nym_node_requests::api::v1::lewes_protocol::models::LPSignatureScheme) -> Self { + match value { + nym_node_requests::api::v1::lewes_protocol::models::LPSignatureScheme::Ed25519 => { + LPSignatureScheme::Ed25519 + } + } + } +} + +#[derive(Debug, Error)] +pub enum MalformedLPData { + #[error("{value} does not correspond to any valid LP KEM")] + UnknownLpKEM { value: LPKEM }, + + #[error("{value} does not correspond to any valid LP Signature Scheme")] + UnknownLpSignatureScheme { value: LPSignatureScheme }, + + #[error("{value} does not correspond to any valid LP Hash Function")] + UnknownLpHashFunction { value: LPHashFunction }, + + #[error("{value} is not a valid hex encoded hash: {source}")] + MalformedHash { + value: String, + source: hex::FromHexError, + }, +} + +impl TryFrom for KEM { + type Error = MalformedLPData; + fn try_from(value: LPKEM) -> Result { + match value { + LPKEM::MlKem768 => Ok(KEM::MlKem768), + LPKEM::XWing => Ok(KEM::XWing), + LPKEM::X25519 => Ok(KEM::X25519), + LPKEM::McEliece => Ok(KEM::McEliece), + // TODO: for backwards compatibility once variants within the LP crate change + // other => Err(MalformedLPData::UnknownLpKEM { value: other }), + } + } +} + +impl TryFrom for HashFunction { + type Error = MalformedLPData; + fn try_from(value: LPHashFunction) -> Result { + match value { + LPHashFunction::Blake3 => Ok(HashFunction::Blake3), + LPHashFunction::Shake128 => Ok(HashFunction::Shake128), + LPHashFunction::Shake256 => Ok(HashFunction::Shake256), + LPHashFunction::Sha256 => Ok(HashFunction::SHA256), + // TODO: for backwards compatibility once variants within the LP crate change + // other => Err(MalformedLPData::UnknownLpHashFunction { value: other }), + } + } +} + +impl TryFrom for SignatureScheme { + type Error = MalformedLPData; + fn try_from(value: LPSignatureScheme) -> Result { + match value { + LPSignatureScheme::Ed25519 => Ok(SignatureScheme::Ed25519), + // TODO: for backwards compatibility once variants within the LP crate change + // other => Err(MalformedLPData::UnknownLpSignatureScheme { value: other }), + } + } +} diff --git a/nym-api/nym-api-requests/src/models/described/v1.rs b/nym-api/nym-api-requests/src/models/described/v1.rs new file mode 100644 index 0000000000..2108422c2a --- /dev/null +++ b/nym-api/nym-api-requests/src/models/described/v1.rs @@ -0,0 +1,198 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::models::{ + AuthenticatorDetailsV1, AuxiliaryDetailsV1, BinaryBuildInformationOwned, DeclaredRolesV1, + HostInformationV1, IpPacketRouterDetailsV1, NetworkRequesterDetailsV1, + OffsetDateTimeJsonSchemaWrapper, WebSocketsV1, WireguardDetailsV1, +}; +use crate::nym_nodes::{BasicEntryInformation, NodeRole, SemiSkimmedNode, SkimmedNode}; +use nym_crypto::asymmetric::{ed25519, x25519}; +use nym_mixnet_contract_common::reward_params::Performance; +use nym_mixnet_contract_common::NodeId; +use nym_network_defaults::{DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT}; +use serde::{Deserialize, Serialize}; +use tracing::warn; +use utoipa::ToSchema; + +// to whoever is thinking of modifying this struct. +// you MUST NOT change its structure in any way - adding, removing or changing fields +// otherwise, it will break old clients as bincode serialisation is not backwards compatible +// even if you put `#[serde(default)]` all over the place +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +pub struct NymNodeDescriptionV1 { + #[schema(value_type = u32)] + pub node_id: NodeId, + pub contract_node_type: DescribedNodeTypeV1, + pub description: NymNodeDataV1, +} + +impl NymNodeDescriptionV1 { + pub fn version(&self) -> &str { + &self.description.build_information.build_version + } + + pub fn entry_information(&self) -> BasicEntryInformation { + BasicEntryInformation { + hostname: self.description.host_information.hostname.clone(), + ws_port: self.description.mixnet_websockets.ws_port, + wss_port: self.description.mixnet_websockets.wss_port, + } + } + + pub fn ed25519_identity_key(&self) -> ed25519::PublicKey { + self.description.host_information.keys.ed25519 + } + + pub fn current_sphinx_key(&self, current_rotation_id: u32) -> x25519::PublicKey { + let keys = &self.description.host_information.keys; + + if keys.current_x25519_sphinx_key.rotation_id == u32::MAX { + // legacy case (i.e. node doesn't support rotation) + return keys.current_x25519_sphinx_key.public_key; + } + + if current_rotation_id == keys.current_x25519_sphinx_key.rotation_id { + // it's the 'current' key + return keys.current_x25519_sphinx_key.public_key; + } + + if let Some(pre_announced) = &keys.pre_announced_x25519_sphinx_key { + if pre_announced.rotation_id == current_rotation_id { + return pre_announced.public_key; + } + } + + warn!( + "unexpected key rotation {current_rotation_id} for node {}", + self.node_id + ); + // this should never be reached, but just in case, return the fallback option + keys.current_x25519_sphinx_key.public_key + } + + pub fn to_skimmed_node( + &self, + current_rotation_id: u32, + role: NodeRole, + performance: Performance, + ) -> SkimmedNode { + let keys = &self.description.host_information.keys; + let entry = if self.description.declared_role.entry { + Some(self.entry_information()) + } else { + None + }; + + SkimmedNode { + node_id: self.node_id, + ed25519_identity_pubkey: keys.ed25519, + ip_addresses: self.description.host_information.ip_address.clone(), + mix_port: self.description.mix_port(), + x25519_sphinx_pubkey: self.current_sphinx_key(current_rotation_id), + // we can't use the declared roles, we have to take whatever was provided in the contract. + // why? say this node COULD operate as an exit, but it might be the case the contract decided + // to assign it an ENTRY role only. we have to use that one instead. + role, + supported_roles: self.description.declared_role, + entry, + performance, + } + } + + pub fn to_semi_skimmed_node( + &self, + current_rotation_id: u32, + role: NodeRole, + performance: Performance, + ) -> SemiSkimmedNode { + let skimmed_node = self.to_skimmed_node(current_rotation_id, role, performance); + + SemiSkimmedNode { + basic: skimmed_node, + x25519_noise_versioned_key: self + .description + .host_information + .keys + .x25519_versioned_noise, + } + } +} + +// to whoever is thinking of modifying this struct. +// you MUST NOT change its structure in any way - adding, removing or changing fields +// otherwise, it will break old clients as bincode serialisation is not backwards compatible +// even if you put `#[serde(default)]` all over the place +#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +#[serde(rename_all = "snake_case")] +#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] +#[cfg_attr( + feature = "generate-ts", + ts( + export, + export_to = "ts-packages/types/src/types/rust/DescribedNodeType.ts" + ) +)] +pub enum DescribedNodeTypeV1 { + LegacyMixnode, + LegacyGateway, + NymNode, +} + +impl DescribedNodeTypeV1 { + pub fn is_nym_node(&self) -> bool { + matches!(self, DescribedNodeTypeV1::NymNode) + } +} + +// to whoever is thinking of modifying this struct. +// you MUST NOT change its structure in any way - adding, removing or changing fields +// otherwise, it will break old clients as bincode serialisation is not backwards compatible +// even if you put `#[serde(default)]` all over the place +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +pub struct NymNodeDataV1 { + #[serde(default)] + pub last_polled: OffsetDateTimeJsonSchemaWrapper, + + pub host_information: HostInformationV1, + + #[serde(default)] + pub declared_role: DeclaredRolesV1, + + #[serde(default)] + pub auxiliary_details: AuxiliaryDetailsV1, + + // TODO: do we really care about ALL build info or just the version? + pub build_information: BinaryBuildInformationOwned, + + #[serde(default)] + pub network_requester: Option, + + #[serde(default)] + pub ip_packet_router: Option, + + #[serde(default)] + pub authenticator: Option, + + #[serde(default)] + pub wireguard: Option, + + // for now we only care about their ws/wss situation, nothing more + pub mixnet_websockets: WebSocketsV1, +} + +impl NymNodeDataV1 { + pub fn mix_port(&self) -> u16 { + self.auxiliary_details + .announce_ports + .mix_port + .unwrap_or(DEFAULT_MIX_LISTENING_PORT) + } + + pub fn verloc_port(&self) -> u16 { + self.auxiliary_details + .announce_ports + .verloc_port + .unwrap_or(DEFAULT_VERLOC_LISTENING_PORT) + } +} diff --git a/nym-api/nym-api-requests/src/models/described/v2.rs b/nym-api/nym-api-requests/src/models/described/v2.rs new file mode 100644 index 0000000000..429fb2ed88 --- /dev/null +++ b/nym-api/nym-api-requests/src/models/described/v2.rs @@ -0,0 +1,356 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::models::{ + AuthenticatorDetailsV1, AuxiliaryDetailsV1, BinaryBuildInformationOwned, DeclaredRolesV1, + DescribedNodeTypeV1, HostInformationV1, HostKeysV1, IpPacketRouterDetailsV1, + LewesProtocolDetailsV1, NetworkRequesterDetailsV1, NymNodeDataV1, NymNodeDescriptionV1, + OffsetDateTimeJsonSchemaWrapper, SphinxKeyV1, WebSocketsV1, WireguardDetailsV1, +}; +use crate::nym_nodes::{BasicEntryInformation, NodeRole, SemiSkimmedNode, SkimmedNode}; +use nym_crypto::asymmetric::{ed25519, x25519}; +use nym_mixnet_contract_common::reward_params::Performance; +use nym_mixnet_contract_common::NodeId; +use nym_network_defaults::{DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT}; +use nym_noise_keys::VersionedNoiseKeyV1; +use serde::{Deserialize, Serialize}; +use tracing::warn; +use utoipa::ToSchema; + +// no changes for the following types +pub type HostInformationV2 = HostInformationV1; +pub type DeclaredRolesV2 = DeclaredRolesV1; +pub type AuxiliaryDetailsV2 = AuxiliaryDetailsV1; +pub type NetworkRequesterDetailsV2 = NetworkRequesterDetailsV1; +pub type IpPacketRouterDetailsV2 = IpPacketRouterDetailsV1; +pub type AuthenticatorDetailsV2 = AuthenticatorDetailsV1; +pub type WireguardDetailsV2 = WireguardDetailsV1; +pub type WebSocketsV2 = WebSocketsV1; +pub type DescribedNodeTypeV2 = DescribedNodeTypeV1; +pub type HostKeysV2 = HostKeysV1; +pub type SphinxKeyV2 = SphinxKeyV1; +pub type VersionedNoiseKeyV2 = VersionedNoiseKeyV1; + +// to whoever is thinking of modifying this struct. +// you MUST NOT change its structure in any way - adding, removing or changing fields +// otherwise, it will break old clients as bincode serialisation is not backwards compatible +// even if you put `#[serde(default)]` all over the place +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +pub struct NymNodeDescriptionV2 { + #[schema(value_type = u32)] + pub node_id: NodeId, + pub contract_node_type: DescribedNodeTypeV2, + pub description: NymNodeDataV2, +} + +impl NymNodeDescriptionV2 { + pub fn version(&self) -> &str { + &self.description.build_information.build_version + } + + pub fn entry_information(&self) -> BasicEntryInformation { + BasicEntryInformation { + hostname: self.description.host_information.hostname.clone(), + ws_port: self.description.mixnet_websockets.ws_port, + wss_port: self.description.mixnet_websockets.wss_port, + } + } + + pub fn ed25519_identity_key(&self) -> ed25519::PublicKey { + self.description.host_information.keys.ed25519 + } + + pub fn current_sphinx_key(&self, current_rotation_id: u32) -> x25519::PublicKey { + let keys = &self.description.host_information.keys; + + if keys.current_x25519_sphinx_key.rotation_id == u32::MAX { + // legacy case (i.e. node doesn't support rotation) + return keys.current_x25519_sphinx_key.public_key; + } + + if current_rotation_id == keys.current_x25519_sphinx_key.rotation_id { + // it's the 'current' key + return keys.current_x25519_sphinx_key.public_key; + } + + if let Some(pre_announced) = &keys.pre_announced_x25519_sphinx_key { + if pre_announced.rotation_id == current_rotation_id { + return pre_announced.public_key; + } + } + + warn!( + "unexpected key rotation {current_rotation_id} for node {}", + self.node_id + ); + // this should never be reached, but just in case, return the fallback option + keys.current_x25519_sphinx_key.public_key + } + + pub fn to_skimmed_node( + &self, + current_rotation_id: u32, + role: NodeRole, + performance: Performance, + ) -> SkimmedNode { + let keys = &self.description.host_information.keys; + let entry = if self.description.declared_role.entry { + Some(self.entry_information()) + } else { + None + }; + + SkimmedNode { + node_id: self.node_id, + ed25519_identity_pubkey: keys.ed25519, + ip_addresses: self.description.host_information.ip_address.clone(), + mix_port: self.description.mix_port(), + x25519_sphinx_pubkey: self.current_sphinx_key(current_rotation_id), + // we can't use the declared roles, we have to take whatever was provided in the contract. + // why? say this node COULD operate as an exit, but it might be the case the contract decided + // to assign it an ENTRY role only. we have to use that one instead. + role, + supported_roles: self.description.declared_role, + entry, + performance, + } + } + + pub fn to_semi_skimmed_node( + &self, + current_rotation_id: u32, + role: NodeRole, + performance: Performance, + ) -> SemiSkimmedNode { + let skimmed_node = self.to_skimmed_node(current_rotation_id, role, performance); + + SemiSkimmedNode { + basic: skimmed_node, + x25519_noise_versioned_key: self + .description + .host_information + .keys + .x25519_versioned_noise, + } + } +} + +// to whoever is thinking of modifying this struct. +// you MUST NOT change its structure in any way - adding, removing or changing fields +// otherwise, it will break old clients as bincode serialisation is not backwards compatible +// even if you put `#[serde(default)]` all over the place +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +pub struct NymNodeDataV2 { + #[serde(default)] + pub last_polled: OffsetDateTimeJsonSchemaWrapper, + + pub host_information: HostInformationV2, + + #[serde(default)] + pub declared_role: DeclaredRolesV2, + + #[serde(default)] + pub auxiliary_details: AuxiliaryDetailsV2, + + // TODO: do we really care about ALL build info or just the version? + pub build_information: BinaryBuildInformationOwned, + + #[serde(default)] + pub network_requester: Option, + + #[serde(default)] + pub ip_packet_router: Option, + + #[serde(default)] + pub authenticator: Option, + + #[serde(default)] + pub wireguard: Option, + + // for now we only care about their ws/wss situation, nothing more + pub mixnet_websockets: WebSocketsV2, + + #[serde(default)] + pub lewes_protocol: Option, +} + +impl NymNodeDataV2 { + pub fn mix_port(&self) -> u16 { + self.auxiliary_details + .announce_ports + .mix_port + .unwrap_or(DEFAULT_MIX_LISTENING_PORT) + } + + pub fn verloc_port(&self) -> u16 { + self.auxiliary_details + .announce_ports + .verloc_port + .unwrap_or(DEFAULT_VERLOC_LISTENING_PORT) + } +} + +impl From for NymNodeDataV1 { + fn from(data: NymNodeDataV2) -> Self { + NymNodeDataV1 { + last_polled: data.last_polled, + host_information: data.host_information, + declared_role: data.declared_role, + auxiliary_details: data.auxiliary_details, + build_information: data.build_information, + network_requester: data.network_requester, + ip_packet_router: data.ip_packet_router, + authenticator: data.authenticator, + wireguard: data.wireguard, + mixnet_websockets: data.mixnet_websockets, + } + } +} + +impl From for NymNodeDataV2 { + fn from(data: NymNodeDataV1) -> Self { + NymNodeDataV2 { + last_polled: data.last_polled, + host_information: data.host_information, + declared_role: data.declared_role, + auxiliary_details: data.auxiliary_details, + build_information: data.build_information, + network_requester: data.network_requester, + ip_packet_router: data.ip_packet_router, + authenticator: data.authenticator, + wireguard: data.wireguard, + mixnet_websockets: data.mixnet_websockets, + lewes_protocol: Default::default(), + } + } +} + +impl From for NymNodeDescriptionV1 { + fn from(value: NymNodeDescriptionV2) -> Self { + NymNodeDescriptionV1 { + node_id: value.node_id, + contract_node_type: value.contract_node_type, + description: value.description.into(), + } + } +} + +impl From for NymNodeDescriptionV2 { + fn from(value: NymNodeDescriptionV1) -> Self { + NymNodeDescriptionV2 { + node_id: value.node_id, + contract_node_type: value.contract_node_type, + description: value.description.into(), + } + } +} + +#[cfg(test)] +pub fn mock_nym_node_description(seed: u64) -> NymNodeDescriptionV2 { + use crate::models::{LPHashFunction, LPSignatureScheme, LPKEM}; + use nym_test_utils::helpers::{u64_seeded_rng, RngCore}; + + let mut rng = u64_seeded_rng(seed); + + let ed25519 = ed25519::KeyPair::new(&mut rng); + + // just reuse the same x25519 key for everything - this is just a data mock + let x25519 = x25519::KeyPair::new(&mut rng); + + let mut kem_hashes_wrapper = std::collections::HashMap::new(); + let mut signing_keys_hashes_wrapper = std::collections::HashMap::new(); + let mut kem_hashes = std::collections::HashMap::new(); + let mut signing_keys_hashes = std::collections::HashMap::new(); + + kem_hashes.insert( + LPHashFunction::Sha256, + hex::encode([(seed % 256) as u8; 32]), + ); + kem_hashes_wrapper.insert(LPKEM::X25519, kem_hashes); + + signing_keys_hashes.insert( + LPHashFunction::Sha256, + hex::encode([(seed % 256) as u8; 32]), + ); + signing_keys_hashes_wrapper.insert(LPSignatureScheme::Ed25519, signing_keys_hashes); + + NymNodeDescriptionV2 { + node_id: rng.next_u32(), + contract_node_type: DescribedNodeTypeV1::NymNode, + description: NymNodeDataV2 { + last_polled: time::OffsetDateTime::from_unix_timestamp(1767225600) + .unwrap() + .into(), + host_information: HostInformationV2 { + ip_address: vec![ + std::net::IpAddr::V4(std::net::Ipv4Addr::new(1, 2, 3, (seed % 255) as u8)), + ], + hostname: Some(format!("my-awesome-node-{seed}.com")), + keys: HostKeysV2 { + ed25519: *ed25519.public_key(), + x25519: *x25519.public_key(), + current_x25519_sphinx_key: SphinxKeyV2 { + rotation_id: 123, + public_key: *x25519.public_key(), + }, + pre_announced_x25519_sphinx_key: None, + x25519_versioned_noise: Some(VersionedNoiseKeyV2 { + supported_version: nym_noise_keys::NoiseVersion::V1, + x25519_pubkey: *x25519.public_key(), + }), + }, + }, + declared_role: DeclaredRolesV2 { + mixnode: false, + entry: true, + exit_nr: true, + exit_ipr: true, + }, + auxiliary_details: AuxiliaryDetailsV2 { + location: Some(celes::Country::switzerland()), + announce_ports: Default::default(), + accepted_operator_terms_and_conditions: true, + }, + build_information: BinaryBuildInformationOwned { + binary_name: "dummy-node".to_string(), + build_timestamp: "2021-02-23T20:14:46.558472672+00:00".to_string(), + build_version: "0.1.0-9-g46f83e1".to_string(), + commit_sha: "46f83e112520533338245862d366f6a02cef07d4".to_string(), + commit_timestamp: "2021-02-23T08:08:02-05:00".to_string(), + commit_branch: "master".to_string(), + rustc_version: "1.52.0-nightly".to_string(), + rustc_channel: "nightly".to_string(), + cargo_profile: "release".to_string(), + cargo_triple: "wasm32-unknown-unknown".to_string(), + }, + network_requester: Some(NetworkRequesterDetailsV2 { + address: "FhtkzizQg2JbZ19kGkRKXdjV2QnFbT5ww88ZAKaD4nkF.7Remi4UVYzn1yL3qYtEcQBGh6tzTYxMdYB4uqyHVc5Z4@62F81C9GrHDRja9WCqozemRFSzFPMecY85MbGwn6efve".to_string(), + uses_exit_policy: true, + }), + ip_packet_router: Some(IpPacketRouterDetailsV2 { + address: "FhtkzizQg2JbZ19kGkRKXdjV2QnFbT5ww88ZAKaD4nkF.7Remi4UVYzn1yL3qYtEcQBGh6tzTYxMdYB4uqyHVc5Z4@62F81C9GrHDRja9WCqozemRFSzFPMecY85MbGwn6efve".to_string(), + }), + authenticator: Some(AuthenticatorDetailsV2 { + address: "FhtkzizQg2JbZ19kGkRKXdjV2QnFbT5ww88ZAKaD4nkF.7Remi4UVYzn1yL3qYtEcQBGh6tzTYxMdYB4uqyHVc5Z4@62F81C9GrHDRja9WCqozemRFSzFPMecY85MbGwn6efve".to_string(), + }), + wireguard: Some(WireguardDetailsV2 { + port: 123, + tunnel_port: 234, + metadata_port: 456, + public_key: x25519.public_key().to_base58_string(), + }), + lewes_protocol: Some(LewesProtocolDetailsV1 { + enabled: true, + control_port: 1234, + data_port: 2345, + x25519: *x25519.public_key(), + kem_keys: kem_hashes_wrapper, + signing_keys: signing_keys_hashes_wrapper, + }), + mixnet_websockets: WebSocketsV2 { + ws_port: 9000, + wss_port: None, + }, + }, + } +} diff --git a/nym-api/nym-api-requests/src/models/mod.rs b/nym-api/nym-api-requests/src/models/mod.rs index 6d7ab23f96..c5f0026d44 100644 --- a/nym-api/nym-api-requests/src/models/mod.rs +++ b/nym-api/nym-api-requests/src/models/mod.rs @@ -31,7 +31,7 @@ pub use schema_helpers::*; pub use nym_mixnet_contract_common::{EpochId, KeyRotationId, KeyRotationState}; pub use nym_node_requests::api::v1::node::models::BinaryBuildInformationOwned; -pub use nym_noise_keys::VersionedNoiseKey; +pub use nym_noise_keys::VersionedNoiseKeyV1; #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] pub struct RequestError { diff --git a/nym-api/nym-api-requests/src/nym_nodes.rs b/nym-api/nym-api-requests/src/nym_nodes.rs index ca7ffab7e8..40b088a681 100644 --- a/nym-api/nym-api-requests/src/nym_nodes.rs +++ b/nym-api/nym-api-requests/src/nym_nodes.rs @@ -1,7 +1,7 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::models::{DeclaredRoles, NymNodeData, OffsetDateTimeJsonSchemaWrapper}; +use crate::models::{DeclaredRolesV1, NymNodeDataV1, OffsetDateTimeJsonSchemaWrapper}; use crate::pagination::{PaginatedResponse, Pagination}; use nym_crypto::asymmetric::ed25519::serde_helpers::bs58_ed25519_pubkey; use nym_crypto::asymmetric::x25519::serde_helpers::bs58_x25519_pubkey; @@ -9,7 +9,7 @@ use nym_crypto::asymmetric::{ed25519, x25519}; use nym_mixnet_contract_common::nym_node::Role; use nym_mixnet_contract_common::reward_params::Performance; use nym_mixnet_contract_common::{EpochId, Interval, NodeId}; -use nym_noise_keys::VersionedNoiseKey; +use nym_noise_keys::VersionedNoiseKeyV1; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::net::IpAddr; @@ -254,7 +254,7 @@ pub struct SkimmedNode { // needed for the purposes of sending appropriate test packets #[serde(default)] - pub supported_roles: DeclaredRoles, + pub supported_roles: DeclaredRolesV1, pub entry: Option, @@ -278,7 +278,7 @@ impl SkimmedNode { pub struct SemiSkimmedNode { pub basic: SkimmedNode, - pub x25519_noise_versioned_key: Option, + pub x25519_noise_versioned_key: Option, // pub location: } @@ -287,7 +287,7 @@ pub struct FullFatNode { pub expanded: SemiSkimmedNode, // kinda temporary for now to make as few changes as possible for now - pub self_described: Option, + pub self_described: Option, } #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, ToSchema)] diff --git a/nym-api/src/network_monitor/monitor/preparer.rs b/nym-api/src/network_monitor/monitor/preparer.rs index 074a4f612f..d2d23a9715 100644 --- a/nym-api/src/network_monitor/monitor/preparer.rs +++ b/nym-api/src/network_monitor/monitor/preparer.rs @@ -8,7 +8,7 @@ use crate::node_describe_cache::cache::DescribedNodes; use crate::node_describe_cache::NodeDescriptionTopologyExt; use crate::node_status_api::NodeStatusCache; use crate::support::caching::cache::SharedCache; -use nym_api_requests::models::{NodeAnnotation, NymNodeDescription}; +use nym_api_requests::models::{NodeAnnotation, NymNodeDescriptionV2}; use nym_contracts_common::NaiveFloat; use nym_crypto::asymmetric::{ed25519, x25519}; use nym_mixnet_contract_common::{LegacyMixLayer, NodeId}; @@ -179,7 +179,7 @@ impl PacketPreparer { rng: &mut R, current_rotation_id: u32, node_statuses: &HashMap, - mixing_nym_nodes: impl Iterator + 'a, + mixing_nym_nodes: impl Iterator + 'a, ) -> HashMap> { let mut layered_mixes = HashMap::new(); @@ -206,7 +206,7 @@ impl PacketPreparer { &self, current_rotation_id: u32, node_statuses: &HashMap, - gateway_capable_nym_nodes: impl Iterator + 'a, + gateway_capable_nym_nodes: impl Iterator + 'a, ) -> Vec<(RoutingNode, f64)> { let mut gateways = Vec::new(); @@ -361,7 +361,7 @@ impl PacketPreparer { fn nym_node_to_routing_node( &self, current_rotation_id: u32, - description: &NymNodeDescription, + description: &NymNodeDescriptionV2, ) -> Option { description.try_to_topology_node(current_rotation_id).ok() } diff --git a/nym-api/src/node_describe_cache/cache.rs b/nym-api/src/node_describe_cache/cache.rs index 3f3de776d6..0e00b986d6 100644 --- a/nym-api/src/node_describe_cache/cache.rs +++ b/nym-api/src/node_describe_cache/cache.rs @@ -1,61 +1,61 @@ // Copyright 2025 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use nym_api_requests::models::{DescribedNodeType, NymNodeData, NymNodeDescription}; +use nym_api_requests::models::{DescribedNodeTypeV2, NymNodeDataV2, NymNodeDescriptionV2}; use nym_mixnet_contract_common::NodeId; use std::collections::HashMap; use std::net::IpAddr; #[derive(Debug, Clone)] pub struct DescribedNodes { - pub(crate) nodes: HashMap, + pub(crate) nodes: HashMap, pub(crate) addresses_cache: HashMap, } impl DescribedNodes { - pub fn force_update(&mut self, node: NymNodeDescription) { + pub fn force_update(&mut self, node: NymNodeDescriptionV2) { for ip in &node.description.host_information.ip_address { self.addresses_cache.insert(*ip, node.node_id); } self.nodes.insert(node.node_id, node); } - pub fn get_description(&self, node_id: &NodeId) -> Option<&NymNodeData> { + pub fn get_description(&self, node_id: &NodeId) -> Option<&NymNodeDataV2> { self.nodes.get(node_id).map(|n| &n.description) } - pub fn get_node(&self, node_id: &NodeId) -> Option<&NymNodeDescription> { + pub fn get_node(&self, node_id: &NodeId) -> Option<&NymNodeDescriptionV2> { self.nodes.get(node_id) } - pub fn all_nodes(&self) -> impl Iterator { + pub fn all_nodes(&self) -> impl Iterator { self.nodes.values() } - pub fn all_nym_nodes(&self) -> impl Iterator { + pub fn all_nym_nodes(&self) -> impl Iterator { self.nodes .values() - .filter(|n| n.contract_node_type == DescribedNodeType::NymNode) + .filter(|n| n.contract_node_type == DescribedNodeTypeV2::NymNode) } - pub fn mixing_nym_nodes(&self) -> impl Iterator { + pub fn mixing_nym_nodes(&self) -> impl Iterator { self.nodes .values() - .filter(|n| n.contract_node_type == DescribedNodeType::NymNode) + .filter(|n| n.contract_node_type == DescribedNodeTypeV2::NymNode) .filter(|n| n.description.declared_role.mixnode) } - pub fn entry_capable_nym_nodes(&self) -> impl Iterator { + pub fn entry_capable_nym_nodes(&self) -> impl Iterator { self.nodes .values() - .filter(|n| n.contract_node_type == DescribedNodeType::NymNode) + .filter(|n| n.contract_node_type == DescribedNodeTypeV2::NymNode) .filter(|n| n.description.declared_role.entry) } - pub fn exit_capable_nym_nodes(&self) -> impl Iterator { + pub fn exit_capable_nym_nodes(&self) -> impl Iterator { self.nodes .values() - .filter(|n| n.contract_node_type == DescribedNodeType::NymNode) + .filter(|n| n.contract_node_type == DescribedNodeTypeV2::NymNode) .filter(|n| n.description.declared_role.can_operate_exit_gateway()) } diff --git a/nym-api/src/node_describe_cache/mod.rs b/nym-api/src/node_describe_cache/mod.rs index b72c1a9103..976904ce2c 100644 --- a/nym-api/src/node_describe_cache/mod.rs +++ b/nym-api/src/node_describe_cache/mod.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::support::caching::cache::UninitialisedCache; -use nym_api_requests::models::NymNodeDescription; +use nym_api_requests::models::{NymNodeDescriptionV1, NymNodeDescriptionV2}; use nym_config::defaults::DEFAULT_NYM_NODE_HTTP_PORT; use nym_mixnet_contract_common::NodeId; use nym_node_requests::api::client::NymNodeApiClientError; @@ -68,7 +68,19 @@ pub(crate) trait NodeDescriptionTopologyExt { ) -> Result; } -impl NodeDescriptionTopologyExt for NymNodeDescription { +impl NodeDescriptionTopologyExt for NymNodeDescriptionV1 { + fn try_to_topology_node( + &self, + current_rotation_id: u32, + ) -> Result { + // for the purposes of routing, performance is completely ignored, + // so add dummy value and piggyback on existing conversion + (&self.to_skimmed_node(current_rotation_id, Default::default(), Default::default())) + .try_into() + } +} + +impl NodeDescriptionTopologyExt for NymNodeDescriptionV2 { fn try_to_topology_node( &self, current_rotation_id: u32, diff --git a/nym-api/src/node_describe_cache/query_helpers.rs b/nym-api/src/node_describe_cache/query_helpers.rs index bcfbdf4049..c800638efc 100644 --- a/nym-api/src/node_describe_cache/query_helpers.rs +++ b/nym-api/src/node_describe_cache/query_helpers.rs @@ -5,14 +5,14 @@ use crate::node_describe_cache::NodeDescribeCacheError; use futures::future::{maybe_done, MaybeDone}; use futures::{FutureExt, TryFutureExt}; use nym_api_requests::models::{ - AuthenticatorDetails, DeclaredRoles, HostInformation, IpPacketRouterDetails, - LewesProtocolDetails, NetworkRequesterDetails, NymNodeData, WebSockets, WireguardDetails, + AuthenticatorDetailsV2, AuxiliaryDetailsV2, DeclaredRolesV2, HostInformationV2, + IpPacketRouterDetailsV2, LewesProtocolDetailsV1, NetworkRequesterDetailsV2, NymNodeDataV2, + WebSocketsV2, WireguardDetailsV2, }; use nym_bin_common::build_information::BinaryBuildInformationOwned; use nym_config::defaults::mainnet; use nym_mixnet_contract_common::NodeId; use nym_node_requests::api::client::{NymNodeApiClientError, NymNodeApiClientExt}; -use nym_node_requests::api::v1::node::models::AuxiliaryDetails; use nym_node_requests::api::Client; use pin_project::pin_project; use std::future::Future; @@ -23,14 +23,14 @@ use tracing::debug; async fn network_requester_future( client: &Client, -) -> Result, NymNodeApiClientError> { +) -> Result, NymNodeApiClientError> { let Ok(nr) = client.get_network_requester().await else { return Ok(None); }; client.get_exit_policy().await.map(|exit_policy| { let uses_nym_exit_policy = exit_policy.upstream_source == mainnet::EXIT_POLICY_URL; - Some(NetworkRequesterDetails { + Some(NetworkRequesterDetailsV2 { address: nr.address, uses_exit_policy: exit_policy.enabled && uses_nym_exit_policy, }) @@ -55,7 +55,8 @@ pub(crate) async fn query_for_described_data( // old nym-nodes will not have this field, so use the default instead debug!("could not obtain auxiliary details of node {node_id}: {err} is it running an old version?") }) - .unwrap_or_else(|_| AuxiliaryDetails::default()), + .ok_into() + .unwrap_or_else(|_| AuxiliaryDetailsV2::default()), client.get_mixnet_websockets().ok_into().map_err(map_query_err), network_requester_future(client).map_err(map_query_err), // `ok_into` ultimately calls `IpPacketRouter::into` to transform it into `IpPacketRouterDetails` @@ -112,14 +113,14 @@ impl Future for NodeDescribedInfoMegaFuture where F1: Future>, - F2: Future>, - F3: Future, - F4: Future>, - F5: Future, NodeDescribeCacheError>>, - F6: Future>, - F7: Future>, - F8: Future>, - F9: Future>, + F2: Future>, + F3: Future, + F4: Future>, + F5: Future, NodeDescribeCacheError>>, + F6: Future>, + F7: Future>, + F8: Future>, + F9: Future>, { type Output = Result; @@ -202,15 +203,15 @@ where struct ResolvedNodeDescribedInfo { build_info: Result, - roles: Result, + roles: Result, // TODO: in the future make it return a Result as well. - auxiliary_details: AuxiliaryDetails, - websockets: Result, - network_requester: Result, NodeDescribeCacheError>, - ipr: Option, - authenticator: Option, - wireguard: Option, - lewes_protocol: Option, + auxiliary_details: AuxiliaryDetailsV2, + websockets: Result, + network_requester: Result, NodeDescribeCacheError>, + ipr: Option, + authenticator: Option, + wireguard: Option, + lewes_protocol: Option, } impl ResolvedNodeDescribedInfo { @@ -232,22 +233,22 @@ impl ResolvedNodeDescribedInfo { #[derive(Debug)] pub(crate) struct UnwrappedResolvedNodeDescribedInfo { pub(crate) build_info: BinaryBuildInformationOwned, - pub(crate) roles: DeclaredRoles, - pub(crate) auxiliary_details: AuxiliaryDetails, - pub(crate) websockets: WebSockets, - pub(crate) network_requester: Option, - pub(crate) ipr: Option, - pub(crate) authenticator: Option, - pub(crate) wireguard: Option, - pub(crate) lewes_protocol: Option, + pub(crate) roles: DeclaredRolesV2, + pub(crate) auxiliary_details: AuxiliaryDetailsV2, + pub(crate) websockets: WebSocketsV2, + pub(crate) network_requester: Option, + pub(crate) ipr: Option, + pub(crate) authenticator: Option, + pub(crate) wireguard: Option, + pub(crate) lewes_protocol: Option, } impl UnwrappedResolvedNodeDescribedInfo { pub(crate) fn into_node_description( self, - host_info: impl Into, - ) -> NymNodeData { - NymNodeData { + host_info: impl Into, + ) -> NymNodeDataV2 { + NymNodeDataV2 { host_information: host_info.into(), last_polled: OffsetDateTime::now_utc().into(), build_information: self.build_info, diff --git a/nym-api/src/node_describe_cache/refresh.rs b/nym-api/src/node_describe_cache/refresh.rs index 24fea96c44..801aa5dc29 100644 --- a/nym-api/src/node_describe_cache/refresh.rs +++ b/nym-api/src/node_describe_cache/refresh.rs @@ -3,7 +3,7 @@ use crate::node_describe_cache::query_helpers::query_for_described_data; use crate::node_describe_cache::NodeDescribeCacheError; -use nym_api_requests::models::{DescribedNodeType, NymNodeDescription}; +use nym_api_requests::models::{DescribedNodeTypeV2, NymNodeDescriptionV2}; use nym_bin_common::bin_info; use nym_config::defaults::DEFAULT_NYM_NODE_HTTP_PORT; use nym_crypto::asymmetric::ed25519; @@ -18,7 +18,7 @@ pub(crate) struct RefreshData { host: String, node_id: NodeId, expected_identity: ed25519::PublicKey, - node_type: DescribedNodeType, + node_type: DescribedNodeTypeV2, port: Option, } @@ -30,7 +30,7 @@ impl<'a> TryFrom<&'a NymNodeDetails> for RefreshData { Ok(RefreshData::new( &node.bond_information.node.host, node.bond_information.identity().parse()?, - DescribedNodeType::NymNode, + DescribedNodeTypeV2::NymNode, node.node_id(), node.bond_information.node.custom_http_port, )) @@ -41,7 +41,7 @@ impl RefreshData { pub fn new( host: impl Into, expected_identity: ed25519::PublicKey, - node_type: DescribedNodeType, + node_type: DescribedNodeTypeV2, node_id: NodeId, port: Option, ) -> Self { @@ -58,7 +58,7 @@ impl RefreshData { self.node_id } - pub(crate) async fn try_refresh(self, allow_all_ips: bool) -> Option { + pub(crate) async fn try_refresh(self, allow_all_ips: bool) -> Option { match try_get_description(self, allow_all_ips).await { Ok(description) => Some(description), Err(err) => { @@ -124,7 +124,7 @@ async fn try_get_client( async fn try_get_description( data: RefreshData, allow_all_ips: bool, -) -> Result { +) -> Result { let client = try_get_client(&data.host, data.node_id, data.port).await?; let map_query_err = |err| NodeDescribeCacheError::ApiFailure { @@ -158,7 +158,7 @@ async fn try_get_description( let node_info = query_for_described_data(&client, data.node_id).await?; let description = node_info.into_node_description(host_info.data); - Ok(NymNodeDescription { + Ok(NymNodeDescriptionV2 { node_id: data.node_id, contract_node_type: data.node_type, description, diff --git a/nym-api/src/node_status_api/cache/config_score.rs b/nym-api/src/node_status_api/cache/config_score.rs index 30f24d0b40..02ae220436 100644 --- a/nym-api/src/node_status_api/cache/config_score.rs +++ b/nym-api/src/node_status_api/cache/config_score.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::mixnet_contract_cache::cache::data::ConfigScoreData; -use nym_api_requests::models::{ConfigScore, NymNodeDescription}; +use nym_api_requests::models::{ConfigScore, NymNodeDescriptionV2}; use nym_contracts_common::NaiveFloat; use nym_mixnet_contract_common::VersionScoreFormulaParams; @@ -19,7 +19,7 @@ fn versions_behind_factor_to_config_score( pub(crate) fn calculate_config_score( config_score_data: &ConfigScoreData, - described_data: Option<&NymNodeDescription>, + described_data: Option<&NymNodeDescriptionV2>, ) -> ConfigScore { let Some(described) = described_data else { return ConfigScore::unavailable(); diff --git a/nym-api/src/nym_nodes/handlers/mod.rs b/nym-api/src/nym_nodes/handlers/mod.rs index 954349c03a..d242f328cf 100644 --- a/nym-api/src/nym_nodes/handlers/mod.rs +++ b/nym-api/src/nym_nodes/handlers/mod.rs @@ -1,495 +1,5 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::node_status_api::models::{AxumErrorResponse, AxumResult}; -use crate::support::http::helpers::{NodeIdParam, PaginationRequest}; -use crate::support::http::state::AppState; -use axum::extract::{Path, Query, State}; -use axum::routing::{get, post}; -use axum::{Json, Router}; -use nym_api_requests::models::{ - AnnotationResponse, NodeDatePerformanceResponse, NodePerformanceResponse, NodeRefreshBody, - NoiseDetails, NymNodeDescription, PerformanceHistoryResponse, RewardedSetResponse, - StakeSaturationResponse, UptimeHistoryResponse, -}; -use nym_api_requests::pagination::{PaginatedResponse, Pagination}; -use nym_contracts_common::NaiveFloat; -use nym_http_api_common::{FormattedResponse, Output, OutputParams}; -use nym_mixnet_contract_common::reward_params::Performance; -use nym_mixnet_contract_common::NymNodeDetails; -use serde::{Deserialize, Serialize}; -use std::time::Duration; -use time::{Date, OffsetDateTime}; -use tower_http::compression::CompressionLayer; -use utoipa::{IntoParams, ToSchema}; - -pub(crate) fn nym_node_routes() -> Router { - Router::new() - .route("/refresh-described", post(refresh_described)) - .route("/noise", get(nodes_noise)) - .route("/bonded", get(get_bonded_nodes)) - .route("/described", get(get_described_nodes)) - .route("/annotation/:node_id", get(get_node_annotation)) - .route("/performance/:node_id", get(get_current_node_performance)) - .route("/stake-saturation/:node_id", get(get_node_stake_saturation)) - .route( - "/historical-performance/:node_id", - get(get_historical_performance), - ) - .route( - "/performance-history/:node_id", - get(get_node_performance_history), - ) - // to make it compatible with all the explorers that were used to using 0-100 values - .route("/uptime-history/:node_id", get(get_node_uptime_history)) - .route("/rewarded-set", get(rewarded_set)) - .layer(CompressionLayer::new()) -} - -#[utoipa::path( - tag = "Nym Nodes", - get, - path = "/rewarded-set", - context_path = "/v1/nym-nodes", - responses( - (status = 200, content( - (RewardedSetResponse = "application/json"), - (RewardedSetResponse = "application/yaml"), - (RewardedSetResponse = "application/bincode") - )) - ), - params(OutputParams) -)] -async fn rewarded_set( - Query(output): Query, - State(state): State, -) -> AxumResult> { - let output = output.output.unwrap_or_default(); - - let rewarded_set = state.nym_contract_cache().rewarded_set_owned().await?; - - Ok(output.to_response(nym_mixnet_contract_common::EpochRewardedSet::from(rewarded_set).into())) -} - -#[utoipa::path( - tag = "Nym Nodes", - post, - request_body = NodeRefreshBody, - path = "/refresh-described", - context_path = "/v1/nym-nodes", -)] -async fn refresh_described( - State(state): State, - Json(request_body): Json, -) -> AxumResult> { - let Some(refresh_data) = state - .nym_contract_cache() - .get_node_refresh_data(request_body.node_identity) - .await - else { - return Err(AxumErrorResponse::not_found(format!( - "node with identity {} does not seem to exist", - request_body.node_identity - ))); - }; - - if !request_body.verify_signature() { - return Err(AxumErrorResponse::unauthorised("invalid request signature")); - } - - if request_body.is_stale() { - return Err(AxumErrorResponse::bad_request("the request is stale")); - } - - let node_id = refresh_data.node_id(); - if let Some(last) = state.forced_refresh.last_refreshed(node_id).await { - // max 1 refresh a minute - let minute_ago = OffsetDateTime::now_utc() - Duration::from_secs(60); - if last > minute_ago { - return Err(AxumErrorResponse::too_many( - "already refreshed node in the last minute", - )); - } - } - // to make sure you can't ddos the endpoint while a request is in progress - state.forced_refresh.set_last_refreshed(node_id).await; - let allow_all_ips = state.forced_refresh.allow_all_ip_addresses; - - if let Some(updated_data) = refresh_data.try_refresh(allow_all_ips).await { - let Ok(mut describe_cache) = state.described_nodes_cache.write().await else { - return Err(AxumErrorResponse::service_unavailable()); - }; - describe_cache.get_mut().force_update(updated_data) - } else { - return Err(AxumErrorResponse::unprocessable_entity( - "failed to refresh node description", - )); - } - - Ok(Json(())) -} - -#[utoipa::path( - tag = "Nym Nodes", - get, - path = "/noise", - context_path = "/v1/nym-nodes", - responses( - (status = 200, content( - (PaginatedResponse = "application/json"), - (PaginatedResponse = "application/yaml"), - (PaginatedResponse = "application/bincode") - )) - ), - params(PaginationRequest) -)] -async fn nodes_noise( - State(state): State, - Query(pagination): Query, -) -> AxumResult>> { - // TODO: implement it - let _ = pagination; - let output = pagination.output.unwrap_or_default(); - - let describe_cache = state.describe_nodes_cache_data().await?; - - let nodes = describe_cache - .all_nodes() - .filter_map(|n| { - n.description - .host_information - .keys - .x25519_versioned_noise - .map(|noise_key| (noise_key, n)) - }) - .map(|(noise_key, node)| NoiseDetails { - key: noise_key, - mixnet_port: node.description.mix_port(), - ip_addresses: node.description.host_information.ip_address.clone(), - }) - .collect::>(); - - let total = nodes.len(); - - Ok(output.to_response(PaginatedResponse { - pagination: Pagination { - total, - page: 0, - size: total, - }, - data: nodes, - })) -} - -#[utoipa::path( - tag = "Nym Nodes", - get, - path = "/bonded", - context_path = "/v1/nym-nodes", - responses( - (status = 200, content( - (PaginatedResponse = "application/json"), - (PaginatedResponse = "application/yaml"), - (PaginatedResponse = "application/bincode") - )) - ), - params(PaginationRequest) -)] -async fn get_bonded_nodes( - State(state): State, - Query(pagination): Query, -) -> FormattedResponse> { - // TODO: implement it - let _ = pagination; - let output = pagination.output.unwrap_or_default(); - - let details = state.nym_contract_cache().nym_nodes().await; - let total = details.len(); - - output.to_response(PaginatedResponse { - pagination: Pagination { - total, - page: 0, - size: total, - }, - data: details, - }) -} - -#[utoipa::path( - tag = "Nym Nodes", - get, - path = "/described", - context_path = "/v1/nym-nodes", - responses( - (status = 200, content( - (PaginatedResponse = "application/json"), - (PaginatedResponse = "application/yaml"), - (PaginatedResponse = "application/bincode") - )) - ), - params(PaginationRequest) -)] -async fn get_described_nodes( - State(state): State, - Query(pagination): Query, -) -> AxumResult>> { - // TODO: implement it - let _ = pagination; - let output = pagination.output.unwrap_or_default(); - - let cache = state.described_nodes_cache.get().await?; - let descriptions = cache.all_nodes().cloned().collect::>(); - - Ok(output.to_response(PaginatedResponse { - pagination: Pagination { - total: descriptions.len(), - page: 0, - size: descriptions.len(), - }, - data: descriptions, - })) -} - -#[utoipa::path( - tag = "Nym Nodes", - get, - path = "/annotation/{node_id}", - context_path = "/v1/nym-nodes", - responses( - (status = 200, content( - (AnnotationResponse = "application/json"), - (AnnotationResponse = "application/yaml"), - (AnnotationResponse = "application/bincode") - )) - ), - params(NodeIdParam, OutputParams), -)] -async fn get_node_annotation( - Path(NodeIdParam { node_id }): Path, - Query(output): Query, - State(state): State, -) -> AxumResult> { - let output = output.output.unwrap_or_default(); - - let annotations = state - .node_status_cache - .node_annotations() - .await - .ok_or_else(AxumErrorResponse::internal)?; - - Ok(output.to_response(AnnotationResponse { - node_id, - annotation: annotations.get(&node_id).cloned(), - })) -} - -#[utoipa::path( - tag = "Nym Nodes", - get, - path = "/performance/{node_id}", - context_path = "/v1/nym-nodes", - responses( - (status = 200, content( - (NodePerformanceResponse = "application/json"), - (NodePerformanceResponse = "application/yaml"), - (NodePerformanceResponse = "application/bincode") - )) - ), - params(NodeIdParam, OutputParams), -)] -async fn get_current_node_performance( - Path(NodeIdParam { node_id }): Path, - Query(output): Query, - State(state): State, -) -> AxumResult> { - let output = output.output.unwrap_or_default(); - - let annotations = state - .node_status_cache - .node_annotations() - .await - .ok_or_else(AxumErrorResponse::internal)?; - - Ok(output.to_response(NodePerformanceResponse { - node_id, - performance: annotations - .get(&node_id) - .map(|n| n.last_24h_performance.naive_to_f64()), - })) -} - -#[utoipa::path( - tag = "Nym Nodes", - get, - path = "/stake-saturation/{node_id}", - context_path = "/v1/nym-nodes", - responses( - (status = 200, content( - (StakeSaturationResponse = "application/json"), - (StakeSaturationResponse = "application/yaml"), - (StakeSaturationResponse = "application/bincode") - )) - ), - params(NodeIdParam, OutputParams), -)] -async fn get_node_stake_saturation( - Path(NodeIdParam { node_id }): Path, - Query(output): Query, - State(state): State, -) -> AxumResult> { - let output = output.get_output(); - - let contract_cache = state.nym_contract_cache(); - let Some(node) = contract_cache.nym_node(node_id).await? else { - return Err(AxumErrorResponse::not_found("nym node bond not found")); - }; - - let rewarding_params = contract_cache.interval_reward_params().await?; - let as_at = contract_cache.cache_timestamp().await; - - Ok(output.to_response(StakeSaturationResponse { - saturation: node.rewarding_details.bond_saturation(&rewarding_params), - uncapped_saturation: node - .rewarding_details - .uncapped_bond_saturation(&rewarding_params), - as_at: as_at.unix_timestamp(), - })) -} - -// todo; probably extract it to requests crate -#[derive(Debug, Serialize, Deserialize, Copy, Clone, IntoParams, ToSchema)] -#[into_params(parameter_in = Query)] -pub(crate) struct DateQuery { - #[schema(value_type = String, example = "1970-01-01")] - pub(crate) date: Date, - - pub(crate) output: Option, -} - -#[utoipa::path( - tag = "Nym Nodes", - get, - path = "/historical-performance/{node_id}", - context_path = "/v1/nym-nodes", - responses( - (status = 200, content( - (NodeDatePerformanceResponse = "application/json"), - (NodeDatePerformanceResponse = "application/yaml"), - (NodeDatePerformanceResponse = "application/bincode") - )) - ), - params(DateQuery, NodeIdParam) -)] -async fn get_historical_performance( - Path(NodeIdParam { node_id }): Path, - Query(DateQuery { date, output }): Query, - State(state): State, -) -> AxumResult> { - let output = output.unwrap_or_default(); - - let uptime = state - .storage() - .get_historical_node_uptime_on(node_id, date) - .await?; - - Ok(output.to_response(NodeDatePerformanceResponse { - node_id, - date, - performance: uptime.and_then(|u| { - Performance::from_percentage_value(u.uptime as u64) - .map(|p| p.naive_to_f64()) - .ok() - }), - })) -} - -#[utoipa::path( - tag = "Nym Nodes", - get, - path = "/performance-history/{node_id}", - context_path = "/v1/nym-nodes", - responses( - (status = 200, content( - (PerformanceHistoryResponse = "application/json"), - (PerformanceHistoryResponse = "application/yaml"), - (PerformanceHistoryResponse = "application/bincode") - )) - ), - params(PaginationRequest, NodeIdParam) -)] -async fn get_node_performance_history( - Path(NodeIdParam { node_id }): Path, - State(state): State, - Query(pagination): Query, -) -> AxumResult> { - // TODO: implement it - let _ = pagination; - let output = pagination.output.unwrap_or_default(); - - let history = state - .storage() - .get_node_uptime_history(node_id) - .await? - .into_iter() - .filter_map(|u| u.try_into().ok()) - .collect::>(); - let total = history.len(); - - Ok(output.to_response(PerformanceHistoryResponse { - node_id, - history: PaginatedResponse { - pagination: Pagination { - total, - page: 0, - size: total, - }, - data: history, - }, - })) -} - -#[utoipa::path( - tag = "Nym Nodes", - get, - path = "/uptime-history/{node_id}", - context_path = "/v1/nym-nodes", - responses( - (status = 200, content( - (PerformanceHistoryResponse = "application/json"), - (PerformanceHistoryResponse = "application/yaml"), - (PerformanceHistoryResponse = "application/bincode") - )) - ), - params(PaginationRequest, NodeIdParam) -)] -async fn get_node_uptime_history( - Path(NodeIdParam { node_id }): Path, - State(state): State, - Query(pagination): Query, -) -> AxumResult> { - // TODO: implement it - let _ = pagination; - let output = pagination.output.unwrap_or_default(); - - let history = state - .storage() - .get_node_uptime_history(node_id) - .await? - .into_iter() - .filter_map(|u| u.try_into().ok()) - .collect::>(); - let total = history.len(); - - Ok(output.to_response(UptimeHistoryResponse { - node_id, - history: PaginatedResponse { - pagination: Pagination { - total, - page: 0, - size: total, - }, - data: history, - }, - })) -} +pub mod v1; +pub mod v2; diff --git a/nym-api/src/nym_nodes/handlers/v1.rs b/nym-api/src/nym_nodes/handlers/v1.rs new file mode 100644 index 0000000000..02878bcaa6 --- /dev/null +++ b/nym-api/src/nym_nodes/handlers/v1.rs @@ -0,0 +1,502 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::node_status_api::models::{AxumErrorResponse, AxumResult}; +use crate::support::http::helpers::{NodeIdParam, PaginationRequest}; +use crate::support::http::state::AppState; +use axum::extract::{Path, Query, State}; +use axum::routing::{get, post}; +use axum::{Json, Router}; +use nym_api_requests::models::{ + AnnotationResponse, NodeDatePerformanceResponse, NodePerformanceResponse, NodeRefreshBody, + NoiseDetails, NymNodeDescriptionV1, PerformanceHistoryResponse, RewardedSetResponse, + StakeSaturationResponse, UptimeHistoryResponse, +}; +use nym_api_requests::pagination::{PaginatedResponse, Pagination}; +use nym_contracts_common::NaiveFloat; +use nym_http_api_common::{FormattedResponse, Output, OutputParams}; +use nym_mixnet_contract_common::reward_params::Performance; +use nym_mixnet_contract_common::NymNodeDetails; +use serde::{Deserialize, Serialize}; +use std::time::Duration; +use time::{Date, OffsetDateTime}; +use tower_http::compression::CompressionLayer; +use utoipa::{IntoParams, ToSchema}; + +#[allow(deprecated)] +pub(crate) fn routes() -> Router { + Router::new() + .route("/refresh-described", post(refresh_described)) + .route("/noise", get(nodes_noise)) + .route("/bonded", get(get_bonded_nodes)) + .route("/described", get(get_described_nodes)) + .route("/annotation/:node_id", get(get_node_annotation)) + .route("/performance/:node_id", get(get_current_node_performance)) + .route("/stake-saturation/:node_id", get(get_node_stake_saturation)) + .route( + "/historical-performance/:node_id", + get(get_historical_performance), + ) + .route( + "/performance-history/:node_id", + get(get_node_performance_history), + ) + // to make it compatible with all the explorers that were used to using 0-100 values + .route("/uptime-history/:node_id", get(get_node_uptime_history)) + .route("/rewarded-set", get(rewarded_set)) + .layer(CompressionLayer::new()) +} + +#[utoipa::path( + tag = "Nym Nodes", + get, + path = "/rewarded-set", + context_path = "/v1/nym-nodes", + responses( + (status = 200, content( + (RewardedSetResponse = "application/json"), + (RewardedSetResponse = "application/yaml"), + (RewardedSetResponse = "application/bincode") + )) + ), + params(OutputParams) +)] +async fn rewarded_set( + Query(output): Query, + State(state): State, +) -> AxumResult> { + let output = output.output.unwrap_or_default(); + + let rewarded_set = state.nym_contract_cache().rewarded_set_owned().await?; + + Ok(output.to_response(nym_mixnet_contract_common::EpochRewardedSet::from(rewarded_set).into())) +} + +#[utoipa::path( + tag = "Nym Nodes", + post, + request_body = NodeRefreshBody, + path = "/refresh-described", + context_path = "/v1/nym-nodes", +)] +async fn refresh_described( + State(state): State, + Json(request_body): Json, +) -> AxumResult> { + let Some(refresh_data) = state + .nym_contract_cache() + .get_node_refresh_data(request_body.node_identity) + .await + else { + return Err(AxumErrorResponse::not_found(format!( + "node with identity {} does not seem to exist", + request_body.node_identity + ))); + }; + + if !request_body.verify_signature() { + return Err(AxumErrorResponse::unauthorised("invalid request signature")); + } + + if request_body.is_stale() { + return Err(AxumErrorResponse::bad_request("the request is stale")); + } + + let node_id = refresh_data.node_id(); + if let Some(last) = state.forced_refresh.last_refreshed(node_id).await { + // max 1 refresh a minute + let minute_ago = OffsetDateTime::now_utc() - Duration::from_secs(60); + if last > minute_ago { + return Err(AxumErrorResponse::too_many( + "already refreshed node in the last minute", + )); + } + } + // to make sure you can't ddos the endpoint while a request is in progress + state.forced_refresh.set_last_refreshed(node_id).await; + let allow_all_ips = state.forced_refresh.allow_all_ip_addresses; + + if let Some(updated_data) = refresh_data.try_refresh(allow_all_ips).await { + let Ok(mut describe_cache) = state.described_nodes_cache.write().await else { + return Err(AxumErrorResponse::service_unavailable()); + }; + describe_cache.get_mut().force_update(updated_data) + } else { + return Err(AxumErrorResponse::unprocessable_entity( + "failed to refresh node description", + )); + } + + Ok(Json(())) +} + +#[utoipa::path( + tag = "Nym Nodes", + get, + path = "/noise", + context_path = "/v1/nym-nodes", + responses( + (status = 200, content( + (PaginatedResponse = "application/json"), + (PaginatedResponse = "application/yaml"), + (PaginatedResponse = "application/bincode") + )) + ), + params(PaginationRequest) +)] +async fn nodes_noise( + State(state): State, + Query(pagination): Query, +) -> AxumResult>> { + // TODO: implement it + let _ = pagination; + let output = pagination.output.unwrap_or_default(); + + let describe_cache = state.describe_nodes_cache_data().await?; + + let nodes = describe_cache + .all_nodes() + .filter_map(|n| { + n.description + .host_information + .keys + .x25519_versioned_noise + .map(|noise_key| (noise_key, n)) + }) + .map(|(noise_key, node)| NoiseDetails { + key: noise_key, + mixnet_port: node.description.mix_port(), + ip_addresses: node.description.host_information.ip_address.clone(), + }) + .collect::>(); + + let total = nodes.len(); + + Ok(output.to_response(PaginatedResponse { + pagination: Pagination { + total, + page: 0, + size: total, + }, + data: nodes, + })) +} + +#[utoipa::path( + tag = "Nym Nodes", + get, + path = "/bonded", + context_path = "/v1/nym-nodes", + responses( + (status = 200, content( + (PaginatedResponse = "application/json"), + (PaginatedResponse = "application/yaml"), + (PaginatedResponse = "application/bincode") + )) + ), + params(PaginationRequest) +)] +async fn get_bonded_nodes( + State(state): State, + Query(pagination): Query, +) -> FormattedResponse> { + // TODO: implement it + let _ = pagination; + let output = pagination.output.unwrap_or_default(); + + let details = state.nym_contract_cache().nym_nodes().await; + let total = details.len(); + + output.to_response(PaginatedResponse { + pagination: Pagination { + total, + page: 0, + size: total, + }, + data: details, + }) +} + +#[utoipa::path( + tag = "Nym Nodes", + get, + path = "/described", + context_path = "/v1/nym-nodes", + responses( + (status = 200, content( + (PaginatedResponse = "application/json"), + (PaginatedResponse = "application/yaml"), + (PaginatedResponse = "application/bincode") + )) + ), + params(PaginationRequest) +)] +// #[deprecated(note = "use '/v2/nym-nodes/described' instead")] +async fn get_described_nodes( + State(state): State, + Query(pagination): Query, +) -> AxumResult>> { + // TODO: implement it + let _ = pagination; + let output = pagination.output.unwrap_or_default(); + + let cache = state.described_nodes_cache.get().await?; + + // convert description to V1 + let descriptions = cache + .all_nodes() + .map(|d| d.clone().into()) + .collect::>(); + + Ok(output.to_response(PaginatedResponse { + pagination: Pagination { + total: descriptions.len(), + page: 0, + size: descriptions.len(), + }, + data: descriptions, + })) +} + +#[utoipa::path( + tag = "Nym Nodes", + get, + path = "/annotation/{node_id}", + context_path = "/v1/nym-nodes", + responses( + (status = 200, content( + (AnnotationResponse = "application/json"), + (AnnotationResponse = "application/yaml"), + (AnnotationResponse = "application/bincode") + )) + ), + params(NodeIdParam, OutputParams), +)] +async fn get_node_annotation( + Path(NodeIdParam { node_id }): Path, + Query(output): Query, + State(state): State, +) -> AxumResult> { + let output = output.output.unwrap_or_default(); + + let annotations = state + .node_status_cache + .node_annotations() + .await + .ok_or_else(AxumErrorResponse::internal)?; + + Ok(output.to_response(AnnotationResponse { + node_id, + annotation: annotations.get(&node_id).cloned(), + })) +} + +#[utoipa::path( + tag = "Nym Nodes", + get, + path = "/performance/{node_id}", + context_path = "/v1/nym-nodes", + responses( + (status = 200, content( + (NodePerformanceResponse = "application/json"), + (NodePerformanceResponse = "application/yaml"), + (NodePerformanceResponse = "application/bincode") + )) + ), + params(NodeIdParam, OutputParams), +)] +async fn get_current_node_performance( + Path(NodeIdParam { node_id }): Path, + Query(output): Query, + State(state): State, +) -> AxumResult> { + let output = output.output.unwrap_or_default(); + + let annotations = state + .node_status_cache + .node_annotations() + .await + .ok_or_else(AxumErrorResponse::internal)?; + + Ok(output.to_response(NodePerformanceResponse { + node_id, + performance: annotations + .get(&node_id) + .map(|n| n.last_24h_performance.naive_to_f64()), + })) +} + +#[utoipa::path( + tag = "Nym Nodes", + get, + path = "/stake-saturation/{node_id}", + context_path = "/v1/nym-nodes", + responses( + (status = 200, content( + (StakeSaturationResponse = "application/json"), + (StakeSaturationResponse = "application/yaml"), + (StakeSaturationResponse = "application/bincode") + )) + ), + params(NodeIdParam, OutputParams), +)] +async fn get_node_stake_saturation( + Path(NodeIdParam { node_id }): Path, + Query(output): Query, + State(state): State, +) -> AxumResult> { + let output = output.get_output(); + + let contract_cache = state.nym_contract_cache(); + let Some(node) = contract_cache.nym_node(node_id).await? else { + return Err(AxumErrorResponse::not_found("nym node bond not found")); + }; + + let rewarding_params = contract_cache.interval_reward_params().await?; + let as_at = contract_cache.cache_timestamp().await; + + Ok(output.to_response(StakeSaturationResponse { + saturation: node.rewarding_details.bond_saturation(&rewarding_params), + uncapped_saturation: node + .rewarding_details + .uncapped_bond_saturation(&rewarding_params), + as_at: as_at.unix_timestamp(), + })) +} + +// todo; probably extract it to requests crate +#[derive(Debug, Serialize, Deserialize, Copy, Clone, IntoParams, ToSchema)] +#[into_params(parameter_in = Query)] +pub(crate) struct DateQuery { + #[schema(value_type = String, example = "1970-01-01")] + pub(crate) date: Date, + + pub(crate) output: Option, +} + +#[utoipa::path( + tag = "Nym Nodes", + get, + path = "/historical-performance/{node_id}", + context_path = "/v1/nym-nodes", + responses( + (status = 200, content( + (NodeDatePerformanceResponse = "application/json"), + (NodeDatePerformanceResponse = "application/yaml"), + (NodeDatePerformanceResponse = "application/bincode") + )) + ), + params(DateQuery, NodeIdParam) +)] +async fn get_historical_performance( + Path(NodeIdParam { node_id }): Path, + Query(DateQuery { date, output }): Query, + State(state): State, +) -> AxumResult> { + let output = output.unwrap_or_default(); + + let uptime = state + .storage() + .get_historical_node_uptime_on(node_id, date) + .await?; + + Ok(output.to_response(NodeDatePerformanceResponse { + node_id, + date, + performance: uptime.and_then(|u| { + Performance::from_percentage_value(u.uptime as u64) + .map(|p| p.naive_to_f64()) + .ok() + }), + })) +} + +#[utoipa::path( + tag = "Nym Nodes", + get, + path = "/performance-history/{node_id}", + context_path = "/v1/nym-nodes", + responses( + (status = 200, content( + (PerformanceHistoryResponse = "application/json"), + (PerformanceHistoryResponse = "application/yaml"), + (PerformanceHistoryResponse = "application/bincode") + )) + ), + params(PaginationRequest, NodeIdParam) +)] +async fn get_node_performance_history( + Path(NodeIdParam { node_id }): Path, + State(state): State, + Query(pagination): Query, +) -> AxumResult> { + // TODO: implement it + let _ = pagination; + let output = pagination.output.unwrap_or_default(); + + let history = state + .storage() + .get_node_uptime_history(node_id) + .await? + .into_iter() + .filter_map(|u| u.try_into().ok()) + .collect::>(); + let total = history.len(); + + Ok(output.to_response(PerformanceHistoryResponse { + node_id, + history: PaginatedResponse { + pagination: Pagination { + total, + page: 0, + size: total, + }, + data: history, + }, + })) +} + +#[utoipa::path( + tag = "Nym Nodes", + get, + path = "/uptime-history/{node_id}", + context_path = "/v1/nym-nodes", + responses( + (status = 200, content( + (PerformanceHistoryResponse = "application/json"), + (PerformanceHistoryResponse = "application/yaml"), + (PerformanceHistoryResponse = "application/bincode") + )) + ), + params(PaginationRequest, NodeIdParam) +)] +async fn get_node_uptime_history( + Path(NodeIdParam { node_id }): Path, + State(state): State, + Query(pagination): Query, +) -> AxumResult> { + // TODO: implement it + let _ = pagination; + let output = pagination.output.unwrap_or_default(); + + let history = state + .storage() + .get_node_uptime_history(node_id) + .await? + .into_iter() + .filter_map(|u| u.try_into().ok()) + .collect::>(); + let total = history.len(); + + Ok(output.to_response(UptimeHistoryResponse { + node_id, + history: PaginatedResponse { + pagination: Pagination { + total, + page: 0, + size: total, + }, + data: history, + }, + })) +} diff --git a/nym-api/src/nym_nodes/handlers/v2.rs b/nym-api/src/nym_nodes/handlers/v2.rs new file mode 100644 index 0000000000..502f2c6e1c --- /dev/null +++ b/nym-api/src/nym_nodes/handlers/v2.rs @@ -0,0 +1,58 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::node_status_api::models::{AxumErrorResponse, AxumResult}; +use crate::support::http::helpers::PaginationRequest; +use crate::support::http::state::AppState; +use axum::extract::{Query, State}; +use axum::routing::get; +use axum::Router; +use nym_api_requests::models::NymNodeDescriptionV2; +use nym_api_requests::pagination::PaginatedResponse; +use nym_http_api_common::FormattedResponse; +use tower_http::compression::CompressionLayer; + +pub(crate) fn routes() -> Router { + Router::new() + .route("/described", get(get_described_nodes)) + .layer(CompressionLayer::new()) +} + +#[utoipa::path( + tag = "Nym Nodes", + get, + path = "/described", + context_path = "/v2/nym-nodes", + responses( + (status = 200, content( + (PaginatedResponse = "application/json"), + (PaginatedResponse = "application/yaml"), + (PaginatedResponse = "application/bincode") + )) + ), + params(PaginationRequest) +)] +async fn get_described_nodes( + State(state): State, + Query(pagination): Query, +) -> AxumResult>> { + let _ = state; + let _ = pagination; + Err(AxumErrorResponse::not_implemented()) + + // // TODO: implement it + // let _ = pagination; + // let output = pagination.output.unwrap_or_default(); + // + // let cache = state.described_nodes_cache.get().await?; + // let descriptions = cache.all_nodes().cloned().collect::>(); + // + // Ok(output.to_response(PaginatedResponse { + // pagination: Pagination { + // total: descriptions.len(), + // page: 0, + // size: descriptions.len(), + // }, + // data: descriptions, + // })) +} diff --git a/nym-api/src/support/http/router.rs b/nym-api/src/support/http/router.rs index 56a8950968..88ca68bc8f 100644 --- a/nym-api/src/support/http/router.rs +++ b/nym-api/src/support/http/router.rs @@ -6,12 +6,11 @@ use crate::ecash::api_routes::handlers::ecash_routes; use crate::mixnet_contract_cache::handlers::{epoch_routes, legacy_nodes_routes}; use crate::network::handlers::nym_network_routes; use crate::node_status_api::handlers::status_routes; -use crate::nym_nodes::handlers::nym_node_routes; -use crate::status; use crate::support::http::openapi::ApiDoc; use crate::support::http::state::AppState; use crate::unstable_routes::v1::unstable_routes_v1; use crate::unstable_routes::v2::unstable_routes_v2; +use crate::{nym_nodes, status}; use anyhow::anyhow; use axum::response::Redirect; use axum::routing::get; @@ -61,12 +60,17 @@ impl RouterBuilder { .nest("/status", status_routes(network_monitor)) .nest("/network", nym_network_routes()) .nest("/api-status", status::handlers::api_status_routes()) - .nest("/nym-nodes", nym_node_routes()) + .nest("/nym-nodes", nym_nodes::handlers::v1::routes()) .nest("/ecash", ecash_routes()) .nest("/unstable", unstable_routes_v1()) .nest("/legacy", legacy_nodes_routes()), // CORS layer needs to be "outside" of routes ) - .nest("/v2", Router::new().nest("/unstable", unstable_routes_v2())); + .nest( + "/v2", + Router::new() + .nest("/unstable", unstable_routes_v2()) + .nest("/nym-nodes", nym_nodes::handlers::v2::routes()), + ); Self { unfinished_router: default_routes, diff --git a/nym-api/src/unstable_routes/v2/nym_nodes/semi_skimmed/mod.rs b/nym-api/src/unstable_routes/v2/nym_nodes/semi_skimmed/mod.rs index d86003c091..555718f50d 100644 --- a/nym-api/src/unstable_routes/v2/nym_nodes/semi_skimmed/mod.rs +++ b/nym-api/src/unstable_routes/v2/nym_nodes/semi_skimmed/mod.rs @@ -7,7 +7,7 @@ use crate::unstable_routes::helpers::refreshed_at; use crate::unstable_routes::v2::nym_nodes::helpers::NodesParamsWithRole; use axum::extract::{Query, State}; use nym_api_requests::models::{ - NodeAnnotation, NymNodeDescription, OffsetDateTimeJsonSchemaWrapper, + NodeAnnotation, NymNodeDescriptionV2, OffsetDateTimeJsonSchemaWrapper, }; use nym_api_requests::nym_nodes::{NodeRole, PaginatedCachedNodesResponseV2, SemiSkimmedNode}; use nym_api_requests::pagination::PaginatedResponse; @@ -29,7 +29,7 @@ fn build_nym_nodes_response<'a, NI>( active_only: bool, ) -> Vec where - NI: Iterator + 'a, + NI: Iterator + 'a, { let mut nodes = Vec::new(); for nym_node in nym_nodes_subset { diff --git a/nym-api/src/unstable_routes/v2/nym_nodes/skimmed/helpers.rs b/nym-api/src/unstable_routes/v2/nym_nodes/skimmed/helpers.rs index 56f9619803..3456c82444 100644 --- a/nym-api/src/unstable_routes/v2/nym_nodes/skimmed/helpers.rs +++ b/nym-api/src/unstable_routes/v2/nym_nodes/skimmed/helpers.rs @@ -7,7 +7,7 @@ use crate::unstable_routes::v2::nym_nodes::helpers::NodesParams; use crate::unstable_routes::v2::nym_nodes::skimmed::PaginatedSkimmedNodes; use axum::extract::{Query, State}; use nym_api_requests::models::{ - NodeAnnotation, NymNodeDescription, OffsetDateTimeJsonSchemaWrapper, + NodeAnnotation, NymNodeDescriptionV2, OffsetDateTimeJsonSchemaWrapper, }; use nym_api_requests::nym_nodes::{NodeRole, PaginatedCachedNodesResponseV2, SkimmedNode}; use nym_http_api_common::Output; @@ -25,7 +25,7 @@ fn build_nym_nodes_response<'a, NI>( active_only: bool, ) -> Vec where - NI: Iterator + 'a, + NI: Iterator + 'a, { let mut nodes = Vec::new(); for nym_node in nym_nodes_subset { @@ -91,7 +91,7 @@ pub(crate) async fn build_skimmed_nodes_response<'a, NI>( ) -> PaginatedSkimmedNodes where // iterator returning relevant subset of nym-nodes (like mixing nym-nodes, entries, etc.) - NI: Iterator + 'a, + NI: Iterator + 'a, { // TODO: implement it let _ = query_params.per_page; diff --git a/nym-authenticator-client/Cargo.toml b/nym-authenticator-client/Cargo.toml index 770aff710d..b1793b6cbf 100644 --- a/nym-authenticator-client/Cargo.toml +++ b/nym-authenticator-client/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "nym-authenticator-client" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +publish = false [lints] workspace = true @@ -20,12 +21,12 @@ tokio-util.workspace = true tokio.workspace = true tracing.workspace = true -nym-authenticator-requests = { path = "../common/authenticator-requests" } -nym-bandwidth-controller = { path = "../common/bandwidth-controller" } -nym-credentials-interface = { path = "../common/credentials-interface" } -nym-crypto = { path = "../common/crypto" } -nym-registration-common = { path = "../common/registration" } -nym-sdk = { path = "../sdk/rust/nym-sdk" } -nym-service-provider-requests-common = { path = "../common/service-provider-requests-common" } -nym-validator-client = { path = "../common/client-libs/validator-client" } -nym-wireguard-types = { path = "../common/wireguard-types" } +nym-authenticator-requests = { workspace = true } +nym-bandwidth-controller = { workspace = true } +nym-credentials-interface = { workspace = true } +nym-crypto = { workspace = true } +nym-registration-common = { workspace = true } +nym-sdk = { workspace = true } +nym-service-provider-requests-common = { workspace = true } +nym-validator-client = { workspace = true } +nym-wireguard-types = { workspace = true } diff --git a/nym-authenticator-client/src/lib.rs b/nym-authenticator-client/src/lib.rs index c09a766696..3b36c647e6 100644 --- a/nym-authenticator-client/src/lib.rs +++ b/nym-authenticator-client/src/lib.rs @@ -4,7 +4,7 @@ use nym_authenticator_requests::client_message::QueryMessageImpl; use nym_bandwidth_controller::{BandwidthTicketProvider, DEFAULT_TICKETS_TO_SPEND}; use nym_crypto::asymmetric::x25519::KeyPair; -use nym_registration_common::GatewayData; +use nym_registration_common::WireguardConfiguration; use std::net::{IpAddr, SocketAddr}; use std::sync::Arc; use std::time::Duration; @@ -261,7 +261,7 @@ impl AuthenticatorClient { &mut self, controller: &dyn BandwidthTicketProvider, ticketbook_type: TicketType, - ) -> std::result::Result { + ) -> std::result::Result { debug!("Registering with the wg gateway..."); let pub_key = self.peer_public_key(); @@ -348,8 +348,9 @@ impl AuthenticatorClient { &self.ip_addr, ®istered_data ); - let gateway_data = GatewayData { + let gateway_data = WireguardConfiguration { public_key: registered_data.pub_key().inner().into(), + psk: None, // Mixnet-based regsitration does not have psk endpoint: SocketAddr::new(self.ip_addr, registered_data.wg_port()), private_ipv4: registered_data.private_ips().ipv4, private_ipv6: registered_data.private_ips().ipv6, diff --git a/nym-browser-extension/storage/Cargo.toml b/nym-browser-extension/storage/Cargo.toml index 61a34e51e7..e25c1b107f 100644 --- a/nym-browser-extension/storage/Cargo.toml +++ b/nym-browser-extension/storage/Cargo.toml @@ -6,6 +6,7 @@ license = "Apache-2.0" repository = "https://github.com/nymtech/nym" description = "WebAssembly-based secure storage for browser extension mnemonics" authors = ["Nym Technologies SA "] +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -13,20 +14,20 @@ authors = ["Nym Technologies SA "] crate-type = ["cdylib", "rlib"] [dependencies] -bip39 = { workspace = true } -zeroize = { workspace = true } +bip39 = { workspace = true } +zeroize = { workspace = true } -js-sys = { workspace = true } -wasm-bindgen = { workspace = true } -wasm-bindgen-futures = { workspace = true } +js-sys = { workspace = true } +wasm-bindgen = { workspace = true } +wasm-bindgen-futures = { workspace = true } serde-wasm-bindgen = { workspace = true } -thiserror = { workspace = true } +thiserror = { workspace = true } -console_error_panic_hook = { workspace = true, optional = true } +console_error_panic_hook = { workspace = true, optional = true } -wasm-utils = { path = "../../common/wasm/utils" } -wasm-storage = { path = "../../common/wasm/storage" } +nym-wasm-utils = { workspace = true } +nym-wasm-storage = { workspace = true } #[package.metadata.wasm-pack.profile.release] diff --git a/nym-browser-extension/storage/src/error.rs b/nym-browser-extension/storage/src/error.rs index e05d0d5b9a..88f3a60582 100644 --- a/nym-browser-extension/storage/src/error.rs +++ b/nym-browser-extension/storage/src/error.rs @@ -1,9 +1,9 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use nym_wasm_storage::error::StorageError; +use nym_wasm_utils::wasm_error; use thiserror::Error; -use wasm_storage::error::StorageError; -use wasm_utils::wasm_error; #[derive(Debug, Error)] pub enum ExtensionStorageError { diff --git a/nym-browser-extension/storage/src/lib.rs b/nym-browser-extension/storage/src/lib.rs index d8bd0d694e..94e5809cc8 100644 --- a/nym-browser-extension/storage/src/lib.rs +++ b/nym-browser-extension/storage/src/lib.rs @@ -14,4 +14,4 @@ pub use error::ExtensionStorageError; pub use storage::ExtensionStorage; #[cfg(target_arch = "wasm32")] -pub use wasm_utils::set_panic_hook; +pub use nym_wasm_utils::set_panic_hook; diff --git a/nym-browser-extension/storage/src/storage.rs b/nym-browser-extension/storage/src/storage.rs index ba484f46aa..88c93eaa59 100644 --- a/nym-browser-extension/storage/src/storage.rs +++ b/nym-browser-extension/storage/src/storage.rs @@ -9,13 +9,13 @@ use crate::ExtensionStorageError; use js_sys::Promise; +use nym_wasm_storage::RawDbResult; +use nym_wasm_storage::{Build, Database, VersionChangeEvent, WasmStorage}; +use nym_wasm_utils::check_promise_result; +use nym_wasm_utils::error::{PromisableResult, PromisableResultError}; use std::sync::Arc; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::future_to_promise; -use wasm_storage::RawDbResult; -use wasm_storage::{Build, Database, VersionChangeEvent, WasmStorage}; -use wasm_utils::check_promise_result; -use wasm_utils::error::{PromisableResult, PromisableResultError}; use zeroize::Zeroizing; const STORAGE_NAME: &str = "nym-extension-storage"; diff --git a/nym-credential-proxy/nym-credential-proxy-requests/Cargo.toml b/nym-credential-proxy/nym-credential-proxy-requests/Cargo.toml index 76f91373b8..dbe84bf6f1 100644 --- a/nym-credential-proxy/nym-credential-proxy-requests/Cargo.toml +++ b/nym-credential-proxy/nym-credential-proxy-requests/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "nym-credential-proxy-requests" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "Request and response definitions for interacting with the Nym Credential Proxy" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -25,12 +26,12 @@ wasm-bindgen = { workspace = true, optional = true } ## openapi: utoipa = { workspace = true, optional = true, features = ["uuid"] } -nym-credentials = { path = "../../common/credentials" } -nym-credentials-interface = { path = "../../common/credentials-interface" } -nym-http-api-common = { path = "../../common/http-api-common", optional = true } -nym-http-api-client = { path = "../../common/http-api-client" } -nym-serde-helpers = { path = "../../common/serde-helpers", features = ["bs58"] } -nym-upgrade-mode-check = { path = "../../common/upgrade-mode-check" } +nym-credentials = { workspace = true } +nym-credentials-interface = { workspace = true } +nym-http-api-common = { workspace = true, optional = true } +nym-http-api-client = { workspace = true } +nym-serde-helpers = { workspace = true, features = ["bs58"] } +nym-upgrade-mode-check = { workspace = true } [target."cfg(target_arch = \"wasm32\")".dependencies.wasmtimer] workspace = true @@ -43,4 +44,4 @@ openapi = ["utoipa", "nym-http-api-common/utoipa", "nym-upgrade-mode-check/opena tsify = ["dep:tsify", "wasm-bindgen"] [lints] -workspace = true \ No newline at end of file +workspace = true diff --git a/nym-credential-proxy/nym-credential-proxy/Cargo.toml b/nym-credential-proxy/nym-credential-proxy/Cargo.toml index c727488d37..3d81b37f0f 100644 --- a/nym-credential-proxy/nym-credential-proxy/Cargo.toml +++ b/nym-credential-proxy/nym-credential-proxy/Cargo.toml @@ -8,6 +8,7 @@ documentation.workspace = true edition.workspace = true license.workspace = true rust-version.workspace = true +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -45,35 +46,35 @@ utoipa = { workspace = true, features = ["axum_extras", "time"] } utoipa-swagger-ui = { workspace = true, features = ["axum"] } zeroize.workspace = true -nym-bin-common = { path = "../../common/bin-common", features = [ +nym-bin-common = { workspace = true, features = [ "basic_tracing", ] } -nym-compact-ecash = { path = "../../common/nym_offline_compact_ecash" } -nym-config = { path = "../../common/config" } -nym-crypto = { path = "../../common/crypto", features = [ +nym-compact-ecash = { workspace = true } +nym-config = { workspace = true } +nym-crypto = { workspace = true, features = [ "asymmetric", "rand", "serde", ] } -nym-credentials = { path = "../../common/credentials" } -nym-credentials-interface = { path = "../../common/credentials-interface" } -nym-ecash-contract-common = { path = "../../common/cosmwasm-smart-contracts/ecash-contract" } -nym-http-api-common = { path = "../../common/http-api-common", features = [ +nym-credentials = { workspace = true } +nym-credentials-interface = { workspace = true } +nym-ecash-contract-common = { workspace = true } +nym-http-api-common = { workspace = true, features = [ "utoipa", "middleware", ] } -nym-http-api-client = { path = "../../common/http-api-client", default-features = false } -nym-validator-client = { path = "../../common/client-libs/validator-client" } -nym-network-defaults = { path = "../../common/network-defaults" } +nym-http-api-client = { workspace = true } +nym-validator-client = { workspace = true } +nym-network-defaults = { workspace = true } -nym-credential-proxy-requests = { path = "../nym-credential-proxy-requests", features = [ +nym-credential-proxy-requests = { workspace = true, features = [ "openapi", ] } -nym-upgrade-mode-check = { path = "../../common/upgrade-mode-check" } -nym-ecash-signer-check = { path = "../../common/ecash-signer-check" } -nym-pemstore = { path = "../../common/pemstore" } +nym-upgrade-mode-check = { workspace = true } +nym-ecash-signer-check = { workspace = true } +nym-pemstore = { workspace = true } -nym-credential-proxy-lib = { path = "../../common/credential-proxy" } +nym-credential-proxy-lib = { workspace = true } [dev-dependencies] tempfile = { workspace = true } diff --git a/nym-credential-proxy/vpn-api-lib-wasm/Cargo.toml b/nym-credential-proxy/vpn-api-lib-wasm/Cargo.toml index 8dc406eaf5..52baabd2cb 100644 --- a/nym-credential-proxy/vpn-api-lib-wasm/Cargo.toml +++ b/nym-credential-proxy/vpn-api-lib-wasm/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "nym-vpn-api-lib-wasm" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +publish = false [lib] crate-type = ["cdylib", "rlib"] @@ -27,12 +28,11 @@ tsify = { workspace = true, features = ["js"] } serde-wasm-bindgen = { workspace = true } # NYM: -nym-bin-common = { path = "../../common/bin-common" } -nym-crypto = { path = "../../common/crypto", features = ["asymmetric"] } -nym-compact-ecash = { path = "../../common/nym_offline_compact_ecash" } -nym-credentials = { path = "../../common/credentials" } -nym-credentials-interface = { path = "../../common/credentials-interface" } -nym-ecash-time = { path = "../../common/ecash-time", features = ["expiration"] } -nym-credential-proxy-requests = { path = "../nym-credential-proxy-requests", default-features = false, features = ["tsify"] } -wasm-utils = { path = "../../common/wasm/utils" } - +nym-bin-common = { workspace = true } +nym-crypto = { workspace = true, features = ["asymmetric"] } +nym-compact-ecash = { workspace = true } +nym-credentials = { workspace = true } +nym-credentials-interface = { workspace = true } +nym-ecash-time = { workspace = true, features = ["expiration"] } +nym-credential-proxy-requests = { workspace = true, features = ["tsify"] } +nym-wasm-utils = { workspace = true } diff --git a/nym-credential-proxy/vpn-api-lib-wasm/src/error.rs b/nym-credential-proxy/vpn-api-lib-wasm/src/error.rs index 7322254064..5e1255bdbc 100644 --- a/nym-credential-proxy/vpn-api-lib-wasm/src/error.rs +++ b/nym-credential-proxy/vpn-api-lib-wasm/src/error.rs @@ -1,9 +1,9 @@ // Copyright 2024 Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only +use nym_wasm_utils::wasm_error; use serde_wasm_bindgen::Error; use thiserror::Error; -use wasm_utils::wasm_error; #[derive(Debug, Error)] pub enum VpnApiLibError { diff --git a/nym-credential-proxy/vpn-api-lib-wasm/src/lib.rs b/nym-credential-proxy/vpn-api-lib-wasm/src/lib.rs index abdff1bfe8..00ca53805a 100644 --- a/nym-credential-proxy/vpn-api-lib-wasm/src/lib.rs +++ b/nym-credential-proxy/vpn-api-lib-wasm/src/lib.rs @@ -19,12 +19,12 @@ use nym_credentials::{ use nym_credentials_interface::TicketType; use nym_crypto::asymmetric::ed25519; use nym_ecash_time::{EcashTime, ecash_default_expiration_date}; +use nym_wasm_utils::console_error; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use time::Date; use tsify::Tsify; use wasm_bindgen::prelude::*; -use wasm_utils::console_error; use zeroize::Zeroizing; pub mod error; @@ -267,11 +267,11 @@ pub struct FullSerialisedNymIssuedTicketbook { #[wasm_bindgen(start)] pub fn main() { - wasm_utils::console_log!("[rust main]: rust module loaded"); - wasm_utils::console_log!( + nym_wasm_utils::console_log!("[rust main]: rust module loaded"); + nym_wasm_utils::console_log!( "vpn-api-lib version used:\n{}", nym_bin_common::bin_info!().pretty_print() ); - wasm_utils::console_log!("[rust main]: setting panic hook"); - wasm_utils::set_panic_hook(); + nym_wasm_utils::console_log!("[rust main]: setting panic hook"); + nym_wasm_utils::set_panic_hook(); } diff --git a/nym-data-observatory/.sqlx/query-08f4e54ac24fccd54f4208797b3749e457f8cd4ba3d7d906a7ab3bf5b4e7dc9c.json b/nym-data-observatory/.sqlx/query-08f4e54ac24fccd54f4208797b3749e457f8cd4ba3d7d906a7ab3bf5b4e7dc9c.json deleted file mode 100644 index cc5863fd0e..0000000000 --- a/nym-data-observatory/.sqlx/query-08f4e54ac24fccd54f4208797b3749e457f8cd4ba3d7d906a7ab3bf5b4e7dc9c.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO transaction\n (hash, height, index, success, messages, memo, signatures, signer_infos, fee, gas_wanted, gas_used, raw_log, logs, events)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)\n ON CONFLICT (hash) DO UPDATE\n SET height = excluded.height,\n index = excluded.index,\n success = excluded.success,\n messages = excluded.messages,\n memo = excluded.memo,\n signatures = excluded.signatures,\n signer_infos = excluded.signer_infos,\n fee = excluded.fee,\n gas_wanted = excluded.gas_wanted,\n gas_used = excluded.gas_used,\n raw_log = excluded.raw_log,\n logs = excluded.logs,\n events = excluded.events\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "Int8", - "Int4", - "Bool", - "Jsonb", - "Text", - "TextArray", - "Jsonb", - "Jsonb", - "Int8", - "Int8", - "Text", - "Jsonb", - "Jsonb" - ] - }, - "nullable": [] - }, - "hash": "08f4e54ac24fccd54f4208797b3749e457f8cd4ba3d7d906a7ab3bf5b4e7dc9c" -} diff --git a/nym-data-observatory/.sqlx/query-0d3709efacf763b06bf14803bb803b5ee5b27879b0026bb0480b3f2722318a75.json b/nym-data-observatory/.sqlx/query-0d3709efacf763b06bf14803bb803b5ee5b27879b0026bb0480b3f2722318a75.json deleted file mode 100644 index 36ba8bb96b..0000000000 --- a/nym-data-observatory/.sqlx/query-0d3709efacf763b06bf14803bb803b5ee5b27879b0026bb0480b3f2722318a75.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO validator (consensus_address, consensus_pubkey)\n VALUES ($1, $2)\n ON CONFLICT DO NOTHING\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "Text" - ] - }, - "nullable": [] - }, - "hash": "0d3709efacf763b06bf14803bb803b5ee5b27879b0026bb0480b3f2722318a75" -} diff --git a/nym-data-observatory/.sqlx/query-1c2fb0e9ffceca21ef8dbea19b116422b1f723d0a316314b50c43c8b29f8891d.json b/nym-data-observatory/.sqlx/query-1c2fb0e9ffceca21ef8dbea19b116422b1f723d0a316314b50c43c8b29f8891d.json deleted file mode 100644 index 2e10a89220..0000000000 --- a/nym-data-observatory/.sqlx/query-1c2fb0e9ffceca21ef8dbea19b116422b1f723d0a316314b50c43c8b29f8891d.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "DELETE FROM pre_commit WHERE height < $1", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [] - }, - "hash": "1c2fb0e9ffceca21ef8dbea19b116422b1f723d0a316314b50c43c8b29f8891d" -} diff --git a/nym-data-observatory/.sqlx/query-2561fb016951ea4cd29e43fb9a4a93e944b0d44ed1f7c1036f306e34372da11c.json b/nym-data-observatory/.sqlx/query-2561fb016951ea4cd29e43fb9a4a93e944b0d44ed1f7c1036f306e34372da11c.json deleted file mode 100644 index 0d1b70f8cc..0000000000 --- a/nym-data-observatory/.sqlx/query-2561fb016951ea4cd29e43fb9a4a93e944b0d44ed1f7c1036f306e34372da11c.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT height\n FROM block\n ORDER BY height ASC\n LIMIT 1\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "height", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - false - ] - }, - "hash": "2561fb016951ea4cd29e43fb9a4a93e944b0d44ed1f7c1036f306e34372da11c" -} diff --git a/nym-data-observatory/.sqlx/query-2679cdf11fa66c7920678cde860c57402119ec7c3aae731b0da831327301466f.json b/nym-data-observatory/.sqlx/query-2679cdf11fa66c7920678cde860c57402119ec7c3aae731b0da831327301466f.json deleted file mode 100644 index b97ea34d16..0000000000 --- a/nym-data-observatory/.sqlx/query-2679cdf11fa66c7920678cde860c57402119ec7c3aae731b0da831327301466f.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE metadata SET last_processed_height = GREATEST(last_processed_height, $1)", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [] - }, - "hash": "2679cdf11fa66c7920678cde860c57402119ec7c3aae731b0da831327301466f" -} diff --git a/nym-data-observatory/.sqlx/query-36ba5941aca6e7b604a10b8b0aba70635028f392fe794d6131827b083e1755e1.json b/nym-data-observatory/.sqlx/query-36ba5941aca6e7b604a10b8b0aba70635028f392fe794d6131827b083e1755e1.json deleted file mode 100644 index dede45475e..0000000000 --- a/nym-data-observatory/.sqlx/query-36ba5941aca6e7b604a10b8b0aba70635028f392fe794d6131827b083e1755e1.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE pruning SET last_pruned_height = $1", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [] - }, - "hash": "36ba5941aca6e7b604a10b8b0aba70635028f392fe794d6131827b083e1755e1" -} diff --git a/nym-data-observatory/.sqlx/query-3bdf81a9db6075f6f77224c30553f419a849d4ec45af40b052a4cbf09b44f3ec.json b/nym-data-observatory/.sqlx/query-3bdf81a9db6075f6f77224c30553f419a849d4ec45af40b052a4cbf09b44f3ec.json deleted file mode 100644 index e638bce922..0000000000 --- a/nym-data-observatory/.sqlx/query-3bdf81a9db6075f6f77224c30553f419a849d4ec45af40b052a4cbf09b44f3ec.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT last_pruned_height FROM pruning\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "last_pruned_height", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - false - ] - }, - "hash": "3bdf81a9db6075f6f77224c30553f419a849d4ec45af40b052a4cbf09b44f3ec" -} diff --git a/nym-data-observatory/.sqlx/query-52c27143720ddfdfd0f5644b60f5b67fd9281ce1de0653efa53b9d9b93cf335d.json b/nym-data-observatory/.sqlx/query-52c27143720ddfdfd0f5644b60f5b67fd9281ce1de0653efa53b9d9b93cf335d.json deleted file mode 100644 index 58af4f89c4..0000000000 --- a/nym-data-observatory/.sqlx/query-52c27143720ddfdfd0f5644b60f5b67fd9281ce1de0653efa53b9d9b93cf335d.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "DELETE FROM message WHERE height < $1", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [] - }, - "hash": "52c27143720ddfdfd0f5644b60f5b67fd9281ce1de0653efa53b9d9b93cf335d" -} diff --git a/nym-data-observatory/.sqlx/query-62e14613f5ffe692346a79086857a22f0444fbc679db1c06b651fb8b5538b278.json b/nym-data-observatory/.sqlx/query-62e14613f5ffe692346a79086857a22f0444fbc679db1c06b651fb8b5538b278.json deleted file mode 100644 index a7c102469d..0000000000 --- a/nym-data-observatory/.sqlx/query-62e14613f5ffe692346a79086857a22f0444fbc679db1c06b651fb8b5538b278.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO pre_commit (validator_address, height, timestamp, voting_power, proposer_priority)\n VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (validator_address, timestamp) DO NOTHING\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "Int8", - "Timestamp", - "Int8", - "Int8" - ] - }, - "nullable": [] - }, - "hash": "62e14613f5ffe692346a79086857a22f0444fbc679db1c06b651fb8b5538b278" -} diff --git a/nym-data-observatory/.sqlx/query-64a484fd46d8ec46797f944a4cced56b6e270ce186f0e49528865d1924343b78.json b/nym-data-observatory/.sqlx/query-64a484fd46d8ec46797f944a4cced56b6e270ce186f0e49528865d1924343b78.json deleted file mode 100644 index 08983f2af9..0000000000 --- a/nym-data-observatory/.sqlx/query-64a484fd46d8ec46797f944a4cced56b6e270ce186f0e49528865d1924343b78.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO block (height, hash, num_txs, total_gas, proposer_address, timestamp)\n VALUES ($1, $2, $3, $4, $5, $6)\n ON CONFLICT DO NOTHING\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8", - "Text", - "Int4", - "Int8", - "Text", - "Timestamp" - ] - }, - "nullable": [] - }, - "hash": "64a484fd46d8ec46797f944a4cced56b6e270ce186f0e49528865d1924343b78" -} diff --git a/nym-data-observatory/.sqlx/query-7e82426f5dbcadf1631ba1a806e19cc462d04222fb20ad76de2a40f3f4f8fe15.json b/nym-data-observatory/.sqlx/query-7e82426f5dbcadf1631ba1a806e19cc462d04222fb20ad76de2a40f3f4f8fe15.json deleted file mode 100644 index 3a60c573ed..0000000000 --- a/nym-data-observatory/.sqlx/query-7e82426f5dbcadf1631ba1a806e19cc462d04222fb20ad76de2a40f3f4f8fe15.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT height\n FROM block\n WHERE timestamp < $1\n ORDER BY timestamp DESC\n LIMIT 1\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "height", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [ - "Timestamp" - ] - }, - "nullable": [ - false - ] - }, - "hash": "7e82426f5dbcadf1631ba1a806e19cc462d04222fb20ad76de2a40f3f4f8fe15" -} diff --git a/nym-data-observatory/.sqlx/query-9455331f9be5a3be28e2bd399a36b2e2d6a9ad4b225c4c883aafc4e9f0428008.json b/nym-data-observatory/.sqlx/query-9455331f9be5a3be28e2bd399a36b2e2d6a9ad4b225c4c883aafc4e9f0428008.json deleted file mode 100644 index 309aa81d9c..0000000000 --- a/nym-data-observatory/.sqlx/query-9455331f9be5a3be28e2bd399a36b2e2d6a9ad4b225c4c883aafc4e9f0428008.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT height\n FROM block\n WHERE timestamp > $1\n ORDER BY timestamp\n LIMIT 1\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "height", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [ - "Timestamp" - ] - }, - "nullable": [ - false - ] - }, - "hash": "9455331f9be5a3be28e2bd399a36b2e2d6a9ad4b225c4c883aafc4e9f0428008" -} diff --git a/nym-data-observatory/.sqlx/query-bc7795e58ce71893c3f32a19db8e77b7bc0a1af315ffd42c3e68156d6e4ace70.json b/nym-data-observatory/.sqlx/query-bc7795e58ce71893c3f32a19db8e77b7bc0a1af315ffd42c3e68156d6e4ace70.json deleted file mode 100644 index caca484b94..0000000000 --- a/nym-data-observatory/.sqlx/query-bc7795e58ce71893c3f32a19db8e77b7bc0a1af315ffd42c3e68156d6e4ace70.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT COUNT(*) as count FROM pre_commit\n WHERE\n validator_address = $1\n AND height >= $2\n AND height <= $3\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "count", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [ - "Text", - "Int8", - "Int8" - ] - }, - "nullable": [ - null - ] - }, - "hash": "bc7795e58ce71893c3f32a19db8e77b7bc0a1af315ffd42c3e68156d6e4ace70" -} diff --git a/nym-data-observatory/.sqlx/query-be43d4873911deca784b7be0531ab7bd82ecd68041aa932a56c8ce09623251e4.json b/nym-data-observatory/.sqlx/query-be43d4873911deca784b7be0531ab7bd82ecd68041aa932a56c8ce09623251e4.json deleted file mode 100644 index f1df706371..0000000000 --- a/nym-data-observatory/.sqlx/query-be43d4873911deca784b7be0531ab7bd82ecd68041aa932a56c8ce09623251e4.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT * FROM validator\n WHERE EXISTS (\n SELECT 1 FROM pre_commit\n WHERE height = $1\n AND pre_commit.validator_address = validator.consensus_address\n )\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "consensus_address", - "type_info": "Text" - }, - { - "ordinal": 1, - "name": "consensus_pubkey", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [ - false, - false - ] - }, - "hash": "be43d4873911deca784b7be0531ab7bd82ecd68041aa932a56c8ce09623251e4" -} diff --git a/nym-data-observatory/.sqlx/query-c88d07fecc3f33deaa6e93db1469ce71582635df47f52dcf3fd1df4e7be6b96d.json b/nym-data-observatory/.sqlx/query-c88d07fecc3f33deaa6e93db1469ce71582635df47f52dcf3fd1df4e7be6b96d.json deleted file mode 100644 index 9bf3eaf97b..0000000000 --- a/nym-data-observatory/.sqlx/query-c88d07fecc3f33deaa6e93db1469ce71582635df47f52dcf3fd1df4e7be6b96d.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT last_processed_height FROM metadata\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "last_processed_height", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - false - ] - }, - "hash": "c88d07fecc3f33deaa6e93db1469ce71582635df47f52dcf3fd1df4e7be6b96d" -} diff --git a/nym-data-observatory/.sqlx/query-cc0ae74082d7d8a89f2d3364676890bbf6150ab394c72783114340d4def5f9ef.json b/nym-data-observatory/.sqlx/query-cc0ae74082d7d8a89f2d3364676890bbf6150ab394c72783114340d4def5f9ef.json deleted file mode 100644 index 5c0da1448a..0000000000 --- a/nym-data-observatory/.sqlx/query-cc0ae74082d7d8a89f2d3364676890bbf6150ab394c72783114340d4def5f9ef.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO message(transaction_hash, index, type, value, involved_accounts_addresses, height)\n VALUES ($1, $2, $3, $4, $5, $6)\n ON CONFLICT (transaction_hash, index) DO UPDATE\n SET height = excluded.height,\n type = excluded.type,\n value = excluded.value,\n involved_accounts_addresses = excluded.involved_accounts_addresses\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "Int8", - "Text", - "Jsonb", - "TextArray", - "Int8" - ] - }, - "nullable": [] - }, - "hash": "cc0ae74082d7d8a89f2d3364676890bbf6150ab394c72783114340d4def5f9ef" -} diff --git a/nym-data-observatory/.sqlx/query-cdba9b267f143c8a8c6c3d6ed713cf00236490b86779559d84740ec18bcfa3a9.json b/nym-data-observatory/.sqlx/query-cdba9b267f143c8a8c6c3d6ed713cf00236490b86779559d84740ec18bcfa3a9.json deleted file mode 100644 index 2ae11a8fbb..0000000000 --- a/nym-data-observatory/.sqlx/query-cdba9b267f143c8a8c6c3d6ed713cf00236490b86779559d84740ec18bcfa3a9.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "DELETE FROM block WHERE height < $1", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [] - }, - "hash": "cdba9b267f143c8a8c6c3d6ed713cf00236490b86779559d84740ec18bcfa3a9" -} diff --git a/nym-data-observatory/.sqlx/query-d89558c37c51e8e6b1e6a9d5a2b13d0598fd856aa019a0cbbae12d7cafb4672f.json b/nym-data-observatory/.sqlx/query-d89558c37c51e8e6b1e6a9d5a2b13d0598fd856aa019a0cbbae12d7cafb4672f.json deleted file mode 100644 index 1970629169..0000000000 --- a/nym-data-observatory/.sqlx/query-d89558c37c51e8e6b1e6a9d5a2b13d0598fd856aa019a0cbbae12d7cafb4672f.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "DELETE FROM transaction WHERE height < $1", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [] - }, - "hash": "d89558c37c51e8e6b1e6a9d5a2b13d0598fd856aa019a0cbbae12d7cafb4672f" -} diff --git a/nym-data-observatory/Cargo.toml b/nym-data-observatory/Cargo.toml index d23b40f20d..2eefa881ef 100644 --- a/nym-data-observatory/Cargo.toml +++ b/nym-data-observatory/Cargo.toml @@ -11,6 +11,7 @@ documentation.workspace = true edition.workspace = true license.workspace = true rust-version.workspace = true +publish = false [dependencies] anyhow = { workspace = true } @@ -19,13 +20,13 @@ axum = { workspace = true, features = ["tokio"] } chrono = { workspace = true } clap = { workspace = true, features = ["cargo", "derive", "env"] } cosmrs = { workspace = true } -nym-config = { path = "../common/config" } -nym-bin-common = { path = "../common/bin-common", features = ["output_format"] } -nym-network-defaults = { path = "../common/network-defaults" } -nym-task = { path = "../common/task" } -nym-validator-client = { path = "../common/client-libs/validator-client" } +nym-config = { workspace = true } +nym-bin-common = { workspace = true, features = ["output_format"] } +nym-network-defaults = { workspace = true } +nym-task = { workspace = true } +nym-validator-client = { workspace = true } nyxd-scraper-psql = { path = "../common/nyxd-scraper-psql" } -nyxd-scraper-shared = { path = "../common/nyxd-scraper-shared" } +nyxd-scraper-shared = { workspace = true } reqwest = { workspace = true, features = ["rustls-tls"] } schemars = { workspace = true } serde = { workspace = true, features = ["derive"] } @@ -49,4 +50,4 @@ anyhow = { workspace = true } blake3 = { workspace = true } glob = { workspace = true } tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } -sqlx = { workspace = true, features = ["runtime-tokio-rustls", "postgres"] } \ No newline at end of file +sqlx = { workspace = true, features = ["runtime-tokio-rustls", "postgres"] } diff --git a/nym-gateway-probe/Cargo.toml b/nym-gateway-probe/Cargo.toml index 8a41137fa5..bf8c621e59 100644 --- a/nym-gateway-probe/Cargo.toml +++ b/nym-gateway-probe/Cargo.toml @@ -7,6 +7,7 @@ homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +publish = false [lints] workspace = true @@ -14,8 +15,6 @@ workspace = true [dependencies] anyhow.workspace = true base64.workspace = true -bs58.workspace = true -bincode.workspace = true bytes.workspace = true clap = { workspace = true, features = ["cargo", "derive"] } futures.workspace = true @@ -23,9 +22,10 @@ hex.workspace = true tracing.workspace = true pnet_packet.workspace = true rand.workspace = true +reqwest = { workspace = true, features = ["socks"] } serde.workspace = true serde_json.workspace = true -thiserror.workspace = true +time = { workspace = true } tokio = { workspace = true, features = [ "process", "rt-multi-thread", @@ -36,40 +36,38 @@ tokio = { workspace = true, features = [ tokio-util.workspace = true tracing-subscriber.workspace = true url = { workspace = true } -x25519-dalek = { workspace = true, features = [ - "reusable_secrets", - "static_secrets", -] } +utoipa = { workspace = true, optional = true } + nym-api-requests = { path = "../nym-api/nym-api-requests" } -nym-authenticator-requests = { path = "../common/authenticator-requests" } -nym-bandwidth-controller = { path = "../common/bandwidth-controller" } -nym-bin-common = { path = "../common/bin-common" } -nym-client-core = { path = "../common/client-core" } -nym-crypto = { path = "../common/crypto" } -nym-config = { path = "../common/config" } +nym-authenticator-client = { workspace = true } +nym-authenticator-requests = { workspace = true } +nym-bandwidth-controller = { workspace = true } +nym-bin-common = { workspace = true } +nym-client-core = { workspace = true } +nym-config = { workspace = true } nym-connection-monitor = { path = "../common/nym-connection-monitor" } -nym-credentials-interface = { path = "../common/credentials-interface" } -nym-credential-utils = { path = "../common/credential-utils" } -nym-ip-packet-client = { path = "../nym-ip-packet-client" } -nym-authenticator-client = { path = "../nym-authenticator-client" } -nym-ip-packet-requests = { path = "../common/ip-packet-requests" } -nym-sdk = { path = "../sdk/rust/nym-sdk" } -nym-validator-client = { path = "../common/client-libs/validator-client" } -nym-credentials = { path = "../common/credentials" } -nym-http-api-client-macro = { path = "../common/http-api-client-macro" } +nym-credentials = { workspace = true } +nym-credentials-interface = { workspace = true } +nym-credential-utils = { workspace = true } +nym-crypto = { workspace = true } nym-http-api-client = { path = "../common/http-api-client" } -nym-node-status-client = { path = "../nym-node-status-api/nym-node-status-client" } -nym-node-requests = { path = "../nym-node/nym-node-requests" } -nym-registration-client = { path = "../nym-registration-client" } +nym-http-api-client-macro = { path = "../common/http-api-client-macro" } +nym-ip-packet-client = { workspace = true } +nym-ip-packet-requests = { workspace = true } +nym-kkt-ciphersuite = { workspace = true } nym-lp = { path = "../common/nym-lp" } -nym-mixnet-contract-common = { path = "../common/cosmwasm-smart-contracts/mixnet-contract" } nym-network-defaults = { path = "../common/network-defaults" } +nym-node-requests = { path = "../nym-node/nym-node-requests" } +nym-node-status-client = { path = "../nym-node-status-api/nym-node-status-client" } +nym-registration-client = { path = "../nym-registration-client" } nym-registration-common = { path = "../common/registration" } -time = { workspace = true } +nym-sdk = { workspace = true } +nym-topology = { workspace = true } +nym-validator-client = { workspace = true } -# TEMP: REMOVE BEFORE PR -nym-topology = { path = "../common/topology" } +[features] +utoipa = ["dep:utoipa"] [build-dependencies] anyhow = { workspace = true } diff --git a/nym-gateway-probe/src/bandwidth_helpers.rs b/nym-gateway-probe/src/bandwidth_helpers.rs deleted file mode 100644 index 1f0048d722..0000000000 --- a/nym-gateway-probe/src/bandwidth_helpers.rs +++ /dev/null @@ -1,249 +0,0 @@ -// Copyright 2025 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use anyhow::{Context, bail}; -use nym_bandwidth_controller::error::BandwidthControllerError; -use nym_client_core::client::base_client::storage::OnDiskPersistent; -use nym_credentials::CredentialSpendingData; -use nym_credentials_interface::TicketType; -use nym_node_status_client::models::AttachedTicketMaterials; -use nym_sdk::bandwidth::BandwidthImporter; -use nym_sdk::mixnet::{DisconnectedMixnetClient, EphemeralCredentialStorage}; -use nym_validator_client::nyxd::error::NyxdError; -use std::time::Duration; -use time::OffsetDateTime; -use tracing::{error, info}; - -pub(crate) async fn import_bandwidth( - bandwidth_importer: BandwidthImporter<'_, EphemeralCredentialStorage>, - attached_ticket_materials: AttachedTicketMaterials, -) -> anyhow::Result<()> { - // 1. import all auxiliary data - for master_vk in attached_ticket_materials.master_verification_keys { - let key = master_vk.try_unpack()?; - info!( - "importing master verification key for epoch {}", - key.epoch_id - ); - bandwidth_importer - .import_master_verification_key(&key) - .await?; - } - for coin_index_signatures in attached_ticket_materials.coin_indices_signatures { - let sigs = coin_index_signatures.try_unpack()?; - info!("importing coin index signatures epoch {}", sigs.epoch_id); - bandwidth_importer - .import_coin_index_signatures(&sigs) - .await?; - } - for expiration_date_signatures in attached_ticket_materials.expiration_date_signatures { - let sigs = expiration_date_signatures.try_unpack()?; - info!( - "importing expiration date signatures epoch {} and expiration {}", - sigs.epoch_id, sigs.expiration_date - ); - bandwidth_importer - .import_expiration_date_signatures(&sigs) - .await?; - } - - // 2. import actual tickets - for ticket in attached_ticket_materials.attached_tickets { - let ticketbook = ticket.ticketbook.try_unpack()?; - info!( - "importing partial ticketbook {}. index to use: {}", - ticketbook.ticketbook_type(), - ticket.usable_index - ); - bandwidth_importer - .import_partial_ticketbook(&ticketbook, ticket.usable_index, ticket.usable_index) - .await?; - } - - Ok(()) -} - -pub(crate) async fn acquire_bandwidth( - mnemonic: &str, - disconnected_mixnet_client: &DisconnectedMixnetClient, - ticketbook_type: TicketType, -) -> anyhow::Result<()> { - // TODO: make it configurable - const MAX_RETRIES: usize = 50; - for i in 0..MAX_RETRIES { - let attempt = i + 1; // since humans usually don't count from 0 in this instance - info!( - "attempt {attempt}/{MAX_RETRIES} for attempting to acquire {ticketbook_type} bandwidth" - ); - let bw_client = disconnected_mixnet_client - .create_bandwidth_client(mnemonic.to_string(), ticketbook_type) - .await?; - info!("Calling bandwidth controller acquire() for {ticketbook_type}"); - match bw_client.acquire().await { - Ok(_) => { - if i > 0 { - info!( - "managed to acquire {ticketbook_type} bandwidth after {attempt} attempts", - ); - } - return Ok(()); - } - Err(nym_sdk::Error::CredentialIssuanceError { source }) => match source { - nym_credential_utils::errors::Error::BandwidthControllerError( - BandwidthControllerError::Nyxd(nyxd_error), - ) => match nyxd_error { - // happens when sequence issue occurs during tx delivery - NyxdError::BroadcastTxErrorDeliverTx { - hash, - height, - code, - raw_log, - } => { - // unfortunately at this point we have to do string matching as the log - // is returned from the go nyxd binary - if raw_log.contains("account sequence mismatch") { - error!( - "another process is using the same mnemonic. we failed to broadcast transaction {hash} due to mismatched sequence number" - ) - } else { - return Err(NyxdError::BroadcastTxErrorDeliverTx { - hash, - height, - code, - raw_log, - } - .into()); - } - } - // happens when sequence issue occurs during tx simulate - NyxdError::AbciError { - code, - log, - pretty_log, - } => { - // unfortunately at this point we have to do string matching as the log - // is returned from the go nyxd binary - if log.contains("account sequence mismatch") { - error!( - "another process is using the same mnemonic. we failed to simulate transaction due to mismatched sequence number" - ) - } else { - return Err(NyxdError::AbciError { - code, - log, - pretty_log, - } - .into()); - } - } - other => { - return Err(other) - .context("another nyxd failure during bandwidth acquisition"); - } - }, - other => { - return Err(other.into()); - } - }, - Err(other) => { - return Err(other.into()); - } - } - - // add a bit of backoff as if the rpc node is slightly out of sync, - // we might use our retry budget for abci queries to the simulate endpoint - tokio::time::sleep(Duration::from_secs(1)).await; - } - - bail!("failed to acquire bandwidth after {MAX_RETRIES} attempts") -} - -/// Create a dummy credential for mock ecash testing -/// -/// Gateway with --lp-use-mock-ecash accepts any credential without verification, -/// so we only need to provide properly structured data with correct types. -/// -/// This is useful for local testing without requiring blockchain access or funded accounts. -/// -/// This uses a pre-serialized test credential from the wireguard tests - since MockEcashManager -/// doesn't verify anything, any valid CredentialSpendingData structure will work. -#[allow(clippy::expect_used)] // Test helper with hardcoded valid data -pub(crate) fn create_dummy_credential( - _gateway_identity: &[u8; 32], - _ticket_type: TicketType, -) -> CredentialSpendingData { - // This is a valid serialized CredentialSpendingData taken from integration tests - // See: common/wireguard-private-metadata/tests/src/lib.rs:CREDENTIAL_BYTES - const CREDENTIAL_BYTES: [u8; 1245] = [ - 0, 0, 4, 133, 96, 179, 223, 185, 136, 23, 213, 166, 59, 203, 66, 69, 209, 181, 227, 254, - 16, 102, 98, 237, 59, 119, 170, 111, 31, 194, 51, 59, 120, 17, 115, 229, 79, 91, 11, 139, - 154, 2, 212, 23, 68, 70, 167, 3, 240, 54, 224, 171, 221, 1, 69, 48, 60, 118, 119, 249, 123, - 35, 172, 227, 131, 96, 232, 209, 187, 123, 4, 197, 102, 90, 96, 45, 125, 135, 140, 99, 1, - 151, 17, 131, 143, 157, 97, 107, 139, 232, 212, 87, 14, 115, 253, 255, 166, 167, 186, 43, - 90, 96, 173, 105, 120, 40, 10, 163, 250, 224, 214, 200, 178, 4, 160, 16, 130, 59, 76, 193, - 39, 240, 3, 101, 141, 209, 183, 226, 186, 207, 56, 210, 187, 7, 164, 240, 164, 205, 37, 81, - 184, 214, 193, 195, 90, 205, 238, 225, 195, 104, 12, 123, 203, 57, 233, 243, 215, 145, 195, - 196, 57, 38, 125, 172, 18, 47, 63, 165, 110, 219, 180, 40, 58, 116, 92, 254, 160, 98, 48, - 92, 254, 232, 107, 184, 80, 234, 60, 160, 235, 249, 76, 41, 38, 165, 28, 40, 136, 74, 48, - 166, 50, 245, 23, 201, 140, 101, 79, 93, 235, 128, 186, 146, 126, 180, 134, 43, 13, 186, - 19, 195, 48, 168, 201, 29, 216, 95, 176, 198, 132, 188, 64, 39, 212, 150, 32, 52, 53, 38, - 228, 199, 122, 226, 217, 75, 40, 191, 151, 48, 164, 242, 177, 79, 14, 122, 105, 151, 85, - 88, 199, 162, 17, 96, 103, 83, 178, 128, 9, 24, 30, 74, 108, 241, 85, 240, 166, 97, 241, - 85, 199, 11, 198, 226, 234, 70, 107, 145, 28, 208, 114, 51, 12, 234, 108, 101, 202, 112, - 48, 185, 22, 159, 67, 109, 49, 27, 149, 90, 109, 32, 226, 112, 7, 201, 208, 209, 104, 31, - 97, 134, 204, 145, 27, 181, 206, 181, 106, 32, 110, 136, 115, 249, 201, 111, 5, 245, 203, - 71, 121, 169, 126, 151, 178, 236, 59, 221, 195, 48, 135, 115, 6, 50, 227, 74, 97, 107, 107, - 213, 90, 2, 203, 154, 138, 47, 128, 52, 134, 128, 224, 51, 65, 240, 90, 8, 55, 175, 180, - 178, 204, 206, 168, 110, 51, 57, 189, 169, 48, 169, 136, 121, 99, 51, 170, 178, 214, 74, 1, - 96, 151, 167, 25, 173, 180, 171, 155, 10, 55, 142, 234, 190, 113, 90, 79, 80, 244, 71, 166, - 30, 235, 113, 150, 133, 1, 218, 17, 109, 111, 223, 24, 216, 177, 41, 2, 204, 65, 221, 212, - 207, 236, 144, 6, 65, 224, 55, 42, 1, 1, 161, 134, 118, 127, 111, 220, 110, 127, 240, 71, - 223, 129, 12, 93, 20, 220, 60, 56, 71, 146, 184, 95, 132, 69, 28, 56, 53, 192, 213, 22, - 119, 230, 152, 225, 182, 188, 163, 219, 37, 175, 247, 73, 14, 247, 38, 72, 243, 1, 48, 131, - 59, 8, 13, 96, 143, 185, 127, 241, 161, 217, 24, 149, 193, 40, 16, 30, 202, 151, 28, 119, - 240, 153, 101, 156, 61, 193, 72, 245, 199, 181, 12, 231, 65, 166, 67, 142, 121, 207, 202, - 58, 197, 113, 188, 248, 42, 124, 105, 48, 161, 241, 55, 209, 36, 194, 27, 63, 233, 144, - 189, 85, 117, 234, 9, 139, 46, 31, 206, 114, 95, 131, 29, 240, 13, 81, 142, 140, 133, 33, - 30, 41, 141, 37, 80, 217, 95, 221, 76, 115, 86, 201, 165, 51, 252, 9, 28, 209, 1, 48, 150, - 74, 248, 212, 187, 222, 66, 210, 3, 200, 19, 217, 171, 184, 42, 148, 53, 150, 57, 50, 6, - 227, 227, 62, 49, 42, 148, 148, 157, 82, 191, 58, 24, 34, 56, 98, 120, 89, 105, 176, 85, - 15, 253, 241, 41, 153, 195, 136, 1, 48, 142, 126, 213, 101, 223, 79, 133, 230, 105, 38, - 161, 149, 2, 21, 136, 150, 42, 72, 218, 85, 146, 63, 223, 58, 108, 186, 183, 248, 62, 20, - 47, 34, 113, 160, 177, 204, 181, 16, 24, 212, 224, 35, 84, 51, 168, 56, 136, 11, 1, 48, - 135, 242, 62, 149, 230, 178, 32, 224, 119, 26, 234, 163, 237, 224, 114, 95, 112, 140, 170, - 150, 96, 125, 136, 221, 180, 78, 18, 11, 12, 184, 2, 198, 217, 119, 43, 69, 4, 172, 109, - 55, 183, 40, 131, 172, 161, 88, 183, 101, 1, 48, 173, 216, 22, 73, 42, 255, 211, 93, 249, - 87, 159, 115, 61, 91, 55, 130, 17, 216, 60, 34, 122, 55, 8, 244, 244, 153, 151, 57, 5, 144, - 178, 55, 249, 64, 211, 168, 34, 148, 56, 89, 92, 203, 70, 124, 219, 152, 253, 165, 0, 32, - 203, 116, 63, 7, 240, 222, 82, 86, 11, 149, 167, 72, 224, 55, 190, 66, 201, 65, 168, 184, - 96, 47, 194, 241, 168, 124, 7, 74, 214, 250, 37, 76, 32, 218, 69, 122, 103, 215, 145, 169, - 24, 212, 229, 168, 106, 10, 144, 31, 13, 25, 178, 242, 250, 106, 159, 40, 48, 163, 165, 61, - 130, 57, 146, 4, 73, 32, 254, 233, 125, 135, 212, 29, 111, 4, 177, 114, 15, 210, 170, 82, - 108, 110, 62, 166, 81, 209, 106, 176, 156, 14, 133, 242, 60, 127, 120, 242, 28, 97, 0, 1, - 32, 103, 93, 109, 89, 240, 91, 1, 84, 150, 50, 206, 157, 203, 49, 220, 120, 234, 175, 234, - 150, 126, 225, 94, 163, 164, 199, 138, 114, 62, 99, 106, 112, 1, 32, 171, 40, 220, 82, 241, - 203, 76, 146, 111, 139, 182, 179, 237, 182, 115, 75, 128, 201, 107, 43, 214, 0, 135, 217, - 160, 68, 150, 232, 144, 114, 237, 98, 32, 30, 134, 232, 59, 93, 163, 253, 244, 13, 202, 52, - 147, 168, 83, 121, 123, 95, 21, 210, 209, 225, 223, 143, 49, 10, 205, 238, 1, 22, 83, 81, - 70, 1, 32, 26, 76, 6, 234, 160, 50, 139, 102, 161, 232, 155, 106, 130, 171, 226, 210, 233, - 178, 85, 247, 71, 123, 55, 53, 46, 67, 148, 137, 156, 207, 208, 107, 1, 32, 102, 31, 4, 98, - 110, 156, 144, 61, 229, 140, 198, 84, 196, 238, 128, 35, 131, 182, 137, 125, 241, 95, 69, - 131, 170, 27, 2, 144, 75, 72, 242, 102, 3, 32, 121, 80, 45, 173, 56, 65, 218, 27, 40, 251, - 197, 32, 169, 104, 123, 110, 90, 78, 153, 166, 38, 9, 129, 228, 99, 8, 1, 116, 142, 233, - 162, 69, 32, 216, 169, 159, 116, 95, 12, 63, 176, 195, 6, 183, 123, 135, 75, 61, 112, 106, - 83, 235, 176, 41, 27, 248, 48, 71, 165, 170, 12, 92, 103, 103, 81, 32, 58, 74, 75, 145, - 192, 94, 153, 69, 80, 128, 241, 3, 16, 117, 192, 86, 161, 103, 44, 174, 211, 196, 182, 124, - 55, 11, 107, 142, 49, 88, 6, 41, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 37, 139, 240, 0, 0, - 0, 0, 0, 0, 0, 1, - ]; - - let mut credential = CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES) - .expect("Failed to deserialize test credential - this is a bug in the test harness"); - - // Update spend_date to today to pass validation - credential.spend_date = OffsetDateTime::now_utc().date(); - - credential -} diff --git a/nym-gateway-probe/src/common/bandwidth_helpers.rs b/nym-gateway-probe/src/common/bandwidth_helpers.rs new file mode 100644 index 0000000000..ee4bb8583e --- /dev/null +++ b/nym-gateway-probe/src/common/bandwidth_helpers.rs @@ -0,0 +1,188 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use anyhow::{Context, bail}; +use nym_bandwidth_controller::BandwidthTicketProvider; +use nym_bandwidth_controller::error::BandwidthControllerError; +use nym_bandwidth_controller::mock::MockBandwidthController; +use nym_client_core::client::base_client::storage::OnDiskPersistent; +use nym_credentials_interface::TicketType; +use nym_node_status_client::models::AttachedTicketMaterials; +use nym_sdk::NymNetworkDetails; +use nym_sdk::bandwidth::BandwidthImporter; +use nym_sdk::mixnet::{CredentialStorage, DisconnectedMixnetClient, EphemeralCredentialStorage}; +use nym_validator_client::nyxd::error::NyxdError; +use std::time::Duration; +use tracing::{error, info}; + +pub(crate) fn build_bandwidth_controller( + network: &NymNetworkDetails, + storage: S, + use_mock_ecash: bool, +) -> anyhow::Result> +where + S: CredentialStorage + 'static, + S::StorageError: Send + Sync + 'static, +{ + if !use_mock_ecash { + let config = nym_validator_client::nyxd::Config::try_from_nym_network_details(network)?; + + let nyxd_url = network + .endpoints + .first() + .map(|ep| ep.nyxd_url()) + .ok_or(anyhow::anyhow!("missing nyxd url"))?; + let rpc_client = + nym_validator_client::nyxd::NyxdClient::connect(config, nyxd_url.as_str())?; + + Ok(Box::new( + nym_bandwidth_controller::BandwidthController::new(storage, rpc_client), + )) + } else { + Ok(Box::new(MockBandwidthController::default())) + } +} + +pub(crate) async fn import_bandwidth( + bandwidth_importer: BandwidthImporter<'_, EphemeralCredentialStorage>, + attached_ticket_materials: AttachedTicketMaterials, +) -> anyhow::Result<()> { + // 1. import all auxiliary data + for master_vk in attached_ticket_materials.master_verification_keys { + let key = master_vk.try_unpack()?; + info!( + "importing master verification key for epoch {}", + key.epoch_id + ); + bandwidth_importer + .import_master_verification_key(&key) + .await?; + } + for coin_index_signatures in attached_ticket_materials.coin_indices_signatures { + let sigs = coin_index_signatures.try_unpack()?; + info!("importing coin index signatures epoch {}", sigs.epoch_id); + bandwidth_importer + .import_coin_index_signatures(&sigs) + .await?; + } + for expiration_date_signatures in attached_ticket_materials.expiration_date_signatures { + let sigs = expiration_date_signatures.try_unpack()?; + info!( + "importing expiration date signatures epoch {} and expiration {}", + sigs.epoch_id, sigs.expiration_date + ); + bandwidth_importer + .import_expiration_date_signatures(&sigs) + .await?; + } + + // 2. import actual tickets + for ticket in attached_ticket_materials.attached_tickets { + let ticketbook = ticket.ticketbook.try_unpack()?; + info!( + "importing partial ticketbook {}. index to use: {}", + ticketbook.ticketbook_type(), + ticket.usable_index + ); + bandwidth_importer + .import_partial_ticketbook(&ticketbook, ticket.usable_index, ticket.usable_index) + .await?; + } + + Ok(()) +} + +pub(crate) async fn acquire_bandwidth( + mnemonic: &str, + disconnected_mixnet_client: &DisconnectedMixnetClient, + ticketbook_type: TicketType, +) -> anyhow::Result<()> { + // TODO: make it configurable + const MAX_RETRIES: usize = 50; + for i in 0..MAX_RETRIES { + let attempt = i + 1; // since humans usually don't count from 0 in this instance + info!( + "attempt {attempt}/{MAX_RETRIES} for attempting to acquire {ticketbook_type} bandwidth" + ); + let bw_client = disconnected_mixnet_client + .create_bandwidth_client(mnemonic.to_string(), ticketbook_type) + .await?; + info!("Calling bandwidth controller acquire() for {ticketbook_type}"); + match bw_client.acquire().await { + Ok(_) => { + if i > 0 { + info!( + "managed to acquire {ticketbook_type} bandwidth after {attempt} attempts", + ); + } + return Ok(()); + } + Err(nym_sdk::Error::CredentialIssuanceError { source }) => match source { + nym_credential_utils::errors::Error::BandwidthControllerError( + BandwidthControllerError::Nyxd(nyxd_error), + ) => match nyxd_error { + // happens when sequence issue occurs during tx delivery + NyxdError::BroadcastTxErrorDeliverTx { + hash, + height, + code, + raw_log, + } => { + // unfortunately at this point we have to do string matching as the log + // is returned from the go nyxd binary + if raw_log.contains("account sequence mismatch") { + error!( + "another process is using the same mnemonic. we failed to broadcast transaction {hash} due to mismatched sequence number" + ) + } else { + return Err(NyxdError::BroadcastTxErrorDeliverTx { + hash, + height, + code, + raw_log, + } + .into()); + } + } + // happens when sequence issue occurs during tx simulate + NyxdError::AbciError { + code, + log, + pretty_log, + } => { + // unfortunately at this point we have to do string matching as the log + // is returned from the go nyxd binary + if log.contains("account sequence mismatch") { + error!( + "another process is using the same mnemonic. we failed to simulate transaction due to mismatched sequence number" + ) + } else { + return Err(NyxdError::AbciError { + code, + log, + pretty_log, + } + .into()); + } + } + other => { + return Err(other) + .context("another nyxd failure during bandwidth acquisition"); + } + }, + other => { + return Err(other.into()); + } + }, + Err(other) => { + return Err(other.into()); + } + } + + // add a bit of backoff as if the rpc node is slightly out of sync, + // we might use our retry budget for abci queries to the simulate endpoint + tokio::time::sleep(Duration::from_secs(1)).await; + } + + bail!("failed to acquire bandwidth after {MAX_RETRIES} attempts") +} diff --git a/nym-gateway-probe/src/common/helpers.rs b/nym-gateway-probe/src/common/helpers.rs new file mode 100644 index 0000000000..5693b8a7a9 --- /dev/null +++ b/nym-gateway-probe/src/common/helpers.rs @@ -0,0 +1,111 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::common::nodes::TestedNodeLpDetails; +use nym_crypto::asymmetric::ed25519; +use nym_ip_packet_requests::v8::response::{ + ControlResponse, DataResponse, InfoLevel, IpPacketResponse, IpPacketResponseData, +}; +use nym_lp::peer::LpRemotePeer; +use nym_sdk::{ + DebugConfig, NymApiTopologyProvider, NymApiTopologyProviderConfig, NymNetworkDetails, + TopologyProvider, mixnet::ReconstructedMessage, +}; +use nym_topology::NymTopology; +use tracing::*; +use url::Url; + +pub fn to_lp_remote_peer(identity: ed25519::PublicKey, data: TestedNodeLpDetails) -> LpRemotePeer { + LpRemotePeer::new(identity, data.x25519).with_key_digests( + data.expected_kem_key_hashes, + data.expected_signing_key_hashes, + ) +} + +pub fn mixnet_debug_config( + min_gateway_performance: Option, + ignore_egress_epoch_role: bool, +) -> nym_client_core::config::DebugConfig { + let mut debug_config = nym_client_core::config::DebugConfig::default(); + debug_config + .traffic + .disable_main_poisson_packet_distribution = true; + debug_config.cover_traffic.disable_loop_cover_traffic_stream = true; + if let Some(minimum_gateway_performance) = min_gateway_performance { + debug_config.topology.minimum_gateway_performance = minimum_gateway_performance; + } + if ignore_egress_epoch_role { + debug_config.topology.ignore_egress_epoch_role = ignore_egress_epoch_role; + } + + debug_config +} + +pub fn unpack_data_response(reconstructed_message: &ReconstructedMessage) -> Option { + match IpPacketResponse::from_reconstructed_message(reconstructed_message) { + Ok(response) => match response.data { + IpPacketResponseData::Data(data_response) => Some(data_response), + IpPacketResponseData::Control(control) => match *control { + ControlResponse::Info(info) => { + let msg = format!("Received info response from the mixnet: {}", info.reply); + match info.level { + InfoLevel::Info => info!("{msg}"), + InfoLevel::Warn => warn!("{msg}"), + InfoLevel::Error => error!("{msg}"), + } + None + } + _ => { + info!("Ignoring: {:?}", control); + None + } + }, + }, + Err(err) => { + warn!("Failed to parse mixnet message: {err}"); + None + } + } +} + +pub async fn fetch_topology( + network_details: &NymNetworkDetails, + debug_config: &DebugConfig, +) -> Result { + // get Nym API URLs from network_details + let nym_api_urls: Vec = network_details + .nym_api_urls + .as_ref() + .map(|urls| urls.iter().filter_map(|u| u.url.parse().ok()).collect()) + .or_else(|| { + network_details + .endpoints + .first() + .and_then(|e| e.api_url()) + .map(|url| vec![url]) + }) + .unwrap_or_default(); + + if nym_api_urls.is_empty() { + return Err(String::from("No nym-api URLs available to fetch topology")); + } + + let topology_config = NymApiTopologyProviderConfig { + min_mixnode_performance: debug_config.topology.minimum_mixnode_performance, + min_gateway_performance: debug_config.topology.minimum_gateway_performance, + use_extended_topology: debug_config.topology.use_extended_topology, + ignore_egress_epoch_role: debug_config.topology.ignore_egress_epoch_role, + }; + + let api_client = nym_http_api_client::Client::new_url(nym_api_urls[0].clone(), None) + .map_err(|e| e.to_string())?; + let mut provider = NymApiTopologyProvider::new(topology_config, nym_api_urls, api_client); + + match provider.get_new_topology().await { + Some(topology) => { + info!("Fetched network topology"); + Ok(topology) + } + None => Err(String::from("Failed to fetch network topology")), + } +} diff --git a/nym-gateway-probe/src/icmp.rs b/nym-gateway-probe/src/common/icmp.rs similarity index 100% rename from nym-gateway-probe/src/icmp.rs rename to nym-gateway-probe/src/common/icmp.rs diff --git a/nym-gateway-probe/src/common/mod.rs b/nym-gateway-probe/src/common/mod.rs index 930623615d..718ab6d8f3 100644 --- a/nym-gateway-probe/src/common/mod.rs +++ b/nym-gateway-probe/src/common/mod.rs @@ -6,8 +6,12 @@ //! This module contains shared functionality used by multiple test modes: //! - WireGuard tunnel testing via netstack -pub mod wireguard; - -pub use wireguard::{ - TwoHopWgTunnelConfig, WgTunnelConfig, run_tunnel_tests, run_two_hop_tunnel_tests, -}; +pub(crate) mod bandwidth_helpers; +pub(crate) mod helpers; +pub(crate) mod icmp; +pub(crate) mod netstack; +pub(crate) mod nodes; +pub(crate) mod probe_tests; +pub(crate) mod socks5_test; +pub mod types; +pub(crate) mod wireguard; diff --git a/nym-gateway-probe/src/netstack.rs b/nym-gateway-probe/src/common/netstack.rs similarity index 98% rename from nym-gateway-probe/src/netstack.rs rename to nym-gateway-probe/src/common/netstack.rs index 3c7bd0772f..7b6f473bed 100644 --- a/nym-gateway-probe/src/netstack.rs +++ b/nym-gateway-probe/src/common/netstack.rs @@ -1,9 +1,11 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only +use crate::config::NetstackArgs; use anyhow::Context; use serde::Deserialize; use std::ffi::{CStr, CString}; +use std::net::SocketAddr; mod sys { use std::ffi::{c_char, c_void}; @@ -15,8 +17,6 @@ mod sys { } } -use crate::NetstackArgs; - #[derive(serde::Serialize)] pub struct NetstackRequest { private_key: String, @@ -224,14 +224,14 @@ pub struct TwoHopNetstackRequestGo { pub entry_wg_ip: String, pub entry_private_key: String, pub entry_public_key: String, - pub entry_endpoint: String, + pub entry_endpoint: SocketAddr, pub entry_awg_args: String, // Exit tunnel configuration (connects via forwarder through entry) pub exit_wg_ip: String, pub exit_private_key: String, pub exit_public_key: String, - pub exit_endpoint: String, + pub exit_endpoint: SocketAddr, pub exit_awg_args: String, // Test parameters diff --git a/nym-gateway-probe/src/nodes.rs b/nym-gateway-probe/src/common/nodes.rs similarity index 69% rename from nym-gateway-probe/src/nodes.rs rename to nym-gateway-probe/src/common/nodes.rs index 9bb68046cb..4e79e84ce6 100644 --- a/nym-gateway-probe/src/nodes.rs +++ b/nym-gateway-probe/src/common/nodes.rs @@ -1,29 +1,32 @@ // Copyright 2025 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::TestedNodeDetails; use anyhow::{Context, anyhow, bail}; use nym_api_requests::models::{ - AuthenticatorDetails, DeclaredRoles, DescribedNodeType, HostInformation, IpPacketRouterDetails, - LewesProtocolDetails, NetworkRequesterDetails, NymNodeData, OffsetDateTimeJsonSchemaWrapper, - WebSockets, WireguardDetails, + AuthenticatorDetailsV1, DeclaredRolesV1, DescribedNodeTypeV1, HostInformationV1, + IpPacketRouterDetailsV1, NetworkRequesterDetailsV1, NymNodeDataV1, + OffsetDateTimeJsonSchemaWrapper, WebSocketsV1, WireguardDetailsV1, }; use nym_authenticator_requests::AuthenticatorVersion; use nym_bin_common::build_information::BinaryBuildInformationOwned; +use nym_crypto::asymmetric::x25519; use nym_http_api_client::UserAgent; +use nym_kkt_ciphersuite::SignatureScheme; +use nym_kkt_ciphersuite::{KEM, KEMKeyDigests}; use nym_network_defaults::DEFAULT_NYM_NODE_HTTP_PORT; use nym_node_requests::api::client::NymNodeApiClientExt; use nym_node_requests::api::v1::node::models::AuxiliaryDetails as NodeAuxiliaryDetails; use nym_sdk::mixnet::NodeIdentity; +use nym_sdk::mixnet::Recipient; use nym_validator_client::client::NymApiClientExt; -use nym_validator_client::models::NymNodeDescription; +use nym_validator_client::models::NymNodeDescriptionV1; use rand::prelude::IteratorRandom; use std::collections::HashMap; +use std::net::{IpAddr, SocketAddr}; use std::time::Duration; use time::OffsetDateTime; -use tracing::{debug, info, warn}; +use tracing::{debug, error, info, warn}; use url::Url; - // in the old behaviour we were getting all skimmed nodes to retrieve performance // that was ultimately unused // should we want to use it again, the code is commented out below @@ -87,7 +90,7 @@ use url::Url; #[derive(Clone)] pub struct DirectoryNode { - described: NymNodeDescription, + described: NymNodeDescriptionV1, } impl DirectoryNode { @@ -96,16 +99,19 @@ impl DirectoryNode { } pub fn to_testable_node(&self) -> anyhow::Result { - let exit_router_address = self - .described - .description + let description = &self.described.description; + + let exit_router_address = description .ip_packet_router .as_ref() .map(|ipr| ipr.address.parse().context("malformed ipr address")) .transpose()?; - let authenticator_address = self - .described - .description + let network_requester_address = description + .network_requester + .as_ref() + .map(|nr| nr.address.parse().context("malformed nr address")) + .transpose()?; + let authenticator_address = description .authenticator .as_ref() .map(|ipr| { @@ -114,32 +120,34 @@ impl DirectoryNode { .context("malformed authenticator address") }) .transpose()?; - let authenticator_version = AuthenticatorVersion::from( - self.described - .description - .build_information - .build_version - .as_str(), - ); - let ip_address = self - .described - .description + let authenticator_version = + AuthenticatorVersion::from(description.build_information.build_version.as_str()); + let ip_address = description .host_information .ip_address .first() - .copied(); + .copied() + .ok_or_else(|| anyhow!("no ip address known"))?; - // Derive LP address from gateway IP + default LP control port (41264) - // TODO: Update this when LP address is exposed in node description API - let lp_address = ip_address.map(|ip| std::net::SocketAddr::new(ip, 41264)); + // let lp_data = description.lewes_protocol.as_ref().and_then(|lp_data| { + // Some(TestedNodeLpDetails { + // address: SocketAddr::new(ip_address, lp_data.control_port), + // expected_kem_key_hashes: lp_data.kem_keys().ok()?, + // expected_signing_key_hashes: lp_data.signing_keys().ok()?, + // x25519: lp_data.x25519, + // // \/ TODO: proper derivation from build version + // lp_version: version::CURRENT, + // }) + // }); Ok(TestedNodeDetails { identity: self.identity(), exit_router_address, + network_requester_address, authenticator_address, authenticator_version, - ip_address, - lp_address, + ip_address: Some(ip_address), + lp_data: None, }) } } @@ -153,21 +161,18 @@ impl DirectoryNode { /// # Returns /// A `DirectoryNode` containing all gateway metadata, or an error if the query fails pub async fn query_gateway_by_ip(address: String) -> anyhow::Result { - info!("Querying gateway directly at address: {}", address); + info!("Querying gateway directly at address: {address}"); // Parse the address to check if it contains a port let addresses_to_try = if address.contains(':') { // Address already has port specified, use it directly - vec![ - format!("http://{}", address), - format!("https://{}", address), - ] + vec![format!("http://{address}"), format!("https://{address}")] } else { // No port specified, try multiple ports in order of likelihood vec![ - format!("http://{}:{}", address, DEFAULT_NYM_NODE_HTTP_PORT), // Standard port 8080 - format!("https://{}", address), // HTTPS proxy (443) - format!("http://{}", address), // HTTP proxy (80) + format!("http://{address}:{DEFAULT_NYM_NODE_HTTP_PORT}"), // Standard port 8080 + format!("https://{address}"), // HTTPS proxy (443) + format!("http://{address}"), // HTTP proxy (80) ] }; @@ -175,7 +180,7 @@ pub async fn query_gateway_by_ip(address: String) -> anyhow::Result anyhow::Result anyhow::Result = None; // Not needed for LP testing - let ip_packet_router: Option = - ipr_result.map(|ipr| IpPacketRouterDetails { + let network_requester: Option = + nr_result.map(|nr| NetworkRequesterDetailsV1 { + address: nr.address, + uses_exit_policy: false, // Field not availabe, to change if it becomes useful here + }); + let ip_packet_router: Option = + ipr_result.map(|ipr| IpPacketRouterDetailsV1 { address: ipr.address, }); - let authenticator: Option = - authenticator_result.map(|auth| AuthenticatorDetails { + let authenticator: Option = + authenticator_result.map(|auth| AuthenticatorDetailsV1 { address: auth.address, }); #[allow(deprecated)] - let wireguard: Option = - wireguard_result.map(|wg| WireguardDetails { + let wireguard: Option = + wireguard_result.map(|wg| WireguardDetailsV1 { port: wg.tunnel_port, // Use tunnel_port for deprecated port field tunnel_port: wg.tunnel_port, metadata_port: wg.metadata_port, public_key: wg.public_key, }); - let lp: Option = lp_result.ok().map(Into::into); - // Construct NymNodeData - let node_data = NymNodeData { + let node_data = NymNodeDataV1 { last_polled: OffsetDateTimeJsonSchemaWrapper(OffsetDateTime::now_utc()), - host_information: HostInformation { + host_information: HostInformationV1 { ip_address: host_info.data.ip_address, hostname: host_info.data.hostname, keys: host_info.data.keys.into(), }, - declared_role: DeclaredRoles { + declared_role: DeclaredRolesV1 { mixnode: roles.mixnode_enabled, entry: roles.gateway_enabled, exit_nr: roles.network_requester_enabled, exit_ipr: roles.ip_packet_router_enabled, }, - auxiliary_details: aux_details, + auxiliary_details: aux_details.into(), build_information: BinaryBuildInformationOwned { binary_name: build_info.binary_name, build_timestamp: build_info.build_timestamp, @@ -286,17 +314,17 @@ pub async fn query_gateway_by_ip(address: String) -> anyhow::Result anyhow::Result { + info!("Selecting random gateway with NR enabled"); + self.nodes + .iter() + .filter(|(_, n)| n.described.description.ip_packet_router.is_some()) + .choose(&mut rand::thread_rng()) + .context("no gateways running NR available") .map(|(id, _)| *id) } @@ -393,7 +431,7 @@ impl NymApiDirectory { .iter() .filter(|(_, n)| n.described.description.declared_role.entry) .choose(&mut rand::thread_rng()) - .ok_or(anyhow!("no entry gateways available")) + .context("no entry gateways available") .map(|(id, _)| *id) } @@ -413,4 +451,39 @@ impl NymApiDirectory { }; Ok(maybe_entry) } + + pub fn exit_gateway(&self, identity: &NodeIdentity) -> anyhow::Result { + let Some(maybe_entry) = self.nodes.get(identity).cloned() else { + bail!("{identity} does not exist") + }; + if !maybe_entry + .described + .description + .declared_role + .can_operate_exit_gateway() + { + bail!("{identity} is not an exit node") + }; + Ok(maybe_entry) + } +} + +#[derive(Debug, Clone)] +pub struct TestedNodeDetails { + pub identity: NodeIdentity, + pub exit_router_address: Option, + pub network_requester_address: Option, + pub authenticator_address: Option, + pub authenticator_version: AuthenticatorVersion, + pub ip_address: Option, + pub lp_data: Option, +} + +#[derive(Debug, Clone)] +pub struct TestedNodeLpDetails { + pub address: SocketAddr, + pub expected_kem_key_hashes: HashMap, + pub expected_signing_key_hashes: HashMap, + pub x25519: x25519::PublicKey, + pub lp_version: u8, } diff --git a/nym-gateway-probe/src/common/probe_tests.rs b/nym-gateway-probe/src/common/probe_tests.rs new file mode 100644 index 0000000000..0ee8140f77 --- /dev/null +++ b/nym-gateway-probe/src/common/probe_tests.rs @@ -0,0 +1,721 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::common::helpers::mixnet_debug_config; +use crate::common::nodes::{TestedNodeDetails, TestedNodeLpDetails}; +use crate::common::socks5_test::HttpsConnectivityTest; +use crate::common::types::{ + Entry, Exit, IpPingReplies, LpProbeResults, ProbeOutcome, Socks5ProbeResults, WgProbeResults, +}; +use crate::common::wireguard::{ + TwoHopWgTunnelConfig, WgTunnelConfig, run_tunnel_tests, run_two_hop_tunnel_tests, +}; +use crate::common::{helpers, icmp}; +use crate::config::{NetstackArgs, Socks5Args}; +use anyhow::bail; +use base64::{Engine, engine::general_purpose}; +use bytes::BytesMut; +use futures::StreamExt; +use nym_authenticator_client::AuthenticatorClient; +use nym_authenticator_requests::{ + AuthenticatorVersion, client_message::ClientMessage, response::AuthenticatorResponse, v2, v3, + v4, v5, v6, +}; +use nym_bandwidth_controller::BandwidthTicketProvider; +use nym_config::defaults::mixnet_vpn::{NYM_TUN_DEVICE_ADDRESS_V4, NYM_TUN_DEVICE_ADDRESS_V6}; +use nym_connection_monitor::self_ping_and_wait; +use nym_credentials_interface::{CredentialSpendingData, TicketType}; +use nym_crypto::asymmetric::{ed25519, x25519}; +use nym_ip_packet_client::IprClientConnect; +use nym_ip_packet_requests::{IpPair, codec::MultiIpPacketCodec}; +use nym_registration_client::{LpRegistrationClient, NestedLpSession}; +use nym_sdk::NymNetworkDetails; +use nym_sdk::mixnet::{MixnetClient, MixnetClientBuilder, NodeIdentity, Recipient, Socks5}; +use nym_topology::{HardcodedTopologyProvider, NymTopology}; +use std::{ + net::{IpAddr, Ipv4Addr, Ipv6Addr}, + sync::Arc, + time::Duration, +}; +use tokio::net::TcpStream; +use tokio_util::{codec::Decoder, sync::CancellationToken}; +use tracing::*; + +pub async fn wg_probe( + mut auth_client: AuthenticatorClient, + gateway_ip: IpAddr, + auth_version: AuthenticatorVersion, + awg_args: Option, + netstack_args: NetstackArgs, + // TODO: update type + credential: CredentialSpendingData, +) -> anyhow::Result { + info!("attempting to use authenticator version {auth_version:?}"); + + let mut rng = rand::thread_rng(); + + // that's a long conversion chain + // (it should be simplified later...) + // nym x25519 -> dalek x25519 -> wireguard wrapper x25519 + let private_key = nym_crypto::asymmetric::encryption::PrivateKey::new(&mut rng); + let public_key = private_key.public_key(); + + let authenticator_pub_key = public_key.inner().into(); + let init_message = match auth_version { + AuthenticatorVersion::V2 => ClientMessage::Initial(Box::new( + v2::registration::InitMessage::new(authenticator_pub_key), + )), + AuthenticatorVersion::V3 => ClientMessage::Initial(Box::new( + v3::registration::InitMessage::new(authenticator_pub_key), + )), + AuthenticatorVersion::V4 => ClientMessage::Initial(Box::new( + v4::registration::InitMessage::new(authenticator_pub_key), + )), + AuthenticatorVersion::V5 => ClientMessage::Initial(Box::new( + v5::registration::InitMessage::new(authenticator_pub_key), + )), + AuthenticatorVersion::V6 => ClientMessage::Initial(Box::new( + v6::registration::InitMessage::new(authenticator_pub_key), + )), + AuthenticatorVersion::V1 | AuthenticatorVersion::UNKNOWN => bail!("unknown version number"), + }; + + let mut wg_outcome = WgProbeResults::default(); + + info!( + "connecting to authenticator: {}...", + auth_client.auth_recipient + ); + let response = auth_client + .send_and_wait_for_response(&init_message) + .await?; + + let registered_data = match response { + AuthenticatorResponse::PendingRegistration(pending_registration_response) => { + // Unwrap since we have already checked that we have the keypair. + debug!("Verifying data"); + pending_registration_response.verify(&private_key)?; + + let credential = credential + .try_into() + .inspect_err(|err| error!("invalid zk-nym data: {err}")) + .ok(); + + let finalized_message = + pending_registration_response.finalise_registration(&private_key, credential); + let client_message = ClientMessage::Final(finalized_message); + + let response = auth_client + .send_and_wait_for_response(&client_message) + .await?; + let AuthenticatorResponse::Registered(registered_response) = response else { + bail!("Unexpected response"); + }; + registered_response + } + AuthenticatorResponse::Registered(registered_response) => registered_response, + _ => bail!("Unexpected response"), + }; + + let peer_public = registered_data.pub_key().inner(); + let public_key_bs64 = general_purpose::STANDARD.encode(peer_public.as_bytes()); + let private_key_hex = hex::encode(private_key.to_bytes()); + let public_key_hex = hex::encode(peer_public.as_bytes()); + + info!("WG connection details"); + info!("Peer public key: {}", public_key_bs64); + info!( + "ips {}(v4) {}(v6), port {}", + registered_data.private_ips().ipv4, + registered_data.private_ips().ipv6, + registered_data.wg_port(), + ); + + let wg_endpoint = format!("{gateway_ip}:{}", registered_data.wg_port()); + + info!("Successfully registered with the gateway"); + + wg_outcome.can_register = true; + + // Run tunnel connectivity tests using shared helper + let tunnel_config = WgTunnelConfig::new( + registered_data.private_ips().ipv4.to_string(), + registered_data.private_ips().ipv6.to_string(), + private_key_hex, + public_key_hex, + wg_endpoint, + ); + + run_tunnel_tests( + &tunnel_config, + &netstack_args, + &awg_args.unwrap_or_default(), + &mut wg_outcome, + ); + + Ok(wg_outcome) +} + +pub async fn lp_registration_probe( + gateway_identity: NodeIdentity, + gateway_lp_data: TestedNodeLpDetails, + bandwidth_controller: &dyn BandwidthTicketProvider, +) -> anyhow::Result { + let lp_address = gateway_lp_data.address; + let lp_version = gateway_lp_data.lp_version; + let peer = helpers::to_lp_remote_peer(gateway_identity, gateway_lp_data); + + info!("Starting LP registration probe for gateway at {lp_address}"); + + let mut lp_outcome = LpProbeResults::default(); + + // Generate Ed25519 keypair for this connection (X25519 will be derived internally by LP) + let mut rng = rand::thread_rng(); + let client_ed25519_keypair = std::sync::Arc::new(ed25519::KeyPair::new(&mut rng)); + + // Step 0: Derive X25519 keys from Ed25519 for the gateways + + // Create LP registration client (uses Ed25519 keys directly, derives X25519 internally) + let mut client = LpRegistrationClient::::new_with_default_config( + client_ed25519_keypair, + peer, + lp_address, + lp_version, + ); + + // Step 1: Perform handshake (connection is implicit in packet-per-connection model) + // LpRegistrationClient uses packet-per-connection model - connect() is gone, + // connection is established during handshake and registration automatically. + info!("Performing LP handshake at {lp_address}..."); + match client.perform_handshake().await { + Ok(_) => { + info!("LP handshake completed successfully"); + lp_outcome.can_connect = true; // Connection succeeded if handshake succeeded + lp_outcome.can_handshake = true; + } + Err(e) => { + let error_msg = format!("LP handshake failed: {}", e); + error!("{}", error_msg); + lp_outcome.error = Some(error_msg); + return Ok(lp_outcome); + } + } + + // Step 2: Register with gateway (send request + receive response in one call) + info!("Sending LP registration request..."); + + // Generate WireGuard keypair for dVPN registration + let mut rng = rand::thread_rng(); + let wg_keypair = nym_crypto::asymmetric::x25519::KeyPair::new(&mut rng); + + // Convert gateway identity to ed25519 public key + let gateway_ed25519_pubkey = match nym_crypto::asymmetric::ed25519::PublicKey::from_bytes( + &gateway_identity.to_bytes(), + ) { + Ok(key) => key, + Err(e) => { + let error_msg = format!("Failed to convert gateway identity: {}", e); + error!("{}", error_msg); + lp_outcome.error = Some(error_msg); + return Ok(lp_outcome); + } + }; + + // Register using the new packet-per-connection API (returns GatewayData directly) + let ticket_type = TicketType::V1WireguardEntry; + let gateway_data = match client + .register_dvpn( + &mut rng, + &wg_keypair, + &gateway_ed25519_pubkey, + bandwidth_controller, + ticket_type, + ) + .await + { + Ok(data) => data, + Err(e) => { + let error_msg = format!("LP registration failed: {}", e); + error!("{}", error_msg); + lp_outcome.error = Some(error_msg); + return Ok(lp_outcome); + } + }; + + info!("LP registration successful! Received gateway data:"); + info!(" - Gateway public key: {:?}", gateway_data.public_key); + info!( + " - PSK: {:?}", + gateway_data + .psk + .map(|k| general_purpose::STANDARD.encode(k)) + ); + info!(" - Private IPv4: {}", gateway_data.private_ipv4); + info!(" - Private IPv6: {}", gateway_data.private_ipv6); + info!(" - Endpoint: {}", gateway_data.endpoint); + lp_outcome.can_register = true; + + Ok(lp_outcome) +} + +/// LP-based WireGuard probe: Tests LP nested session registration + WireGuard tunnel connectivity +/// +/// This function tests the full VPN flow using LP registration instead of mixnet+authenticator: +/// 1. Connects to entry gateway (outer LP session) +/// 2. Registers with exit gateway via entry forwarding (nested LP session) +/// 3. Receives WireGuard configuration from both gateways +/// 4. Tests WireGuard tunnel connectivity (IPv4/IPv6) +/// +/// This validates that IP hiding works (exit sees entry IP, not client IP) and that the +/// full VPN tunnel operates correctly after LP registration. +/// +// Known issue in localnet mode - After this probe runs, container networking +// to the external internet becomes unstable while internal container-to-container traffic +// continues to work. The two-hop WireGuard tunnel itself succeeds (handshake completes), +// but subsequent DNS/ping tests may timeout. This appears to be related to Apple Container +// Runtime networking quirks combined with our NAT/iptables configuration. Tracked in +// beads issue nym-vbdo. Workaround: restart the localnet containers between probe runs. +pub async fn wg_probe_lp( + entry_gateway: &TestedNodeDetails, + exit_gateway: &TestedNodeDetails, + bandwidth_controller: &dyn BandwidthTicketProvider, + awg_args: Option, + netstack_args: NetstackArgs, +) -> anyhow::Result { + // Validate that both gateways have required information + let entry_lp_data = entry_gateway + .lp_data + .clone() + .ok_or_else(|| anyhow::anyhow!("Entry gateway missing LP data"))?; + + let exit_lp_data = exit_gateway + .lp_data + .clone() + .ok_or_else(|| anyhow::anyhow!("Exit gateway missing LP data"))?; + + let entry_address = entry_lp_data.address; + let exit_address = exit_lp_data.address; + + let entry_lp_version = entry_lp_data.lp_version; + let exit_lp_version = exit_lp_data.lp_version; + + info!("Starting LP-based WireGuard probe (entry→exit via forwarding)"); + + let mut wg_outcome = WgProbeResults::default(); + + // Generate Ed25519 keypairs for LP protocol + let mut rng = rand::thread_rng(); + let entry_lp_keypair = Arc::new(ed25519::KeyPair::new(&mut rng)); + let exit_lp_keypair = Arc::new(ed25519::KeyPair::new(&mut rng)); + + // Generate WireGuard keypairs for VPN registration + let entry_wg_keypair = x25519::KeyPair::new(&mut rng); + let exit_wg_keypair = x25519::KeyPair::new(&mut rng); + + let entry_peer = helpers::to_lp_remote_peer(entry_gateway.identity, entry_lp_data); + let exit_peer = helpers::to_lp_remote_peer(exit_gateway.identity, exit_lp_data); + + // STEP 1: Establish outer LP session with entry gateway + // LpRegistrationClient uses packet-per-connection model - connect() is gone, + // connection is established automatically during handshake. + info!("Establishing outer LP session with entry gateway..."); + let mut entry_client = LpRegistrationClient::::new_with_default_config( + entry_lp_keypair, + entry_peer, + entry_address, + entry_lp_version, + ); + + // Perform handshake with entry gateway (connection is implicit) + if let Err(e) = entry_client.perform_handshake().await { + error!("Failed to handshake with entry gateway: {}", e); + return Ok(wg_outcome); + } + info!("Outer LP session with entry gateway established"); + + // STEP 2: Use nested session to register with exit gateway via forwarding + info!("Registering with exit gateway via entry forwarding..."); + let mut nested_session = + NestedLpSession::new(exit_address, exit_lp_keypair, exit_peer, exit_lp_version); + + let exit_gateway_pubkey = exit_gateway.identity; + + // Perform handshake and registration with exit gateway via forwarding + let exit_gateway_data = match nested_session + .handshake_and_register_dvpn( + &mut entry_client, + &mut rng, + &exit_wg_keypair, + &exit_gateway_pubkey, + bandwidth_controller, + TicketType::V1WireguardExit, + ) + .await + { + Ok(data) => data, + Err(e) => { + error!("Failed to register with exit gateway: {}", e); + return Ok(wg_outcome); + } + }; + + info!("Exit gateway registration successful via forwarding"); + + // STEP 3: Register with entry gateway + info!("Registering with entry gateway..."); + let entry_gateway_pubkey = + ed25519::PublicKey::from_bytes(&entry_gateway.identity.to_bytes()) + .map_err(|e| anyhow::anyhow!("Invalid entry gateway identity: {}", e))?; + + // Use packet-per-connection register() which returns GatewayData directly + let entry_gateway_data = match entry_client + .register_dvpn( + &mut rng, + &entry_wg_keypair, + &entry_gateway_pubkey, + bandwidth_controller, + TicketType::V1WireguardEntry, + ) + .await + { + Ok(data) => data, + Err(e) => { + error!("Failed to register with entry gateway: {}", e); + return Ok(wg_outcome); + } + }; + info!("Entry gateway registration successful"); + + info!("LP registration successful for both gateways!"); + wg_outcome.can_register = true; + + // STEP 4: Test WireGuard tunnels using two-hop configuration + // Traffic flows: Exit tunnel -> UDP Forwarder -> Entry tunnel -> Exit Gateway -> Internet + // The exit gateway endpoint is not directly reachable from the host in localnet. + // We must tunnel through the entry gateway using the UDP forwarder pattern. + + // Convert keys to hex for netstack + let entry_private_key_hex = hex::encode(entry_wg_keypair.private_key().to_bytes()); + let entry_public_key_hex = hex::encode(entry_gateway_data.public_key.to_bytes()); + let exit_private_key_hex = hex::encode(exit_wg_keypair.private_key().to_bytes()); + let exit_public_key_hex = hex::encode(exit_gateway_data.public_key.to_bytes()); + + // Build WireGuard endpoint addresses + // Entry endpoint uses entry_ip (host-reachable) + port from registration + let entry_wg_endpoint = entry_gateway_data.endpoint; + // Exit endpoint uses exit_ip + port from registration (forwarded via entry) + let exit_wg_endpoint = exit_gateway_data.endpoint; + + info!("Two-hop WireGuard configuration:"); + info!(" Entry gateway:"); + info!(" Private IPv4: {}", entry_gateway_data.private_ipv4); + info!(" Endpoint: {}", entry_wg_endpoint); + info!(" Exit gateway:"); + info!(" Private IPv4: {}", exit_gateway_data.private_ipv4); + info!(" Endpoint (via forwarder): {}", exit_wg_endpoint); + + // Build two-hop tunnel configuration + let two_hop_config = TwoHopWgTunnelConfig::new( + entry_gateway_data.private_ipv4.to_string(), + entry_private_key_hex, + entry_public_key_hex, + entry_wg_endpoint, + awg_args.clone().unwrap_or_default(), // Entry AWG args + exit_gateway_data.private_ipv4.to_string(), + exit_private_key_hex, + exit_public_key_hex, + exit_wg_endpoint, + awg_args.unwrap_or_default(), // Exit AWG args + ); + + // Run two-hop tunnel connectivity tests + run_two_hop_tunnel_tests(&two_hop_config, &netstack_args, &mut wg_outcome); + + info!("LP-based two-hop WireGuard probe completed"); + Ok(wg_outcome) +} + +pub async fn do_ping( + mut mixnet_client: MixnetClient, + our_address: Recipient, + exit_router_address: Option, + tested_entry: bool, +) -> (anyhow::Result, MixnetClient) { + let entry = do_ping_entry(&mut mixnet_client, our_address, tested_entry).await; + + let (exit_result, mixnet_client) = if let Some(exit_router_address) = exit_router_address { + let (maybe_ip_pair, mut mixnet_client) = + connect_exit(mixnet_client, exit_router_address).await; + match maybe_ip_pair { + Some(ip_pair) => ( + do_ping_exit(&mut mixnet_client, ip_pair, exit_router_address).await, + mixnet_client, + ), + None => (Ok(Some(Exit::fail_to_connect())), mixnet_client), + } + } else { + (Ok(None), mixnet_client) + }; + + ( + exit_result.map(|exit| ProbeOutcome { + as_entry: entry, + as_exit: exit, + socks5: None, + wg: None, + lp: None, + }), + mixnet_client, + ) +} + +async fn do_ping_entry( + mixnet_client: &mut MixnetClient, + our_address: Recipient, + tested_entry: bool, +) -> Entry { + // Step 1: confirm that the entry gateway is routing our mixnet traffic + info!("Sending mixnet ping to ourselves to verify mixnet connection"); + + if self_ping_and_wait(our_address, mixnet_client) + .await + .is_err() + { + return if tested_entry { + Entry::fail_to_connect() + } else { + Entry::EntryFailure + }; + } + info!("Successfully mixnet pinged ourselves"); + + Entry::success() +} + +async fn connect_exit( + mixnet_client: MixnetClient, + exit_router_address: Recipient, +) -> (Option, MixnetClient) { + // Step 2: connect to the exit gateway + info!( + "Connecting to exit gateway: {}", + exit_router_address.gateway().to_base58_string() + ); + // The IPR supports cancellation, but it's unused in the gateway probe + let cancel_token = CancellationToken::new(); + let mut ipr_client = IprClientConnect::new(mixnet_client, cancel_token); + + let maybe_ip_pair = ipr_client.connect(exit_router_address).await; + let mixnet_client = ipr_client.into_mixnet_client(); + + if let Ok(our_ips) = maybe_ip_pair { + info!("Successfully connected to exit gateway"); + info!("Using mixnet VPN IP addresses: {our_ips}"); + (Some(our_ips), mixnet_client) + } else { + (None, mixnet_client) + } +} + +pub async fn do_ping_exit( + mixnet_client: &mut MixnetClient, + our_ips: IpPair, + exit_router_address: Recipient, +) -> anyhow::Result> { + // Step 3: perform ICMP connectivity checks for the exit gateway + send_icmp_pings(mixnet_client, our_ips, exit_router_address).await?; + listen_for_icmp_ping_replies(mixnet_client, our_ips).await +} + +async fn send_icmp_pings( + mixnet_client: &MixnetClient, + our_ips: IpPair, + exit_router_address: Recipient, +) -> anyhow::Result<()> { + // ipv4 addresses for testing + let ipr_tun_ip_v4 = NYM_TUN_DEVICE_ADDRESS_V4; + let external_ip_v4 = Ipv4Addr::new(8, 8, 8, 8); + + // ipv6 addresses for testing + let ipr_tun_ip_v6 = NYM_TUN_DEVICE_ADDRESS_V6; + let external_ip_v6 = Ipv6Addr::new(0x2001, 0x4860, 0x4860, 0, 0, 0, 0, 0x8888); + + info!( + "Sending ICMP echo requests to: {ipr_tun_ip_v4}, {ipr_tun_ip_v6}, {external_ip_v4}, {external_ip_v6}" + ); + + // send ipv4 pings + for ii in 0..10 { + icmp::send_ping_v4( + mixnet_client, + our_ips, + ii, + ipr_tun_ip_v4, + exit_router_address, + ) + .await?; + icmp::send_ping_v4( + mixnet_client, + our_ips, + ii, + external_ip_v4, + exit_router_address, + ) + .await?; + } + + // send ipv6 pings + for ii in 0..10 { + icmp::send_ping_v6( + mixnet_client, + our_ips, + ii, + ipr_tun_ip_v6, + exit_router_address, + ) + .await?; + icmp::send_ping_v6( + mixnet_client, + our_ips, + ii, + external_ip_v6, + exit_router_address, + ) + .await?; + } + Ok(()) +} + +pub async fn listen_for_icmp_ping_replies( + mixnet_client: &mut MixnetClient, + our_ips: IpPair, +) -> anyhow::Result> { + let mut multi_ip_packet_decoder = MultiIpPacketCodec::new(); + let mut registered_replies = IpPingReplies::new(); + + loop { + tokio::select! { + _ = tokio::time::sleep(Duration::from_secs(2)) => { + info!("Finished waiting for ICMP echo reply from exit gateway"); + break; + } + Some(reconstructed_message) = mixnet_client.next() => { + let Some(data_response) = helpers::unpack_data_response(&reconstructed_message) else { + continue; + }; + + // IP packets are bundled together in a mixnet message + let mut bytes = BytesMut::from(&*data_response.ip_packet); + while let Ok(Some(packet)) = multi_ip_packet_decoder.decode(&mut bytes) { + if let Some(event) = icmp::check_for_icmp_beacon_reply(&packet.into_bytes(), icmp::icmp_identifier(), our_ips) { + info!("Received ICMP echo reply from exit gateway"); + info!("Connection event: {event:?}"); + registered_replies.register_event(&event); + } + } + } + } + } + + Ok(Some(Exit { + can_connect: true, + can_route_ip_v4: registered_replies.ipr_tun_ip_v4, + can_route_ip_external_v4: registered_replies.external_ip_v4, + can_route_ip_v6: registered_replies.ipr_tun_ip_v6, + can_route_ip_external_v6: registered_replies.external_ip_v6, + })) +} + +/// Creates a SOCKS5 proxy connection through the mixnet to the exit GW +/// and performs necessary tests. +#[instrument(level = "info", name = "socks5_test", skip_all)] +pub(crate) async fn do_socks5_connectivity_test( + nr_recipient: &Recipient, + entry_gateway_id: NodeIdentity, + network_details: NymNetworkDetails, + min_gw_performance: Option, + socks5_args: Socks5Args, + maybe_topology: Option, +) -> anyhow::Result { + info!( + "Starting SOCKS5 test through Network Requester: {}", + nr_recipient + ); + if socks5_args.socks5_json_rpc_url_list.is_empty() { + bail!("You need to define JSON RPC URLs in order to test SOCKS5") + } + + info!( + "Network Requester gateway: {}", + nr_recipient.gateway().to_base58_string() + ); + info!( + "Network Requester identity: {}", + nr_recipient.identity().to_base58_string() + ); + + // create ephemeral SOCKS5 client + let socks5_config = Socks5::new(nr_recipient.to_string()); + + // debug config similar to main probe + let debug_config = mixnet_debug_config(min_gw_performance, true); + + let mut socks5_client_builder = MixnetClientBuilder::new_ephemeral() + // Specify entry gateway explicitly + .request_gateway(entry_gateway_id.to_base58_string()) + .socks5_config(socks5_config) + .network_details(network_details) + .debug_config(debug_config); + + if let Some(topology) = maybe_topology { + socks5_client_builder = socks5_client_builder + .custom_topology_provider(Box::new(HardcodedTopologyProvider::new(topology))); + } + + let disconnected_socks5_client = socks5_client_builder.build()?; + + // connect to mixnet via SOCKS5 + let socks5_client = match disconnected_socks5_client + .connect_to_mixnet_via_socks5() + .await + { + Ok(client) => { + info!("🌐 Successfully connected to mixnet via SOCKS5 proxy"); + info!( + "Connected via entry gateway: {}", + client.nym_address().gateway().to_base58_string() + ); + client + } + Err(e) => { + error!("Failed to establish SOCKS5 connection: {}", e); + return Ok(Socks5ProbeResults::error_before_connecting(format!( + "SOCKS5 connection failed: {}", + e + ))); + } + }; + + let test = match HttpsConnectivityTest::new( + socks5_args.test_count, + socks5_args.mixnet_client_timeout_sec, + socks5_args.failure_count_cutoff, + socks5_args.socks5_json_rpc_url_list, + socks5_client.socks5_url(), + ) { + Ok(test) => test, + Err(err) => { + socks5_client.disconnect().await; + + error!("{err}"); + return Ok(Socks5ProbeResults::error_after_connecting( + "Failed to create client", + )); + } + }; + + let result = test.run_tests().await; + socks5_client.disconnect().await; + + Ok(Socks5ProbeResults::with_http_result(result)) +} diff --git a/nym-gateway-probe/src/common/socks5_test/json_rpc_client.rs b/nym-gateway-probe/src/common/socks5_test/json_rpc_client.rs new file mode 100644 index 0000000000..e38269ed04 --- /dev/null +++ b/nym-gateway-probe/src/common/socks5_test/json_rpc_client.rs @@ -0,0 +1,255 @@ +use anyhow::bail; +use rand::Rng; +use reqwest::Proxy; +use serde::{Deserialize, Serialize}; +use std::time::Duration; +use tokio::time::Instant; +use tracing::{debug, error, info, warn}; + +use crate::common::socks5_test::SingleHttpsTestResult; + +pub struct JsonRpcClient { + client: reqwest::Client, + client_timeout: Duration, + test_endpoints: Vec, +} + +impl JsonRpcClient { + pub fn new( + client_timeout: u64, + proxy: Option, + test_endpoints: Vec, + ) -> anyhow::Result { + let mut builder = reqwest::Client::builder().timeout(Duration::from_secs(client_timeout)); + + if let Some(proxy) = proxy { + builder = builder.proxy(proxy); + } + let client = builder.build()?; + + Ok(Self { + client_timeout: Duration::from_secs(client_timeout), + test_endpoints, + client, + }) + } + + pub(super) async fn https_request_with_fallbacks(&self) -> SingleHttpsTestResult { + let mut error_msg = String::new(); + + // endpoints are used as fallbacks: in case of success, return early + for endpoint in self.test_endpoints.iter() { + info!( + "Testing against {} with timeout {}s", + endpoint, + self.client_timeout.as_secs() + ); + let start = Instant::now(); + + let res = self.eth_chainid(endpoint).await; + let elapsed = start.elapsed(); + match res { + Ok((status, JsonRpcResponse::Ok { .. })) => { + debug!( + "HTTPS test completed: status={}, latency={}ms", + status.as_u16(), + elapsed.as_millis() + ); + return SingleHttpsTestResult { + success: true, + status_code: Some(status.as_u16()), + latency_ms: Some(elapsed.as_millis() as u64), + endpoint_used: Some(endpoint.to_string()), + error: None, + }; + } + Ok((_, JsonRpcResponse::Err { error, .. })) => { + warn!("JSON-RPC error: {} (code: {})", error.message, error.code); + error_msg = format!("JSON-RPC error: {}", error.message); + } + Err(e) => { + error_msg = e.to_string(); + error!("{}", &error_msg); + } + } + } + + SingleHttpsTestResult { + success: false, + status_code: None, + latency_ms: None, + endpoint_used: self.test_endpoints.last().cloned(), + error: Some(error_msg), + } + } + + async fn eth_chainid( + &self, + endpoint: &str, + ) -> anyhow::Result<(reqwest::StatusCode, JsonRpcResponse)> { + match self + .client + .post(endpoint) + .header(reqwest::header::CONTENT_TYPE, "application/json") + .json(&JsonRpcRequestBody::eth_chainid()) + .send() + .await + .and_then(reqwest::Response::error_for_status) + { + Ok(response) => { + let status = response.status(); + if status.is_success() { + // Deserialize body into JsonRpcResponse + response + .json::() + .await + .map(|res| (status, res)) + .map_err(From::from) + } else { + bail!("HTTP error status: {}", status.as_u16()); + } + } + Err(e) => { + error!("HTTPS request failed: {}", e); + bail!("HTTPS request failed: {}", e); + } + } + } + + pub async fn ensure_endpoint_works(&self) -> anyhow::Result<()> { + let mut any_works = false; + for endpoint in self.test_endpoints.iter() { + if let Err(err) = self.eth_chainid(endpoint).await { + warn!("Endpoint {endpoint} error: {err}"); + } else { + any_works = true; + } + } + + if any_works { + Ok(()) + } else { + bail!("None of the endpoints are valid, see logs"); + } + } +} + +/// https://www.jsonrpc.org/specification +#[derive(Serialize)] +struct JsonRpcRequestBody { + // A String specifying the version of the JSON-RPC protocol. MUST be exactly "2.0". + jsonrpc: String, + method: String, + // A Structured value that holds the parameter values to be used during the invocation of the method. This member MAY be omitted. + params: serde_json::Value, + // The Server MUST reply with the same value in the Response object if included. + // This member is used to correlate the context between the two objects. + id: i64, +} + +impl JsonRpcRequestBody { + /// Very simple endpoint that requires no dynamic input + /// + /// https://ethereum.org/developers/docs/apis/json-rpc/#eth_chainId + pub fn eth_chainid() -> Self { + Self { + jsonrpc: String::from("2.0"), + method: String::from("eth_chainId"), + params: serde_json::json!([]), + id: rand::thread_rng().r#gen(), + } + } + + /// Create an eth_getBlockByNumber request with invalid params for testing error responses + #[cfg(test)] + pub fn eth_get_block_by_number_invalid() -> Self { + Self { + jsonrpc: String::from("2.0"), + method: String::from("eth_getBlockByNumber"), + // Invalid params: should be [blockNumber, boolean] but we pass garbage + params: serde_json::json!(["invalid_block_number"]), + id: rand::thread_rng().r#gen(), + } + } +} + +// dead code: we need these fields for deserialization, even if we don't read them explicitly +#[allow(dead_code)] +#[derive(Deserialize)] +#[serde(untagged)] +enum JsonRpcResponse { + Ok { + jsonrpc: String, + // have to use opaque Value because spec say this might be string, number or null (we don't care either way) + id: serde_json::Value, + // we don't really care for the exact result, just whether the response is OK or error + result: serde_json::Value, + }, + Err { + jsonrpc: String, + // have to use opaque Value because spec say this might be string, number or null (we don't care either way) + id: serde_json::Value, + error: JsonRpcError, + }, +} + +// dead code: we need these fields for deserialization, even if we don't read them explicitly +#[allow(dead_code)] +#[derive(Debug, Deserialize)] +struct JsonRpcError { + pub code: i64, + pub message: String, + #[serde(default)] + pub data: Option, +} + +#[cfg(test)] +mod test { + use super::*; + + const JSON_RPC_ENDPOINT: &str = "https://cloudflare-eth.com"; + + #[tokio::test] + async fn test_eth_chainid_returns_ok_response() { + let client = reqwest::Client::new(); + let response = client + .post(JSON_RPC_ENDPOINT) + .header(reqwest::header::CONTENT_TYPE, "application/json") + .json(&JsonRpcRequestBody::eth_chainid()) + .send() + .await + .expect("Failed to send request"); + + assert!(response.status().is_success()); + + let json_response: JsonRpcResponse = + response.json().await.expect("Failed to parse response"); + + assert!( + matches!(json_response, JsonRpcResponse::Ok { .. }), + "Expected Ok variant for eth_chainId" + ); + } + + #[tokio::test] + async fn test_eth_get_block_by_number_invalid_returns_error_response() { + let client = reqwest::Client::new(); + let response = client + .post(JSON_RPC_ENDPOINT) + .header(reqwest::header::CONTENT_TYPE, "application/json") + .json(&JsonRpcRequestBody::eth_get_block_by_number_invalid()) + .send() + .await + .expect("Failed to send request"); + + assert!(response.status().is_success()); // HTTP 200 but JSON-RPC error + + let json_response: JsonRpcResponse = + response.json().await.expect("Failed to parse response"); + + assert!( + matches!(json_response, JsonRpcResponse::Err { .. }), + "Expected Err variant for invalid params" + ); + } +} diff --git a/nym-gateway-probe/src/common/socks5_test/mod.rs b/nym-gateway-probe/src/common/socks5_test/mod.rs new file mode 100644 index 0000000000..61372f7c71 --- /dev/null +++ b/nym-gateway-probe/src/common/socks5_test/mod.rs @@ -0,0 +1,166 @@ +use serde::{Deserialize, Serialize}; +use tracing::{info, warn}; + +pub(crate) use json_rpc_client::JsonRpcClient; + +mod json_rpc_client; + +pub struct HttpsConnectivityTest { + test_count: u64, + failure_count_cutoff: usize, + client: JsonRpcClient, +} + +impl HttpsConnectivityTest { + pub fn new( + test_count: u64, + mixnet_client_timeout: u64, + failure_count_cutoff: usize, + json_rpc_test_endpoints: Vec, + socks5_proxy_url: String, + ) -> anyhow::Result { + let proxy = reqwest::Proxy::all(socks5_proxy_url) + .map_err(|e| anyhow::anyhow!("Failed to create proxy: {}", e))?; + let client = + JsonRpcClient::new(mixnet_client_timeout, Some(proxy), json_rpc_test_endpoints)?; + let res = Self { + test_count: std::cmp::max(test_count, 1), + failure_count_cutoff, + client, + }; + + Ok(res) + } + + pub async fn run_tests(self) -> HttpsConnectivityResult { + let mut results = Vec::new(); + + for i in 1..=self.test_count { + info!("Running test {}/{}", i, self.test_count); + let interim_res = self.client.https_request_with_fallbacks().await; + + if interim_res.success { + info!( + "{}/{} latency: {}ms", + i, + self.test_count, + interim_res.latency_ms.unwrap_or(0) + ); + } + + results.push(interim_res); + + // early exit + let unsuccessful = results.iter().filter(|r| !r.success).count(); + if unsuccessful > self.failure_count_cutoff { + warn!("Too many failed runs: returning early..."); + break; + } + } + + let final_result = HttpsConnectivityResult::from_results(results); + info!("AVG latency (in ms): {:?}", final_result.https_latency_ms); + final_result + } +} + +/// single HTTPS test attempt +struct SingleHttpsTestResult { + success: bool, + status_code: Option, + latency_ms: Option, + endpoint_used: Option, + error: Option, +} + +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct HttpsConnectivityResult { + /// successfully completed HTTPS request + https_success: bool, + + /// HTTPS status code received + https_status_code: Option, + + /// average HTTPS request latency in milliseconds + https_latency_ms: Option, + + /// among multiple endpoints available, list the one actually used + endpoint_used: Option, + + /// error message(s) (if any) + errors: Option>, +} + +impl HttpsConnectivityResult { + pub fn with_errors(errors: Vec) -> Self { + Self { + https_success: false, + https_status_code: None, + https_latency_ms: None, + endpoint_used: None, + errors: Some(errors), + } + } + + fn from_results(results: Vec) -> Self { + let (successes, errors): (Vec, Vec) = + results.into_iter().partition(|r| r.success); + let errors = errors + .into_iter() + .map(|r| r.error) + .collect::>>() + // partition above guarantees this vec is non-empty + .unwrap_or_default(); + + // use the last successful result for status_code and endpoint + // this works as an empty check as well: if there is no last success, array must be empty hence only errors are present + let Some(last_success) = successes.last() else { + return Self::with_errors(errors); + }; + + // average latency from successful runs + let mut successes_count = 0; + let total_latency: u64 = successes + .iter() + .filter_map(|r| { + successes_count += 1; + r.latency_ms + }) + .sum(); + let avg_latency = total_latency / successes_count as u64; + + Self { + https_success: true, + https_status_code: last_success.status_code, + https_latency_ms: Some(avg_latency), + endpoint_used: last_success.endpoint_used.clone(), + // even in case of success, some errors were possible + errors: if errors.is_empty() { + None + } else { + Some(errors) + }, + } + } + + pub fn https_success(&self) -> bool { + self.https_success + } + + pub fn https_status_code(&self) -> Option<&u16> { + self.https_status_code.as_ref() + } + + pub fn https_latency_ms(&self) -> Option<&u64> { + self.https_latency_ms.as_ref() + } + + pub fn endpoint_used(&self) -> Option<&String> { + self.endpoint_used.as_ref() + } + + pub fn errors(&self) -> Option<&Vec> { + self.errors.as_ref() + } +} diff --git a/nym-gateway-probe/src/types.rs b/nym-gateway-probe/src/common/types.rs similarity index 71% rename from nym-gateway-probe/src/types.rs rename to nym-gateway-probe/src/common/types.rs index ec887d61fb..34edadabfb 100644 --- a/nym-gateway-probe/src/types.rs +++ b/nym-gateway-probe/src/common/types.rs @@ -1,6 +1,8 @@ use nym_connection_monitor::ConnectionStatusEvent; use serde::{Deserialize, Serialize}; +pub use super::socks5_test::HttpsConnectivityResult; + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ProbeResult { pub node: String, @@ -8,14 +10,17 @@ pub struct ProbeResult { pub outcome: ProbeOutcome, } +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ProbeOutcome { pub as_entry: Entry, pub as_exit: Option, + pub socks5: Option, pub wg: Option, pub lp: Option, } +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] #[derive(Debug, Clone, Serialize, Deserialize, Default)] #[serde(rename = "wg")] pub struct WgProbeResults { @@ -45,6 +50,7 @@ pub struct WgProbeResults { pub download_error_v6: String, } +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] #[derive(Debug, Clone, Serialize, Deserialize, Default)] #[serde(rename = "lp")] pub struct LpProbeResults { @@ -54,6 +60,7 @@ pub struct LpProbeResults { pub error: Option, } +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] #[allow(clippy::enum_variant_names)] @@ -95,12 +102,14 @@ impl Entry { } } +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] #[derive(Debug, Clone, Serialize, Deserialize)] pub struct EntryTestResult { pub can_connect: bool, pub can_route: bool, } +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Exit { pub can_connect: bool, @@ -132,6 +141,47 @@ impl Exit { } } +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Socks5ProbeResults { + /// whether we could establish a SOCKS5 proxy connection + can_connect_socks5: bool, + + /// HTTPS connectivity test + https_connectivity: HttpsConnectivityResult, +} + +impl Socks5ProbeResults { + pub fn with_http_result(https_connectivity: HttpsConnectivityResult) -> Self { + Self { + can_connect_socks5: true, + https_connectivity, + } + } + + pub fn error_before_connecting(error: impl Into) -> Self { + Self { + can_connect_socks5: false, + https_connectivity: HttpsConnectivityResult::with_errors(vec![error.into()]), + } + } + + pub fn error_after_connecting(error: impl Into) -> Self { + Self { + can_connect_socks5: true, + https_connectivity: HttpsConnectivityResult::with_errors(vec![error.into()]), + } + } + + pub fn can_connect_socks5(&self) -> bool { + self.can_connect_socks5 + } + + pub fn https_connectivity(&self) -> &HttpsConnectivityResult { + &self.https_connectivity + } +} + #[derive(Debug, Clone, Default)] pub struct IpPingReplies { pub ipr_tun_ip_v4: bool, diff --git a/nym-gateway-probe/src/common/wireguard.rs b/nym-gateway-probe/src/common/wireguard.rs index 3d58d68a53..2ded0a0ae3 100644 --- a/nym-gateway-probe/src/common/wireguard.rs +++ b/nym-gateway-probe/src/common/wireguard.rs @@ -7,13 +7,14 @@ //! that is shared between different test modes (authenticator-based and LP-based). use nym_config::defaults::{WG_METADATA_PORT, WG_TUN_DEVICE_IP_ADDRESS_V4}; +use std::net::SocketAddr; use tracing::{error, info}; use crate::NetstackArgs; -use crate::netstack::{ +use crate::common::netstack::{ NetstackRequest, NetstackRequestGo, NetstackResult, TwoHopNetstackRequestGo, }; -use crate::types::WgProbeResults; +use crate::common::types::WgProbeResults; /// Safe division that returns 0.0 when divisor is 0 (instead of NaN/Inf) fn safe_ratio(received: u16, sent: u16) -> f32 { @@ -99,7 +100,7 @@ pub fn run_tunnel_tests( info!("Testing IPv4 tunnel connectivity..."); let ipv4_request = NetstackRequestGo::from_rust_v4(&netstack_request); - match crate::netstack::ping(&ipv4_request) { + match crate::common::netstack::ping(&ipv4_request) { Ok(NetstackResult::Response(netstack_response_v4)) => { info!( "WireGuard probe response for IPv4: {:#?}", @@ -137,7 +138,7 @@ pub fn run_tunnel_tests( info!("Testing IPv6 tunnel connectivity..."); let ipv6_request = NetstackRequestGo::from_rust_v6(&netstack_request); - match crate::netstack::ping(&ipv6_request) { + match crate::common::netstack::ping(&ipv6_request) { Ok(NetstackResult::Response(netstack_response_v6)) => { info!( "WireGuard probe response for IPv6: {:#?}", @@ -185,7 +186,7 @@ pub struct TwoHopWgTunnelConfig { /// Entry gateway's WireGuard public key (hex encoded) pub entry_public_key_hex: String, /// Entry WireGuard endpoint address (entry_gateway_ip:port) - pub entry_endpoint: String, + pub entry_endpoint: SocketAddr, /// Entry Amnezia WG args (empty for standard WG) pub entry_awg_args: String, @@ -197,7 +198,7 @@ pub struct TwoHopWgTunnelConfig { /// Exit gateway's WireGuard public key (hex encoded) pub exit_public_key_hex: String, /// Exit WireGuard endpoint address (exit_gateway_ip:port, forwarded via entry) - pub exit_endpoint: String, + pub exit_endpoint: SocketAddr, /// Exit Amnezia WG args (empty for standard WG) pub exit_awg_args: String, } @@ -209,24 +210,24 @@ impl TwoHopWgTunnelConfig { entry_private_ipv4: impl Into, entry_private_key_hex: impl Into, entry_public_key_hex: impl Into, - entry_endpoint: impl Into, + entry_endpoint: SocketAddr, entry_awg_args: impl Into, exit_private_ipv4: impl Into, exit_private_key_hex: impl Into, exit_public_key_hex: impl Into, - exit_endpoint: impl Into, + exit_endpoint: SocketAddr, exit_awg_args: impl Into, ) -> Self { Self { entry_private_ipv4: entry_private_ipv4.into(), entry_private_key_hex: entry_private_key_hex.into(), entry_public_key_hex: entry_public_key_hex.into(), - entry_endpoint: entry_endpoint.into(), + entry_endpoint, entry_awg_args: entry_awg_args.into(), exit_private_ipv4: exit_private_ipv4.into(), exit_private_key_hex: exit_private_key_hex.into(), exit_public_key_hex: exit_public_key_hex.into(), - exit_endpoint: exit_endpoint.into(), + exit_endpoint, exit_awg_args: exit_awg_args.into(), } } @@ -256,14 +257,14 @@ pub fn run_two_hop_tunnel_tests( entry_wg_ip: config.entry_private_ipv4.clone(), entry_private_key: config.entry_private_key_hex.clone(), entry_public_key: config.entry_public_key_hex.clone(), - entry_endpoint: config.entry_endpoint.clone(), + entry_endpoint: config.entry_endpoint, entry_awg_args: config.entry_awg_args.clone(), // Exit tunnel config exit_wg_ip: config.exit_private_ipv4.clone(), exit_private_key: config.exit_private_key_hex.clone(), exit_public_key: config.exit_public_key_hex.clone(), - exit_endpoint: config.exit_endpoint.clone(), + exit_endpoint: config.exit_endpoint, exit_awg_args: config.exit_awg_args.clone(), // Test parameters (use IPv4 config) @@ -281,7 +282,7 @@ pub fn run_two_hop_tunnel_tests( info!(" Entry endpoint: {}", config.entry_endpoint); info!(" Exit endpoint (via forwarder): {}", config.exit_endpoint); - match crate::netstack::ping_two_hop(&request) { + match crate::common::netstack::ping_two_hop(&request) { Ok(NetstackResult::Response(response)) => { info!("Two-hop WireGuard probe response (IPv4): {:#?}", response); wg_outcome.can_handshake_v4 = response.can_handshake; diff --git a/nym-gateway-probe/src/config/credentials.rs b/nym-gateway-probe/src/config/credentials.rs new file mode 100644 index 0000000000..71bc92b56f --- /dev/null +++ b/nym-gateway-probe/src/config/credentials.rs @@ -0,0 +1,94 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use clap::{ArgGroup, Args}; +use nym_credentials_interface::TicketType; +use nym_node_status_client::models::AttachedTicketMaterials; +use nym_sdk::mixnet::{ + CredentialStorage, DisconnectedMixnetClient, Ephemeral, MixnetClientStorage, OnDiskPersistent, +}; + +use crate::common::bandwidth_helpers::{acquire_bandwidth, import_bandwidth}; + +#[derive(Debug, Args)] +pub struct CredentialArgs { + /// Serialized credential data + #[arg(long)] + pub ticket_materials: String, + + /// Version of the serialized credential + #[arg(long, default_value_t = 1)] + pub ticket_materials_revision: u8, +} + +impl CredentialArgs { + pub async fn import_credential( + self, + mixnet_client: &DisconnectedMixnetClient, + ) -> anyhow::Result<()> { + let tickets_materials = AttachedTicketMaterials::from_serialised_string( + self.ticket_materials, + self.ticket_materials_revision, + )?; + let bandwidth_import = mixnet_client.begin_bandwidth_import(); + import_bandwidth(bandwidth_import, tickets_materials).await?; + Ok(()) + } +} + +/// Two ways to inject credentials when not running as agent +/// 1. Mnemonic : expected to be used on lower envs +/// - mnemonic +/// 2. Mock ecash : expected to be used for local setups +/// - use_mock_ecash +#[derive(Debug, Args)] +#[command(group( + ArgGroup::new("credential_mode") + .args(["use_mock_ecash","mnemonic"]) + .required(true) + .multiple(false) +))] +pub struct CredentialMode { + /// Use mock ecash credentials for testing (requires gateway with --lp-use-mock-ecash) + #[arg(long, action = clap::ArgAction::SetTrue)] + pub use_mock_ecash: bool, + + /// Mnemonic to get credentials from the blockchain. It needs NYMs. + #[arg(long)] + pub mnemonic: Option, +} + +impl CredentialMode { + pub async fn acquire( + &self, + disconnected_mixnet_client: &DisconnectedMixnetClient, + storage: &OnDiskPersistent, + ) -> anyhow::Result<()> { + // Return immediately as there is nothing to do + if self.use_mock_ecash { + return Ok(()); + } + let ticketbook_count = storage + .credential_store() + .get_ticketbooks_info() + .await? + .len(); + tracing::info!("Credential store contains {} ticketbooks", ticketbook_count); + + if ticketbook_count < 1 { + let mnemonic = self.mnemonic.as_ref().ok_or_else(|| { + anyhow::anyhow!( + "We are not using mock ecash and mnemonic is not set, this should not happen" + ) + })?; + for ticketbook_type in [ + TicketType::V1MixnetEntry, + TicketType::V1WireguardEntry, + TicketType::V1WireguardExit, + ] { + acquire_bandwidth(mnemonic, disconnected_mixnet_client, ticketbook_type).await?; + } + } + Ok(()) + } +} diff --git a/nym-gateway-probe/src/config/mod.rs b/nym-gateway-probe/src/config/mod.rs new file mode 100644 index 0000000000..6ad78c942f --- /dev/null +++ b/nym-gateway-probe/src/config/mod.rs @@ -0,0 +1,49 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use clap::Args; + +mod credentials; +mod netstack; +mod socks5; +mod test_mode; + +pub use credentials::{CredentialArgs, CredentialMode}; +pub use netstack::NetstackArgs; +pub use socks5::Socks5Args; +pub use test_mode::TestMode; + +#[derive(Args, Debug)] +pub struct ProbeConfig { + /// Only choose gateway with that minimum performance + #[arg(long)] + pub min_gateway_mixnet_performance: Option, + + /// Test mode - explicitly specify which tests to run + /// + /// Modes: + /// core. - Traditional mixnet testing (entry/exit pings + WireGuard via authenticator) + /// wg-mix - Wireguard via authenticator + /// wg-lp - Entry LP + Exit LP (nested forwarding) + WireGuard + /// lp-only - LP registration only (no WireGuard) + /// socks5-only - Socks5 network requester test + /// all - Mixnet, wireguard over authenticator and LP registration + /// + #[arg(long, default_value_t = TestMode::default(), verbatim_doc_comment)] + pub test_mode: TestMode, + + #[arg(long, global = true)] + pub ignore_egress_epoch_role: bool, + + /// Arguments to be appended to the wireguard config enabling amnezia-wg configuration + #[arg(long)] + pub amnezia_args: Option, + + /// Arguments to manage netstack downloads + #[command(flatten)] + pub netstack_args: NetstackArgs, + + /// Arguments to configure socks5 probe + #[command(flatten)] + pub socks5_args: Socks5Args, +} diff --git a/nym-gateway-probe/src/config/netstack.rs b/nym-gateway-probe/src/config/netstack.rs new file mode 100644 index 0000000000..1e704bcf55 --- /dev/null +++ b/nym-gateway-probe/src/config/netstack.rs @@ -0,0 +1,209 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use clap::Args; + +#[derive(Args, Clone, Debug)] +pub struct NetstackArgs { + #[arg(long, hide = true, default_value_t = 180)] + pub netstack_download_timeout_sec: u64, + + #[arg(long, hide = true, default_value_t = 30)] + pub metadata_timeout_sec: u64, + + #[arg(long, hide = true, default_value = "1.1.1.1")] + pub netstack_v4_dns: String, + + #[arg(long, hide = true, default_value = "2606:4700:4700::1111")] + pub netstack_v6_dns: String, + + #[arg(long, hide = true, default_value_t = 5)] + pub netstack_num_ping: u8, + + #[arg(long, hide = true, default_value_t = 3)] + pub netstack_send_timeout_sec: u64, + + #[arg(long, hide = true, default_value_t = 3)] + pub netstack_recv_timeout_sec: u64, + + #[arg(long, hide= true, default_values_t = vec!["nym.com".to_string()])] + pub netstack_ping_hosts_v4: Vec, + + #[arg(long, hide= true, default_values_t = vec!["1.1.1.1".to_string()])] + pub netstack_ping_ips_v4: Vec, + + #[arg(long, hide= true, default_values_t = vec!["cloudflare.com".to_string()])] + pub netstack_ping_hosts_v6: Vec, + + #[arg(long, hide= true, default_values_t = vec!["2001:4860:4860::8888".to_string(), "2606:4700:4700::1111".to_string(), "2620:fe::fe".to_string()])] + pub netstack_ping_ips_v6: Vec, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_netstack_args_default_values() { + // Test that the default values are correctly set in the struct definition + // This validates that our changes to the default values are correct + + // Create a default instance to test the values + let args = NetstackArgs { + netstack_download_timeout_sec: 180, + metadata_timeout_sec: 30, + netstack_v4_dns: "1.1.1.1".to_string(), + netstack_v6_dns: "2606:4700:4700::1111".to_string(), + netstack_num_ping: 5, + netstack_send_timeout_sec: 3, + netstack_recv_timeout_sec: 3, + netstack_ping_hosts_v4: vec!["nym.com".to_string()], + netstack_ping_ips_v4: vec!["1.1.1.1".to_string()], + netstack_ping_hosts_v6: vec!["cloudflare.com".to_string()], + netstack_ping_ips_v6: vec![ + "2001:4860:4860::8888".to_string(), + "2606:4700:4700::1111".to_string(), + "2620:fe::fe".to_string(), + ], + }; + + // Test IPv4 defaults + assert_eq!(args.netstack_ping_hosts_v4, vec!["nym.com"]); + assert_eq!(args.netstack_ping_ips_v4, vec!["1.1.1.1"]); + assert_eq!(args.netstack_v4_dns, "1.1.1.1"); + + // Test IPv6 defaults + assert_eq!(args.netstack_ping_hosts_v6, vec!["cloudflare.com"]); + assert_eq!( + args.netstack_ping_ips_v6, + vec![ + "2001:4860:4860::8888", + "2606:4700:4700::1111", + "2620:fe::fe" + ] + ); + assert_eq!(args.netstack_v6_dns, "2606:4700:4700::1111"); + + // Test other defaults + assert_eq!(args.netstack_download_timeout_sec, 180); + assert_eq!(args.netstack_num_ping, 5); + assert_eq!(args.netstack_send_timeout_sec, 3); + assert_eq!(args.netstack_recv_timeout_sec, 3); + } + + #[test] + fn test_netstack_args_custom_construction() { + // Test that we can create instances with custom values + let args = NetstackArgs { + netstack_download_timeout_sec: 300, + metadata_timeout_sec: 30, + netstack_v4_dns: "8.8.8.8".to_string(), + netstack_v6_dns: "2001:4860:4860::8888".to_string(), + netstack_num_ping: 10, + netstack_send_timeout_sec: 5, + netstack_recv_timeout_sec: 5, + netstack_ping_hosts_v4: vec!["example.com".to_string()], + netstack_ping_ips_v4: vec!["8.8.8.8".to_string()], + netstack_ping_hosts_v6: vec!["ipv6.example.com".to_string()], + netstack_ping_ips_v6: vec!["2001:4860:4860::8888".to_string()], + }; + + assert_eq!(args.netstack_ping_hosts_v4, vec!["example.com"]); + assert_eq!(args.netstack_ping_hosts_v6, vec!["ipv6.example.com"]); + assert_eq!(args.netstack_ping_ips_v4, vec!["8.8.8.8"]); + assert_eq!(args.netstack_ping_ips_v6, vec!["2001:4860:4860::8888"]); + assert_eq!(args.netstack_v4_dns, "8.8.8.8"); + assert_eq!(args.netstack_v6_dns, "2001:4860:4860::8888"); + assert_eq!(args.netstack_download_timeout_sec, 300); + assert_eq!(args.netstack_num_ping, 10); + assert_eq!(args.netstack_send_timeout_sec, 5); + assert_eq!(args.netstack_recv_timeout_sec, 5); + } + + #[test] + fn test_netstack_args_multiple_values() { + // Test that multiple hosts and IPs can be stored + let args = NetstackArgs { + netstack_download_timeout_sec: 180, + metadata_timeout_sec: 30, + netstack_v4_dns: "1.1.1.1".to_string(), + netstack_v6_dns: "2606:4700:4700::1111".to_string(), + netstack_num_ping: 5, + netstack_send_timeout_sec: 3, + netstack_recv_timeout_sec: 3, + netstack_ping_hosts_v4: vec!["nym.com".to_string(), "example.com".to_string()], + netstack_ping_ips_v4: vec!["1.1.1.1".to_string(), "8.8.8.8".to_string()], + netstack_ping_hosts_v6: vec![ + "cloudflare.com".to_string(), + "ipv6.example.com".to_string(), + ], + netstack_ping_ips_v6: vec![ + "2001:4860:4860::8888".to_string(), + "2606:4700:4700::1111".to_string(), + ], + }; + + assert_eq!(args.netstack_ping_hosts_v4, vec!["nym.com", "example.com"]); + assert_eq!( + args.netstack_ping_hosts_v6, + vec!["cloudflare.com", "ipv6.example.com"] + ); + assert_eq!(args.netstack_ping_ips_v4, vec!["1.1.1.1", "8.8.8.8"]); + assert_eq!( + args.netstack_ping_ips_v6, + vec!["2001:4860:4860::8888", "2606:4700:4700::1111"] + ); + } + + #[test] + fn test_netstack_args_edge_cases() { + // Test edge cases like zero values and empty vectors + let args = NetstackArgs { + netstack_download_timeout_sec: 0, + metadata_timeout_sec: 30, + netstack_v4_dns: "1.1.1.1".to_string(), + netstack_v6_dns: "2606:4700:4700::1111".to_string(), + netstack_num_ping: 0, + netstack_send_timeout_sec: 0, + netstack_recv_timeout_sec: 0, + netstack_ping_hosts_v4: vec![], + netstack_ping_ips_v4: vec![], + netstack_ping_hosts_v6: vec![], + netstack_ping_ips_v6: vec![], + }; + + assert_eq!(args.netstack_num_ping, 0); + assert_eq!(args.netstack_send_timeout_sec, 0); + assert_eq!(args.netstack_recv_timeout_sec, 0); + assert_eq!(args.netstack_download_timeout_sec, 0); + assert!(args.netstack_ping_hosts_v4.is_empty()); + assert!(args.netstack_ping_ips_v4.is_empty()); + assert!(args.netstack_ping_hosts_v6.is_empty()); + assert!(args.netstack_ping_ips_v6.is_empty()); + } + + #[test] + fn test_netstack_args_domain_validation() { + // Test that our domain choices are reasonable + let args = NetstackArgs { + netstack_download_timeout_sec: 180, + metadata_timeout_sec: 30, + netstack_v4_dns: "1.1.1.1".to_string(), + netstack_v6_dns: "2606:4700:4700::1111".to_string(), + netstack_num_ping: 5, + netstack_send_timeout_sec: 3, + netstack_recv_timeout_sec: 3, + netstack_ping_hosts_v4: vec!["nym.com".to_string()], + netstack_ping_ips_v4: vec!["1.1.1.1".to_string()], + netstack_ping_hosts_v6: vec!["cloudflare.com".to_string()], + netstack_ping_ips_v6: vec!["2001:4860:4860::8888".to_string()], + }; + + assert!(args.netstack_ping_hosts_v4[0].contains("nym")); + + assert!(args.netstack_ping_hosts_v6[0].contains("cloudflare")); + + assert_eq!(args.netstack_v4_dns, "1.1.1.1"); + assert_eq!(args.netstack_v6_dns, "2606:4700:4700::1111"); + } +} diff --git a/nym-gateway-probe/src/config/socks5.rs b/nym-gateway-probe/src/config/socks5.rs new file mode 100644 index 0000000000..3832dd4320 --- /dev/null +++ b/nym-gateway-probe/src/config/socks5.rs @@ -0,0 +1,34 @@ +use clap::Args; + +use crate::common::socks5_test::JsonRpcClient; + +const DEFAULT_RPC_ENDPOINT: &str = "https://cloudflare-eth.com"; + +#[derive(Args, Debug)] +pub struct Socks5Args { + #[arg(long, hide = true, value_delimiter = ';', default_value = DEFAULT_RPC_ENDPOINT)] + pub socks5_json_rpc_url_list: Vec, + + #[arg(long, hide = true, default_value_t = 30)] + pub mixnet_client_timeout_sec: u64, + + #[arg(long, hide = true, default_value_t = 10)] + pub test_count: u64, + + /// stops socks5 test early after this many failed attempts + #[arg(long, hide = true, default_value_t = 3)] + pub failure_count_cutoff: usize, +} + +impl Socks5Args { + pub async fn validate_socks5_endpoints(&self) -> anyhow::Result<()> { + let client = JsonRpcClient::new( + self.mixnet_client_timeout_sec, + None, + self.socks5_json_rpc_url_list.clone(), + )?; + client.ensure_endpoint_works().await?; + + Ok(()) + } +} diff --git a/nym-gateway-probe/src/config/test_mode.rs b/nym-gateway-probe/src/config/test_mode.rs new file mode 100644 index 0000000000..eec509f0e9 --- /dev/null +++ b/nym-gateway-probe/src/config/test_mode.rs @@ -0,0 +1,200 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +//! Test mode definitions for gateway probe. +//! +//! This module defines the different test modes supported by the gateway probe: +//! - Core: Traditional mixnet path testing and Wireguard via authenticator +//! - WgMix: Wireguard via authenticator +//! - WgLp: Entry LP + Exit LP (nested forwarding) + WireGuard +//! - LpOnly: LP registration only, no WireGuard +//! - Socks5Only: Socks5 test +//! - All: Mixnet, wireguard over authenticator and LP registration + +/// Test mode for the gateway probe. +/// +/// Determines which tests are performed and how connections are established. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum TestMode { + /// Mixnet tests + WireGuard via authenticator + #[default] + Core, + /// Wireguard via authenticator + WgMix, + /// Wireguard over LP + WgLp, + /// LP registration only - test handshake and registration + LpOnly, + /// Socks5 test only + Socks5Only, + /// Mixnet tests, Wireguard tests, LP tests, Socks5 test + All, +} + +impl TestMode { + // Wether we need to run mixnet tests + pub fn mixnet_tests(&self) -> bool { + matches!(self, TestMode::Core | TestMode::All) + } + + // Wether we need to run Wiregurd tests + pub fn wireguard_tests(&self) -> bool { + matches!( + self, + TestMode::Core | TestMode::WgMix | TestMode::WgLp | TestMode::All + ) + } + + // Wether we need to run Lp tests + pub fn lp_tests(&self) -> bool { + matches!(self, TestMode::WgLp | TestMode::LpOnly | TestMode::All) + } + + // Wether we need to run socks5 tests + pub fn socks5_tests(&self) -> bool { + matches!(self, TestMode::Socks5Only | TestMode::All) + } + + /// Whether this mode requires a mixnet client + pub fn needs_mixnet(&self) -> bool { + matches!(self, TestMode::Core | TestMode::All | TestMode::WgMix) + } +} + +impl std::fmt::Display for TestMode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + TestMode::Core => write!(f, "core"), + TestMode::WgMix => write!(f, "wg-mix"), + TestMode::WgLp => write!(f, "wg-lp"), + TestMode::LpOnly => write!(f, "lp-only"), + TestMode::Socks5Only => write!(f, "socks5-only"), + TestMode::All => write!(f, "all"), + } + } +} + +impl std::str::FromStr for TestMode { + type Err = String; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "mixnet" | "core" => Ok(TestMode::Core), + "wg-mix" | "wgmix" | "wg_mix" => Ok(TestMode::WgMix), + "wg-lp" | "wglp" | "wg_lp" => Ok(TestMode::WgLp), + "lp-only" | "lponly" | "lp_only" => Ok(TestMode::LpOnly), + "socks5-only" | "socks5only" | "socks5_only" => Ok(TestMode::Socks5Only), + "all" => Ok(TestMode::All), + _ => Err(format!( + "Unknown test mode: '{}'. Valid modes: core, wg-mix, wg-lp, lp-only, socks5-only, all", + s + )), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + // ============ Helper method tests ============ + + #[test] + fn test_needs_mixnet() { + assert!(TestMode::Core.needs_mixnet()); + assert!(TestMode::WgMix.needs_mixnet()); + assert!(!TestMode::WgLp.needs_mixnet()); + assert!(!TestMode::LpOnly.needs_mixnet()); + assert!(!TestMode::Socks5Only.needs_mixnet()); + assert!(TestMode::All.needs_mixnet()); + } + + // ============ Display tests ============ + + #[test] + fn test_display() { + assert_eq!(TestMode::Core.to_string(), "core"); + assert_eq!(TestMode::WgMix.to_string(), "wg-mix"); + assert_eq!(TestMode::WgLp.to_string(), "wg-lp"); + assert_eq!(TestMode::LpOnly.to_string(), "lp-only"); + assert_eq!(TestMode::Socks5Only.to_string(), "socks5-only"); + assert_eq!(TestMode::All.to_string(), "all"); + } + + // ============ FromStr tests ============ + + #[test] + fn test_from_str_canonical() { + assert_eq!("core".parse::().unwrap(), TestMode::Core); + assert_eq!("wg-mix".parse::().unwrap(), TestMode::WgMix); + assert_eq!("wg-lp".parse::().unwrap(), TestMode::WgLp); + assert_eq!("lp-only".parse::().unwrap(), TestMode::LpOnly); + assert_eq!( + "socks5-only".parse::().unwrap(), + TestMode::Socks5Only + ); + assert_eq!("all".parse::().unwrap(), TestMode::All); + } + + #[test] + fn test_from_str_alternate_formats() { + // Default aliases + assert_eq!("mixnet".parse::().unwrap(), TestMode::Core); + + // snake_case + assert_eq!("wg_mix".parse::().unwrap(), TestMode::WgMix); + assert_eq!("wg_lp".parse::().unwrap(), TestMode::WgLp); + assert_eq!("lp_only".parse::().unwrap(), TestMode::LpOnly); + assert_eq!( + "socks5_only".parse::().unwrap(), + TestMode::Socks5Only + ); + + // no separator + assert_eq!("wgmix".parse::().unwrap(), TestMode::WgMix); + assert_eq!("wglp".parse::().unwrap(), TestMode::WgLp); + assert_eq!("lponly".parse::().unwrap(), TestMode::LpOnly); + assert_eq!( + "socks5only".parse::().unwrap(), + TestMode::Socks5Only + ); + } + + #[test] + fn test_from_str_case_insensitive() { + assert_eq!("cOrE".parse::().unwrap(), TestMode::Core); + assert_eq!("WG-MIX".parse::().unwrap(), TestMode::WgMix); + assert_eq!("Wg_Lp".parse::().unwrap(), TestMode::WgLp); + assert_eq!("LpOnly".parse::().unwrap(), TestMode::LpOnly); + assert_eq!( + "soCkS5-oNlY".parse::().unwrap(), + TestMode::Socks5Only + ); + assert_eq!("ALL".parse::().unwrap(), TestMode::All); + } + + #[test] + fn test_from_str_invalid() { + assert!("invalid".parse::().is_err()); + assert!("".parse::().is_err()); + assert!("mix".parse::().is_err()); + } + + // ============ Roundtrip test ============ + + #[test] + fn test_display_fromstr_roundtrip() { + for mode in [ + TestMode::Core, + TestMode::WgMix, + TestMode::WgLp, + TestMode::LpOnly, + TestMode::Socks5Only, + TestMode::All, + ] { + let s = mode.to_string(); + let parsed: TestMode = s.parse().unwrap(); + assert_eq!(mode, parsed); + } + } +} diff --git a/nym-gateway-probe/src/lib.rs b/nym-gateway-probe/src/lib.rs index 53c3e097e2..b7a6a23b19 100644 --- a/nym-gateway-probe/src/lib.rs +++ b/nym-gateway-probe/src/lib.rs @@ -1,1841 +1,449 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use std::{ - net::{IpAddr, Ipv4Addr, Ipv6Addr}, - sync::Arc, - time::Duration, +use crate::common::bandwidth_helpers::build_bandwidth_controller; +use crate::common::helpers; +use crate::common::nodes::TestedNodeDetails; +use crate::common::probe_tests::{ + do_ping, do_socks5_connectivity_test, lp_registration_probe, wg_probe, wg_probe_lp, }; - -use crate::types::Entry; -use anyhow::bail; -use base64::{Engine as _, engine::general_purpose}; -use bytes::BytesMut; -use clap::Args; -use futures::StreamExt; +use crate::common::types::{Entry, LpProbeResults}; +use crate::config::{CredentialArgs, CredentialMode, NetstackArgs, ProbeConfig}; use nym_authenticator_client::{AuthClientMixnetListener, AuthenticatorClient}; -use nym_authenticator_requests::{ - AuthenticatorVersion, client_message::ClientMessage, response::AuthenticatorResponse, v2, v3, - v4, v5, v6, -}; +use nym_bandwidth_controller::BandwidthTicketProvider; use nym_client_core::config::ForgetMe; -use nym_config::defaults::{ - NymNetworkDetails, - mixnet_vpn::{NYM_TUN_DEVICE_ADDRESS_V4, NYM_TUN_DEVICE_ADDRESS_V6}, -}; -use nym_connection_monitor::self_ping_and_wait; -use nym_credentials_interface::{CredentialSpendingData, TicketType}; -use nym_crypto::asymmetric::x25519::KeyPair; -use nym_ip_packet_client::IprClientConnect; -use nym_ip_packet_requests::{ - IpPair, - codec::MultiIpPacketCodec, - v8::response::{ - ControlResponse, DataResponse, InfoLevel, IpPacketResponse, IpPacketResponseData, - }, -}; +use nym_config::defaults::NymNetworkDetails; +use nym_credentials_interface::TicketType; +use nym_crypto::asymmetric::x25519; use nym_sdk::mixnet::{ - CredentialStorage, Ephemeral, KeyStore, MixnetClient, MixnetClientBuilder, MixnetClientStorage, - NodeIdentity, Recipient, ReconstructedMessage, StoragePaths, + Ephemeral, KeyStore, MixnetClient, MixnetClientBuilder, MixnetClientStorage, StoragePaths, }; +use nym_topology::{HardcodedTopologyProvider, NymTopology}; use rand::rngs::OsRng; use std::path::PathBuf; -use tokio::net::TcpStream; -use tokio_util::{codec::Decoder, sync::CancellationToken}; +use std::sync::Arc; +use tokio_util::sync::CancellationToken; use tracing::*; -use types::WgProbeResults; -use url::Url; -use crate::{ - icmp::{check_for_icmp_beacon_reply, icmp_identifier, send_ping_v4, send_ping_v6}, - types::Exit, -}; +pub use crate::common::nodes::{NymApiDirectory, query_gateway_by_ip}; +pub use crate::common::types::{ProbeOutcome, ProbeResult}; -mod bandwidth_helpers; mod common; -mod icmp; -pub mod mode; -mod netstack; -pub mod nodes; -mod types; - -use crate::bandwidth_helpers::{acquire_bandwidth, import_bandwidth}; -use crate::nodes::{DirectoryNode, NymApiDirectory}; -pub use mode::TestMode; -use nym_node_status_client::models::AttachedTicketMaterials; -pub use types::{IpPingReplies, ProbeOutcome, ProbeResult}; - -#[derive(Args, Clone)] -pub struct NetstackArgs { - #[arg(long, default_value_t = 180)] - netstack_download_timeout_sec: u64, - - #[arg(long, default_value_t = 30)] - metadata_timeout_sec: u64, - - #[arg(long, default_value = "1.1.1.1")] - netstack_v4_dns: String, - - #[arg(long, default_value = "2606:4700:4700::1111")] - netstack_v6_dns: String, - - #[arg(long, default_value_t = 5)] - netstack_num_ping: u8, - - #[arg(long, default_value_t = 3)] - netstack_send_timeout_sec: u64, - - #[arg(long, default_value_t = 3)] - netstack_recv_timeout_sec: u64, - - #[arg(long, default_values_t = vec!["nym.com".to_string()])] - netstack_ping_hosts_v4: Vec, - - #[arg(long, default_values_t = vec!["1.1.1.1".to_string()])] - netstack_ping_ips_v4: Vec, - - #[arg(long, default_values_t = vec!["cloudflare.com".to_string()])] - netstack_ping_hosts_v6: Vec, - - #[arg(long, default_values_t = vec!["2001:4860:4860::8888".to_string(), "2606:4700:4700::1111".to_string(), "2620:fe::fe".to_string()])] - netstack_ping_ips_v6: Vec, -} - -#[derive(Args)] -pub struct CredentialArgs { - #[arg(long)] - ticket_materials: Option, - - #[arg(long, default_value_t = 1)] - ticket_materials_revision: u8, -} - -impl CredentialArgs { - fn decode_attached_ticket_materials(&self) -> anyhow::Result { - let ticket_materials = self - .ticket_materials - .as_ref() - .ok_or_else(|| anyhow::anyhow!("ticket_materials is required"))? - .clone(); - - Ok(AttachedTicketMaterials::from_serialised_string( - ticket_materials, - self.ticket_materials_revision, - )?) - } -} - -#[derive(Default, Debug)] -pub enum TestedNode { - #[default] - SameAsEntry, - Custom { - identity: NodeIdentity, - shares_entry: bool, - }, -} - -impl TestedNode { - pub fn is_same_as_entry(&self) -> bool { - matches!( - self, - TestedNode::SameAsEntry - | TestedNode::Custom { - shares_entry: true, - .. - } - ) - } -} - -#[derive(Debug, Clone)] -pub struct TestedNodeDetails { - identity: NodeIdentity, - exit_router_address: Option, - authenticator_address: Option, - authenticator_version: AuthenticatorVersion, - ip_address: Option, - lp_address: Option, -} - -impl TestedNodeDetails { - /// Create from CLI args (localnet mode - no HTTP query needed) - /// Only identity and LP address are required; other fields are None/default. - pub fn from_cli(identity: NodeIdentity, lp_address: std::net::SocketAddr) -> Self { - Self { - identity, - ip_address: Some(lp_address.ip()), - lp_address: Some(lp_address), - // These are None in localnet mode - only needed for mixnet/authenticator - exit_router_address: None, - authenticator_address: None, - authenticator_version: AuthenticatorVersion::UNKNOWN, - } - } - - /// Check if this node has sufficient info for LP testing - pub fn can_test_lp(&self) -> bool { - self.lp_address.is_some() - } - - /// Check if this node has sufficient info for mixnet testing - pub fn can_test_mixnet(&self) -> bool { - self.exit_router_address.is_some() || self.authenticator_address.is_some() - } -} +pub use common::types; +pub mod config; pub struct Probe { - entrypoint: NodeIdentity, - tested_node: TestedNode, - amnezia_args: String, - netstack_args: NetstackArgs, - credentials_args: CredentialArgs, - /// Pre-queried gateway node (used when --gateway-ip is specified) - direct_gateway_node: Option, - /// Pre-queried exit gateway node (used when --exit-gateway-ip is specified for LP forwarding) - exit_gateway_node: Option, - /// Localnet entry gateway info (used when --entry-gateway-identity is specified) - localnet_entry: Option, - /// Localnet exit gateway info (used when --exit-gateway-identity is specified) - localnet_exit: Option, + /// Entry node + entry_node: TestedNodeDetails, + /// Optional exit gateway node. If not provided, entry will be used + exit_node: Option, + + config: ProbeConfig, + + network: NymNetworkDetails, + + topology: Option, } impl Probe { + /// Create a probe with pre-queried gateway nodes pub fn new( - entrypoint: NodeIdentity, - tested_node: TestedNode, - netstack_args: NetstackArgs, - credentials_args: CredentialArgs, + entry_node: TestedNodeDetails, + exit_node: Option, + network: NymNetworkDetails, + config: ProbeConfig, ) -> Self { Self { - entrypoint, - tested_node, - amnezia_args: "".into(), - netstack_args, - credentials_args, - direct_gateway_node: None, - exit_gateway_node: None, - localnet_entry: None, - localnet_exit: None, + entry_node, + exit_node, + network, + config, + topology: None, } } - /// Create a probe with a pre-queried gateway node (for direct IP mode) - pub fn new_with_gateway( - entrypoint: NodeIdentity, - tested_node: TestedNode, - netstack_args: NetstackArgs, - credentials_args: CredentialArgs, - gateway_node: DirectoryNode, - ) -> Self { - Self { - entrypoint, - tested_node, - amnezia_args: "".into(), - netstack_args, - credentials_args, - direct_gateway_node: Some(gateway_node), - exit_gateway_node: None, - localnet_entry: None, - localnet_exit: None, - } - } - - /// Create a probe with both entry and exit gateways pre-queried (for LP forwarding tests) - pub fn new_with_gateways( - entrypoint: NodeIdentity, - tested_node: TestedNode, - netstack_args: NetstackArgs, - credentials_args: CredentialArgs, - entry_gateway_node: DirectoryNode, - exit_gateway_node: DirectoryNode, - ) -> Self { - Self { - entrypoint, - tested_node, - amnezia_args: "".into(), - netstack_args, - credentials_args, - direct_gateway_node: Some(entry_gateway_node), - exit_gateway_node: Some(exit_gateway_node), - localnet_entry: None, - localnet_exit: None, - } - } - - /// Create a probe for localnet mode (no HTTP query needed) - /// Uses identity + LP address directly from CLI args - pub fn new_localnet( - entry: TestedNodeDetails, - exit: Option, - netstack_args: NetstackArgs, - credentials_args: CredentialArgs, - ) -> Self { - let entrypoint = entry.identity; - Self { - entrypoint, - tested_node: TestedNode::SameAsEntry, - amnezia_args: "".into(), - netstack_args, - credentials_args, - direct_gateway_node: None, - exit_gateway_node: None, - localnet_entry: Some(entry), - localnet_exit: exit, - } - } - - pub fn with_amnezia(&mut self, args: &str) -> &Self { - self.amnezia_args = args.to_string(); - self - } - - #[allow(clippy::too_many_arguments)] - pub async fn probe( - self, - directory: Option, - nyxd_url: Url, - ignore_egress_epoch_role: bool, - only_wireguard: bool, - only_lp_registration: bool, - test_lp_wg: bool, - min_mixnet_performance: Option, + /// Run a probe as an NS agent, i.e. a bonded node on a known network + pub async fn probe_run_agent( + mut self, + credential_args: CredentialArgs, ) -> anyhow::Result { - let tickets_materials = self.credentials_args.decode_attached_ticket_materials()?; - - let tested_entry = self.tested_node.is_same_as_entry(); - let (mixnet_entry_gateway_id, node_info) = self.lookup_gateway(&directory).await?; - let storage = Ephemeral::default(); + let mixnet_debug_config = helpers::mixnet_debug_config( + self.config.min_gateway_mixnet_performance, + self.config.ignore_egress_epoch_role, + ); + + // If we need to run at least one mixnet client, prefetch topology + if self.config.test_mode.needs_mixnet() || self.config.test_mode.socks5_tests() { + self.topology = helpers::fetch_topology(&self.network, &mixnet_debug_config) + .await + .inspect_err(|e| warn!("Failed to fetch topology for that run, mixnet clients will have to handle themselves : {e}")).ok(); + } + // Connect to the mixnet via the entry gateway - let disconnected_mixnet_client = MixnetClientBuilder::new_with_storage(storage.clone()) - .request_gateway(mixnet_entry_gateway_id.to_string()) - .network_details(NymNetworkDetails::new_from_env()) - .debug_config(mixnet_debug_config( - min_mixnet_performance, - ignore_egress_epoch_role, - )) + let mut mixnet_client_builder = MixnetClientBuilder::new_with_storage(storage.clone()) + .request_gateway(self.entry_node.identity.to_string()) + .network_details(self.network.clone()) + .debug_config(mixnet_debug_config) .with_forget_me(ForgetMe::new_all()) - .credentials_mode(true) - .build()?; + .credentials_mode(true); - // in normal operation expects the ticket material to be provided as an argument - let bandwidth_import = disconnected_mixnet_client.begin_bandwidth_import(); - import_bandwidth(bandwidth_import, tickets_materials).await?; + if let Some(topology) = &self.topology { + mixnet_client_builder = mixnet_client_builder.custom_topology_provider(Box::new( + HardcodedTopologyProvider::new(topology.clone()), + )); + } - let mixnet_client = Box::pin(disconnected_mixnet_client.connect_to_mixnet()).await; + let disconnected_mixnet_client = mixnet_client_builder.build()?; - // Convert legacy flags to TestMode - let has_exit = self.exit_gateway_node.is_some() || self.localnet_exit.is_some(); - let test_mode = - TestMode::from_flags(only_wireguard, only_lp_registration, test_lp_wg, has_exit); + // Import credential + credential_args + .import_credential(&disconnected_mixnet_client) + .await?; - self.do_probe_test( - Some(mixnet_client), - storage, - mixnet_entry_gateway_id, - node_info, - directory.as_ref(), - nyxd_url, - tested_entry, - test_mode, - only_wireguard, - false, // Not using mock ecash in regular probe mode - ) - .await + let bandwidth_provider = + build_bandwidth_controller(&self.network, storage.credential_store().clone(), false)?; + + // Mixnet client start + let mixnet_client = if self.config.test_mode.needs_mixnet() { + Some(disconnected_mixnet_client.connect_to_mixnet().await) + } else { + // Make sure keys are generated, in case we don't start the mixnet client + let key_store = storage.key_store(); + let mut rng = OsRng; + if key_store.load_keys().await.is_err() { + tracing::log::debug!("Generating new client keys"); + nym_client_core::init::generate_new_client_keys(&mut rng, key_store).await?; + } + None + }; + + self.do_probe_test(mixnet_client, bandwidth_provider).await } - #[allow(clippy::too_many_arguments)] + /// Run a probe on unannounced gateway(s) some tests will not be available pub async fn probe_run_locally( self, config_dir: &PathBuf, - mnemonic: Option<&str>, - directory: Option, - nyxd_url: Url, - ignore_egress_epoch_role: bool, - only_wireguard: bool, - only_lp_registration: bool, - test_lp_wg: bool, - min_mixnet_performance: Option, - use_mock_ecash: bool, + credential: CredentialMode, ) -> anyhow::Result { - // Localnet mode - identity + LP address from CLI, no HTTP query - // This path is used when --entry-gateway-identity is specified - if let Some(entry_info) = &self.localnet_entry { - info!("Using localnet mode with CLI-provided gateway identities"); - - // Initialize storage (needed for credentials) - if !config_dir.exists() { - std::fs::create_dir_all(config_dir)?; - } - let storage_paths = StoragePaths::new_from_dir(config_dir)?; - let storage = storage_paths - .initialise_default_persistent_storage() - .await?; - - // For localnet, use entry as the test node (or exit if provided) - let mixnet_entry_gateway_id = entry_info.identity; - let node_info = if let Some(exit_info) = &self.localnet_exit { - exit_info.clone() - } else { - entry_info.clone() - }; - - // Convert legacy flags to TestMode - let has_exit = self.localnet_exit.is_some(); - let test_mode = - TestMode::from_flags(only_wireguard, only_lp_registration, test_lp_wg, has_exit); - - return self - .do_probe_test( - None, - storage, - mixnet_entry_gateway_id, - node_info, - directory.as_ref(), - nyxd_url, - false, // tested_entry - test_mode, - only_wireguard, - use_mock_ecash, - ) - .await; - } - - // If both gateways are pre-queried via --gateway-ip and --exit-gateway-ip, - // skip mixnet setup entirely - we have all the data we need - if self.direct_gateway_node.is_some() && self.exit_gateway_node.is_some() { - let entry_node = if let Some(entry_node) = self.direct_gateway_node.as_ref() { - entry_node - } else { - return Err(anyhow::anyhow!("Entry gateway node is missing")); - }; - let exit_node = if let Some(exit_node) = self.exit_gateway_node.as_ref() { - exit_node - } else { - return Err(anyhow::anyhow!("Exit gateway node is missing")); - }; - - // Initialize storage (needed for credentials) - if !config_dir.exists() { - std::fs::create_dir_all(config_dir)?; - } - let storage_paths = StoragePaths::new_from_dir(config_dir)?; - let storage = storage_paths - .initialise_default_persistent_storage() - .await?; - - // Get node details from pre-queried nodes - let mixnet_entry_gateway_id = entry_node.identity(); - let node_info = exit_node.to_testable_node()?; - - // Convert legacy flags to TestMode (has_exit = true since we have exit_gateway_node) - let test_mode = - TestMode::from_flags(only_wireguard, only_lp_registration, test_lp_wg, true); - - return self - .do_probe_test( - None, - storage, - mixnet_entry_gateway_id, - node_info, - directory.as_ref(), - nyxd_url, - false, // tested_entry - test_mode, - only_wireguard, - use_mock_ecash, - ) - .await; - } - - // If only testing LP registration, use the dedicated LP-only path - // This skips mixnet setup entirely and allows testing local gateways - if only_lp_registration { - return self - .probe_lp_only(config_dir, directory, nyxd_url, use_mock_ecash) - .await; - } - - let tested_entry = self.tested_node.is_same_as_entry(); - let (mixnet_entry_gateway_id, node_info) = self.lookup_gateway(&directory).await?; - - if config_dir.is_file() { - bail!("provided configuration directory is a file"); - } - - if !config_dir.exists() { - std::fs::create_dir_all(config_dir)?; - } - let storage_paths = StoragePaths::new_from_dir(config_dir)?; let storage = storage_paths .initialise_default_persistent_storage() .await?; - // Connect to the mixnet via the entry gateway, without forget-me flag so that gateway remembers client - // and keeps its bandwidth between probe runs + // We cannot run mixnet tests on unannounced gateway, but we still need one to import credential if not using mock ecash let disconnected_mixnet_client = MixnetClientBuilder::new_with_storage(storage.clone()) - .request_gateway(mixnet_entry_gateway_id.to_string()) - .network_details(NymNetworkDetails::new_from_env()) - .debug_config(mixnet_debug_config( - min_mixnet_performance, - ignore_egress_epoch_role, - )) - .credentials_mode(true) + .credentials_mode(!credential.use_mock_ecash) .build()?; + // Acquire credential if needed + credential + .acquire(&disconnected_mixnet_client, &storage) + .await?; + + let bandwidth_provider = build_bandwidth_controller( + &self.network, + storage.credential_store().clone(), + credential.use_mock_ecash, + )?; + + // Make sure keys are generated let key_store = storage.key_store(); let mut rng = OsRng; - - // WORKAROUND SINCE IT HASN'T MADE IT TO THE MONOREPO: if key_store.load_keys().await.is_err() { tracing::log::debug!("Generating new client keys"); nym_client_core::init::generate_new_client_keys(&mut rng, key_store).await?; } - let ticketbook_count = storage - .credential_store() - .get_ticketbooks_info() - .await? - .len(); - - info!("Credential store contains {} ticketbooks", ticketbook_count); - - // Only acquire real bandwidth if not using mock ecash - if ticketbook_count < 1 && !use_mock_ecash { - let mnemonic = mnemonic.ok_or_else(|| { - anyhow::anyhow!("mnemonic is required when not using mock ecash (--use-mock-ecash)") - })?; - for ticketbook_type in [ - TicketType::V1MixnetEntry, - TicketType::V1WireguardEntry, - TicketType::V1WireguardExit, - ] { - acquire_bandwidth(mnemonic, &disconnected_mixnet_client, ticketbook_type).await?; - } - } else if use_mock_ecash { - info!("Using mock ecash mode - skipping bandwidth acquisition"); - } - - let mixnet_client = Box::pin(disconnected_mixnet_client.connect_to_mixnet()).await; - - // Convert legacy flags to TestMode - let has_exit = self.exit_gateway_node.is_some() || self.localnet_exit.is_some(); - let test_mode = - TestMode::from_flags(only_wireguard, only_lp_registration, test_lp_wg, has_exit); - - self.do_probe_test( - Some(mixnet_client), - storage, - mixnet_entry_gateway_id, - node_info, - directory.as_ref(), - nyxd_url, - tested_entry, - test_mode, - only_wireguard, - use_mock_ecash, - ) - .await + self.do_probe_test(None, bandwidth_provider).await } - /// Probe LP registration only, skipping all mixnet tests - /// This is useful for testing local dev gateways that aren't registered in nym-api - pub async fn probe_lp_only( - self, + pub async fn probe_run( + mut self, config_dir: &PathBuf, - directory: Option, - nyxd_url: Url, - use_mock_ecash: bool, + credential: CredentialMode, ) -> anyhow::Result { - let tested_entry = self.tested_node.is_same_as_entry(); - let (mixnet_entry_gateway_id, node_info) = self.lookup_gateway(&directory).await?; - - if config_dir.is_file() { - bail!("provided configuration directory is a file"); - } - - if !config_dir.exists() { - std::fs::create_dir_all(config_dir)?; - } - let storage_paths = StoragePaths::new_from_dir(config_dir)?; let storage = storage_paths .initialise_default_persistent_storage() .await?; - let key_store = storage.key_store(); - let mut rng = OsRng; + let mixnet_debug_config = helpers::mixnet_debug_config( + self.config.min_gateway_mixnet_performance, + self.config.ignore_egress_epoch_role, + ); - // Generate client keys if they don't exist - if key_store.load_keys().await.is_err() { - tracing::log::debug!("Generating new client keys"); - nym_client_core::init::generate_new_client_keys(&mut rng, key_store).await?; + // If we need to run at least one mixnet client, prefetch topology + if self.config.test_mode.needs_mixnet() || self.config.test_mode.socks5_tests() { + self.topology = helpers::fetch_topology(&self.network, &mixnet_debug_config) + .await + .inspect_err(|e| warn!("Failed to fetch topology for that run, mixnet clients will have to handle themselves : {e}")).ok(); } - // Check if node has LP address - let (lp_address, ip_address) = match (node_info.lp_address, node_info.ip_address) { - (Some(lp_addr), Some(ip_addr)) => (lp_addr, ip_addr), - _ => { - bail!("Gateway does not have LP address configured"); - } - }; + // Connect to the mixnet via the entry gateway, with forget-me flag only for stats so that gateway remembers client + // and keeps its bandwidth between probe runs + let mut mixnet_client_builder = MixnetClientBuilder::new_with_storage(storage.clone()) + .request_gateway(self.entry_node.identity.to_string()) + .network_details(self.network.clone()) + .debug_config(mixnet_debug_config) + .with_forget_me(ForgetMe::new_stats()) + .credentials_mode(!credential.use_mock_ecash); - info!("Testing LP registration for gateway {}", node_info.identity); + if let Some(topology) = &self.topology { + mixnet_client_builder = mixnet_client_builder.custom_topology_provider(Box::new( + HardcodedTopologyProvider::new(topology.clone()), + )); + } - // Create bandwidth controller for credential preparation - let config = nym_validator_client::nyxd::Config::try_from_nym_network_details( - &NymNetworkDetails::new_from_env(), - )?; - let client = nym_validator_client::nyxd::NyxdClient::connect(config, nyxd_url.as_str())?; - let bw_controller = nym_bandwidth_controller::BandwidthController::new( + let disconnected_mixnet_client = mixnet_client_builder.build()?; + + // Acquire credential if needed + credential + .acquire(&disconnected_mixnet_client, &storage) + .await?; + + let bandwidth_provider = build_bandwidth_controller( + &self.network, storage.credential_store().clone(), - client, - ); + credential.use_mock_ecash, + )?; - // Run LP registration probe - let lp_outcome = lp_registration_probe( - node_info.identity, - lp_address, - ip_address, - &bw_controller, - use_mock_ecash, - ) - .await - .unwrap_or_default(); - - // Return result with only LP outcome - Ok(ProbeResult { - node: node_info.identity.to_string(), - used_entry: mixnet_entry_gateway_id.to_string(), - outcome: types::ProbeOutcome { - as_entry: types::Entry::NotTested, - as_exit: if tested_entry { - None - } else { - Some(types::Exit::fail_to_connect()) - }, - wg: None, - lp: Some(lp_outcome), - }, - }) - } - - pub async fn lookup_gateway( - &self, - directory: &Option, - ) -> anyhow::Result<(NodeIdentity, TestedNodeDetails)> { - // If we have a pre-queried gateway node (direct IP mode), use that - if let Some(direct_node) = &self.direct_gateway_node { - info!("Using pre-queried gateway node from direct IP query"); - let node_info = direct_node.to_testable_node()?; - info!("connecting to entry gateway: {}", direct_node.identity()); - debug!( - "authenticator version: {:?}", - node_info.authenticator_version - ); - return Ok((self.entrypoint, node_info)); - } - - // Otherwise, use the directory (original behavior) - let directory = directory - .as_ref() - .ok_or_else(|| anyhow::anyhow!("Directory is required when not using --gateway-ip"))?; - - // Setup the entry gateways - let entry_gateway = directory.entry_gateway(&self.entrypoint)?; - - let node_info: TestedNodeDetails = match self.tested_node { - TestedNode::Custom { - identity: _, - shares_entry: true, - } => { - debug!( - "testing node {} as both entry and exit", - entry_gateway.identity() - ); - entry_gateway.to_testable_node()? + // Mixnet client start + let mixnet_client = if self.config.test_mode.needs_mixnet() { + Some(disconnected_mixnet_client.connect_to_mixnet().await) + } else { + // Make sure keys are generated, in case we don't start the mixnet client + let key_store = storage.key_store(); + let mut rng = OsRng; + if key_store.load_keys().await.is_err() { + tracing::log::debug!("Generating new client keys"); + nym_client_core::init::generate_new_client_keys(&mut rng, key_store).await?; } - TestedNode::Custom { - identity, - shares_entry: false, - } => { - let node = directory.get_nym_node(identity)?; - info!( - "testing node {} (via entry {})", - node.identity(), - entry_gateway.identity() - ); - node.to_testable_node()? - } - TestedNode::SameAsEntry => entry_gateway.to_testable_node()?, + None }; - info!("connecting to entry gateway: {}", entry_gateway.identity()); - debug!( - "authenticator version: {:?}", - node_info.authenticator_version - ); - - Ok((self.entrypoint, node_info)) + self.do_probe_test(mixnet_client, bandwidth_provider).await } #[allow(clippy::too_many_arguments)] - pub async fn do_probe_test( - &self, + pub async fn do_probe_test( + self, mixnet_client: Option>, - storage: T, - mixnet_entry_gateway_id: NodeIdentity, - node_info: TestedNodeDetails, - directory: Option<&NymApiDirectory>, - nyxd_url: Url, - tested_entry: bool, - test_mode: TestMode, - only_wireguard: bool, - use_mock_ecash: bool, - ) -> anyhow::Result - where - T: MixnetClientStorage + Clone + 'static, - ::StorageError: Send + Sync, - { - // test_mode replaces the old only_lp_registration and test_lp_wg flags. - // only_wireguard is kept separate as it controls ping behavior within Mixnet mode. - let mut rng = rand::thread_rng(); - let mixnet_client = match mixnet_client { - Some(Ok(mixnet_client)) => Some(mixnet_client), - Some(Err(err)) => { - error!("Failed to connect to mixnet: {err}"); - return Ok(ProbeResult { - node: node_info.identity.to_string(), - used_entry: mixnet_entry_gateway_id.to_string(), - outcome: ProbeOutcome { - as_entry: if tested_entry { - Entry::fail_to_connect() - } else { - Entry::EntryFailure - }, - as_exit: None, - wg: None, - lp: None, - }, - }); - } - None => None, + bandwith_provider: Box, + ) -> anyhow::Result { + // Setup exit node + let entry_under_test = self.exit_node.is_none(); + let exit_node = self.exit_node.unwrap_or(self.entry_node.clone()); + + let mut probe_result = ProbeResult { + node: self.entry_node.identity.to_string(), + used_entry: exit_node.identity.to_string(), + outcome: ProbeOutcome { + as_entry: Entry::NotTested, + as_exit: None, + wg: None, + lp: None, + socks5: None, + }, }; - // Determine if we should run ping tests: - // - Only in Mixnet mode (LP modes don't use mixnet) - // - And only if not --only-wireguard (which skips pings) - let run_ping_tests = test_mode.needs_mixnet() && !only_wireguard; + let mixnet_client = match mixnet_client { + Some(Ok(mixnet_client)) => { + // We can connect, we don't know about routing yet, but having `false` if we don't test it is weird + probe_result.outcome.as_entry = Entry::success(); + info!( + "Successfully connected to entry gateway: {}", + self.entry_node.identity + ); + info!("Our nym address: {}", *mixnet_client.nym_address()); + Some(mixnet_client) + } + Some(Err(err)) => { + error!("Failed to connect to mixnet: {err}"); + probe_result.outcome.as_entry = if entry_under_test { + Entry::fail_to_connect() + } else { + Entry::EntryFailure + }; + None + } + None => { + // At the moment, this is no-op. But if the initialization changes, we will have the correct value + probe_result.outcome.as_entry = Entry::NotTested; + None + } + }; - let (outcome, mixnet_client) = if let Some(mixnet_client) = mixnet_client { - let nym_address = *mixnet_client.nym_address(); - let entry_gateway = nym_address.gateway().to_base58_string(); - - info!("Successfully connected to entry gateway: {entry_gateway}"); - info!("Our nym address: {nym_address}"); - - // Run ping tests if applicable - let (outcome, mixnet_client) = if run_ping_tests { - do_ping( - mixnet_client, - nym_address, - node_info.exit_router_address, - tested_entry, - ) - .await - } else { - ( - Ok(ProbeOutcome { - as_entry: if tested_entry { - Entry::success() - } else { - Entry::NotTested - }, - as_exit: None, - wg: None, - lp: None, - }), - mixnet_client, - ) - }; - (outcome, Some(mixnet_client)) - } else if test_mode.uses_lp() && test_mode.tests_wireguard() { - // LP modes (SingleHop/TwoHop) don't need mixnet client - // Create default outcome and continue to LP-WG test below - ( - Ok(ProbeOutcome { - as_entry: Entry::NotTested, - as_exit: None, - wg: None, - lp: None, - }), - None, - ) - } else { - // For Mixnet mode, missing mixnet client is a failure - ( - Ok(ProbeOutcome { - as_entry: if tested_entry { + // Mixnet ping tests + // There is some weird gymnastics with the mixnet client, but we need to give and then retrieve ownership + let mixnet_client = if self.config.test_mode.mixnet_tests() { + match mixnet_client { + Some(client) => { + let nym_address = *client.nym_address(); + let (outcome, client) = do_ping( + client, + nym_address, + exit_node.exit_router_address, + entry_under_test, + ) + .await; + match outcome { + Ok(outcome) => { + probe_result.outcome = outcome; + } + Err(e) => { + error!("Mixnet ping tests ended with an error : {e}"); + } + } + Some(client) + } + None => { + error!("Mixnet tests cannot be run without a mixnet client"); + probe_result.outcome.as_entry = if entry_under_test { Entry::fail_to_connect() } else { Entry::EntryFailure - }, - as_exit: None, - wg: None, - lp: None, - }), - None, - ) + }; + None + } + } + } else { + mixnet_client }; - let wg_outcome = if !test_mode.tests_wireguard() { - // LpOnly mode: skip WireGuard test - WgProbeResults::default() - } else if test_mode.uses_lp() { + // Wireguard with Authenticator test + if let Some(mixnet_client) = mixnet_client { + // We have a mixnet_client to disconnect at the end here + if self.config.test_mode.wireguard_tests() { + if let (Some(authenticator), Some(ip_address)) = + (exit_node.authenticator_address, exit_node.ip_address) + { + info!("Testing WireGuard via Mixnet registration"); + // Run wireguard with authenticator + let nym_address = *mixnet_client.nym_address(); + // Start the mixnet listener that the auth clients use to receive messages. + let mixnet_listener_task = + AuthClientMixnetListener::new(mixnet_client, CancellationToken::new()) + .start(); + let mut rng = rand::thread_rng(); + let auth_client = AuthenticatorClient::new( + mixnet_listener_task.subscribe(), + mixnet_listener_task.mixnet_sender(), + nym_address, + authenticator, + exit_node.authenticator_version, + Arc::new(x25519::KeyPair::new(&mut rng)), + ip_address, + ); + + let (wg_ticket_type, credential_provider) = if entry_under_test { + (TicketType::V1WireguardEntry, self.entry_node.identity) + } else { + (TicketType::V1WireguardExit, exit_node.identity) + }; + + let credential = bandwith_provider + .get_ecash_ticket(wg_ticket_type, credential_provider, 1) + .await? + .data; + + let outcome = wg_probe( + auth_client, + ip_address, + exit_node.authenticator_version, + self.config.amnezia_args.clone(), + self.config.netstack_args.clone(), + credential, + ) + .await + .unwrap_or_default(); + + // Add wg results to probe result + probe_result.outcome.wg = Some(outcome); + mixnet_listener_task.stop().await; + } else { + warn!("Not enough information to run WireGuard via mixnet registration tests"); + mixnet_client.disconnect().await; + } + } else { + // We are not running WG tests, we don't need the mixnet client anmore + mixnet_client.disconnect().await; + } + } + + // At this point, any mixnet client MUST be disconnected + + // The current probe includes registration as part of the Wireguard result, which makes that a bit awkward + // If we're supposed to run WireGuard tests and LP tests, and Mixnet registration failed, let's try to do it with lp + // This behavior should change in the future + if probe_result.outcome.wg.is_none() + && self.config.test_mode.wireguard_tests() + && self.config.test_mode.lp_tests() + { // Test WireGuard via LP registration (nested session forwarding) info!("Testing WireGuard via LP registration (no mixnet)"); - // Create bandwidth controller for LP registration - let config = nym_validator_client::nyxd::Config::try_from_nym_network_details( - &NymNetworkDetails::new_from_env(), - )?; - let client = - nym_validator_client::nyxd::NyxdClient::connect(config, nyxd_url.as_str())?; - let bw_controller = nym_bandwidth_controller::BandwidthController::new( - storage.credential_store().clone(), - client, - ); - - // Determine entry and exit gateways - // Three modes for gateway resolution: - // 1. direct_gateway_node/exit_gateway_node - from --gateway-ip (HTTP API query) - // 2. localnet_entry/localnet_exit - from --entry-gateway-identity (CLI-only) - // 3. directory lookup - original behavior for production - let (entry_gateway, exit_gateway) = if let Some(exit_node) = &self.exit_gateway_node { - // Both entry and exit gateways were pre-queried (direct IP mode) - info!("Using pre-queried entry and exit gateways for LP forwarding test"); - let entry_node = self - .direct_gateway_node - .as_ref() - .ok_or_else(|| anyhow::anyhow!("Entry gateway not available"))?; - - let entry_gateway = entry_node.to_testable_node()?; - let exit_gateway = exit_node.to_testable_node()?; - - (entry_gateway, exit_gateway) - } else if let Some(exit_localnet) = &self.localnet_exit { - // Localnet mode: use CLI-provided identities and LP addresses - info!("Using localnet entry and exit gateways for LP forwarding test"); - let entry_localnet = self.localnet_entry.as_ref().ok_or_else(|| { - anyhow::anyhow!("Entry gateway not available in localnet mode") - })?; - - (entry_localnet.clone(), exit_localnet.clone()) - } else { - // Original behavior: query from directory - // The tested node is the exit - let exit_gateway = node_info.clone(); - - let directory = directory - .ok_or_else(|| anyhow::anyhow!("Directory is required for LP-WG test mode"))?; - let entry_gateway_node = directory.entry_gateway(&mixnet_entry_gateway_id)?; - let entry_gateway = entry_gateway_node.to_testable_node()?; - - (entry_gateway, exit_gateway) - }; - - wg_probe_lp( - &entry_gateway, - &exit_gateway, - &bw_controller, - use_mock_ecash, - self.amnezia_args.clone(), - self.netstack_args.clone(), - ) - .await - .unwrap_or_default() - } else if let (Some(authenticator), Some(ip_address)) = - (node_info.authenticator_address, node_info.ip_address) - { - let mixnet_client = if let Some(mixnet_client) = mixnet_client { - mixnet_client - } else { - bail!( - "Mixnet client is required for authenticator WireGuard probe, run in LP mode instead" - ); - }; - - let nym_address = *mixnet_client.nym_address(); - // Start the mixnet listener that the auth clients use to receive messages. - let mixnet_listener_task = - AuthClientMixnetListener::new(mixnet_client, CancellationToken::new()).start(); - - let auth_client = AuthenticatorClient::new( - mixnet_listener_task.subscribe(), - mixnet_listener_task.mixnet_sender(), - nym_address, - authenticator, - node_info.authenticator_version, - Arc::new(KeyPair::new(&mut rng)), - ip_address, - ); - let config = nym_validator_client::nyxd::Config::try_from_nym_network_details( - &NymNetworkDetails::new_from_env(), - )?; - let client = - nym_validator_client::nyxd::NyxdClient::connect(config, nyxd_url.as_str())?; - let bw_controller = nym_bandwidth_controller::BandwidthController::new( - storage.credential_store().clone(), - client, - ); - let (wg_ticket_type, credential_provider) = if tested_entry { - ( - TicketType::V1WireguardEntry, - nym_address.gateway().to_bytes(), - ) - } else { - (TicketType::V1WireguardExit, node_info.identity.to_bytes()) - }; - - let credential = bw_controller - .prepare_ecash_ticket(wg_ticket_type, credential_provider, 1) - .await? - .data; - - let outcome = wg_probe( - auth_client, - ip_address, - node_info.authenticator_version, - self.amnezia_args.clone(), - self.netstack_args.clone(), - credential, + let outcome = wg_probe_lp( + &self.entry_node, + &exit_node, + &bandwith_provider, + self.config.amnezia_args.clone(), + self.config.netstack_args.clone(), ) .await .unwrap_or_default(); - - mixnet_listener_task.stop().await; - - outcome - } else { - WgProbeResults::default() - }; + probe_result.outcome.wg = Some(outcome); + } // Test LP registration if node has LP address - let lp_outcome = if let (Some(lp_address), Some(ip_address)) = - (node_info.lp_address, node_info.ip_address) - { - info!("Node has LP address, testing LP registration..."); + if self.config.test_mode.lp_tests() { + if let Some(lp_data) = self.entry_node.lp_data { + info!("Node has LP data, testing LP registration..."); - // Prepare bandwidth credential for LP registration - let config = nym_validator_client::nyxd::Config::try_from_nym_network_details( - &NymNetworkDetails::new_from_env(), - )?; - let client = - nym_validator_client::nyxd::NyxdClient::connect(config, nyxd_url.as_str())?; - let bw_controller = nym_bandwidth_controller::BandwidthController::new( - storage.credential_store().clone(), - client, - ); + let outcome = + lp_registration_probe(self.entry_node.identity, lp_data, &bandwith_provider) + .await + .unwrap_or_default(); - let outcome = lp_registration_probe( - node_info.identity, - lp_address, - ip_address, - &bw_controller, - use_mock_ecash, - ) - .await - .unwrap_or_default(); + probe_result.outcome.lp = Some(outcome); + } else { + warn!("LP test was requested, but node did not have LP data"); - Some(outcome) - } else { - info!("Node does not have LP address, skipping LP registration test"); - None - }; - - // Disconnect the mixnet client gracefully - outcome.map(|mut outcome| { - outcome.wg = Some(wg_outcome); - outcome.lp = lp_outcome; - ProbeResult { - node: node_info.identity.to_string(), - used_entry: mixnet_entry_gateway_id.to_string(), - outcome, - } - }) - } -} - -async fn wg_probe( - mut auth_client: AuthenticatorClient, - gateway_ip: IpAddr, - auth_version: AuthenticatorVersion, - awg_args: String, - netstack_args: NetstackArgs, - // TODO: update type - credential: CredentialSpendingData, -) -> anyhow::Result { - info!("attempting to use authenticator version {auth_version:?}"); - - let mut rng = rand::thread_rng(); - - // that's a long conversion chain - // (it should be simplified later...) - // nym x25519 -> dalek x25519 -> wireguard wrapper x25519 - let private_key = nym_crypto::asymmetric::encryption::PrivateKey::new(&mut rng); - let public_key = private_key.public_key(); - - let authenticator_pub_key = public_key.inner().into(); - let init_message = match auth_version { - AuthenticatorVersion::V2 => ClientMessage::Initial(Box::new( - v2::registration::InitMessage::new(authenticator_pub_key), - )), - AuthenticatorVersion::V3 => ClientMessage::Initial(Box::new( - v3::registration::InitMessage::new(authenticator_pub_key), - )), - AuthenticatorVersion::V4 => ClientMessage::Initial(Box::new( - v4::registration::InitMessage::new(authenticator_pub_key), - )), - AuthenticatorVersion::V5 => ClientMessage::Initial(Box::new( - v5::registration::InitMessage::new(authenticator_pub_key), - )), - AuthenticatorVersion::V6 => ClientMessage::Initial(Box::new( - v6::registration::InitMessage::new(authenticator_pub_key), - )), - AuthenticatorVersion::V1 | AuthenticatorVersion::UNKNOWN => bail!("unknown version number"), - }; - - let mut wg_outcome = WgProbeResults::default(); - - info!( - "connecting to authenticator: {}...", - auth_client.auth_recipient - ); - let response = auth_client - .send_and_wait_for_response(&init_message) - .await?; - - let registered_data = match response { - AuthenticatorResponse::PendingRegistration(pending_registration_response) => { - // Unwrap since we have already checked that we have the keypair. - debug!("Verifying data"); - pending_registration_response.verify(&private_key)?; - - let credential = credential - .try_into() - .inspect_err(|err| error!("invalid zk-nym data: {err}")) - .ok(); - - let finalized_message = - pending_registration_response.finalise_registration(&private_key, credential); - let client_message = ClientMessage::Final(finalized_message); - - let response = auth_client - .send_and_wait_for_response(&client_message) - .await?; - let AuthenticatorResponse::Registered(registered_response) = response else { - bail!("Unexpected response"); + probe_result.outcome.lp = Some(LpProbeResults { + can_connect: false, + can_handshake: false, + can_register: false, + error: Some("no LP data".into()), + }) }; - registered_response } - AuthenticatorResponse::Registered(registered_response) => registered_response, - _ => bail!("Unexpected response"), - }; - let peer_public = registered_data.pub_key().inner(); - let static_private = x25519_dalek::StaticSecret::from(private_key.to_bytes()); - let public_key_bs64 = general_purpose::STANDARD.encode(peer_public.as_bytes()); - let private_key_hex = hex::encode(static_private.to_bytes()); - let public_key_hex = hex::encode(peer_public.as_bytes()); - - info!("WG connection details"); - info!("Peer public key: {}", public_key_bs64); - info!( - "ips {}(v4) {}(v6), port {}", - registered_data.private_ips().ipv4, - registered_data.private_ips().ipv6, - registered_data.wg_port(), - ); - - let wg_endpoint = format!("{gateway_ip}:{}", registered_data.wg_port()); - - info!("Successfully registered with the gateway"); - - wg_outcome.can_register = true; - - // Run tunnel connectivity tests using shared helper - let tunnel_config = common::WgTunnelConfig::new( - registered_data.private_ips().ipv4.to_string(), - registered_data.private_ips().ipv6.to_string(), - private_key_hex, - public_key_hex, - wg_endpoint, - ); - - common::run_tunnel_tests(&tunnel_config, &netstack_args, &awg_args, &mut wg_outcome); - - Ok(wg_outcome) -} - -async fn lp_registration_probe( - gateway_identity: NodeIdentity, - gateway_lp_address: std::net::SocketAddr, - gateway_ip: IpAddr, - bandwidth_controller: &nym_bandwidth_controller::BandwidthController< - nym_validator_client::nyxd::NyxdClient, - St, - >, - use_mock_ecash: bool, -) -> anyhow::Result -where - St: nym_sdk::mixnet::CredentialStorage + Clone + Send + Sync + 'static, - ::StorageError: Send + Sync, -{ - use nym_crypto::asymmetric::ed25519; - use nym_registration_client::LpRegistrationClient; - - info!( - "Starting LP registration probe for gateway at {}", - gateway_lp_address - ); - - let mut lp_outcome = types::LpProbeResults::default(); - - // Generate Ed25519 keypair for this connection (X25519 will be derived internally by LP) - let mut rng = rand::thread_rng(); - let client_ed25519_keypair = std::sync::Arc::new(ed25519::KeyPair::new(&mut rng)); - - // Create LP registration client (uses Ed25519 keys directly, derives X25519 internally) - let mut client = LpRegistrationClient::::new_with_default_psk( - client_ed25519_keypair, - gateway_identity, - gateway_lp_address, - gateway_ip, - ); - - // Step 1: Perform handshake (connection is implicit in packet-per-connection model) - // LpRegistrationClient uses packet-per-connection model - connect() is gone, - // connection is established during handshake and registration automatically. - info!("Performing LP handshake at {}...", gateway_lp_address); - match client.perform_handshake().await { - Ok(_) => { - info!("LP handshake completed successfully"); - lp_outcome.can_connect = true; // Connection succeeded if handshake succeeded - lp_outcome.can_handshake = true; - } - Err(e) => { - let error_msg = format!("LP handshake failed: {}", e); - error!("{}", error_msg); - lp_outcome.error = Some(error_msg); - return Ok(lp_outcome); - } - } - - // Step 2: Register with gateway (send request + receive response in one call) - info!("Sending LP registration request..."); - - // Generate WireGuard keypair for dVPN registration - let mut rng = rand::thread_rng(); - let wg_keypair = nym_crypto::asymmetric::x25519::KeyPair::new(&mut rng); - - // Convert gateway identity to ed25519 public key - let gateway_ed25519_pubkey = match nym_crypto::asymmetric::ed25519::PublicKey::from_bytes( - &gateway_identity.to_bytes(), - ) { - Ok(key) => key, - Err(e) => { - let error_msg = format!("Failed to convert gateway identity: {}", e); - error!("{}", error_msg); - lp_outcome.error = Some(error_msg); - return Ok(lp_outcome); - } - }; - - // Register using the new packet-per-connection API (returns GatewayData directly) - let ticket_type = TicketType::V1WireguardEntry; - let gateway_data = if use_mock_ecash { - info!("Using mock ecash credential for LP registration"); - let credential = crate::bandwidth_helpers::create_dummy_credential( - &gateway_ed25519_pubkey.to_bytes(), - ticket_type, - ); - - match client - .register_with_credential(&wg_keypair, credential, ticket_type) - .await - { - Ok(data) => data, - Err(e) => { - let error_msg = format!("LP registration failed (mock ecash): {}", e); - error!("{}", error_msg); - lp_outcome.error = Some(error_msg); - return Ok(lp_outcome); - } - } - } else { - info!("Using real bandwidth controller for LP registration"); - match client - .register( - &wg_keypair, - &gateway_ed25519_pubkey, - bandwidth_controller, - ticket_type, - ) - .await - { - Ok(data) => data, - Err(e) => { - let error_msg = format!("LP registration failed: {}", e); - error!("{}", error_msg); - lp_outcome.error = Some(error_msg); - return Ok(lp_outcome); - } - } - }; - - info!("LP registration successful! Received gateway data:"); - info!(" - Gateway public key: {:?}", gateway_data.public_key); - info!(" - Private IPv4: {}", gateway_data.private_ipv4); - info!(" - Private IPv6: {}", gateway_data.private_ipv6); - info!(" - Endpoint: {}", gateway_data.endpoint); - lp_outcome.can_register = true; - - Ok(lp_outcome) -} - -/// LP-based WireGuard probe: Tests LP nested session registration + WireGuard tunnel connectivity -/// -/// This function tests the full VPN flow using LP registration instead of mixnet+authenticator: -/// 1. Connects to entry gateway (outer LP session) -/// 2. Registers with exit gateway via entry forwarding (nested LP session) -/// 3. Receives WireGuard configuration from both gateways -/// 4. Tests WireGuard tunnel connectivity (IPv4/IPv6) -/// -/// This validates that IP hiding works (exit sees entry IP, not client IP) and that the -/// full VPN tunnel operates correctly after LP registration. -/// -// Known issue in localnet mode - After this probe runs, container networking -// to the external internet becomes unstable while internal container-to-container traffic -// continues to work. The two-hop WireGuard tunnel itself succeeds (handshake completes), -// but subsequent DNS/ping tests may timeout. This appears to be related to Apple Container -// Runtime networking quirks combined with our NAT/iptables configuration. Tracked in -// beads issue nym-vbdo. Workaround: restart the localnet containers between probe runs. -async fn wg_probe_lp( - entry_gateway: &TestedNodeDetails, - exit_gateway: &TestedNodeDetails, - bandwidth_controller: &nym_bandwidth_controller::BandwidthController< - nym_validator_client::nyxd::NyxdClient, - St, - >, - use_mock_ecash: bool, - awg_args: String, - netstack_args: NetstackArgs, -) -> anyhow::Result -where - St: nym_sdk::mixnet::CredentialStorage + Clone + Send + Sync + 'static, - ::StorageError: Send + Sync, -{ - use nym_crypto::asymmetric::{ed25519, x25519}; - use nym_registration_client::{LpRegistrationClient, NestedLpSession}; - - info!("Starting LP-based WireGuard probe (entry→exit via forwarding)"); - - let mut wg_outcome = WgProbeResults::default(); - - // Validate that both gateways have required information - let entry_lp_address = entry_gateway - .lp_address - .ok_or_else(|| anyhow::anyhow!("Entry gateway missing LP address"))?; - let exit_lp_address = exit_gateway - .lp_address - .ok_or_else(|| anyhow::anyhow!("Exit gateway missing LP address"))?; - let entry_ip = entry_gateway - .ip_address - .ok_or_else(|| anyhow::anyhow!("Entry gateway missing IP address"))?; - let exit_ip = exit_gateway - .ip_address - .ok_or_else(|| anyhow::anyhow!("Exit gateway missing IP address"))?; - - // Generate Ed25519 keypairs for LP protocol - let mut rng = rand::thread_rng(); - let entry_lp_keypair = Arc::new(ed25519::KeyPair::new(&mut rng)); - let exit_lp_keypair = Arc::new(ed25519::KeyPair::new(&mut rng)); - - // Generate WireGuard keypairs for VPN registration - let entry_wg_keypair = x25519::KeyPair::new(&mut rng); - let exit_wg_keypair = x25519::KeyPair::new(&mut rng); - - // STEP 1: Establish outer LP session with entry gateway - // LpRegistrationClient uses packet-per-connection model - connect() is gone, - // connection is established automatically during handshake. - info!("Establishing outer LP session with entry gateway..."); - let mut entry_client = LpRegistrationClient::::new_with_default_psk( - entry_lp_keypair, - entry_gateway.identity, - entry_lp_address, - entry_ip, - ); - - // Perform handshake with entry gateway (connection is implicit) - if let Err(e) = entry_client.perform_handshake().await { - error!("Failed to handshake with entry gateway: {}", e); - return Ok(wg_outcome); - } - info!("Outer LP session with entry gateway established"); - - // STEP 2: Use nested session to register with exit gateway via forwarding - info!("Registering with exit gateway via entry forwarding..."); - let mut nested_session = NestedLpSession::new( - exit_gateway.identity.to_bytes(), - exit_lp_address.to_string(), - exit_lp_keypair, - ed25519::PublicKey::from_bytes(&exit_gateway.identity.to_bytes()) - .map_err(|e| anyhow::anyhow!("Invalid exit gateway identity: {}", e))?, - ); - - // Convert exit gateway identity to ed25519 public key for registration - let exit_gateway_pubkey = ed25519::PublicKey::from_bytes(&exit_gateway.identity.to_bytes()) - .map_err(|e| anyhow::anyhow!("Invalid exit gateway identity: {}", e))?; - - // Perform handshake and registration with exit gateway via forwarding - let exit_gateway_data = if use_mock_ecash { - info!("Using mock ecash credential for exit gateway registration"); - let credential = crate::bandwidth_helpers::create_dummy_credential( - &exit_gateway_pubkey.to_bytes(), - TicketType::V1WireguardExit, - ); - match nested_session - .handshake_and_register_with_credential( - &mut entry_client, - &exit_wg_keypair, - credential, - TicketType::V1WireguardExit, - exit_ip, - ) - .await - { - Ok(data) => data, - Err(e) => { - error!("Failed to register with exit gateway (mock ecash): {}", e); - return Ok(wg_outcome); - } - } - } else { - match nested_session - .handshake_and_register( - &mut entry_client, - &exit_wg_keypair, - &exit_gateway_pubkey, - bandwidth_controller, - TicketType::V1WireguardExit, - exit_ip, - ) - .await - { - Ok(data) => data, - Err(e) => { - error!("Failed to register with exit gateway: {}", e); - return Ok(wg_outcome); - } - } - }; - info!("Exit gateway registration successful via forwarding"); - - // STEP 3: Register with entry gateway - info!("Registering with entry gateway..."); - let entry_gateway_pubkey = - ed25519::PublicKey::from_bytes(&entry_gateway.identity.to_bytes()) - .map_err(|e| anyhow::anyhow!("Invalid entry gateway identity: {}", e))?; - - // Use packet-per-connection register() which returns GatewayData directly - let entry_gateway_data = if use_mock_ecash { - info!("Using mock ecash credential for entry gateway registration"); - let credential = crate::bandwidth_helpers::create_dummy_credential( - &entry_gateway_pubkey.to_bytes(), - TicketType::V1WireguardEntry, - ); - match entry_client - .register_with_credential(&entry_wg_keypair, credential, TicketType::V1WireguardEntry) - .await - { - Ok(data) => data, - Err(e) => { - error!("Failed to register with entry gateway (mock ecash): {}", e); - return Ok(wg_outcome); - } - } - } else { - match entry_client - .register( - &entry_wg_keypair, - &entry_gateway_pubkey, - bandwidth_controller, - TicketType::V1WireguardEntry, - ) - .await - { - Ok(data) => data, - Err(e) => { - error!("Failed to register with entry gateway: {}", e); - return Ok(wg_outcome); - } - } - }; - info!("Entry gateway registration successful"); - - info!("LP registration successful for both gateways!"); - wg_outcome.can_register = true; - - // STEP 4: Test WireGuard tunnels using two-hop configuration - // Traffic flows: Exit tunnel -> UDP Forwarder -> Entry tunnel -> Exit Gateway -> Internet - // The exit gateway endpoint is not directly reachable from the host in localnet. - // We must tunnel through the entry gateway using the UDP forwarder pattern. - - // Convert keys to hex for netstack - let entry_private_key_hex = hex::encode(entry_wg_keypair.private_key().to_bytes()); - let entry_public_key_hex = hex::encode(entry_gateway_data.public_key.to_bytes()); - let exit_private_key_hex = hex::encode(exit_wg_keypair.private_key().to_bytes()); - let exit_public_key_hex = hex::encode(exit_gateway_data.public_key.to_bytes()); - - // Build WireGuard endpoint addresses - // Entry endpoint uses entry_ip (host-reachable) + port from registration - let entry_wg_endpoint = format!("{}:{}", entry_ip, entry_gateway_data.endpoint.port()); - // Exit endpoint uses exit_ip + port from registration (forwarded via entry) - let exit_wg_endpoint = format!("{}:{}", exit_ip, exit_gateway_data.endpoint.port()); - - info!("Two-hop WireGuard configuration:"); - info!(" Entry gateway:"); - info!(" Private IPv4: {}", entry_gateway_data.private_ipv4); - info!(" Endpoint: {}", entry_wg_endpoint); - info!(" Exit gateway:"); - info!(" Private IPv4: {}", exit_gateway_data.private_ipv4); - info!(" Endpoint (via forwarder): {}", exit_wg_endpoint); - - // Build two-hop tunnel configuration - let two_hop_config = common::TwoHopWgTunnelConfig::new( - entry_gateway_data.private_ipv4.to_string(), - entry_private_key_hex, - entry_public_key_hex, - entry_wg_endpoint, - awg_args.clone(), // Entry AWG args - exit_gateway_data.private_ipv4.to_string(), - exit_private_key_hex, - exit_public_key_hex, - exit_wg_endpoint, - awg_args, // Exit AWG args - ); - - // Run two-hop tunnel connectivity tests - common::run_two_hop_tunnel_tests(&two_hop_config, &netstack_args, &mut wg_outcome); - - info!("LP-based two-hop WireGuard probe completed"); - Ok(wg_outcome) -} - -fn mixnet_debug_config( - min_gateway_performance: Option, - ignore_egress_epoch_role: bool, -) -> nym_client_core::config::DebugConfig { - let mut debug_config = nym_client_core::config::DebugConfig::default(); - debug_config - .traffic - .disable_main_poisson_packet_distribution = true; - debug_config.cover_traffic.disable_loop_cover_traffic_stream = true; - if let Some(minimum_gateway_performance) = min_gateway_performance { - debug_config.topology.minimum_gateway_performance = minimum_gateway_performance; - } - if ignore_egress_epoch_role { - debug_config.topology.ignore_egress_epoch_role = ignore_egress_epoch_role; - } - - debug_config -} - -async fn do_ping( - mut mixnet_client: MixnetClient, - our_address: Recipient, - exit_router_address: Option, - tested_entry: bool, -) -> (anyhow::Result, MixnetClient) { - let entry = do_ping_entry(&mut mixnet_client, our_address, tested_entry).await; - - let (exit_result, mixnet_client) = if let Some(exit_router_address) = exit_router_address { - let (maybe_ip_pair, mut mixnet_client) = - connect_exit(mixnet_client, exit_router_address).await; - match maybe_ip_pair { - Some(ip_pair) => ( - do_ping_exit(&mut mixnet_client, ip_pair, exit_router_address).await, - mixnet_client, - ), - None => (Ok(Some(Exit::fail_to_connect())), mixnet_client), - } - } else { - (Ok(None), mixnet_client) - }; - - ( - exit_result.map(|exit| ProbeOutcome { - as_entry: entry, - as_exit: exit, - wg: None, - lp: None, - }), - mixnet_client, - ) -} - -async fn do_ping_entry( - mixnet_client: &mut MixnetClient, - our_address: Recipient, - tested_entry: bool, -) -> Entry { - // Step 1: confirm that the entry gateway is routing our mixnet traffic - info!("Sending mixnet ping to ourselves to verify mixnet connection"); - - if self_ping_and_wait(our_address, mixnet_client) - .await - .is_err() - { - return if tested_entry { - Entry::fail_to_connect() - } else { - Entry::EntryFailure - }; - } - info!("Successfully mixnet pinged ourselves"); - - Entry::success() -} - -async fn connect_exit( - mixnet_client: MixnetClient, - exit_router_address: Recipient, -) -> (Option, MixnetClient) { - // Step 2: connect to the exit gateway - info!( - "Connecting to exit gateway: {}", - exit_router_address.gateway().to_base58_string() - ); - // The IPR supports cancellation, but it's unused in the gateway probe - let cancel_token = CancellationToken::new(); - let mut ipr_client = IprClientConnect::new(mixnet_client, cancel_token); - - let maybe_ip_pair = ipr_client.connect(exit_router_address).await; - let mixnet_client = ipr_client.into_mixnet_client(); - - if let Ok(our_ips) = maybe_ip_pair { - info!("Successfully connected to exit gateway"); - info!("Using mixnet VPN IP addresses: {our_ips}"); - (Some(our_ips), mixnet_client) - } else { - (None, mixnet_client) - } -} - -async fn do_ping_exit( - mixnet_client: &mut MixnetClient, - our_ips: IpPair, - exit_router_address: Recipient, -) -> anyhow::Result> { - // Step 3: perform ICMP connectivity checks for the exit gateway - send_icmp_pings(mixnet_client, our_ips, exit_router_address).await?; - listen_for_icmp_ping_replies(mixnet_client, our_ips).await -} - -async fn send_icmp_pings( - mixnet_client: &MixnetClient, - our_ips: IpPair, - exit_router_address: Recipient, -) -> anyhow::Result<()> { - // ipv4 addresses for testing - let ipr_tun_ip_v4 = NYM_TUN_DEVICE_ADDRESS_V4; - let external_ip_v4 = Ipv4Addr::new(8, 8, 8, 8); - - // ipv6 addresses for testing - let ipr_tun_ip_v6 = NYM_TUN_DEVICE_ADDRESS_V6; - let external_ip_v6 = Ipv6Addr::new(0x2001, 0x4860, 0x4860, 0, 0, 0, 0, 0x8888); - - info!( - "Sending ICMP echo requests to: {ipr_tun_ip_v4}, {ipr_tun_ip_v6}, {external_ip_v4}, {external_ip_v6}" - ); - - // send ipv4 pings - for ii in 0..10 { - send_ping_v4( - mixnet_client, - our_ips, - ii, - ipr_tun_ip_v4, - exit_router_address, - ) - .await?; - send_ping_v4( - mixnet_client, - our_ips, - ii, - external_ip_v4, - exit_router_address, - ) - .await?; - } - - // send ipv6 pings - for ii in 0..10 { - send_ping_v6( - mixnet_client, - our_ips, - ii, - ipr_tun_ip_v6, - exit_router_address, - ) - .await?; - send_ping_v6( - mixnet_client, - our_ips, - ii, - external_ip_v6, - exit_router_address, - ) - .await?; - } - Ok(()) -} - -async fn listen_for_icmp_ping_replies( - mixnet_client: &mut MixnetClient, - our_ips: IpPair, -) -> anyhow::Result> { - let mut multi_ip_packet_decoder = MultiIpPacketCodec::new(); - let mut registered_replies = IpPingReplies::new(); - - loop { - tokio::select! { - _ = tokio::time::sleep(Duration::from_secs(2)) => { - info!("Finished waiting for ICMP echo reply from exit gateway"); - break; - } - Some(reconstructed_message) = mixnet_client.next() => { - let Some(data_response) = unpack_data_response(&reconstructed_message) else { - continue; - }; - - // IP packets are bundled together in a mixnet message - let mut bytes = BytesMut::from(&*data_response.ip_packet); - while let Ok(Some(packet)) = multi_ip_packet_decoder.decode(&mut bytes) { - if let Some(event) = check_for_icmp_beacon_reply(&packet.into_bytes(), icmp_identifier(), our_ips) { - info!("Received ICMP echo reply from exit gateway"); - info!("Connection event: {event:?}"); - registered_replies.register_event(&event); + // Test socks5 connectivity + if self.config.test_mode.socks5_tests() { + // test failure doesn't stop further tests + if let Some(network_requester) = exit_node.network_requester_address { + match do_socks5_connectivity_test( + &network_requester, + self.entry_node.identity, + self.network.clone(), + self.config.min_gateway_mixnet_performance, + self.config.socks5_args, + self.topology, + ) + .await + { + Ok(results) => probe_result.outcome.socks5 = Some(results), + Err(e) => { + error!("SOCKS5 test failed: {}", e); } } + } else { + warn!("No NR available, skipping SOCKS5 tests"); } } - } - Ok(Some(Exit { - can_connect: true, - can_route_ip_v4: registered_replies.ipr_tun_ip_v4, - can_route_ip_external_v4: registered_replies.external_ip_v4, - can_route_ip_v6: registered_replies.ipr_tun_ip_v6, - can_route_ip_external_v6: registered_replies.external_ip_v6, - })) -} - -fn unpack_data_response(reconstructed_message: &ReconstructedMessage) -> Option { - match IpPacketResponse::from_reconstructed_message(reconstructed_message) { - Ok(response) => match response.data { - IpPacketResponseData::Data(data_response) => Some(data_response), - IpPacketResponseData::Control(control) => match *control { - ControlResponse::Info(info) => { - let msg = format!("Received info response from the mixnet: {}", info.reply); - match info.level { - InfoLevel::Info => info!("{msg}"), - InfoLevel::Warn => warn!("{msg}"), - InfoLevel::Error => error!("{msg}"), - } - None - } - _ => { - info!("Ignoring: {:?}", control); - None - } - }, - }, - Err(err) => { - warn!("Failed to parse mixnet message: {err}"); - None - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_netstack_args_default_values() { - // Test that the default values are correctly set in the struct definition - // This validates that our changes to the default values are correct - - // Create a default instance to test the values - let args = NetstackArgs { - netstack_download_timeout_sec: 180, - metadata_timeout_sec: 30, - netstack_v4_dns: "1.1.1.1".to_string(), - netstack_v6_dns: "2606:4700:4700::1111".to_string(), - netstack_num_ping: 5, - netstack_send_timeout_sec: 3, - netstack_recv_timeout_sec: 3, - netstack_ping_hosts_v4: vec!["nym.com".to_string()], - netstack_ping_ips_v4: vec!["1.1.1.1".to_string()], - netstack_ping_hosts_v6: vec!["cloudflare.com".to_string()], - netstack_ping_ips_v6: vec![ - "2001:4860:4860::8888".to_string(), - "2606:4700:4700::1111".to_string(), - "2620:fe::fe".to_string(), - ], - }; - - // Test IPv4 defaults - assert_eq!(args.netstack_ping_hosts_v4, vec!["nym.com"]); - assert_eq!(args.netstack_ping_ips_v4, vec!["1.1.1.1"]); - assert_eq!(args.netstack_v4_dns, "1.1.1.1"); - - // Test IPv6 defaults - assert_eq!(args.netstack_ping_hosts_v6, vec!["cloudflare.com"]); - assert_eq!( - args.netstack_ping_ips_v6, - vec![ - "2001:4860:4860::8888", - "2606:4700:4700::1111", - "2620:fe::fe" - ] - ); - assert_eq!(args.netstack_v6_dns, "2606:4700:4700::1111"); - - // Test other defaults - assert_eq!(args.netstack_download_timeout_sec, 180); - assert_eq!(args.netstack_num_ping, 5); - assert_eq!(args.netstack_send_timeout_sec, 3); - assert_eq!(args.netstack_recv_timeout_sec, 3); - } - - #[test] - fn test_netstack_args_custom_construction() { - // Test that we can create instances with custom values - let args = NetstackArgs { - netstack_download_timeout_sec: 300, - metadata_timeout_sec: 30, - netstack_v4_dns: "8.8.8.8".to_string(), - netstack_v6_dns: "2001:4860:4860::8888".to_string(), - netstack_num_ping: 10, - netstack_send_timeout_sec: 5, - netstack_recv_timeout_sec: 5, - netstack_ping_hosts_v4: vec!["example.com".to_string()], - netstack_ping_ips_v4: vec!["8.8.8.8".to_string()], - netstack_ping_hosts_v6: vec!["ipv6.example.com".to_string()], - netstack_ping_ips_v6: vec!["2001:4860:4860::8888".to_string()], - }; - - assert_eq!(args.netstack_ping_hosts_v4, vec!["example.com"]); - assert_eq!(args.netstack_ping_hosts_v6, vec!["ipv6.example.com"]); - assert_eq!(args.netstack_ping_ips_v4, vec!["8.8.8.8"]); - assert_eq!(args.netstack_ping_ips_v6, vec!["2001:4860:4860::8888"]); - assert_eq!(args.netstack_v4_dns, "8.8.8.8"); - assert_eq!(args.netstack_v6_dns, "2001:4860:4860::8888"); - assert_eq!(args.netstack_download_timeout_sec, 300); - assert_eq!(args.netstack_num_ping, 10); - assert_eq!(args.netstack_send_timeout_sec, 5); - assert_eq!(args.netstack_recv_timeout_sec, 5); - } - - #[test] - fn test_netstack_args_multiple_values() { - // Test that multiple hosts and IPs can be stored - let args = NetstackArgs { - netstack_download_timeout_sec: 180, - metadata_timeout_sec: 30, - netstack_v4_dns: "1.1.1.1".to_string(), - netstack_v6_dns: "2606:4700:4700::1111".to_string(), - netstack_num_ping: 5, - netstack_send_timeout_sec: 3, - netstack_recv_timeout_sec: 3, - netstack_ping_hosts_v4: vec!["nym.com".to_string(), "example.com".to_string()], - netstack_ping_ips_v4: vec!["1.1.1.1".to_string(), "8.8.8.8".to_string()], - netstack_ping_hosts_v6: vec![ - "cloudflare.com".to_string(), - "ipv6.example.com".to_string(), - ], - netstack_ping_ips_v6: vec![ - "2001:4860:4860::8888".to_string(), - "2606:4700:4700::1111".to_string(), - ], - }; - - assert_eq!(args.netstack_ping_hosts_v4, vec!["nym.com", "example.com"]); - assert_eq!( - args.netstack_ping_hosts_v6, - vec!["cloudflare.com", "ipv6.example.com"] - ); - assert_eq!(args.netstack_ping_ips_v4, vec!["1.1.1.1", "8.8.8.8"]); - assert_eq!( - args.netstack_ping_ips_v6, - vec!["2001:4860:4860::8888", "2606:4700:4700::1111"] - ); - } - - #[test] - fn test_netstack_args_edge_cases() { - // Test edge cases like zero values and empty vectors - let args = NetstackArgs { - netstack_download_timeout_sec: 0, - metadata_timeout_sec: 30, - netstack_v4_dns: "1.1.1.1".to_string(), - netstack_v6_dns: "2606:4700:4700::1111".to_string(), - netstack_num_ping: 0, - netstack_send_timeout_sec: 0, - netstack_recv_timeout_sec: 0, - netstack_ping_hosts_v4: vec![], - netstack_ping_ips_v4: vec![], - netstack_ping_hosts_v6: vec![], - netstack_ping_ips_v6: vec![], - }; - - assert_eq!(args.netstack_num_ping, 0); - assert_eq!(args.netstack_send_timeout_sec, 0); - assert_eq!(args.netstack_recv_timeout_sec, 0); - assert_eq!(args.netstack_download_timeout_sec, 0); - assert!(args.netstack_ping_hosts_v4.is_empty()); - assert!(args.netstack_ping_ips_v4.is_empty()); - assert!(args.netstack_ping_hosts_v6.is_empty()); - assert!(args.netstack_ping_ips_v6.is_empty()); - } - - #[test] - fn test_netstack_args_domain_validation() { - // Test that our domain choices are reasonable - let args = NetstackArgs { - netstack_download_timeout_sec: 180, - metadata_timeout_sec: 30, - netstack_v4_dns: "1.1.1.1".to_string(), - netstack_v6_dns: "2606:4700:4700::1111".to_string(), - netstack_num_ping: 5, - netstack_send_timeout_sec: 3, - netstack_recv_timeout_sec: 3, - netstack_ping_hosts_v4: vec!["nym.com".to_string()], - netstack_ping_ips_v4: vec!["1.1.1.1".to_string()], - netstack_ping_hosts_v6: vec!["cloudflare.com".to_string()], - netstack_ping_ips_v6: vec!["2001:4860:4860::8888".to_string()], - }; - - assert!(args.netstack_ping_hosts_v4[0].contains("nym")); - - assert!(args.netstack_ping_hosts_v6[0].contains("cloudflare")); - - assert_eq!(args.netstack_v4_dns, "1.1.1.1"); - assert_eq!(args.netstack_v6_dns, "2606:4700:4700::1111"); + Ok(probe_result) } } diff --git a/nym-gateway-probe/src/mode/mod.rs b/nym-gateway-probe/src/mode/mod.rs deleted file mode 100644 index 5c6e45450d..0000000000 --- a/nym-gateway-probe/src/mode/mod.rs +++ /dev/null @@ -1,283 +0,0 @@ -// Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only - -//! Test mode definitions for gateway probe. -//! -//! This module defines the different test modes supported by the gateway probe: -//! - Mixnet: Traditional mixnet path testing -//! - SingleHop: LP registration + WireGuard on single gateway -//! - TwoHop: Entry LP + Exit LP (nested forwarding) + WireGuard -//! - LpOnly: LP registration only, no WireGuard - -/// Test mode for the gateway probe. -/// -/// Determines which tests are performed and how connections are established. -// This enum replaces the scattered boolean flags (only_wireguard, -// only_lp_registration, test_lp_wg) with explicit, named modes for clarity. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub enum TestMode { - /// Traditional mixnet testing - connects via mixnet, tests entry/exit pings + WireGuard via authenticator - #[default] - Mixnet, - /// LP registration + WireGuard on single gateway (no mixnet, no forwarding) - SingleHop, - /// Entry LP + Exit LP (nested session forwarding) + WireGuard tunnel - TwoHop, - /// LP registration only - test handshake and registration, skip WireGuard - LpOnly, -} - -impl TestMode { - /// Infer test mode from legacy boolean flags (backward compatibility) - pub fn from_flags( - only_wireguard: bool, - only_lp_registration: bool, - test_lp_wg: bool, - has_exit_gateway: bool, - ) -> Self { - if only_lp_registration { - TestMode::LpOnly - } else if test_lp_wg { - if has_exit_gateway { - TestMode::TwoHop - } else { - TestMode::SingleHop - } - } else if only_wireguard { - // WireGuard via authenticator (still uses mixnet path) - TestMode::Mixnet - } else { - TestMode::Mixnet - } - } - - /// Whether this mode requires a mixnet client - pub fn needs_mixnet(&self) -> bool { - matches!(self, TestMode::Mixnet) - } - - /// Whether this mode uses LP registration - pub fn uses_lp(&self) -> bool { - matches!( - self, - TestMode::SingleHop | TestMode::TwoHop | TestMode::LpOnly - ) - } - - /// Whether this mode tests WireGuard tunnels - pub fn tests_wireguard(&self) -> bool { - matches!( - self, - TestMode::Mixnet | TestMode::SingleHop | TestMode::TwoHop - ) - } - - /// Whether this mode requires an exit gateway - pub fn needs_exit_gateway(&self) -> bool { - matches!(self, TestMode::TwoHop) - } -} - -impl std::fmt::Display for TestMode { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - TestMode::Mixnet => write!(f, "mixnet"), - TestMode::SingleHop => write!(f, "single-hop"), - TestMode::TwoHop => write!(f, "two-hop"), - TestMode::LpOnly => write!(f, "lp-only"), - } - } -} - -impl std::str::FromStr for TestMode { - type Err = String; - - fn from_str(s: &str) -> Result { - match s.to_lowercase().as_str() { - "mixnet" => Ok(TestMode::Mixnet), - "single-hop" | "singlehop" | "single_hop" => Ok(TestMode::SingleHop), - "two-hop" | "twohop" | "two_hop" => Ok(TestMode::TwoHop), - "lp-only" | "lponly" | "lp_only" => Ok(TestMode::LpOnly), - _ => Err(format!( - "Unknown test mode: '{}'. Valid modes: mixnet, single-hop, two-hop, lp-only", - s - )), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - // ============ from_flags() tests ============ - - #[test] - fn test_from_flags_default_is_mixnet() { - // All flags false -> Mixnet (default) - assert_eq!( - TestMode::from_flags(false, false, false, false), - TestMode::Mixnet - ); - } - - #[test] - fn test_from_flags_only_wireguard_is_mixnet() { - // only_wireguard still uses mixnet path (WG via authenticator) - assert_eq!( - TestMode::from_flags(true, false, false, false), - TestMode::Mixnet - ); - } - - #[test] - fn test_from_flags_only_lp_registration() { - // only_lp_registration -> LpOnly (takes priority) - assert_eq!( - TestMode::from_flags(false, true, false, false), - TestMode::LpOnly - ); - // Even with other flags set, only_lp_registration wins - assert_eq!( - TestMode::from_flags(true, true, true, true), - TestMode::LpOnly - ); - } - - #[test] - fn test_from_flags_test_lp_wg_single_hop() { - // test_lp_wg without exit gateway -> SingleHop - assert_eq!( - TestMode::from_flags(false, false, true, false), - TestMode::SingleHop - ); - } - - #[test] - fn test_from_flags_test_lp_wg_two_hop() { - // test_lp_wg with exit gateway -> TwoHop - assert_eq!( - TestMode::from_flags(false, false, true, true), - TestMode::TwoHop - ); - } - - #[test] - fn test_from_flags_has_exit_gateway_alone_is_mixnet() { - // has_exit_gateway alone doesn't change mode - assert_eq!( - TestMode::from_flags(false, false, false, true), - TestMode::Mixnet - ); - } - - // ============ Helper method tests ============ - - #[test] - fn test_needs_mixnet() { - assert!(TestMode::Mixnet.needs_mixnet()); - assert!(!TestMode::SingleHop.needs_mixnet()); - assert!(!TestMode::TwoHop.needs_mixnet()); - assert!(!TestMode::LpOnly.needs_mixnet()); - } - - #[test] - fn test_uses_lp() { - assert!(!TestMode::Mixnet.uses_lp()); - assert!(TestMode::SingleHop.uses_lp()); - assert!(TestMode::TwoHop.uses_lp()); - assert!(TestMode::LpOnly.uses_lp()); - } - - #[test] - fn test_tests_wireguard() { - assert!(TestMode::Mixnet.tests_wireguard()); - assert!(TestMode::SingleHop.tests_wireguard()); - assert!(TestMode::TwoHop.tests_wireguard()); - assert!(!TestMode::LpOnly.tests_wireguard()); - } - - #[test] - fn test_needs_exit_gateway() { - assert!(!TestMode::Mixnet.needs_exit_gateway()); - assert!(!TestMode::SingleHop.needs_exit_gateway()); - assert!(TestMode::TwoHop.needs_exit_gateway()); - assert!(!TestMode::LpOnly.needs_exit_gateway()); - } - - // ============ Display tests ============ - - #[test] - fn test_display() { - assert_eq!(TestMode::Mixnet.to_string(), "mixnet"); - assert_eq!(TestMode::SingleHop.to_string(), "single-hop"); - assert_eq!(TestMode::TwoHop.to_string(), "two-hop"); - assert_eq!(TestMode::LpOnly.to_string(), "lp-only"); - } - - // ============ FromStr tests ============ - - #[test] - fn test_from_str_canonical() { - assert_eq!("mixnet".parse::().unwrap(), TestMode::Mixnet); - assert_eq!( - "single-hop".parse::().unwrap(), - TestMode::SingleHop - ); - assert_eq!("two-hop".parse::().unwrap(), TestMode::TwoHop); - assert_eq!("lp-only".parse::().unwrap(), TestMode::LpOnly); - } - - #[test] - fn test_from_str_alternate_formats() { - // snake_case - assert_eq!( - "single_hop".parse::().unwrap(), - TestMode::SingleHop - ); - assert_eq!("two_hop".parse::().unwrap(), TestMode::TwoHop); - assert_eq!("lp_only".parse::().unwrap(), TestMode::LpOnly); - - // no separator - assert_eq!( - "singlehop".parse::().unwrap(), - TestMode::SingleHop - ); - assert_eq!("twohop".parse::().unwrap(), TestMode::TwoHop); - assert_eq!("lponly".parse::().unwrap(), TestMode::LpOnly); - } - - #[test] - fn test_from_str_case_insensitive() { - assert_eq!("MIXNET".parse::().unwrap(), TestMode::Mixnet); - assert_eq!( - "Single-Hop".parse::().unwrap(), - TestMode::SingleHop - ); - assert_eq!("TWO_HOP".parse::().unwrap(), TestMode::TwoHop); - assert_eq!("LpOnly".parse::().unwrap(), TestMode::LpOnly); - } - - #[test] - fn test_from_str_invalid() { - assert!("invalid".parse::().is_err()); - assert!("".parse::().is_err()); - assert!("mix".parse::().is_err()); - } - - // ============ Roundtrip test ============ - - #[test] - fn test_display_fromstr_roundtrip() { - for mode in [ - TestMode::Mixnet, - TestMode::SingleHop, - TestMode::TwoHop, - TestMode::LpOnly, - ] { - let s = mode.to_string(); - let parsed: TestMode = s.parse().unwrap(); - assert_eq!(mode, parsed); - } - } -} diff --git a/nym-gateway-probe/src/run.rs b/nym-gateway-probe/src/run.rs index 6fc3d7083b..7bb668206f 100644 --- a/nym-gateway-probe/src/run.rs +++ b/nym-gateway-probe/src/run.rs @@ -1,16 +1,12 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use anyhow::bail; use clap::{Parser, Subcommand}; use nym_bin_common::bin_info; use nym_config::defaults::setup_env; -use nym_gateway_probe::nodes::{NymApiDirectory, query_gateway_by_ip}; -use nym_gateway_probe::{ - CredentialArgs, NetstackArgs, ProbeResult, TestMode, TestedNode, TestedNodeDetails, -}; +use nym_gateway_probe::config::{CredentialArgs, CredentialMode, ProbeConfig}; +use nym_gateway_probe::{NymApiDirectory, ProbeResult, query_gateway_by_ip}; use nym_sdk::mixnet::NodeIdentity; -use std::net::SocketAddr; use std::path::Path; use std::{path::PathBuf, sync::OnceLock}; use tracing::*; @@ -19,136 +15,84 @@ fn pretty_build_info_static() -> &'static str { static PRETTY_BUILD_INFORMATION: OnceLock = OnceLock::new(); PRETTY_BUILD_INFORMATION.get_or_init(|| bin_info!().pretty_print()) } - -fn validate_node_identity(s: &str) -> Result { - match s.parse() { - Ok(cg) => Ok(cg), - Err(_) => Err(format!("failed to parse country group: {s}")), - } -} +const DEFAULT_CONFIG_DIR: &str = "/tmp/nym-gateway-probe/config/"; #[derive(Parser)] #[clap(author = "Nymtech", version, long_version = pretty_build_info_static(), about)] struct CliArgs { #[command(subcommand)] - command: Option, + command: Commands, /// Path pointing to an env file describing the network. - #[arg(short, long, global = true)] + #[arg(short, long)] config_env_file: Option, - /// The specific gateway specified by ID. - #[arg(long, short = 'g', alias = "gateway", global = true)] - entry_gateway: Option, - - /// The address of the gateway to probe directly (bypasses directory lookup) - /// Supports formats: IP (192.168.66.5), IP:PORT (192.168.66.5:8080), HOST:PORT (localhost:30004) - #[arg(long, global = true)] - gateway_ip: Option, - - /// The address of the exit gateway for LP forwarding tests (used with --test-lp-wg) - /// When specified, --gateway-ip becomes the entry gateway and this becomes the exit gateway - /// Supports formats: IP (192.168.66.5), IP:PORT (192.168.66.5:8080), HOST:PORT (localhost:30004) - #[arg(long, global = true)] - exit_gateway_ip: Option, - - /// Ed25519 identity of the entry gateway (base58 encoded) - /// When provided, skips HTTP API query - use for localnet testing - #[arg(long, global = true)] - entry_gateway_identity: Option, - - /// Ed25519 identity of the exit gateway (base58 encoded) - /// When provided, skips HTTP API query - use for localnet testing - #[arg(long, global = true)] - exit_gateway_identity: Option, - - /// LP listener address for entry gateway (e.g., "192.168.66.6:41264") - /// Used with --entry-gateway-identity for localnet mode - #[arg(long, global = true)] - entry_lp_address: Option, - - /// LP listener address for exit gateway (e.g., "172.18.0.5:41264") - /// This is the address the entry gateway uses to reach exit (for forwarding) - /// Used with --exit-gateway-identity for localnet mode - #[arg(long, global = true)] - exit_lp_address: Option, - - /// Default LP control port when deriving LP address from gateway IP - #[arg(long, global = true, default_value = "41264")] - lp_port: u16, - - /// Identity of the node to test - #[arg(long, short, value_parser = validate_node_identity, global = true)] - node: Option, - - #[arg(long, global = true)] - min_gateway_mixnet_performance: Option, - - // this was a dead field - // #[arg(long, global = true)] - // min_gateway_vpn_performance: Option, - #[arg(long, global = true)] - only_wireguard: bool, - - #[arg(long, global = true)] - only_lp_registration: bool, - - /// Test WireGuard via LP registration (no mixnet) - uses nested session forwarding - #[arg(long, global = true)] - test_lp_wg: bool, - - /// Test mode - explicitly specify which tests to run - /// - /// Modes: - /// mixnet - Traditional mixnet testing (entry/exit pings + WireGuard via authenticator) - /// single-hop - LP registration + WireGuard on single gateway (no mixnet) - /// two-hop - Entry LP + Exit LP (nested forwarding) + WireGuard tunnel - /// lp-only - LP registration only (no WireGuard) - /// - /// If not specified, mode is inferred from other flags: - /// --only-lp-registration → lp-only - /// --test-lp-wg with exit gateway → two-hop - /// --test-lp-wg without exit → single-hop - /// otherwise → mixnet - #[arg(long, global = true, value_name = "MODE")] - mode: Option, - /// Disable logging during probe - #[arg(long, global = true)] - ignore_egress_epoch_role: bool, - - #[arg(long, global = true)] + #[arg(long)] no_log: bool, - - /// Arguments to be appended to the wireguard config enabling amnezia-wg configuration - #[arg(long, short, global = true)] - amnezia_args: Option, - - /// Arguments to manage netstack downloads - #[command(flatten)] - netstack_args: NetstackArgs, - - /// Arguments to manage credentials - #[command(flatten)] - credential_args: CredentialArgs, } -const DEFAULT_CONFIG_DIR: &str = "/tmp/nym-gateway-probe/config/"; - +#[allow(clippy::large_enum_variant)] #[derive(Subcommand, Debug)] enum Commands { - /// Run the probe locally + /// Run the probe on an unannounced gateway. IP must be provided. Bypasses directory lookup RunLocal { - /// Provide a mnemonic to get credentials (optional when using --use-mock-ecash) - #[arg(long)] - mnemonic: Option, - + /// Directory for credential and mixnet storage #[arg(long)] config_dir: Option, - /// Use mock ecash credentials for testing (requires gateway with --lp-use-mock-ecash) + /// The address of the gateway + /// Supports formats: IP (192.168.66.5), IP:PORT (192.168.66.5:8080), HOST:PORT (localhost:30004) #[arg(long)] - use_mock_ecash: bool, + entry_gateway_ip: String, + + /// The address of the exit gateway. If not provided, entry acts as exit + /// Supports formats: IP (192.168.66.5), IP:PORT (192.168.66.5:8080), HOST:PORT (localhost:30004) + #[arg(long)] + exit_gateway_ip: Option, + + /// Arguments to manage credentials + #[command(flatten)] + credential_mode: CredentialMode, + + #[command(flatten)] + probe_config: ProbeConfig, + }, + + /// Run the probe on a bonded gateway. Uses directory lookup + Run { + /// Directory for credential and mixnet storage + #[arg(long)] + config_dir: Option, + + /// The specific gateway specified by ID. + #[arg(long, short = 'g', alias = "gateway")] + entry_gateway: NodeIdentity, + + /// Optional identity of the exit node to test, if not provided, entry_gateway is used + #[arg(long)] + exit_gateway: Option, + + /// Arguments to manage credentials + #[command(flatten)] + credential_mode: CredentialMode, + + #[command(flatten)] + probe_config: ProbeConfig, + }, + + /// Run the probe by NS agents + RunAgent { + /// The specific gateway specified by ID. + #[arg(long, short = 'g', alias = "gateway")] + entry_gateway: NodeIdentity, + + /// Arguments to manage credentials + #[command(flatten)] + credential_args: CredentialArgs, + + #[command(flatten)] + probe_config: ProbeConfig, }, } @@ -168,214 +112,66 @@ fn setup_logging() { .init(); } -/// Resolve the test mode from explicit --mode arg or infer from legacy flags -fn resolve_test_mode( - mode_arg: Option<&str>, - only_wireguard: bool, - only_lp_registration: bool, - test_lp_wg: bool, - has_exit_gateway: bool, -) -> anyhow::Result { - if let Some(mode_str) = mode_arg { - // Explicit --mode takes priority - mode_str - .parse::() - .map_err(|e| anyhow::anyhow!("{}", e)) - } else { - // Infer from legacy flags - Ok(TestMode::from_flags( - only_wireguard, - only_lp_registration, - test_lp_wg, - has_exit_gateway, - )) - } -} - -/// Convert TestMode back to legacy boolean flags for backward compatibility -fn mode_to_flags(mode: TestMode) -> (bool, bool, bool) { - match mode { - TestMode::Mixnet => (false, false, false), // only_wireguard handled separately - TestMode::SingleHop => (false, false, true), - TestMode::TwoHop => (false, false, true), - TestMode::LpOnly => (false, true, false), - } -} - pub(crate) async fn run() -> anyhow::Result { let args = CliArgs::parse(); if !args.no_log { setup_logging(); } debug!("{:?}", nym_bin_common::bin_info_local_vergen!()); - setup_env(args.config_env_file.as_ref()); + setup_env(args.config_env_file.as_ref()); let network = nym_sdk::NymNetworkDetails::new_from_env(); - let nyxd_url = network - .endpoints - .first() - .map(|ep| ep.nyxd_url()) - .ok_or(anyhow::anyhow!("missing nyxd url"))?; + match args.command { + Commands::RunLocal { + config_dir, + entry_gateway_ip, + exit_gateway_ip, + credential_mode, + probe_config, + } => { + info!("Using direct IP query mode for gateway: {entry_gateway_ip}"); + let entry_details = query_gateway_by_ip(entry_gateway_ip) + .await? + .to_testable_node()?; - // Three resolution modes in priority order: - // 1. Localnet mode: --entry-gateway-identity provided (no HTTP query) - // 2. Direct IP mode: --gateway-ip provided (queries HTTP API) - // 3. Directory mode: uses nym-api directory service - - // Localnet mode: identity provided via CLI, skip HTTP queries entirely - if let Some(entry_identity_str) = &args.entry_gateway_identity { - info!("Using localnet mode with CLI-provided gateway identity"); - - let entry_identity = NodeIdentity::from_base58_string(entry_identity_str)?; - - // Entry LP address: explicit or derived from gateway_ip + lp_port - let entry_lp_addr: SocketAddr = if let Some(lp_addr) = &args.entry_lp_address { - lp_addr - .parse() - .map_err(|e| anyhow::anyhow!("Invalid entry-lp-address '{}': {}", lp_addr, e))? - } else if let Some(gw_ip) = &args.gateway_ip { - // Derive LP address from gateway IP - let ip: std::net::IpAddr = gw_ip - .parse() - .map_err(|e| anyhow::anyhow!("Invalid gateway-ip '{}': {}", gw_ip, e))?; - SocketAddr::new(ip, args.lp_port) - } else { - anyhow::bail!( - "--entry-lp-address or --gateway-ip required with --entry-gateway-identity" - ); - }; - - let entry_details = TestedNodeDetails::from_cli(entry_identity, entry_lp_addr); - - // Parse exit gateway if provided - let exit_details = if let Some(exit_identity_str) = &args.exit_gateway_identity { - let exit_identity = NodeIdentity::from_base58_string(exit_identity_str)?; - let exit_lp_addr: SocketAddr = args - .exit_lp_address - .as_ref() - .ok_or_else(|| { - anyhow::anyhow!("--exit-lp-address required with --exit-gateway-identity") - })? - .parse() - .map_err(|e| anyhow::anyhow!("Invalid exit-lp-address: {}", e))?; - Some(TestedNodeDetails::from_cli(exit_identity, exit_lp_addr)) - } else { - None - }; - - // Resolve test mode from --mode arg or infer from flags - let has_exit = exit_details.is_some(); - let test_mode = resolve_test_mode( - args.mode.as_deref(), - args.only_wireguard, - args.only_lp_registration, - args.test_lp_wg, - has_exit, - )?; - - // Validate that two-hop mode has required exit gateway - if test_mode.needs_exit_gateway() && !has_exit { - bail!( - "--mode two-hop requires exit gateway \ - (use --exit-gateway-identity and --exit-lp-address)" - ); - } - - info!("Test mode: {}", test_mode); - - // Convert back to flags for backward compatibility with existing probe methods - // only_wireguard is preserved from args since it's orthogonal to mode - // (it means "skip ping tests" in mixnet mode, irrelevant for LP modes) - let (_, only_lp_registration, test_lp_wg) = mode_to_flags(test_mode); - let only_wireguard = args.only_wireguard; - - let mut trial = nym_gateway_probe::Probe::new_localnet( - entry_details, - exit_details, - args.netstack_args, - args.credential_args, - ); - - if let Some(awg_args) = args.amnezia_args { - trial.with_amnezia(&awg_args); - } - - // Localnet mode doesn't need directory, but nyxd_url is still used for credentials - return match &args.command { - Some(Commands::RunLocal { - mnemonic, - config_dir, - use_mock_ecash, - }) => { - let config_dir = config_dir - .clone() - .unwrap_or_else(|| Path::new(DEFAULT_CONFIG_DIR).join(&network.network_name)); - - info!( - "using the following directory for the probe config: {}", - config_dir.display() - ); - - Box::pin(trial.probe_run_locally( - &config_dir, - mnemonic.as_deref(), - None, // No directory in localnet mode - nyxd_url, - args.ignore_egress_epoch_role, - only_wireguard, - only_lp_registration, - test_lp_wg, - args.min_gateway_mixnet_performance, - *use_mock_ecash, - )) - .await - } - None => { - Box::pin(trial.probe( - None, // No directory in localnet mode - nyxd_url, - args.ignore_egress_epoch_role, - only_wireguard, - only_lp_registration, - test_lp_wg, - args.min_gateway_mixnet_performance, - )) - .await - } - }; - } - - // If gateway IP is provided, query it directly without using the directory - let (entry, directory, gateway_node, exit_gateway_node) = - if let Some(gateway_ip) = args.gateway_ip.clone() { - info!("Using direct IP query mode for gateway: {}", gateway_ip); - let gateway_node = query_gateway_by_ip(gateway_ip).await?; - let identity = gateway_node.identity(); - - // Query exit gateway if provided (for LP forwarding tests) - let exit_node = if let Some(exit_gateway_ip) = args.exit_gateway_ip { - info!( - "Using direct IP query mode for exit gateway: {}", - exit_gateway_ip - ); - Some(query_gateway_by_ip(exit_gateway_ip).await?) + // Parse exit gateway if provided + let exit_details = if let Some(ip_address) = exit_gateway_ip { + info!("Using direct IP query mode for exit gateway: {ip_address}"); + Some(query_gateway_by_ip(ip_address).await?.to_testable_node()?) } else { None }; - // Still create the directory for potential secondary lookups, - // but only if API URL is available - let directory = - if let Some(api_url) = network.endpoints.first().and_then(|ep| ep.api_url()) { - Some(NymApiDirectory::new(api_url).await?) - } else { - None - }; + let config_dir = config_dir + .clone() + .unwrap_or_else(|| Path::new(DEFAULT_CONFIG_DIR).join(&network.network_name)); - (identity, directory, Some(gateway_node), exit_node) - } else { - // Original behavior: use directory service + if config_dir.is_file() { + anyhow::bail!("provided configuration directory is a file"); + } + + if !config_dir.exists() { + std::fs::create_dir_all(config_dir.clone())?; + } + + info!( + "using the following directory for the probe config: {}", + config_dir.display() + ); + + let trial = + nym_gateway_probe::Probe::new(entry_details, exit_details, network, probe_config); + + Box::pin(trial.probe_run_locally(&config_dir, credential_mode)).await + } + Commands::Run { + entry_gateway, + exit_gateway, + config_dir, + credential_mode, + probe_config, + } => { let api_url = network .endpoints .first() @@ -383,111 +179,57 @@ pub(crate) async fn run() -> anyhow::Result { .ok_or(anyhow::anyhow!("missing api url"))?; let directory = NymApiDirectory::new(api_url).await?; + let entry_details = directory + .entry_gateway(&entry_gateway)? + .to_testable_node()?; + let exit_details = exit_gateway + .map(|id_key| directory.exit_gateway(&id_key)) + .transpose()? + .map(|node| node.to_testable_node()) + .transpose()?; - let entry = if let Some(gateway) = &args.entry_gateway { - NodeIdentity::from_base58_string(gateway)? - } else { - directory.random_exit_with_ipr()? - }; - - (entry, Some(directory), None, None) - }; - - let test_point = if let Some(node) = args.node { - TestedNode::Custom { - identity: node, - shares_entry: false, - } - } else { - TestedNode::SameAsEntry - }; - - // Resolve test mode from --mode arg or infer from flags - let has_exit = exit_gateway_node.is_some(); - let test_mode = resolve_test_mode( - args.mode.as_deref(), - args.only_wireguard, - args.only_lp_registration, - args.test_lp_wg, - has_exit, - )?; - info!("Test mode: {}", test_mode); - - // Convert back to flags for backward compatibility with existing probe methods - // only_wireguard is preserved from args since it's orthogonal to mode - let (_, only_lp_registration, test_lp_wg) = mode_to_flags(test_mode); - let only_wireguard = args.only_wireguard; - - let mut trial = if let (Some(entry_node), Some(exit_node)) = (&gateway_node, &exit_gateway_node) - { - // Both entry and exit gateways provided (for LP telescoping tests) - info!("Using both entry and exit gateways for LP forwarding test"); - nym_gateway_probe::Probe::new_with_gateways( - entry, - test_point, - args.netstack_args, - args.credential_args, - entry_node.clone(), - exit_node.clone(), - ) - } else if let Some(gw_node) = gateway_node { - // Only entry gateway provided - nym_gateway_probe::Probe::new_with_gateway( - entry, - test_point, - args.netstack_args, - args.credential_args, - gw_node, - ) - } else { - // No direct gateways, use directory lookup - nym_gateway_probe::Probe::new(entry, test_point, args.netstack_args, args.credential_args) - }; - - if let Some(awg_args) = args.amnezia_args { - trial.with_amnezia(&awg_args); - } - - match &args.command { - Some(Commands::RunLocal { - mnemonic, - config_dir, - use_mock_ecash, - }) => { let config_dir = config_dir .clone() .unwrap_or_else(|| Path::new(DEFAULT_CONFIG_DIR).join(&network.network_name)); + if config_dir.is_file() { + anyhow::bail!("provided configuration directory is a file"); + } + + if !config_dir.exists() { + std::fs::create_dir_all(config_dir.clone())?; + } + info!( "using the following directory for the probe config: {}", config_dir.display() ); - Box::pin(trial.probe_run_locally( - &config_dir, - mnemonic.as_deref(), - directory, - nyxd_url, - args.ignore_egress_epoch_role, - only_wireguard, - only_lp_registration, - test_lp_wg, - args.min_gateway_mixnet_performance, - *use_mock_ecash, - )) - .await + let trial = + nym_gateway_probe::Probe::new(entry_details, exit_details, network, probe_config); + Box::pin(trial.probe_run(&config_dir, credential_mode)).await } - None => { - Box::pin(trial.probe( - directory, - nyxd_url, - args.ignore_egress_epoch_role, - only_wireguard, - only_lp_registration, - test_lp_wg, - args.min_gateway_mixnet_performance, - )) - .await + Commands::RunAgent { + entry_gateway, + credential_args, + mut probe_config, + } => { + let api_url = network + .endpoints + .first() + .and_then(|ep| ep.api_url()) + .ok_or(anyhow::anyhow!("missing api url"))?; + + let directory = NymApiDirectory::new(api_url).await?; + let entry_details = directory + .entry_gateway(&entry_gateway)? + .to_testable_node()?; + + // Agents run everything + probe_config.test_mode = nym_gateway_probe::config::TestMode::All; + + let trial = nym_gateway_probe::Probe::new(entry_details, None, network, probe_config); + Box::pin(trial.probe_run_agent(credential_args)).await } } } diff --git a/nym-ip-packet-client/Cargo.toml b/nym-ip-packet-client/Cargo.toml index 86b0e21830..e631a759ca 100644 --- a/nym-ip-packet-client/Cargo.toml +++ b/nym-ip-packet-client/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "nym-ip-packet-client" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "Nym's implementation of a client that sends and receives IP packets" [lints] workspace = true @@ -20,5 +21,5 @@ tokio-util.workspace = true tokio.workspace = true tracing.workspace = true -nym-sdk = { path = "../sdk/rust/nym-sdk" } -nym-ip-packet-requests = { path = "../common/ip-packet-requests" } +nym-sdk = { workspace = true } +nym-ip-packet-requests = { workspace = true } diff --git a/nym-network-monitor/Cargo.toml b/nym-network-monitor/Cargo.toml index a84a5f3c02..5f68dec06e 100644 --- a/nym-network-monitor/Cargo.toml +++ b/nym-network-monitor/Cargo.toml @@ -7,6 +7,7 @@ homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -30,15 +31,15 @@ utoipa-swagger-ui = { workspace = true, features = ["axum"] } tokio-postgres = { workspace = true } # internal -nym-bin-common = { path = "../common/bin-common", features = ["basic_tracing"] } -nym-client-core = { path = "../common/client-core" } -nym-crypto = { path = "../common/crypto" } -nym-network-defaults = { path = "../common/network-defaults" } -nym-gateway-requests = { path = "../common/gateway-requests" } -nym-sdk = { path = "../sdk/rust/nym-sdk" } -nym-sphinx = { path = "../common/nymsphinx" } -nym-topology = { path = "../common/topology" } -nym-types = { path = "../common/types" } -nym-validator-client = { path = "../common/client-libs/validator-client" } -nym-http-api-client = { path = "../common/http-api-client" } -nym-mixnet-contract-common = { path = "../common/cosmwasm-smart-contracts/mixnet-contract" } +nym-bin-common = { workspace = true, features = ["basic_tracing"] } +nym-client-core = { workspace = true } +nym-crypto = { workspace = true } +nym-network-defaults = { workspace = true } +nym-gateway-requests = { workspace = true } +nym-sdk = { workspace = true } +nym-sphinx = { workspace = true } +nym-topology = { workspace = true } +nym-types = { workspace = true } +nym-validator-client = { workspace = true } +nym-http-api-client = { workspace = true } +nym-mixnet-contract-common = { workspace = true } diff --git a/nym-node-status-api/nym-node-status-agent/Cargo.toml b/nym-node-status-api/nym-node-status-agent/Cargo.toml index b7840cd9a6..cfb9cea2c3 100644 --- a/nym-node-status-api/nym-node-status-agent/Cargo.toml +++ b/nym-node-status-api/nym-node-status-agent/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-node-status-agent" -version = "1.0.7" +version = "1.1.2" authors.workspace = true repository.workspace = true homepage.workspace = true @@ -12,16 +12,19 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +publish = false [dependencies] anyhow = { workspace = true } clap = { workspace = true, features = ["derive", "env"] } futures = { workspace = true } -nym-bin-common = { path = "../../common/bin-common", features = ["models"] } -nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] } +nym-bin-common = { workspace = true, features = ["models"] } +nym-crypto = { workspace = true, features = ["asymmetric", "rand"] } nym-node-status-client = { path = "../nym-node-status-client" } rand = { workspace = true } +regex = { workspace = true } +serde_json = { workspace = true } tokio = { workspace = true, features = [ "macros", "rt-multi-thread", diff --git a/nym-node-status-api/nym-node-status-agent/README.md b/nym-node-status-api/nym-node-status-agent/README.md index bf75649c25..c70732570e 100644 --- a/nym-node-status-api/nym-node-status-agent/README.md +++ b/nym-node-status-api/nym-node-status-agent/README.md @@ -4,7 +4,6 @@ An agent to run tests and report results back to the Node Status API. Environment variables that can be set individually are: -- `NYM_NODE_MNEMONICS` - mnemonic to get tickets for tests - `NODE_STATUS_AGENT_SERVER_PORT` - Node Status API port - `NODE_STATUS_AGENT_SERVER_ADDRESS` - Node Status API address diff --git a/nym-node-status-api/nym-node-status-agent/run.sh b/nym-node-status-api/nym-node-status-agent/run.sh index 1061ad5f95..99ebb1867a 100755 --- a/nym-node-status-api/nym-node-status-agent/run.sh +++ b/nym-node-status-api/nym-node-status-agent/run.sh @@ -1,33 +1,31 @@ #!/bin/bash +# used primarily for local testing + set -eu export ENVIRONMENT=${ENVIRONMENT:-"mainnet"} -probe_git_ref="nym-vpn-core-v1.4.0" - crate_root=$(dirname $(realpath "$0")) +echo crate_root=${crate_root} monorepo_root=$(realpath "${crate_root}/../..") +echo monorepo_root=${monorepo_root} -echo "Expecting nym-vpn-client repo at a sibling level of nym monorepo dir" -gateway_probe_src=$(dirname "${monorepo_root}")/nym-vpn-client/nym-vpn-core +gateway_probe_src="${monorepo_root}/nym-gateway-probe" echo "gateway_probe_src=$gateway_probe_src" set -a source "${monorepo_root}/envs/${ENVIRONMENT}.env" set +a -if [ -z "$NYM_NODE_MNEMONICS" ]; then - echo "NYM_NODE_MNEMONICS is required to run an agent" - exit 1 -fi export RUST_LOG="info" NODE_STATUS_AGENT_SERVER_ADDRESS="http://127.0.0.1" NODE_STATUS_AGENT_SERVER_PORT="8000" SERVER="${NODE_STATUS_AGENT_SERVER_ADDRESS}|${NODE_STATUS_AGENT_SERVER_PORT}" -export NODE_STATUS_AGENT_AUTH_KEY="BjyC9SsHAZUzPRkQR4sPTvVrp4GgaquTh5YfSJksvvWT" +# hardcoded key used only for LOCAL TESTING +export NODE_STATUS_AGENT_AUTH_KEY=${NODE_STATUS_AGENT_AUTH_KEY_STAGING:-"BjyC9SsHAZUzPRkQR4sPTvVrp4GgaquTh5YfSJksvvWT"} export NODE_STATUS_AGENT_PROBE_PATH="$crate_root/nym-gateway-probe" -export NODE_STATUS_AGENT_PROBE_EXTRA_ARGS="netstack-download-timeout-sec=30,netstack-num-ping=2,netstack-send-timeout-sec=1,netstack-recv-timeout-sec=1" +export NODE_STATUS_AGENT_PROBE_EXTRA_ARGS="netstack-download-timeout-sec=30,netstack-num-ping=2,netstack-send-timeout-sec=1,netstack-recv-timeout-sec=1,socks5-json-rpc-url-list=https://cloudflare-eth.com;https://ethereum.publicnode.com" workers=${1:-1} echo "Running $workers workers in parallel" @@ -35,11 +33,9 @@ echo "Running $workers workers in parallel" # build & copy over GW probe function copy_gw_probe() { pushd $gateway_probe_src - git fetch -a - git checkout $probe_git_ref cargo build --release --package nym-gateway-probe - cp target/release/nym-gateway-probe "$crate_root" + cp "${monorepo_root}/target/release/nym-gateway-probe" "$crate_root" $crate_root/nym-gateway-probe --version popd diff --git a/nym-node-status-api/nym-node-status-agent/src/cli/run_probe.rs b/nym-node-status-api/nym-node-status-agent/src/cli/run_probe.rs index 7d0e106b76..820136b3f0 100644 --- a/nym-node-status-api/nym-node-status-agent/src/cli/run_probe.rs +++ b/nym-node-status-api/nym-node-status-agent/src/cli/run_probe.rs @@ -52,11 +52,41 @@ pub(crate) async fn run_probe( // Run the probe let log = probe.run_and_get_log( - &Some(gateway_identity_key.clone()), + gateway_identity_key.clone(), probe_extra_args, testrun.ticket_materials, ); + // Inspect the probe output for socks5 field + // Extract JSON from log output (probe outputs logs followed by JSON) + let json_str = extract_json_from_log(&log); + if json_str.is_empty() { + tracing::error!("Failed to extract JSON from probe output"); + } else { + match serde_json::from_str::(&json_str) { + Ok(json) => { + if let Some(outcome) = json.get("outcome") { + match outcome.get("socks5") { + Some(socks5) if socks5.is_null() => { + tracing::warn!("🌐⚠️ socks5 field is NULL in probe output"); + } + Some(socks5) => { + tracing::info!("🌐 socks5 field present: {}", socks5); + } + None => { + tracing::warn!("🌐⚠️ socks5 field is MISSING from probe output"); + } + } + } else { + tracing::warn!("🌐⚠️ outcome field is MISSING from probe output"); + } + } + Err(e) => { + tracing::error!("Failed to parse probe output as JSON: {e}"); + } + } + } + // Submit to ALL servers in parallel let handles = servers .iter() @@ -122,3 +152,17 @@ pub(crate) async fn run_probe( Ok(()) } + +/// Extract JSON from probe log output. +/// The probe outputs log lines followed by JSON starting with `\n{ `. +fn extract_json_from_log(log: &str) -> String { + static RE: std::sync::LazyLock = + std::sync::LazyLock::new(|| regex::Regex::new(r"\n\{\s").expect("Invalid regex pattern")); + + let result: Vec<_> = RE.splitn(log, 2).collect(); + if result.len() == 2 { + format!("{{ {}", result[1]) + } else { + String::new() + } +} diff --git a/nym-node-status-api/nym-node-status-agent/src/probe.rs b/nym-node-status-api/nym-node-status-agent/src/probe.rs index 3ee47646b9..610c07c4bd 100644 --- a/nym-node-status-api/nym-node-status-agent/src/probe.rs +++ b/nym-node-status-api/nym-node-status-agent/src/probe.rs @@ -73,16 +73,15 @@ impl GwProbe { pub(crate) fn run_and_get_log( &self, - gateway_key: &Option, + gateway_key: String, probe_extra_args: &Vec, ticket_materials: AttachedTicketMaterials, ) -> String { let mut command = std::process::Command::new(&self.path); command.stdout(std::process::Stdio::piped()); - if let Some(gateway_id) = gateway_key { - command.arg("--gateway").arg(gateway_id); - } + command.arg("run-agent"); + command.arg("--gateway").arg(gateway_key); tracing::info!("Extra args for the probe:"); for arg in probe_extra_args { diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-0112296b190328a3856d1adf51aafa2525da6c0b871633aad80ad555db9cf47c.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-0112296b190328a3856d1adf51aafa2525da6c0b871633aad80ad555db9cf47c.json deleted file mode 100644 index 068bc54b4b..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-0112296b190328a3856d1adf51aafa2525da6c0b871633aad80ad555db9cf47c.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n SELECT epoch_id as \"epoch_id: u32\", serialised_key, serialization_revision as \"serialization_revision: u8\"\n FROM master_verification_key WHERE epoch_id = ?\n ", - "describe": { - "columns": [ - { - "name": "epoch_id: u32", - "ordinal": 0, - "type_info": "Integer" - }, - { - "name": "serialised_key", - "ordinal": 1, - "type_info": "Blob" - }, - { - "name": "serialization_revision: u8", - "ordinal": 2, - "type_info": "Integer" - } - ], - "parameters": { - "Right": 1 - }, - "nullable": [ - false, - false, - false - ] - }, - "hash": "0112296b190328a3856d1adf51aafa2525da6c0b871633aad80ad555db9cf47c" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-1fc72f8ba24039548047e1766c9105614dea7fd301f0ec38bfe85bfe546dad40.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-1fc72f8ba24039548047e1766c9105614dea7fd301f0ec38bfe85bfe546dad40.json deleted file mode 100644 index cdc057df57..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-1fc72f8ba24039548047e1766c9105614dea7fd301f0ec38bfe85bfe546dad40.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n INSERT INTO ecash_deposit_usage (deposit_id, ticketbooks_requested_on, client_pubkey, request_uuid)\n VALUES (?, ?, ?, ?)\n ", - "describe": { - "columns": [], - "parameters": { - "Right": 4 - }, - "nullable": [] - }, - "hash": "1fc72f8ba24039548047e1766c9105614dea7fd301f0ec38bfe85bfe546dad40" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-28681fcd8e2d4326f628681b8f2a317aabce063a650be362d3a8ed83cc7c3549.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-28681fcd8e2d4326f628681b8f2a317aabce063a650be362d3a8ed83cc7c3549.json deleted file mode 100644 index 32f3f9958c..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-28681fcd8e2d4326f628681b8f2a317aabce063a650be362d3a8ed83cc7c3549.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n DELETE FROM blinded_shares WHERE created < ?\n ", - "describe": { - "columns": [], - "parameters": { - "Right": 1 - }, - "nullable": [] - }, - "hash": "28681fcd8e2d4326f628681b8f2a317aabce063a650be362d3a8ed83cc7c3549" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-2930ca6e3875c74acb7abb9ad889f166ad7f57681f76a1d0c7723d007c1f2c1e.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-2930ca6e3875c74acb7abb9ad889f166ad7f57681f76a1d0c7723d007c1f2c1e.json deleted file mode 100644 index 9ef7d96cca..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-2930ca6e3875c74acb7abb9ad889f166ad7f57681f76a1d0c7723d007c1f2c1e.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n INSERT INTO global_expiration_date_signatures(expiration_date, epoch_id, serialised_signatures, serialization_revision)\n VALUES (?, ?, ?, ?)\n ", - "describe": { - "columns": [], - "parameters": { - "Right": 4 - }, - "nullable": [] - }, - "hash": "2930ca6e3875c74acb7abb9ad889f166ad7f57681f76a1d0c7723d007c1f2c1e" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-396f40c33f0f62796eb7449d640bd97845350f4fb9f806c60b93c7cebd5e410d.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-396f40c33f0f62796eb7449d640bd97845350f4fb9f806c60b93c7cebd5e410d.json deleted file mode 100644 index 944c86e33e..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-396f40c33f0f62796eb7449d640bd97845350f4fb9f806c60b93c7cebd5e410d.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n SELECT error_message\n FROM blinded_shares\n WHERE id = ?;\n ", - "describe": { - "columns": [ - { - "name": "error_message", - "ordinal": 0, - "type_info": "Text" - } - ], - "parameters": { - "Right": 1 - }, - "nullable": [ - true - ] - }, - "hash": "396f40c33f0f62796eb7449d640bd97845350f4fb9f806c60b93c7cebd5e410d" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-52b378e282d93db941eff53b5b311e5732ece0bf84ea98f2328b20add8f2b5ef.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-52b378e282d93db941eff53b5b311e5732ece0bf84ea98f2328b20add8f2b5ef.json deleted file mode 100644 index 77a99816d4..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-52b378e282d93db941eff53b5b311e5732ece0bf84ea98f2328b20add8f2b5ef.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n DELETE FROM partial_blinded_wallet_failure WHERE created < ?\n ", - "describe": { - "columns": [], - "parameters": { - "Right": 1 - }, - "nullable": [] - }, - "hash": "52b378e282d93db941eff53b5b311e5732ece0bf84ea98f2328b20add8f2b5ef" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-70d8f240ad6edda6b8c7f2e800e7fca89d80869484f2f3c66cabb898f0298c62.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-70d8f240ad6edda6b8c7f2e800e7fca89d80869484f2f3c66cabb898f0298c62.json deleted file mode 100644 index 2f22ed93fa..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-70d8f240ad6edda6b8c7f2e800e7fca89d80869484f2f3c66cabb898f0298c62.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "INSERT INTO master_verification_key(epoch_id, serialised_key, serialization_revision) VALUES (?, ?, ?)", - "describe": { - "columns": [], - "parameters": { - "Right": 3 - }, - "nullable": [] - }, - "hash": "70d8f240ad6edda6b8c7f2e800e7fca89d80869484f2f3c66cabb898f0298c62" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-97d97ebb6bc8f4114fdea9ebc9f57f91a11f5057273cb70bd0e629712d17dd41.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-97d97ebb6bc8f4114fdea9ebc9f57f91a11f5057273cb70bd0e629712d17dd41.json deleted file mode 100644 index 84e8ba9f16..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-97d97ebb6bc8f4114fdea9ebc9f57f91a11f5057273cb70bd0e629712d17dd41.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n INSERT INTO partial_blinded_wallet_failure(corresponding_deposit, epoch_id, expiration_date, node_id, created, failure_message)\n VALUES (?, ?, ?, ?, ?, ?)\n ", - "describe": { - "columns": [], - "parameters": { - "Right": 6 - }, - "nullable": [] - }, - "hash": "97d97ebb6bc8f4114fdea9ebc9f57f91a11f5057273cb70bd0e629712d17dd41" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-a8b7ce0fe4755c28b96d1e503e313ab15fed747fb0cee1c9f949fb58461b3f79.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-a8b7ce0fe4755c28b96d1e503e313ab15fed747fb0cee1c9f949fb58461b3f79.json deleted file mode 100644 index 6cb8138cc5..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-a8b7ce0fe4755c28b96d1e503e313ab15fed747fb0cee1c9f949fb58461b3f79.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n SELECT epoch_id as \"epoch_id: u32\", serialised_signatures, serialization_revision as \"serialization_revision: u8\"\n FROM global_coin_index_signatures WHERE epoch_id = ?\n ", - "describe": { - "columns": [ - { - "name": "epoch_id: u32", - "ordinal": 0, - "type_info": "Integer" - }, - { - "name": "serialised_signatures", - "ordinal": 1, - "type_info": "Blob" - }, - { - "name": "serialization_revision: u8", - "ordinal": 2, - "type_info": "Integer" - } - ], - "parameters": { - "Right": 1 - }, - "nullable": [ - false, - false, - false - ] - }, - "hash": "a8b7ce0fe4755c28b96d1e503e313ab15fed747fb0cee1c9f949fb58461b3f79" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-b8257a0832d0124f0a8aaaf81dc6a811c593aea8febf1f891117e5e84213f147.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-b8257a0832d0124f0a8aaaf81dc6a811c593aea8febf1f891117e5e84213f147.json deleted file mode 100644 index 03efdc4734..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-b8257a0832d0124f0a8aaaf81dc6a811c593aea8febf1f891117e5e84213f147.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n DELETE FROM partial_blinded_wallet WHERE created < ?\n ", - "describe": { - "columns": [], - "parameters": { - "Right": 1 - }, - "nullable": [] - }, - "hash": "b8257a0832d0124f0a8aaaf81dc6a811c593aea8febf1f891117e5e84213f147" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-c2b841762bdb963fff337ef5c8ec9f560017b4da6b0303ea0397d9568229e167.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-c2b841762bdb963fff337ef5c8ec9f560017b4da6b0303ea0397d9568229e167.json deleted file mode 100644 index 6b70481c75..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-c2b841762bdb963fff337ef5c8ec9f560017b4da6b0303ea0397d9568229e167.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n SELECT\n t1.node_id as \"node_id!\",\n t1.blinded_signature as \"blinded_signature!\",\n t1.epoch_id as \"epoch_id!\",\n t1.expiration_date as \"expiration_date!: Date\"\n FROM partial_blinded_wallet as t1\n JOIN ecash_deposit_usage as t2\n on t1.corresponding_deposit = t2.deposit_id\n JOIN blinded_shares as t3\n ON t2.request_uuid = t3.request_uuid\n WHERE t3.device_id = ? AND t3.credential_id = ?;\n ", - "describe": { - "columns": [ - { - "name": "node_id!", - "ordinal": 0, - "type_info": "Integer" - }, - { - "name": "blinded_signature!", - "ordinal": 1, - "type_info": "Blob" - }, - { - "name": "epoch_id!", - "ordinal": 2, - "type_info": "Integer" - }, - { - "name": "expiration_date!: Date", - "ordinal": 3, - "type_info": "Date" - } - ], - "parameters": { - "Right": 2 - }, - "nullable": [ - true, - true, - true, - true - ] - }, - "hash": "c2b841762bdb963fff337ef5c8ec9f560017b4da6b0303ea0397d9568229e167" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-d3510846941fa2525926b9bfbcdabd806877ce914b514d4f7cd6be318c4debe6.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-d3510846941fa2525926b9bfbcdabd806877ce914b514d4f7cd6be318c4debe6.json deleted file mode 100644 index 9de4c7f230..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-d3510846941fa2525926b9bfbcdabd806877ce914b514d4f7cd6be318c4debe6.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "INSERT INTO global_coin_index_signatures(epoch_id, serialised_signatures, serialization_revision) VALUES (?, ?, ?)", - "describe": { - "columns": [], - "parameters": { - "Right": 3 - }, - "nullable": [] - }, - "hash": "d3510846941fa2525926b9bfbcdabd806877ce914b514d4f7cd6be318c4debe6" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-db176e98198fe594d88eb860d918f633a94d18a19b7f0f96935a62560def7d0f.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-db176e98198fe594d88eb860d918f633a94d18a19b7f0f96935a62560def7d0f.json deleted file mode 100644 index e5dd7c8413..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-db176e98198fe594d88eb860d918f633a94d18a19b7f0f96935a62560def7d0f.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n INSERT INTO partial_blinded_wallet(corresponding_deposit, epoch_id, expiration_date, node_id, created, blinded_signature)\n VALUES (?, ?, ?, ?, ?, ?)\n ", - "describe": { - "columns": [], - "parameters": { - "Right": 6 - }, - "nullable": [] - }, - "hash": "db176e98198fe594d88eb860d918f633a94d18a19b7f0f96935a62560def7d0f" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-e584253e3856355899537eb8fc152f2bfed2d918b894ec0f588e38dd5e8ad726.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-e584253e3856355899537eb8fc152f2bfed2d918b894ec0f588e38dd5e8ad726.json deleted file mode 100644 index 340f83a280..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-e584253e3856355899537eb8fc152f2bfed2d918b894ec0f588e38dd5e8ad726.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n UPDATE ecash_deposit_usage\n SET ticketbook_request_error = ?\n WHERE deposit_id = ?\n ", - "describe": { - "columns": [], - "parameters": { - "Right": 2 - }, - "nullable": [] - }, - "hash": "e584253e3856355899537eb8fc152f2bfed2d918b894ec0f588e38dd5e8ad726" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-e77ffab19b099b84470fe5611716a2e314787586a46cffd074abb67f2f4d109e.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-e77ffab19b099b84470fe5611716a2e314787586a46cffd074abb67f2f4d109e.json deleted file mode 100644 index 449bc6869f..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-e77ffab19b099b84470fe5611716a2e314787586a46cffd074abb67f2f4d109e.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n SELECT t1.node_id, t1.blinded_signature, t1.epoch_id, t1.expiration_date as \"expiration_date!: Date\"\n FROM partial_blinded_wallet as t1\n JOIN ecash_deposit_usage as t2\n on t1.corresponding_deposit = t2.deposit_id\n JOIN blinded_shares as t3\n ON t2.request_uuid = t3.request_uuid\n WHERE t3.id = ?;\n ", - "describe": { - "columns": [ - { - "name": "node_id", - "ordinal": 0, - "type_info": "Integer" - }, - { - "name": "blinded_signature", - "ordinal": 1, - "type_info": "Blob" - }, - { - "name": "epoch_id", - "ordinal": 2, - "type_info": "Integer" - }, - { - "name": "expiration_date!: Date", - "ordinal": 3, - "type_info": "Date" - } - ], - "parameters": { - "Right": 1 - }, - "nullable": [ - false, - false, - false, - false - ] - }, - "hash": "e77ffab19b099b84470fe5611716a2e314787586a46cffd074abb67f2f4d109e" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-ef60c2683211cc4ec2d3e46392518a1f62fa67dfe8f130deb876ebee11bf1602.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-ef60c2683211cc4ec2d3e46392518a1f62fa67dfe8f130deb876ebee11bf1602.json deleted file mode 100644 index c78ff30f50..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-ef60c2683211cc4ec2d3e46392518a1f62fa67dfe8f130deb876ebee11bf1602.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n SELECT error_message\n FROM blinded_shares\n WHERE device_id = ? AND credential_id = ?;\n ", - "describe": { - "columns": [ - { - "name": "error_message", - "ordinal": 0, - "type_info": "Text" - } - ], - "parameters": { - "Right": 2 - }, - "nullable": [ - true - ] - }, - "hash": "ef60c2683211cc4ec2d3e46392518a1f62fa67dfe8f130deb876ebee11bf1602" -} diff --git a/nym-node-status-api/nym-node-status-api/Cargo.toml b/nym-node-status-api/nym-node-status-api/Cargo.toml index df6d5c12cf..8d414c8817 100644 --- a/nym-node-status-api/nym-node-status-api/Cargo.toml +++ b/nym-node-status-api/nym-node-status-api/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-node-status-api" -version = "4.0.12" +version = "4.0.14" authors.workspace = true repository.workspace = true homepage.workspace = true @@ -11,6 +11,7 @@ documentation.workspace = true edition.workspace = true license.workspace = true rust-version.workspace = true +publish = false [dependencies] ammonia = { workspace = true } @@ -26,22 +27,23 @@ humantime = { workspace = true } itertools = { workspace = true } moka = { workspace = true, features = ["future"] } -nym-credentials = { path = "../../common/credentials" } -nym-credential-proxy-lib = { path = "../../common/credential-proxy" } -nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common" } -nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract", features = ["utoipa"] } -nym-bin-common = { path = "../../common/bin-common", features = ["models"] } +nym-credentials = { workspace = true } +nym-credential-proxy-lib = { workspace = true } +nym-contracts-common = { workspace = true } +nym-mixnet-contract-common = { workspace = true, features = ["utoipa"] } +nym-bin-common = { workspace = true, features = ["models"] } nym-node-status-client = { path = "../nym-node-status-client" } -nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "serde"] } -nym-http-api-client = { path = "../../common/http-api-client" } -nym-http-api-common = { path = "../../common/http-api-common", features = ["middleware"] } -nym-network-defaults = { path = "../../common/network-defaults" } -nym-serde-helpers = { path = "../../common/serde-helpers" } -nym-statistics-common = { path = "../../common/statistics" } -nym-validator-client = { path = "../../common/client-libs/validator-client" } -nym-task = { path = "../../common/task" } -nym-node-requests = { path = "../../nym-node/nym-node-requests", features = ["openapi"] } -nym-ecash-time = { path = "../../common/ecash-time", features = ["expiration"] } +nym-crypto = { workspace = true, features = ["asymmetric", "serde"] } +nym-gateway-probe = { path = "../../nym-gateway-probe", features = ["utoipa"] } +nym-http-api-client = { workspace = true } +nym-http-api-common = { workspace = true, features = ["middleware"] } +nym-network-defaults = { workspace = true } +nym-serde-helpers = { workspace = true } +nym-statistics-common = { workspace = true } +nym-validator-client = { workspace = true } +nym-task = { workspace = true } +nym-node-requests = { workspace = true, features = ["openapi", "client"] } +nym-ecash-time = { workspace = true, features = ["expiration"] } rand = { workspace = true } rand_chacha = { workspace = true } @@ -68,7 +70,7 @@ utoipa-swagger-ui = { workspace = true, features = ["axum"] } utoipauto = { workspace = true } zeroize = { workspace = true } -nym-node-metrics = { path = "../../nym-node/nym-node-metrics" } +nym-node-metrics = { workspace = true } [build-dependencies] anyhow = { workspace = true } diff --git a/nym-node-status-api/nym-node-status-api/Dockerfile b/nym-node-status-api/nym-node-status-api/Dockerfile index 0c31f97e71..0b022bd350 100644 --- a/nym-node-status-api/nym-node-status-api/Dockerfile +++ b/nym-node-status-api/nym-node-status-api/Dockerfile @@ -1,6 +1,13 @@ # this will only work with VPN, otherwise remove the harbor part FROM harbor.nymte.ch/dockerhub/rust:latest AS builder +RUN apt update && apt install -yy libdbus-1-dev pkg-config libclang-dev + +# Install go (required for nym-gateway-probe dependency) +RUN wget https://go.dev/dl/go1.22.5.linux-amd64.tar.gz -O go.tar.gz +RUN tar -xzvf go.tar.gz -C /usr/local +ENV PATH=/go/bin:/usr/local/go/bin:$PATH + COPY ./ /usr/src/nym WORKDIR /usr/src/nym/nym-node-status-api/nym-node-status-api/ diff --git a/nym-node-status-api/nym-node-status-api/Makefile b/nym-node-status-api/nym-node-status-api/Makefile index 410e39661e..65e699d116 100644 --- a/nym-node-status-api/nym-node-status-api/Makefile +++ b/nym-node-status-api/nym-node-status-api/Makefile @@ -2,6 +2,8 @@ # --- Configuration --- TEST_DATABASE_URL := postgres://testuser:testpass@localhost:5433/nym_node_status_api_test +ENVIRONMENT ?= mainnet +MONOREPO_ROOT := $(shell realpath ../..) # Docker compose service names DB_SERVICE_NAME := postgres-test @@ -54,6 +56,20 @@ test-db-prepare: ## Run sqlx prepare for compile-time query verification @echo "Running sqlx prepare for PostgreSQL..." DATABASE_URL="$(TEST_DATABASE_URL)" cargo sqlx prepare +# --- Run Targets --- +.PHONY: run +run: ## Run the service (assumes database is already running) + @echo "Starting nym-node-status-api with $(ENVIRONMENT) environment..." + @set -a && source "$(MONOREPO_ROOT)/envs/$(ENVIRONMENT).env" && set +a && \ + DATABASE_URL="$(TEST_DATABASE_URL)" \ + NYM_API_CLIENT_TIMEOUT="$${NYM_API_CLIENT_TIMEOUT:-60}" \ + NODE_STATUS_API_AGENT_KEY_LIST="$${NODE_STATUS_API_AGENT_KEY_LIST:-H4z8kx5Kkf5JMQHhxaW1MwYndjKCDHC7HsVhHTFfBZ4J}" \ + RUST_LOG="$${RUST_LOG:-debug}" \ + cargo run --package nym-node-status-api + +.PHONY: run-with-db +run-with-db: dev-db run ## Start database and run the service + # --- Build and Test Targets --- .PHONY: test-db-run test-db-run: ## Run tests diff --git a/nym-node-status-api/nym-node-status-api/src/cli/mod.rs b/nym-node-status-api/nym-node-status-api/src/cli/mod.rs index 4883e95139..812f24dbf7 100644 --- a/nym-node-status-api/nym-node-status-api/src/cli/mod.rs +++ b/nym-node-status-api/nym-node-status-api/src/cli/mod.rs @@ -53,10 +53,19 @@ pub(crate) struct Cli { #[clap(long, env = "DATABASE_URL")] pub(crate) database_url: String, + /// SQLx pool acquire timeout in seconds (how long to wait for a connection) #[clap(long, default_value = "5", env = "SQLX_BUSY_TIMEOUT_S")] #[arg(value_parser = parse_duration_std)] pub(crate) sqlx_busy_timeout_s: Duration, + /// Maximum number of connections in the SQLx pool + #[clap(long, default_value_t = 20, env = "SQLX_MAX_CONNECTIONS")] + pub(crate) sqlx_max_connections: u32, + + /// Minimum number of connections to keep in the SQLx pool + #[clap(long, default_value_t = 5, env = "SQLX_MIN_CONNECTIONS")] + pub(crate) sqlx_min_connections: u32, + #[clap( long, default_value = "300", diff --git a/nym-node-status-api/nym-node-status-api/src/db/mod.rs b/nym-node-status-api/nym-node-status-api/src/db/mod.rs index 0205b4b64e..03fe202e3a 100644 --- a/nym-node-status-api/nym-node-status-api/src/db/mod.rs +++ b/nym-node-status-api/nym-node-status-api/src/db/mod.rs @@ -7,7 +7,11 @@ pub(crate) mod queries; #[cfg(test)] mod tests; -use sqlx::{ConnectOptions, PgPool, Postgres, migrate::Migrator, postgres::PgConnectOptions}; +use sqlx::{ + ConnectOptions, PgPool, Postgres, + migrate::Migrator, + postgres::{PgConnectOptions, PgPoolOptions}, +}; static MIGRATOR: Migrator = sqlx::migrate!("./migrations_pg"); @@ -21,7 +25,12 @@ pub(crate) struct Storage { } impl Storage { - pub async fn init(connection_url: String, _busy_timeout: Duration) -> Result { + pub async fn init( + connection_url: String, + acquire_timeout: Duration, + max_connections: u32, + min_connections: u32, + ) -> Result { use std::env; let mut connect_options = PgConnectOptions::from_str(&connection_url)?.disable_statement_logging(); @@ -33,7 +42,19 @@ impl Storage { .ssl_mode(sqlx::postgres::PgSslMode::Require) .ssl_root_cert(ssl_cert); } - let pool = sqlx::PgPool::connect_with(connect_options) + + tracing::info!( + "Initializing DB pool: max_connections={}, min_connections={}, acquire_timeout={}s", + max_connections, + min_connections, + acquire_timeout.as_secs() + ); + + let pool = PgPoolOptions::new() + .max_connections(max_connections) + .min_connections(min_connections) + .acquire_timeout(acquire_timeout) + .connect_with(connect_options) .await .map_err(|err| anyhow!("Failed to connect to {}: {}", &connection_url, err))?; diff --git a/nym-node-status-api/nym-node-status-api/src/db/models.rs b/nym-node-status-api/nym-node-status-api/src/db/models.rs index 18e84ba322..20c7e84d2c 100644 --- a/nym-node-status-api/nym-node-status-api/src/db/models.rs +++ b/nym-node-status-api/nym-node-status-api/src/db/models.rs @@ -8,9 +8,9 @@ use nym_crypto::asymmetric::ed25519::serde_helpers::bs58_ed25519_pubkey; use nym_crypto::asymmetric::x25519::serde_helpers::bs58_x25519_pubkey; use nym_crypto::asymmetric::{ed25519, x25519}; use nym_network_defaults::DEFAULT_NYM_NODE_HTTP_PORT; -use nym_node_requests::api::v1::node::models::{AuxiliaryDetails, NodeDescription}; +use nym_node_requests::api::v1::node::models::NodeDescription; use nym_validator_client::{ - client::NymNodeDetails, models::NymNodeDescription, nym_api::SkimmedNode, + client::NymNodeDetails, models::NymNodeDescriptionV1, nym_api::SkimmedNode, }; use serde::{Deserialize, Serialize}; use sqlx::FromRow; @@ -226,9 +226,11 @@ use mixnode::MixnodeSummary; use nym_bin_common::build_information::BinaryBuildInformationOwned; use nym_mixnet_contract_common::NodeId; use nym_validator_client::models::{ - AuthenticatorDetails, DeclaredRoles, DescribedNodeType, HostInformation, HostKeys, - IpPacketRouterDetails, LewesProtocolDetails, NetworkRequesterDetails, NymNodeData, - OffsetDateTimeJsonSchemaWrapper, SphinxKey, VersionedNoiseKey, WebSockets, WireguardDetails, + AuthenticatorDetailsV2, AuxiliaryDetailsV2, DeclaredRolesV2, DescribedNodeTypeV2, + HostInformationV2, HostKeysV2, IpPacketRouterDetailsV2, LewesProtocolDetailsV1, + NetworkRequesterDetailsV2, NymNodeDataV2, NymNodeDescriptionV2, + OffsetDateTimeJsonSchemaWrapper, SphinxKeyV1, VersionedNoiseKeyV1, WebSocketsV1, + WireguardDetailsV2, }; #[derive(Debug, Clone, Deserialize, Serialize, ToSchema)] @@ -462,7 +464,7 @@ impl NymNodeInsertRecord { pub fn new( skimmed_node: SkimmedNode, bond_info: Option<&NymNodeDetails>, - self_described: Option<&NymNodeDescription>, + self_described: Option<&NymNodeDescriptionV1>, ) -> anyhow::Result { let now = OffsetDateTime::now_utc().unix_timestamp(); @@ -553,33 +555,33 @@ pub struct InsertNodeScraperRecords { #[derive(Clone, Serialize, Deserialize, Debug)] pub struct NymNodeDescriptionDeHelper { pub node_id: NodeId, - pub contract_node_type: DescribedNodeType, + pub contract_node_type: DescribedNodeTypeV2, pub description: NymNodeDataDeHelper, } #[allow(deprecated)] -impl From for NymNodeDescription { +impl From for NymNodeDescriptionV2 { fn from(helper: NymNodeDescriptionDeHelper) -> Self { let current_x25519_sphinx_key = helper .description .host_information .keys .current_x25519_sphinx_key - .unwrap_or(SphinxKey { + .unwrap_or(SphinxKeyV1 { // indicate the legacy case rotation_id: u32::MAX, public_key: helper.description.host_information.keys.x25519, }); - NymNodeDescription { + NymNodeDescriptionV2 { node_id: helper.node_id, contract_node_type: helper.contract_node_type, - description: NymNodeData { + description: NymNodeDataV2 { last_polled: helper.description.last_polled, - host_information: HostInformation { + host_information: HostInformationV2 { ip_address: helper.description.host_information.ip_address, hostname: helper.description.host_information.hostname, - keys: HostKeys { + keys: HostKeysV2 { ed25519: helper.description.host_information.keys.ed25519, x25519: helper.description.host_information.keys.x25519, current_x25519_sphinx_key, @@ -609,6 +611,8 @@ impl From for NymNodeDescription { } } +// in this instance, since data is stored as json +// adding a new field with #[serde(default)] is fine #[derive(Clone, Serialize, Deserialize, Debug)] pub struct NymNodeDataDeHelper { #[serde(default)] @@ -617,31 +621,31 @@ pub struct NymNodeDataDeHelper { pub host_information: HostInformationDeHelper, #[serde(default)] - pub declared_role: DeclaredRoles, + pub declared_role: DeclaredRolesV2, #[serde(default)] - pub auxiliary_details: AuxiliaryDetails, + pub auxiliary_details: AuxiliaryDetailsV2, // TODO: do we really care about ALL build info or just the version? pub build_information: BinaryBuildInformationOwned, #[serde(default)] - pub network_requester: Option, + pub network_requester: Option, #[serde(default)] - pub ip_packet_router: Option, + pub ip_packet_router: Option, #[serde(default)] - pub authenticator: Option, + pub authenticator: Option, #[serde(default)] - pub wireguard: Option, + pub wireguard: Option, #[serde(default)] - pub lewes_protocol: Option, + pub lewes_protocol: Option, // for now we only care about their ws/wss situation, nothing more - pub mixnet_websockets: WebSockets, + pub mixnet_websockets: WebSocketsV1, } #[derive(Clone, Serialize, Deserialize, Debug)] @@ -661,11 +665,11 @@ pub struct HostKeysDeHelper { pub x25519: x25519::PublicKey, // legacy data will NOT have this information - pub current_x25519_sphinx_key: Option, + pub current_x25519_sphinx_key: Option, #[serde(default)] - pub pre_announced_x25519_sphinx_key: Option, + pub pre_announced_x25519_sphinx_key: Option, #[serde(default)] - pub x25519_versioned_noise: Option, + pub x25519_versioned_noise: Option, } diff --git a/nym-node-status-api/nym-node-status-api/src/db/queries/nym_nodes.rs b/nym-node-status-api/nym-node-status-api/src/db/queries/nym_nodes.rs index ae53e041c2..969fd087f6 100644 --- a/nym-node-status-api/nym-node-status-api/src/db/queries/nym_nodes.rs +++ b/nym-node-status-api/nym-node-status-api/src/db/queries/nym_nodes.rs @@ -1,14 +1,5 @@ -use crate::db::models::NymNodeDescriptionDeHelper; -use futures_util::TryStreamExt; -use nym_node_requests::api::v1::node::models::NodeDescription; -use nym_validator_client::{ - client::{NodeId, NymNodeDetails}, - models::NymNodeDescription, -}; -use std::collections::HashMap; -use tracing::{instrument, warn}; - use crate::db::DbConnection; +use crate::db::models::NymNodeDescriptionDeHelper; use crate::http::models::DailyStats; use crate::{ db::{ @@ -17,6 +8,12 @@ use crate::{ }, node_scraper::helpers::NodeDescriptionResponse, }; +use futures_util::TryStreamExt; +use nym_node_requests::api::v1::node::models::NodeDescription; +use nym_validator_client::client::{NodeId, NymNodeDetails}; +use nym_validator_client::models::NymNodeDescriptionV2; +use std::collections::HashMap; +use tracing::{instrument, warn}; pub(crate) async fn get_all_nym_nodes(pool: &DbPool) -> anyhow::Result> { let mut conn = pool.acquire().await?; @@ -195,7 +192,7 @@ pub(crate) async fn get_described_node_bond_info( pub(crate) async fn get_node_self_description( pool: &DbPool, -) -> anyhow::Result> { +) -> anyhow::Result> { let mut conn = pool.acquire().await?; sqlx::query!( diff --git a/nym-node-status-api/nym-node-status-api/src/db/tests.rs b/nym-node-status-api/nym-node-status-api/src/db/tests.rs index a80bdc1bdd..e1f444301c 100644 --- a/nym-node-status-api/nym-node-status-api/src/db/tests.rs +++ b/nym-node-status-api/nym-node-status-api/src/db/tests.rs @@ -128,7 +128,7 @@ mod db_tests { node_role: serde_json::json!(nym_validator_client::nym_nodes::NodeRole::Mixnode { layer: 1 }), - supported_roles: serde_json::json!(nym_validator_client::models::DeclaredRoles { + supported_roles: serde_json::json!(nym_validator_client::models::DeclaredRolesV1 { entry: false, mixnode: true, exit_nr: false, @@ -181,7 +181,7 @@ fn test_nym_node_insert_record_new() { mix_port: 1789, x25519_sphinx_pubkey: x25519_pk, role: nym_validator_client::nym_nodes::NodeRole::Mixnode { layer: 1 }, - supported_roles: nym_validator_client::models::DeclaredRoles { + supported_roles: nym_validator_client::models::DeclaredRolesV1 { entry: false, mixnode: true, exit_nr: false, @@ -208,7 +208,7 @@ fn test_nym_node_insert_record_new() { ); assert_eq!( record.supported_roles, - serde_json::json!(nym_validator_client::models::DeclaredRoles { + serde_json::json!(nym_validator_client::models::DeclaredRolesV1 { entry: false, mixnode: true, exit_nr: false, @@ -233,7 +233,7 @@ fn test_nym_node_insert_record_with_entry() { mix_port: 1789, x25519_sphinx_pubkey: x25519_pk, role: nym_validator_client::nym_nodes::NodeRole::EntryGateway, - supported_roles: nym_validator_client::models::DeclaredRoles { + supported_roles: nym_validator_client::models::DeclaredRolesV1 { entry: true, mixnode: false, exit_nr: true, @@ -425,7 +425,7 @@ fn test_nym_node_dto_with_invalid_keys() { node_role: serde_json::json!(nym_validator_client::nym_nodes::NodeRole::Mixnode { layer: 1 }), - supported_roles: serde_json::json!(nym_validator_client::models::DeclaredRoles { + supported_roles: serde_json::json!(nym_validator_client::models::DeclaredRolesV1 { entry: false, mixnode: true, exit_nr: false, @@ -463,7 +463,7 @@ fn test_nym_node_dto_with_invalid_performance() { node_role: serde_json::json!(nym_validator_client::nym_nodes::NodeRole::Mixnode { layer: 1 }), - supported_roles: serde_json::json!(nym_validator_client::models::DeclaredRoles { + supported_roles: serde_json::json!(nym_validator_client::models::DeclaredRolesV1 { entry: false, mixnode: true, exit_nr: false, diff --git a/nym-node-status-api/nym-node-status-api/src/http/models/gw_probe/mod.rs b/nym-node-status-api/nym-node-status-api/src/http/models/gw_probe/mod.rs new file mode 100644 index 0000000000..dbce0bdce6 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/src/http/models/gw_probe/mod.rs @@ -0,0 +1,447 @@ +use serde::Deserialize; +use serde::Serialize; +use strum::EnumString; +use tracing::error; +use utoipa::ToSchema; + +pub(crate) mod socks5_calc; +#[cfg(test)] +mod test; + +#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)] +pub struct LastProbeResult { + node: String, + used_entry: String, + outcome: ProbeOutcome, +} + +use nym_gateway_probe::types::ProbeResult as ProbeResultLatest; + +impl From for LastProbeResult { + fn from(value: ProbeResultLatest) -> Self { + Self { + node: value.node, + used_entry: value.used_entry, + outcome: value.outcome.into(), + } + } +} + +impl LastProbeResult { + pub(crate) fn deserialize_with_fallback(value: serde_json::Value) -> anyhow::Result { + // first try matching latest struct from GW probe crate + let mut probe_result = match serde_json::from_value::(value.clone()) { + Ok(probe_result) => probe_result.into(), + // as a fallback, try parsing struct from this crate + Err(_) => match serde_json::from_value::(value) { + Ok(probe_result) => probe_result, + Err(e) => { + error!("Failed to deserialize probe result: {e}"); + return Err(e.into()); + } + }, + }; + + probe_result.outcome.wg = probe_result.outcome.wg.clone().map(|mut wg| { + if wg.can_handshake.is_none() { + wg.can_handshake = Some(wg.can_handshake_v4); + } + if wg.can_resolve_dns.is_none() { + wg.can_resolve_dns = Some(wg.can_resolve_dns_v4); + } + if wg.ping_hosts_performance.is_none() { + wg.ping_hosts_performance = Some(wg.ping_hosts_performance_v4); + } + if wg.ping_ips_performance.is_none() { + wg.ping_ips_performance = Some(wg.ping_ips_performance_v4); + } + wg + }); + + Ok(probe_result) + } + + pub(crate) fn outcome(self) -> ProbeOutcome { + self.outcome + } +} + +/// gateway probe output returned on the API +#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)] +pub struct DvpnGwProbe { + last_updated_utc: String, + outcome: DvpnProbeOutcome, +} + +impl DvpnGwProbe { + pub fn from_outcome(outcome: DvpnProbeOutcome, last_updated_utc: String) -> Self { + Self { + last_updated_utc, + outcome, + } + } + + pub fn can_route_entry(&self) -> bool { + match &self.outcome.as_entry { + Entry::Tested(entry_test_result) => entry_test_result.can_route, + Entry::NotTested | Entry::EntryFailure => false, + } + } + + pub fn can_route_exit(&self) -> Option { + self.outcome + .as_exit + .as_ref() + .map(|outcome| outcome.can_route_ip_external_v4 && outcome.can_route_ip_external_v6) + } +} + +/// this structure is parsed on VPN API so it has some fields which must not be changed +#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)] +pub struct DvpnProbeOutcome { + pub as_entry: Entry, + pub as_exit: Option, + pub wg: Option, + pub socks5: Option, +} + +impl DvpnProbeOutcome { + pub fn from_raw_probe_outcome(outcome: ProbeOutcome, score: ScoreValue) -> Self { + let errors = outcome + .socks5 + .clone() + .and_then(|s| s.https_connectivity.errors); + let can_proxy_https = outcome + .socks5 + .map(|s| s.https_connectivity.https_success) + .unwrap_or_else(|| match score { + ScoreValue::Offline => false, + ScoreValue::Low | ScoreValue::Medium | ScoreValue::High => true, + }); + Self { + as_entry: outcome.as_entry.clone(), + as_exit: outcome.as_exit.clone(), + wg: outcome.wg.clone(), + socks5: Some(Socks5 { + can_proxy_https, + score, + errors, + }), + } + } +} + +#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)] +pub struct ProbeOutcome { + pub as_entry: Entry, + pub as_exit: Option, + pub wg: Option, + pub socks5: Option, +} + +use nym_gateway_probe::types::ProbeOutcome as ProbeOutcomeLatest; + +impl From for ProbeOutcome { + fn from(value: ProbeOutcomeLatest) -> Self { + Self { + as_entry: value.as_entry.into(), + as_exit: value.as_exit.map(From::from), + wg: value.wg.map(From::from), + socks5: value.socks5.map(From::from), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] +#[serde(untagged)] +#[allow(clippy::enum_variant_names)] +pub enum Entry { + Tested(EntryTestResult), + NotTested, + EntryFailure, +} + +use nym_gateway_probe::types::Entry as EntryLatest; + +impl From for Entry { + fn from(value: EntryLatest) -> Self { + match value { + EntryLatest::Tested(entry_test_result) => Self::Tested(entry_test_result.into()), + EntryLatest::NotTested => Self::NotTested, + EntryLatest::EntryFailure => Self::EntryFailure, + } + } +} + +use nym_gateway_probe::types::EntryTestResult as EntryTestResultLatest; + +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] +pub struct EntryTestResult { + pub can_connect: bool, + pub can_route: bool, +} + +impl From for EntryTestResult { + fn from(value: EntryTestResultLatest) -> Self { + Self { + can_connect: value.can_connect, + can_route: value.can_route, + } + } +} + +#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)] +pub struct Exit { + pub can_connect: bool, + pub can_route_ip_v4: bool, + pub can_route_ip_external_v4: bool, + pub can_route_ip_v6: bool, + pub can_route_ip_external_v6: bool, +} + +use nym_gateway_probe::types::Exit as ExitLatest; + +impl From for Exit { + fn from(value: ExitLatest) -> Self { + Self { + can_connect: value.can_connect, + can_route_ip_v4: value.can_route_ip_v4, + can_route_ip_external_v4: value.can_route_ip_external_v4, + can_route_ip_v6: value.can_route_ip_v6, + can_route_ip_external_v6: value.can_route_ip_external_v6, + } + } +} + +#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)] +pub struct WgProbeResults { + // mandatory fields + pub can_register: bool, + pub can_handshake: Option, + pub can_resolve_dns: Option, + pub ping_hosts_performance: Option, + pub ping_ips_performance: Option, + + pub can_query_metadata_v4: Option, + pub can_handshake_v4: bool, + pub can_resolve_dns_v4: bool, + pub ping_hosts_performance_v4: f32, + pub ping_ips_performance_v4: f32, + + pub can_handshake_v6: bool, + pub can_resolve_dns_v6: bool, + pub ping_hosts_performance_v6: f32, + pub ping_ips_performance_v6: f32, + + pub download_duration_sec_v4: u64, + pub download_duration_milliseconds_v4: Option, + pub downloaded_file_size_bytes_v4: Option, + pub downloaded_file_v4: String, + pub download_error_v4: String, + + pub download_duration_sec_v6: u64, + pub download_duration_milliseconds_v6: Option, + pub downloaded_file_size_bytes_v6: Option, + pub downloaded_file_v6: String, + pub download_error_v6: String, +} + +use nym_gateway_probe::types::WgProbeResults as WgProbeResultsLatest; + +use crate::http::models::Gateway; + +impl From for WgProbeResults { + fn from(value: WgProbeResultsLatest) -> Self { + Self { + can_register: value.can_register, + can_handshake: Some(value.can_handshake_v4), + can_resolve_dns: Some(value.can_resolve_dns_v4), + ping_hosts_performance: Some(value.ping_hosts_performance_v4), + ping_ips_performance: Some(value.ping_ips_performance_v4), + + can_query_metadata_v4: Some(value.can_query_metadata_v4), + can_handshake_v4: value.can_handshake_v4, + can_resolve_dns_v4: value.can_resolve_dns_v4, + ping_hosts_performance_v4: value.ping_hosts_performance_v4, + ping_ips_performance_v4: value.ping_ips_performance_v4, + + can_handshake_v6: value.can_handshake_v6, + can_resolve_dns_v6: value.can_resolve_dns_v6, + ping_hosts_performance_v6: value.ping_hosts_performance_v6, + ping_ips_performance_v6: value.ping_ips_performance_v6, + + download_duration_sec_v4: value.download_duration_sec_v4, + download_duration_milliseconds_v4: Some(value.download_duration_milliseconds_v4), + downloaded_file_size_bytes_v4: Some(value.downloaded_file_size_bytes_v4), + downloaded_file_v4: value.downloaded_file_v4, + download_error_v4: value.download_error_v4, + + download_duration_sec_v6: value.download_duration_sec_v6, + download_duration_milliseconds_v6: Some(value.download_duration_milliseconds_v6), + downloaded_file_size_bytes_v6: Some(value.downloaded_file_size_bytes_v6), + downloaded_file_v6: value.downloaded_file_v6, + download_error_v6: value.download_error_v6, + } + } +} + +struct NodeScore { + download_speed_score: f64, + ping_ips_score: f64, + mixnet_performance: f64, +} + +impl NodeScore { + // Weighted scoring: mixnet performance (40%), download speed (30%), ping performance (30%) + fn calculate_weighted_score(&self) -> f64 { + (self.mixnet_performance * 0.4) + + (self.download_speed_score * 0.3) + + (self.ping_ips_score * 0.3) + } +} + +#[derive(Debug, Clone, Deserialize, Serialize, ToSchema, EnumString)] +#[serde(rename_all = "snake_case")] +#[strum(serialize_all = "snake_case")] +#[derive(PartialEq)] +pub enum ScoreValue { + Offline, + Low, + Medium, + High, +} + +/// calculates a visual score for the gateway using weighted metrics +pub(super) fn calculate_score(gateway: &Gateway, probe_outcome: &LastProbeResult) -> ScoreValue { + let mixnet_performance = gateway.performance as f64 / 100.0; + + let node_score = probe_outcome + .outcome + .wg + .as_ref() + .map(|p| { + let ping_ips_performance = p.ping_ips_performance_v4 as f64; + + let duration_sec = + p.download_duration_milliseconds_v4 + .unwrap_or_else(|| p.download_duration_sec_v4 * 1000) as f64 + / 1000f64; + + // get the file size downloaded in bytes and convert to MB, or default to 1MB + let file_size_mb = + p.downloaded_file_size_bytes_v4.unwrap_or(1048576) as f64 / 1024f64 / 1024f64; + let speed_mbps = file_size_mb / duration_sec; + + let file_download_score = if speed_mbps > 5.0 { + 1.0 + } else if speed_mbps > 2.0 { + 0.75 + } else if speed_mbps > 1.0 { + 0.5 + } else if speed_mbps > 0.5 { + 0.25 + } else { + 0.1 + }; + + NodeScore { + download_speed_score: file_download_score, + ping_ips_score: ping_ips_performance, + mixnet_performance, + } + }) + .unwrap_or(NodeScore { + download_speed_score: 0.0, + ping_ips_score: 0.0, + mixnet_performance, + }); + + let weighted_score = node_score.calculate_weighted_score(); + + if weighted_score > 0.75 { + ScoreValue::High + } else if weighted_score > 0.5 { + ScoreValue::Medium + } else if weighted_score > 0.1 { + ScoreValue::Low + } else { + ScoreValue::Offline + } +} + +/// calculates a visual load score for the gateway +pub(super) fn calculate_load(probe_outcome: &LastProbeResult) -> ScoreValue { + let score = probe_outcome + .outcome + .wg + .clone() + .map(|p| p.ping_ips_performance_v4 as f64) + .unwrap_or(0f64); + + if score > 0.8 { + ScoreValue::Low + } else if score > 0.4 { + ScoreValue::Medium + } else { + ScoreValue::High + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] +pub struct Socks5 { + pub can_proxy_https: bool, + pub score: ScoreValue, + pub errors: Option>, +} + +use nym_gateway_probe::types::Socks5ProbeResults as Socks5ProbeResultsLatest; + +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] +pub struct Socks5ProbeResults { + /// whether we could establish a SOCKS5 proxy connection + pub can_connect_socks5: bool, + + /// HTTPS connectivity test + pub https_connectivity: HttpsConnectivityResult, +} + +impl From for Socks5ProbeResults { + fn from(value: Socks5ProbeResultsLatest) -> Self { + Self { + can_connect_socks5: value.can_connect_socks5(), + https_connectivity: value.https_connectivity().clone().into(), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] +pub struct HttpsConnectivityResult { + /// successfully completed HTTPS request + https_success: bool, + + /// HTTPS status code received + https_status_code: Option, + + /// average HTTPS request latency in milliseconds + https_latency_ms: Option, + + /// among multiple endpoints available, list the one actually used + endpoint_used: Option, + + /// error message(s) (if any) + errors: Option>, +} + +use nym_gateway_probe::types::HttpsConnectivityResult as HttpsConnectivityResultLatest; + +impl From for HttpsConnectivityResult { + fn from(value: HttpsConnectivityResultLatest) -> Self { + Self { + https_success: value.https_success(), + https_status_code: value.https_status_code().cloned(), + https_latency_ms: value.https_latency_ms().cloned(), + endpoint_used: value.endpoint_used().cloned(), + errors: value.errors().cloned(), + } + } +} diff --git a/nym-node-status-api/nym-node-status-api/src/http/models/gw_probe/socks5_calc.rs b/nym-node-status-api/nym-node-status-api/src/http/models/gw_probe/socks5_calc.rs new file mode 100644 index 0000000000..beb31a4ca7 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/src/http/models/gw_probe/socks5_calc.rs @@ -0,0 +1,289 @@ +use std::collections::HashMap; + +use tracing::warn; + +use crate::http::models::{ + Gateway, + gw_probe::{LastProbeResult, ScoreValue}, +}; + +pub(crate) fn calculate_socks5_percentiles(gateways: &[Gateway]) -> HashMap { + let parsed_gateways = gateways + .iter() + // discard untested gateways + .filter_map(|gw| { + gw.last_probe_result + .as_ref() + .map(|res| (gw.gateway_identity_key.clone(), res)) + }) + // discard unparsable probe results (error) + .filter_map(|(id, value)| { + LastProbeResult::deserialize_with_fallback(value.to_owned()) + .inspect_err(|err| warn!("Failed to deserialize probe result: {err}")) + .ok() + .map(|parsed| (id, parsed)) + }) + .map(|(id, res)| { + let latency = res + .outcome + .socks5 + // if socks5 is null, test failed or gw doesn't support it + .and_then(|socks5| socks5.https_connectivity.https_latency_ms) + .unwrap_or(0); + + (id, latency) + }) + .collect::>(); + + score_from_sorted_latencies(parsed_gateways) +} + +/// Assigns a score to each gateway based on their relative latency compared to +/// the whole set. Higher score = lower latency. +/// +/// - latency == 0 => Offline +/// - nonzero buckets: +/// - High = lowest 50% +/// - Medium = next 25% +/// - Low = worst 25% +pub fn score_from_sorted_latencies(gateways: Vec<(String, u64)>) -> HashMap { + // sort ascending + let mut gateways = gateways; + gateways.sort_by_cached_key(|(_, latency)| *latency); + + let (offline_gws, online_gws): (Vec<_>, Vec<_>) = + gateways.into_iter().partition(|(_, latency)| *latency == 0); + + let online_count = online_gws.len(); + + // x / 2 = 0.5x + let high_end_idx = online_count.div_ceil(2); + // 3x / 4 = 0.75x + let medium_end_idx = online_count.saturating_mul(3).div_ceil(4); + // `Low` gets assigned to everything else + + let mut result = HashMap::new(); + + // first flag all the offline gateways + for (id, _lat) in offline_gws { + result.entry(id).or_insert(ScoreValue::Offline); + } + + // secondly go over remaining non-zero elements, assign into buckets + for (idx, (id, _)) in online_gws.into_iter().enumerate() { + let score = if idx < high_end_idx { + ScoreValue::High + } else if idx < medium_end_idx { + ScoreValue::Medium + } else { + ScoreValue::Low + }; + + result.entry(id).or_insert(score); + } + + result +} + +#[cfg(test)] +mod socks5_score_calc_tests { + // clippy complains despite imports being used + #[allow(unused_imports)] + use super::*; + + #[test] + fn empty_input() { + let result = score_from_sorted_latencies(vec![]); + assert!(result.is_empty()); + } + + #[test] + fn all_offline() { + let items = vec![ + ("a".to_string(), 0), + ("b".to_string(), 0), + ("c".to_string(), 0), + ]; + let result = score_from_sorted_latencies(items); + + assert_eq!(result.len(), 3); + assert_eq!(result.get("a"), Some(&ScoreValue::Offline)); + assert_eq!(result.get("b"), Some(&ScoreValue::Offline)); + assert_eq!(result.get("c"), Some(&ScoreValue::Offline)); + } + + #[test] + fn single_zero() { + let items = vec![("a".to_string(), 0)]; + let result = score_from_sorted_latencies(items); + + assert_eq!(result.len(), 1); + assert_eq!(result.get("a"), Some(&ScoreValue::Offline)); + } + + #[test] + fn single_nonzero() { + // Single non-zero element: lowest 50% of 1 = ceil(1/2) = 1, so it's High + let items = vec![("a".to_string(), 100)]; + let result = score_from_sorted_latencies(items); + + assert_eq!(result.len(), 1); + assert_eq!(result.get("a"), Some(&ScoreValue::High)); + } + + #[test] + fn two_nonzero_elements() { + // 2 non-zero: high_end = ceil(2/2) = 1, medium_end = ceil(6/4) = 2 + // idx 0 -> High, idx 1 -> Medium + let items = vec![("a".to_string(), 100), ("b".to_string(), 200)]; + let result = score_from_sorted_latencies(items); + + assert_eq!(result.len(), 2); + assert_eq!(result.get("a"), Some(&ScoreValue::High)); + assert_eq!(result.get("b"), Some(&ScoreValue::Medium)); + } + + #[test] + fn mix_zeros_and_nonzeros() { + // Zeros become Offline, non-zeros get percentile scores + let items = vec![ + ("offline1".to_string(), 0), + ("fast".to_string(), 50), + ("offline2".to_string(), 0), + ("slow".to_string(), 200), + ]; + let result = score_from_sorted_latencies(items); + + assert_eq!(result.len(), 4); + assert_eq!(result.get("offline1"), Some(&ScoreValue::Offline)); + assert_eq!(result.get("offline2"), Some(&ScoreValue::Offline)); + // 2 non-zero: high_end = 1, medium_end = 2 + assert_eq!(result.get("fast"), Some(&ScoreValue::High)); + assert_eq!(result.get("slow"), Some(&ScoreValue::Medium)); + } + + #[test] + fn unsorted_input() { + // Input is unsorted, function should work regardless + let items = vec![ + ("slow".to_string(), 300), + ("fast".to_string(), 100), + ("medium".to_string(), 200), + ]; + let result = score_from_sorted_latencies(items); + + // 3 non-zero: high_end = ceil(3/2) = 2, medium_end = ceil(9/4) = 3 + // sorted: fast(100), medium(200), slow(300) + // idx 0,1 -> High, idx 2 -> Medium + assert_eq!(result.get("fast"), Some(&ScoreValue::High)); + assert_eq!(result.get("medium"), Some(&ScoreValue::High)); + assert_eq!(result.get("slow"), Some(&ScoreValue::Medium)); + } + + #[test] + fn duplicate_ids_keeps_first() { + // Duplicate IDs: first occurrence is kept + let items = vec![ + ("dup".to_string(), 100), // first occurrence, fast + ("other".to_string(), 200), + ("dup".to_string(), 300), // duplicate, slow - should be ignored + ]; + let result = score_from_sorted_latencies(items); + + // 3 non-zero: high_end = 2, medium_end = 3 + // sorted: dup(100), other(200), dup(300) + // idx 0 -> High (dup first), idx 1 -> High (other), idx 2 -> Medium (dup second, ignored) + assert_eq!(result.len(), 2); + assert_eq!(result.get("dup"), Some(&ScoreValue::High)); + assert_eq!(result.get("other"), Some(&ScoreValue::High)); + } + + #[test] + fn four_nonzero_clean_percentiles() { + // 4 non-zero: high_end = ceil(4/2) = 2, medium_end = ceil(12/4) = 3 + // idx 0,1 -> High (50%), idx 2 -> Medium (25%), idx 3 -> Low (25%) + let items = vec![ + ("a".to_string(), 100), + ("b".to_string(), 200), + ("c".to_string(), 300), + ("d".to_string(), 400), + ]; + let result = score_from_sorted_latencies(items); + + assert_eq!(result.len(), 4); + assert_eq!(result.get("a"), Some(&ScoreValue::High)); + assert_eq!(result.get("b"), Some(&ScoreValue::High)); + assert_eq!(result.get("c"), Some(&ScoreValue::Medium)); + assert_eq!(result.get("d"), Some(&ScoreValue::Low)); + } + + #[test] + fn eight_nonzero_clean_percentiles() { + // 8 non-zero: high_end = ceil(8/2) = 4, medium_end = ceil(24/4) = 6 + // idx 0-3 -> High, idx 4-5 -> Medium, idx 6-7 -> Low + let items: Vec<(String, u64)> = (1..=8) + .map(|i| (format!("node{}", i), i as u64 * 100)) + .collect(); + let result = score_from_sorted_latencies(items); + + assert_eq!(result.len(), 8); + assert_eq!(result.get("node1"), Some(&ScoreValue::High)); + assert_eq!(result.get("node2"), Some(&ScoreValue::High)); + assert_eq!(result.get("node3"), Some(&ScoreValue::High)); + assert_eq!(result.get("node4"), Some(&ScoreValue::High)); + assert_eq!(result.get("node5"), Some(&ScoreValue::Medium)); + assert_eq!(result.get("node6"), Some(&ScoreValue::Medium)); + assert_eq!(result.get("node7"), Some(&ScoreValue::Low)); + assert_eq!(result.get("node8"), Some(&ScoreValue::Low)); + } + + #[test] + fn five_nonzero_ceiling_division() { + // 5 non-zero: high_end = ceil(5/2) = 3, medium_end = ceil(15/4) = 4 + // idx 0,1,2 -> High (3), idx 3 -> Medium (1), idx 4 -> Low (1) + let items: Vec<(String, u64)> = (1..=5) + .map(|i| (format!("n{}", i), i as u64 * 10)) + .collect(); + let result = score_from_sorted_latencies(items); + + assert_eq!(result.get("n1"), Some(&ScoreValue::High)); + assert_eq!(result.get("n2"), Some(&ScoreValue::High)); + assert_eq!(result.get("n3"), Some(&ScoreValue::High)); + assert_eq!(result.get("n4"), Some(&ScoreValue::Medium)); + assert_eq!(result.get("n5"), Some(&ScoreValue::Low)); + } + + #[test] + fn seven_nonzero_ceiling_division() { + // 7 non-zero: high_end = ceil(7/2) = 4, medium_end = ceil(21/4) = 6 + // idx 0-3 -> High (4), idx 4-5 -> Medium (2), idx 6 -> Low (1) + let items: Vec<(String, u64)> = (1..=7) + .map(|i| (format!("n{}", i), i as u64 * 10)) + .collect(); + let result = score_from_sorted_latencies(items); + + assert_eq!(result.get("n1"), Some(&ScoreValue::High)); + assert_eq!(result.get("n2"), Some(&ScoreValue::High)); + assert_eq!(result.get("n3"), Some(&ScoreValue::High)); + assert_eq!(result.get("n4"), Some(&ScoreValue::High)); + assert_eq!(result.get("n5"), Some(&ScoreValue::Medium)); + assert_eq!(result.get("n6"), Some(&ScoreValue::Medium)); + assert_eq!(result.get("n7"), Some(&ScoreValue::Low)); + } + + #[test] + fn three_nonzero() { + // 3 non-zero: high_end = ceil(3/2) = 2, medium_end = ceil(9/4) = 3 + // idx 0,1 -> High, idx 2 -> Medium (no Low bucket) + let items = vec![ + ("a".to_string(), 100), + ("b".to_string(), 200), + ("c".to_string(), 300), + ]; + let result = score_from_sorted_latencies(items); + + assert_eq!(result.get("a"), Some(&ScoreValue::High)); + assert_eq!(result.get("b"), Some(&ScoreValue::High)); + assert_eq!(result.get("c"), Some(&ScoreValue::Medium)); + } +} diff --git a/nym-node-status-api/nym-node-status-api/src/http/models/gw_probe/test.rs b/nym-node-status-api/nym-node-status-api/src/http/models/gw_probe/test.rs new file mode 100644 index 0000000000..66bae5605b --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/src/http/models/gw_probe/test.rs @@ -0,0 +1,533 @@ +use crate::http::models::Gateway; + +use super::*; + +#[test] +fn test_weighted_score_calculation() { + // Helper function to create a test gateway + fn create_test_gateway(performance: u8) -> Gateway { + Gateway { + gateway_identity_key: "test_key".to_string(), + bonded: true, + performance, + self_described: None, + explorer_pretty_bond: None, + description: nym_node_requests::api::v1::node::models::NodeDescription { + moniker: "test".to_string(), + details: "test".to_string(), + security_contact: "test@example.com".to_string(), + website: "https://example.com".to_string(), + }, + last_probe_result: None, + last_probe_log: None, + last_testrun_utc: None, + last_updated_utc: "2025-10-10T00:00:00Z".to_string(), + routing_score: 0.0, + config_score: 0, + bridges: None, + } + } + + // Helper function to create a test probe outcome + fn create_test_probe_outcome( + download_speed_mbps: f64, + ping_ips_performance: f32, + ) -> LastProbeResult { + let duration_sec = 1.0; + let file_size_mb = download_speed_mbps; + + LastProbeResult { + node: "test_node".to_string(), + used_entry: "test_entry".to_string(), + outcome: ProbeOutcome { + as_entry: Entry::Tested(EntryTestResult { + can_connect: true, + can_route: true, + }), + as_exit: None, + wg: Some(WgProbeResults { + can_register: true, + can_handshake: Some(true), + can_resolve_dns: Some(true), + ping_hosts_performance: Some(ping_ips_performance), + ping_ips_performance: Some(ping_ips_performance), + can_query_metadata_v4: Some(true), + can_handshake_v4: true, + can_resolve_dns_v4: true, + ping_hosts_performance_v4: ping_ips_performance, + ping_ips_performance_v4: ping_ips_performance, + can_handshake_v6: true, + can_resolve_dns_v6: true, + ping_hosts_performance_v6: ping_ips_performance, + ping_ips_performance_v6: ping_ips_performance, + download_duration_sec_v4: (duration_sec * 1000.0) as u64, + download_duration_milliseconds_v4: Some((duration_sec * 1000.0) as u64), + downloaded_file_size_bytes_v4: Some((file_size_mb * 1024.0 * 1024.0) as u64), + downloaded_file_v4: "test".to_string(), + download_error_v4: "".to_string(), + download_duration_sec_v6: 0, + download_duration_milliseconds_v6: Some(0), + downloaded_file_size_bytes_v6: Some(0), + downloaded_file_v6: "".to_string(), + download_error_v6: "".to_string(), + }), + // TODO dz test this as well + socks5: None, + }, + } + } + + // Test case 1: Excellent node (should be High) + let gateway = create_test_gateway(90); // 90% mixnet performance + let probe = create_test_probe_outcome(6.0, 1.0); // 6 Mbps, 100% ping + let score = calculate_score(&gateway, &probe); + assert_eq!(score, ScoreValue::High, "Excellent node should be High"); + + // Test case 2: Good node (should be High with weighted scoring) + let gateway = create_test_gateway(90); // 90% mixnet performance + let probe = create_test_probe_outcome(3.0, 0.9); // 3 Mbps (0.75 score), 90% ping + let score = calculate_score(&gateway, &probe); + assert_eq!( + score, + ScoreValue::High, + "Good node should be High with weighted scoring" + ); + + // Test case 3: Medium node + let gateway = create_test_gateway(80); // 80% mixnet performance + let probe = create_test_probe_outcome(1.5, 0.8); // 1.5 Mbps (0.5 score), 80% ping + let score = calculate_score(&gateway, &probe); + assert_eq!(score, ScoreValue::Medium, "Medium node should be Medium"); + + // Test case 4: Poor node + let gateway = create_test_gateway(60); // 60% mixnet performance + let probe = create_test_probe_outcome(0.3, 0.3); // 0.3 Mbps (0.1 score), 30% ping + let score = calculate_score(&gateway, &probe); + assert_eq!(score, ScoreValue::Low, "Poor node should be Low"); + + // Test case 5: Failed node + let gateway = create_test_gateway(10); // 10% mixnet performance + let probe = create_test_probe_outcome(0.1, 0.0); // 0.1 Mbps (0.1 score), 0% ping + let score = calculate_score(&gateway, &probe); + assert_eq!(score, ScoreValue::Offline, "Failed node should be Offline"); + + // Test case 6: Edge case - just above threshold + let gateway = create_test_gateway(76); // 76% mixnet performance + let probe = create_test_probe_outcome(2.1, 0.75); // 2.1 Mbps (0.75 score), 75% ping + let score = calculate_score(&gateway, &probe); + // Weighted: (0.76 * 0.4) + (0.75 * 0.3) + (0.75 * 0.3) = 0.304 + 0.225 + 0.225 = 0.754 + assert_eq!( + score, + ScoreValue::High, + "Edge case just above 0.75 threshold should be High" + ); +} + +/// Smoke test to ensure conversion from nym_gateway_probe crate's ProbeResult +/// to this crate's ProbeResult doesn't silently drop or default fields when +/// nym_gateway_probe types change. +/// +/// All values are set to non-default (booleans=true, numbers non-zero, strings non-empty) +/// to catch cases where new fields might be left as default after conversion. +#[test] +fn conversion_from_gw_probe_latest() { + use nym_gateway_probe::types::{ + Entry as EntryLatest, EntryTestResult as EntryTestResultLatest, Exit as ExitLatest, + ProbeOutcome as ProbeOutcomeLatest, ProbeResult as ProbeResultLatest, + WgProbeResults as WgProbeResultsLatest, + }; + + // Build a ProbeResultLatest with ALL non-default values + let wg_latest = WgProbeResultsLatest { + can_register: true, + can_query_metadata_v4: true, + can_handshake_v4: true, + can_resolve_dns_v4: true, + ping_hosts_performance_v4: 0.95, + ping_ips_performance_v4: 0.92, + can_handshake_v6: true, + can_resolve_dns_v6: true, + ping_hosts_performance_v6: 0.88, + ping_ips_performance_v6: 0.85, + download_duration_sec_v4: 5, + download_duration_milliseconds_v4: 5123, + downloaded_file_size_bytes_v4: 10485760, + downloaded_file_v4: "test-file-v4.bin".to_string(), + download_error_v4: "none-v4".to_string(), + download_duration_sec_v6: 6, + download_duration_milliseconds_v6: 6234, + downloaded_file_size_bytes_v6: 20971520, + downloaded_file_v6: "test-file-v6.bin".to_string(), + download_error_v6: "none-v6".to_string(), + }; + let probe_latest = ProbeResultLatest { + node: "test-node-identity-key".to_string(), + used_entry: "test-entry-node".to_string(), + outcome: ProbeOutcomeLatest { + as_entry: EntryLatest::Tested(EntryTestResultLatest { + can_connect: true, + can_route: true, + }), + as_exit: Some(ExitLatest { + can_connect: true, + can_route_ip_v4: true, + can_route_ip_external_v4: true, + can_route_ip_v6: true, + can_route_ip_external_v6: true, + }), + socks5: None, + lp: None, + wg: Some(wg_latest.clone()), + }, + }; + + // convert to this crate's LastProbeResult + let result: LastProbeResult = probe_latest.clone().into(); + + assert_eq!(result.node, probe_latest.node); + assert_eq!(result.used_entry, probe_latest.used_entry); + + match &result.outcome.as_entry { + Entry::Tested(entry) => { + assert!(entry.can_connect); + assert!(entry.can_route); + } + other => panic!("Expected Entry::Tested, got {:?}", other), + } + + // Exit conversion + let exit = result + .outcome + .as_exit + .as_ref() + .expect("as_exit should be Some"); + assert!(exit.can_connect); + assert!(exit.can_route_ip_v4); + assert!(exit.can_route_ip_external_v4,); + assert!(exit.can_route_ip_v6); + assert!(exit.can_route_ip_external_v6,); + + // WgProbeResults conversion + let wg = result.outcome.wg.as_ref().expect("wg should be Some"); + assert!(wg.can_register); + assert_eq!(wg.can_query_metadata_v4, Some(true),); + assert!(wg.can_handshake_v4); + assert!(wg.can_resolve_dns_v4,); + assert_eq!( + wg.ping_hosts_performance_v4, + wg_latest.ping_hosts_performance_v4 + ); + assert_eq!( + wg.ping_ips_performance_v4, + wg_latest.ping_ips_performance_v4 + ); + assert!(wg.can_handshake_v6); + assert!(wg.can_resolve_dns_v6); + assert_eq!( + wg.ping_hosts_performance_v6, + wg_latest.ping_hosts_performance_v6 + ); + assert_eq!( + wg.ping_ips_performance_v6, + wg_latest.ping_ips_performance_v6 + ); + assert_eq!( + wg.download_duration_sec_v4, + wg_latest.download_duration_sec_v4 + ); + assert_eq!( + wg.download_duration_milliseconds_v4, + Some(wg_latest.download_duration_milliseconds_v4), + ); + assert_eq!( + wg.downloaded_file_size_bytes_v4, + Some(wg_latest.downloaded_file_size_bytes_v4), + ); + assert_eq!(wg.downloaded_file_v4, wg_latest.downloaded_file_v4); + assert_eq!(wg.download_error_v4, wg_latest.download_error_v4); + assert_eq!( + wg.download_duration_sec_v6, + wg_latest.download_duration_sec_v6 + ); + assert_eq!( + wg.download_duration_milliseconds_v6, + Some(wg_latest.download_duration_milliseconds_v6) + ); + assert_eq!( + wg.downloaded_file_size_bytes_v6, + Some(wg_latest.downloaded_file_size_bytes_v6) + ); + assert_eq!(wg.downloaded_file_v6, wg_latest.downloaded_file_v6); + assert_eq!(wg.download_error_v6, wg_latest.download_error_v6); + + // fields that map from v4 values + assert_eq!(wg.can_handshake, Some(true)); + assert_eq!(wg.can_resolve_dns, Some(true)); + assert_eq!( + wg.ping_hosts_performance, + Some(wg_latest.ping_hosts_performance_v4) + ); + assert_eq!( + wg.ping_ips_performance, + Some(wg_latest.ping_ips_performance_v4) + ); +} + +#[test] +fn conversion_entry_variants() { + use nym_gateway_probe::types::Entry as EntryLatest; + + let not_tested: Entry = EntryLatest::NotTested.into(); + assert!(matches!(not_tested, Entry::NotTested)); + + let failure: Entry = EntryLatest::EntryFailure.into(); + assert!(matches!(failure, Entry::EntryFailure)); +} + +/// Backwards compatibility: this crate's struct may be present in DB of +/// some gateways even after the new nym_gateway_probe format is published. +/// DB entry needs to stay deserializable into a valid struct. +#[test] +fn deserialize_this_crate_format() { + // JSON that matches this crate's ProbeResult format (not nym_gateway_probe) + let old_format_json = serde_json::json!({ + "node": "old-node-key", + "used_entry": "old-entry-key", + "outcome": { + "as_entry": { + "can_connect": true, + "can_route": false + }, + "as_exit": null, + "wg": null + } + }); + + let result = LastProbeResult::deserialize_with_fallback(old_format_json) + .expect("Should deserialize old format"); + + assert_eq!(result.node, "old-node-key"); + assert_eq!(result.used_entry, "old-entry-key"); + match &result.outcome.as_entry { + Entry::Tested(entry) => { + assert!(entry.can_connect); + assert!(!entry.can_route); + } + other => panic!("Expected Entry::Tested, got {:?}", other), + } + assert!(result.outcome.as_exit.is_none()); + assert!(result.outcome.wg.is_none()); +} + +/// Test that the latest nym_gateway_probe format deserializes correctly +#[test] +fn deserialize_latest_gw_probe_format() { + // JSON that matches nym_gateway_probe::types::ProbeResult format + let latest_format_json = serde_json::json!({ + "node": "new-node-key", + "used_entry": "new-entry-key", + "outcome": { + "as_entry": { + "can_connect": true, + "can_route": true + }, + "as_exit": { + "can_connect": true, + "can_route_ip_v4": true, + "can_route_ip_external_v4": true, + "can_route_ip_v6": false, + "can_route_ip_external_v6": false + }, + "socks5": null, + "lp": null, + "wg": { + "can_register": true, + "can_query_metadata_v4": true, + "can_handshake_v4": true, + "can_resolve_dns_v4": true, + "ping_hosts_performance_v4": 0.9, + "ping_ips_performance_v4": 0.85, + "can_handshake_v6": false, + "can_resolve_dns_v6": false, + "ping_hosts_performance_v6": 0.0, + "ping_ips_performance_v6": 0.0, + "download_duration_sec_v4": 3, + "download_duration_milliseconds_v4": 3456, + "downloaded_file_size_bytes_v4": 5242880, + "downloaded_file_v4": "5mb.bin", + "download_error_v4": "", + "download_duration_sec_v6": 0, + "download_duration_milliseconds_v6": 0, + "downloaded_file_size_bytes_v6": 0, + "downloaded_file_v6": "", + "download_error_v6": "ipv6 not supported" + } + } + }); + + let result = LastProbeResult::deserialize_with_fallback(latest_format_json) + .expect("Should deserialize latest format"); + + assert_eq!(result.node, "new-node-key"); + assert_eq!(result.used_entry, "new-entry-key"); + + let exit = result.outcome.as_exit.as_ref().expect("should have exit"); + assert!(exit.can_route_ip_external_v4); + assert!(!exit.can_route_ip_external_v6); + + let wg = result.outcome.wg.as_ref().expect("should have wg"); + assert!(wg.can_register); + assert_eq!(wg.download_duration_milliseconds_v4, Some(3456)); + assert_eq!(wg.download_error_v6, "ipv6 not supported"); +} + +/// Serialize LastProbeResult to JSON and back to ensure serde attributes +/// work correctly and no data is lost. +#[test] +fn round_trip_serialization() { + let original = LastProbeResult { + node: "round-trip-node".to_string(), + used_entry: "round-trip-entry".to_string(), + outcome: ProbeOutcome { + as_entry: Entry::Tested(EntryTestResult { + can_connect: true, + can_route: true, + }), + as_exit: Some(Exit { + can_connect: true, + can_route_ip_v4: true, + can_route_ip_external_v4: true, + can_route_ip_v6: true, + can_route_ip_external_v6: true, + }), + wg: Some(WgProbeResults { + can_register: true, + can_handshake: Some(true), + can_resolve_dns: Some(true), + ping_hosts_performance: Some(0.95), + ping_ips_performance: Some(0.92), + can_query_metadata_v4: Some(true), + can_handshake_v4: true, + can_resolve_dns_v4: true, + ping_hosts_performance_v4: 0.95, + ping_ips_performance_v4: 0.92, + can_handshake_v6: true, + can_resolve_dns_v6: true, + ping_hosts_performance_v6: 0.88, + ping_ips_performance_v6: 0.85, + download_duration_sec_v4: 5, + download_duration_milliseconds_v4: Some(5123), + downloaded_file_size_bytes_v4: Some(10485760), + downloaded_file_v4: "test-file-v4.bin".to_string(), + download_error_v4: "none-v4".to_string(), + download_duration_sec_v6: 6, + download_duration_milliseconds_v6: Some(6234), + downloaded_file_size_bytes_v6: Some(20971520), + downloaded_file_v6: "test-file-v6.bin".to_string(), + download_error_v6: "none-v6".to_string(), + }), + // TODO dz test this as well + socks5: None, + }, + }; + + // Serialize to JSON + let json_string = + serde_json::to_string(&original).expect("Should serialize LastProbeResult to JSON"); + + // Deserialize back + let deserialized: LastProbeResult = + serde_json::from_str(&json_string).expect("Should deserialize JSON to LastProbeResult"); + + // Verify top-level fields + assert_eq!(deserialized.node, original.node); + assert_eq!(deserialized.used_entry, original.used_entry); + + // Verify Entry + match (&original.outcome.as_entry, &deserialized.outcome.as_entry) { + (Entry::Tested(orig), Entry::Tested(deser)) => { + assert_eq!(orig.can_connect, deser.can_connect); + assert_eq!(orig.can_route, deser.can_route); + } + _ => panic!("Entry mismatch after round-trip"), + } + + // Verify Exit + let orig_exit = original.outcome.as_exit.as_ref().unwrap(); + let deser_exit = deserialized.outcome.as_exit.as_ref().unwrap(); + assert_eq!(orig_exit.can_connect, deser_exit.can_connect); + assert_eq!(orig_exit.can_route_ip_v4, deser_exit.can_route_ip_v4); + assert_eq!( + orig_exit.can_route_ip_external_v4, + deser_exit.can_route_ip_external_v4 + ); + assert_eq!(orig_exit.can_route_ip_v6, deser_exit.can_route_ip_v6); + assert_eq!( + orig_exit.can_route_ip_external_v6, + deser_exit.can_route_ip_external_v6 + ); + + // Verify WgProbeResults + let orig_wg = original.outcome.wg.as_ref().unwrap(); + let deser_wg = deserialized.outcome.wg.as_ref().unwrap(); + assert_eq!(orig_wg.can_register, deser_wg.can_register); + assert_eq!(orig_wg.can_handshake, deser_wg.can_handshake); + assert_eq!(orig_wg.can_resolve_dns, deser_wg.can_resolve_dns); + assert_eq!( + orig_wg.ping_hosts_performance, + deser_wg.ping_hosts_performance + ); + assert_eq!(orig_wg.ping_ips_performance, deser_wg.ping_ips_performance); + assert_eq!( + orig_wg.can_query_metadata_v4, + deser_wg.can_query_metadata_v4 + ); + assert_eq!(orig_wg.can_handshake_v4, deser_wg.can_handshake_v4); + assert_eq!(orig_wg.can_resolve_dns_v4, deser_wg.can_resolve_dns_v4); + assert_eq!( + orig_wg.ping_hosts_performance_v4, + deser_wg.ping_hosts_performance_v4 + ); + assert_eq!( + orig_wg.ping_ips_performance_v4, + deser_wg.ping_ips_performance_v4 + ); + assert_eq!(orig_wg.can_handshake_v6, deser_wg.can_handshake_v6); + assert_eq!(orig_wg.can_resolve_dns_v6, deser_wg.can_resolve_dns_v6); + assert_eq!( + orig_wg.ping_hosts_performance_v6, + deser_wg.ping_hosts_performance_v6 + ); + assert_eq!( + orig_wg.ping_ips_performance_v6, + deser_wg.ping_ips_performance_v6 + ); + assert_eq!( + orig_wg.download_duration_sec_v4, + deser_wg.download_duration_sec_v4 + ); + assert_eq!( + orig_wg.download_duration_milliseconds_v4, + deser_wg.download_duration_milliseconds_v4 + ); + assert_eq!( + orig_wg.downloaded_file_size_bytes_v4, + deser_wg.downloaded_file_size_bytes_v4 + ); + assert_eq!(orig_wg.downloaded_file_v4, deser_wg.downloaded_file_v4); + assert_eq!(orig_wg.download_error_v4, deser_wg.download_error_v4); + assert_eq!( + orig_wg.download_duration_sec_v6, + deser_wg.download_duration_sec_v6 + ); + assert_eq!( + orig_wg.download_duration_milliseconds_v6, + deser_wg.download_duration_milliseconds_v6 + ); + assert_eq!( + orig_wg.downloaded_file_size_bytes_v6, + deser_wg.downloaded_file_size_bytes_v6 + ); + assert_eq!(orig_wg.downloaded_file_v6, deser_wg.downloaded_file_v6); + assert_eq!(orig_wg.download_error_v6, deser_wg.download_error_v6); +} diff --git a/nym-node-status-api/nym-node-status-api/src/http/models.rs b/nym-node-status-api/nym-node-status-api/src/http/models/mod.rs similarity index 61% rename from nym-node-status-api/nym-node-status-api/src/http/models.rs rename to nym-node-status-api/nym-node-status-api/src/http/models/mod.rs index 2f0fe09183..6fd599ba6e 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/models.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/models/mod.rs @@ -1,12 +1,17 @@ use std::net::IpAddr; -use crate::monitor::ExplorerPrettyBond; +use crate::{ + http::models::gw_probe::{ + DvpnGwProbe, DvpnProbeOutcome, LastProbeResult, ScoreValue, calculate_load, calculate_score, + }, + monitor::ExplorerPrettyBond, +}; use cosmwasm_std::{Addr, Coin, Decimal}; use nym_mixnet_contract_common::CoinSchema; use nym_node_requests::api::v1::node::models::NodeDescription; use nym_validator_client::{ client::NodeId, - models::{AuthenticatorDetails, BinaryBuildInformationOwned, IpPacketRouterDetails}, + models::{AuthenticatorDetailsV1, BinaryBuildInformationOwned, IpPacketRouterDetailsV1}, nym_api::SkimmedNode, nym_nodes::{BasicEntryInformation, NodeRole}, }; @@ -17,8 +22,11 @@ use utoipa::ToSchema; use crate::db::models::NymNodeDataDeHelper; use crate::node_scraper::models::BridgeInformation; + pub(crate) use nym_node_status_client::models::TestrunAssignment; +pub(crate) mod gw_probe; + #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub struct Gateway { pub gateway_identity_key: String, @@ -75,17 +83,6 @@ pub struct Location { pub asn: Option, } -#[derive(Debug, Clone, Deserialize, Serialize, ToSchema, EnumString)] -#[serde(rename_all = "snake_case")] -#[strum(serialize_all = "snake_case")] -#[derive(PartialEq)] -pub enum ScoreValue { - Offline, - Low, - Medium, - High, -} - #[derive(Debug, Clone, Deserialize, Serialize, ToSchema)] pub struct DVpnGatewayPerformance { last_updated_utc: String, @@ -100,10 +97,10 @@ pub struct DVpnGateway { pub identity_key: String, pub name: String, pub description: Option, - pub ip_packet_router: Option, - pub authenticator: Option, + pub ip_packet_router: Option, + pub authenticator: Option, pub location: Location, - pub last_probe: Option, + pub last_probe: Option, #[schema(value_type = Vec)] pub ip_addresses: Vec, pub mix_port: u16, @@ -126,124 +123,25 @@ impl DVpnGateway { pub fn can_route_entry(&self) -> bool { self.last_probe .as_ref() - .map(|probe| match &probe.outcome.as_entry { - directory_gw_probe_outcome::Entry::Tested(entry_test_result) => { - entry_test_result.can_route - } - directory_gw_probe_outcome::Entry::NotTested - | directory_gw_probe_outcome::Entry::EntryFailure => false, - }) + .map(DvpnGwProbe::can_route_entry) .unwrap_or(false) } pub fn can_route_exit(&self) -> bool { self.last_probe .as_ref() - .map(|probe| { - probe - .outcome - .as_exit - .as_ref() - .map(|outcome| { - outcome.can_route_ip_external_v4 && outcome.can_route_ip_external_v6 - }) - .unwrap_or(false) - }) + .map(|probe| probe.can_route_exit().unwrap_or(false)) .unwrap_or(false) } } -/// based on -/// https://github.com/nymtech/nym-vpn-client/blob/nym-vpn-core-v1.10.0/nym-vpn-core/crates/nym-gateway-probe/src/types.rs -/// TODO: long term types should be moved into this repo because nym-vpn-client -/// could pull it as a dependency and we'd have a single source of truth -#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)] -pub struct LastProbeResult { - node: String, - used_entry: String, - outcome: ProbeOutcome, -} - -#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)] -pub struct DirectoryGwProbe { - last_updated_utc: String, - outcome: ProbeOutcome, -} - -#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)] -pub struct ProbeOutcome { - as_entry: directory_gw_probe_outcome::Entry, - as_exit: Option, - wg: Option, -} - -pub mod directory_gw_probe_outcome { - use super::*; - - #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] - #[serde(untagged)] - #[allow(clippy::enum_variant_names)] - pub enum Entry { - Tested(EntryTestResult), - NotTested, - EntryFailure, - } - - #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] - pub struct EntryTestResult { - pub can_connect: bool, - pub can_route: bool, - } - - #[derive(Debug, Clone, Deserialize, Serialize, ToSchema)] - pub struct Exit { - pub can_connect: bool, - pub can_route_ip_v4: bool, - pub can_route_ip_external_v4: bool, - pub can_route_ip_v6: bool, - pub can_route_ip_external_v6: bool, - } -} - -pub mod wg_outcome_versions { - use super::*; - - #[derive(Debug, Clone, Deserialize, Serialize, ToSchema)] - pub struct ProbeOutcomeV1 { - pub can_register: bool, - pub can_handshake: Option, - pub can_resolve_dns: Option, - pub ping_hosts_performance: Option, - pub ping_ips_performance: Option, - - pub can_query_metadata_v4: Option, - pub can_handshake_v4: bool, - pub can_resolve_dns_v4: bool, - pub ping_hosts_performance_v4: f32, - pub ping_ips_performance_v4: f32, - - pub can_handshake_v6: bool, - pub can_resolve_dns_v6: bool, - pub ping_hosts_performance_v6: f32, - pub ping_ips_performance_v6: f32, - - pub download_duration_sec_v4: u64, - pub download_duration_milliseconds_v4: Option, - pub downloaded_file_size_bytes_v4: Option, - pub downloaded_file_v4: String, - pub download_error_v4: String, - - pub download_duration_sec_v6: u64, - pub download_duration_milliseconds_v6: Option, - pub downloaded_file_size_bytes_v6: Option, - pub downloaded_file_v6: String, - pub download_error_v6: String, - } -} - impl DVpnGateway { #[instrument(level = tracing::Level::INFO, name = "dvpn_gw_new", skip_all, fields(gateway_key = gateway.gateway_identity_key, node_id = skimmed_node.node_id))] - pub(crate) fn new(gateway: Gateway, skimmed_node: &SkimmedNode) -> anyhow::Result { + pub(crate) fn new( + gateway: Gateway, + skimmed_node: &SkimmedNode, + socks5_score: Option<&ScoreValue>, + ) -> anyhow::Result { let location = gateway .explorer_pretty_bond .clone() @@ -275,35 +173,22 @@ impl DVpnGateway { .ok() }); - tracing::info!("🌈 gateway probe result: {:?}", gateway.last_probe_result); + tracing::debug!("🌈 gateway probe result: {:?}", gateway.last_probe_result); let (last_probe_result, performance_v2) = match gateway.last_probe_result { Some(ref value) => { - let mut parsed = serde_json::from_value::(value.clone()) + let parsed = LastProbeResult::deserialize_with_fallback(value.clone()) .inspect_err(|err| { error!("Failed to deserialize probe result: {err}"); })?; - parsed.outcome.wg = parsed.outcome.wg.clone().map(|mut wg| { - if wg.can_handshake.is_none() { - wg.can_handshake = Some(wg.can_handshake_v4); - } - if wg.can_resolve_dns.is_none() { - wg.can_resolve_dns = Some(wg.can_resolve_dns_v4); - } - if wg.ping_hosts_performance.is_none() { - wg.ping_hosts_performance = Some(wg.ping_hosts_performance_v4); - } - if wg.ping_ips_performance.is_none() { - wg.ping_ips_performance = Some(wg.ping_ips_performance_v4); - } - wg - }); - - tracing::info!("🌈 gateway probe parsed: {:?}", parsed); + tracing::trace!("🌈 gateway probe parsed: {:?}", parsed); let mixnet_score = calculate_mixnet_score(&gateway); let score = calculate_score(&gateway, &parsed); let mut load = calculate_load(&parsed); + let socks5_score = socks5_score.unwrap_or(&ScoreValue::Offline).to_owned(); + let dvpn_probe_result = + DvpnProbeOutcome::from_raw_probe_outcome(parsed.outcome(), socks5_score); // clamp the load value to offline, when the score is offline if score == ScoreValue::Offline { @@ -319,7 +204,7 @@ impl DVpnGateway { // the network monitor's measure is a good proxy for node uptime, it can be improved in the future uptime_percentage_last_24_hours: network_monitor_performance_mixnet_mode, }; - (Some(parsed), Some(performance_v2)) + (Some(dvpn_probe_result), Some(performance_v2)) } None => (None, None), }; @@ -356,10 +241,8 @@ impl DVpnGateway { } }), }, - last_probe: last_probe_result.map(|res| DirectoryGwProbe { - last_updated_utc: last_updated_utc.to_string(), - outcome: res.outcome, - }), + last_probe: last_probe_result + .map(|res| DvpnGwProbe::from_outcome(res, last_updated_utc)), ip_addresses: skimmed_node.ip_addresses.clone(), mix_port: skimmed_node.mix_port, role: skimmed_node.role.clone(), @@ -372,21 +255,6 @@ impl DVpnGateway { } } -struct NodeScore { - download_speed_score: f64, - ping_ips_score: f64, - mixnet_performance: f64, -} - -impl NodeScore { - // Weighted scoring: mixnet performance (40%), download speed (30%), ping performance (30%) - fn calculate_weighted_score(&self) -> f64 { - (self.mixnet_performance * 0.4) - + (self.download_speed_score * 0.3) - + (self.ping_ips_score * 0.3) - } -} - /// calculates the gateway probe score for mixnet mode fn calculate_mixnet_score(gateway: &Gateway) -> ScoreValue { let mixnet_performance = gateway.performance as f64 / 100.0; @@ -402,82 +270,6 @@ fn calculate_mixnet_score(gateway: &Gateway) -> ScoreValue { } } -/// calculates a visual score for the gateway using weighted metrics -fn calculate_score(gateway: &Gateway, probe_outcome: &LastProbeResult) -> ScoreValue { - let mixnet_performance = gateway.performance as f64 / 100.0; - - let node_score = probe_outcome - .outcome - .wg - .as_ref() - .map(|p| { - let ping_ips_performance = p.ping_ips_performance_v4 as f64; - - let duration_sec = - p.download_duration_milliseconds_v4 - .unwrap_or_else(|| p.download_duration_sec_v4 * 1000) as f64 - / 1000f64; - - // get the file size downloaded in bytes and convert to MB, or default to 1MB - let file_size_mb = - p.downloaded_file_size_bytes_v4.unwrap_or(1048576) as f64 / 1024f64 / 1024f64; - let speed_mbps = file_size_mb / duration_sec; - - let file_download_score = if speed_mbps > 5.0 { - 1.0 - } else if speed_mbps > 2.0 { - 0.75 - } else if speed_mbps > 1.0 { - 0.5 - } else if speed_mbps > 0.5 { - 0.25 - } else { - 0.1 - }; - - NodeScore { - download_speed_score: file_download_score, - ping_ips_score: ping_ips_performance, - mixnet_performance, - } - }) - .unwrap_or(NodeScore { - download_speed_score: 0.0, - ping_ips_score: 0.0, - mixnet_performance, - }); - - let weighted_score = node_score.calculate_weighted_score(); - - if weighted_score > 0.75 { - ScoreValue::High - } else if weighted_score > 0.5 { - ScoreValue::Medium - } else if weighted_score > 0.1 { - ScoreValue::Low - } else { - ScoreValue::Offline - } -} - -/// calculates a visual load score for the gateway -fn calculate_load(probe_outcome: &LastProbeResult) -> ScoreValue { - let score = probe_outcome - .outcome - .wg - .clone() - .map(|p| p.ping_ips_performance_v4 as f64) - .unwrap_or(0f64); - - if score > 0.8 { - ScoreValue::Low - } else if score > 0.4 { - ScoreValue::Medium - } else { - ScoreValue::High - } -} - fn to_percent(performance: u8) -> String { let fraction = performance as f32 / 100.0; format!("{fraction:.2}") @@ -485,6 +277,7 @@ fn to_percent(performance: u8) -> String { #[cfg(test)] mod test { + use super::*; use std::str::FromStr; @@ -756,130 +549,6 @@ mod test { assert!(service.mixnet_websockets.is_none()); assert!(service.last_successful_ping_utc.is_none()); } - - #[test] - fn test_weighted_score_calculation() { - use crate::http::models::directory_gw_probe_outcome::EntryTestResult; - use crate::http::models::wg_outcome_versions::ProbeOutcomeV1; - - // Helper function to create a test gateway - fn create_test_gateway(performance: u8) -> Gateway { - Gateway { - gateway_identity_key: "test_key".to_string(), - bonded: true, - performance, - self_described: None, - explorer_pretty_bond: None, - description: nym_node_requests::api::v1::node::models::NodeDescription { - moniker: "test".to_string(), - details: "test".to_string(), - security_contact: "test@example.com".to_string(), - website: "https://example.com".to_string(), - }, - last_probe_result: None, - last_probe_log: None, - last_testrun_utc: None, - last_updated_utc: "2025-10-10T00:00:00Z".to_string(), - routing_score: 0.0, - config_score: 0, - bridges: None, - } - } - - // Helper function to create a test probe outcome - fn create_test_probe_outcome( - download_speed_mbps: f64, - ping_ips_performance: f32, - ) -> LastProbeResult { - let duration_sec = 1.0; - let file_size_mb = download_speed_mbps; - - LastProbeResult { - node: "test_node".to_string(), - used_entry: "test_entry".to_string(), - outcome: ProbeOutcome { - as_entry: directory_gw_probe_outcome::Entry::Tested(EntryTestResult { - can_connect: true, - can_route: true, - }), - as_exit: None, - wg: Some(ProbeOutcomeV1 { - can_register: true, - can_handshake: Some(true), - can_resolve_dns: Some(true), - ping_hosts_performance: Some(ping_ips_performance), - ping_ips_performance: Some(ping_ips_performance), - can_query_metadata_v4: Some(true), - can_handshake_v4: true, - can_resolve_dns_v4: true, - ping_hosts_performance_v4: ping_ips_performance, - ping_ips_performance_v4: ping_ips_performance, - can_handshake_v6: true, - can_resolve_dns_v6: true, - ping_hosts_performance_v6: ping_ips_performance, - ping_ips_performance_v6: ping_ips_performance, - download_duration_sec_v4: (duration_sec * 1000.0) as u64, - download_duration_milliseconds_v4: Some((duration_sec * 1000.0) as u64), - downloaded_file_size_bytes_v4: Some( - (file_size_mb * 1024.0 * 1024.0) as u64, - ), - downloaded_file_v4: "test".to_string(), - download_error_v4: "".to_string(), - download_duration_sec_v6: 0, - download_duration_milliseconds_v6: Some(0), - downloaded_file_size_bytes_v6: Some(0), - downloaded_file_v6: "".to_string(), - download_error_v6: "".to_string(), - }), - }, - } - } - - // Test case 1: Excellent node (should be High) - let gateway = create_test_gateway(90); // 90% mixnet performance - let probe = create_test_probe_outcome(6.0, 1.0); // 6 Mbps, 100% ping - let score = calculate_score(&gateway, &probe); - assert_eq!(score, ScoreValue::High, "Excellent node should be High"); - - // Test case 2: Good node (should be High with weighted scoring) - let gateway = create_test_gateway(90); // 90% mixnet performance - let probe = create_test_probe_outcome(3.0, 0.9); // 3 Mbps (0.75 score), 90% ping - let score = calculate_score(&gateway, &probe); - assert_eq!( - score, - ScoreValue::High, - "Good node should be High with weighted scoring" - ); - - // Test case 3: Medium node - let gateway = create_test_gateway(80); // 80% mixnet performance - let probe = create_test_probe_outcome(1.5, 0.8); // 1.5 Mbps (0.5 score), 80% ping - let score = calculate_score(&gateway, &probe); - assert_eq!(score, ScoreValue::Medium, "Medium node should be Medium"); - - // Test case 4: Poor node - let gateway = create_test_gateway(60); // 60% mixnet performance - let probe = create_test_probe_outcome(0.3, 0.3); // 0.3 Mbps (0.1 score), 30% ping - let score = calculate_score(&gateway, &probe); - assert_eq!(score, ScoreValue::Low, "Poor node should be Low"); - - // Test case 5: Failed node - let gateway = create_test_gateway(10); // 10% mixnet performance - let probe = create_test_probe_outcome(0.1, 0.0); // 0.1 Mbps (0.1 score), 0% ping - let score = calculate_score(&gateway, &probe); - assert_eq!(score, ScoreValue::Offline, "Failed node should be Offline"); - - // Test case 6: Edge case - just above threshold - let gateway = create_test_gateway(76); // 76% mixnet performance - let probe = create_test_probe_outcome(2.1, 0.75); // 2.1 Mbps (0.75 score), 75% ping - let score = calculate_score(&gateway, &probe); - // Weighted: (0.76 * 0.4) + (0.75 * 0.3) + (0.75 * 0.3) = 0.304 + 0.225 + 0.225 = 0.754 - assert_eq!( - score, - ScoreValue::High, - "Edge case just above 0.75 threshold should be High" - ); - } } #[derive(Debug, Clone, Deserialize, Serialize, ToSchema)] @@ -917,10 +586,10 @@ pub(crate) struct ExtendedNymNode { pub(crate) original_pledge: u128, pub(crate) bonding_address: Option, pub(crate) bonded: bool, - pub(crate) node_type: nym_validator_client::models::DescribedNodeType, + pub(crate) node_type: nym_validator_client::models::DescribedNodeTypeV1, pub(crate) ip_address: String, pub(crate) accepted_tnc: bool, - pub(crate) self_description: nym_validator_client::models::NymNodeData, + pub(crate) self_description: nym_validator_client::models::NymNodeDataV2, pub(crate) rewarding_details: Option, pub(crate) description: NodeDescription, pub(crate) geoip: Option, diff --git a/nym-node-status-api/nym-node-status-api/src/http/state.rs b/nym-node-status-api/nym-node-status-api/src/http/state.rs index f03bfe0398..14bfc99c8d 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/state.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/state.rs @@ -20,7 +20,10 @@ use crate::{ db::{DbPool, queries}, http::{ error::{HttpError, HttpResult}, - models::{DVpnGateway, DailyStats, ExtendedNymNode, Gateway, NodeGeoData, SummaryHistory}, + models::{ + DVpnGateway, DailyStats, ExtendedNymNode, Gateway, NodeGeoData, SummaryHistory, + gw_probe::socks5_calc::calculate_socks5_percentiles, + }, }, monitor::{DelegationsCache, NodeGeoCache}, }; @@ -321,6 +324,8 @@ impl HttpCache { } }; + let socks5_scores = calculate_socks5_percentiles(&gateways); + let res_gws = gateways .iter() .filter(|gw| gw.bonded) @@ -335,7 +340,7 @@ impl HttpCache { } }) .filter_map( - |(gw, skimmed_node)| match DVpnGateway::new(gw.clone(), skimmed_node) { + |(gw, skimmed_node)| match DVpnGateway::new(gw.clone(), skimmed_node, socks5_scores.get(&gw.gateway_identity_key)) { Ok(gw) => Some(gw), Err(err) => { error!( diff --git a/nym-node-status-api/nym-node-status-api/src/main.rs b/nym-node-status-api/nym-node-status-api/src/main.rs index ebb76373ac..352f310c01 100644 --- a/nym-node-status-api/nym-node-status-api/src/main.rs +++ b/nym-node-status-api/nym-node-status-api/src/main.rs @@ -31,6 +31,7 @@ async fn main() -> anyhow::Result<()> { if let Some(env_file) = &args.config_env_file { setup_env(Some(env_file)); } + let network = nym_network_defaults::NymNetworkDetails::new_from_env(); let mut shutdown_manager = ShutdownManager::build_new_default()?; @@ -46,7 +47,13 @@ async fn main() -> anyhow::Result<()> { tracing::debug!("Using config:\n{:#?}", args); } - let storage = db::Storage::init(connection_url, args.sqlx_busy_timeout_s).await?; + let storage = db::Storage::init( + connection_url, + args.sqlx_busy_timeout_s, + args.sqlx_max_connections, + args.sqlx_min_connections, + ) + .await?; let db_pool = storage.pool_owned(); // node geocache is shared between node monitor and HTTP server @@ -55,9 +62,9 @@ async fn main() -> anyhow::Result<()> { .build(); let delegations_cache = DelegationsCache::new(); - let client_config = nym_validator_client::nyxd::Config::try_from_nym_network_details( - &nym_network_defaults::NymNetworkDetails::new_from_env(), - )?; + let client_config = nym_validator_client::nyxd::Config::try_from_nym_network_details(&network)?; + tracing::info!("Network: {}", network.network_name); + let nyxd_client = NyxdClient::connect(client_config.clone(), args.nyxd_addr.as_str()) .map_err(|err| anyhow::anyhow!("Couldn't connect: {}", err))?; diff --git a/nym-node-status-api/nym-node-status-api/src/metrics_scraper/error.rs b/nym-node-status-api/nym-node-status-api/src/metrics_scraper/error.rs index 26ff4a5d97..65004fbeaf 100644 --- a/nym-node-status-api/nym-node-status-api/src/metrics_scraper/error.rs +++ b/nym-node-status-api/nym-node-status-api/src/metrics_scraper/error.rs @@ -57,7 +57,9 @@ mod tests { let error = NodeScraperError::MalformedHost { host: "".to_string(), node_id: 0, - source: Box::new(NymNodeApiClientError::NotFound { url: dummy_url() }), + source: Box::new(NymNodeApiClientError::NotFound { + url: Box::new(dummy_url()), + }), }; let error_msg = error.to_string(); @@ -112,7 +114,9 @@ mod tests { #[allow(deprecated)] fn test_error_different_sources() { // Test with different NymNodeApiClientError variants - let not_found_error = NymNodeApiClientError::NotFound { url: dummy_url() }; + let not_found_error = NymNodeApiClientError::NotFound { + url: Box::new(dummy_url()), + }; let error1 = NodeScraperError::MalformedHost { host: "host1".to_string(), node_id: 1, diff --git a/nym-node-status-api/nym-node-status-api/src/metrics_scraper/mod.rs b/nym-node-status-api/nym-node-status-api/src/metrics_scraper/mod.rs index c89fcc40c3..ab843af8e5 100644 --- a/nym-node-status-api/nym-node-status-api/src/metrics_scraper/mod.rs +++ b/nym-node-status-api/nym-node-status-api/src/metrics_scraper/mod.rs @@ -4,7 +4,7 @@ use nym_network_defaults::{DEFAULT_NYM_NODE_HTTP_PORT, NymNetworkDetails}; use nym_node_requests::api::{client::NymNodeApiClientExt, v1::metrics::models::SessionStats}; use nym_validator_client::{ client::{NodeId, NymNodeDetails}, - models::{DescribedNodeType, NymNodeDescription}, + models::{DescribedNodeTypeV1, NymNodeDescriptionV1}, }; use time::OffsetDateTime; @@ -64,6 +64,10 @@ async fn run( //SW TBC what nodes exactly need to be scraped, the skimmed node endpoint seems to return more nodes let bonded_nodes = nym_api.get_all_bonded_nym_nodes().await?; + + // for now allow usage of old endpoint for we don't need LP related data + // and the new endpoint might not be immediately deployed + #[allow(deprecated)] let all_nodes = nym_api.get_all_described_nodes().await?; //legacy node that did not upgrade the contract bond yet tracing::debug!("Fetched {} total nodes", all_nodes.len()); @@ -74,7 +78,7 @@ async fn run( all_nodes .into_iter() - .filter(|n| n.contract_node_type != DescribedNodeType::LegacyMixnode) + .filter(|n| n.contract_node_type != DescribedNodeTypeV1::LegacyMixnode) .for_each(|n| { nodes_to_scrape.entry(n.node_id).or_insert_with(|| n.into()); }); @@ -211,8 +215,8 @@ impl From for MetricsScrapingData { } } -impl From for MetricsScrapingData { - fn from(value: NymNodeDescription) -> Self { +impl From for MetricsScrapingData { + fn from(value: NymNodeDescriptionV1) -> Self { MetricsScrapingData::new( value.description.host_information.ip_address[0].to_string(), value.node_id, diff --git a/nym-node-status-api/nym-node-status-api/src/monitor/mod.rs b/nym-node-status-api/nym-node-status-api/src/monitor/mod.rs index a1fcab2b8f..75a1926a66 100644 --- a/nym-node-status-api/nym-node-status-api/src/monitor/mod.rs +++ b/nym-node-status-api/nym-node-status-api/src/monitor/mod.rs @@ -16,7 +16,7 @@ use nym_validator_client::{ }; use nym_validator_client::{ client::{NodeId, NymApiClientExt, NymNodeDetails}, - models::NymNodeDescription, + models::NymNodeDescriptionV1, }; use std::{collections::HashMap, sync::Arc}; use tokio::{sync::RwLock, time::Duration}; @@ -282,7 +282,7 @@ impl Monitor { } #[instrument(level = "info", skip_all)] - async fn location_cached(&mut self, node: &NymNodeDescription) -> Location { + async fn location_cached(&mut self, node: &NymNodeDescriptionV1) -> Location { let node_id = node.node_id; match self.geocache.get(&node_id).await { @@ -310,7 +310,7 @@ impl Monitor { &self, skimmed_nodes: Vec, bonded_node_info: &HashMap, - described_nodes: &HashMap, + described_nodes: &HashMap, ) -> Vec { skimmed_nodes .into_iter() @@ -335,7 +335,7 @@ impl Monitor { async fn prepare_gateway_data( &mut self, - described_gateways: &[&NymNodeDescription], + described_gateways: &[&NymNodeDescriptionV1], skimmed_gateways: &[SkimmedNode], bonded_nodes: &HashMap, ) -> anyhow::Result> { diff --git a/nym-node-status-api/nym-node-status-client/Cargo.toml b/nym-node-status-api/nym-node-status-client/Cargo.toml index 881cfa159e..194ac61357 100644 --- a/nym-node-status-api/nym-node-status-client/Cargo.toml +++ b/nym-node-status-api/nym-node-status-client/Cargo.toml @@ -12,13 +12,14 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +publish = false [dependencies] anyhow = { workspace = true } bs58 = { workspace = true } bincode = { workspace = true } -nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "serde", ] } -nym-credentials = { path = "../../common/credentials" } +nym-crypto = { workspace = true, features = ["asymmetric", "serde", ] } +nym-credentials = { workspace = true } reqwest = { workspace = true, features = ["json"] } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } diff --git a/nym-node/Cargo.toml b/nym-node/Cargo.toml index fd5ffbd12b..d2dd9f0951 100644 --- a/nym-node/Cargo.toml +++ b/nym-node/Cargo.toml @@ -3,13 +3,14 @@ [package] name = "nym-node" -version = "1.24.0" +version = "1.25.0" authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license = "GPL-3.0" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -27,6 +28,7 @@ console-subscriber = { workspace = true, optional = true } csv = { workspace = true } clap = { workspace = true, features = ["cargo", "env"] } futures = { workspace = true } +hex = { workspace = true } humantime-serde = { workspace = true } human-repr = { workspace = true } ipnetwork = { workspace = true } @@ -49,39 +51,40 @@ zeroize = { workspace = true, features = ["zeroize_derive"] } cupid = { workspace = true } sysinfo = { workspace = true } -nym-bin-common = { path = "../common/bin-common", features = [ +nym-bin-common = { workspace = true, features = [ "basic_tracing", "output_format", ] } -nym-client-core-config-types = { path = "../common/client-core/config-types", features = [ +nym-client-core-config-types = { workspace = true, features = [ "disk-persistence", ] } -nym-config = { path = "../common/config" } -nym-credential-verification = { path = "../common/credential-verification" } -nym-crypto = { path = "../common/crypto", features = ["asymmetric", "rand"] } -nym-nonexhaustive-delayqueue = { path = "../common/nonexhaustive-delayqueue" } -nym-mixnet-client = { path = "../common/client-libs/mixnet-client" } -nym-noise = { path = "../common/nymnoise" } -nym-noise-keys = { path = "../common/nymnoise/keys" } -nym-pemstore = { path = "../common/pemstore" } -nym-sphinx-acknowledgements = { path = "../common/nymsphinx/acknowledgements" } -nym-sphinx-addressing = { path = "../common/nymsphinx/addressing" } -nym-sphinx-framing = { path = "../common/nymsphinx/framing" } -nym-sphinx-types = { path = "../common/nymsphinx/types" } -nym-sphinx-forwarding = { path = "../common/nymsphinx/forwarding" } -nym-sphinx-routing = { path = "../common/nymsphinx/routing" } -nym-sphinx-params = { path = "../common/nymsphinx/params" } -nym-statistics-common = { path = "../common/statistics" } -nym-task = { path = "../common/task" } -nym-types = { path = "../common/types" } -nym-validator-client = { path = "../common/client-libs/validator-client" } -nym-wireguard = { path = "../common/wireguard" } -nym-wireguard-types = { path = "../common/wireguard-types", default-features = false } -nym-verloc = { path = "../common/verloc" } -nym-metrics = { path = "../common/nym-metrics" } -nym-gateway-stats-storage = { path = "../common/gateway-stats-storage" } -nym-topology = { path = "../common/topology" } -nym-http-api-client = { path = "../common/http-api-client" } +nym-config = { workspace = true } +nym-credential-verification = { workspace = true } +nym-crypto = { workspace = true, features = ["asymmetric", "rand"] } +nym-kkt = { path = "../common/nym-kkt" } +nym-nonexhaustive-delayqueue = { workspace = true } +nym-mixnet-client = { workspace = true } +nym-noise = { workspace = true } +nym-noise-keys = { workspace = true } +nym-pemstore = { workspace = true } +nym-sphinx-acknowledgements = { workspace = true } +nym-sphinx-addressing = { workspace = true } +nym-sphinx-framing = { workspace = true } +nym-sphinx-types = { workspace = true } +nym-sphinx-forwarding = { workspace = true } +nym-sphinx-routing = { workspace = true } +nym-sphinx-params = { workspace = true } +nym-statistics-common = { workspace = true } +nym-task = { workspace = true } +nym-types = { workspace = true } +nym-validator-client = { workspace = true } +nym-wireguard = { workspace = true } +nym-wireguard-types = { workspace = true } +nym-verloc = { workspace = true } +nym-metrics = { workspace = true } +nym-gateway-stats-storage = { workspace = true } +nym-topology = { workspace = true } +nym-http-api-client = { workspace = true } # http server # useful for `#[axum_macros::debug_handler]` @@ -92,15 +95,15 @@ tower-http = { workspace = true, features = ["fs"] } utoipa = { workspace = true, features = ["axum_extras", "time"] } utoipa-swagger-ui = { workspace = true, features = ["axum"] } -nym-http-api-common = { path = "../common/http-api-common", features = [ +nym-http-api-common = { workspace = true, features = [ "utoipa", "output", "middleware", ] } -nym-node-requests = { path = "nym-node-requests", default-features = false, features = [ +nym-node-requests = { workspace = true, default-features = false, features = [ "openapi", ] } -nym-node-metrics = { path = "nym-node-metrics" } +nym-node-metrics = { workspace = true } # nodes: nym-gateway = { path = "../gateway" } diff --git a/nym-node/nym-node-metrics/Cargo.toml b/nym-node/nym-node-metrics/Cargo.toml index 7797040114..28b32ac5e8 100644 --- a/nym-node/nym-node-metrics/Cargo.toml +++ b/nym-node/nym-node-metrics/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "nym-node-metrics" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "Crate defining various metrics for Nym Nodes (Mix Nodes and Gateways) such as ongoing connections, packets mixed, and packets dropped" [dependencies] dashmap = { workspace = true } @@ -16,5 +17,5 @@ time = { workspace = true } strum = { workspace = true } tracing = { workspace = true } -nym-metrics = { path = "../../common/nym-metrics" } -nym-statistics-common = { path = "../../common/statistics" } \ No newline at end of file +nym-metrics = { workspace = true } +nym-statistics-common = { workspace = true } diff --git a/nym-node/nym-node-requests/Cargo.toml b/nym-node/nym-node-requests/Cargo.toml index ad1ed3013a..6c311152ab 100644 --- a/nym-node/nym-node-requests/Cargo.toml +++ b/nym-node/nym-node-requests/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "nym-node-requests" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "Nym Node API endpoint definitions and functions" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -22,31 +23,32 @@ time = { workspace = true, features = ["serde", "formatting", "parsing"] } thiserror = { workspace = true } url = { workspace = true, features = ["serde"] } -nym-crypto = { path = "../../common/crypto", features = [ +nym-crypto = { workspace = true, features = [ "asymmetric", "serde", ] } -nym-exit-policy = { path = "../../common/exit-policy" } -nym-noise-keys = { path = "../../common/nymnoise/keys" } -nym-wireguard-types = { path = "../../common/wireguard-types", default-features = false } -nym-upgrade-mode-check = { path = "../../common/upgrade-mode-check", features = ["openapi"] } +nym-exit-policy = { workspace = true } +nym-noise-keys = { workspace = true } +nym-wireguard-types = { workspace = true } +nym-upgrade-mode-check = { workspace = true, features = ["openapi"] } +nym-kkt-ciphersuite = { workspace = true } # feature-specific dependencies: ## client: async-trait = { workspace = true, optional = true } -nym-http-api-client = { path = "../../common/http-api-client", optional = true } +nym-http-api-client = { workspace = true, optional = true } ## openapi: utoipa = { workspace = true, features = ["time"], optional = true } -nym-bin-common = { path = "../../common/bin-common", features = [ +nym-bin-common = { workspace = true, features = [ "bin_info_schema", ] } [dev-dependencies] tokio = { workspace = true, features = ["full"] } rand_chacha = { workspace = true } -nym-crypto = { path = "../../common/crypto", features = ["rand"] } +nym-crypto = { workspace = true, features = ["rand"] } [features] diff --git a/nym-node/nym-node-requests/src/api/mod.rs b/nym-node/nym-node-requests/src/api/mod.rs index 9c3e0a67d2..ba32570c2e 100644 --- a/nym-node/nym-node-requests/src/api/mod.rs +++ b/nym-node/nym-node-requests/src/api/mod.rs @@ -132,7 +132,7 @@ mod tests { use super::*; use crate::api::v1::node::models::{HostKeys, SphinxKey}; use nym_crypto::asymmetric::{ed25519, x25519}; - use nym_noise_keys::{NoiseVersion, VersionedNoiseKey}; + use nym_noise_keys::{NoiseVersion, VersionedNoiseKeyV1}; use rand_chacha::rand_core::SeedableRng; #[test] @@ -141,7 +141,7 @@ mod tests { let ed22519 = ed25519::KeyPair::new(&mut rng); let x25519_sphinx = x25519::KeyPair::new(&mut rng); let x25519_sphinx2 = x25519::KeyPair::new(&mut rng); - let x25519_versioned_noise = VersionedNoiseKey { + let x25519_versioned_noise = VersionedNoiseKeyV1 { supported_version: NoiseVersion::V1, x25519_pubkey: *x25519::KeyPair::new(&mut rng).public_key(), }; @@ -305,7 +305,7 @@ mod tests { public_key: *x25519_sphinx.public_key(), }, pre_announced_x25519_sphinx_key: None, - x25519_versioned_noise: Some(VersionedNoiseKey { + x25519_versioned_noise: Some(VersionedNoiseKeyV1 { supported_version: NoiseVersion::V1, x25519_pubkey: legacy_info_noise.keys.x25519_noise.unwrap(), }), @@ -384,7 +384,7 @@ mod tests { public_key: *x25519_sphinx.public_key(), }, pre_announced_x25519_sphinx_key: None, - x25519_versioned_noise: Some(VersionedNoiseKey { + x25519_versioned_noise: Some(VersionedNoiseKeyV1 { supported_version: NoiseVersion::V1, x25519_pubkey: legacy_info_noise.keys.x25519_noise.parse().unwrap(), }), diff --git a/nym-node/nym-node-requests/src/api/v1/lewes_protocol/models.rs b/nym-node/nym-node-requests/src/api/v1/lewes_protocol/models.rs index 687b6d621c..17900472b6 100644 --- a/nym-node/nym-node-requests/src/api/v1/lewes_protocol/models.rs +++ b/nym-node/nym-node-requests/src/api/v1/lewes_protocol/models.rs @@ -1,10 +1,14 @@ // Copyright 2026 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use nym_crypto::asymmetric::x25519; +use nym_crypto::asymmetric::x25519::serde_helpers::bs58_x25519_pubkey; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use strum_macros::{Display, EnumIter, EnumString}; -#[derive(Serialize, Deserialize, Debug, Clone, Copy, JsonSchema)] +#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)] #[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] pub struct LewesProtocol { /// Helper field that specifies whether the LP listener(s) is enabled on this node. @@ -16,4 +20,200 @@ pub struct LewesProtocol { /// LP UDP data address (default: 51264) for Sphinx packets wrapped in LP pub data_port: u16, + + #[serde(with = "bs58_x25519_pubkey")] + #[schemars(with = "String")] + #[schema(value_type = String)] + /// LP public key + pub x25519: x25519::PublicKey, + + /// Digests of the KEM keys available to this node alongside hashing algorithms used + /// for their computation. + /// note: digests are hex encoded + pub kem_keys: HashMap>, + + /// Digests of the signing keys available to this node alongside hashing algorithms used + /// for their computation. + /// note: digests are hex encoded + pub signing_keys: HashMap>, +} + +impl LewesProtocol { + pub fn new( + enabled: bool, + control_port: u16, + data_port: u16, + x25519: x25519::PublicKey, + kem_keys: HashMap>, + signing_keys: HashMap>, + ) -> Self { + LewesProtocol { + enabled, + control_port, + data_port, + x25519, + kem_keys, + signing_keys, + } + } +} + +// explicitly redefine available HashFunctions and KEMs so that we would not +// accidentally remove some type and thus break backwards compatibility at deserialisation level +// (the only thing that should break at that point would be conversion into proper nym-kkt types +// which would return a concrete error variant) + +#[derive( + Serialize, + Deserialize, + Debug, + Clone, + Copy, + JsonSchema, + Hash, + PartialEq, + Eq, + PartialOrd, + Display, + EnumString, +)] +#[strum(serialize_all = "lowercase")] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +pub enum LPKEM { + MlKem768, + XWing, + X25519, + McEliece, +} + +impl From for nym_kkt_ciphersuite::KEM { + fn from(lpkem: LPKEM) -> Self { + match lpkem { + LPKEM::MlKem768 => nym_kkt_ciphersuite::KEM::MlKem768, + LPKEM::XWing => nym_kkt_ciphersuite::KEM::XWing, + LPKEM::X25519 => nym_kkt_ciphersuite::KEM::X25519, + LPKEM::McEliece => nym_kkt_ciphersuite::KEM::McEliece, + } + } +} + +impl From for LPKEM { + fn from(kem: nym_kkt_ciphersuite::KEM) -> Self { + match kem { + nym_kkt_ciphersuite::KEM::MlKem768 => LPKEM::MlKem768, + nym_kkt_ciphersuite::KEM::XWing => LPKEM::XWing, + nym_kkt_ciphersuite::KEM::X25519 => LPKEM::X25519, + nym_kkt_ciphersuite::KEM::McEliece => LPKEM::McEliece, + } + } +} + +#[derive( + Serialize, + Deserialize, + Debug, + Clone, + Copy, + JsonSchema, + Hash, + PartialEq, + Eq, + PartialOrd, + Display, + EnumString, + EnumIter, +)] +#[strum(serialize_all = "lowercase")] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +pub enum LPHashFunction { + Blake3, + Shake128, + Shake256, + Sha256, +} + +impl From for nym_kkt_ciphersuite::HashFunction { + fn from(lp_hash_fnction: LPHashFunction) -> Self { + match lp_hash_fnction { + LPHashFunction::Blake3 => nym_kkt_ciphersuite::HashFunction::Blake3, + LPHashFunction::Shake128 => nym_kkt_ciphersuite::HashFunction::Shake128, + LPHashFunction::Shake256 => nym_kkt_ciphersuite::HashFunction::Shake256, + LPHashFunction::Sha256 => nym_kkt_ciphersuite::HashFunction::SHA256, + } + } +} + +impl From for LPHashFunction { + fn from(kem: nym_kkt_ciphersuite::HashFunction) -> Self { + match kem { + nym_kkt_ciphersuite::HashFunction::Blake3 => LPHashFunction::Blake3, + nym_kkt_ciphersuite::HashFunction::Shake128 => LPHashFunction::Shake128, + nym_kkt_ciphersuite::HashFunction::Shake256 => LPHashFunction::Shake256, + nym_kkt_ciphersuite::HashFunction::SHA256 => LPHashFunction::Sha256, + } + } +} + +#[derive( + Serialize, + Deserialize, + Debug, + Clone, + Copy, + JsonSchema, + Hash, + PartialEq, + Eq, + PartialOrd, + Display, + EnumString, + EnumIter, +)] +#[strum(serialize_all = "lowercase")] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +pub enum LPSignatureScheme { + Ed25519, +} + +impl From for nym_kkt_ciphersuite::SignatureScheme { + fn from(lp_hash_fnction: LPSignatureScheme) -> Self { + match lp_hash_fnction { + LPSignatureScheme::Ed25519 => nym_kkt_ciphersuite::SignatureScheme::Ed25519, + } + } +} + +impl From for LPSignatureScheme { + fn from(kem: nym_kkt_ciphersuite::SignatureScheme) -> Self { + match kem { + nym_kkt_ciphersuite::SignatureScheme::Ed25519 => LPSignatureScheme::Ed25519, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use nym_kkt_ciphersuite::SignatureScheme; + + #[test] + fn kem_display() { + assert_eq!(LPKEM::MlKem768.to_string(), "mlkem768"); + assert_eq!(LPKEM::XWing.to_string(), "xwing"); + assert_eq!(LPKEM::X25519.to_string(), "x25519"); + assert_eq!(LPKEM::McEliece.to_string(), "mceliece"); + } + + #[test] + fn hash_function_display() { + assert_eq!(LPHashFunction::Blake3.to_string(), "blake3"); + assert_eq!(LPHashFunction::Shake128.to_string(), "shake128"); + assert_eq!(LPHashFunction::Shake256.to_string(), "shake256"); + assert_eq!(LPHashFunction::Sha256.to_string(), "sha256"); + } + + #[test] + fn signature_scheme_display() { + assert_eq!(SignatureScheme::Ed25519.to_string(), "ed25519"); + } } diff --git a/nym-node/nym-node-requests/src/api/v1/node/models.rs b/nym-node/nym-node-requests/src/api/v1/node/models.rs index 2d0a9fe16f..4a6a750688 100644 --- a/nym-node/nym-node-requests/src/api/v1/node/models.rs +++ b/nym-node/nym-node-requests/src/api/v1/node/models.rs @@ -6,7 +6,7 @@ use nym_crypto::asymmetric::ed25519::{self, serde_helpers::bs58_ed25519_pubkey}; use nym_crypto::asymmetric::x25519::{ self, serde_helpers::bs58_x25519_pubkey, serde_helpers::option_bs58_x25519_pubkey, }; -use nym_noise_keys::VersionedNoiseKey; +use nym_noise_keys::VersionedNoiseKeyV1; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::net::IpAddr; @@ -146,7 +146,7 @@ pub struct HostKeys { /// Base58-encoded x25519 public key of this node used for the noise protocol. #[serde(default)] - pub x25519_versioned_noise: Option, + pub x25519_versioned_noise: Option, } // we need the intermediate struct to help us with the new explicit sphinx key fields @@ -193,7 +193,7 @@ struct HostKeysDeHelper { /// Base58-encoded x25519 public key of this node used for the noise protocol. #[serde(default)] - pub x25519_versioned_noise: Option, + pub x25519_versioned_noise: Option, } #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] diff --git a/nym-node/src/node/http/router/api/v1/lewes_protocol/mod.rs b/nym-node/src/node/http/router/api/v1/lewes_protocol/mod.rs index 67496def85..36d8dc785d 100644 --- a/nym-node/src/node/http/router/api/v1/lewes_protocol/mod.rs +++ b/nym-node/src/node/http/router/api/v1/lewes_protocol/mod.rs @@ -7,7 +7,7 @@ use nym_node_requests::api::v1::lewes_protocol::models; pub mod root; -#[derive(Debug, Default, Clone, Copy)] +#[derive(Debug, Default, Clone)] pub struct Config { pub details: Option, } diff --git a/nym-node/src/node/http/router/mod.rs b/nym-node/src/node/http/router/mod.rs index 9594621d8b..dbaee0e8d5 100644 --- a/nym-node/src/node/http/router/mod.rs +++ b/nym-node/src/node/http/router/mod.rs @@ -12,7 +12,6 @@ use nym_http_api_common::middleware::logging; use nym_node_requests::api::v1::authenticator::models::Authenticator; use nym_node_requests::api::v1::gateway::models::{Bridges, Gateway}; use nym_node_requests::api::v1::ip_packet_router::models::IpPacketRouter; -use nym_node_requests::api::v1::lewes_protocol::models::LewesProtocol; use nym_node_requests::api::v1::mixnode::models::Mixnode; use nym_node_requests::api::v1::network_requester::exit_policy::models::UsedExitPolicy; use nym_node_requests::api::v1::network_requester::models::NetworkRequester; @@ -133,11 +132,11 @@ impl HttpServerConfig { self } - #[must_use] - pub fn with_lewes_protocol(mut self, lewes_protocol: LewesProtocol) -> Self { - self.api.v1_config.lewes_protocol.details = Some(lewes_protocol); - self - } + // #[must_use] + // pub fn with_lewes_protocol(mut self, lewes_protocol: LewesProtocol) -> Self { + // self.api.v1_config.lewes_protocol.details = Some(lewes_protocol); + // self + // } } pub struct NymNodeRouter { diff --git a/nym-node/src/node/http/state/mod.rs b/nym-node/src/node/http/state/mod.rs index 8ed756aa60..f1c9f2d81b 100644 --- a/nym-node/src/node/http/state/mod.rs +++ b/nym-node/src/node/http/state/mod.rs @@ -7,7 +7,7 @@ use crate::node::key_rotation::active_keys::ActiveSphinxKeys; use nym_credential_verification::UpgradeModeState; use nym_crypto::asymmetric::ed25519; use nym_node_metrics::NymNodeMetrics; -use nym_noise_keys::VersionedNoiseKey; +use nym_noise_keys::VersionedNoiseKeyV1; use nym_verloc::measurements::SharedVerlocStats; use std::net::IpAddr; use std::sync::Arc; @@ -20,7 +20,7 @@ pub mod metrics; pub(crate) struct StaticNodeInformation { pub(crate) ed25519_identity_keys: Arc, - pub(crate) x25519_versioned_noise_key: Option, + pub(crate) x25519_versioned_noise_key: Option, pub(crate) ip_addresses: Vec, pub(crate) hostname: Option, } diff --git a/nym-node/src/node/mod.rs b/nym-node/src/node/mod.rs index 42d803f46d..460889d3db 100644 --- a/nym-node/src/node/mod.rs +++ b/nym-node/src/node/mod.rs @@ -52,7 +52,7 @@ use nym_node_metrics::NymNodeMetrics; use nym_node_metrics::events::MetricEventsSender; use nym_node_requests::api::v1::node::models::{AnnouncePorts, NodeDescription}; use nym_noise::config::{NoiseConfig, NoiseNetworkView}; -use nym_noise_keys::VersionedNoiseKey; +use nym_noise_keys::VersionedNoiseKeyV1; use nym_sphinx_acknowledgements::AckKey; use nym_sphinx_addressing::Recipient; use nym_task::{ShutdownManager, ShutdownToken, ShutdownTracker}; @@ -84,6 +84,7 @@ mod shared_network; pub struct GatewayTasksData { mnemonic: Arc>, + psq_kem_key: Arc, client_storage: nym_gateway::node::GatewayStorage, stats_storage: nym_gateway::node::PersistentStatsStorage, } @@ -105,7 +106,11 @@ impl GatewayTasksData { Ok(()) } - async fn new(config: &GatewayTasksConfig) -> Result { + async fn new( + config: &GatewayTasksConfig, + // this argument is temporary while we still derive KEM x25519 out of identity ed25519 + ed25519_identity: &ed25519::KeyPair, + ) -> Result { let client_storage = nym_gateway::node::GatewayStorage::init( &config.storage_paths.clients_storage, config.debug.message_retrieval_limit, @@ -120,6 +125,7 @@ impl GatewayTasksData { Ok(GatewayTasksData { mnemonic: Arc::new(config.storage_paths.load_mnemonic_from_file()?), + psq_kem_key: Arc::new(ed25519_identity.to_x25519()), client_storage, stats_storage, }) @@ -391,7 +397,6 @@ pub(crate) struct NymNode { sphinx_key_manager: Option, // to be used when noise is integrated - #[allow(dead_code)] x25519_noise_keys: Arc, } @@ -454,10 +459,14 @@ impl NymNode { let current_rotation_id = get_current_rotation_id(&config.mixnet.nym_api_urls, &config.mixnet.nyxd_urls).await?; + let ed25519_identity_keys = load_ed25519_identity_keypair( + &config.storage_paths.keys.ed25519_identity_storage_paths(), + )?; + let entry_gateway = + GatewayTasksData::new(&config.gateway_tasks, &ed25519_identity_keys).await?; + Ok(NymNode { - ed25519_identity_keys: Arc::new(load_ed25519_identity_keypair( - &config.storage_paths.keys.ed25519_identity_storage_paths(), - )?), + ed25519_identity_keys: Arc::new(ed25519_identity_keys), sphinx_key_manager: Some(SphinxKeyManager::try_load_or_regenerate( current_rotation_id, &config.storage_paths.keys.primary_x25519_sphinx_key_file, @@ -469,7 +478,7 @@ impl NymNode { description: load_node_description(&config.storage_paths.description)?, metrics: NymNodeMetrics::new(), verloc_stats: Default::default(), - entry_gateway: GatewayTasksData::new(&config.gateway_tasks).await?, + entry_gateway, upgrade_mode_state: UpgradeModeState::new( config.gateway_tasks.upgrade_mode.attester_public_key, ), @@ -631,6 +640,8 @@ impl NymNode { let mut gateway_tasks_builder = GatewayTasksBuilder::new( config.gateway, self.ed25519_identity_keys.clone(), + self.x25519_noise_keys.clone(), + self.entry_gateway.psq_kem_key.clone(), self.entry_gateway.client_storage.clone(), mix_packet_sender, metrics_sender, @@ -657,6 +668,26 @@ impl NymNode { let upgrade_mode_common_state = gateway_tasks_builder.build_upgrade_mode_common_state(upgrade_check_request_sender); + // Set WireGuard data early so other builders can access it + if self.config.wireguard.enabled { + let Some(wg_data) = self.wireguard.take() else { + return Err(NymNodeError::WireguardDataUnavailable); + }; + gateway_tasks_builder.set_wireguard_data(wg_data.into()); + } + + let wg_peer_registrator = gateway_tasks_builder + .build_peer_registrator(upgrade_mode_common_state.clone()) + .await?; + + if let Some(wg_peer_registrator) = wg_peer_registrator.as_ref() { + let cleanup_task = wg_peer_registrator.cleanup_task(self.shutdown_token()); + self.shutdown_tracker().try_spawn_named( + async move { cleanup_task.run().await }, + "StaleRegistrationRemover", + ); + }; + // if we're running in entry mode, start the websocket if self.modes().entry { info!( @@ -672,26 +703,19 @@ impl NymNode { self.shutdown_tracker() .try_spawn_named(async move { websocket.run().await }, "EntryWebsocket"); - // Set WireGuard data early so LP listener can access it - // (LP listener needs wg_peer_controller for dVPN registrations) - if self.config.wireguard.enabled { - let Some(wg_data) = self.wireguard.take() else { - return Err(NymNodeError::WireguardDataUnavailable); - }; - gateway_tasks_builder.set_wireguard_data(wg_data.into()); - } - // Start LP listener if enabled info!( "starting the LP listener on {} (data handler on: {})", self.config.gateway_tasks.lp.control_bind_address, self.config.gateway_tasks.lp.data_bind_address, ); - let mut lp_listener = gateway_tasks_builder - .build_lp_listener(active_clients_store.clone()) + let lp_listener = gateway_tasks_builder + .build_lp_listener(wg_peer_registrator.clone(), active_clients_store.clone()) .await?; - self.shutdown_tracker() - .try_spawn_named(async move { lp_listener.run().await }, "LpListener"); + // make sure lp listener can be built, but do not start it + let _ = lp_listener; + // self.shutdown_tracker() + // .try_spawn_named(async move { lp_listener.run().await }, "LpListener"); } else { info!("node not running in entry mode: the websocket and LP will remain closed"); } @@ -728,8 +752,16 @@ impl NymNode { gateway_tasks_builder.set_authenticator_opts(config.auth_opts); + let Some(peer_registrator) = wg_peer_registrator else { + return Err(NymNodeError::WireguardDataUnavailable); + }; + let authenticator = gateway_tasks_builder - .build_wireguard_authenticator(upgrade_mode_common_state.clone(), topology_provider) + .build_wireguard_authenticator( + peer_registrator, + upgrade_mode_common_state.clone(), + topology_provider, + ) .await?; let started_authenticator = authenticator.start_service_provider().await?; active_clients_store.insert_embedded(started_authenticator.handle); @@ -759,6 +791,40 @@ impl NymNode { Ok(()) } + // + // fn compute_kem_key_hashes(&self) -> HashMap> { + // let kem = LPKEM::X25519; + // + // let kem_key_bytes = self.entry_gateway.psq_kem_key.public_key().as_bytes(); + // + // // convert from `nym_kkt_ciphersuite` types into `nym_nodes_requests` + // let digests = produce_key_digests(kem_key_bytes.as_ref()) + // .into_iter() + // .map(|(f, digest)| (f.into(), hex::encode(&digest))) + // .collect(); + // + // let mut hashes = HashMap::new(); + // hashes.insert(kem, digests); + // hashes + // } + // + // fn compute_signing_key_hashes( + // &self, + // ) -> HashMap> { + // let scheme = LPSignatureScheme::Ed25519; + // + // let kem_key_bytes = self.ed25519_identity_keys.public_key().as_bytes(); + // + // // convert from `nym_kkt_ciphersuite` types into `nym_nodes_requests` + // let digests = produce_key_digests(kem_key_bytes.as_ref()) + // .into_iter() + // .map(|(f, digest)| (f.into(), hex::encode(&digest))) + // .collect(); + // + // let mut hashes = HashMap::new(); + // hashes.insert(scheme, digests); + // hashes + // } pub(crate) async fn build_http_server(&self) -> Result { let auxiliary_details = api_requests::v1::node::models::AuxiliaryDetails { @@ -833,12 +899,6 @@ impl NymNode { policy: None, }; - let lp_details = api_requests::v1::lewes_protocol::models::LewesProtocol { - enabled: self.modes().entry, - control_port: self.config.gateway_tasks.lp.announced_control_port(), - data_port: self.config.gateway_tasks.lp.announced_data_port(), - }; - let mut config = HttpServerConfig::new() .with_landing_page_assets(self.config.http.landing_page_assets_path.as_ref()) .with_mixnode_details(mixnode_details) @@ -849,8 +909,7 @@ impl NymNode { .with_used_exit_policy(exit_policy_details) .with_description(self.description.clone()) .with_auxiliary_details(auxiliary_details) - .with_prometheus_bearer_token(self.config.http.access_token.clone()) - .with_lewes_protocol(lp_details); + .with_prometheus_bearer_token(self.config.http.access_token.clone()); if self.config.http.expose_system_info { config = config.with_system_info(get_system_info( @@ -878,7 +937,7 @@ impl NymNode { let x25519_versioned_noise_key = if self.config.mixnet.debug.unsafe_disable_noise { None } else { - Some(VersionedNoiseKey { + Some(VersionedNoiseKeyV1 { supported_version: nym_noise::LATEST_NOISE_VERSION, x25519_pubkey: *self.x25519_noise_keys.public_key(), }) diff --git a/nym-outfox/Cargo.toml b/nym-outfox/Cargo.toml index 0c3c3e8763..8d7855bc48 100644 --- a/nym-outfox/Cargo.toml +++ b/nym-outfox/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-outfox" -version = "0.1.0" +version.workspace = true description = "Outfox package format" edition = { workspace = true } authors = { workspace = true } diff --git a/nym-registration-client/Cargo.toml b/nym-registration-client/Cargo.toml index efa0b5c5a6..3209fc7e47 100644 --- a/nym-registration-client/Cargo.toml +++ b/nym-registration-client/Cargo.toml @@ -1,12 +1,14 @@ [package] name = "nym-registration-client" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "Crate for dealing with Mixnet and Wireguard registration between Nym clients and Gateways" +publish = false [lints] workspace = true @@ -22,15 +24,16 @@ tracing.workspace = true typed-builder.workspace = true url.workspace = true -nym-authenticator-client = { path = "../nym-authenticator-client" } -nym-bandwidth-controller = { path = "../common/bandwidth-controller" } -nym-credential-storage = { path = "../common/credential-storage" } -nym-credentials-interface = { path = "../common/credentials-interface" } -nym-crypto = { path = "../common/crypto" } -nym-ip-packet-client = { path = "../nym-ip-packet-client" } + +nym-authenticator-client = { workspace = true } +nym-bandwidth-controller = { workspace = true } +nym-credential-storage = { workspace = true } +nym-credentials-interface = { workspace = true } +nym-crypto = { workspace = true } +nym-ip-packet-client = { workspace = true } nym-lp = { path = "../common/nym-lp" } nym-lp-transport = { path = "../common/nym-lp-transport" } -nym-registration-common = { path = "../common/registration" } -nym-sdk = { path = "../sdk/rust/nym-sdk" } -nym-validator-client = { path = "../common/client-libs/validator-client" } +nym-registration-common = { workspace = true } +nym-sdk = { workspace = true } +nym-validator-client = { workspace = true } nym-wireguard-types = { path = "../common/wireguard-types" } diff --git a/nym-registration-client/src/builder/config.rs b/nym-registration-client/src/builder/config.rs index bf3dff774c..302227fd34 100644 --- a/nym-registration-client/src/builder/config.rs +++ b/nym-registration-client/src/builder/config.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: GPL-3.0-only use nym_credential_storage::persistent_storage::PersistentStorage; -use nym_registration_common::NymNode; +use nym_registration_common::NymNodeInformation; use nym_sdk::{ DebugConfig, NymNetworkDetails, RememberMe, TopologyProvider, UserAgent, mixnet::{ @@ -17,33 +17,45 @@ use std::{path::PathBuf, sync::Arc, time::Duration}; use tokio_util::sync::CancellationToken; use typed_builder::TypedBuilder; -use crate::config::RegistrationMode; -use crate::error::RegistrationClientError; +use crate::{LpRegistrationConfig, config::RegistrationMode}; +use crate::{config::RegistrationClientConfig, error::RegistrationClientError}; const VPN_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(15); const MIXNET_CLIENT_STARTUP_TIMEOUT: Duration = Duration::from_secs(30); #[derive(Clone)] pub struct NymNodeWithKeys { - pub node: NymNode, + pub node: NymNodeInformation, pub keys: Arc, } #[derive(TypedBuilder)] pub struct BuilderConfig { + // For future reference + // Common options pub entry_node: NymNodeWithKeys, pub exit_node: NymNodeWithKeys, pub data_path: Option, + pub mode: RegistrationMode, + pub cancel_token: CancellationToken, + + // Toggle + #[builder(default)] + pub enable_lp_regitration: bool, + + // Mixnet based only option pub mixnet_client_config: MixnetClientConfig, #[builder(default = MIXNET_CLIENT_STARTUP_TIMEOUT)] pub mixnet_client_startup_timeout: Duration, - pub mode: RegistrationMode, pub user_agent: UserAgent, pub custom_topology_provider: Box, pub network_env: NymNetworkDetails, - pub cancel_token: CancellationToken, #[cfg(unix)] pub connection_fd_callback: Arc, + + // LP based only option + #[builder(default)] + pub lp_registration_config: LpRegistrationConfig, } #[derive(Clone, Default, Debug, Eq, PartialEq)] @@ -75,29 +87,36 @@ impl BuilderConfig { // Mixnet mode uses 5-hop configuration RegistrationMode::Mixnet => mixnet_debug_config(&self.mixnet_client_config), // Wireguard and LP both use 2-hop configuration - RegistrationMode::Wireguard | RegistrationMode::Lp => { - two_hop_debug_config(&self.mixnet_client_config) - } + RegistrationMode::Wireguard => two_hop_debug_config(&self.mixnet_client_config), } } - pub async fn setup_storage( + pub fn registration_client_config(&self) -> RegistrationClientConfig { + RegistrationClientConfig { + entry: self.entry_node.clone(), + exit: self.exit_node.clone(), + mode: self.mode, + lp_registration_config: self.lp_registration_config, + } + } + + pub async fn setup_mixnet_client_storage( &self, ) -> Result, RegistrationClientError> { if let Some(path) = &self.data_path { tracing::debug!("Using custom key storage path: {}", path.display()); let storage_paths = StoragePaths::new_from_dir(path) - .map_err(|err| RegistrationClientError::BuildMixnetClient(Box::new(err)))?; + .map_err(|err| RegistrationClientError::StorageInitialization(Box::new(err)))?; let mixnet_client_storage = storage_paths .initialise_persistent_storage(&self.mixnet_client_debug_config()) .await - .map_err(|err| RegistrationClientError::BuildMixnetClient(Box::new(err)))?; + .map_err(|err| RegistrationClientError::StorageInitialization(Box::new(err)))?; let credential_storage = storage_paths .persistent_credential_storage() .await - .map_err(|err| RegistrationClientError::BuildMixnetClient(Box::new(err)))?; + .map_err(|err| RegistrationClientError::StorageInitialization(Box::new(err)))?; Ok(Some((mixnet_client_storage, credential_storage))) } else { @@ -105,6 +124,26 @@ impl BuilderConfig { } } + pub async fn setup_credential_storage( + &self, + ) -> Result, RegistrationClientError> { + if let Some(path) = &self.data_path { + tracing::debug!("Using custom credential storage path: {}", path.display()); + + let storage_paths = StoragePaths::new_from_dir(path) + .map_err(|err| RegistrationClientError::StorageInitialization(Box::new(err)))?; + + let credential_storage = storage_paths + .persistent_credential_storage() + .await + .map_err(|err| RegistrationClientError::StorageInitialization(Box::new(err)))?; + + Ok(Some(credential_storage)) + } else { + Ok(None) + } + } + pub async fn build_and_connect_mixnet_client( self, builder: MixnetClientBuilder, @@ -121,7 +160,7 @@ impl BuilderConfig { let debug_config = self.mixnet_client_debug_config(); let remember_me = match self.mode { RegistrationMode::Mixnet => RememberMe::new_mixnet(), - RegistrationMode::Wireguard | RegistrationMode::Lp => RememberMe::new_vpn(), + RegistrationMode::Wireguard => RememberMe::new_vpn(), }; let identity = self.entry_node.node.identity.to_string(); diff --git a/nym-registration-client/src/builder/mod.rs b/nym-registration-client/src/builder/mod.rs index 9e568baa18..37e66a03df 100644 --- a/nym-registration-client/src/builder/mod.rs +++ b/nym-registration-client/src/builder/mod.rs @@ -13,7 +13,10 @@ use nym_validator_client::{ nyxd::{Config as NyxdClientConfig, NyxdClient}, }; -use crate::{RegistrationClient, config::RegistrationClientConfig, error::RegistrationClientError}; +use crate::{ + RegistrationClient, clients::MixnetBasedRegistrationClient, config::RegistrationMode, + error::RegistrationClientError, +}; use config::BuilderConfig; pub(crate) mod config; @@ -27,13 +30,41 @@ impl RegistrationClientBuilder { Self { config } } + pub fn use_lp(&self) -> bool { + let lp_enabled = self.config.enable_lp_regitration; + let lp_info_available = self.config.entry_node.node.lp_data.is_some() + && self.config.exit_node.node.lp_data.is_some(); + // To remove when LP supports Mixnet registration + let wireguard_mode = self.config.mode == RegistrationMode::Wireguard; + let use_lp = lp_enabled && lp_info_available && wireguard_mode; + if !use_lp && lp_enabled { + tracing::warn!( + "LP is enabled but can't be used: Missing LP information: {lp_info_available}, wireguard mode: {wireguard_mode}" + ); + } + use_lp + } + pub async fn build(self) -> Result { - let storage = self.config.setup_storage().await?; - let config = RegistrationClientConfig { - entry: self.config.entry_node.clone(), - exit: self.config.exit_node.clone(), - mode: self.config.mode, - }; + if self.use_lp() { + tracing::debug!("Using LP for registration"); + Err(RegistrationClientError::LpRegistrationNotPossible { + node_id: "any".to_string(), + }) + // Ok(RegistrationClient::Lp(Box::new(self.build_lp().await?))) + } else { + tracing::debug!("Using Mixnet for registration"); + Ok(RegistrationClient::Mixnet(Box::new( + self.build_mixnet().await?, + ))) + } + } + + pub(crate) async fn build_mixnet( + self, + ) -> Result { + let storage = self.config.setup_mixnet_client_storage().await?; + let config = self.config.registration_client_config(); let cancel_token = self.config.cancel_token.clone(); let (event_tx, event_rx) = mpsc::unbounded(); @@ -83,7 +114,7 @@ impl RegistrationClientBuilder { }; let mixnet_client_address = *mixnet_client.nym_address(); - Ok(RegistrationClient { + Ok(MixnetBasedRegistrationClient { mixnet_client, config, cancel_token, @@ -92,6 +123,30 @@ impl RegistrationClientBuilder { event_rx, }) } + + // async fn build_lp(self) -> Result { + // let storage = self.config.setup_credential_storage().await?; + // let config = self.config.registration_client_config(); + // + // let nyxd_client = get_nyxd_client(&self.config.network_env)?; + // + // let bandwidth_controller: Box = + // if let Some(credential_storage) = storage { + // Box::new(BandwidthController::new(credential_storage, nyxd_client)) + // } else { + // Box::new(BandwidthController::new( + // EphemeralCredentialStorage::default(), + // nyxd_client, + // )) + // }; + // + // Ok(LpBasedRegistrationClient { + // config, + // bandwidth_controller, + // cancel_token: self.config.cancel_token.clone(), + // fallback_client_builder: Some(self), + // }) + // } } // temporary while we use the legacy bandwidth-controller diff --git a/nym-registration-client/src/clients/lp.rs b/nym-registration-client/src/clients/lp.rs new file mode 100644 index 0000000000..3f3ce0abbc --- /dev/null +++ b/nym-registration-client/src/clients/lp.rs @@ -0,0 +1,202 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +#![allow(dead_code)] + +use crate::builder::RegistrationClientBuilder; +use crate::config::RegistrationClientConfig; +use crate::config::RegistrationMode; +use crate::error::RegistrationClientError; +use crate::lp_client::helpers::to_lp_remote_peer; +use crate::lp_client::{LpRegistrationClient, NestedLpSession}; +use crate::types::{LpRegistrationResult, RegistrationResult}; + +use nym_bandwidth_controller::BandwidthTicketProvider; +use nym_credentials_interface::TicketType; +use nym_crypto::aes::cipher::crypto_common::rand_core::{CryptoRng, RngCore}; +use nym_crypto::asymmetric::ed25519; + +use rand::rngs::OsRng; +use std::sync::Arc; +use tokio::net::TcpStream; +use tokio_util::sync::CancellationToken; + +pub struct LpBasedRegistrationClient { + pub(crate) config: RegistrationClientConfig, + pub(crate) bandwidth_controller: Box, + pub(crate) cancel_token: CancellationToken, + // While we allow a fallback, we need to be able to build it + pub(crate) fallback_client_builder: Option, +} + +impl LpBasedRegistrationClient { + // create dedicated method taking RNG instance for tests + async fn register_wg_with_rng( + self, + rng: &mut R, + ) -> Result + where + R: RngCore + CryptoRng, + { + // Extract and validate LP data + let entry_lp_data = self.config.entry.node.lp_data.ok_or( + RegistrationClientError::LpRegistrationNotPossible { + node_id: self.config.entry.node.identity.to_base58_string(), + }, + )?; + + let exit_lp_data = self.config.exit.node.lp_data.ok_or( + RegistrationClientError::LpRegistrationNotPossible { + node_id: self.config.exit.node.identity.to_base58_string(), + }, + )?; + + let entry_lp_protocol = entry_lp_data.lp_protocol_version; + let exit_lp_protocol = exit_lp_data.lp_protocol_version; + + let entry_address = entry_lp_data.address; + let exit_address = exit_lp_data.address; + + tracing::debug!("Entry gateway LP address: {entry_address}"); + tracing::debug!("Exit gateway LP address: {exit_address}"); + + // Generate fresh Ed25519 keypairs for LP registration + // These are ephemeral and used only for the LP handshake protocol + let entry_lp_keypair = Arc::new(ed25519::KeyPair::new(&mut OsRng)); + let exit_lp_keypair = Arc::new(ed25519::KeyPair::new(&mut OsRng)); + + let entry_peer = to_lp_remote_peer(self.config.entry.node.identity, entry_lp_data); + let exit_peer = to_lp_remote_peer(self.config.exit.node.identity, exit_lp_data); + + // STEP 1: Establish outer session with entry gateway + // This creates the LP session that will be used to forward packets to exit. + // Uses packet-per-connection model: each handshake packet on new TCP connection. + tracing::info!("Establishing outer session with entry gateway"); + let mut entry_client = LpRegistrationClient::new( + entry_lp_keypair.clone(), + entry_peer, + entry_address, + entry_lp_protocol, + self.config.lp_registration_config, + ); + + // Perform handshake with entry gateway (outer session now established) + entry_client.perform_handshake().await.map_err(|source| { + RegistrationClientError::EntryGatewayRegisterLp { + gateway_id: self.config.entry.node.identity.to_base58_string(), + lp_address: entry_address, + source: Box::new(source), + } + })?; + + tracing::info!("Outer session with entry gateway established"); + + // STEP 2: Use nested session to register with exit gateway via forwarding + // This hides the client's IP address from the exit gateway + tracing::info!("Registering with exit gateway via entry forwarding"); + let mut nested_session = + NestedLpSession::new(exit_address, exit_lp_keypair, exit_peer, exit_lp_protocol); + + // Perform handshake and registration with exit gateway (all via entry forwarding) + let exit_gateway_data = nested_session + .handshake_and_register_dvpn::( + &mut entry_client, + rng, + &self.config.exit.keys, + &self.config.exit.node.identity, + &*self.bandwidth_controller, + TicketType::V1WireguardExit, + ) + .await + .map_err(|source| RegistrationClientError::ExitGatewayRegisterLp { + gateway_id: self.config.exit.node.identity.to_base58_string(), + lp_address: exit_address, + source: Box::new(source), + })?; + + tracing::info!("Exit gateway registration completed via forwarding"); + + // STEP 3: Register with entry gateway (packet-per-connection) + tracing::info!("Registering with entry gateway"); + let entry_gateway_data = entry_client + .register_dvpn( + rng, + &self.config.entry.keys, + &self.config.entry.node.identity, + &*self.bandwidth_controller, + TicketType::V1WireguardEntry, + ) + .await + .map_err(|source| RegistrationClientError::EntryGatewayRegisterLp { + gateway_id: self.config.entry.node.identity.to_base58_string(), + lp_address: entry_address, + source: Box::new(source), + })?; + + tracing::info!("Entry gateway registration successful"); + + tracing::info!("LP registration successful for both gateways"); + + // LP is registration-only (packet-per-connection model). + // All data flows through WireGuard after this point. + // Each LP packet used its own TCP connection which was closed after the exchange. + // Exit registration was completed via forwarding through entry gateway. + Ok(RegistrationResult::Lp(Box::new(LpRegistrationResult { + entry_gateway_data, + exit_gateway_data, + bw_controller: self.bandwidth_controller, + }))) + } + + async fn register_wg(self) -> Result { + let mut rng = rand::rngs::OsRng; + + self.register_wg_with_rng(&mut rng).await + } + + pub(crate) async fn register(mut self) -> Result { + let fallback = self.fallback_client_builder.take(); + match &self.config.mode { + RegistrationMode::Mixnet => { + if let Some(fallback) = fallback { + register_with_fallback(fallback).await + } else { + Err(RegistrationClientError::UnsupportedMode) + } + } + RegistrationMode::Wireguard => { + let lp_registration_result = self + .cancel_token + .clone() + .run_until_cancelled(self.register_wg()) + .await; + match lp_registration_result { + // Everything went fine + Some(Ok(res)) => Ok(res), + + // LP reg failed, try fallback if we have one + Some(Err(e)) => { + tracing::error!("LP registration failed : {e}"); + if let Some(fallback) = fallback { + tracing::info!("Registering with fallback"); + register_with_fallback(fallback).await + } else { + Err(e) + } + } + + // Cancelled registration + None => Err(RegistrationClientError::Cancelled), + } + } + } + } +} + +async fn register_with_fallback( + client_builder: RegistrationClientBuilder, +) -> Result { + // This is forcefully building a mixnet based client + let fallback_client = client_builder.build_mixnet().await?; + fallback_client.register().await +} diff --git a/nym-registration-client/src/clients/mixnet.rs b/nym-registration-client/src/clients/mixnet.rs new file mode 100644 index 0000000000..859eee94ea --- /dev/null +++ b/nym-registration-client/src/clients/mixnet.rs @@ -0,0 +1,232 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::config::RegistrationClientConfig; +use crate::config::RegistrationMode; +use crate::error::RegistrationClientError; +use crate::types::{MixnetRegistrationResult, RegistrationResult, WireguardRegistrationResult}; +use nym_authenticator_client::AuthClientMixnetListenerHandle; +use nym_authenticator_client::{AuthClientMixnetListener, AuthenticatorClient}; +use nym_bandwidth_controller::BandwidthTicketProvider; +use nym_credentials_interface::TicketType; +use nym_ip_packet_client::IprClientConnect; +use nym_registration_common::AssignedAddresses; +use nym_sdk::mixnet::{EventReceiver, MixnetClient, Recipient}; +use tokio_util::sync::CancellationToken; + +pub struct MixnetBasedRegistrationClient { + pub(crate) mixnet_client: MixnetClient, + pub(crate) config: RegistrationClientConfig, + pub(crate) mixnet_client_address: Recipient, + pub(crate) bandwidth_controller: Box, + pub(crate) cancel_token: CancellationToken, + pub(crate) event_rx: EventReceiver, +} + +enum MixnetClientHandle { + Authenticator(AuthClientMixnetListenerHandle), + Sdk(Box), +} + +impl MixnetClientHandle { + async fn stop(self) { + match self { + Self::Authenticator(handle) => handle.stop().await, + Self::Sdk(handle) => handle.disconnect().await, + } + } +} +// Bundle of an actual error and the underlying mixnet client so it can be shutdown correctly if needed +struct RegistrationError { + mixnet_client_handle: MixnetClientHandle, + source: crate::RegistrationClientError, +} + +impl MixnetBasedRegistrationClient { + async fn register_mix_exit(self) -> Result { + let entry_mixnet_gateway_ip = self.config.entry.node.ip_address; + + let exit_mixnet_gateway_ip = self.config.exit.node.ip_address; + + let Some(ipr_address) = self.config.exit.node.ipr_address else { + return Err(RegistrationError { + mixnet_client_handle: MixnetClientHandle::Sdk(Box::new(self.mixnet_client)), + source: RegistrationClientError::NoIpPacketRouterAddress { + node_id: self.config.exit.node.identity.to_base58_string(), + }, + }); + }; + + let mut ipr_client = + IprClientConnect::new(self.mixnet_client, self.cancel_token.child_token()); + + let interface_addresses = match self + .cancel_token + .run_until_cancelled(ipr_client.connect(ipr_address)) + .await + { + Some(Ok(addr)) => addr, + Some(Err(e)) => { + return Err(RegistrationError { + mixnet_client_handle: MixnetClientHandle::Sdk(Box::new( + ipr_client.into_mixnet_client(), + )), + source: RegistrationClientError::ConnectToIpPacketRouter(e), + }); + } + None => { + return Err(RegistrationError { + mixnet_client_handle: MixnetClientHandle::Sdk(Box::new( + ipr_client.into_mixnet_client(), + )), + source: RegistrationClientError::Cancelled, + }); + } + }; + + Ok(RegistrationResult::Mixnet(Box::new( + MixnetRegistrationResult { + mixnet_client: ipr_client.into_mixnet_client(), + assigned_addresses: AssignedAddresses { + interface_addresses, + exit_mix_address: ipr_address, + mixnet_client_address: self.mixnet_client_address, + entry_mixnet_gateway_ip, + exit_mixnet_gateway_ip, + }, + event_rx: self.event_rx, + }, + ))) + } + + async fn register_wg(self) -> Result { + let Some(entry_auth_address) = self.config.entry.node.authenticator_address else { + return Err(RegistrationError { + mixnet_client_handle: MixnetClientHandle::Sdk(Box::new(self.mixnet_client)), + source: RegistrationClientError::AuthenticationNotPossible { + node_id: self.config.entry.node.identity.to_base58_string(), + }, + }); + }; + + let Some(exit_auth_address) = self.config.exit.node.authenticator_address else { + return Err(RegistrationError { + mixnet_client_handle: MixnetClientHandle::Sdk(Box::new(self.mixnet_client)), + source: RegistrationClientError::AuthenticationNotPossible { + node_id: self.config.exit.node.identity.to_base58_string(), + }, + }); + }; + + let entry_version = self.config.entry.node.version; + tracing::debug!("Entry gateway version: {entry_version}"); + let exit_version = self.config.exit.node.version; + tracing::debug!("Exit gateway version: {exit_version}"); + + // Start the auth client mixnet listener, which will listen for incoming messages from the + // mixnet and rebroadcast them to the auth clients. + let mixnet_listener = + AuthClientMixnetListener::new(self.mixnet_client, self.cancel_token.child_token()) + .start(); + + let mut entry_auth_client = AuthenticatorClient::new( + mixnet_listener.subscribe(), + mixnet_listener.mixnet_sender(), + self.mixnet_client_address, + entry_auth_address, + entry_version, + self.config.entry.keys, + self.config.entry.node.ip_address, + ); + + let mut exit_auth_client = AuthenticatorClient::new( + mixnet_listener.subscribe(), + mixnet_listener.mixnet_sender(), + self.mixnet_client_address, + exit_auth_address, + exit_version, + self.config.exit.keys, + self.config.exit.node.ip_address, + ); + + let entry_fut = entry_auth_client + .register_wireguard(&*self.bandwidth_controller, TicketType::V1WireguardEntry); + let exit_fut = exit_auth_client + .register_wireguard(&*self.bandwidth_controller, TicketType::V1WireguardExit); + + let (entry, exit) = match Box::pin( + self.cancel_token + .run_until_cancelled(async { tokio::join!(entry_fut, exit_fut) }), + ) + .await + { + Some((entry, exit)) => (entry, exit), + None => { + return Err(RegistrationError { + mixnet_client_handle: MixnetClientHandle::Authenticator(mixnet_listener), + source: RegistrationClientError::Cancelled, + }); + } + }; + + let entry = match entry { + Ok(entry) => entry, + Err(source) => { + return Err(RegistrationError { + mixnet_client_handle: MixnetClientHandle::Authenticator(mixnet_listener), + source: RegistrationClientError::from_authenticator_error( + source, + self.config.entry.node.identity.to_base58_string(), + entry_auth_address, + true, + ), + }); + } + }; + + let exit = match exit { + Ok(exit) => exit, + Err(source) => { + return Err(RegistrationError { + mixnet_client_handle: MixnetClientHandle::Authenticator(mixnet_listener), + source: RegistrationClientError::from_authenticator_error( + source, + self.config.exit.node.identity.to_base58_string(), + exit_auth_address, + false, + ), + }); + } + }; + + Ok(RegistrationResult::Wireguard(Box::new( + WireguardRegistrationResult { + entry_gateway_client: entry_auth_client, + exit_gateway_client: exit_auth_client, + entry_gateway_data: entry, + exit_gateway_data: exit, + authenticator_listener_handle: mixnet_listener, + bw_controller: self.bandwidth_controller, + }, + ))) + } + + pub(crate) async fn register(self) -> Result { + let registration_result = match self.config.mode { + RegistrationMode::Mixnet => self.register_mix_exit().await, + RegistrationMode::Wireguard => self.register_wg().await, + }; + + // If we failed to register, shut down the mixnet client and wait for it to exit + match registration_result { + Ok(result) => Ok(result), + Err(error) => { + tracing::debug!("Registration failed"); + tracing::debug!("Shutting down mixnet client"); + error.mixnet_client_handle.stop().await; + tracing::debug!("Mixnet client stopped"); + Err(error.source) + } + } + } +} diff --git a/nym-registration-client/src/clients/mod.rs b/nym-registration-client/src/clients/mod.rs new file mode 100644 index 0000000000..3a96c164de --- /dev/null +++ b/nym-registration-client/src/clients/mod.rs @@ -0,0 +1,10 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +#![allow(unused)] + +pub mod lp; +pub mod mixnet; + +pub use lp::LpBasedRegistrationClient; +pub use mixnet::MixnetBasedRegistrationClient; diff --git a/nym-registration-client/src/config.rs b/nym-registration-client/src/config.rs index b5b59435fb..b045049021 100644 --- a/nym-registration-client/src/config.rs +++ b/nym-registration-client/src/config.rs @@ -1,6 +1,8 @@ // Copyright 2025 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only +use crate::LpRegistrationConfig; + use crate::builder::config::NymNodeWithKeys; /// Registration mode for the client @@ -8,26 +10,13 @@ use crate::builder::config::NymNodeWithKeys; pub enum RegistrationMode { /// 5-hop mixnet with IPR (IP Packet Router) Mixnet, - /// 2-hop WireGuard with authenticator + /// 2-hop WireGuard Wireguard, - /// 2-hop WireGuard with LP (Lewes Protocol) - Lp, -} - -impl RegistrationMode { - /// Legacy method for backward compatibility - #[deprecated(note = "use explicit enum variant instead")] - pub fn legacy_two_hop(use_two_hop: bool) -> RegistrationMode { - if use_two_hop { - Self::Wireguard - } else { - Self::Mixnet - } - } } pub struct RegistrationClientConfig { pub(crate) entry: NymNodeWithKeys, pub(crate) exit: NymNodeWithKeys, pub(crate) mode: RegistrationMode, + pub(crate) lp_registration_config: LpRegistrationConfig, } diff --git a/nym-registration-client/src/error.rs b/nym-registration-client/src/error.rs index 449d6d4608..a6a85cc5cd 100644 --- a/nym-registration-client/src/error.rs +++ b/nym-registration-client/src/error.rs @@ -3,9 +3,15 @@ #[derive(thiserror::Error, Debug)] pub enum RegistrationClientError { + #[error("trying to register in mixnet mode with LP and no fallback. This shouldn't happen")] + UnsupportedMode, + #[error("failed to build mixnet client")] BuildMixnetClient(#[source] Box), + #[error("failed to initialize storage")] + StorageInitialization(#[source] Box), + #[error("failed to connect to mixnet")] ConnectToMixnet(#[source] Box), diff --git a/nym-registration-client/src/lib.rs b/nym-registration-client/src/lib.rs index 516f7c6245..3ae9cb64fa 100644 --- a/nym-registration-client/src/lib.rs +++ b/nym-registration-client/src/lib.rs @@ -1,24 +1,6 @@ // Copyright 2025 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use tokio_util::sync::CancellationToken; - -use crate::config::RegistrationClientConfig; -use nym_authenticator_client::{AuthClientMixnetListener, AuthenticatorClient}; -use nym_bandwidth_controller::BandwidthTicketProvider; -use nym_credentials_interface::TicketType; -use nym_ip_packet_client::IprClientConnect; -use nym_registration_common::AssignedAddresses; -use nym_sdk::mixnet::{EventReceiver, MixnetClient, Recipient}; -use std::sync::Arc; -use tokio::net::TcpStream; - -mod builder; -mod config; -mod error; -mod lp_client; -mod types; - pub use builder::RegistrationClientBuilder; pub use builder::config::{ BuilderConfig as RegistrationClientBuilderConfig, MixnetClientConfig, @@ -26,250 +8,30 @@ pub use builder::config::{ }; pub use config::RegistrationMode; pub use error::RegistrationClientError; -pub use lp_client::{LpConfig, LpRegistrationClient, NestedLpSession, error::LpClientError}; +pub use lp_client::{ + LpRegistrationClient, LpRegistrationConfig, NestedLpSession, error::LpClientError, +}; pub use types::{ LpRegistrationResult, MixnetRegistrationResult, RegistrationResult, WireguardRegistrationResult, }; -pub struct RegistrationClient { - mixnet_client: MixnetClient, - config: RegistrationClientConfig, - mixnet_client_address: Recipient, - bandwidth_controller: Box, - cancel_token: CancellationToken, - event_rx: EventReceiver, +mod builder; +mod clients; +mod config; +mod error; +mod lp_client; +mod types; + +pub enum RegistrationClient { + Mixnet(Box), + // Lp(Box), } impl RegistrationClient { - async fn register_mix_exit(self) -> Result { - let entry_mixnet_gateway_ip = self.config.entry.node.ip_address; - - let exit_mixnet_gateway_ip = self.config.exit.node.ip_address; - - let ipr_address = self.config.exit.node.ipr_address.ok_or( - RegistrationClientError::NoIpPacketRouterAddress { - node_id: self.config.exit.node.identity.to_base58_string(), - }, - )?; - let mut ipr_client = IprClientConnect::new(self.mixnet_client, self.cancel_token.clone()); - let interface_addresses = ipr_client - .connect(ipr_address) - .await - .map_err(RegistrationClientError::ConnectToIpPacketRouter)?; - - Ok(RegistrationResult::Mixnet(Box::new( - MixnetRegistrationResult { - mixnet_client: ipr_client.into_mixnet_client(), - assigned_addresses: AssignedAddresses { - interface_addresses, - exit_mix_address: ipr_address, - mixnet_client_address: self.mixnet_client_address, - entry_mixnet_gateway_ip, - exit_mixnet_gateway_ip, - }, - event_rx: self.event_rx, - }, - ))) - } - - async fn register_wg(self) -> Result { - let entry_auth_address = self.config.entry.node.authenticator_address.ok_or( - RegistrationClientError::AuthenticationNotPossible { - node_id: self.config.entry.node.identity.to_base58_string(), - }, - )?; - - let exit_auth_address = self.config.exit.node.authenticator_address.ok_or( - RegistrationClientError::AuthenticationNotPossible { - node_id: self.config.exit.node.identity.to_base58_string(), - }, - )?; - - let entry_version = self.config.entry.node.version; - tracing::debug!("Entry gateway version: {entry_version}"); - let exit_version = self.config.exit.node.version; - tracing::debug!("Exit gateway version: {exit_version}"); - - // Start the auth client mixnet listener, which will listen for incoming messages from the - // mixnet and rebroadcast them to the auth clients. - let mixnet_listener = - AuthClientMixnetListener::new(self.mixnet_client, self.cancel_token.clone()).start(); - - let mut entry_auth_client = AuthenticatorClient::new( - mixnet_listener.subscribe(), - mixnet_listener.mixnet_sender(), - self.mixnet_client_address, - entry_auth_address, - entry_version, - self.config.entry.keys, - self.config.entry.node.ip_address, - ); - - let mut exit_auth_client = AuthenticatorClient::new( - mixnet_listener.subscribe(), - mixnet_listener.mixnet_sender(), - self.mixnet_client_address, - exit_auth_address, - exit_version, - self.config.exit.keys, - self.config.exit.node.ip_address, - ); - - let entry_fut = entry_auth_client - .register_wireguard(&*self.bandwidth_controller, TicketType::V1WireguardEntry); - let exit_fut = exit_auth_client - .register_wireguard(&*self.bandwidth_controller, TicketType::V1WireguardExit); - - let (entry, exit) = Box::pin(async { tokio::join!(entry_fut, exit_fut) }).await; - - let entry = entry.map_err(|source| { - RegistrationClientError::from_authenticator_error( - source, - self.config.entry.node.identity.to_base58_string(), - entry_auth_address, - true, // is entry - ) - })?; - let exit = exit.map_err(|source| { - RegistrationClientError::from_authenticator_error( - source, - self.config.exit.node.identity.to_base58_string(), - exit_auth_address, - false, // is exit (not entry) - ) - })?; - - Ok(RegistrationResult::Wireguard(Box::new( - WireguardRegistrationResult { - entry_gateway_client: entry_auth_client, - exit_gateway_client: exit_auth_client, - entry_gateway_data: entry, - exit_gateway_data: exit, - authenticator_listener_handle: mixnet_listener, - bw_controller: self.bandwidth_controller, - }, - ))) - } - - async fn register_lp(self) -> Result { - use crate::lp_client::{LpRegistrationClient, NestedLpSession}; - - // Extract and validate LP addresses - let entry_lp_address = self.config.entry.node.lp_address.ok_or( - RegistrationClientError::LpRegistrationNotPossible { - node_id: self.config.entry.node.identity.to_base58_string(), - }, - )?; - - let exit_lp_address = self.config.exit.node.lp_address.ok_or( - RegistrationClientError::LpRegistrationNotPossible { - node_id: self.config.exit.node.identity.to_base58_string(), - }, - )?; - - tracing::debug!("Entry gateway LP address: {entry_lp_address}"); - tracing::debug!("Exit gateway LP address: {exit_lp_address}"); - - // Generate fresh Ed25519 keypairs for LP registration - // These are ephemeral and used only for the LP handshake protocol - use nym_crypto::asymmetric::ed25519; - use rand::rngs::OsRng; - let entry_lp_keypair = Arc::new(ed25519::KeyPair::new(&mut OsRng)); - let exit_lp_keypair = Arc::new(ed25519::KeyPair::new(&mut OsRng)); - - // STEP 1: Establish outer session with entry gateway - // This creates the LP session that will be used to forward packets to exit. - // Uses packet-per-connection model: each handshake packet on new TCP connection. - tracing::info!("Establishing outer session with entry gateway"); - let mut entry_client = LpRegistrationClient::::new_with_default_psk( - entry_lp_keypair.clone(), - self.config.entry.node.identity, - entry_lp_address, - self.config.entry.node.ip_address, - ); - - // Perform handshake with entry gateway (outer session now established) - entry_client.perform_handshake().await.map_err(|source| { - RegistrationClientError::EntryGatewayRegisterLp { - gateway_id: self.config.entry.node.identity.to_base58_string(), - lp_address: entry_lp_address, - source: Box::new(source), - } - })?; - - tracing::info!("Outer session with entry gateway established"); - - // STEP 2: Use nested session to register with exit gateway via forwarding - // This hides the client's IP address from the exit gateway - tracing::info!("Registering with exit gateway via entry forwarding"); - let mut nested_session = NestedLpSession::new( - self.config.exit.node.identity.to_bytes(), - exit_lp_address.to_string(), - exit_lp_keypair, - self.config.exit.node.identity, - ); - - // Perform handshake and registration with exit gateway (all via entry forwarding) - let exit_gateway_data = nested_session - .handshake_and_register::( - &mut entry_client, - &self.config.exit.keys, - &self.config.exit.node.identity, - &*self.bandwidth_controller, - TicketType::V1WireguardExit, - self.config.exit.node.ip_address, - ) - .await - .map_err(|source| RegistrationClientError::ExitGatewayRegisterLp { - gateway_id: self.config.exit.node.identity.to_base58_string(), - lp_address: exit_lp_address, - source: Box::new(source), - })?; - - tracing::info!("Exit gateway registration completed via forwarding"); - - // STEP 3: Register with entry gateway (packet-per-connection) - tracing::info!("Registering with entry gateway"); - let entry_gateway_data = entry_client - .register( - &self.config.entry.keys, - &self.config.entry.node.identity, - &*self.bandwidth_controller, - TicketType::V1WireguardEntry, - ) - .await - .map_err(|source| RegistrationClientError::EntryGatewayRegisterLp { - gateway_id: self.config.entry.node.identity.to_base58_string(), - lp_address: entry_lp_address, - source: Box::new(source), - })?; - - tracing::info!("Entry gateway registration successful"); - - tracing::info!("LP registration successful for both gateways"); - - // LP is registration-only (packet-per-connection model). - // All data flows through WireGuard after this point. - // Each LP packet used its own TCP connection which was closed after the exchange. - // Exit registration was completed via forwarding through entry gateway. - Ok(RegistrationResult::Lp(Box::new(LpRegistrationResult { - entry_gateway_data, - exit_gateway_data, - bw_controller: self.bandwidth_controller, - }))) - } - pub async fn register(self) -> Result { - self.cancel_token - .clone() - .run_until_cancelled(async { - match self.config.mode { - RegistrationMode::Mixnet => self.register_mix_exit().await, - RegistrationMode::Wireguard => self.register_wg().await, - RegistrationMode::Lp => self.register_lp().await, - } - }) - .await - .ok_or(RegistrationClientError::Cancelled)? + match self { + RegistrationClient::Mixnet(client) => client.register().await, + // RegistrationClient::Lp(client) => client.register().await, + } } } diff --git a/nym-registration-client/src/lp_client/client.rs b/nym-registration-client/src/lp_client/client.rs index a7b482e4c5..1ae732dfb6 100644 --- a/nym-registration-client/src/lp_client/client.rs +++ b/nym-registration-client/src/lp_client/client.rs @@ -3,25 +3,37 @@ //! LP (Lewes Protocol) registration client for direct gateway connections. -use super::config::LpConfig; +use super::config::LpRegistrationConfig; use super::error::{LpClientError, Result}; +use crate::lp_client::helpers::{ + LpDataDeliverExt, LpDataSendExt, convert_forward_data, try_convert_forward_response, +}; +use crate::lp_client::state_machine_helpers::{ + extract_forwarded_response, get_recv_key, get_send_key, prepare_send_packet, +}; use bytes::BytesMut; use nym_bandwidth_controller::{BandwidthTicketProvider, DEFAULT_TICKETS_TO_SPEND}; -use nym_credentials_interface::{CredentialSpendingData, TicketType}; +use nym_credentials_interface::TicketType; use nym_crypto::asymmetric::{ed25519, x25519}; use nym_lp::LpPacket; use nym_lp::codec::{OuterAeadKey, parse_lp_packet, serialize_lp_packet}; use nym_lp::message::ForwardPacketData; -use nym_lp::serialisation::{BincodeOptions, lp_bincode_serializer}; -use nym_lp::state_machine::{LpAction, LpInput, LpStateMachine}; +use nym_lp::packet::version; +use nym_lp::peer::{LpLocalPeer, LpRemotePeer}; +use nym_lp::state_machine::{LpAction, LpData, LpInput, LpStateMachine}; use nym_lp_transport::traits::LpTransport; -use nym_registration_common::{GatewayData, LpRegistrationRequest, LpRegistrationResponse}; +use nym_registration_common::dvpn::LpDvpnRegistrationResponseMessageContent; +use nym_registration_common::{ + LpRegistrationRequest, LpRegistrationResponse, WireguardConfiguration, + WireguardRegistrationData, +}; use nym_wireguard_types::PeerPublicKey; -use std::net::{IpAddr, SocketAddr}; +use rand::{CryptoRng, RngCore}; +use std::net::SocketAddr; use std::sync::Arc; -use std::time::{SystemTime, UNIX_EPOCH}; -use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; use tokio::net::TcpStream; +use tracing::warn; /// LP (Lewes Protocol) registration client for direct gateway connections. /// @@ -37,24 +49,25 @@ use tokio::net::TcpStream; /// // Connection automatically closes after registration /// ``` pub struct LpRegistrationClient { - /// Client's Ed25519 identity keypair (used for PSQ authentication and X25519 derivation). - local_ed25519_keypair: Arc, + /// Encapsulates all the client keys needed for the Lewes Protocol. + lp_local_peer: LpLocalPeer, - /// Gateway's Ed25519 public key (from directory/discovery). - gateway_ed25519_public_key: ed25519::PublicKey, + /// Encapsulates all the gateway keys needed for the Lewes Protocol. + gateway_lp_peer: LpRemotePeer, /// Gateway LP listener address (host:port, e.g., "1.1.1.1:41264"). gateway_lp_address: SocketAddr, + /// Supported protocol version of the remote gateway. + /// Included in case we have to downgrade our version. + gateway_supported_lp_protocol_version: u8, + /// LP state machine for managing connection lifecycle. /// Created during handshake initiation. Persists across packet-per-connection calls. state_machine: Option, - /// Client's IP address for registration metadata. - client_ip: IpAddr, - /// Configuration for timeouts and TCP parameters. - config: LpConfig, + config: LpRegistrationConfig, /// Persistent TCP stream for the connection. /// Opened on first use, closed after registration. @@ -68,27 +81,39 @@ where /// Creates a new LP registration client. /// /// # Arguments - /// * `local_ed25519_keypair` - Client's Ed25519 identity keypair (for PSQ auth and X25519 derivation) - /// * `gateway_ed25519_public_key` - Gateway's Ed25519 public key (from directory/discovery) + /// * `local_ed25519_keypair` - Client's Ed25519 identity keypair + /// * `gateway_lp_peer` - Encapsulates all the gateway keys needed for the Lewes Protocol /// * `gateway_lp_address` - Gateway's LP listener socket address - /// * `client_ip` - Client IP address for registration + /// * `gateway_supported_lp_protocol_version` - Gateway's LP protocol version /// * `config` - Configuration for timeouts and TCP parameters (use `LpConfig::default()`) /// /// # Note /// This creates the client. Call `perform_handshake()` to establish the LP session. pub fn new( local_ed25519_keypair: Arc, - gateway_ed25519_public_key: ed25519::PublicKey, + gateway_lp_peer: LpRemotePeer, gateway_lp_address: SocketAddr, - client_ip: IpAddr, - config: LpConfig, + gateway_supported_lp_protocol_version: u8, + config: LpRegistrationConfig, ) -> Self { + let lp_protocol = if gateway_supported_lp_protocol_version > version::CURRENT { + warn!( + "suggested LP protocol ({gateway_supported_lp_protocol_version}) is higher than the current known version. attempting to downgrade it to {}", + version::CURRENT + ); + version::CURRENT + } else { + gateway_supported_lp_protocol_version + }; + + let local_x25519_keypair = local_ed25519_keypair.to_x25519(); + let lp_local_peer = LpLocalPeer::new(local_ed25519_keypair, Arc::new(local_x25519_keypair)); Self { - local_ed25519_keypair, - gateway_ed25519_public_key, + lp_local_peer, + gateway_lp_peer, gateway_lp_address, + gateway_supported_lp_protocol_version: lp_protocol, state_machine: None, - client_ip, config, stream: None, } @@ -98,28 +123,50 @@ where /// /// # Arguments /// * `local_ed25519_keypair` - Client's Ed25519 identity keypair - /// * `gateway_ed25519_public_key` - Gateway's Ed25519 public key + /// * `gateway_lp_peer` - Encapsulates all the gateway keys needed for the Lewes Protocol /// * `gateway_lp_address` - Gateway's LP listener socket address - /// * `client_ip` - Client IP address for registration + /// * `gateway_supported_lp_protocol_version` - Gateway's LP protocol version /// /// Uses default config (LpConfig::default()) with sane timeout and TCP parameters. /// PSK is derived automatically during handshake inside the state machine. /// For custom config, use `new()` directly. - pub fn new_with_default_psk( + pub fn new_with_default_config( local_ed25519_keypair: Arc, - gateway_ed25519_public_key: ed25519::PublicKey, + gateway_lp_peer: LpRemotePeer, gateway_lp_address: SocketAddr, - client_ip: IpAddr, + gateway_supported_lp_protocol_version: u8, ) -> Self { Self::new( local_ed25519_keypair, - gateway_ed25519_public_key, + gateway_lp_peer, gateway_lp_address, - client_ip, - LpConfig::default(), + gateway_supported_lp_protocol_version, + LpRegistrationConfig::default(), ) } + fn state_machine(&self) -> Result<&LpStateMachine> { + self.state_machine.as_ref().ok_or_else(|| { + LpClientError::transport( + "State machine not available - has the handshake been completed?", + ) + }) + } + + fn state_machine_mut(&mut self) -> Result<&mut LpStateMachine> { + self.state_machine.as_mut().ok_or_else(|| { + LpClientError::transport( + "State machine not available - has the handshake been completed?", + ) + }) + } + + fn stream_mut(&mut self) -> Result<&mut S> { + self.stream + .as_mut() + .ok_or_else(|| LpClientError::transport("Cannot send: not connected")) + } + /// Returns whether the client has completed the handshake and is ready for registration. pub fn is_handshake_complete(&self) -> bool { self.state_machine @@ -134,11 +181,6 @@ where self.gateway_lp_address } - /// Returns the client's IP address. - pub fn client_ip(&self) -> IpAddr { - self.client_ip - } - /// Returns reference to the established connection between the client and the gateway. pub fn connection(&self) -> &Option { &self.stream @@ -200,27 +242,86 @@ where Ok(()) } + /// Attempt to send an Lp packet on the persistent stream + /// and attempt to immediately read a response. + /// + /// Both packets are going to be optionally encrypted/decrypted based on the availability of keys + /// within the internal `LpStateMachine` + /// + /// # Arguments + /// * `packet` - The LP packet to send + /// + /// # Errors + /// Returns an error if not connected or if send or receive fails. + async fn send_and_receive_packet(&mut self, packet: &LpPacket) -> Result { + self.try_send_packet(packet).await?; + self.try_receive_packet().await + } + + /// Attempt to send an Lp packet on the persistent stream + /// and attempt to immediately read a response + /// within the provided timeout. + /// + /// Both packets are going to be optionally encrypted/decrypted based on the availability of keys + /// within the internal `LpStateMachine` + /// + /// # Arguments + /// * `packet` - The LP packet to send + /// + /// # Errors + /// Returns an error if not connected, the timeout has been reached, or if send or receive fails. + async fn send_and_receive_packet_with_timeout( + &mut self, + packet: &LpPacket, + timeout: Duration, + ) -> Result { + tokio::time::timeout(timeout, self.send_and_receive_packet(packet)) + .await + .map_err(|_| LpClientError::ResponseReceiveTimeout { timeout })? + } + /// Sends an LP packet on the persistent stream. /// /// # Arguments /// * `packet` - The LP packet to send + /// + /// # Errors + /// Returns an error if not connected or if send fails. + async fn try_send_packet(&mut self, packet: &LpPacket) -> Result<()> { + let state_machine = self.state_machine()?; + let send_key = get_send_key(state_machine); + self.try_send_packet_with_key(packet, send_key.as_ref()) + .await + } + + /// Sends an LP packet (and optionally encrypted) on the persistent stream. + /// + /// # Arguments + /// * `packet` - The LP packet to send /// * `outer_key` - Optional outer AEAD key for encryption /// /// # Errors /// Returns an error if not connected or if send fails. - async fn send_packet( + async fn try_send_packet_with_key( &mut self, packet: &LpPacket, outer_key: Option<&OuterAeadKey>, ) -> Result<()> { - let stream = self - .stream - .as_mut() - .ok_or_else(|| LpClientError::Transport("Cannot send: not connected".to_string()))?; - + let stream = self.stream_mut()?; Self::send_packet_with_key(stream, packet, outer_key).await } + /// Receives an LP packet from the persistent stream. + /// + /// # Errors + /// Returns an error if not connected or if receive fails. + async fn try_receive_packet(&mut self) -> Result { + let state_machine = self.state_machine()?; + let recv_key = get_recv_key(state_machine); + + self.try_receive_packet_with_key(recv_key.as_ref()).await + } + /// Receives an LP packet from the persistent stream. /// /// # Arguments @@ -228,11 +329,11 @@ where /// /// # Errors /// Returns an error if not connected or if receive fails. - async fn receive_packet(&mut self, outer_key: Option<&OuterAeadKey>) -> Result { - let stream = self - .stream - .as_mut() - .ok_or_else(|| LpClientError::Transport("Cannot receive: not connected".to_string()))?; + async fn try_receive_packet_with_key( + &mut self, + outer_key: Option<&OuterAeadKey>, + ) -> Result { + let stream = self.stream_mut()?; Self::receive_packet_with_key(stream, outer_key).await } @@ -319,27 +420,13 @@ where // Ensure we have a TCP connection self.ensure_connected().await?; - // Step 1: Derive X25519 keys from Ed25519 for Noise protocol (internal to ClientHello) - // The Ed25519 keys are used for PSQ authentication and also converted to X25519 - let client_x25519_public = self - .local_ed25519_keypair - .public_key() - .to_x25519() - .map_err(|e| { - LpClientError::Crypto(format!("Failed to derive X25519 public key: {e}")) - })?; - let timestamp = SystemTime::now() .duration_since(UNIX_EPOCH) .map_err(|_| LpClientError::Other("System time before UNIX epoch".into()))? .as_secs(); - // Step 2: Generate ClientHelloData with fresh salt and both public keys - let client_hello_data = nym_lp::ClientHelloData::new_with_fresh_salt( - client_x25519_public.to_bytes(), - self.local_ed25519_keypair.public_key().to_bytes(), - timestamp, - ); + // Step 1: Generate ClientHelloData with fresh salt and both public keys + let client_hello_data = self.lp_local_peer.build_client_hello_data(timestamp); let salt = client_hello_data.salt; let receiver_index = client_hello_data.receiver_index; @@ -349,10 +436,11 @@ where receiver_index ); - // Step 3: Send ClientHello and receive Ack (persistent connection) + // Step 2: Send ClientHello and receive Ack (persistent connection) let client_hello_header = nym_lp::packet::LpHeader::new( nym_lp::BOOTSTRAP_RECEIVER_IDX, // session_id not yet established 0, // counter starts at 0 + self.gateway_supported_lp_protocol_version, ); let client_hello_packet = nym_lp::LpPacket::new( client_hello_header, @@ -360,11 +448,14 @@ where ); // Send ClientHello (no outer key - before PSK) - self.send_packet(&client_hello_packet, None).await?; + self.try_send_packet_with_key(&client_hello_packet, None) + .await?; // Receive Ack (no outer key - before PSK) - let ack_response = self.receive_packet(None).await?; + let ack_response = self.try_receive_packet_with_key(None).await?; // Verify we received Ack + // this confirms that gateway is fine with our suggested protocol version + // in the future we probably have some fancier negotiation match ack_response.message() { nym_lp::LpMessage::Ack => { tracing::debug!("Received Ack for ClientHello"); @@ -377,20 +468,18 @@ where } } - // Step 4: Create state machine as initiator with Ed25519 keys + // Step 3: Create state machine as initiator with Ed25519 keys // PSK derivation happens internally in the state machine constructor let mut state_machine = LpStateMachine::new( receiver_index, true, // is_initiator - ( - self.local_ed25519_keypair.private_key(), - self.local_ed25519_keypair.public_key(), - ), - &self.gateway_ed25519_public_key, + self.lp_local_peer.clone(), + self.gateway_lp_peer.clone(), &salt, + self.gateway_supported_lp_protocol_version, )?; - // Step 5: Start handshake - get first packet to send (KKT request) + // Step 4: Start handshake - get first packet to send (KKT request) let mut pending_packet: Option = None; if let Some(action) = state_machine.process_input(LpInput::StartHandshake) { match action? { @@ -406,7 +495,7 @@ where } } - // Step 6: Handshake loop - all packets on persistent connection + // Step 5: Handshake loop - all packets on persistent connection loop { // Send pending packet if we have one if let Some(packet) = pending_packet.take() { @@ -427,8 +516,9 @@ where send_key.is_some(), recv_key.is_some() ); - self.send_packet(&packet, send_key.as_ref()).await?; - let response = self.receive_packet(recv_key.as_ref()).await?; + self.try_send_packet_with_key(&packet, send_key.as_ref()) + .await?; + let response = self.try_receive_packet_with_key(recv_key.as_ref()).await?; tracing::trace!("Received handshake response"); // Process the received packet @@ -452,9 +542,10 @@ where .ok() .and_then(|s| s.outer_aead_key()); tracing::trace!("Sending final handshake packet"); - self.send_packet(&final_packet, send_key.as_ref()).await?; + self.try_send_packet_with_key(&final_packet, send_key.as_ref()) + .await?; let ack_response = - self.receive_packet(recv_key.as_ref()).await?; + self.try_receive_packet_with_key(recv_key.as_ref()).await?; // Validate Ack response match ack_response.message() { @@ -544,7 +635,7 @@ where packet: &LpPacket, send_key: Option<&OuterAeadKey>, recv_key: Option<&OuterAeadKey>, - config: &LpConfig, + config: &LpRegistrationConfig, ) -> Result { // 1. Connect with timeout let mut stream = tokio::time::timeout(config.connect_timeout, S::connect(address)) @@ -599,30 +690,10 @@ where serialize_lp_packet(packet, &mut packet_buf, outer_key) .map_err(|e| LpClientError::Transport(format!("Failed to serialize packet: {e}")))?; - // Send 4-byte length prefix (u32 big-endian) - let len = packet_buf.len() as u32; stream - .write_all(&len.to_be_bytes()) + .send_serialised_packet(&packet_buf) .await - .map_err(|e| LpClientError::Transport(format!("Failed to send packet length: {e}")))?; - - // Send the actual packet data - stream - .write_all(&packet_buf) - .await - .map_err(|e| LpClientError::Transport(format!("Failed to send packet data: {e}")))?; - - // Flush to ensure data is sent immediately - stream - .flush() - .await - .map_err(|e| LpClientError::Transport(format!("Failed to flush stream: {e}")))?; - - tracing::trace!( - "Sent LP packet ({} bytes + 4 byte header)", - packet_buf.len() - ); - Ok(()) + .map_err(|err| LpClientError::Transport(err.to_string())) } /// Receives an LP packet from a TCP stream with length-prefixed framing. @@ -642,39 +713,108 @@ where stream: &mut S, outer_key: Option<&OuterAeadKey>, ) -> Result { - // Read 4-byte length prefix (u32 big-endian) - let mut len_buf = [0u8; 4]; - stream - .read_exact(&mut len_buf) + let packet_buf = stream + .receive_raw_packet() .await - .map_err(|e| LpClientError::Transport(format!("Failed to read packet length: {e}")))?; - - let packet_len = u32::from_be_bytes(len_buf) as usize; - - // Sanity check to prevent huge allocations - const MAX_PACKET_SIZE: usize = 65536; // 64KB max - if packet_len > MAX_PACKET_SIZE { - return Err(LpClientError::Transport(format!( - "Packet size {packet_len} exceeds maximum {MAX_PACKET_SIZE}", - ))); - } - - // Read the actual packet data - let mut packet_buf = vec![0u8; packet_len]; - stream - .read_exact(&mut packet_buf) - .await - .map_err(|e| LpClientError::Transport(format!("Failed to read packet data: {e}")))?; + .map_err(|err| LpClientError::transport(err.to_string()))?; let packet = parse_lp_packet(&packet_buf, outer_key) .map_err(|e| LpClientError::Transport(format!("Failed to parse packet: {e}")))?; - tracing::trace!("Received LP packet ({} bytes + 4 byte header)", packet_len); Ok(packet) } - /// Sends registration request and receives response in a single operation. + /// This is an internal method only meant to be called by `Self::register_dvpn` if the gateway + /// responds with a credential request. This is expected in every initial interaction with a particular gateway. /// + /// This method will actually attempt to retrieve a valid credential from the `bandwidth_controller` + /// + /// # Arguments + /// * `gateway_identity` - Gateway's ed25519 identity for credential verification + /// * `bandwidth_controller` - Provider for bandwidth credentials + /// * `ticket_type` - Type of bandwidth ticket to use + /// + /// # Returns + /// * `Ok(WireguardConfiguration)` - Gateway configuration data on successful registration + /// + /// # Errors + /// Returns an error if: + /// - Credential acquisition fails + /// - Request serialization/encryption fails + /// - Network communication fails + /// - Gateway rejected the registration + /// - Response times out (see LpConfig::registration_timeout) + async fn finalise_dvpn_registration( + &mut self, + gateway_identity: ed25519::PublicKey, + bandwidth_controller: &dyn BandwidthTicketProvider, + ticket_type: TicketType, + ) -> Result { + tracing::debug!("Acquiring bandwidth credential for registration"); + + // 1. Get bandwidth credential from controller + let credential_spending = bandwidth_controller + .get_ecash_ticket(ticket_type, gateway_identity, DEFAULT_TICKETS_TO_SPEND) + .await + .map_err(|e| { + LpClientError::SendRegistrationRequest(format!( + "Failed to acquire bandwidth credential: {e}", + )) + })? + .data; + + // 2. Build registration request + + // for now we do NOT support upgrade mode (yeah... no.) + let credential = credential_spending + .try_into() + .map_err(|err| LpClientError::Other(format!("malformed stored credential: {err}")))?; + + let request = LpRegistrationRequest::new_finalise_dvpn(credential); + + tracing::trace!("Built dVPN registration finalisation request"); + + // 3. Serialize the request + let lp_data = request.to_lp_data()?; + + // 4. Encrypt and prepare packet via state machine + let state_machine = self.state_machine_mut()?; + let request_packet = prepare_send_packet(lp_data, state_machine)?; + + // 5. Send initial request and receive response on persistent connection with timeout + let response_packet = self + .send_and_receive_packet_with_timeout(&request_packet, self.config.registration_timeout) + .await?; + + // 6. Decrypt via state machine (re-borrow) + let state_machine = self.state_machine_mut()?; + let received_data = extract_forwarded_response(response_packet, state_machine)?; + + // 7. Extract decrypted data and deserialise the response + let response = LpRegistrationResponse::from_lp_data(received_data)?; + let Some(dvpn_response) = response.into_dvpn_response() else { + return Err(LpClientError::unexpected_response( + "did not get a dvpn registration response after sending initial request", + )); + }; + + // 8. check response to the initial request + match dvpn_response.content { + LpDvpnRegistrationResponseMessageContent::RegistrationFailure(res) => { + let reason = res.error; + // the registration has failed + tracing::warn!("Gateway rejected registration: {reason}"); + Err(LpClientError::RegistrationRejected { reason }) + } + LpDvpnRegistrationResponseMessageContent::CompletedRegistration(res) => Ok(res.config), + LpDvpnRegistrationResponseMessageContent::RequiresCredential(_) => { + Err(LpClientError::unexpected_response( + "received request for additional dvpn data after sending credential!", + )) + } + } + } + /// This is the primary registration method. It acquires a bandwidth credential, /// sends the registration request, and receives the response /// on the same underlying connection. @@ -682,13 +822,14 @@ where /// for that please use [`Self::register_with_retry`] instead /// /// # Arguments + /// * `rng` - RNG instance for generating PSK /// * `wg_keypair` - Client's WireGuard x25519 keypair /// * `gateway_identity` - Gateway's ed25519 identity for credential verification /// * `bandwidth_controller` - Provider for bandwidth credentials /// * `ticket_type` - Type of bandwidth ticket to use /// /// # Returns - /// * `Ok(GatewayData)` - Gateway configuration data on successful registration + /// * `Ok(WireguardConfiguration)` - Gateway configuration data on successful registration /// /// # Errors /// Returns an error if: @@ -698,185 +839,78 @@ where /// - Network communication fails /// - Gateway rejected the registration /// - Response times out (see LpConfig::registration_timeout) - pub async fn register( + pub async fn register_dvpn( &mut self, + rng: &mut R, wg_keypair: &x25519::KeyPair, gateway_identity: &ed25519::PublicKey, bandwidth_controller: &dyn BandwidthTicketProvider, ticket_type: TicketType, - ) -> Result { - tracing::debug!("Acquiring bandwidth credential for registration"); - - // Get bandwidth credential from controller - let credential = bandwidth_controller - .get_ecash_ticket(ticket_type, *gateway_identity, DEFAULT_TICKETS_TO_SPEND) - .await - .map_err(|e| { - LpClientError::SendRegistrationRequest(format!( - "Failed to acquire bandwidth credential: {e}", - )) - })? - .data; - - self.register_with_credential(wg_keypair, credential, ticket_type) - .await - } - - /// Sends registration request with a pre-generated credential. - /// - /// This is useful for testing with mock ecash credentials. - /// Uses the persistent TCP connection established during handshake. - /// - /// # Arguments - /// * `wg_keypair` - Client's WireGuard x25519 keypair - /// * `credential` - Pre-generated bandwidth credential - /// * `ticket_type` - Type of bandwidth ticket - /// - /// # Returns - /// * `Ok(GatewayData)` - Gateway configuration data on successful registration - /// - /// # Connection Lifecycle - /// The connection stays open after registration to support `send_forward_packet()`. - /// Callers should call `close()` when done with all operations. - /// - /// # Panics / Errors - /// Returns error if handshake not completed or if connection was closed. - pub async fn register_with_credential( - &mut self, - wg_keypair: &x25519::KeyPair, - credential: CredentialSpendingData, - ticket_type: TicketType, - ) -> Result { - tracing::debug!("Sending registration request (persistent connection)"); - + ) -> Result + where + R: RngCore + CryptoRng, + { // 1. Build registration request - let wg_public_key = PeerPublicKey::new(wg_keypair.public_key().to_bytes().into()); - let request = - LpRegistrationRequest::new_dvpn(wg_public_key, credential, ticket_type, self.client_ip); + let wg_public_key = PeerPublicKey::from(*wg_keypair.public_key()); + let mut psk = [0u8; 32]; + rng.fill_bytes(&mut psk); + let request = LpRegistrationRequest::new_initial_dvpn(wg_public_key, psk); - tracing::trace!("Built registration request: {:?}", request); + tracing::trace!("Built dVPN registration request: {request:?}"); // 2. Serialize the request - let request_bytes = lp_bincode_serializer().serialize(&request).map_err(|e| { - LpClientError::SendRegistrationRequest(format!("Failed to serialize request: {e}")) - })?; + let lp_data = request.to_lp_data()?; - tracing::debug!( - "Sending registration request ({} bytes)", - request_bytes.len() - ); + // 3. Encrypt and prepare packet via state machine + let state_machine = self.state_machine_mut()?; + let request_packet = prepare_send_packet(lp_data, state_machine)?; - // 3. Encrypt and prepare packet via state machine (scoped borrow) - let (request_packet, send_key, recv_key) = { - let state_machine = self.state_machine.as_mut().ok_or_else(|| { - LpClientError::transport("Cannot register: handshake not completed") - })?; - - let action = state_machine - .process_input(LpInput::SendData(request_bytes)) - .ok_or_else(|| LpClientError::transport("State machine returned no action"))? - .map_err(|e| { - LpClientError::SendRegistrationRequest(format!( - "Failed to encrypt registration request: {e}", - )) - })?; - - let request_packet = match action { - LpAction::SendPacket(packet) => packet, - other => { - return Err(LpClientError::Transport(format!( - "Unexpected action when sending registration data: {other:?}", - ))); - } - }; - - // Get outer keys from session - let send_key = state_machine - .session() - .ok() - .and_then(|s| s.outer_aead_key_for_sending()); - let recv_key = state_machine - .session() - .ok() - .and_then(|s| s.outer_aead_key()); - - (request_packet, send_key, recv_key) - }; // state_machine borrow ends here - - // 4. Send request and receive response on persistent connection with timeout - let response_packet = tokio::time::timeout(self.config.registration_timeout, async { - self.send_packet(&request_packet, send_key.as_ref()).await?; - self.receive_packet(recv_key.as_ref()).await - }) - .await - .map_err(|_| { - LpClientError::ReceiveRegistrationResponse(format!( - "Registration timeout after {:?}", - self.config.registration_timeout - )) - })??; - - tracing::trace!("Received registration response packet"); + // 4. Send initial request and receive response on persistent connection with timeout + let response_packet = self + .send_and_receive_packet_with_timeout(&request_packet, self.config.registration_timeout) + .await?; // 5. Decrypt via state machine (re-borrow) - let state_machine = self - .state_machine - .as_mut() - .ok_or_else(|| LpClientError::transport("State machine disappeared unexpectedly"))?; - let action = state_machine - .process_input(LpInput::ReceivePacket(response_packet)) - .ok_or_else(|| LpClientError::transport("State machine returned no action"))? - .map_err(|e| { - LpClientError::ReceiveRegistrationResponse(format!( - "Failed to decrypt registration response: {e}", - )) - })?; + let state_machine = self.state_machine_mut()?; + let received_data = extract_forwarded_response(response_packet, state_machine)?; - // 7. Extract decrypted data - let response_data = match action { - LpAction::DeliverData(data) => data, - other => { - return Err(LpClientError::Transport(format!( - "Unexpected action when receiving registration response: {other:?}" - ))); + // 6. Extract decrypted data and deserialise the response + let response = LpRegistrationResponse::from_lp_data(received_data)?; + let Some(dvpn_response) = response.into_dvpn_response() else { + return Err(LpClientError::unexpected_response( + "did not get a dvpn registration response after sending initial request", + )); + }; + + // 7. check response to the initial request + let final_response = match dvpn_response.content { + LpDvpnRegistrationResponseMessageContent::RegistrationFailure(res) => { + let reason = res.error; + // the registration has failed + tracing::warn!("Gateway rejected registration: {reason}"); + return Err(LpClientError::RegistrationRejected { reason }); + } + LpDvpnRegistrationResponseMessageContent::CompletedRegistration(res) => res.config, + LpDvpnRegistrationResponseMessageContent::RequiresCredential(_) => { + // we're registering for the first time with this gateway - we need to attach a credential + + // 8. retrieve credential from the controller + self.finalise_dvpn_registration( + *gateway_identity, + bandwidth_controller, + ticket_type, + ) + .await? } }; - // 8. Deserialize the response - let response: LpRegistrationResponse = lp_bincode_serializer() - .deserialize(&response_data) - .map_err(|e| { - LpClientError::ReceiveRegistrationResponse(format!( - "Failed to deserialize registration response: {e}", - )) - })?; - - tracing::debug!( - "Received registration response: success={}", - response.success, - ); - - // 9. Validate and extract GatewayData - if !response.success { - let error_msg = response - .error - .unwrap_or_else(|| "Unknown error".to_string()); - tracing::warn!("Gateway rejected registration: {error_msg}"); - return Err(LpClientError::RegistrationRejected { reason: error_msg }); - } - - let gateway_data = response.gateway_data.ok_or_else(|| { - LpClientError::ReceiveRegistrationResponse( - "Gateway response missing gateway_data despite success=true".to_string(), - ) - })?; - - tracing::info!( - "LP registration successful! Allocated bandwidth: {} bytes", - response.allocated_bandwidth - ); - - Ok(gateway_data) + Ok(WireguardConfiguration { + public_key: final_response.public_key, + psk: Some(psk), + endpoint: SocketAddr::new(self.gateway_lp_address.ip(), final_response.port), + private_ipv4: final_response.private_ipv4, + private_ipv6: final_response.private_ipv6, + }) } /// Register with automatic retry on network failure. @@ -894,6 +928,7 @@ where /// will return the cached result instead of spending a new credential. /// /// # Arguments + /// * `rng` - RNG instance for generating PSK /// * `wg_keypair` - Client's WireGuard x25519 keypair (same key used for all retries) /// * `gateway_identity` - Gateway's ed25519 identity for credential verification /// * `bandwidth_controller` - Provider for bandwidth credentials @@ -909,39 +944,30 @@ where /// # Note /// Unlike `register()`, this method handles the full flow including handshake. /// Do NOT call `perform_handshake()` before this method. - pub async fn register_with_retry( + pub async fn register_with_retry( &mut self, + rng: &mut R, wg_keypair: &x25519::KeyPair, gateway_identity: &ed25519::PublicKey, bandwidth_controller: &dyn BandwidthTicketProvider, ticket_type: TicketType, max_retries: u32, - ) -> Result { + ) -> Result + where + R: RngCore + CryptoRng, + { tracing::debug!("Starting resilient registration (max_retries={max_retries})",); - // Acquire credential ONCE before any attempts - let credential = bandwidth_controller - .get_ecash_ticket(ticket_type, *gateway_identity, DEFAULT_TICKETS_TO_SPEND) - .await - .map_err(|e| { - LpClientError::SendRegistrationRequest(format!( - "Failed to acquire bandwidth credential: {e}", - )) - })? - .data; - let mut last_error = None; for attempt in 0..=max_retries { + let attempt_display = attempt + 1; + if attempt > 0 { // Exponential backoff with jitter: 100ms, 200ms, 400ms, 800ms, 1600ms (capped) let base_delay_ms = 100u64 * (1 << attempt.min(4)); let jitter_ms = rand::random::() % (base_delay_ms / 4 + 1); let delay = std::time::Duration::from_millis(base_delay_ms + jitter_ms); - tracing::info!( - "Retrying registration (attempt {}) after {:?}", - attempt + 1, - delay - ); + tracing::info!("Retrying registration (attempt {attempt_display}) after {delay:?}"); tokio::time::sleep(delay).await; } @@ -953,24 +979,30 @@ where self.state_machine = None; if let Err(e) = self.perform_handshake().await { - tracing::warn!("Handshake failed on attempt {}: {e}", attempt + 1); + tracing::warn!("Handshake failed on attempt {attempt_display}: {e}"); last_error = Some(e); continue; } } match self - .register_with_credential(wg_keypair, credential.clone(), ticket_type) + .register_dvpn( + rng, + wg_keypair, + gateway_identity, + bandwidth_controller, + ticket_type, + ) .await { Ok(data) => { if attempt > 0 { - tracing::info!("Registration succeeded on retry attempt {}", attempt + 1); + tracing::info!("Registration succeeded on retry attempt {attempt_display}"); } return Ok(data); } Err(e) => { - tracing::warn!("Registration attempt {} failed: {e}", attempt + 1); + tracing::warn!("Registration attempt {attempt_display} failed: {e}"); last_error = Some(e); } } @@ -990,9 +1022,7 @@ where /// Multiple forward packets can be sent on the same connection. /// /// # Arguments - /// * `target_identity` - Target gateway's Ed25519 identity (32 bytes) - /// * `target_address` - Target gateway's LP address (e.g., "1.1.1.1:41264") - /// * `inner_packet_bytes` - Complete inner LP packet bytes to forward to exit gateway + /// * `forward_data` - encapsulated target gateway's ed25519 identity, socket address and serialised inner LP packet /// /// # Returns /// * `Ok(Vec)` - Decrypted response bytes from the exit gateway @@ -1017,45 +1047,28 @@ where /// inner_bytes.to_vec(), /// ).await?; /// ``` - pub async fn send_forward_packet( + pub async fn send_forward_packet_with_response( &mut self, - target_identity: [u8; 32], - target_address: String, - inner_packet_bytes: Vec, + forward_data: ForwardPacketData, ) -> Result> { + let target_address = forward_data.target_lp_address; + tracing::debug!( - "Sending ForwardPacket to {} ({} inner bytes, persistent connection)", - target_address, - inner_packet_bytes.len() + "Sending ForwardPacket to {target_address} ({} inner bytes, persistent connection)", + forward_data.inner_packet_bytes.len() ); - // 1. Construct ForwardPacketData - let forward_data = ForwardPacketData { - target_gateway_identity: target_identity, - target_lp_address: target_address.clone(), - inner_packet_bytes, - }; + // 1. Serialize the ForwardPacketData + let input = convert_forward_data(forward_data)?; - // 2. Serialize the ForwardPacketData - let forward_data_bytes = lp_bincode_serializer() - .serialize(&forward_data) - .map_err(|e| { - LpClientError::Transport(format!("Failed to serialize ForwardPacketData: {e}")) - })?; - - tracing::trace!( - "Serialized ForwardPacketData ({} bytes)", - forward_data_bytes.len() - ); - - // 3. Encrypt and prepare packet via state machine (scoped borrow) + // 2. Encrypt and prepare packet via state machine (scoped borrow) let (forward_packet, send_key, recv_key) = { let state_machine = self.state_machine.as_mut().ok_or_else(|| { LpClientError::transport("Cannot send forward packet: handshake not completed") })?; let action = state_machine - .process_input(LpInput::SendData(forward_data_bytes)) + .process_input(input) .ok_or_else(|| LpClientError::transport("State machine returned no action"))? .map_err(|e| { LpClientError::Transport(format!("Failed to encrypt ForwardPacket: {e}")) @@ -1084,10 +1097,11 @@ where (forward_packet, send_key, recv_key) }; // state_machine borrow ends here - // 4. Send and receive on persistent connection with timeout + // 3. Send and receive on persistent connection with timeout let response_packet = tokio::time::timeout(self.config.forward_timeout, async { - self.send_packet(&forward_packet, send_key.as_ref()).await?; - self.receive_packet(recv_key.as_ref()).await + self.try_send_packet_with_key(&forward_packet, send_key.as_ref()) + .await?; + self.try_receive_packet_with_key(recv_key.as_ref()).await }) .await .map_err(|_| { @@ -1098,7 +1112,7 @@ where })??; tracing::trace!("Received response packet from entry gateway"); - // 5. Decrypt via state machine (re-borrow) + // 4. Decrypt via state machine (re-borrow) let state_machine = self .state_machine .as_mut() @@ -1110,24 +1124,15 @@ where LpClientError::Transport(format!("Failed to decrypt forward response: {e}")) })?; - // 7. Extract decrypted response data - let response_data = match action { - LpAction::DeliverData(data) => data, - other => { - return Err(LpClientError::Transport(format!( - "Unexpected action when receiving forward response: {:?}", - other - ))); - } - }; + // 5. Extract decrypted response data + let response_data = try_convert_forward_response(action)?; tracing::debug!( - "Successfully received forward response from {} ({} bytes)", - target_address, + "Successfully received forward response from {target_address} ({} bytes)", response_data.len() ); - Ok(response_data.to_vec()) + Ok(response_data) } /// Wrap data in an LP packet for UDP transmission to the data plane (port 51264). @@ -1165,7 +1170,7 @@ where // Process data through state machine to create LP packet let action = state_machine - .process_input(LpInput::SendData(data.to_vec())) + .process_input(LpInput::SendData(LpData::new_opaque(data.to_vec()))) .ok_or_else(|| LpClientError::transport("State machine returned no action"))? .map_err(|e| LpClientError::Transport(format!("Failed to encrypt data: {e}")))?; @@ -1218,24 +1223,26 @@ where #[cfg(test)] mod tests { use super::*; + use nym_lp::packet::version; #[test] fn test_client_creation() { let mut rng = rand::thread_rng(); let keypair = Arc::new(ed25519::KeyPair::new(&mut rng)); - let gateway_key = *ed25519::KeyPair::new(&mut rng).public_key(); + let gateway_ed_keys = ed25519::KeyPair::new(&mut rng); + let gateway_x_keys = gateway_ed_keys.to_x25519(); + let gateway_peer = + LpRemotePeer::new(*gateway_ed_keys.public_key(), *gateway_x_keys.public_key()); let address = "127.0.0.1:41264".parse().unwrap(); - let client_ip = "192.168.1.100".parse().unwrap(); - let client = LpRegistrationClient::::new_with_default_psk( + let client = LpRegistrationClient::::new_with_default_config( keypair, - gateway_key, + gateway_peer, address, - client_ip, + version::CURRENT, ); assert!(!client.is_handshake_complete()); assert_eq!(client.gateway_address(), address); - assert_eq!(client.client_ip(), client_ip); } } diff --git a/nym-registration-client/src/lp_client/config.rs b/nym-registration-client/src/lp_client/config.rs index def4ca8f97..506fc559a2 100644 --- a/nym-registration-client/src/lp_client/config.rs +++ b/nym-registration-client/src/lp_client/config.rs @@ -26,8 +26,8 @@ use std::time::Duration; /// - Fail fast enough for good UX (no indefinite hangs) /// - Allow sufficient time for real network conditions /// - Optimize for latency over throughput (small messages) -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct LpConfig { +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct LpRegistrationConfig { /// TCP connection timeout (nym-102). /// /// Maximum time to wait for TCP connection establishment. @@ -69,7 +69,7 @@ pub struct LpConfig { pub tcp_keepalive: Option, } -impl Default for LpConfig { +impl Default for LpRegistrationConfig { fn default() -> Self { Self { // nym-102: Sane timeout defaults for real network conditions @@ -91,7 +91,7 @@ mod tests { #[test] fn test_default_config() { - let config = LpConfig::default(); + let config = LpRegistrationConfig::default(); assert_eq!(config.connect_timeout, Duration::from_secs(10)); assert_eq!(config.handshake_timeout, Duration::from_secs(15)); @@ -100,12 +100,4 @@ mod tests { assert!(config.tcp_nodelay); assert_eq!(config.tcp_keepalive, None); } - - #[test] - fn test_config_clone() { - let config = LpConfig::default(); - let cloned = config.clone(); - - assert_eq!(config, cloned); - } } diff --git a/nym-registration-client/src/lp_client/error.rs b/nym-registration-client/src/lp_client/error.rs index bbc0a6ef91..32443f4e01 100644 --- a/nym-registration-client/src/lp_client/error.rs +++ b/nym-registration-client/src/lp_client/error.rs @@ -4,8 +4,9 @@ //! Error types for LP (Lewes Protocol) client operations. use nym_lp::LpError; -use nym_lp::serialisation::BincodeError; +use nym_registration_common::BincodeError; use std::io; +use std::time::Duration; use thiserror::Error; /// Errors that can occur during LP client operations. @@ -35,6 +36,10 @@ pub enum LpClientError { #[error("Gateway rejected registration: {reason}")] RegistrationRejected { reason: String }, + /// Failed to receive response within specified deadline + #[error("Failed to receive response within the set timeout: {timeout:?}")] + ResponseReceiveTimeout { timeout: Duration }, + /// LP transport error #[error("LP transport error: {0}")] Transport(String), @@ -55,9 +60,8 @@ pub enum LpClientError { #[error("Timeout waiting for {operation}")] Timeout { operation: String }, - /// Cryptographic operation failed - #[error("Cryptographic error: {0}")] - Crypto(String), + #[error("received an unexpected response: {message}")] + UnexpectedResponse { message: String }, /// Another uncategorized error #[error("{0}")] @@ -68,6 +72,12 @@ impl LpClientError { pub fn transport(message: impl Into) -> LpClientError { LpClientError::Transport(message.into()) } + + pub fn unexpected_response(message: impl Into) -> LpClientError { + LpClientError::UnexpectedResponse { + message: message.into(), + } + } } pub type Result = std::result::Result; diff --git a/nym-registration-client/src/lp_client/helpers.rs b/nym-registration-client/src/lp_client/helpers.rs new file mode 100644 index 0000000000..3eb53706fd --- /dev/null +++ b/nym-registration-client/src/lp_client/helpers.rs @@ -0,0 +1,101 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +#![allow(dead_code)] + +use crate::LpClientError; +use nym_crypto::asymmetric::ed25519; +use nym_lp::message::ForwardPacketData; +use nym_lp::peer::LpRemotePeer; +use nym_lp::state_machine::{LpAction, LpData, LpDataKind, LpInput}; +use nym_registration_common::{ + LpRegistrationRequest, LpRegistrationResponse, NymNodeLPInformation, +}; + +pub(crate) trait LpDataSendExt { + fn to_lp_data(&self) -> Result; +} + +pub(crate) trait LpDataDeliverExt: Sized { + fn from_lp_data(data: LpData) -> Result; +} + +impl LpDataSendExt for LpRegistrationRequest { + fn to_lp_data(&self) -> Result { + let request_bytes = self.serialise().map_err(|e| { + LpClientError::SendRegistrationRequest(format!("Failed to serialize request: {e}")) + })?; + + tracing::debug!( + "Sending registration request ({} bytes)", + request_bytes.len() + ); + + Ok(LpData::new_registration(request_bytes)) + } +} + +impl LpDataDeliverExt for LpRegistrationResponse { + fn from_lp_data(data: LpData) -> Result { + if data.kind != LpDataKind::Registration { + return Err(LpClientError::Transport(format!( + "did not receive a valid registration response. got {:?} instead", + data.kind + ))); + } + + let response = LpRegistrationResponse::try_deserialise(&data.content).map_err(|e| { + LpClientError::Transport(format!("Failed to deserialize registration response: {e}",)) + })?; + + Ok(response) + } +} + +impl LpDataSendExt for ForwardPacketData { + fn to_lp_data(&self) -> Result { + let request_bytes = self.to_bytes(); + + tracing::trace!( + "Sending forward packet data request ({} bytes)", + request_bytes.len() + ); + + Ok(LpData::new_forward(request_bytes)) + } +} + +pub(crate) fn convert_forward_data(request: ForwardPacketData) -> Result { + Ok(LpInput::SendData(request.to_lp_data()?)) +} + +pub(crate) fn try_convert_forward_response(action: LpAction) -> Result, LpClientError> { + let response_data = match action { + LpAction::DeliverData(data) => data, + other => { + return Err(LpClientError::Transport(format!( + "Unexpected action when receiving forward response: {:?}", + other + ))); + } + }; + + if response_data.kind != LpDataKind::Forward { + return Err(LpClientError::Transport(format!( + "did not receive a valid forward response. got {:?} instead", + response_data.kind + ))); + } + + Ok(response_data.content.into()) +} + +pub(crate) fn to_lp_remote_peer( + identity: ed25519::PublicKey, + data: NymNodeLPInformation, +) -> LpRemotePeer { + LpRemotePeer::new(identity, data.x25519).with_key_digests( + data.expected_kem_key_hashes, + data.expected_signing_key_hashes, + ) +} diff --git a/nym-registration-client/src/lp_client/mod.rs b/nym-registration-client/src/lp_client/mod.rs index d4085bbbaa..492b01ed2e 100644 --- a/nym-registration-client/src/lp_client/mod.rs +++ b/nym-registration-client/src/lp_client/mod.rs @@ -17,7 +17,7 @@ //! ```ignore //! use nym_registration_client::lp_client::LpRegistrationClient; //! -//! let mut client = LpRegistrationClient::new_with_default_psk( +//! let mut client = LpRegistrationClient::new_with_default_config( //! keypair, //! gateway_public_key, //! gateway_lp_address, @@ -34,9 +34,11 @@ mod client; mod config; pub(crate) mod error; +pub(crate) mod helpers; mod nested_session; +mod state_machine_helpers; pub use client::LpRegistrationClient; -pub use config::LpConfig; +pub use config::LpRegistrationConfig; pub use error::LpClientError; pub use nested_session::NestedLpSession; diff --git a/nym-registration-client/src/lp_client/nested_session.rs b/nym-registration-client/src/lp_client/nested_session.rs index d4f35898ee..631206785c 100644 --- a/nym-registration-client/src/lp_client/nested_session.rs +++ b/nym-registration-client/src/lp_client/nested_session.rs @@ -20,20 +20,32 @@ use super::client::LpRegistrationClient; use super::error::{LpClientError, Result}; -use bytes::BytesMut; -use nym_bandwidth_controller::BandwidthTicketProvider; +use crate::lp_client::helpers::{LpDataDeliverExt, LpDataSendExt}; +use crate::lp_client::state_machine_helpers::{ + extract_forwarded_response, get_recv_key, get_send_key, prepare_serialised_send_packet, + serialize_packet, +}; +use nym_bandwidth_controller::{BandwidthTicketProvider, DEFAULT_TICKETS_TO_SPEND}; use nym_credentials_interface::TicketType; use nym_crypto::asymmetric::{ed25519, x25519}; -use nym_lp::codec::{OuterAeadKey, parse_lp_packet, serialize_lp_packet}; -use nym_lp::serialisation::{BincodeOptions, lp_bincode_serializer}; -use nym_lp::state_machine::{LpAction, LpInput, LpStateMachine}; +use nym_lp::codec::{OuterAeadKey, parse_lp_packet}; +use nym_lp::message::ForwardPacketData; +use nym_lp::packet::version; +use nym_lp::peer::{LpLocalPeer, LpRemotePeer}; +use nym_lp::state_machine::{LpAction, LpData, LpInput, LpStateMachine}; use nym_lp::{LpMessage, LpPacket}; use nym_lp_transport::traits::LpTransport; -use nym_registration_common::{GatewayData, LpRegistrationRequest, LpRegistrationResponse}; +use nym_registration_common::dvpn::LpDvpnRegistrationResponseMessageContent; +use nym_registration_common::{ + LpRegistrationRequest, LpRegistrationResponse, WireguardConfiguration, + WireguardRegistrationData, +}; use nym_wireguard_types::PeerPublicKey; -use std::net::IpAddr; +use rand::{CryptoRng, RngCore}; +use std::net::SocketAddr; use std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; +use tracing::warn; /// Manages a nested LP session where the client establishes a handshake with /// an exit gateway by forwarding packets through an entry gateway. @@ -56,17 +68,18 @@ use std::time::{SystemTime, UNIX_EPOCH}; /// let gateway_data = nested.handshake_and_register(&mut outer_client, ...).await?; /// ``` pub struct NestedLpSession { - /// Exit gateway's Ed25519 identity (32 bytes) - exit_identity: [u8; 32], - /// Exit gateway's LP address (e.g., "2.2.2.2:41264") - exit_address: String, + exit_address: SocketAddr, - /// Client's Ed25519 keypair (for PSQ authentication and X25519 derivation) - client_keypair: Arc, + /// Encapsulates all the client keys needed for the Lewes Protocol. + lp_local_peer: LpLocalPeer, - /// Exit gateway's Ed25519 public key - exit_public_key: ed25519::PublicKey, + /// Encapsulates all the exit gateway keys needed for the Lewes Protocol. + gateway_lp_peer: LpRemotePeer, + + /// Supported protocol version of the remote gateway. + /// Included in case we have to downgrade our version. + gateway_supported_lp_protocol_version: u8, /// LP state machine for exit gateway session (populated after handshake) state_machine: Option, @@ -76,25 +89,80 @@ impl NestedLpSession { /// Creates a new nested LP session handler. /// /// # Arguments - /// * `exit_identity` - Exit gateway's Ed25519 identity (32 bytes) /// * `exit_address` - Exit gateway's LP address (e.g., "2.2.2.2:41264") /// * `client_keypair` - Client's Ed25519 keypair - /// * `exit_public_key` - Exit gateway's Ed25519 public key + /// * `gateway_lp_peer` - Encapsulates all the gateway keys needed for the Lewes Protocol + /// * `gateway_supported_lp_protocol_version` - Gateway's LP protocol version pub fn new( - exit_identity: [u8; 32], - exit_address: String, + exit_address: SocketAddr, client_keypair: Arc, - exit_public_key: ed25519::PublicKey, + gateway_lp_peer: LpRemotePeer, + gateway_supported_lp_protocol_version: u8, ) -> Self { + let local_x25519_keypair = client_keypair.to_x25519(); + let lp_local_peer = LpLocalPeer::new(client_keypair, Arc::new(local_x25519_keypair)); + + let lp_protocol = if gateway_supported_lp_protocol_version > version::CURRENT { + warn!( + "suggested LP protocol ({gateway_supported_lp_protocol_version}) is higher than the current known version. attempting to downgrade it to {}", + version::CURRENT + ); + version::CURRENT + } else { + gateway_supported_lp_protocol_version + }; + Self { - exit_identity, exit_address, - client_keypair, - exit_public_key, + lp_local_peer, + gateway_lp_peer, + gateway_supported_lp_protocol_version: lp_protocol, state_machine: None, } } + fn state_machine(&self) -> Result<&LpStateMachine> { + self.state_machine.as_ref().ok_or_else(|| { + LpClientError::transport( + "State machine not available - has the handshake been completed?", + ) + }) + } + + fn state_machine_mut(&mut self) -> Result<&mut LpStateMachine> { + self.state_machine.as_mut().ok_or_else(|| { + LpClientError::transport( + "State machine not available - has the handshake been completed?", + ) + }) + } + + /// Attempt to parse received bytes into an LpPacket + fn parse_received_lp_packet(&self, response_bytes: Vec) -> Result { + let state_machine = self.state_machine()?; + let outer_key = get_recv_key(state_machine); + Self::parse_packet(&response_bytes, outer_key.as_ref()) + } + + /// Attempt to wrap the provided `LpData` into a `ForwardPacketData` + /// using the inner state machine. + fn prepare_forward_packet(&mut self, data: LpData) -> Result { + let state_machine = self.state_machine_mut()?; + let inner_packet_bytes = prepare_serialised_send_packet(data, state_machine)?; + Ok(ForwardPacketData::new( + self.gateway_lp_peer.ed25519(), + self.exit_address, + inner_packet_bytes, + )) + } + + /// Attempt to recover received `LpData` from the received `LpPacket` + /// using the inner state machine. + fn extract_forwarded_response(&mut self, response_packet: LpPacket) -> Result { + let state_machine = self.state_machine_mut()?; + extract_forwarded_response(response_packet, state_machine) + } + /// Performs the LP handshake with the exit gateway by forwarding packets /// through the entry gateway. /// @@ -125,22 +193,13 @@ impl NestedLpSession { self.exit_address ); - // Step 1: Derive X25519 keys from Ed25519 for Noise protocol - let client_x25519_public = self.client_keypair.public_key().to_x25519().map_err(|e| { - LpClientError::Crypto(format!("Failed to derive X25519 public key: {}", e)) - })?; - let timestamp = SystemTime::now() .duration_since(UNIX_EPOCH) .map_err(|_| LpClientError::Other("System time before UNIX epoch".into()))? .as_secs(); - // Step 2: Generate ClientHello for exit gateway - let client_hello_data = nym_lp::ClientHelloData::new_with_fresh_salt( - client_x25519_public.to_bytes(), - self.client_keypair.public_key().to_bytes(), - timestamp, - ); + // Step 1: Generate ClientHello for exit gateway + let client_hello_data = self.lp_local_peer.build_client_hello_data(timestamp); let salt = client_hello_data.salt; let receiver_index = client_hello_data.receiver_index; @@ -149,10 +208,11 @@ impl NestedLpSession { client_hello_data.extract_timestamp() ); - // Step 3: Send ClientHello to exit gateway via forwarding + // Step 2: Send ClientHello to exit gateway via forwarding let client_hello_header = nym_lp::packet::LpHeader::new( nym_lp::BOOTSTRAP_RECEIVER_IDX, // Use constant for bootstrap session 0, // counter starts at 0 + self.gateway_supported_lp_protocol_version, ); let client_hello_packet = nym_lp::LpPacket::new( client_hello_header, @@ -160,16 +220,20 @@ impl NestedLpSession { ); // Serialize and forward ClientHello (no state machine yet, no outer key) - let client_hello_bytes = Self::serialize_packet(&client_hello_packet, None)?; + let client_hello_bytes = serialize_packet(&client_hello_packet, None)?; + let forward_packet_data = ForwardPacketData::new( + self.gateway_lp_peer.ed25519(), + self.exit_address, + client_hello_bytes, + ); + let response_bytes = outer_client - .send_forward_packet( - self.exit_identity, - self.exit_address.clone(), - client_hello_bytes, - ) + .send_forward_packet_with_response(forward_packet_data) .await?; // Parse and validate Ack response (cleartext, no outer key before PSK derivation) + // this confirms that gateway is fine with our suggested protocol version + // in the future we probably have some fancier negotiation let ack_response = Self::parse_packet(&response_bytes, None)?; match ack_response.message() { LpMessage::Ack => { @@ -188,19 +252,17 @@ impl NestedLpSession { } } - // Step 4: Create state machine for exit gateway handshake + // Step 3: Create state machine for exit gateway handshake let mut state_machine = LpStateMachine::new( receiver_index, true, // is_initiator - ( - self.client_keypair.private_key(), - self.client_keypair.public_key(), - ), - &self.exit_public_key, + self.lp_local_peer.clone(), + self.gateway_lp_peer.clone(), &salt, + self.gateway_supported_lp_protocol_version, )?; - // Step 5: Get initial packet from StartHandshake + // Step 4: Get initial packet from StartHandshake let mut pending_packet: Option = None; if let Some(action) = state_machine.process_input(LpInput::StartHandshake) { match action? { @@ -215,7 +277,7 @@ impl NestedLpSession { } } - // Step 6: Handshake loop - each packet on new connection via forwarding + // Step 5: Handshake loop - each packet on new connection via forwarding loop { if let Some(packet) = pending_packet.take() { tracing::trace!("Sending handshake packet to exit via forwarding"); @@ -283,156 +345,102 @@ impl NestedLpSession { Ok(()) } - /// Performs handshake and registration with the exit gateway via forwarding, - /// using a pre-made credential. + /// This is an internal method only meant to be called by `Self::handshake_and_register_dvpn` if the gateway + /// responds with a credential request. This is expected in every initial interaction with a particular gateway. /// - /// This variant is useful for mock ecash testing where the credential is provided - /// directly instead of being acquired from a bandwidth controller. + /// This method will actually attempt to retrieve a valid credential from the `bandwidth_controller` /// /// # Arguments /// * `outer_client` - Connected LP client with established outer session to entry gateway - /// * `wg_keypair` - Client's WireGuard x25519 keypair - /// * `credential` - Pre-made bandwidth credential (e.g., mock ecash) + /// * `gateway_identity` - Gateway's ed25519 identity for credential verification + /// * `bandwidth_controller` - Provider for bandwidth credentials /// * `ticket_type` - Type of bandwidth ticket to use - /// * `client_ip` - Client IP address for registration metadata /// /// # Returns - /// * `Ok(GatewayData)` - Exit gateway configuration data on successful registration - pub async fn handshake_and_register_with_credential( + /// * `Ok(WireguardConfiguration)` - Gateway configuration data on successful registration + /// + /// # Errors + /// Returns an error if: + /// - Credential acquisition fails + /// - Request serialization/encryption fails + /// - Forwarding through entry gateway fails + /// - Network communication fails + /// - Gateway rejected the registration + /// - Response times out (see LpConfig::registration_timeout) + async fn finalise_dvpn_registration( &mut self, outer_client: &mut LpRegistrationClient, - wg_keypair: &x25519::KeyPair, - credential: nym_credentials_interface::CredentialSpendingData, + gateway_identity: ed25519::PublicKey, + bandwidth_controller: &dyn BandwidthTicketProvider, ticket_type: TicketType, - client_ip: IpAddr, - ) -> Result + ) -> Result where S: LpTransport + Unpin, { - // Step 1: Perform handshake with exit gateway via forwarding - self.perform_handshake(outer_client).await?; + tracing::debug!("Acquiring bandwidth credential for registration"); - // Step 2: Get the state machine (must exist after successful handshake) - let state_machine = self.state_machine.as_mut().ok_or_else(|| { - LpClientError::Transport("State machine missing after handshake".to_string()) - })?; - - tracing::debug!( - "Building registration request for exit gateway (with pre-made credential)" - ); - - // Step 3: Build registration request (credential already provided) - let wg_public_key = PeerPublicKey::new(wg_keypair.public_key().to_bytes().into()); - let request = - LpRegistrationRequest::new_dvpn(wg_public_key, credential, ticket_type, client_ip); - - tracing::trace!("Built registration request: {:?}", request); - - // Step 4: Serialize the request - let request_bytes = lp_bincode_serializer().serialize(&request).map_err(|e| { - LpClientError::Transport(format!("Failed to serialize registration request: {}", e)) - })?; - - tracing::debug!( - "Sending registration request to exit gateway via forwarding ({} bytes)", - request_bytes.len() - ); - - // Step 5: Encrypt and prepare packet via state machine - let action = state_machine - .process_input(LpInput::SendData(request_bytes)) - .ok_or_else(|| { - LpClientError::Transport("State machine returned no action".to_string()) - })? + // Step 1: Get bandwidth credential from controller + let credential_spending = bandwidth_controller + .get_ecash_ticket(ticket_type, gateway_identity, DEFAULT_TICKETS_TO_SPEND) + .await .map_err(|e| { - LpClientError::Transport(format!("Failed to encrypt registration request: {}", e)) - })?; - - // Step 6: Send the encrypted packet via forwarding - let outer_key = Self::get_send_key(state_machine); - let response_bytes = match action { - LpAction::SendPacket(packet) => { - let packet_bytes = Self::serialize_packet(&packet, outer_key.as_ref())?; - outer_client - .send_forward_packet( - self.exit_identity, - self.exit_address.clone(), - packet_bytes, - ) - .await? - } - other => { - return Err(LpClientError::Transport(format!( - "Unexpected action when sending registration data: {:?}", - other - ))); - } - }; - - tracing::trace!("Received registration response from exit gateway"); - - // Step 7: Parse response bytes to LP packet - let outer_key = Self::get_recv_key(state_machine); - let response_packet = Self::parse_packet(&response_bytes, outer_key.as_ref())?; - - // Step 8: Decrypt via state machine - let action = state_machine - .process_input(LpInput::ReceivePacket(response_packet)) - .ok_or_else(|| { - LpClientError::Transport("State machine returned no action".to_string()) - })? - .map_err(|e| { - LpClientError::Transport(format!("Failed to decrypt registration response: {}", e)) - })?; - - // Step 9: Extract decrypted data - let response_data = match action { - LpAction::DeliverData(data) => data, - other => { - return Err(LpClientError::Transport(format!( - "Unexpected action when receiving registration response: {:?}", - other - ))); - } - }; - - // Step 10: Deserialize the response - let response: LpRegistrationResponse = lp_bincode_serializer() - .deserialize(&response_data) - .map_err(|e| { - LpClientError::Transport(format!( - "Failed to deserialize registration response: {}", - e + LpClientError::SendRegistrationRequest(format!( + "Failed to acquire bandwidth credential: {e}", )) - })?; + })? + .data; - tracing::debug!( - "Received registration response from exit: success={}", - response.success, - ); + // Step 2: Build registration request - // Step 11: Validate and extract GatewayData - if !response.success { - let error_msg = response - .error - .unwrap_or_else(|| "Unknown error".to_string()); - tracing::warn!("Exit gateway rejected registration: {}", error_msg); - return Err(LpClientError::RegistrationRejected { reason: error_msg }); + // for now we do NOT support upgrade mode (yeah... no.) + let credential = credential_spending + .try_into() + .map_err(|err| LpClientError::Other(format!("malformed stored credential: {err}")))?; + + let request = LpRegistrationRequest::new_finalise_dvpn(credential); + + tracing::trace!("Built dVPN registration finalisation request"); + + // Step 3: Serialize the request + let send_data = request.to_lp_data()?; + + // Step 4: Encrypt and prepare packet via state machine + let forward_packet = self.prepare_forward_packet(send_data)?; + + // Step 5: Send the encrypted packet via forwarding + let response_bytes = outer_client + .send_forward_packet_with_response(forward_packet) + .await?; + + // Step 6: Parse response bytes to LP packet + let response_packet = self.parse_received_lp_packet(response_bytes)?; + + // Step 7: Decrypt via state machine + let response_data = self.extract_forwarded_response(response_packet)?; + + // Step 8: Extract decrypted data and deserialise the response + let response = LpRegistrationResponse::from_lp_data(response_data)?; + let Some(dvpn_response) = response.into_dvpn_response() else { + return Err(LpClientError::unexpected_response( + "did not get a dvpn registration response after sending initial request", + )); + }; + + // Step 9: check response to the initial request + match dvpn_response.content { + LpDvpnRegistrationResponseMessageContent::RegistrationFailure(res) => { + let reason = res.error; + // the registration has failed + tracing::warn!("Gateway rejected registration: {reason}"); + Err(LpClientError::RegistrationRejected { reason }) + } + LpDvpnRegistrationResponseMessageContent::CompletedRegistration(res) => Ok(res.config), + LpDvpnRegistrationResponseMessageContent::RequiresCredential(_) => { + Err(LpClientError::unexpected_response( + "received request for additional dvpn data after sending credential!", + )) + } } - - // Extract gateway_data - let gateway_data = response.gateway_data.ok_or_else(|| { - LpClientError::Transport( - "Gateway response missing gateway_data despite success=true".to_string(), - ) - })?; - - tracing::info!( - "Exit gateway registration successful! Allocated bandwidth: {} bytes", - response.allocated_bandwidth - ); - - Ok(gateway_data) } /// Performs handshake and registration with the exit gateway via forwarding. @@ -462,153 +470,89 @@ impl NestedLpSession { /// - Forwarding through entry gateway fails /// - Response decryption/deserialization fails /// - Gateway rejects the registration - pub async fn handshake_and_register( + pub async fn handshake_and_register_dvpn( &mut self, outer_client: &mut LpRegistrationClient, + rng: &mut R, wg_keypair: &x25519::KeyPair, gateway_identity: &ed25519::PublicKey, bandwidth_controller: &dyn BandwidthTicketProvider, ticket_type: TicketType, - client_ip: IpAddr, - ) -> Result + ) -> Result where S: LpTransport + Unpin, + R: RngCore + CryptoRng, { // Step 1: Perform handshake with exit gateway via forwarding self.perform_handshake(outer_client).await?; - // Step 2: Get the state machine (must exist after successful handshake) - let state_machine = self.state_machine.as_mut().ok_or_else(|| { - LpClientError::Transport("State machine missing after handshake".to_string()) - })?; - tracing::debug!("Building registration request for exit gateway"); - // Step 3: Acquire bandwidth credential - let credential = bandwidth_controller - .get_ecash_ticket( - ticket_type, - *gateway_identity, - nym_bandwidth_controller::DEFAULT_TICKETS_TO_SPEND, - ) - .await - .map_err(|e| { - LpClientError::Transport(format!("Failed to acquire bandwidth credential: {}", e)) - })? - .data; + // Step 2: Build registration request + let wg_public_key = PeerPublicKey::from(*wg_keypair.public_key()); + let mut psk = [0u8; 32]; + rng.fill_bytes(&mut psk); - // Step 4: Build registration request - let wg_public_key = PeerPublicKey::new(wg_keypair.public_key().to_bytes().into()); - let request = - LpRegistrationRequest::new_dvpn(wg_public_key, credential, ticket_type, client_ip); + let request = LpRegistrationRequest::new_initial_dvpn(wg_public_key, psk); - tracing::trace!("Built registration request: {:?}", request); + // Step 3: Serialize the request + let send_data = request.to_lp_data()?; - // Step 5: Serialize the request - let request_bytes = lp_bincode_serializer().serialize(&request).map_err(|e| { - LpClientError::Transport(format!("Failed to serialize registration request: {}", e)) - })?; + // Step 4: Encrypt and prepare packet via state machine + let forward_packet = self.prepare_forward_packet(send_data)?; - tracing::debug!( - "Sending registration request to exit gateway via forwarding ({} bytes)", - request_bytes.len() - ); - - // Step 6: Encrypt and prepare packet via state machine - let action = state_machine - .process_input(LpInput::SendData(request_bytes)) - .ok_or_else(|| { - LpClientError::Transport("State machine returned no action".to_string()) - })? - .map_err(|e| { - LpClientError::Transport(format!("Failed to encrypt registration request: {}", e)) - })?; - - // Step 7: Send the encrypted packet via forwarding - let outer_key = Self::get_send_key(state_machine); - let response_bytes = match action { - LpAction::SendPacket(packet) => { - let packet_bytes = Self::serialize_packet(&packet, outer_key.as_ref())?; - outer_client - .send_forward_packet( - self.exit_identity, - self.exit_address.clone(), - packet_bytes, - ) - .await? - } - other => { - return Err(LpClientError::Transport(format!( - "Unexpected action when sending registration data: {:?}", - other - ))); - } - }; + // Step 5: Send the encrypted packet via forwarding + let response_bytes = outer_client + .send_forward_packet_with_response(forward_packet) + .await?; tracing::trace!("Received registration response from exit gateway"); - // Step 8: Parse response bytes to LP packet - let outer_key = Self::get_recv_key(state_machine); - let response_packet = Self::parse_packet(&response_bytes, outer_key.as_ref())?; + // Step 6: Parse response bytes to LP packet + let response_packet = self.parse_received_lp_packet(response_bytes)?; - // Step 9: Decrypt via state machine - let action = state_machine - .process_input(LpInput::ReceivePacket(response_packet)) - .ok_or_else(|| { - LpClientError::Transport("State machine returned no action".to_string()) - })? - .map_err(|e| { - LpClientError::Transport(format!("Failed to decrypt registration response: {}", e)) - })?; + // Step 7: Decrypt via state machine + let response_data = self.extract_forwarded_response(response_packet)?; - // Step 10: Extract decrypted data - let response_data = match action { - LpAction::DeliverData(data) => data, - other => { - return Err(LpClientError::Transport(format!( - "Unexpected action when receiving registration response: {:?}", - other - ))); + // Step 8: Extract decrypted data and deserialise the response + let response = LpRegistrationResponse::from_lp_data(response_data)?; + let Some(dvpn_response) = response.into_dvpn_response() else { + return Err(LpClientError::unexpected_response( + "did not get a dvpn registration response after sending initial request", + )); + }; + + // Step 9: check response to the initial request + let final_response = match dvpn_response.content { + LpDvpnRegistrationResponseMessageContent::RegistrationFailure(res) => { + let reason = res.error; + // the registration has failed + tracing::warn!("Gateway rejected registration: {reason}"); + return Err(LpClientError::RegistrationRejected { reason }); + } + LpDvpnRegistrationResponseMessageContent::CompletedRegistration(res) => res.config, + LpDvpnRegistrationResponseMessageContent::RequiresCredential(_) => { + // we're registering for the first time with this gateway - we need to attach a credential + + // Step 10: retrieve credential from the controller + self.finalise_dvpn_registration( + outer_client, + *gateway_identity, + bandwidth_controller, + ticket_type, + ) + .await? } }; - // Step 11: Deserialize the response - let response: LpRegistrationResponse = lp_bincode_serializer() - .deserialize(&response_data) - .map_err(|e| { - LpClientError::Transport(format!( - "Failed to deserialize registration response: {}", - e - )) - })?; - - tracing::debug!( - "Received registration response from exit: success={}", - response.success, - ); - - // Step 12: Validate and extract GatewayData - if !response.success { - let error_msg = response - .error - .unwrap_or_else(|| "Unknown error".to_string()); - tracing::warn!("Exit gateway rejected registration: {}", error_msg); - return Err(LpClientError::RegistrationRejected { reason: error_msg }); - } - - // Extract gateway_data - let gateway_data = response.gateway_data.ok_or_else(|| { - LpClientError::Transport( - "Gateway response missing gateway_data despite success=true".to_string(), - ) - })?; - - tracing::info!( - "Exit gateway registration successful! Allocated bandwidth: {} bytes", - response.allocated_bandwidth - ); - - Ok(gateway_data) + // JS/SW TODO Adapt this to new gateway response + Ok(WireguardConfiguration { + public_key: final_response.public_key, + psk: Some(psk), + endpoint: SocketAddr::new(self.exit_address.ip(), final_response.port), + private_ipv4: final_response.private_ipv4, + private_ipv6: final_response.private_ipv6, + }) } /// Performs handshake and registration with the exit gateway via forwarding, @@ -640,37 +584,25 @@ impl NestedLpSession { /// # Errors /// Returns an error if all retry attempts fail. #[allow(clippy::too_many_arguments)] - pub async fn handshake_and_register_with_retry( + pub async fn handshake_and_register_dvpn_with_retry( &mut self, outer_client: &mut LpRegistrationClient, + rng: &mut R, wg_keypair: &x25519::KeyPair, gateway_identity: &ed25519::PublicKey, bandwidth_controller: &dyn BandwidthTicketProvider, ticket_type: TicketType, - client_ip: IpAddr, max_retries: u32, - ) -> Result + ) -> Result where S: LpTransport + Unpin, + R: RngCore + CryptoRng, { tracing::debug!( "Starting resilient exit registration (max_retries={})", max_retries ); - // Acquire credential ONCE before any attempts - let credential = bandwidth_controller - .get_ecash_ticket( - ticket_type, - *gateway_identity, - nym_bandwidth_controller::DEFAULT_TICKETS_TO_SPEND, - ) - .await - .map_err(|e| { - LpClientError::Transport(format!("Failed to acquire bandwidth credential: {}", e)) - })? - .data; - let mut last_error = None; for attempt in 0..=max_retries { if attempt > 0 { @@ -697,12 +629,13 @@ impl NestedLpSession { } match self - .handshake_and_register_with_credential( + .handshake_and_register_dvpn( outer_client, + rng, wg_keypair, - credential.clone(), + gateway_identity, + bandwidth_controller, ticket_type, - client_ip, ) .await { @@ -743,54 +676,20 @@ impl NestedLpSession { where S: LpTransport + Unpin, { - let send_key = Self::get_send_key(state_machine); - let packet_bytes = Self::serialize_packet(packet, send_key.as_ref())?; + let send_key = get_send_key(state_machine); + let packet_bytes = serialize_packet(packet, send_key.as_ref())?; + let forward_data = ForwardPacketData::new( + self.gateway_lp_peer.ed25519(), + self.exit_address, + packet_bytes, + ); let response_bytes = outer_client - .send_forward_packet(self.exit_identity, self.exit_address.clone(), packet_bytes) + .send_forward_packet_with_response(forward_data) .await?; - let recv_key = Self::get_recv_key(state_machine); + let recv_key = get_recv_key(state_machine); Self::parse_packet(&response_bytes, recv_key.as_ref()) } - /// Gets the outer AEAD key for sending (encryption) from the state machine. - /// - /// Returns `None` during early handshake before PSK derivation. - fn get_send_key(state_machine: &LpStateMachine) -> Option { - state_machine - .session() - .ok() - .and_then(|s| s.outer_aead_key_for_sending()) - } - - /// Gets the outer AEAD key for receiving (decryption) from the state machine. - /// - /// Returns `None` during early handshake before PSK derivation. - fn get_recv_key(state_machine: &LpStateMachine) -> Option { - state_machine - .session() - .ok() - .and_then(|s| s.outer_aead_key()) - } - - /// Serializes an LP packet to bytes. - /// - /// # Arguments - /// * `packet` - The LP packet to serialize - /// - /// # Returns - /// * `Ok(Vec)` - Serialized packet bytes - /// - /// # Errors - /// Returns an error if serialization fails - fn serialize_packet(packet: &LpPacket, outer_key: Option<&OuterAeadKey>) -> Result> { - let mut buf = BytesMut::new(); - // Use outer AEAD key when available (after PSK derivation) - serialize_lp_packet(packet, &mut buf, outer_key).map_err(|e| { - LpClientError::Transport(format!("Failed to serialize LP packet: {}", e)) - })?; - Ok(buf.to_vec()) - } - /// Parses an LP packet from bytes. /// /// # Arguments @@ -804,6 +703,6 @@ impl NestedLpSession { fn parse_packet(bytes: &[u8], outer_key: Option<&OuterAeadKey>) -> Result { // Use outer AEAD key when available (after PSK derivation) parse_lp_packet(bytes, outer_key) - .map_err(|e| LpClientError::Transport(format!("Failed to parse LP packet: {}", e))) + .map_err(|e| LpClientError::Transport(format!("Failed to parse LP packet: {e}"))) } } diff --git a/nym-registration-client/src/lp_client/state_machine_helpers.rs b/nym-registration-client/src/lp_client/state_machine_helpers.rs new file mode 100644 index 0000000000..47da7c1255 --- /dev/null +++ b/nym-registration-client/src/lp_client/state_machine_helpers.rs @@ -0,0 +1,106 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::LpClientError; +use bytes::BytesMut; +use nym_lp::codec::{OuterAeadKey, serialize_lp_packet}; +use nym_lp::state_machine::{LpAction, LpData, LpInput}; +use nym_lp::{LpPacket, LpStateMachine}; + +/// Gets the outer AEAD key for sending (encryption) from the state machine. +/// +/// Returns `None` during early handshake before PSK derivation. +pub(crate) fn get_send_key(state_machine: &LpStateMachine) -> Option { + state_machine + .session() + .ok() + .and_then(|s| s.outer_aead_key_for_sending()) +} + +/// Gets the outer AEAD key for receiving (decryption) from the state machine. +/// +/// Returns `None` during early handshake before PSK derivation. +pub(crate) fn get_recv_key(state_machine: &LpStateMachine) -> Option { + state_machine + .session() + .ok() + .and_then(|s| s.outer_aead_key()) +} + +/// Serializes an LP packet to bytes. +/// +/// # Arguments +/// * `packet` - The LP packet to serialize +/// +/// # Returns +/// * `Ok(Vec)` - Serialized packet bytes +/// +/// # Errors +/// Returns an error if serialization fails +pub(crate) fn serialize_packet( + packet: &LpPacket, + outer_key: Option<&OuterAeadKey>, +) -> Result, LpClientError> { + let mut buf = BytesMut::new(); + // Use outer AEAD key when available (after PSK derivation) + serialize_lp_packet(packet, &mut buf, outer_key) + .map_err(|e| LpClientError::Transport(format!("Failed to serialize LP packet: {}", e)))?; + Ok(buf.to_vec()) +} + +/// Attempt to prepare the provided data for sending by wrapping it in appropriate `LpAction`, +/// and attempting to extract `LpPacket` from the provided srtate machine. +pub(crate) fn prepare_send_packet( + data: LpData, + state_machine: &mut LpStateMachine, +) -> Result { + let action = state_machine + .process_input(LpInput::SendData(data)) + .ok_or_else(|| LpClientError::transport("State machine returned no action"))? + .map_err(|e| { + LpClientError::SendRegistrationRequest(format!( + "Failed to encrypt registration request: {e}", + )) + })?; + + match action { + LpAction::SendPacket(packet) => Ok(packet), + other => Err(LpClientError::Transport(format!( + "Unexpected action when trying to send packet data: {other:?}", + ))), + } +} + +/// Attempt to prepare the provided data for sending by wrapping it in appropriate `LpAction`, +/// serialising and finally encrypting (if appropriate key is available) the resultant `LpPacket` +/// It uses the provided state machine. +pub(crate) fn prepare_serialised_send_packet( + data: LpData, + state_machine: &mut LpStateMachine, +) -> Result, LpClientError> { + let packet = prepare_send_packet(data, state_machine)?; + + let send_key = get_send_key(state_machine); + serialize_packet(&packet, send_key.as_ref()) +} + +/// Attempt to recover received `LpData` from the received `LpPacket` +/// using the provided state machine. +pub(crate) fn extract_forwarded_response( + response_packet: LpPacket, + state_machine: &mut LpStateMachine, +) -> Result { + let action = state_machine + .process_input(LpInput::ReceivePacket(response_packet)) + .ok_or_else(|| LpClientError::Transport("State machine returned no action".to_string()))? + .map_err(|e| { + LpClientError::Transport(format!("Failed to decrypt received response: {e}")) + })?; + + match action { + LpAction::DeliverData(data) => Ok(data), + other => Err(LpClientError::Transport(format!( + "Unexpected action when receiving response: {other:?}" + ))), + } +} diff --git a/nym-registration-client/src/types.rs b/nym-registration-client/src/types.rs index ad387d7b5b..9d38bebc59 100644 --- a/nym-registration-client/src/types.rs +++ b/nym-registration-client/src/types.rs @@ -3,7 +3,7 @@ use nym_authenticator_client::{AuthClientMixnetListenerHandle, AuthenticatorClient}; use nym_bandwidth_controller::BandwidthTicketProvider; -use nym_registration_common::{AssignedAddresses, GatewayData}; +use nym_registration_common::{AssignedAddresses, WireguardConfiguration}; use nym_sdk::mixnet::{EventReceiver, MixnetClient}; pub enum RegistrationResult { @@ -21,8 +21,8 @@ pub struct MixnetRegistrationResult { pub struct WireguardRegistrationResult { pub entry_gateway_client: AuthenticatorClient, pub exit_gateway_client: AuthenticatorClient, - pub entry_gateway_data: GatewayData, - pub exit_gateway_data: GatewayData, + pub entry_gateway_data: WireguardConfiguration, + pub exit_gateway_data: WireguardConfiguration, pub authenticator_listener_handle: AuthClientMixnetListenerHandle, pub bw_controller: Box, } @@ -39,10 +39,10 @@ pub struct WireguardRegistrationResult { /// * `bw_controller` - Bandwidth ticket provider for credential management pub struct LpRegistrationResult { /// Gateway configuration data from entry gateway - pub entry_gateway_data: GatewayData, + pub entry_gateway_data: WireguardConfiguration, /// Gateway configuration data from exit gateway - pub exit_gateway_data: GatewayData, + pub exit_gateway_data: WireguardConfiguration, /// Bandwidth controller for credential management pub bw_controller: Box, diff --git a/nym-signers-monitor/Cargo.toml b/nym-signers-monitor/Cargo.toml index d043aa2871..56adfc4ac5 100644 --- a/nym-signers-monitor/Cargo.toml +++ b/nym-signers-monitor/Cargo.toml @@ -9,6 +9,7 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +publish = false [dependencies] anyhow = { workspace = true } @@ -20,11 +21,11 @@ tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } tracing = { workspace = true } url = { workspace = true } -nym-bin-common = { path = "../common/bin-common", features = ["output_format", "basic_tracing"] } -nym-ecash-signer-check = { path = "../common/ecash-signer-check" } -nym-network-defaults = { path = "../common/network-defaults" } -nym-task = { path = "../common/task" } -nym-validator-client = { path = "../common/client-libs/validator-client" } +nym-bin-common = { workspace = true, features = ["output_format", "basic_tracing"] } +nym-ecash-signer-check = { workspace = true } +nym-network-defaults = { workspace = true } +nym-task = { workspace = true } +nym-validator-client = { workspace = true } zulip-client = { path = "../common/zulip-client" } [lints] diff --git a/sqlx-pool-guard/Cargo.toml b/nym-sqlx-pool-guard/Cargo.toml similarity index 80% rename from sqlx-pool-guard/Cargo.toml rename to nym-sqlx-pool-guard/Cargo.toml index cef202e4df..3eecfffb99 100644 --- a/sqlx-pool-guard/Cargo.toml +++ b/nym-sqlx-pool-guard/Cargo.toml @@ -1,8 +1,12 @@ [package] -name = "sqlx-pool-guard" -version = "0.1.0" +name = "nym-sqlx-pool-guard" +version.workspace = true edition = "2024" license.workspace = true +description = "Platform-specific functions for SQLX dbs" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true [lints] workspace = true diff --git a/sqlx-pool-guard/src/apple.rs b/nym-sqlx-pool-guard/src/apple.rs similarity index 100% rename from sqlx-pool-guard/src/apple.rs rename to nym-sqlx-pool-guard/src/apple.rs diff --git a/sqlx-pool-guard/src/lib.rs b/nym-sqlx-pool-guard/src/lib.rs similarity index 100% rename from sqlx-pool-guard/src/lib.rs rename to nym-sqlx-pool-guard/src/lib.rs diff --git a/sqlx-pool-guard/src/linux.rs b/nym-sqlx-pool-guard/src/linux.rs similarity index 100% rename from sqlx-pool-guard/src/linux.rs rename to nym-sqlx-pool-guard/src/linux.rs diff --git a/sqlx-pool-guard/src/windows.rs b/nym-sqlx-pool-guard/src/windows.rs similarity index 100% rename from sqlx-pool-guard/src/windows.rs rename to nym-sqlx-pool-guard/src/windows.rs diff --git a/nym-statistics-api/Cargo.toml b/nym-statistics-api/Cargo.toml index be52fc8b38..d603503585 100644 --- a/nym-statistics-api/Cargo.toml +++ b/nym-statistics-api/Cargo.toml @@ -11,7 +11,7 @@ documentation.workspace = true edition.workspace = true license.workspace = true rust-version.workspace = true - +publish = false [dependencies] anyhow.workspace = true @@ -38,17 +38,17 @@ utoipa-swagger-ui = { workspace = true, features = ["axum"] } utoipauto.workspace = true #internal -nym-bin-common = { path = "../common/bin-common" } -nym-http-api-common = { path = "../common/http-api-common", features = [ +nym-bin-common = { workspace = true } +nym-http-api-common = { workspace = true, features = [ "middleware", ] } -nym-statistics-common = { path = "../common/statistics", features = [ +nym-statistics-common = { workspace = true, features = [ "openapi", ] } -nym-task = { path = "../common/task" } +nym-task = { workspace = true } -nym-http-api-client = { path = "../common/http-api-client" } -nym-validator-client = { path = "../common/client-libs/validator-client" } +nym-http-api-client = { workspace = true } +nym-validator-client = { workspace = true } [build-dependencies] anyhow = { workspace = true } diff --git a/nym-statistics-api/src/network_view.rs b/nym-statistics-api/src/network_view.rs index 1491a00903..d4e44d50a5 100644 --- a/nym-statistics-api/src/network_view.rs +++ b/nym-statistics-api/src/network_view.rs @@ -5,7 +5,7 @@ use anyhow::Result; use nym_task::ShutdownToken; use celes::Country; -use nym_validator_client::models::NymNodeDescription; +use nym_validator_client::models::NymNodeDescriptionV1; use std::collections::HashMap; use std::time::Duration; use std::{net::IpAddr, sync::Arc}; @@ -28,7 +28,8 @@ struct NodesQuerier { } impl NodesQuerier { - async fn current_nymnodes(&self) -> Result> { + #[allow(deprecated)] + async fn current_nymnodes(&self) -> Result> { Ok(self .client .get_all_described_nodes() diff --git a/nym-validator-rewarder/Cargo.toml b/nym-validator-rewarder/Cargo.toml index 0655bd21ef..bf2fc671f8 100644 --- a/nym-validator-rewarder/Cargo.toml +++ b/nym-validator-rewarder/Cargo.toml @@ -7,6 +7,7 @@ homepage.workspace = true documentation.workspace = true edition.workspace = true license = "GPL-3.0" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -32,22 +33,22 @@ humantime = { workspace = true } humantime-serde.workspace = true # internal -nym-bin-common = { path = "../common/bin-common", features = ["output_format", "basic_tracing"] } -nym-config = { path = "../common/config" } -nym-ecash-time = { path = "../common/ecash-time" } -nym-contracts-common = { path = "../common/cosmwasm-smart-contracts/contracts-common" } -nym-compact-ecash = { path = "../common/nym_offline_compact_ecash" } -nym-crypto = { path = "../common/crypto", features = ["asymmetric", "rand"] } -nym-credentials = { path = "../common/credentials" } -nym-network-defaults = { path = "../common/network-defaults" } -nym-task = { path = "../common/task" } -nym-validator-client = { path = "../common/client-libs/validator-client" } -nym-http-api-client = { path = "../common/http-api-client" } -nym-coconut-dkg-common = { path = "../common/cosmwasm-smart-contracts/coconut-dkg" } +nym-bin-common = { workspace = true, features = ["output_format", "basic_tracing"] } +nym-config = { workspace = true } +nym-ecash-time = { workspace = true } +nym-contracts-common = { workspace = true } +nym-compact-ecash = { workspace = true } +nym-crypto = { workspace = true, features = ["asymmetric", "rand"] } +nym-credentials = { workspace = true } +nym-network-defaults = { workspace = true } +nym-task = { workspace = true } +nym-validator-client = { workspace = true } +nym-http-api-client = { workspace = true } +nym-coconut-dkg-common = { workspace = true } nyxd-scraper-sqlite = { path = "../common/nyxd-scraper-sqlite" } -nym-ticketbooks-merkle = { path = "../common/ticketbooks-merkle" } -nym-serde-helpers = { path = "../common/serde-helpers", features = ["base64"] } -nym-pemstore = { path = "../common/pemstore" } +nym-ticketbooks-merkle = { workspace = true } +nym-serde-helpers = { workspace = true, features = ["base64"] } +nym-pemstore = { workspace = true } [build-dependencies] anyhow = { workspace = true } @@ -56,7 +57,7 @@ tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } [dev-dependencies] rand_chacha = { workspace = true } -nym-credentials-interface = { path = "../common/credentials-interface" } +nym-credentials-interface = { workspace = true } [lints] workspace = true diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index 0ebc2184e4..6f4ebfbc81 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -81,7 +81,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ "crypto-common", - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -106,7 +106,7 @@ dependencies = [ "cipher", "ctr", "ghash", - "subtle", + "subtle 2.6.1", ] [[package]] @@ -259,7 +259,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" dependencies = [ "base64ct", - "blake2", + "blake2 0.10.6", "cpufeatures", "password-hash", ] @@ -681,7 +681,7 @@ dependencies = [ "ripemd", "secp256k1", "sha2 0.10.9", - "subtle", + "subtle 2.6.1", "zeroize", ] @@ -730,6 +730,18 @@ dependencies = [ "serde", ] +[[package]] +name = "blake2" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94cb07b0da6a73955f8fb85d24c466778e70cda767a568229b104f0264089330" +dependencies = [ + "byte-tools", + "crypto-mac", + "digest 0.8.1", + "opaque-debug 0.2.3", +] + [[package]] name = "blake2" version = "0.10.6" @@ -756,7 +768,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -765,7 +777,7 @@ version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -799,22 +811,6 @@ dependencies = [ "piper", ] -[[package]] -name = "bls12_381" -version = "0.8.0" -source = "git+https://github.com/jstuczyn/bls12_381?branch=temp%2Fexperimental-serdect-updated#9bf520059cb28323fc51469cae86868ef4fa6fbd" -dependencies = [ - "digest 0.10.7", - "ff", - "group", - "pairing", - "rand_core 0.6.4", - "serde", - "serdect 0.3.0", - "subtle", - "zeroize", -] - [[package]] name = "bnum" version = "0.11.0" @@ -858,6 +854,12 @@ version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + [[package]] name = "bytemuck" version = "1.22.0" @@ -1026,6 +1028,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chacha" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddf3c081b5fba1e5615640aae998e0fbd10c24cbd897ee39ed754a77601a4862" +dependencies = [ + "byteorder", + "keystream", +] + [[package]] name = "chrono" version = "0.4.40" @@ -1423,9 +1435,9 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ - "generic-array", + "generic-array 0.14.7", "rand_core 0.6.4", - "subtle", + "subtle 2.6.1", "zeroize", ] @@ -1435,11 +1447,21 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array", + "generic-array 0.14.7", "rand_core 0.6.4", "typenum", ] +[[package]] +name = "crypto-mac" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" +dependencies = [ + "generic-array 0.12.4", + "subtle 1.0.0", +] + [[package]] name = "cssparser" version = "0.27.2" @@ -1505,7 +1527,7 @@ dependencies = [ "fiat-crypto", "rustc_version", "serde", - "subtle", + "subtle 2.6.1", "zeroize", ] @@ -1746,13 +1768,22 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array 0.12.4", +] + [[package]] name = "digest" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -1764,7 +1795,7 @@ dependencies = [ "block-buffer 0.10.4", "const-oid", "crypto-common", - "subtle", + "subtle 2.6.1", ] [[package]] @@ -1956,7 +1987,7 @@ dependencies = [ "rand_core 0.6.4", "serde", "sha2 0.10.9", - "subtle", + "subtle 2.6.1", "zeroize", ] @@ -1991,7 +2022,7 @@ dependencies = [ "crypto-bigint", "digest 0.10.7", "ff", - "generic-array", + "generic-array 0.14.7", "group", "hkdf", "pem-rfc7468", @@ -1999,7 +2030,7 @@ dependencies = [ "rand_core 0.6.4", "sec1", "serdect 0.2.0", - "subtle", + "subtle 2.6.1", "zeroize", ] @@ -2179,7 +2210,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" dependencies = [ "rand_core 0.6.4", - "subtle", + "subtle 2.6.1", ] [[package]] @@ -2526,6 +2557,15 @@ dependencies = [ "windows 0.58.0", ] +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -2591,7 +2631,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" dependencies = [ - "opaque-debug", + "opaque-debug 0.3.1", "polyval", ] @@ -2705,7 +2745,7 @@ checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", "rand_core 0.6.4", - "subtle", + "subtle 2.6.1", ] [[package]] @@ -3458,7 +3498,7 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -3709,6 +3749,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "keystream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33070833c9ee02266356de0c43f723152bd38bd96ddf52c82b3af10c9138b28" + [[package]] name = "kuchikiki" version = "0.8.2" @@ -3800,6 +3846,18 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" +[[package]] +name = "lioness" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae926706ba42c425c9457121178330d75e273df2e82e28b758faf3de3a9acb9" +dependencies = [ + "arrayref", + "blake2 0.8.1", + "chacha", + "keystream", +] + [[package]] name = "litemap" version = "0.7.5" @@ -4123,18 +4181,19 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.3" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" dependencies = [ "num_enum_derive", + "rustversion", ] [[package]] name = "num_enum_derive" -version = "0.7.3" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" dependencies = [ "proc-macro-crate 3.3.0", "proc-macro2", @@ -4153,9 +4212,10 @@ dependencies = [ [[package]] name = "nym-api-requests" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bs58", + "celes", "cosmrs", "cosmwasm-std", "ecdsa", @@ -4169,6 +4229,7 @@ dependencies = [ "nym-crypto", "nym-ecash-signer-check-types", "nym-ecash-time", + "nym-kkt-ciphersuite", "nym-mixnet-contract-common", "nym-network-defaults", "nym-node-requests", @@ -4179,6 +4240,8 @@ dependencies = [ "serde", "serde_json", "sha2 0.10.9", + "strum", + "strum_macros", "tendermint", "tendermint-rpc", "thiserror 2.0.12", @@ -4189,7 +4252,7 @@ dependencies = [ [[package]] name = "nym-bin-common" -version = "0.6.0" +version = "1.20.1" dependencies = [ "const-str", "log", @@ -4201,9 +4264,26 @@ dependencies = [ "vergen", ] +[[package]] +name = "nym-bls12_381-fork" +version = "0.8.0-forked" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce84633751030f960a2fd167b5270ec21da4c40d9b6400e1b56676a682fe6f3d" +dependencies = [ + "digest 0.10.7", + "ff", + "group", + "pairing", + "rand_core 0.6.4", + "serde", + "serdect 0.3.0", + "subtle 2.6.1", + "zeroize", +] + [[package]] name = "nym-coconut-dkg-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -4216,29 +4296,29 @@ dependencies = [ [[package]] name = "nym-compact-ecash" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bincode", - "bls12_381", "bs58", "cfg-if", "digest 0.10.7", "ff", "group", "itertools 0.14.0", + "nym-bls12_381-fork", "nym-network-defaults", "nym-pemstore", "rand 0.8.5", "serde", "sha2 0.10.9", - "subtle", + "subtle 2.6.1", "thiserror 2.0.12", "zeroize", ] [[package]] name = "nym-config" -version = "0.1.0" +version = "1.20.1" dependencies = [ "dirs 6.0.0", "handlebars", @@ -4252,7 +4332,7 @@ dependencies = [ [[package]] name = "nym-contracts-common" -version = "0.5.0" +version = "1.20.1" dependencies = [ "bs58", "cosmwasm-schema", @@ -4266,9 +4346,9 @@ dependencies = [ [[package]] name = "nym-credentials-interface" -version = "0.1.0" +version = "1.20.1" dependencies = [ - "bls12_381", + "nym-bls12_381-fork", "nym-compact-ecash", "nym-ecash-time", "nym-network-defaults", @@ -4284,7 +4364,7 @@ dependencies = [ [[package]] name = "nym-crypto" -version = "0.4.0" +version = "1.20.1" dependencies = [ "base64 0.22.1", "bs58", @@ -4305,7 +4385,7 @@ dependencies = [ [[package]] name = "nym-ecash-contract-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bs58", "cosmwasm-schema", @@ -4318,7 +4398,7 @@ dependencies = [ [[package]] name = "nym-ecash-signer-check-types" -version = "0.1.0" +version = "1.20.1" dependencies = [ "nym-coconut-dkg-common", "nym-crypto", @@ -4333,14 +4413,14 @@ dependencies = [ [[package]] name = "nym-ecash-time" -version = "0.1.0" +version = "1.20.1" dependencies = [ "time", ] [[package]] name = "nym-exit-policy" -version = "0.1.0" +version = "1.20.1" dependencies = [ "serde", "serde_json", @@ -4351,7 +4431,7 @@ dependencies = [ [[package]] name = "nym-group-contract-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "cosmwasm-schema", "cw-controllers", @@ -4362,7 +4442,7 @@ dependencies = [ [[package]] name = "nym-http-api-client" -version = "0.1.0" +version = "1.20.1" dependencies = [ "async-trait", "bincode", @@ -4392,7 +4472,7 @@ dependencies = [ [[package]] name = "nym-http-api-client-macro" -version = "0.1.0" +version = "1.20.1" dependencies = [ "proc-macro-crate 3.3.0", "proc-macro2", @@ -4403,7 +4483,7 @@ dependencies = [ [[package]] name = "nym-http-api-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bincode", "serde", @@ -4411,9 +4491,19 @@ dependencies = [ "tracing", ] +[[package]] +name = "nym-kkt-ciphersuite" +version = "1.20.1" +dependencies = [ + "num_enum", + "strum", + "strum_macros", + "thiserror 2.0.12", +] + [[package]] name = "nym-mixnet-contract-common" -version = "0.6.0" +version = "1.20.1" dependencies = [ "bs58", "cosmwasm-schema", @@ -4434,7 +4524,7 @@ dependencies = [ [[package]] name = "nym-multisig-contract-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -4449,7 +4539,7 @@ dependencies = [ [[package]] name = "nym-network-defaults" -version = "0.1.0" +version = "1.20.1" dependencies = [ "cargo_metadata 0.19.2", "dotenvy", @@ -4457,13 +4547,15 @@ dependencies = [ "regex", "schemars", "serde", + "serde_json", + "tracing", "url", "utoipa", ] [[package]] name = "nym-node-requests" -version = "0.1.0" +version = "1.20.1" dependencies = [ "async-trait", "celes", @@ -4472,6 +4564,7 @@ dependencies = [ "nym-crypto", "nym-exit-policy", "nym-http-api-client", + "nym-kkt-ciphersuite", "nym-noise-keys", "nym-upgrade-mode-check", "nym-wireguard-types", @@ -4488,7 +4581,7 @@ dependencies = [ [[package]] name = "nym-noise-keys" -version = "0.1.0" +version = "1.20.1" dependencies = [ "nym-crypto", "schemars", @@ -4498,7 +4591,7 @@ dependencies = [ [[package]] name = "nym-pemstore" -version = "0.3.0" +version = "1.20.1" dependencies = [ "pem", "tracing", @@ -4507,7 +4600,7 @@ dependencies = [ [[package]] name = "nym-performance-contract-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -4520,7 +4613,7 @@ dependencies = [ [[package]] name = "nym-serde-helpers" -version = "0.1.0" +version = "1.20.1" dependencies = [ "base64 0.22.1", "bs58", @@ -4531,18 +4624,19 @@ dependencies = [ [[package]] name = "nym-sphinx-types" -version = "0.2.0" +version = "1.20.1" dependencies = [ + "sphinx-packet", "thiserror 2.0.12", ] [[package]] name = "nym-store-cipher" -version = "0.1.0" +version = "1.20.1" dependencies = [ "aes-gcm", "argon2", - "generic-array", + "generic-array 0.14.7", "getrandom 0.2.15", "rand 0.8.5", "serde", @@ -4553,7 +4647,7 @@ dependencies = [ [[package]] name = "nym-ticketbooks-merkle" -version = "0.1.0" +version = "1.20.1" dependencies = [ "nym-credentials-interface", "nym-serde-helpers", @@ -4567,7 +4661,7 @@ dependencies = [ [[package]] name = "nym-types" -version = "1.0.0" +version = "1.20.1" dependencies = [ "base64 0.22.1", "cosmrs", @@ -4597,7 +4691,7 @@ dependencies = [ [[package]] name = "nym-upgrade-mode-check" -version = "0.1.0" +version = "1.20.1" dependencies = [ "jwt-simple", "nym-crypto", @@ -4613,7 +4707,7 @@ dependencies = [ [[package]] name = "nym-validator-client" -version = "0.1.0" +version = "1.20.1" dependencies = [ "async-trait", "base64 0.22.1", @@ -4662,7 +4756,7 @@ dependencies = [ [[package]] name = "nym-vesting-contract-common" -version = "0.7.0" +version = "1.20.1" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -4708,7 +4802,7 @@ dependencies = [ [[package]] name = "nym-wireguard-types" -version = "0.1.0" +version = "1.20.1" dependencies = [ "base64 0.22.1", "nym-crypto", @@ -4960,6 +5054,12 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + [[package]] name = "opaque-debug" version = "0.3.1" @@ -5157,7 +5257,7 @@ checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", "rand_core 0.6.4", - "subtle", + "subtle 2.6.1", ] [[package]] @@ -5533,7 +5633,7 @@ checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" dependencies = [ "cfg-if", "cpufeatures", - "opaque-debug", + "opaque-debug 0.3.1", "universal-hash", ] @@ -5865,6 +5965,16 @@ dependencies = [ "getrandom 0.3.2", ] +[[package]] +name = "rand_distr" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + [[package]] name = "rand_hc" version = "0.2.0" @@ -6078,7 +6188,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ "hmac", - "subtle", + "subtle 2.6.1", ] [[package]] @@ -6152,7 +6262,7 @@ dependencies = [ "sha2 0.10.9", "signature", "spki", - "subtle", + "subtle 2.6.1", "zeroize", ] @@ -6226,7 +6336,7 @@ dependencies = [ "ring", "rustls-pki-types", "rustls-webpki 0.103.1", - "subtle", + "subtle 2.6.1", "zeroize", ] @@ -6377,10 +6487,10 @@ checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ "base16ct", "der", - "generic-array", + "generic-array 0.14.7", "pkcs8", "serdect 0.2.0", - "subtle", + "subtle 2.6.1", "zeroize", ] @@ -6672,7 +6782,7 @@ dependencies = [ "cfg-if", "cpufeatures", "digest 0.9.0", - "opaque-debug", + "opaque-debug 0.3.1", ] [[package]] @@ -6831,6 +6941,32 @@ dependencies = [ "system-deps", ] +[[package]] +name = "sphinx-packet" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c26f0c20d909fdda1c5d0ece3973127ca421984d55b000215df365e93722fc6e" +dependencies = [ + "aes", + "arrayref", + "blake2 0.8.1", + "bs58", + "byteorder", + "chacha", + "ctr", + "curve25519-dalek", + "digest 0.10.7", + "hkdf", + "hmac", + "lioness", + "rand 0.8.5", + "rand_distr", + "sha2 0.10.9", + "subtle 2.6.1", + "x25519-dalek", + "zeroize", +] + [[package]] name = "spin" version = "0.9.8" @@ -6911,6 +7047,12 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "subtle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" + [[package]] name = "subtle" version = "2.6.1" @@ -7499,7 +7641,7 @@ dependencies = [ "serde_repr", "sha2 0.10.9", "signature", - "subtle", + "subtle 2.6.1", "subtle-encoding", "tendermint-proto", "time", @@ -7554,7 +7696,7 @@ dependencies = [ "serde", "serde_bytes", "serde_json", - "subtle", + "subtle 2.6.1", "subtle-encoding", "tendermint", "tendermint-config", @@ -8115,7 +8257,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ "crypto-common", - "subtle", + "subtle 2.6.1", ] [[package]] diff --git a/nyx-chain-watcher/Cargo.toml b/nyx-chain-watcher/Cargo.toml index 3a2f09149b..479d24460e 100644 --- a/nyx-chain-watcher/Cargo.toml +++ b/nyx-chain-watcher/Cargo.toml @@ -12,6 +12,7 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +publish = false [dependencies] anyhow = { workspace = true } @@ -19,11 +20,11 @@ async-trait.workspace = true axum = { workspace = true, features = ["tokio"] } chrono = { workspace = true } clap = { workspace = true, features = ["cargo", "derive", "env"] } -nym-config = { path = "../common/config" } -nym-bin-common = { path = "../common/bin-common", features = ["output_format"] } -nym-network-defaults = { path = "../common/network-defaults" } -nym-task = { path = "../common/task" } -nym-validator-client = { path = "../common/client-libs/validator-client" } +nym-config = { workspace = true } +nym-bin-common = { workspace = true, features = ["output_format"] } +nym-network-defaults = { workspace = true } +nym-task = { workspace = true } +nym-validator-client = { workspace = true } nyxd-scraper-sqlite = { path = "../common/nyxd-scraper-sqlite" } reqwest = { workspace = true, features = ["rustls-tls"] } schemars = { workspace = true } diff --git a/scripts/nym-node-setup/network-tunnel-manager.sh b/scripts/nym-node-setup/network-tunnel-manager.sh index a901d5be3c..5360157ed3 100755 --- a/scripts/nym-node-setup/network-tunnel-manager.sh +++ b/scripts/nym-node-setup/network-tunnel-manager.sh @@ -350,8 +350,11 @@ apply_iptables_rules() { iptables -t nat -C POSTROUTING -o "$NETWORK_DEVICE" -j MASQUERADE 2>/dev/null || \ iptables -t nat -A POSTROUTING -o "$NETWORK_DEVICE" -j MASQUERADE - iptables -C FORWARD -i "$interface" -o "$NETWORK_DEVICE" -j ACCEPT 2>/dev/null || \ - iptables -I FORWARD 1 -i "$interface" -o "$NETWORK_DEVICE" -j ACCEPT + # governed by NYM-EXIT, do not add a broad FORWARD ACCEPT + if ! iptables -C FORWARD -i "$interface" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" 2>/dev/null; then + iptables -C FORWARD -i "$interface" -o "$NETWORK_DEVICE" -j ACCEPT 2>/dev/null || \ + iptables -I FORWARD 1 -i "$interface" -o "$NETWORK_DEVICE" -j ACCEPT + fi iptables -C FORWARD -i "$NETWORK_DEVICE" -o "$interface" -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || \ iptables -I FORWARD 2 -i "$NETWORK_DEVICE" -o "$interface" -m state --state RELATED,ESTABLISHED -j ACCEPT @@ -360,8 +363,11 @@ apply_iptables_rules() { ip6tables -t nat -C POSTROUTING -o "$NETWORK_DEVICE" -j MASQUERADE 2>/dev/null || \ ip6tables -t nat -A POSTROUTING -o "$NETWORK_DEVICE" -j MASQUERADE - ip6tables -C FORWARD -i "$interface" -o "$NETWORK_DEVICE" -j ACCEPT 2>/dev/null || \ - ip6tables -I FORWARD 1 -i "$interface" -o "$NETWORK_DEVICE" -j ACCEPT + # governed by NYM-EXIT, do not add a broad FORWARD ACCEPT + if ! ip6tables -C FORWARD -i "$interface" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" 2>/dev/null; then + ip6tables -C FORWARD -i "$interface" -o "$NETWORK_DEVICE" -j ACCEPT 2>/dev/null || \ + ip6tables -I FORWARD 1 -i "$interface" -o "$NETWORK_DEVICE" -j ACCEPT + fi ip6tables -C FORWARD -i "$NETWORK_DEVICE" -o "$interface" -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || \ ip6tables -I FORWARD 2 -i "$NETWORK_DEVICE" -o "$interface" -m state --state RELATED,ESTABLISHED -j ACCEPT @@ -559,7 +565,7 @@ create_nym_chain() { ip6tables -N "$NYM_CHAIN" fi - # remove *all* FORWARD -> NYM-EXIT jumps + # remove *all* FORWARD -> NYM-EXIT jumps while read -r rule; do spec="${rule#-A FORWARD }" iptables -D FORWARD $spec 2>/dev/null || true @@ -570,11 +576,20 @@ create_nym_chain() { ip6tables -D FORWARD $spec 2>/dev/null || true done < <(ip6tables -S FORWARD | grep -F " -j $NYM_CHAIN" || true) - # add the single correct hook - iptables -I FORWARD 1 -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" - ip6tables -I FORWARD 1 -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" + # remove broad ACCEPT rules for wg + tun outbound so NYM-EXIT is authoritative + iptables -D FORWARD -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j ACCEPT 2>/dev/null || true + iptables -D FORWARD -i "$TUNNEL_INTERFACE" -o "$NETWORK_DEVICE" -j ACCEPT 2>/dev/null || true + ip6tables -D FORWARD -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j ACCEPT 2>/dev/null || true + ip6tables -D FORWARD -i "$TUNNEL_INTERFACE" -o "$NETWORK_DEVICE" -j ACCEPT 2>/dev/null || true - ok "NYM-EXIT chain ready + single FORWARD hook installed" + # install the correct hook for both wg + tun + iptables -I FORWARD 1 -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" + iptables -I FORWARD 1 -i "$TUNNEL_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" + + ip6tables -I FORWARD 1 -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" + ip6tables -I FORWARD 1 -i "$TUNNEL_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" + + ok "NYM-EXIT chain ready + FORWARD hooks installed for $WG_INTERFACE and $TUNNEL_INTERFACE" } @@ -588,16 +603,10 @@ setup_nat_rules() { ip6tables -t nat -A POSTROUTING -o "$NETWORK_DEVICE" -j MASQUERADE fi - if ! iptables -C FORWARD -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j ACCEPT 2>/dev/null; then - iptables -I FORWARD 1 -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j ACCEPT - fi + # keep reverse RELATED,ESTABLISHED in FORWARD for return traffic. if ! iptables -C FORWARD -i "$NETWORK_DEVICE" -o "$WG_INTERFACE" -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null; then iptables -I FORWARD 2 -i "$NETWORK_DEVICE" -o "$WG_INTERFACE" -m state --state RELATED,ESTABLISHED -j ACCEPT fi - - if ! ip6tables -C FORWARD -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j ACCEPT 2>/dev/null; then - ip6tables -I FORWARD 1 -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j ACCEPT - fi if ! ip6tables -C FORWARD -i "$NETWORK_DEVICE" -o "$WG_INTERFACE" -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null; then ip6tables -I FORWARD 2 -i "$NETWORK_DEVICE" -o "$WG_INTERFACE" -m state --state RELATED,ESTABLISHED -j ACCEPT fi @@ -606,7 +615,7 @@ setup_nat_rules() { configure_exit_dns_and_icmp() { info "ensuring dns and icmp are allowed inside nym exit chain" - # Remove any existing DNS/ICMP rules first to avoid duplicates + # remove any existing DNS/ICMP rules first to avoid duplicates iptables -D "$NYM_CHAIN" -p udp --dport 53 -j ACCEPT 2>/dev/null || true iptables -D "$NYM_CHAIN" -p tcp --dport 53 -j ACCEPT 2>/dev/null || true iptables -D "$NYM_CHAIN" -p icmp --icmp-type echo-request -j ACCEPT 2>/dev/null || true @@ -615,7 +624,7 @@ configure_exit_dns_and_icmp() { ip6tables -D "$NYM_CHAIN" -p tcp --dport 53 -j ACCEPT 2>/dev/null || true ip6tables -D "$NYM_CHAIN" -p ipv6-icmp -j ACCEPT 2>/dev/null || true - # Insert rules at the beginning in correct order: DNS first, then ICMP + # insert rules at the beginning in correct order: DNS first, then ICMP iptables -I "$NYM_CHAIN" 1 -p udp --dport 53 -j ACCEPT iptables -I "$NYM_CHAIN" 2 -p tcp --dport 53 -j ACCEPT iptables -I "$NYM_CHAIN" 3 -p icmp --icmp-type echo-request -j ACCEPT @@ -642,12 +651,13 @@ apply_port_allowlist() { ["NTP"]="123" ["IMAP"]="143" ["IMAP3"]="220" + ["SSHAlternative1"]="223" ["LDAP"]="389" ["HTTPS"]="443" ["SMBWindowsFileShare"]="445" ["Kpasswd"]="464" # this port is opened and rate limited in apply_smtps_465_rate_limit - # ["SMTP"]="465" + # ["SMTP"]="465" ["RTSP"]="554" ["SMTPSubmission"]="587" ["LDAPS"]="636" @@ -679,10 +689,13 @@ apply_port_allowlist() { ["GNUnet"]="2086-2087" ["NBX"]="2095-2096" ["Zephyr"]="2102-2104" + ["SSHAlternative2"]="2222" ["XboxLive"]="3074" ["MySQL"]="3306" + ["SteamGaming1"]="3478" ["SVN"]="3690" ["RWHOIS"]="4321" + ["SteamGaming2"]="4379-4380" ["Virtuozzo"]="4643" ["RTPVOIP"]="5000-5005" ["MMCC"]="5050" @@ -718,11 +731,12 @@ apply_port_allowlist() { ["DarkFi"]="26661" ["Steam"]="27000-27050" ["WhatsAppRange"]="3478-3484" + ["DiscordVoiceChat"]="50000-65535" ["ElectrumSSL"]="50002" ["MOSH"]="60000-61000" ["Mumble"]="64738" ["Metadata"]="51830" - + ) local port @@ -768,16 +782,17 @@ EOF if [[ -n "$ip_range" ]]; then - # ipv4 reject - if ! iptables -C "$NYM_CHAIN" -d "$ip_range" -j REJECT 2>/dev/null; then - iptables -A "$NYM_CHAIN" -d "$ip_range" -j REJECT --reject-with icmp-port-unreachable \ + # insert blocklist BEFORE the allowlist (after DNS/ICMP bootstrap rules) + # ipv4 reject (DNS/ICMP occupy positions 1-4) + if ! iptables -C "$NYM_CHAIN" -d "$ip_range" -j REJECT --reject-with icmp-port-unreachable 2>/dev/null; then + iptables -I "$NYM_CHAIN" 5 -d "$ip_range" -j REJECT --reject-with icmp-port-unreachable \ || error "warning: failed adding ipv4 reject for $ip_range" fi - # ipv6 reject + # ipv6 reject (DNS/ICMP occupy positions 1-3) if [[ "$ip_range" == *":"* ]]; then - if ! ip6tables -C "$NYM_CHAIN" -d "$ip_range" -j REJECT 2>/dev/null; then - ip6tables -A "$NYM_CHAIN" -d "$ip_range" -j REJECT \ + if ! ip6tables -C "$NYM_CHAIN" -d "$ip_range" -j REJECT --reject-with icmp6-port-unreachable 2>/dev/null; then + ip6tables -I "$NYM_CHAIN" 4 -d "$ip_range" -j REJECT --reject-with icmp6-port-unreachable \ || error "warning: failed adding ipv6 reject for $ip_range" fi fi @@ -808,8 +823,11 @@ clear_exit_policy_rules() { iptables -F "$NYM_CHAIN" 2>/dev/null || true ip6tables -F "$NYM_CHAIN" 2>/dev/null || true + # remove hooks for BOTH wg + tun iptables -D FORWARD -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" 2>/dev/null || true + iptables -D FORWARD -i "$TUNNEL_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" 2>/dev/null || true ip6tables -D FORWARD -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" 2>/dev/null || true + ip6tables -D FORWARD -i "$TUNNEL_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" 2>/dev/null || true iptables -X "$NYM_CHAIN" 2>/dev/null || true ip6tables -X "$NYM_CHAIN" 2>/dev/null || true @@ -819,21 +837,36 @@ show_exit_policy_status() { info "nym exit policy status" info "network device: $NETWORK_DEVICE" info "wireguard interface: $WG_INTERFACE" + info "tunnel interface: $TUNNEL_INTERFACE" echo if ! ip link show "$WG_INTERFACE" >/dev/null 2>&1; then error "warning: wireguard interface $WG_INTERFACE not found" else - info "interface details:" + info "wireguard interface details:" ip link show "$WG_INTERFACE" echo - info "ipv4 addresses:" + info "wireguard ipv4 addresses:" ip -4 addr show dev "$WG_INTERFACE" echo - info "ipv6 addresses:" + info "wireguard ipv6 addresses:" ip -6 addr show dev "$WG_INTERFACE" fi + echo + if ! ip link show "$TUNNEL_INTERFACE" >/dev/null 2>&1; then + error "warning: tunnel interface $TUNNEL_INTERFACE not found" + else + info "tunnel interface details:" + ip link show "$TUNNEL_INTERFACE" + echo + info "tunnel ipv4 addresses:" + ip -4 addr show dev "$TUNNEL_INTERFACE" + echo + info "tunnel ipv6 addresses:" + ip -6 addr show dev "$TUNNEL_INTERFACE" + fi + echo info "iptables chains for ${NYM_CHAIN}:" iptables -L "$NYM_CHAIN" -n -v 2>/dev/null || echo "ipv4 chain not found" @@ -1039,6 +1072,8 @@ test_port_range_rules() { "8332-8333:tcp:bitcoin" "18080-18081:tcp:monero" "3478-3484:tcp:whatsapp" + "50000-65535:tcp:discord" + "4379-4380:tcp:steam" ) local failures=0 @@ -1100,17 +1135,32 @@ test_forward_chain_hook() { local failures=0 + # verify BOTH interfaces are hooked to NYM-EXIT (IPv4 + IPv6) if iptables -C FORWARD -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" 2>/dev/null; then - ok "ipv4 forward hook ok: -i $WG_INTERFACE -o $NETWORK_DEVICE -> $NYM_CHAIN" + ok "ipv4 forward hook ok (wg): -i $WG_INTERFACE -o $NETWORK_DEVICE -> $NYM_CHAIN" else - error "ipv4 forward hook missing or wrong" + error "ipv4 forward hook missing or wrong (wg)" + ((failures++)) + fi + + if iptables -C FORWARD -i "$TUNNEL_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" 2>/dev/null; then + ok "ipv4 forward hook ok (tun): -i $TUNNEL_INTERFACE -o $NETWORK_DEVICE -> $NYM_CHAIN" + else + error "ipv4 forward hook missing or wrong (tun)" ((failures++)) fi if ip6tables -C FORWARD -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" 2>/dev/null; then - ok "ipv6 forward hook ok: -i $WG_INTERFACE -o $NETWORK_DEVICE -> $NYM_CHAIN" + ok "ipv6 forward hook ok (wg): -i $WG_INTERFACE -o $NETWORK_DEVICE -> $NYM_CHAIN" else - error "ipv6 forward hook missing or wrong" + error "ipv6 forward hook missing or wrong (wg)" + ((failures++)) + fi + + if ip6tables -C FORWARD -i "$TUNNEL_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" 2>/dev/null; then + ok "ipv6 forward hook ok (tun): -i $TUNNEL_INTERFACE -o $NETWORK_DEVICE -> $NYM_CHAIN" + else + error "ipv6 forward hook missing or wrong (tun)" ((failures++)) fi @@ -1178,7 +1228,6 @@ exit_policy_run_tests() { ############################################################################### nym_tunnel_setup() { - # this mirrors your previous chain of calls but inside one script info "running full tunnel setup for ${TUNNEL_INTERFACE} and ${WG_INTERFACE}" check_tunnel_iptables "$TUNNEL_INTERFACE" diff --git a/sdk/ffi/cpp/Cargo.toml b/sdk/ffi/cpp/Cargo.toml index 8ac5f608db..307f669a15 100644 --- a/sdk/ffi/cpp/Cargo.toml +++ b/sdk/ffi/cpp/Cargo.toml @@ -1,8 +1,12 @@ [package] name = "nym-cpp-ffi" -version = "0.1.2" +version.workspace = true edition = "2021" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true license.workspace = true +description = "C++ FFI bindings for Nym Rust SDK" [lib] name = "nym_cpp_ffi" @@ -12,10 +16,10 @@ crate-type = ["cdylib"] # Async runtime tokio = { workspace = true, features = ["full"] } # Nym clients, addressing, packet format, common tools (logging), ffi shared -nym-sdk = { path = "../../rust/nym-sdk/" } -nym-bin-common = { path = "../../../common/bin-common", features = ["basic_tracing"] } -nym-sphinx-anonymous-replies = { path = "../../../common/nymsphinx/anonymous-replies" } -nym-ffi-shared = { path = "../shared" } +nym-sdk = { workspace = true } +nym-bin-common = { workspace = true, features = ["basic_tracing"] } +nym-sphinx-anonymous-replies = { workspace = true } +nym-ffi-shared = { workspace = true } lazy_static = { workspace = true } # error handling anyhow = { workspace = true } diff --git a/sdk/ffi/go/Cargo.toml b/sdk/ffi/go/Cargo.toml index 3366f424a5..76b1e91a57 100644 --- a/sdk/ffi/go/Cargo.toml +++ b/sdk/ffi/go/Cargo.toml @@ -1,8 +1,12 @@ [package] name = "nym-go-ffi" -version = "0.2.2" +version.workspace = true edition = "2021" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true license.workspace = true +description = "Go FFI bindings for Nym Rust SDK" [lib] crate-type = ["cdylib"] @@ -12,11 +16,11 @@ name = "nym_go_ffi" # Bindgen uniffi = { workspace = true, features = ["cli"] } # Nym clients, addressing, packet format, common tools (logging), ffi shared -nym-sdk = { path = "../../rust/nym-sdk/" } -nym-crypto = { path = "../../../common/crypto" } -nym-bin-common = { path = "../../../common/bin-common", features = ["basic_tracing"] } -nym-sphinx-anonymous-replies = { path = "../../../common/nymsphinx/anonymous-replies" } -nym-ffi-shared = { path = "../shared" } +nym-sdk = { workspace = true } +nym-crypto = { workspace = true } +nym-bin-common = { workspace = true, features = ["basic_tracing"] } +nym-sphinx-anonymous-replies = { workspace = true } +nym-ffi-shared = { workspace = true } # Async runtime tokio = { workspace = true, features = ["full"] } lazy_static = { workspace = true } diff --git a/sdk/ffi/shared/Cargo.toml b/sdk/ffi/shared/Cargo.toml index e409035681..d5e3434892 100644 --- a/sdk/ffi/shared/Cargo.toml +++ b/sdk/ffi/shared/Cargo.toml @@ -1,17 +1,21 @@ [package] name = "nym-ffi-shared" -version = "0.2.1" +version.workspace = true edition = "2021" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true license.workspace = true +description = "Common crate for use by Rust SDK FFI crates" [dependencies] # Async runtime tokio = { workspace = true, features = ["full"] } # Nym clients, addressing, packet format, common tools (logging) -nym-sdk = { path = "../../rust/nym-sdk/" } -nym-bin-common = { path = "../../../common/bin-common" } -nym-crypto = { path = "../../../common/crypto" } -nym-sphinx-anonymous-replies = { path = "../../../common/nymsphinx/anonymous-replies" } +nym-sdk = { workspace = true } +nym-bin-common = { workspace = true } +nym-crypto = { workspace = true } +nym-sphinx-anonymous-replies = { workspace = true } # static var macro lazy_static = { workspace = true } # error handling diff --git a/sdk/rust/nym-sdk/Cargo.toml b/sdk/rust/nym-sdk/Cargo.toml index 523e5801c5..70e1441e8a 100644 --- a/sdk/rust/nym-sdk/Cargo.toml +++ b/sdk/rust/nym-sdk/Cargo.toml @@ -1,8 +1,11 @@ [package] name = "nym-sdk" -version = "0.1.0" +version.workspace = true edition = "2021" license.workspace = true +description = "Nym's Rust SDK" +repository = "https://github.com/nymtech/nym/sdk/rust/nym-sdk/" +# documentation = "https://docs.rs/nym-sdk" Max: once we upload to crates.io and this is generated, this can be uncommented. # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -17,33 +20,33 @@ path = "src/tcp_proxy/bin/proxy_client.rs" [dependencies] async-trait = { workspace = true } bip39 = { workspace = true } -nym-client-core = { path = "../../../common/client-core", features = [ +nym-client-core = { workspace = true, features = [ "fs-credentials-storage", "fs-surb-storage", "fs-gateways-storage", ] } -nym-crypto = { path = "../../../common/crypto" } -nym-gateway-requests = { path = "../../../common/gateway-requests" } -nym-bandwidth-controller = { path = "../../../common/bandwidth-controller" } -nym-credentials = { path = "../../../common/credentials" } -nym-credentials-interface = { path = "../../../common/credentials-interface" } -nym-credential-storage = { path = "../../../common/credential-storage" } -nym-credential-utils = { path = "../../../common/credential-utils" } -nym-network-defaults = { path = "../../../common/network-defaults" } -nym-sphinx = { path = "../../../common/nymsphinx" } -nym-statistics-common = { path = "../../../common/statistics" } -nym-task = { path = "../../../common/task" } -nym-topology = { path = "../../../common/topology" } -nym-socks5-client-core = { path = "../../../common/socks5-client-core" } -nym-validator-client = { path = "../../../common/client-libs/validator-client", features = [ +nym-crypto = { workspace = true } +nym-gateway-requests = { workspace = true } +nym-bandwidth-controller = { workspace = true } +nym-credentials = { workspace = true } +nym-credentials-interface = { workspace = true } +nym-credential-storage = { workspace = true } +nym-credential-utils = { workspace = true } +nym-network-defaults = { workspace = true } +nym-sphinx = { workspace = true } +nym-statistics-common = { workspace = true } +nym-task = { workspace = true } +nym-topology = { workspace = true } +nym-socks5-client-core = { workspace = true } +nym-validator-client = { workspace = true , features = [ "http-client", ] } -nym-http-api-client = { path = "../../../common/http-api-client" } -nym-socks5-requests = { path = "../../../common/socks5/requests" } -nym-ordered-buffer = { path = "../../../common/socks5/ordered-buffer" } -nym-service-providers-common = { path = "../../../service-providers/common" } -nym-sphinx-addressing = { path = "../../../common/nymsphinx/addressing" } -nym-bin-common = { path = "../../../common/bin-common", features = [ +nym-http-api-client = { workspace = true } +nym-socks5-requests = { workspace = true } +nym-ordered-buffer = { workspace = true } +nym-service-providers-common = { workspace = true } +nym-sphinx-addressing = { workspace = true } +nym-bin-common = { workspace = true, features = [ "basic_tracing", ] } bytecodec = { workspace = true } @@ -82,7 +85,7 @@ reqwest = { workspace = true, features = ["json", "socks"] } thiserror = { workspace = true } tokio = { workspace = true, features = ["full"] } time = { workspace = true } -nym-bin-common = { path = "../../../common/bin-common", features = ["basic_tracing"] } +nym-bin-common = {workspace = true, features = ["basic_tracing"] } # extra dependencies for libp2p examples #libp2p = { git = "https://github.com/ChainSafe/rust-libp2p.git", rev = "e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6", features = [ "identify", "macros", "ping", "tokio", "tcp", "dns", "websocket", "noise", "mplex", "yamux", "gossipsub" ]} diff --git a/sdk/rust/nym-sdk/src/client_pool.rs b/sdk/rust/nym-sdk/src/client_pool.rs index 0a2471275b..ae3d1a200d 100644 --- a/sdk/rust/nym-sdk/src/client_pool.rs +++ b/sdk/rust/nym-sdk/src/client_pool.rs @@ -1,3 +1,6 @@ +//! A variable-size pool of ephemeral Nym Clients to quickly grab and use. +//! +//! //! use crate::mixnet::{MixnetClient, MixnetClientBuilder, NymNetworkDetails}; //! use anyhow::Result; //! use std::fmt; diff --git a/sdk/rust/nym-sdk/src/mixnet/client.rs b/sdk/rust/nym-sdk/src/mixnet/client.rs index 5020c1bd5c..20e562e662 100644 --- a/sdk/rust/nym-sdk/src/mixnet/client.rs +++ b/sdk/rust/nym-sdk/src/mixnet/client.rs @@ -601,6 +601,13 @@ where ); let available_gateways = self.available_gateways().await?; + for node in available_gateways.iter() { + debug!( + "node_id={}, identity_key={}", + node.node_id, + node.identity_key.to_base58_string() + ); + } Ok(GatewaySetup::New { specification: selection_spec, diff --git a/sdk/rust/nym-sdk/src/mixnet/socks5_client.rs b/sdk/rust/nym-sdk/src/mixnet/socks5_client.rs index 0a9ee37c90..1875924616 100644 --- a/sdk/rust/nym-sdk/src/mixnet/socks5_client.rs +++ b/sdk/rust/nym-sdk/src/mixnet/socks5_client.rs @@ -1,10 +1,13 @@ +use std::time::Duration; + use nym_client_core::client::base_client::ClientState; use nym_socks5_client_core::config::Socks5; use nym_sphinx::addressing::clients::Recipient; use nym_task::connections::LaneQueueLengths; use nym_task::ShutdownTracker; +use tokio::sync::RwLockReadGuard; -use nym_topology::NymTopology; +use nym_topology::{NymRouteProvider, NymTopology, NymTopologyError}; use crate::mixnet::client::MixnetClientBuilder; use crate::Result; @@ -85,4 +88,28 @@ impl Socks5MixnetClient { pub async fn disconnect(self) { self.task_handle.shutdown().await; } + + /// Gets the current route provider if topology is available. + /// Returns `None` if topology is empty/not yet fetched. + async fn read_current_route_provider(&self) -> Option> { + self.client_state + .topology_accessor + .current_route_provider() + .await + } + + /// Wait for topology to become available, with a timeout. + /// Returns `Ok(())` when topology is ready, or `Err` if timeout is reached. + pub async fn wait_for_topology(&self, timeout: Duration) -> Result<(), NymTopologyError> { + let deadline = tokio::time::Instant::now() + timeout; + loop { + if self.read_current_route_provider().await.is_some() { + return Ok(()); + } + if tokio::time::Instant::now() >= deadline { + return Err(NymTopologyError::EmptyNetworkTopology); + } + tokio::time::sleep(Duration::from_millis(100)).await; + } + } } diff --git a/service-providers/common/Cargo.toml b/service-providers/common/Cargo.toml index f8fb5b201b..ad8b2b7574 100644 --- a/service-providers/common/Cargo.toml +++ b/service-providers/common/Cargo.toml @@ -1,14 +1,18 @@ [package] name = "nym-service-providers-common" -version = "0.1.0" +version.workspace = true edition = "2021" license.workspace = true +description = "Common crate for Nym Service Providers" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -nym-bin-common = { path = "../../common/bin-common" } -nym-sphinx-anonymous-replies = { path = "../../common/nymsphinx/anonymous-replies" } +nym-bin-common = { workspace = true } +nym-sphinx-anonymous-replies = { workspace = true } async-trait = { workspace = true } log = { workspace = true } @@ -21,6 +25,5 @@ serde_json = { workspace = true } anyhow = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } -nym-sdk = { path = "../../sdk/rust/nym-sdk" } -nym-socks5-requests = { path = "../../common/socks5/requests" } - +nym-sdk = { workspace = true } +nym-socks5-requests = { workspace = true } diff --git a/service-providers/ip-packet-router/Cargo.toml b/service-providers/ip-packet-router/Cargo.toml index aee3a35ab8..5e3340871f 100644 --- a/service-providers/ip-packet-router/Cargo.toml +++ b/service-providers/ip-packet-router/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "nym-ip-packet-router" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license = "GPL-3.0" +publish = false [dependencies] anyhow.workspace = true @@ -17,25 +18,26 @@ clap.workspace = true etherparse = { workspace = true } futures = { workspace = true } log = { workspace = true } -nym-bin-common = { path = "../../common/bin-common", features = ["clap", "basic_tracing"] } -nym-client-core = { path = "../../common/client-core" } -nym-config = { path = "../../common/config" } -nym-crypto = { path = "../../common/crypto" } -nym-exit-policy = { path = "../../common/exit-policy" } -nym-id = { path = "../../common/nym-id" } -nym-ip-packet-requests = { path = "../../common/ip-packet-requests" } -nym-kcp = { path = "../../common/nym-kcp" } -nym-network-defaults = { path = "../../common/network-defaults" } + +nym-bin-common = { workspace = true, features = ["clap", "basic_tracing"] } +nym-client-core = { workspace = true } +nym-config = { workspace = true } +nym-crypto = { workspace = true } +nym-exit-policy = { workspace = true } +nym-id = { workspace = true } +nym-ip-packet-requests = { workspace = true } +nym-kcp = { path = "../../common/nym-kcp" } # TODO MAX add to workspace dependencies +nym-network-defaults = { workspace = true } nym-network-requester = { path = "../network-requester" } -nym-sdk = { path = "../../sdk/rust/nym-sdk" } -nym-service-provider-requests-common = { path = "../../common/service-provider-requests-common" } -nym-service-providers-common = { path = "../common" } -nym-sphinx = { path = "../../common/nymsphinx" } -nym-task = { path = "../../common/task" } -nym-tun = { path = "../../common/tun" } -nym-types = { path = "../../common/types" } -nym-wireguard = { path = "../../common/wireguard" } -nym-wireguard-types = { path = "../../common/wireguard-types" } +nym-sdk = { workspace = true } +nym-service-provider-requests-common = { workspace = true } +nym-service-providers-common = { workspace = true } +nym-sphinx = { workspace = true } +nym-task = { workspace = true } +nym-tun = { workspace = true } +nym-types = { workspace = true } +nym-wireguard = { workspace = true } +nym-wireguard-types = { workspace = true } rand = { workspace = true } reqwest.workspace = true serde = { workspace = true, features = ["derive"] } diff --git a/service-providers/network-requester/Cargo.toml b/service-providers/network-requester/Cargo.toml index 1c30f17c30..8d79940d1a 100644 --- a/service-providers/network-requester/Cargo.toml +++ b/service-providers/network-requester/Cargo.toml @@ -4,10 +4,11 @@ [package] name = "nym-network-requester" license = "GPL-3.0" -version = "1.1.70" +version = "1.1.71" authors.workspace = true edition.workspace = true rust-version = "1.85" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -42,25 +43,25 @@ time = { workspace = true } zeroize = { workspace = true } # internal -nym-async-file-watcher = { path = "../../common/async-file-watcher" } -nym-bin-common = { path = "../../common/bin-common", features = ["output_format", "clap", "basic_tracing"] } -nym-client-core = { path = "../../common/client-core", features = ["cli", "fs-gateways-storage", "fs-surb-storage"] } -nym-client-websocket-requests = { path = "../../clients/native/websocket-requests" } -nym-config = { path = "../../common/config" } -nym-credentials = { path = "../../common/credentials" } -nym-credential-storage = { path = "../../common/credential-storage" } -nym-crypto = { path = "../../common/crypto" } -nym-network-defaults = { path = "../../common/network-defaults" } -nym-ordered-buffer = { path = "../../common/socks5/ordered-buffer" } -nym-sdk = { path = "../../sdk/rust/nym-sdk" } -nym-service-providers-common = { path = "../common" } -nym-socks5-proxy-helpers = { path = "../../common/socks5/proxy-helpers" } -nym-socks5-requests = { path = "../../common/socks5/requests" } -nym-sphinx = { path = "../../common/nymsphinx" } -nym-task = { path = "../../common/task" } -nym-types = { path = "../../common/types" } -nym-exit-policy = { path = "../../common/exit-policy", features = ["client"] } -nym-id = { path = "../../common/nym-id" } +nym-async-file-watcher = { workspace = true } +nym-bin-common = { workspace = true, features = ["output_format", "clap", "basic_tracing"] } +nym-client-core = { workspace = true, features = ["cli", "fs-gateways-storage", "fs-surb-storage"] } +nym-client-websocket-requests = { workspace = true } +nym-config = { workspace = true } +nym-credentials = { workspace = true } +nym-credential-storage = { workspace = true } +nym-crypto = { workspace = true } +nym-network-defaults = { workspace = true } +nym-ordered-buffer = { workspace = true } +nym-sdk = { workspace = true } +nym-service-providers-common = { workspace = true } +nym-socks5-proxy-helpers = { workspace = true } +nym-socks5-requests = { workspace = true } +nym-sphinx = { workspace = true } +nym-task = { workspace = true } +nym-types = { workspace = true } +nym-exit-policy = { workspace = true, features = ["client"] } +nym-id = { workspace = true } [dev-dependencies] tempfile = { workspace = true } diff --git a/tools/echo-server/Cargo.toml b/tools/echo-server/Cargo.toml index f58a0e46b3..4f9ec4de02 100644 --- a/tools/echo-server/Cargo.toml +++ b/tools/echo-server/Cargo.toml @@ -8,7 +8,7 @@ documentation.workspace = true edition.workspace = true license.workspace = true rust-version.workspace = true - +publish = false [[bin]] name = "echo-server" @@ -26,14 +26,14 @@ serde = { workspace = true, features = ["derive"] } tracing.workspace = true tracing-subscriber = { workspace = true } bytecodec = { workspace = true } -nym-sdk = { path = "../../sdk/rust/nym-sdk" } +nym-sdk = { workspace = true } bytes.workspace = true dirs.workspace = true clap.workspace = true -nym-bin-common = { path = "../../common/bin-common", features = [ +nym-bin-common = { workspace = true, features = [ "basic_tracing", "output_format", ] } -nym-crypto = { path = "../../common/crypto", features = ["asymmetric"] } +nym-crypto = { workspace = true, features = ["asymmetric"] } futures = { workspace = true } tempfile.workspace = true diff --git a/tools/internal/contract-state-importer/importer-cli/Cargo.toml b/tools/internal/contract-state-importer/importer-cli/Cargo.toml index db708db2e8..143b18ffbb 100644 --- a/tools/internal/contract-state-importer/importer-cli/Cargo.toml +++ b/tools/internal/contract-state-importer/importer-cli/Cargo.toml @@ -9,6 +9,7 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +publish = false [dependencies] anyhow = { workspace = true } @@ -21,9 +22,9 @@ tokio = { workspace = true, features = ["rt-multi-thread", "net", "signal"] } tracing = { workspace = true } importer-contract = { path = "../importer-contract" } -nym-validator-client = { path = "../../../../common/client-libs/validator-client" } -nym-bin-common = { path = "../../../../common/bin-common", features = ["basic_tracing"] } -nym-network-defaults = { path = "../../../../common/network-defaults" } +nym-validator-client = { workspace = true } +nym-bin-common = { workspace = true, features = ["basic_tracing"] } +nym-network-defaults = { workspace = true } -nym-mixnet-contract-common = { path = "../../../../common/cosmwasm-smart-contracts/mixnet-contract" } -nym-vesting-contract-common = { path = "../../../../common/cosmwasm-smart-contracts/vesting-contract" } \ No newline at end of file +nym-mixnet-contract-common = { workspace = true } +nym-vesting-contract-common = { workspace = true } diff --git a/tools/internal/contract-state-importer/importer-contract/Cargo.toml b/tools/internal/contract-state-importer/importer-contract/Cargo.toml index 2c09e7071e..72f0476206 100644 --- a/tools/internal/contract-state-importer/importer-contract/Cargo.toml +++ b/tools/internal/contract-state-importer/importer-contract/Cargo.toml @@ -8,7 +8,7 @@ documentation.workspace = true edition.workspace = true license.workspace = true readme.workspace = true - +publish = false [lib] crate-type = ["cdylib", "rlib"] @@ -21,4 +21,4 @@ cosmwasm-schema = { workspace = true } [features] default = ["library"] -library = [] \ No newline at end of file +library = [] diff --git a/tools/internal/mixnet-connectivity-check/Cargo.toml b/tools/internal/mixnet-connectivity-check/Cargo.toml index af55790aa4..9d2d36c9d8 100644 --- a/tools/internal/mixnet-connectivity-check/Cargo.toml +++ b/tools/internal/mixnet-connectivity-check/Cargo.toml @@ -9,6 +9,7 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +publish = false [dependencies] anyhow = { workspace = true } @@ -17,7 +18,7 @@ futures = { workspace = true } tracing = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread", "signal", "macros"] } -nym-network-defaults = { path = "../../../common/network-defaults" } -nym-bin-common = { path = "../../../common/bin-common", features = ["basic_tracing", "output_format"] } -nym-crypto = { path = "../../../common/crypto", features = ["asymmetric"] } -nym-sdk = { path = "../../../sdk/rust/nym-sdk" } +nym-network-defaults = { workspace = true } +nym-bin-common = { workspace = true, features = ["basic_tracing", "output_format"] } +nym-crypto = { workspace = true, features = ["asymmetric"] } +nym-sdk = { workspace = true } diff --git a/tools/internal/ssl-inject/Cargo.toml b/tools/internal/ssl-inject/Cargo.toml index 86220b4919..d87167628e 100644 --- a/tools/internal/ssl-inject/Cargo.toml +++ b/tools/internal/ssl-inject/Cargo.toml @@ -3,6 +3,7 @@ name = "ssl-inject" version = "0.1.0" edition = "2021" license.workspace = true +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/tools/internal/testnet-manager/Cargo.toml b/tools/internal/testnet-manager/Cargo.toml index 6f799d84a4..2ca0b01ece 100644 --- a/tools/internal/testnet-manager/Cargo.toml +++ b/tools/internal/testnet-manager/Cargo.toml @@ -8,6 +8,8 @@ documentation.workspace = true edition.workspace = true license.workspace = true rust-version.workspace = true +publish = false + [dependencies] anyhow.workspace = true @@ -32,24 +34,24 @@ url.workspace = true zeroize = { workspace = true, features = ["zeroize_derive"] } -nym-bin-common = { path = "../../../common/bin-common", features = ["output_format", "basic_tracing"] } -nym-crypto = { path = "../../../common/crypto", features = ["asymmetric", "rand", "serde"] } -nym-config = { path = "../../../common/config" } -nym-validator-client = { path = "../../../common/client-libs/validator-client" } -nym-compact-ecash = { path = "../../../common/nym_offline_compact_ecash" } -nym-http-api-client = { path = "../../../common/http-api-client" } +nym-bin-common = { workspace = true, features = ["output_format", "basic_tracing"] } +nym-crypto = { workspace = true, features = ["asymmetric", "rand", "serde"] } +nym-config = { workspace = true } +nym-validator-client = { workspace = true } +nym-compact-ecash = { workspace = true } +nym-http-api-client = { workspace = true } dkg-bypass-contract = { path = "dkg-bypass-contract", default-features = false } # contracts: -nym-mixnet-contract-common = { path = "../../../common/cosmwasm-smart-contracts/mixnet-contract" } -nym-contracts-common = { path = "../../../common/cosmwasm-smart-contracts/contracts-common" } -nym-vesting-contract-common = { path = "../../../common/cosmwasm-smart-contracts/vesting-contract" } -nym-group-contract-common = { path = "../../../common/cosmwasm-smart-contracts/group-contract" } -nym-ecash-contract-common = { path = "../../../common/cosmwasm-smart-contracts/ecash-contract" } -nym-coconut-dkg-common = { path = "../../../common/cosmwasm-smart-contracts/coconut-dkg" } -nym-multisig-contract-common = { path = "../../../common/cosmwasm-smart-contracts/multisig-contract" } -nym-performance-contract-common = { path = "../../../common/cosmwasm-smart-contracts/nym-performance-contract" } -nym-pemstore = { path = "../../../common/pemstore" } +nym-mixnet-contract-common = { workspace = true } +nym-contracts-common = { workspace = true } +nym-vesting-contract-common = { workspace = true } +nym-group-contract-common = { workspace = true } +nym-ecash-contract-common = { workspace = true } +nym-coconut-dkg-common = { workspace = true } +nym-multisig-contract-common = { workspace = true } +nym-performance-contract-common = { workspace = true } +nym-pemstore = { workspace = true } [build-dependencies] diff --git a/tools/internal/testnet-manager/dkg-bypass-contract/Cargo.toml b/tools/internal/testnet-manager/dkg-bypass-contract/Cargo.toml index fa45302f00..5074d8d91f 100644 --- a/tools/internal/testnet-manager/dkg-bypass-contract/Cargo.toml +++ b/tools/internal/testnet-manager/dkg-bypass-contract/Cargo.toml @@ -5,6 +5,7 @@ edition = { workspace = true } authors = { workspace = true } license = { workspace = true } repository = { workspace = true } +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -17,9 +18,9 @@ cosmwasm-std = { workspace = true } cosmwasm-schema = { workspace = true } cw-storage-plus = { workspace = true } -nym-coconut-dkg-common = { path = "../../../../common/cosmwasm-smart-contracts/coconut-dkg" } -nym-contracts-common = { path = "../../../../common/cosmwasm-smart-contracts/contracts-common" } +nym-coconut-dkg-common = { workspace = true } +nym-contracts-common = { workspace = true } [features] default = ["library"] -library = [] \ No newline at end of file +library = [] diff --git a/tools/internal/validator-status-check/Cargo.toml b/tools/internal/validator-status-check/Cargo.toml index a0d09e336f..7b335095b7 100644 --- a/tools/internal/validator-status-check/Cargo.toml +++ b/tools/internal/validator-status-check/Cargo.toml @@ -9,6 +9,7 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +publish = false [dependencies] anyhow = { workspace = true } @@ -22,10 +23,10 @@ tracing = { workspace = true } time = { workspace = true } -nym-validator-client = { path = "../../../common/client-libs/validator-client" } -nym-bin-common = { path = "../../../common/bin-common", features = ["output_format", "basic_tracing"] } -nym-network-defaults = { path = "../../../common/network-defaults" } -nym-http-api-client = { path = "../../../common/http-api-client" } +nym-validator-client = { workspace = true } +nym-bin-common = { workspace = true, features = ["output_format", "basic_tracing"] } +nym-network-defaults = { workspace = true } +nym-http-api-client = { workspace = true } [lints] workspace = true diff --git a/tools/nym-cli/Cargo.toml b/tools/nym-cli/Cargo.toml index 56ab89f71b..02ec79ce93 100644 --- a/tools/nym-cli/Cargo.toml +++ b/tools/nym-cli/Cargo.toml @@ -1,9 +1,10 @@ [package] name = "nym-cli" -version = "1.1.69" +version = "1.1.70" authors.workspace = true edition = "2021" license.workspace = true +publish = false [dependencies] base64 = { workspace = true } @@ -22,9 +23,9 @@ anyhow = { workspace = true } tap = { workspace = true } nym-cli-commands = { path = "../../common/commands" } -nym-bin-common = { path = "../../common/bin-common", features = ["basic_tracing"] } -nym-validator-client = { path = "../../common/client-libs/validator-client", features = ["http-client"] } -nym-network-defaults = { path = "../../common/network-defaults" } +nym-bin-common = { workspace = true, features = ["basic_tracing"] } +nym-validator-client = { workspace = true, features = ["http-client"] } +nym-network-defaults = { workspace = true } [package.metadata.deb] name = "nym-cli" diff --git a/tools/nym-id-cli/Cargo.toml b/tools/nym-id-cli/Cargo.toml index bada736bfd..7d264bb2e8 100644 --- a/tools/nym-id-cli/Cargo.toml +++ b/tools/nym-id-cli/Cargo.toml @@ -7,6 +7,7 @@ homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -17,6 +18,6 @@ clap = { workspace = true, features = ["derive"] } tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } tracing.workspace = true -nym-bin-common = { path = "../../common/bin-common", features = ["output_format", "basic_tracing"] } -nym-credential-storage = { path = "../../common/credential-storage" } -nym-id = { path = "../../common/nym-id" } +nym-bin-common = { workspace = true, features = ["output_format", "basic_tracing"] } +nym-credential-storage = { workspace = true } +nym-id = { workspace = true } diff --git a/tools/nym-lp-client/Cargo.toml b/tools/nym-lp-client/Cargo.toml index f26c2f34f9..d3e6b08bd5 100644 --- a/tools/nym-lp-client/Cargo.toml +++ b/tools/nym-lp-client/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" description = "LP+KCP mixnet client" license.workspace = true +publish = false [[bin]] name = "nym-lp-client" @@ -27,6 +28,7 @@ url = { workspace = true } # Nym crates nym-api-requests = { path = "../../nym-api/nym-api-requests" } nym-crypto = { path = "../../common/crypto" } +nym-kkt-ciphersuite = { workspace = true } nym-http-api-client = { path = "../../common/http-api-client" } nym-kcp = { path = "../../common/nym-kcp" } nym-lp = { path = "../../common/nym-lp" } diff --git a/tools/nym-lp-client/src/client.rs b/tools/nym-lp-client/src/client.rs index 6d35cb47a5..3be0bc18b2 100644 --- a/tools/nym-lp-client/src/client.rs +++ b/tools/nym-lp-client/src/client.rs @@ -32,6 +32,8 @@ use tracing::{debug, info, trace}; use crate::topology::{GatewayInfo, SpeedtestTopology}; use nym_ip_packet_requests::v8::request::IpPacketRequest; +use nym_lp::packet::version; +use nym_lp::peer::LpRemotePeer; use nym_sphinx::forwarding::packet::MixPacket; /// Conv ID for KCP - hash of source and destination addresses @@ -116,13 +118,17 @@ impl SpeedtestClient { self.gateway.lp_address ); - let client_ip = "0.0.0.0".parse()?; + let gw_peer = LpRemotePeer::new(self.gateway.identity, self.gateway.identity.to_x25519()?) + .with_key_digests( + self.gateway.kem_key_hashes.clone(), + self.gateway.signing_key_hashes.clone(), + ); - let mut lp_client = LpRegistrationClient::::new_with_default_psk( + let mut lp_client = LpRegistrationClient::::new_with_default_config( self.identity_keypair.clone(), - self.gateway.identity, + gw_peer, self.gateway.lp_address, - client_ip, + self.gateway.lp_version, ); let start = Instant::now(); @@ -159,13 +165,17 @@ impl SpeedtestClient { self.gateway.lp_address ); - let client_ip = "0.0.0.0".parse()?; + let gw_peer = LpRemotePeer::new(self.gateway.identity, self.gateway.identity.to_x25519()?) + .with_key_digests( + self.gateway.kem_key_hashes.clone(), + self.gateway.signing_key_hashes.clone(), + ); - let mut lp_client = LpRegistrationClient::::new_with_default_psk( + let mut lp_client = LpRegistrationClient::new_with_default_config( self.identity_keypair.clone(), - self.gateway.identity, + gw_peer, self.gateway.lp_address, - client_ip, + self.gateway.lp_version, ); let start = Instant::now(); diff --git a/tools/nym-lp-client/src/topology.rs b/tools/nym-lp-client/src/topology.rs index 9cb44206a1..3e0f76b6ff 100644 --- a/tools/nym-lp-client/src/topology.rs +++ b/tools/nym-lp-client/src/topology.rs @@ -6,14 +6,17 @@ #![allow(unused)] use anyhow::{anyhow, bail, Context, Result}; +use nym_api_requests::models::{LPHashFunction, LPKEM}; use nym_api_requests::nym_nodes::SkimmedNode; use nym_crypto::asymmetric::ed25519; use nym_http_api_client::UserAgent; +use nym_kkt_ciphersuite::{KEMKeyDigests, SignatureScheme, SigningKeyDigests, KEM}; use nym_sphinx_types::Node as SphinxNode; use nym_topology::{NymRouteProvider, NymTopology, NymTopologyMetadata}; use nym_validator_client::nym_api::NymApiClientExt; use rand::prelude::IteratorRandom; use rand::{CryptoRng, Rng}; +use std::collections::HashMap; use std::net::SocketAddr; use tracing::{debug, info}; use url::Url; @@ -27,6 +30,9 @@ const LP_DATA_PORT: u16 = 51264; #[derive(Debug, Clone)] pub struct GatewayInfo { pub identity: ed25519::PublicKey, + pub kem_key_hashes: HashMap, + pub signing_key_hashes: HashMap, + pub sphinx_key: nym_crypto::asymmetric::x25519::PublicKey, /// Mix host (IP:port for Sphinx mixing) pub mix_host: SocketAddr, @@ -34,6 +40,7 @@ pub struct GatewayInfo { pub lp_address: SocketAddr, /// LP data address (IP:51264) for Sphinx packets wrapped in LP pub lp_data_address: SocketAddr, + pub lp_version: u8, } /// Topology for routing Sphinx packets @@ -181,41 +188,42 @@ impl SpeedtestTopology { /// Extract gateway info for LP connections from a SkimmedNode fn gateway_info_from_skimmed(node: &SkimmedNode) -> Result { - let first_ip = node - .ip_addresses - .first() - .ok_or_else(|| anyhow!("node has no IP addresses"))?; - - // LP default control port - const LP_CONTROL_PORT: u16 = 41264; - - Ok(GatewayInfo { - identity: node.ed25519_identity_pubkey, - sphinx_key: node.x25519_sphinx_pubkey, - mix_host: SocketAddr::new(*first_ip, node.mix_port), - lp_address: SocketAddr::new(*first_ip, LP_CONTROL_PORT), - lp_data_address: SocketAddr::new(*first_ip, LP_DATA_PORT), - }) + todo!("insufficient information to convert into GatewayInfo") + // let first_ip = node + // .ip_addresses + // .first() + // .ok_or_else(|| anyhow!("node has no IP addresses"))?; + // + // // LP default control port + // const LP_CONTROL_PORT: u16 = 41264; + // + // Ok(GatewayInfo { + // identity: node.ed25519_identity_pubkey, + // sphinx_key: node.x25519_sphinx_pubkey, + // mix_host: SocketAddr::new(*first_ip, node.mix_port), + // lp_address: SocketAddr::new(*first_ip, LP_CONTROL_PORT), + // lp_data_address: SocketAddr::new(*first_ip, LP_DATA_PORT), + // }) } -#[cfg(test)] -mod tests { - use super::*; - - #[tokio::test] - #[ignore = "requires network access"] - async fn test_fetch_topology() { - let nym_api = Url::parse("https://validator.nymtech.net/api").unwrap(); - let topology = SpeedtestTopology::fetch(&nym_api).await.unwrap(); - - assert!(topology.gateway_count() > 0); - println!("Found {} gateways", topology.gateway_count()); - - let mut rng = rand::thread_rng(); - let gateway = topology.random_gateway(&mut rng).unwrap(); - println!("Selected gateway: {:?}", gateway.identity); - - let route = topology.random_route_to_gateway(&mut rng, gateway).unwrap(); - println!("Route has {} hops", route.len()); - } -} +// #[cfg(test)] +// mod tests { +// use super::*; +// +// #[tokio::test] +// #[ignore = "requires network access"] +// async fn test_fetch_topology() { +// let nym_api = Url::parse("https://validator.nymtech.net/api").unwrap(); +// let topology = SpeedtestTopology::fetch(&nym_api).await.unwrap(); +// +// assert!(topology.gateway_count() > 0); +// println!("Found {} gateways", topology.gateway_count()); +// +// let mut rng = rand::thread_rng(); +// let gateway = topology.random_gateway(&mut rng).unwrap(); +// println!("Selected gateway: {:?}", gateway.identity); +// +// let route = topology.random_route_to_gateway(&mut rng, gateway).unwrap(); +// println!("Route has {} hops", route.len()); +// } +// } diff --git a/tools/nym-nr-query/Cargo.toml b/tools/nym-nr-query/Cargo.toml index f5ec6f6475..da2d03e544 100644 --- a/tools/nym-nr-query/Cargo.toml +++ b/tools/nym-nr-query/Cargo.toml @@ -3,6 +3,7 @@ name = "nym-nr-query" version = "0.1.0" edition = "2021" license.workspace = true +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -10,10 +11,10 @@ license.workspace = true anyhow = { workspace = true } clap = { workspace = true, features = ["cargo", "derive"]} log = { workspace = true } -nym-bin-common = { path = "../../common/bin-common", features = ["output_format", "basic_tracing"] } -nym-network-defaults = { path = "../../common/network-defaults" } -nym-sdk = { path = "../../sdk/rust/nym-sdk" } -nym-service-providers-common = { path = "../../service-providers/common" } -nym-socks5-requests = { path = "../../common/socks5/requests" } +nym-bin-common = { workspace = true, features = ["output_format", "basic_tracing"] } +nym-network-defaults = { workspace = true } +nym-sdk = { workspace = true } +nym-service-providers-common = { workspace = true } +nym-socks5-requests = { workspace = true } serde = { workspace = true } tokio = { workspace = true, features = [ "net", "rt-multi-thread", "macros" ] } diff --git a/tools/nymvisor/Cargo.toml b/tools/nymvisor/Cargo.toml index 03914ea8c4..f3371670a9 100644 --- a/tools/nymvisor/Cargo.toml +++ b/tools/nymvisor/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "nymvisor" -version = "0.1.34" +version = "0.1.35" authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -32,10 +33,10 @@ thiserror = { workspace = true } tracing = { workspace = true } url = { workspace = true, features = ["serde"] } -nym-async-file-watcher = { path = "../../common/async-file-watcher" } -nym-bin-common = { path = "../../common/bin-common", features = ["output_format", "basic_tracing"] } -nym-config = { path = "../../common/config" } -nym-task = { path = "../../common/task"} +nym-async-file-watcher = { workspace = true } +nym-bin-common = { workspace = true, features = ["output_format", "basic_tracing"] } +nym-config = { workspace = true } +nym-task = { workspace = true} [dev-dependencies] tokio = { workspace = true, features = ["full"] } diff --git a/tools/ts-rs-cli/Cargo.toml b/tools/ts-rs-cli/Cargo.toml index 6c75876550..e2230dae3f 100644 --- a/tools/ts-rs-cli/Cargo.toml +++ b/tools/ts-rs-cli/Cargo.toml @@ -3,6 +3,7 @@ name = "ts-rs-cli" version = "0.1.0" edition = "2021" license.workspace = true +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -11,11 +12,11 @@ anyhow = { workspace = true } ts-rs = { workspace = true } walkdir = { workspace = true } -nym-validator-client = { path = "../../common/client-libs/validator-client", features = [ +nym-validator-client = { workspace = true, features = [ "generate-ts" ] } -nym-api-requests = { path = "../../nym-api/nym-api-requests", features = ["generate-ts"] } -nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract", features = ["generate-ts"] } -nym-vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract", features = ["generate-ts"] } -nym-types = { path = "../../common/types", features = ["generate-ts"] } +nym-api-requests = { workspace = true, features = ["generate-ts"] } +nym-mixnet-contract-common = { workspace = true, features = ["generate-ts"] } +nym-vesting-contract-common = { workspace = true, features = ["generate-ts"] } +nym-types = { workspace = true, features = ["generate-ts"] } nym-wallet-types = { path = "../../nym-wallet/nym-wallet-types", features = ["generate-ts"] } diff --git a/tools/ts-rs-cli/src/main.rs b/tools/ts-rs-cli/src/main.rs index 9c80766327..546d6bd316 100644 --- a/tools/ts-rs-cli/src/main.rs +++ b/tools/ts-rs-cli/src/main.rs @@ -1,7 +1,7 @@ #![allow(deprecated)] use nym_api_requests::models::{ - AnnotationResponse, DeclaredRoles, DescribedNodeType, GatewayCoreStatusResponse, + AnnotationResponse, DeclaredRolesV1, DescribedNodeTypeV1, GatewayCoreStatusResponse, HistoricalPerformanceResponse, HistoricalUptimeResponse, MixnodeCoreStatusResponse, MixnodeStatus, MixnodeStatusResponse, NodeAnnotation, NodeDatePerformanceResponse, NodePerformanceResponse, PerformanceHistoryResponse, StakeSaturationResponse, @@ -152,8 +152,8 @@ fn main() -> anyhow::Result<()> { do_export!(UptimeHistoryResponse); do_export!(HistoricalUptimeResponse); do_export!(HistoricalPerformanceResponse); - do_export!(DescribedNodeType); - do_export!(DeclaredRoles); + do_export!(DescribedNodeTypeV1); + do_export!(DeclaredRolesV1); do_export!(PaginatedResponse); do_export!(Pagination); diff --git a/wasm/client/Cargo.toml b/wasm/client/Cargo.toml index 4b5d7d19e9..3bc15e7ade 100644 --- a/wasm/client/Cargo.toml +++ b/wasm/client/Cargo.toml @@ -11,6 +11,7 @@ license = "Apache-2.0" repository = "https://github.com/nymtech/nym" description = "A webassembly client which can be used to interact with the the Nym privacy platform. Wasm is used for Sphinx packet generation." rust-version = "1.85" +publish = false [lib] crate-type = ["cdylib", "rlib"] @@ -32,12 +33,13 @@ thiserror = { workspace = true } tsify = { workspace = true, features = ["js"] } web-sys = { workspace = true } -nym-bin-common = { path = "../../common/bin-common" } -wasm-client-core = { path = "../../common/wasm/client-core" } -wasm-utils = { path = "../../common/wasm/utils" } -nym-gateway-requests = { path = "../../common/gateway-requests" } +nym-bin-common = { workspace = true } +nym-wasm-client-core = { workspace = true } +nym-wasm-utils = { workspace = true } +nym-gateway-requests = { workspace = true } + +nym-node-tester-utils = { workspace = true, optional = true } -nym-node-tester-utils = { path = "../../common/node-tester-utils", optional = true } nym-node-tester-wasm = { path = "../node-tester", optional = true } tokio_with_wasm = { workspace = true, features = ["full"] } diff --git a/wasm/client/src/client.rs b/wasm/client/src/client.rs index 8c006b7f09..7d6df14b35 100644 --- a/wasm/client/src/client.rs +++ b/wasm/client/src/client.rs @@ -14,30 +14,30 @@ use crate::response_pusher::ResponsePusher; use js_sys::Promise; use nym_bin_common::bin_info; use nym_gateway_requests::ClientRequest; +use nym_wasm_client_core::client::base_client::storage::GatewaysDetailsStore; +use nym_wasm_client_core::client::{ + base_client::{BaseClientBuilder, ClientInput, ClientOutput, ClientState}, + inbound_messages::InputMessage, +}; +use nym_wasm_client_core::config::r#override::DebugWasmOverride; +use nym_wasm_client_core::helpers::{ + add_gateway, generate_new_client_keys, parse_recipient, parse_sender_tag, +}; +use nym_wasm_client_core::nym_task::connections::TransmissionLane; +use nym_wasm_client_core::nym_task::ShutdownTracker; +use nym_wasm_client_core::storage::core_client_traits::FullWasmClientStorage; +use nym_wasm_client_core::storage::wasm_client_traits::WasmClientStorage; +use nym_wasm_client_core::storage::ClientStorage; +use nym_wasm_client_core::topology::{SerializableTopologyExt, WasmFriendlyNymTopology}; +use nym_wasm_client_core::{IdentityKey, NymTopology, PacketType, QueryReqwestRpcNyxdClient}; +use nym_wasm_utils::error::PromisableResult; +use nym_wasm_utils::{check_promise_result, console_error, console_log}; use serde::{Deserialize, Serialize}; use std::sync::Arc; use tokio_with_wasm::sync::mpsc; use tsify::Tsify; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::future_to_promise; -use wasm_client_core::client::base_client::storage::GatewaysDetailsStore; -use wasm_client_core::client::{ - base_client::{BaseClientBuilder, ClientInput, ClientOutput, ClientState}, - inbound_messages::InputMessage, -}; -use wasm_client_core::config::r#override::DebugWasmOverride; -use wasm_client_core::helpers::{ - add_gateway, generate_new_client_keys, parse_recipient, parse_sender_tag, -}; -use wasm_client_core::nym_task::connections::TransmissionLane; -use wasm_client_core::nym_task::ShutdownTracker; -use wasm_client_core::storage::core_client_traits::FullWasmClientStorage; -use wasm_client_core::storage::wasm_client_traits::WasmClientStorage; -use wasm_client_core::storage::ClientStorage; -use wasm_client_core::topology::{SerializableTopologyExt, WasmFriendlyNymTopology}; -use wasm_client_core::{IdentityKey, NymTopology, PacketType, QueryReqwestRpcNyxdClient}; -use wasm_utils::error::PromisableResult; -use wasm_utils::{check_promise_result, console_error, console_log}; #[cfg(feature = "node-tester")] use crate::helpers::{NymClientTestRequest, WasmTopologyTestExt}; diff --git a/wasm/client/src/config.rs b/wasm/client/src/config.rs index 9bb19ad8c1..06249c6bd4 100644 --- a/wasm/client/src/config.rs +++ b/wasm/client/src/config.rs @@ -8,10 +8,12 @@ #![allow(clippy::empty_docs)] use crate::error::WasmClientError; +use nym_wasm_client_core::config::{ + new_base_client_config, BaseClientConfig, ConfigDebug, DebugWasm, +}; use serde::{Deserialize, Serialize}; use tsify::Tsify; use wasm_bindgen::prelude::*; -use wasm_client_core::config::{new_base_client_config, BaseClientConfig, ConfigDebug, DebugWasm}; pub const DEFAULT_CLIENT_ID: &str = "nym-mixnet-client"; diff --git a/wasm/client/src/encoded_payload_helper.rs b/wasm/client/src/encoded_payload_helper.rs index 77294f624c..7b2080a2cd 100644 --- a/wasm/client/src/encoded_payload_helper.rs +++ b/wasm/client/src/encoded_payload_helper.rs @@ -325,10 +325,10 @@ mod tests { message.extend_from_slice(serialized_metadata.as_bytes()); message.extend_from_slice(&payload_data); - wasm_utils::console_log!("message length: {}", message.len()); - wasm_utils::console_log!("metadata length: {}", metadata_length); - wasm_utils::console_log!("payload length: {}", payload_data.len()); - wasm_utils::console_log!( + nym_wasm_utils::console_log!("message length: {}", message.len()); + nym_wasm_utils::console_log!("metadata length: {}", metadata_length); + nym_wasm_utils::console_log!("payload length: {}", payload_data.len()); + nym_wasm_utils::console_log!( "8 + metadata_length + payload_length = {}", payload_data.len() + metadata_length as usize + 8 ); diff --git a/wasm/client/src/error.rs b/wasm/client/src/error.rs index a8d7c2190b..48598833b2 100644 --- a/wasm/client/src/error.rs +++ b/wasm/client/src/error.rs @@ -1,11 +1,11 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use nym_wasm_client_core::error::WasmCoreError; +use nym_wasm_client_core::topology::WasmTopologyError; +use nym_wasm_client_core::ClientCoreError; +use nym_wasm_utils::wasm_error; use thiserror::Error; -use wasm_client_core::error::WasmCoreError; -use wasm_client_core::topology::WasmTopologyError; -use wasm_client_core::ClientCoreError; -use wasm_utils::wasm_error; #[cfg(feature = "node-tester")] use nym_node_tester_utils::error::NetworkTestingError; diff --git a/wasm/client/src/helpers.rs b/wasm/client/src/helpers.rs index b6cdba3d33..d568d76250 100644 --- a/wasm/client/src/helpers.rs +++ b/wasm/client/src/helpers.rs @@ -2,16 +2,16 @@ // SPDX-License-Identifier: Apache-2.0 use js_sys::Promise; +use nym_wasm_client_core::client::base_client::{ClientInput, ClientState}; +use nym_wasm_client_core::client::inbound_messages::InputMessage; +use nym_wasm_client_core::error::WasmCoreError; +use nym_wasm_client_core::topology::{Role, WasmFriendlyNymTopology}; +use nym_wasm_client_core::NymTopology; +use nym_wasm_utils::error::simple_js_error; +use nym_wasm_utils::{check_promise_result, console_log}; use std::sync::Arc; use wasm_bindgen::JsValue; use wasm_bindgen_futures::future_to_promise; -use wasm_client_core::client::base_client::{ClientInput, ClientState}; -use wasm_client_core::client::inbound_messages::InputMessage; -use wasm_client_core::error::WasmCoreError; -use wasm_client_core::topology::{Role, WasmFriendlyNymTopology}; -use wasm_client_core::NymTopology; -use wasm_utils::error::simple_js_error; -use wasm_utils::{check_promise_result, console_log}; #[cfg(feature = "node-tester")] use nym_node_tester_wasm::types::{NodeTestMessage, WasmTestMessageExt}; diff --git a/wasm/client/src/lib.rs b/wasm/client/src/lib.rs index ee5ff5a1ba..3fe894a865 100644 --- a/wasm/client/src/lib.rs +++ b/wasm/client/src/lib.rs @@ -14,17 +14,17 @@ mod helpers; #[cfg(target_arch = "wasm32")] mod response_pusher; #[cfg(target_arch = "wasm32")] -use wasm_bindgen::prelude::*; +use nym_wasm_utils::set_panic_hook; #[cfg(target_arch = "wasm32")] -use wasm_utils::set_panic_hook; +use wasm_bindgen::prelude::*; #[wasm_bindgen(start)] #[cfg(target_arch = "wasm32")] pub fn main_js() { - wasm_utils::console_log!("[rust main]: setting panic hook"); + nym_wasm_utils::console_log!("[rust main]: setting panic hook"); set_panic_hook(); - wasm_utils::console_log!("[rust main]: rust module loaded"); - wasm_utils::console_log!( + nym_wasm_utils::console_log!("[rust main]: rust module loaded"); + nym_wasm_utils::console_log!( "wasm client version used: {}", nym_bin_common::bin_info_owned!() ); diff --git a/wasm/client/src/response_pusher.rs b/wasm/client/src/response_pusher.rs index e0a795a79b..be2ba38312 100644 --- a/wasm/client/src/response_pusher.rs +++ b/wasm/client/src/response_pusher.rs @@ -4,13 +4,13 @@ use futures::channel::mpsc; use futures::StreamExt; use js_sys::Uint8Array; -use wasm_bindgen::JsValue; -use wasm_bindgen_futures::spawn_local; -use wasm_client_core::client::base_client::ClientOutput; -use wasm_client_core::client::received_buffer::{ +use nym_wasm_client_core::client::base_client::ClientOutput; +use nym_wasm_client_core::client::received_buffer::{ ReceivedBufferMessage, ReceivedBufferRequestSender, ReconstructedMessagesReceiver, }; -use wasm_utils::console_error; +use nym_wasm_utils::console_error; +use wasm_bindgen::JsValue; +use wasm_bindgen_futures::spawn_local; pub(crate) struct ResponsePusher { reconstructed_receiver: ReconstructedMessagesReceiver, diff --git a/wasm/full-nym-wasm/Cargo.toml b/wasm/full-nym-wasm/Cargo.toml index 3e98e42d24..f3a7357e6c 100644 --- a/wasm/full-nym-wasm/Cargo.toml +++ b/wasm/full-nym-wasm/Cargo.toml @@ -7,6 +7,7 @@ keywords = ["nym", "sphinx", "wasm", "webassembly", "privacy"] license = "Apache-2.0" repository = "https://github.com/nymtech/nym" rust-version = "1.85" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -14,9 +15,10 @@ rust-version = "1.85" crate-type = ["cdylib", "rlib"] [dependencies] -nym-client-wasm = { path = "../client", optional = true } +nym-client-wasm = { workspace = true, optional = true } + nym-node-tester-wasm = { path = "../node-tester", optional = true } -mix-fetch-wasm = { path = "../mix-fetch", optional = true } +mix-fetch-wasm = { workspace = true, optional = true } [features] default = ["client", "node-tester"] @@ -26,4 +28,3 @@ mix-fetch = ["mix-fetch-wasm"] [package.metadata.wasm-pack.profile.release] wasm-opt = false - diff --git a/wasm/mix-fetch/Cargo.toml b/wasm/mix-fetch/Cargo.toml index 00d91b2e25..f1d81694ad 100644 --- a/wasm/mix-fetch/Cargo.toml +++ b/wasm/mix-fetch/Cargo.toml @@ -7,6 +7,7 @@ keywords = ["nym", "fetch", "wasm", "webassembly", "privacy"] license = "Apache-2.0" repository = "https://github.com/nymtech/nym" rust-version = "1.85" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -27,14 +28,14 @@ wasm-bindgen-futures = { workspace = true } thiserror = { workspace = true } tsify = { workspace = true, features = ["js"] } -nym-bin-common = { path = "../../common/bin-common" } -nym-http-api-client = { path = "../../common/http-api-client" } -nym-socks5-requests = { path = "../../common/socks5/requests" } -nym-ordered-buffer = { path = "../../common/socks5/ordered-buffer" } -nym-service-providers-common = { path = "../../service-providers/common" } -nym-validator-client = { path = "../../common/client-libs/validator-client", default-features = false } -wasm-client-core = { path = "../../common/wasm/client-core" } -wasm-utils = { path = "../../common/wasm/utils" } +nym-bin-common = { workspace = true } +nym-http-api-client = { workspace = true } +nym-socks5-requests = { workspace = true } +nym-ordered-buffer = { workspace = true } +nym-service-providers-common = { workspace = true } +nym-validator-client = { workspace = true, default-features = false } +nym-wasm-client-core = { workspace = true } +nym-wasm-utils = { workspace = true } [package.metadata.wasm-pack.profile.release] wasm-opt = false diff --git a/wasm/mix-fetch/src/active_requests.rs b/wasm/mix-fetch/src/active_requests.rs index 3d4e8a5f31..72fe6ac9d9 100644 --- a/wasm/mix-fetch/src/active_requests.rs +++ b/wasm/mix-fetch/src/active_requests.rs @@ -6,11 +6,11 @@ use crate::go_bridge::{goWasmCloseRemoteSocket, goWasmInjectConnError, goWasmInj use crate::RequestId; use nym_ordered_buffer::OrderedMessageBuffer; use nym_socks5_requests::SocketData; +use nym_wasm_utils::{console_error, console_log}; use rand::{thread_rng, RngCore}; use std::collections::HashMap; use std::sync::Arc; use tokio::sync::Mutex; -use wasm_utils::{console_error, console_log}; #[derive(Clone, Default)] pub struct ActiveRequests { diff --git a/wasm/mix-fetch/src/client.rs b/wasm/mix-fetch/src/client.rs index 736c82c413..6fd5d642ac 100644 --- a/wasm/mix-fetch/src/client.rs +++ b/wasm/mix-fetch/src/client.rs @@ -11,22 +11,22 @@ use crate::{config, RequestId}; use js_sys::Promise; use nym_bin_common::bin_info; use nym_socks5_requests::RemoteAddress; +use nym_wasm_client_core::client::base_client::storage::GatewaysDetailsStore; +use nym_wasm_client_core::client::base_client::{BaseClientBuilder, ClientInput, ClientOutput}; +use nym_wasm_client_core::client::inbound_messages::InputMessage; +use nym_wasm_client_core::helpers::{add_gateway, generate_new_client_keys}; +use nym_wasm_client_core::nym_task::connections::TransmissionLane; +use nym_wasm_client_core::nym_task::ShutdownTracker; +use nym_wasm_client_core::storage::core_client_traits::FullWasmClientStorage; +use nym_wasm_client_core::storage::wasm_client_traits::WasmClientStorage; +use nym_wasm_client_core::storage::ClientStorage; +use nym_wasm_client_core::{IdentityKey, QueryReqwestRpcNyxdClient, Recipient}; +use nym_wasm_utils::console_log; +use nym_wasm_utils::error::PromisableResult; use std::sync::atomic::{AtomicBool, Ordering}; use tokio::sync::Mutex; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::future_to_promise; -use wasm_client_core::client::base_client::storage::GatewaysDetailsStore; -use wasm_client_core::client::base_client::{BaseClientBuilder, ClientInput, ClientOutput}; -use wasm_client_core::client::inbound_messages::InputMessage; -use wasm_client_core::helpers::{add_gateway, generate_new_client_keys}; -use wasm_client_core::nym_task::connections::TransmissionLane; -use wasm_client_core::nym_task::ShutdownTracker; -use wasm_client_core::storage::core_client_traits::FullWasmClientStorage; -use wasm_client_core::storage::wasm_client_traits::WasmClientStorage; -use wasm_client_core::storage::ClientStorage; -use wasm_client_core::{IdentityKey, QueryReqwestRpcNyxdClient, Recipient}; -use wasm_utils::console_log; -use wasm_utils::error::PromisableResult; #[wasm_bindgen] pub struct MixFetchClient { diff --git a/wasm/mix-fetch/src/config.rs b/wasm/mix-fetch/src/config.rs index 621e041913..9d312ac280 100644 --- a/wasm/mix-fetch/src/config.rs +++ b/wasm/mix-fetch/src/config.rs @@ -10,13 +10,15 @@ #![allow(clippy::empty_docs)] use crate::error::MixFetchError; +use nym_wasm_client_core::config::{ + new_base_client_config, BaseClientConfig, ConfigDebug, DebugWasm, +}; +use nym_wasm_client_core::helpers::parse_recipient; +use nym_wasm_client_core::Recipient; use serde::{Deserialize, Serialize}; use std::time::Duration; use tsify::Tsify; use wasm_bindgen::prelude::*; -use wasm_client_core::config::{new_base_client_config, BaseClientConfig, ConfigDebug, DebugWasm}; -use wasm_client_core::helpers::parse_recipient; -use wasm_client_core::Recipient; const DEFAULT_MIX_FETCH_TIMEOUT: Duration = Duration::from_secs(5); const DEFAULT_MIX_FETCH_ID: &str = "_default-nym-mix-fetch"; diff --git a/wasm/mix-fetch/src/error.rs b/wasm/mix-fetch/src/error.rs index c3347acddf..ceb449df04 100644 --- a/wasm/mix-fetch/src/error.rs +++ b/wasm/mix-fetch/src/error.rs @@ -5,10 +5,10 @@ use crate::RequestId; use nym_ordered_buffer::OrderedMessageError; use nym_socks5_requests::ConnectionError; use nym_validator_client::nym_api::error::NymAPIError; +use nym_wasm_client_core::error::WasmCoreError; +use nym_wasm_client_core::ClientCoreError; +use nym_wasm_utils::wasm_error; use thiserror::Error; -use wasm_client_core::error::WasmCoreError; -use wasm_client_core::ClientCoreError; -use wasm_utils::wasm_error; #[derive(Debug, Error)] pub enum MixFetchError { diff --git a/wasm/mix-fetch/src/fetch.rs b/wasm/mix-fetch/src/fetch.rs index 83d7caaff1..a03e8f70fb 100644 --- a/wasm/mix-fetch/src/fetch.rs +++ b/wasm/mix-fetch/src/fetch.rs @@ -12,15 +12,15 @@ use crate::config::{MixFetchConfig, MixFetchConfigOpts, MixFetchDebugOverride}; use crate::error::MixFetchError; use crate::helpers::get_network_requester; use js_sys::Promise; +use nym_wasm_client_core::config::r#override::DebugWasmOverride; +use nym_wasm_utils::console_log; +use nym_wasm_utils::error::PromisableResultError; use serde::{Deserialize, Serialize}; use std::sync::OnceLock; use tsify::Tsify; use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::JsValue; use wasm_bindgen_futures::future_to_promise; -use wasm_client_core::config::r#override::DebugWasmOverride; -use wasm_utils::console_log; -use wasm_utils::error::PromisableResultError; pub type RequestId = u64; diff --git a/wasm/mix-fetch/src/go_bridge.rs b/wasm/mix-fetch/src/go_bridge.rs index 6ea8f0704d..086001c0a5 100644 --- a/wasm/mix-fetch/src/go_bridge.rs +++ b/wasm/mix-fetch/src/go_bridge.rs @@ -4,9 +4,9 @@ use crate::error::MixFetchError; use crate::mix_fetch_client; use js_sys::Promise; +use nym_wasm_utils::error::{simple_rejected_promise, PromisableResult}; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::future_to_promise; -use wasm_utils::error::{simple_rejected_promise, PromisableResult}; /// Called by go runtime whenever local connection produces any data that has to be sent to the remote. // diff --git a/wasm/mix-fetch/src/helpers.rs b/wasm/mix-fetch/src/helpers.rs index 4f6daf85b1..9476d61715 100644 --- a/wasm/mix-fetch/src/helpers.rs +++ b/wasm/mix-fetch/src/helpers.rs @@ -5,9 +5,9 @@ use crate::error::MixFetchError; use crate::error::MixFetchError::NoNetworkRequesters; use nym_http_api_client::Client; use nym_validator_client::nym_api::NymApiClientExt; +use nym_wasm_utils::console_log; use rand::seq::SliceRandom; use rand::thread_rng; -use wasm_utils::console_log; // since this client is temporary (and will be properly integrated into nym-api eventually), // we're using hardcoded URL for mainnet diff --git a/wasm/mix-fetch/src/lib.rs b/wasm/mix-fetch/src/lib.rs index cbb9d151d4..0018748027 100644 --- a/wasm/mix-fetch/src/lib.rs +++ b/wasm/mix-fetch/src/lib.rs @@ -28,11 +28,11 @@ use wasm_bindgen::prelude::*; #[wasm_bindgen(start)] #[cfg(target_arch = "wasm32")] pub fn main() { - wasm_utils::console_log!("[rust main]: rust module loaded"); - wasm_utils::console_log!( + nym_wasm_utils::console_log!("[rust main]: rust module loaded"); + nym_wasm_utils::console_log!( "mix fetch version used: {}", nym_bin_common::bin_info_owned!() ); - wasm_utils::console_log!("[rust main]: setting panic hook"); - wasm_utils::set_panic_hook(); + nym_wasm_utils::console_log!("[rust main]: setting panic hook"); + nym_wasm_utils::set_panic_hook(); } diff --git a/wasm/mix-fetch/src/request_writer.rs b/wasm/mix-fetch/src/request_writer.rs index fa55b746d2..12138bb005 100644 --- a/wasm/mix-fetch/src/request_writer.rs +++ b/wasm/mix-fetch/src/request_writer.rs @@ -6,13 +6,13 @@ use futures::channel::mpsc; use futures::StreamExt; use nym_service_providers_common::interface::ResponseContent; use nym_socks5_requests::{Socks5ProviderResponse, Socks5ResponseContent}; -use wasm_bindgen_futures::spawn_local; -use wasm_client_core::client::base_client::ClientOutput; -use wasm_client_core::client::received_buffer::{ +use nym_wasm_client_core::client::base_client::ClientOutput; +use nym_wasm_client_core::client::received_buffer::{ ReceivedBufferMessage, ReceivedBufferRequestSender, ReconstructedMessagesReceiver, }; -use wasm_client_core::ReconstructedMessage; -use wasm_utils::console_error; +use nym_wasm_client_core::ReconstructedMessage; +use nym_wasm_utils::console_error; +use wasm_bindgen_futures::spawn_local; pub(crate) struct RequestWriter { reconstructed_receiver: ReconstructedMessagesReceiver, diff --git a/wasm/mix-fetch/src/socks_helpers.rs b/wasm/mix-fetch/src/socks_helpers.rs index 827e8ac120..8dffb0ad2e 100644 --- a/wasm/mix-fetch/src/socks_helpers.rs +++ b/wasm/mix-fetch/src/socks_helpers.rs @@ -5,7 +5,7 @@ use nym_service_providers_common::interface::ProviderInterfaceVersion; use nym_socks5_requests::{ ConnectionId, RemoteAddress, SocketData, Socks5ProtocolVersion, Socks5ProviderRequest, }; -use wasm_client_core::Recipient; +use nym_wasm_client_core::Recipient; pub(crate) const PROVIDER_INTERFACE_VERSION: ProviderInterfaceVersion = ProviderInterfaceVersion::new_current(); diff --git a/wasm/node-tester/Cargo.toml b/wasm/node-tester/Cargo.toml index dd1fb9fded..8a61eb06ab 100644 --- a/wasm/node-tester/Cargo.toml +++ b/wasm/node-tester/Cargo.toml @@ -7,6 +7,7 @@ keywords = ["nym", "sphinx", "webassembly", "privacy", "tester"] license = "Apache-2.0" repository = "https://github.com/nymtech/nym" rust-version = "1.85" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -27,9 +28,9 @@ tsify = { workspace = true, features = ["js"] } wasmtimer = { workspace = true } -nym-node-tester-utils = { path = "../../common/node-tester-utils" } -wasm-client-core = { path = "../../common/wasm/client-core" } -wasm-utils = { path = "../../common/wasm/utils" } +nym-node-tester-utils = { workspace = true } +nym-wasm-client-core = { workspace = true } +nym-wasm-utils = { workspace = true } [package.metadata.wasm-pack.profile.release] wasm-opt = false diff --git a/wasm/node-tester/src/ephemeral_receiver.rs b/wasm/node-tester/src/ephemeral_receiver.rs index a7a9545de6..4ad0f9d041 100644 --- a/wasm/node-tester/src/ephemeral_receiver.rs +++ b/wasm/node-tester/src/ephemeral_receiver.rs @@ -6,10 +6,10 @@ use futures::StreamExt; use nym_node_tester_utils::processor::Received; use nym_node_tester_utils::receiver::ReceivedReceiver; use nym_node_tester_utils::FragmentIdentifier; +use nym_wasm_utils::{console_error, console_log, console_warn}; use std::collections::HashSet; use std::time::Duration; use tokio::sync::MutexGuard as AsyncMutexGuard; -use wasm_utils::{console_error, console_log, console_warn}; use wasmtimer::tokio::sleep; pub(crate) struct EphemeralTestReceiver<'a> { diff --git a/wasm/node-tester/src/error.rs b/wasm/node-tester/src/error.rs index f5679833d8..65bf60ecbb 100644 --- a/wasm/node-tester/src/error.rs +++ b/wasm/node-tester/src/error.rs @@ -2,11 +2,11 @@ // SPDX-License-Identifier: Apache-2.0 use nym_node_tester_utils::error::NetworkTestingError; +use nym_wasm_client_core::error::WasmCoreError; +use nym_wasm_client_core::topology::WasmTopologyError; +use nym_wasm_client_core::{ClientCoreError, GatewayClientError}; +use nym_wasm_utils::wasm_error; use thiserror::Error; -use wasm_client_core::error::WasmCoreError; -use wasm_client_core::topology::WasmTopologyError; -use wasm_client_core::{ClientCoreError, GatewayClientError}; -use wasm_utils::wasm_error; #[derive(Debug, Error)] pub enum NodeTesterError { diff --git a/wasm/node-tester/src/helpers.rs b/wasm/node-tester/src/helpers.rs index fa7790e2f6..6562ae65ec 100644 --- a/wasm/node-tester/src/helpers.rs +++ b/wasm/node-tester/src/helpers.rs @@ -10,12 +10,12 @@ use crate::types::WasmTestMessageExt; use js_sys::Promise; use nym_node_tester_utils::processor::Received; use nym_node_tester_utils::receiver::ReceivedReceiver; +use nym_wasm_utils::console_warn; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use tokio::sync::{Mutex as AsyncMutex, MutexGuard as AsyncMutexGuard}; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::future_to_promise; -use wasm_utils::console_warn; #[derive(Clone)] pub(super) struct ReceivedReceiverWrapper(Arc>>); diff --git a/wasm/node-tester/src/lib.rs b/wasm/node-tester/src/lib.rs index f2cdee034d..0828ecbc43 100644 --- a/wasm/node-tester/src/lib.rs +++ b/wasm/node-tester/src/lib.rs @@ -13,4 +13,4 @@ pub mod tester; pub mod types; #[cfg(target_arch = "wasm32")] -pub use wasm_client_core::set_panic_hook; +pub use nym_wasm_client_core::set_panic_hook; diff --git a/wasm/node-tester/src/tester.rs b/wasm/node-tester/src/tester.rs index 599e9ce3f9..f525ad1c77 100644 --- a/wasm/node-tester/src/tester.rs +++ b/wasm/node-tester/src/tester.rs @@ -15,6 +15,21 @@ use js_sys::Promise; use nym_node_tester_utils::receiver::SimpleMessageReceiver; use nym_node_tester_utils::tester::LegacyMixLayer; use nym_node_tester_utils::{NodeTester, PacketSize, PreparedFragment}; +use nym_wasm_client_core::client::base_client::storage::gateways_storage::GatewayDetails; +use nym_wasm_client_core::client::mix_traffic::transceiver::PacketRouter; +use nym_wasm_client_core::helpers::{ + current_network_topology_async, setup_from_topology, EphemeralCredentialStorage, +}; +use nym_wasm_client_core::nym_task::ShutdownManager; +use nym_wasm_client_core::storage::ClientStorage; +use nym_wasm_client_core::topology::WasmFriendlyNymTopology; +use nym_wasm_client_core::{ + nym_task, BandwidthController, ClientKeys, ClientStatsSender, GatewayClient, + GatewayClientConfig, GatewayConfig, IdentityKey, InitialisationResult, NodeIdentity, + NymTopology, QueryReqwestRpcNyxdClient, Recipient, +}; +use nym_wasm_utils::check_promise_result; +use nym_wasm_utils::error::PromisableResult; use rand::rngs::OsRng; use serde::{Deserialize, Serialize}; use std::collections::HashSet; @@ -25,21 +40,6 @@ use tokio::sync::Mutex as AsyncMutex; use tsify::Tsify; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::future_to_promise; -use wasm_client_core::client::base_client::storage::gateways_storage::GatewayDetails; -use wasm_client_core::client::mix_traffic::transceiver::PacketRouter; -use wasm_client_core::helpers::{ - current_network_topology_async, setup_from_topology, EphemeralCredentialStorage, -}; -use wasm_client_core::nym_task::ShutdownManager; -use wasm_client_core::storage::ClientStorage; -use wasm_client_core::topology::WasmFriendlyNymTopology; -use wasm_client_core::{ - nym_task, BandwidthController, ClientKeys, ClientStatsSender, GatewayClient, - GatewayClientConfig, GatewayConfig, IdentityKey, InitialisationResult, NodeIdentity, - NymTopology, QueryReqwestRpcNyxdClient, Recipient, -}; -use wasm_utils::check_promise_result; -use wasm_utils::error::PromisableResult; pub const NODE_TESTER_ID: &str = "_nym-node-tester"; diff --git a/wasm/node-tester/src/types.rs b/wasm/node-tester/src/types.rs index e1a5dcf766..2bcbe84395 100644 --- a/wasm/node-tester/src/types.rs +++ b/wasm/node-tester/src/types.rs @@ -2,10 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 use nym_node_tester_utils::TestMessage; +use nym_wasm_utils::console_log; use serde::{Deserialize, Serialize}; use std::fmt::{Display, Formatter}; use wasm_bindgen::prelude::wasm_bindgen; -use wasm_utils::console_log; pub type NodeTestMessage = TestMessage; diff --git a/wasm/zknym-lib/Cargo.toml b/wasm/zknym-lib/Cargo.toml index 572e26c6bd..b9d086535e 100644 --- a/wasm/zknym-lib/Cargo.toml +++ b/wasm/zknym-lib/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "zknym-lib" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -30,12 +31,12 @@ zeroize.workspace = true rand = { workspace = true } -nym-bin-common = { path = "../../common/bin-common" } -nym-compact-ecash = { path = "../../common/nym_offline_compact_ecash" } -nym-credentials = { path = "../../common/credentials" } -nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] } -nym-http-api-client = { path = "../../common/http-api-client" } -wasm-utils = { path = "../../common/wasm/utils" } +nym-bin-common = { workspace = true } +nym-compact-ecash = { workspace = true } +nym-credentials = { workspace = true } +nym-crypto = { workspace = true, features = ["asymmetric", "rand"] } +nym-http-api-client = { workspace = true } +nym-wasm-utils = { workspace = true } [dev-dependencies] anyhow = { workspace = true } @@ -43,4 +44,4 @@ tokio = { workspace = true, features = ["full"] } [package.metadata.wasm-pack.profile.release] -wasm-opt = false \ No newline at end of file +wasm-opt = false diff --git a/wasm/zknym-lib/src/bandwidth_voucher.rs b/wasm/zknym-lib/src/bandwidth_voucher.rs index d64fdd58f3..7c5307ad7f 100644 --- a/wasm/zknym-lib/src/bandwidth_voucher.rs +++ b/wasm/zknym-lib/src/bandwidth_voucher.rs @@ -19,7 +19,7 @@ // use std::collections::HashMap; // use tsify::Tsify; // use wasm_bindgen::prelude::wasm_bindgen; -// use wasm_utils::console_error; +// use nym_wasm_utils::console_error; // use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing}; // // // tiny 'hacks' to just allow passing responses from vpn-api queries diff --git a/wasm/zknym-lib/src/error.rs b/wasm/zknym-lib/src/error.rs index b06ca52ef2..31b8091927 100644 --- a/wasm/zknym-lib/src/error.rs +++ b/wasm/zknym-lib/src/error.rs @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 use nym_http_api_client::HttpClientError; +use nym_wasm_utils::wasm_error; use thiserror::Error; -use wasm_utils::wasm_error; #[derive(Debug, Error)] pub enum ZkNymError { diff --git a/wasm/zknym-lib/src/lib.rs b/wasm/zknym-lib/src/lib.rs index 4d0b342232..7bff719be4 100644 --- a/wasm/zknym-lib/src/lib.rs +++ b/wasm/zknym-lib/src/lib.rs @@ -23,8 +23,8 @@ pub(crate) mod vpn_api_client; #[wasm_bindgen(start)] // #[cfg(target_arch = "wasm32")] pub fn main() { - wasm_utils::console_log!("[rust main]: rust module loaded"); - wasm_utils::console_log!( + nym_wasm_utils::console_log!("[rust main]: rust module loaded"); + nym_wasm_utils::console_log!( "wasm zk-nym version used: {}", nym_bin_common::bin_info_owned!() );